gojay

high performance JSON encoder/decoder with stream API for Golang
git clone git://git.lair.cx/gojay
Log | Files | Refs | README | LICENSE

commit 0daebb40c873255a06be8cc7dabef7b5bc1d6c0a
parent 7166fd659b3e557c8be1b04c95527add7a0f5f67
Author: francoispqt <francois@parquet.ninja>
Date:   Thu, 24 May 2018 00:01:48 +0800

fix cases where malformed numbers can cause panic

Diffstat:
Mdecode_number_float.go | 39+++++++++++++++++++++++++--------------
Mdecode_number_float_test.go | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mdecode_number_int.go | 35+++++++++++++++++++++++++----------
Mdecode_number_int_test.go | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Aexamples/fuzz/Makefile | 12++++++++++++
Mexamples/fuzz/main.go | 3+++
6 files changed, 304 insertions(+), 29 deletions(-)

diff --git a/decode_number_float.go b/decode_number_float.go @@ -83,7 +83,7 @@ func (dec *Decoder) getFloat() (float64, error) { end = i beforeDecimal = (beforeDecimal << 3) + (beforeDecimal << 1) continue - } else if c == 'e' || c == 'E' { + } else if (c == 'e' || c == 'E') && j < i-1 { afterDecimal := dec.atoi64(start, end) dec.cursor = i + 1 expI := end - start + 2 @@ -93,18 +93,22 @@ func (dec *Decoder) getFloat() (float64, error) { pow := pow10uint64[expI] floatVal := float64(beforeDecimal+afterDecimal) / float64(pow) exp := dec.getExponent() - if +exp+1 >= int64(len(pow10uint64)) { + pExp := (exp + (exp >> 31)) ^ (exp >> 31) + 1 // abs + if pExp >= int64(len(pow10uint64)) { return 0, dec.raiseInvalidJSONErr(dec.cursor) } // if exponent is negative if exp < 0 { - return float64(floatVal) * (1 / float64(pow10uint64[exp*-1+1])), nil + return float64(floatVal) * (1 / float64(pow10uint64[pExp])), nil } - return float64(floatVal) * float64(pow10uint64[exp+1]), nil + return float64(floatVal) * float64(pow10uint64[pExp]), nil } dec.cursor = i break } + if end >= dec.length || end <= start { + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } // then we add both integers // then we divide the number by the power found afterDecimal := dec.atoi64(start, end) @@ -120,14 +124,15 @@ func (dec *Decoder) getFloat() (float64, error) { beforeDecimal := uint64(dec.atoi64(start, end)) // get exponent exp := dec.getExponent() - if +exp+1 >= int64(len(pow10uint64)) { + pExp := (exp + (exp >> 31)) ^ (exp >> 31) + 1 // abs + if pExp >= int64(len(pow10uint64)) { return 0, dec.raiseInvalidJSONErr(dec.cursor) } // if exponent is negative if exp < 0 { - return float64(beforeDecimal) * (1 / float64(pow10uint64[exp*-1+1])), nil + return float64(beforeDecimal) * (1 / float64(pow10uint64[pExp])), nil } - return float64(beforeDecimal) * float64(pow10uint64[exp+1]), nil + return float64(beforeDecimal) * float64(pow10uint64[pExp]), nil case ' ', '\n', '\t', '\r', ',', '}', ']': // does not have decimal dec.cursor = j return float64(dec.atoi64(start, end)), nil @@ -221,7 +226,7 @@ func (dec *Decoder) getFloat32() (float32, error) { end = i beforeDecimal = (beforeDecimal << 3) + (beforeDecimal << 1) continue - } else if c == 'e' || c == 'E' { + } else if (c == 'e' || c == 'E') && j < i-1 { afterDecimal := dec.atoi32(start, end) dec.cursor = i + 1 expI := end - start + 2 @@ -231,18 +236,22 @@ func (dec *Decoder) getFloat32() (float32, error) { pow := pow10uint64[expI] floatVal := float32(beforeDecimal+afterDecimal) / float32(pow) exp := dec.getExponent() - if +exp+1 >= int64(len(pow10uint64)) { + pExp := (exp + (exp >> 31)) ^ (exp >> 31) + 1 // abs + if pExp >= int64(len(pow10uint64)) { return 0, dec.raiseInvalidJSONErr(dec.cursor) } // if exponent is negative if exp < 0 { - return float32(floatVal) * (1 / float32(pow10uint64[exp*-1+1])), nil + return float32(floatVal) * (1 / float32(pow10uint64[pExp])), nil } - return float32(floatVal) * float32(pow10uint64[exp+1]), nil + return float32(floatVal) * float32(pow10uint64[pExp]), nil } dec.cursor = i break } + if end >= dec.length || end <= start { + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } // then we add both integers // then we divide the number by the power found afterDecimal := dec.atoi32(start, end) @@ -258,14 +267,16 @@ func (dec *Decoder) getFloat32() (float32, error) { beforeDecimal := uint32(dec.atoi32(start, end)) // get exponent exp := dec.getExponent() - if +exp+1 >= int64(len(pow10uint64)) { + pExp := (exp + (exp >> 31)) ^ (exp >> 31) + 1 + // log.Print(exp, " after") + if pExp >= int64(len(pow10uint64)) { return 0, dec.raiseInvalidJSONErr(dec.cursor) } // if exponent is negative if exp < 0 { - return float32(beforeDecimal) * (1 / float32(pow10uint64[exp*-1+1])), nil + return float32(beforeDecimal) * (1 / float32(pow10uint64[pExp])), nil } - return float32(beforeDecimal) * float32(pow10uint64[exp+1]), nil + return float32(beforeDecimal) * float32(pow10uint64[pExp]), nil case ' ', '\n', '\t', '\r', ',', '}', ']': // does not have decimal dec.cursor = j return float32(dec.atoi32(start, end)), nil diff --git a/decode_number_float_test.go b/decode_number_float_test.go @@ -15,6 +15,7 @@ func TestDecoderFloat64(t *testing.T) { name string json string expectedResult float64 + skipResult bool err bool errType interface{} }{ @@ -67,6 +68,42 @@ func TestDecoderFloat64(t *testing.T) { errType: InvalidJSONError(""), }, { + name: "basic-err1", + json: "0.", + expectedResult: 0, + err: true, + }, + { + name: "basic-err2", + json: "-1.", + expectedResult: 0, + err: true, + }, + { + name: "exp-err", + json: "0e-20", + expectedResult: 0, + err: true, + }, + { + name: "exp-err3", + json: "-9e-60", + expectedResult: 0, + err: true, + }, + { + name: "exp-err4", + json: "0.e-2", + skipResult: true, + err: true, + }, + { + name: "exp-err5", + json: "-5.E-2", + skipResult: true, + err: true, + }, + { name: "basic-exponent-positive-positive-exp4", json: "8e+005", expectedResult: 800000, @@ -210,7 +247,9 @@ func TestDecoderFloat64(t *testing.T) { } else { assert.Nil(t, err, "Err must be nil") } - assert.Equal(t, testCase.expectedResult*1000000, math.Round(v*1000000), fmt.Sprintf("v must be equal to %f", testCase.expectedResult)) + if !testCase.skipResult { + assert.Equal(t, testCase.expectedResult*1000000, math.Round(v*1000000), fmt.Sprintf("v must be equal to %f", testCase.expectedResult)) + } }) } t.Run("pool-error", func(t *testing.T) { @@ -256,6 +295,7 @@ func TestDecoderFloat32(t *testing.T) { name string json string expectedResult float32 + skipResult bool err bool errType interface{} }{ @@ -280,6 +320,47 @@ func TestDecoderFloat32(t *testing.T) { expectedResult: 0, }, { + name: "basic-err1", + json: "0.", + expectedResult: 0, + err: true, + }, + { + name: "basic-err2", + json: "-1.", + expectedResult: 0, + err: true, + }, + { + name: "exp-err", + json: "0e-20", + expectedResult: 0, + err: true, + }, + { + name: "exp-err3", + json: "-9e-60", + expectedResult: 0, + err: true, + }, + { + name: "exp-err4", + json: "0.e-2", + skipResult: true, + err: true, + }, + { + name: "exp-err5", + json: "-5.E-2", + skipResult: true, + err: true, + }, + { + name: "basic-null", + json: "null", + expectedResult: 0, + }, + { name: "basic-null-err", json: "trua", expectedResult: 0, @@ -457,7 +538,13 @@ func TestDecoderFloat32(t *testing.T) { } else { assert.Nil(t, err, "Err must be nil") } - assert.Equal(t, float64(testCase.expectedResult*1000000), math.Round(float64(v*1000000)), fmt.Sprintf("v must be equal to %f", testCase.expectedResult)) + if !testCase.skipResult { + assert.Equal( + t, + float64(testCase.expectedResult*1000000), math.Round(float64(v*1000000)), + fmt.Sprintf("v must be equal to %f", testCase.expectedResult), + ) + } }) } t.Run("pool-error", func(t *testing.T) { diff --git a/decode_number_int.go b/decode_number_int.go @@ -138,15 +138,18 @@ func (dec *Decoder) getInt16() (int16, error) { // look for exponent (e,E) as exponent can change the // way number should be parsed to int. // if no exponent found, just unmarshal the number before decimal point - startDecimal := j + 1 - endDecimal := j + 1 j++ + startDecimal := j + endDecimal := j - 1 for ; j < dec.length || dec.read(); j++ { switch dec.data[j] { case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': endDecimal = j continue case 'e', 'E': + if startDecimal > endDecimal { + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } dec.cursor = j + 1 // can try unmarshalling to int as Exponent might change decimal number to non decimal // let's get the float value first @@ -317,15 +320,18 @@ func (dec *Decoder) getInt8() (int8, error) { // look for exponent (e,E) as exponent can change the // way number should be parsed to int. // if no exponent found, just unmarshal the number before decimal point - startDecimal := j + 1 - endDecimal := j + 1 j++ + startDecimal := j + endDecimal := j - 1 for ; j < dec.length || dec.read(); j++ { switch dec.data[j] { case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': endDecimal = j continue case 'e', 'E': + if startDecimal > endDecimal { + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } dec.cursor = j + 1 // can try unmarshalling to int as Exponent might change decimal number to non decimal // let's get the float value first @@ -495,15 +501,19 @@ func (dec *Decoder) getInt32() (int32, error) { // look for exponent (e,E) as exponent can change the // way number should be parsed to int. // if no exponent found, just unmarshal the number before decimal point - startDecimal := j + 1 - endDecimal := j + 1 j++ + startDecimal := j + endDecimal := j - 1 for ; j < dec.length || dec.read(); j++ { switch dec.data[j] { case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': endDecimal = j continue case 'e', 'E': + // if eg 1.E + if startDecimal > endDecimal { + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } dec.cursor = j + 1 // can try unmarshalling to int as Exponent might change decimal number to non decimal // let's get the float value first @@ -677,15 +687,19 @@ func (dec *Decoder) getInt64() (int64, error) { // look for exponent (e,E) as exponent can change the // way number should be parsed to int. // if no exponent found, just unmarshal the number before decimal point - startDecimal := j + 1 - endDecimal := j + 1 j++ + startDecimal := j + endDecimal := j - 1 for ; j < dec.length || dec.read(); j++ { switch dec.data[j] { case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': endDecimal = j continue case 'e', 'E': + // if eg 1.E + if startDecimal > endDecimal { + return 0, dec.raiseInvalidJSONErr(dec.cursor) + } dec.cursor = j + 1 // can try unmarshalling to int as Exponent might change decimal number to non decimal // let's get the float value first @@ -707,10 +721,11 @@ func (dec *Decoder) getInt64() (int64, error) { floatVal := float64(beforeDecimal+afterDecimal) / float64(pow) // we have the floating value, now multiply by the exponent exp := dec.getExponent() - if +exp+1 >= int64(len(pow10uint64)) { + pExp := (exp + (exp >> 31)) ^ (exp >> 31) + 1 // abs + if pExp >= int64(len(pow10uint64)) { return 0, dec.raiseInvalidJSONErr(dec.cursor) } - val := floatVal * float64(pow10uint64[exp+1]) + val := floatVal * float64(pow10uint64[pExp]) return int64(val), nil case ' ', '\t', '\n', ',', ']', '}': dec.cursor = j diff --git a/decode_number_int_test.go b/decode_number_int_test.go @@ -209,20 +209,34 @@ func TestDecoderInt(t *testing.T) { errType: InvalidJSONError(""), }, { - name: "error3", + name: "error4", json: "0.E----", expectedResult: 0, err: true, errType: InvalidJSONError(""), }, { - name: "error3", + name: "error5", json: "0E40", expectedResult: 0, err: true, errType: InvalidJSONError(""), }, { + name: "error6", + json: "0.e-9", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error7", + json: "-5.e-2", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { name: "invalid-type", json: `"string"`, expectedResult: 0, @@ -237,7 +251,7 @@ func TestDecoderInt(t *testing.T) { err := Unmarshal(json, &v) if testCase.err { assert.NotNil(t, err, "Err must not be nil") - if testCase.errType != nil { + if testCase.errType != nil && err != nil { assert.IsType( t, testCase.errType, @@ -511,6 +525,34 @@ func TestDecoderInt64(t *testing.T) { err: true, }, { + name: "error4", + json: "0.E----", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error5", + json: "0E40", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error6", + json: "0.e-9", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error7", + json: "-5.e-2", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { name: "invalid-type", json: `"string"`, expectedResult: 0, @@ -803,6 +845,34 @@ func TestDecoderInt32(t *testing.T) { errType: InvalidJSONError(""), }, { + name: "error4", + json: "0.E----", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error5", + json: "0E40", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error6", + json: "0.e-9", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error7", + json: "-5.e-2", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { name: "basic-float", json: "8.32 ", expectedResult: 8, @@ -1134,6 +1204,41 @@ func TestDecoderInt16(t *testing.T) { errType: InvalidJSONError(""), }, { + name: "error4", + json: "0.E----", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error5", + json: "0E40", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error6", + json: "0.e-9", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error7", + json: "0.e", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error8", + json: "-5.e-2", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { name: "invalid-type", json: `"string"`, expectedResult: 0, @@ -1440,6 +1545,48 @@ func TestDecoderInt8(t *testing.T) { err: true, }, { + name: "error4", + json: "0.E----", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error5", + json: "0E40", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error6", + json: "0.e-9", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error7", + json: "0.e", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error8", + json: "-5.e-2", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error8", + json: "-5.01e", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { name: "invalid-type", json: `"string"`, expectedResult: 0, diff --git a/examples/fuzz/Makefile b/examples/fuzz/Makefile @@ -0,0 +1,11 @@ +.PHONY: gofuzzbuild +gofuzzbuild: + go-fuzz-build github.com/francoispqt/gojay/examples/fuzz + +.PHONY: gofuzz +gofuzz: + go-fuzz -bin=fuzz-fuzz.zip -workdir=. + +.PHONY: gofuzzclean +gofuzzclean: + rm -rf corpus crashers suppressions fuzz-fuzz.zip +\ No newline at end of file diff --git a/examples/fuzz/main.go b/examples/fuzz/main.go @@ -6,6 +6,7 @@ import ( type user struct { id int + age float64 name string email string } @@ -15,6 +16,8 @@ func (u *user) UnmarshalJSONObject(dec *gojay.Decoder, key string) error { switch key { case "id": return dec.Int(&u.id) + case "age": + return dec.Float(&u.age) case "name": return dec.String(&u.name) case "email":