From 724dd47aa3431b9d4cf9ac2eebbf7b38a629afca Mon Sep 17 00:00:00 2001 From: Maciej Kisiel Date: Thu, 12 Mar 2026 21:54:23 +0100 Subject: [PATCH] internal: fix Unicode zero character handling (#841) A fix was submitted to our dependency, tests added to validate the problematic scenario. We also added some fuzz tests to check for additional issues, none were found. --- go.mod | 2 +- go.sum | 4 +- internal/json/json_test.go | 111 +++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9e99d46..f3f327b 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/go-cmp v0.7.0 github.com/google/jsonschema-go v0.4.2 - github.com/segmentio/encoding v0.5.3 + github.com/segmentio/encoding v0.5.4 github.com/yosida95/uritemplate/v3 v3.0.2 golang.org/x/oauth2 v0.35.0 golang.org/x/tools v0.42.0 diff --git a/go.sum b/go.sum index 998e07a..a60fd62 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbc github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= -github.com/segmentio/encoding v0.5.3 h1:OjMgICtcSFuNvQCdwqMCv9Tg7lEOXGwm1J5RPQccx6w= -github.com/segmentio/encoding v0.5.3/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= +github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0= +github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= diff --git a/internal/json/json_test.go b/internal/json/json_test.go index feeba70..34d0226 100644 --- a/internal/json/json_test.go +++ b/internal/json/json_test.go @@ -5,6 +5,9 @@ package json import ( + "bytes" + "encoding/json" + "fmt" "testing" "github.com/google/go-cmp/cmp" @@ -61,3 +64,111 @@ func TestUnmarshalCaseSensitivity(t *testing.T) { }) } } + +func TestUnmarshalNullCharacter(t *testing.T) { + type Target struct { + Field string `json:"field"` + } + + tests := []struct { + name string + json string + want string + }{ + { + name: "null char in middle", + json: `{"fi\u0000eld": "value"}`, + }, + { + name: "null char at end", + json: `{"field\u0000": "value"}`, + }, + { + name: "null char at start", + json: `{"\u0000field": "value"}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got Target + if err := Unmarshal([]byte(tt.json), &got); err != nil { + t.Fatalf("Unmarshal failed: %v", err) + } + if got.Field != "" { + t.Errorf("Field was set: %s", tt.json) + } + }) + } +} + +func FuzzUnmarshalKeyComparison(f *testing.F) { + // Add seed corpus for expected valid key and the known problematic cases. + f.Add("field") + f.Add("FIELD") + f.Add("field\x00") + + f.Fuzz(func(t *testing.T, key string) { + // Generate valid JSON with the fuzzed key. + // json.Encoder ensures the key is properly escaped (e.g. \x00 becomes \u0000). + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(false) + if err := enc.Encode(key); err != nil { + return + } + encodedKey := buf.Bytes() + jsonData := fmt.Sprintf(`{%s: "value"}`, encodedKey) + + type Target struct { + Field string `json:"field"` + } + + var got Target + if err := Unmarshal([]byte(jsonData), &got); err != nil { + return + } + + if got.Field == "value" && key != "field" { + t.Errorf("Unmarshal matched key %q (JSON: %s) to field 'field'", key, encodedKey) + } + }) +} + +func FuzzUnmarshalFieldIsolation(f *testing.F) { + // Add seed corpus. + f.Add("other", "value") + f.Add("safe", "overwrite") + f.Add("safe\x00", "attack") + + f.Fuzz(func(t *testing.T, key, val string) { + // Generate valid JSON with the fuzzed key and value. + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(false) + if err := enc.Encode(key); err != nil { + return + } + encodedKey := buf.Bytes() + buf.Reset() + if err := enc.Encode(val); err != nil { + return + } + encodedVal := buf.Bytes() + + jsonData := fmt.Sprintf(`{"safe": "value", %s: %s}`, encodedKey, encodedVal) + + type Target struct { + Safe string `json:"safe"` + } + + var got Target + if err := Unmarshal([]byte(jsonData), &got); err != nil { + return + } + + if got.Safe != "value" && key != "safe" { + t.Errorf("Field 'safe' improperly modified by key %q (JSON: %s). Got %q, want %q", key, encodedKey, got.Safe, "value") + } + }) +}