diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fe16f3a8bd..7baad6074d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,6 +91,7 @@ * [BUGFIX] Parquet Converter: Fix `auto_forget_delay` having no effect. The ring lifecycler was created without the auto-forget delegate, so unhealthy instances were never automatically removed from the ring. #7752 * [BUGFIX] Alertmanager: Reject the global `mattermost_webhook_url_file` setting in per-tenant configs, consistent with every other global `*_file` setting. #7768 * [BUGFIX] Alertmanager: Tighten per-tenant config validation to reject additional file-based settings. #7767 +* [BUGFIX] Querier: Fix panic (`index out of range [-1]`) in the active request tracker when truncating a `match[]`/`query` value made entirely of invalid UTF-8 continuation bytes. The backwards scan for a rune boundary now stops at index 0 instead of underflowing. #7743 ## 1.21.1 2026-06-04 diff --git a/pkg/util/request_tracker/request_extractor.go b/pkg/util/request_tracker/request_extractor.go index cbd9f31e8fe..b9e7ff4d0f0 100644 --- a/pkg/util/request_tracker/request_extractor.go +++ b/pkg/util/request_tracker/request_extractor.go @@ -83,7 +83,7 @@ func trimStringByBytes(str string, size int) string { bytesStr := []byte(str) trimIndex := len(bytesStr) if size < len(bytesStr) { - for !utf8.RuneStart(bytesStr[size]) { + for size > 0 && !utf8.RuneStart(bytesStr[size]) { size-- } trimIndex = size diff --git a/pkg/util/request_tracker/request_tracker_test.go b/pkg/util/request_tracker/request_tracker_test.go index 13ef0a802cd..5cdc6fed2b5 100644 --- a/pkg/util/request_tracker/request_tracker_test.go +++ b/pkg/util/request_tracker/request_tracker_test.go @@ -161,6 +161,20 @@ func TestTrimForJsonMarshalMultiByteUTF8(t *testing.T) { } } +// TestTrimForJsonMarshalInvalidUTF8 reproduces a panic where a string made +// entirely of UTF-8 continuation bytes (0x80-0xBF, no valid rune-start byte) +// caused the backwards scan for a rune boundary to underflow past index 0 +// and index the byte slice with a negative index. +func TestTrimForJsonMarshalInvalidUTF8(t *testing.T) { + invalid := strings.Repeat("\x80", 1200) + + require.NotPanics(t, func() { + out := trimForJsonMarshal(invalid, 800) + assert.True(t, utf8.ValidString(out), "result should be valid UTF-8") + assert.Equal(t, "", out) + }) +} + // TestGenerateJSONEntryWithTruncatedFieldNegativeSize reproduces the request // tracker panic where a multi-byte UTF-8 field had to be truncated to a // negative remaining size because the rest of the entry already consumed the