From 267be7dc20a32032481943cee2e188b505fb9a82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20Krajcsovits?= Date: Thu, 18 Sep 2025 09:21:03 +0200 Subject: [PATCH] fix(chunkenc): error out when reading unknown histogram schemas from chunks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Otherwise higher level code like PromQL needs to constantly check if it can handle the samples. Signed-off-by: György Krajcsovits --- model/histogram/generic.go | 4 ++++ tsdb/chunkenc/float_histogram.go | 6 ++++++ tsdb/chunkenc/float_histogram_test.go | 30 +++++++++++++++++++++++++++ tsdb/chunkenc/histogram.go | 6 ++++++ tsdb/chunkenc/histogram_test.go | 30 +++++++++++++++++++++++++++ 5 files changed, 76 insertions(+) diff --git a/model/histogram/generic.go b/model/histogram/generic.go index 4c0940a1f6..b5aa19f9cb 100644 --- a/model/histogram/generic.go +++ b/model/histogram/generic.go @@ -54,6 +54,10 @@ func IsExponentialSchemaReserved(s int32) bool { return s >= ExponentialSchemaMinReserved && s <= ExponentialSchemaMaxReserved } +func IsValidSchema(s int32) bool { + return IsCustomBucketsSchema(s) || IsExponentialSchema(s) +} + // BucketCount is a type constraint for the count in a bucket, which can be // float64 (for type FloatHistogram) or uint64 (for type Histogram). type BucketCount interface { diff --git a/tsdb/chunkenc/float_histogram.go b/tsdb/chunkenc/float_histogram.go index 7e9bf0085a..1626b45239 100644 --- a/tsdb/chunkenc/float_histogram.go +++ b/tsdb/chunkenc/float_histogram.go @@ -954,6 +954,12 @@ func (it *floatHistogramIterator) Next() ValueType { it.err = err return ValNone } + + if !histogram.IsValidSchema(schema) { + it.err = fmt.Errorf("invalid histogram schema %d", schema) + return ValNone + } + it.schema = schema it.zThreshold = zeroThreshold it.pSpans, it.nSpans = posSpans, negSpans diff --git a/tsdb/chunkenc/float_histogram_test.go b/tsdb/chunkenc/float_histogram_test.go index f715716b65..be5a2673e5 100644 --- a/tsdb/chunkenc/float_histogram_test.go +++ b/tsdb/chunkenc/float_histogram_test.go @@ -14,6 +14,7 @@ package chunkenc import ( + "fmt" "testing" "github.com/stretchr/testify/require" @@ -1462,3 +1463,32 @@ func TestFloatHistogramEmptyBucketsWithGaps(t *testing.T) { require.Equal(t, ValNone, it.Next()) require.NoError(t, it.Err()) } + +func TestFloatHistogramIteratorFailIfSchemaInValid(t *testing.T) { + for _, schema := range []int32{-101, 101} { + t.Run(fmt.Sprintf("schema %d", schema), func(t *testing.T) { + h := &histogram.FloatHistogram{ + Schema: schema, + Count: 10, + Sum: 15.0, + ZeroThreshold: 1e-100, + PositiveSpans: []histogram.Span{ + {Offset: 0, Length: 2}, + {Offset: 1, Length: 2}, + }, + PositiveBuckets: []float64{1, 2, 3, 4}, + } + + c := NewFloatHistogramChunk() + app, err := c.Appender() + require.NoError(t, err) + + _, _, _, err = app.AppendFloatHistogram(nil, 1, h, false) + require.NoError(t, err) + + it := c.Iterator(nil) + require.Equal(t, ValNone, it.Next()) + require.EqualError(t, it.Err(), fmt.Sprintf("invalid histogram schema %d", schema)) + }) + } +} diff --git a/tsdb/chunkenc/histogram.go b/tsdb/chunkenc/histogram.go index 4a8460fa85..deb52d83a8 100644 --- a/tsdb/chunkenc/histogram.go +++ b/tsdb/chunkenc/histogram.go @@ -1077,6 +1077,12 @@ func (it *histogramIterator) Next() ValueType { it.err = err return ValNone } + + if !histogram.IsValidSchema(schema) { + it.err = fmt.Errorf("invalid histogram schema %d", schema) + return ValNone + } + it.schema = schema it.zThreshold = zeroThreshold it.pSpans, it.nSpans = posSpans, negSpans diff --git a/tsdb/chunkenc/histogram_test.go b/tsdb/chunkenc/histogram_test.go index 5c708faf5f..d2b1f593a3 100644 --- a/tsdb/chunkenc/histogram_test.go +++ b/tsdb/chunkenc/histogram_test.go @@ -14,6 +14,7 @@ package chunkenc import ( + "fmt" "testing" "github.com/stretchr/testify/require" @@ -1818,3 +1819,32 @@ func TestIntHistogramEmptyBucketsWithGaps(t *testing.T) { require.Equal(t, ValNone, it.Next()) require.NoError(t, it.Err()) } + +func TestHistogramIteratorFailIfSchemaInValid(t *testing.T) { + for _, schema := range []int32{-101, 101} { + t.Run(fmt.Sprintf("schema %d", schema), func(t *testing.T) { + h := &histogram.Histogram{ + Schema: schema, + Count: 10, + Sum: 15.0, + ZeroThreshold: 1e-100, + PositiveSpans: []histogram.Span{ + {Offset: 0, Length: 2}, + {Offset: 1, Length: 2}, + }, + PositiveBuckets: []int64{1, 2, 3, 4}, + } + + c := NewHistogramChunk() + app, err := c.Appender() + require.NoError(t, err) + + _, _, _, err = app.AppendHistogram(nil, 1, h, false) + require.NoError(t, err) + + it := c.Iterator(nil) + require.Equal(t, ValNone, it.Next()) + require.EqualError(t, it.Err(), fmt.Sprintf("invalid histogram schema %d", schema)) + }) + } +}