diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cc35782015..6c2100d65bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,7 @@ * [BUGFIX] Ring: Fix DynamoDB KV CAS not retrying on transactional conditional check failures. `TransactWriteItems` reports condition failures as `TransactionCanceledException` with a `ConditionalCheckFailed` cancellation reason, which was not recognized as retryable, so any concurrent ring update conflict (e.g. many ingesters joining during a rolling update) failed immediately instead of re-reading and retrying. `TransactionConflict` cancellation reasons are also treated as retryable. #7706 * [BUGFIX] Distributor: Return HTTP 499 (Client Closed Request) instead of 500 when a remote-write or OTLP push is canceled by the client, so client-side cancellations are no longer counted as server-side errors. #7717 * [BUGFIX] Querier: Fix gRPC `codes.Canceled` errors being mapped to HTTP 500 instead of 499 when a client cancels a query. #7738 +* [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. #7729 ## 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