Skip to content

Commit e2cca9c

Browse files
authored
[PLAT-111995] detect and prevent polluted data return to user (#51)
2 parents 40c085e + 1ec9240 commit e2cca9c

File tree

5 files changed

+126
-3
lines changed

5 files changed

+126
-3
lines changed

pkg/dedup/merge_iter.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,17 @@ func (m *mergedSeriesIterator) Seek(t int64) chunkenc.ValueType {
7777
}
7878
}
7979
currT := it.AtT()
80-
if currT >= t && currT < picked {
81-
picked = currT
82-
m.lastIter = it
80+
if currT >= t {
81+
if currT < picked {
82+
picked = currT
83+
m.lastIter = it
84+
} else if currT == picked {
85+
_, currV := it.At()
86+
_, pickedV := m.lastIter.At()
87+
if currV < pickedV {
88+
m.lastIter = it
89+
}
90+
}
8391
}
8492
}
8593
if picked == math.MaxInt64 {

pkg/dedup/merge_iter_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,41 @@ func TestMergedSeriesIterator(t *testing.T) {
175175
},
176176
},
177177
},
178+
{
179+
name: "Avoid corrupt Values",
180+
input: []series{
181+
{
182+
lset: labels.Labels{{Name: "a", Value: "5"}, {Name: "c", Value: "6"}},
183+
samples: []sample{{10000, 1}, {20000, 23492}, {30000, 3}, {50000, 5}},
184+
}, {
185+
lset: labels.Labels{{Name: "a", Value: "5"}, {Name: "c", Value: "6"}},
186+
samples: []sample{{10000, 1}, {20000, 2}, {30000, 3}, {50000, 5}},
187+
}, {
188+
lset: labels.Labels{{Name: "a", Value: "5"}, {Name: "c", Value: "6"}},
189+
samples: []sample{{10000, 1}, {20000, 2}, {30000, 3}, {50000, 5}},
190+
},
191+
{
192+
lset: labels.Labels{{Name: "b", Value: "5"}, {Name: "c", Value: "6"}},
193+
samples: []sample{{10000, 1}, {20000, 2}, {30000, 3}, {50000, 5}},
194+
}, {
195+
lset: labels.Labels{{Name: "b", Value: "5"}, {Name: "c", Value: "6"}},
196+
samples: []sample{{10000, 1}, {20000, 2}, {30000, 3}, {50000, 5}},
197+
}, {
198+
lset: labels.Labels{{Name: "b", Value: "5"}, {Name: "c", Value: "6"}},
199+
samples: []sample{{10000, 1}, {20000, 1234}, {30000, 3}, {50000, 5}},
200+
},
201+
},
202+
exp: []series{
203+
{
204+
lset: labels.Labels{{Name: "a", Value: "5"}, {Name: "c", Value: "6"}},
205+
samples: []sample{{10000, 1}, {20000, 2}, {30000, 3}, {50000, 5}},
206+
},
207+
{
208+
lset: labels.Labels{{Name: "b", Value: "5"}, {Name: "c", Value: "6"}},
209+
samples: []sample{{10000, 1}, {20000, 2}, {30000, 3}, {50000, 5}},
210+
},
211+
},
212+
},
178213
{
179214
name: "ignore sampling interval too small",
180215
input: []series{

pkg/store/detector.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) The Thanos Authors.
2+
// Licensed under the Apache License 2.0.
3+
4+
package store
5+
6+
import (
7+
"strings"
8+
9+
"github.com/prometheus/prometheus/model/labels"
10+
"github.com/thanos-io/thanos/pkg/store/storepb"
11+
)
12+
13+
// So far we found there is a bug in prometheus tsdb code when OOO is enabled.
14+
// This is a workaround to detect the issue when tsdb selects irrelevant matched data.
15+
// See https://github.com/thanos-io/thanos/issues/7481
16+
func detectCorruptLabels(lbls labels.Labels, matchers []storepb.LabelMatcher) bool {
17+
for _, m := range matchers {
18+
v := lbls.Get(m.Name)
19+
if v == "" {
20+
continue
21+
}
22+
if (m.Type == storepb.LabelMatcher_EQ && v != m.Value) ||
23+
(m.Type == storepb.LabelMatcher_NEQ && v == m.Value) {
24+
return true
25+
} else if m.Name == labels.MetricName &&
26+
(m.Type == storepb.LabelMatcher_RE || m.Type == storepb.LabelMatcher_NRE) {
27+
matcher, err := labels.NewFastRegexMatcher(m.Value)
28+
return err != nil ||
29+
(m.Type == storepb.LabelMatcher_RE && !matcher.MatchString(v)) ||
30+
(m.Type == storepb.LabelMatcher_NRE && matcher.MatchString(v))
31+
}
32+
}
33+
return false
34+
}
35+
36+
func requestMatches(matchers []storepb.LabelMatcher) string {
37+
var b strings.Builder
38+
for _, m := range matchers {
39+
b.WriteString(m.String())
40+
}
41+
return b.String()
42+
}

pkg/store/detector_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) The Thanos Authors.
2+
// Licensed under the Apache License 2.0.
3+
4+
package store
5+
6+
import (
7+
"testing"
8+
9+
"github.com/prometheus/prometheus/model/labels"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/thanos-io/thanos/pkg/store/storepb"
12+
)
13+
14+
func TestDetectCorruptLabels_EQ(t *testing.T) {
15+
good := labels.New(labels.Label{Name: labels.MetricName, Value: "up"})
16+
bad := labels.New(labels.Label{Name: labels.MetricName, Value: "kube_proxy_corrupt"})
17+
eq := []storepb.LabelMatcher{{Name: "__name__", Type: storepb.LabelMatcher_EQ, Value: "up"}}
18+
neq := []storepb.LabelMatcher{{Name: "__name__", Type: storepb.LabelMatcher_NEQ, Value: "up"}}
19+
assert.False(t, detectCorruptLabels(good, eq))
20+
assert.True(t, detectCorruptLabels(bad, eq))
21+
assert.False(t, detectCorruptLabels(bad, neq))
22+
assert.True(t, detectCorruptLabels(good, neq))
23+
}
24+
25+
func TestDetectCorruptLabels_RE(t *testing.T) {
26+
good := labels.New(labels.Label{Name: labels.MetricName, Value: "usage_database_pool"})
27+
bad := labels.New(labels.Label{Name: labels.MetricName, Value: "kube_proxy_corrupt"})
28+
re := []storepb.LabelMatcher{{Name: "__name__", Type: storepb.LabelMatcher_RE, Value: "usage_.+_pool"}}
29+
nre := []storepb.LabelMatcher{{Name: "__name__", Type: storepb.LabelMatcher_NRE, Value: "usage_.+_pool"}}
30+
assert.False(t, detectCorruptLabels(good, re))
31+
assert.True(t, detectCorruptLabels(bad, re))
32+
assert.False(t, detectCorruptLabels(bad, nre))
33+
assert.True(t, detectCorruptLabels(good, nre))
34+
}

pkg/store/tsdb.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,10 @@ func (s *TSDBStore) Series(r *storepb.SeriesRequest, seriesSrv storepb.Store_Ser
216216
if !shardMatcher.MatchesLabels(completeLabelset) {
217217
continue
218218
}
219+
if detectCorruptLabels(completeLabelset, r.Matchers) {
220+
return status.Errorf(codes.DataLoss, "corrupt prometheus tsdb index detected: requesting %s, got unmatched series %s",
221+
requestMatches(r.Matchers), completeLabelset.String())
222+
}
219223

220224
storeSeries := storepb.Series{Labels: labelpb.ZLabelsFromPromLabels(completeLabelset)}
221225
if r.SkipChunks {

0 commit comments

Comments
 (0)