From 3b4ca665fbe89bdf44560454684bb6829070d7f4 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Fri, 24 Feb 2023 09:20:15 -0700 Subject: jsonparse: Fix a bug allowing a trailing comma in objects --- ReleaseNotes.md | 3 ++ compat/json/compat_test.go | 1 + .../json/testdata/fuzz/FuzzEquiv/f9afef161f73f7cf | 2 ++ internal/jsonparse/parse.go | 33 ++++++++++++++-------- internal/jsonparse/parse_test.go | 14 ++++----- 5 files changed, 35 insertions(+), 18 deletions(-) create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/f9afef161f73f7cf diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 71973aa..be5b8ff 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -37,6 +37,9 @@ now reflect the `InvalidUTF8` setting, rather than simply saying "unflushed unicode garbage". + - Bugfix: No longer allows a comma after the last key:value pair in + an object. + # v0.3.7 (2023-02-20) Theme: Fixes from fuzzing (part 1?) diff --git a/compat/json/compat_test.go b/compat/json/compat_test.go index 3de48f7..af92093 100644 --- a/compat/json/compat_test.go +++ b/compat/json/compat_test.go @@ -196,6 +196,7 @@ func TestCompatUnmarshal(t *testing.T) { "syntax-01": {In: `{}x`, ExpErr: `invalid character 'x' after top-level value`}, "syntax-02": {In: `x`, ExpErr: `invalid character 'x' looking for beginning of value`}, "syntax-03": {In: `{x`, ExpErr: `invalid character 'x' looking for beginning of object key string`}, + "syntax-18": {In: `{"":0,}`, ExpErr: `invalid character '}' looking for beginning of object key string`}, "syntax-04": {In: `{""x`, ExpErr: `invalid character 'x' after object key`}, "syntax-05": {In: `{"":0x`, ExpErr: `invalid character 'x' after object key:value pair`}, "syntax-06": {In: `[0x`, ExpErr: `invalid character 'x' after array element`}, diff --git a/compat/json/testdata/fuzz/FuzzEquiv/f9afef161f73f7cf b/compat/json/testdata/fuzz/FuzzEquiv/f9afef161f73f7cf new file mode 100644 index 0000000..6618d0f --- /dev/null +++ b/compat/json/testdata/fuzz/FuzzEquiv/f9afef161f73f7cf @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("{\"\":{},}") diff --git a/internal/jsonparse/parse.go b/internal/jsonparse/parse.go index 5547df4..ce71a65 100644 --- a/internal/jsonparse/parse.go +++ b/internal/jsonparse/parse.go @@ -279,7 +279,8 @@ type Parser struct { // rule; they need some special assignments: // // { object: waiting for key to start or '}' - // » object: reading key / waiting for colon + // } object: waiting for key to start + // : object: reading key / waiting for colon // o object: reading value / waiting for ',' or '}' // // [ array: waiting for item to start or ']' @@ -298,17 +299,17 @@ type Parser struct { // stack processed // ? // { { - // »" {" - // »" {"x - // » {"x" + // :" {" + // :" {"x + // : {"x" // o? {"x": // o" {"x":" // o" {"x":"y // o {"x":"y" - // { {"x":"y", - // »" {"x":"y"," - // »" {"x":"y","a - // » {"x":"y","a" + // } {"x":"y", + // :" {"x":"y"," + // :" {"x":"y","a + // : {"x":"y","a" // o? {"x":"y","a": // o" {"x":"y","a":" // o" {"x":"y","a":"b @@ -627,7 +628,7 @@ func (par *Parser) HandleRune(c rune, isRune bool) (RuneType, error) { case 0x0020, 0x000A, 0x000D, 0x0009: return RuneTypeSpace, nil case '"': - par.replaceState(RuneTypeStringEnd) + par.replaceState(RuneTypeObjectColon) return par.pushState(RuneTypeStringBeg), nil case '}': par.popState() @@ -635,7 +636,17 @@ func (par *Parser) HandleRune(c rune, isRune bool) (RuneType, error) { default: return RuneTypeError, &InvalidCharacterError{c, isRune, "looking for beginning of object key string"} } - case RuneTypeStringEnd: // waiting for ':' + case RuneTypeObjectEnd: // waiting for key to start + switch c { + case 0x0020, 0x000A, 0x000D, 0x0009: + return RuneTypeSpace, nil + case '"': + par.replaceState(RuneTypeObjectColon) + return par.pushState(RuneTypeStringBeg), nil + default: + return RuneTypeError, &InvalidCharacterError{c, isRune, "looking for beginning of object key string"} + } + case RuneTypeObjectColon: // waiting for ':' switch c { case 0x0020, 0x000A, 0x000D, 0x0009: return RuneTypeSpace, nil @@ -651,7 +662,7 @@ func (par *Parser) HandleRune(c rune, isRune bool) (RuneType, error) { case 0x0020, 0x000A, 0x000D, 0x0009: return RuneTypeSpace, nil case ',': - par.replaceState(RuneTypeObjectBeg) + par.replaceState(RuneTypeObjectEnd) return RuneTypeObjectComma, nil case '}': par.popState() diff --git a/internal/jsonparse/parse_test.go b/internal/jsonparse/parse_test.go index acb43e8..fe94c58 100644 --- a/internal/jsonparse/parse_test.go +++ b/internal/jsonparse/parse_test.go @@ -24,17 +24,17 @@ func TestParserHandleRune(t *testing.T) { // st,// processed `?`, `{`, // { - `»"`, // {" - `»"`, // {"x - `»`, // {"x" + `:"`, // {" + `:"`, // {"x + `:`, // {"x" `o?`, // {"x": `o"`, // {"x":" `o"`, // {"x":"y `o`, // {"x":"y" - `{`, // {"x":"y", - `»"`, // {"x":"y"," - `»"`, // {"x":"y","a - `»`, // {"x":"y","a" + `}`, // {"x":"y", + `:"`, // {"x":"y"," + `:"`, // {"x":"y","a + `:`, // {"x":"y","a" `o?`, // {"x":"y","a": `o"`, // {"x":"y","a":" `o"`, // {"x":"y","a":"b -- cgit v1.2.3-2-g168b