Skip to content

Commit 594cebf

Browse files
authored
Querier: fix panic on invalid UTF-8 in active request tracker (#7743)
* Querier: fix panic on invalid UTF-8 in active request tracker Motivation: trimStringByBytes in the active request tracker scans backwards from a byte offset for a UTF-8 rune-start byte, with no lower bound. If a truncated value consists entirely of UTF-8 continuation bytes (0x80-0xBF), no byte in the string is ever a rune start, so the loop decrements past 0 and the subsequent slice index panics with "index out of range [-1]". The active request tracker is enabled by default (-querier.active-query-tracker-dir) and wraps the Prometheus API router, including /api/v1/series, /api/v1/labels and /api/v1/label/{name}/values. A tenant-supplied match[] or query value long enough to be truncated (over maxEntrySize) and made of invalid UTF-8 continuation bytes reaches trimStringByBytes byte-for-byte, so this is reachable from request input without special privileges. This is the same underflow class as #7640, which added a bound in trimForJsonMarshalRecursive but did not touch the scan inside trimStringByBytes; its regression tests only use valid multi-byte UTF-8, which always terminates the backwards scan before reaching index 0. This change only fixes the panic in trimStringByBytes. It does not add a recover() to the querier worker goroutine that runs request handling (pkg/querier/worker/scheduler_processor.go) — that was suggested in the issue as a separate, additional hardening measure and is out of scope for this fix. Approach: Bound the backwards scan with size > 0 so it stops at index 0 instead of underflowing. For any valid UTF-8 input, byte 0 is always a rune start, so this does not change behavior for legitimate input; it only changes the degenerate all-continuation-byte case, which now correctly trims to an empty string instead of panicking. Validation: - go build ./... - go test ./pkg/util/request_tracker/... (all pass, including the new TestTrimForJsonMarshalInvalidUTF8 regression test) - Confirmed TestTrimForJsonMarshalInvalidUTF8 reproduces the exact panic ("index out of range [-1]" at request_extractor.go:86) against the pre-fix code by temporarily reverting the one-line fix and re-running the test, then restored the fix and re-ran to confirm it passes. Fixes #7729 Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com> * Fix CHANGELOG PR number reference for UTF-8 panic bugfix entry Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com> --------- Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com> Co-authored-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
1 parent b06043e commit 594cebf

3 files changed

Lines changed: 16 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
* [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
9494
* [BUGFIX] Alertmanager: Reject the global `mattermost_webhook_url_file` setting in per-tenant configs, consistent with every other global `*_file` setting. #7768
9595
* [BUGFIX] Alertmanager: Tighten per-tenant config validation to reject additional file-based settings. #7767
96+
* [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
9697

9798
## 1.21.1 2026-06-04
9899

pkg/util/request_tracker/request_extractor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func trimStringByBytes(str string, size int) string {
8383
bytesStr := []byte(str)
8484
trimIndex := len(bytesStr)
8585
if size < len(bytesStr) {
86-
for !utf8.RuneStart(bytesStr[size]) {
86+
for size > 0 && !utf8.RuneStart(bytesStr[size]) {
8787
size--
8888
}
8989
trimIndex = size

pkg/util/request_tracker/request_tracker_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,20 @@ func TestTrimForJsonMarshalMultiByteUTF8(t *testing.T) {
161161
}
162162
}
163163

164+
// TestTrimForJsonMarshalInvalidUTF8 reproduces a panic where a string made
165+
// entirely of UTF-8 continuation bytes (0x80-0xBF, no valid rune-start byte)
166+
// caused the backwards scan for a rune boundary to underflow past index 0
167+
// and index the byte slice with a negative index.
168+
func TestTrimForJsonMarshalInvalidUTF8(t *testing.T) {
169+
invalid := strings.Repeat("\x80", 1200)
170+
171+
require.NotPanics(t, func() {
172+
out := trimForJsonMarshal(invalid, 800)
173+
assert.True(t, utf8.ValidString(out), "result should be valid UTF-8")
174+
assert.Equal(t, "", out)
175+
})
176+
}
177+
164178
// TestGenerateJSONEntryWithTruncatedFieldNegativeSize reproduces the request
165179
// tracker panic where a multi-byte UTF-8 field had to be truncated to a
166180
// negative remaining size because the rest of the entry already consumed the

0 commit comments

Comments
 (0)