From cb4469a2246b955edef97adb5132d53fdd3bfbe4 Mon Sep 17 00:00:00 2001 From: Mohamed MAACHE Date: Sun, 5 Jul 2026 11:14:10 +0200 Subject: [PATCH] fix: bounds-check nul sentinel in struct key escape scan decodeKeyCharByEscapedChar did not handle a dangling backslash at the end of the buffer. It treated the trailing nul sentinel as an unrecognized escaped char and returned a cursor past it, letting decodeKeyByBitmapUint8's raw char() read walk one byte past the allocation. Fatal under checkptr/-race on malformed input. Fixes #577, #575 --- decode_test.go | 28 ++++++++++++++++++++++++++++ internal/decoder/struct.go | 3 +++ 2 files changed, 31 insertions(+) diff --git a/decode_test.go b/decode_test.go index 8f32cedb..5c403e5d 100644 --- a/decode_test.go +++ b/decode_test.go @@ -4057,3 +4057,31 @@ func TestIssue429(t *testing.T) { } } } + +func TestIssue577(t *testing.T) { + // struct key with a dangling backslash at end of input must decode + // error, not read past the buffer (goccy/go-json#575, #577) + type T struct { + Hash string `json:"hash"` + PrevHash string `json:"prev_hash"` + Data string `json:"data"` + Seq int64 `json:"seq"` + TsNs int64 `json:"ts_ns"` + Kind uint8 `json:"kind"` + } + inputs := []string{ + "{\"\\29\\", + "{\"hAs\\", + "{\"a\\", + "{\"\\", + } + for i := 0; i < 8; i++ { + inputs = append(inputs, `{"`+strings.Repeat("a", i)+`\`) + } + for _, b := range inputs { + var x T + if err := json.Unmarshal([]byte(b), &x); err == nil { + t.Errorf("input %q: expected decode error, got nil", b) + } + } +} diff --git a/internal/decoder/struct.go b/internal/decoder/struct.go index 313da153..3077e7a0 100644 --- a/internal/decoder/struct.go +++ b/internal/decoder/struct.go @@ -215,6 +215,9 @@ func decodeKeyCharByEscapedChar(buf []byte, cursor int64) ([]byte, int64, error) return []byte{'\t'}, cursor, nil case 'u': return decodeKeyCharByUnicodeRune(buf, cursor) + case nul: + // dangling backslash at end of buffer, don't step past the sentinel + return nil, cursor, errors.ErrUnexpectedEndOfJSON("string", cursor) } return nil, cursor, nil }