Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

expression: check json max depth #37759

Merged
merged 5 commits into from
Sep 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions errno/errcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,7 @@ const (
ErrInvalidJSONPathWildcard = 3149
ErrInvalidJSONContainsPathType = 3150
ErrJSONUsedAsKey = 3152
ErrJSONDocumentTooDeep = 3157
ErrJSONDocumentNULLKey = 3158
ErrSecureTransportRequired = 3159
ErrBadUser = 3162
Expand Down
1 change: 1 addition & 0 deletions errno/errname.go
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,7 @@ var MySQLErrName = map[uint16]*mysql.ErrMessage{
ErrInvalidJSONPathWildcard: mysql.Message("In this situation, path expressions may not contain the * and ** tokens.", nil),
ErrInvalidJSONContainsPathType: mysql.Message("The second argument can only be either 'one' or 'all'.", nil),
ErrJSONUsedAsKey: mysql.Message("JSON column '%-.192s' cannot be used in key specification.", nil),
ErrJSONDocumentTooDeep: mysql.Message("The JSON document exceeds the maximum depth.", nil),
ErrJSONDocumentNULLKey: mysql.Message("JSON documents may not contain NULL member names.", nil),
ErrSecureTransportRequired: mysql.Message("Connections using insecure transport are prohibited while --require_secure_transport=ON.", nil),
ErrBadUser: mysql.Message("User %s does not exist.", nil),
Expand Down
5 changes: 5 additions & 0 deletions errors.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1621,6 +1621,11 @@ error = '''
The second argument can only be either 'one' or 'all'.
'''

["json:3157"]
error = '''
The JSON document exceeds the maximum depth.
'''

["json:3158"]
error = '''
JSON documents may not contain NULL member names.
Expand Down
6 changes: 5 additions & 1 deletion executor/aggfuncs/func_json_arrayagg.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ func (e *jsonArrayagg) AppendFinalResult2Chunk(sctx sessionctx.Context, pr Parti
return nil
}

chk.AppendJSON(e.ordinal, types.CreateBinaryJSON(p.entries))
json, err := types.CreateBinaryJSONWithCheck(p.entries)
if err != nil {
return errors.Trace(err)
}
chk.AppendJSON(e.ordinal, json)
return nil
}

Expand Down
6 changes: 5 additions & 1 deletion executor/aggfuncs/func_json_objectagg.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ func (e *jsonObjectAgg) AppendFinalResult2Chunk(sctx sessionctx.Context, pr Part
return nil
}

chk.AppendJSON(e.ordinal, types.CreateBinaryJSON(p.entries))
bj, err := types.CreateBinaryJSONWithCheck(p.entries)
if err != nil {
return errors.Trace(err)
}
chk.AppendJSON(e.ordinal, bj)
return nil
}

Expand Down
17 changes: 14 additions & 3 deletions expression/builtin_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,11 @@ func (b *builtinJSONObjectSig) evalJSON(row chunk.Row) (res types.BinaryJSON, is
jsons[key] = value
}
}
return types.CreateBinaryJSON(jsons), false, nil
bj, err := types.CreateBinaryJSONWithCheck(jsons)
if err != nil {
return res, true, err
}
return bj, false, nil
}

type jsonArrayFunctionClass struct {
Expand Down Expand Up @@ -603,7 +607,11 @@ func (b *builtinJSONArraySig) evalJSON(row chunk.Row) (res types.BinaryJSON, isN
}
jsons = append(jsons, j)
}
return types.CreateBinaryJSON(jsons), false, nil
bj, err := types.CreateBinaryJSONWithCheck(jsons)
if err != nil {
return res, true, err
}
return bj, false, nil
}

type jsonContainsPathFunctionClass struct {
Expand Down Expand Up @@ -976,7 +984,10 @@ func (b *builtinJSONArrayAppendSig) appendJSONArray(res types.BinaryJSON, p stri
// res.Extract will return a json object instead of an array if there is an object at path pathExpr.
// JSON_ARRAY_APPEND({"a": "b"}, "$", {"b": "c"}) => [{"a": "b"}, {"b", "c"}]
// We should wrap them to a single array first.
obj = types.CreateBinaryJSON([]interface{}{obj})
obj, err = types.CreateBinaryJSONWithCheck([]interface{}{obj})
if err != nil {
return res, true, err
}
}

obj = types.MergeBinaryJSON([]types.BinaryJSON{obj, v})
Expand Down
12 changes: 10 additions & 2 deletions expression/builtin_json_vec.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,11 @@ func (b *builtinJSONArraySig) vecEvalJSON(input *chunk.Chunk, result *chunk.Colu
}
result.ReserveJSON(nr)
for i := 0; i < nr; i++ {
result.AppendJSON(types.CreateBinaryJSON(jsons[i]))
bj, err := types.CreateBinaryJSONWithCheck(jsons[i])
if err != nil {
return err
}
result.AppendJSON(bj)
}
return nil
}
Expand Down Expand Up @@ -537,7 +541,11 @@ func (b *builtinJSONObjectSig) vecEvalJSON(input *chunk.Chunk, result *chunk.Col
}

for i := 0; i < nr; i++ {
result.AppendJSON(types.CreateBinaryJSON(jsons[i]))
bj, err := types.CreateBinaryJSONWithCheck(jsons[i])
if err != nil {
return err
}
result.AppendJSON(bj)
}
return nil
}
Expand Down
36 changes: 36 additions & 0 deletions expression/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7532,6 +7532,42 @@ func TestCastRealAsTime(t *testing.T) {
"<nil> <nil> <nil>"))
}

func TestJSONDepth(t *testing.T) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
tk.MustExec("use test")
tk.MustExec("create table t(a JSON)")
tk.MustGetErrCode(`insert into t
with recursive c1 as (select cast(1 as signed) c, json_array(1) as a
union
select c + 1, json_array_insert(a, concat('$', repeat('[0]', c)), json_array(1))
from c1
where c < 101)
select a from c1 where c > 100;`, errno.ErrJSONDocumentTooDeep)
tk.MustExec(`insert into t
with recursive c1 as (select cast(1 as signed) c, json_array(1) as a
union
select c + 1, json_array_insert(a, concat('$', repeat('[0]', c)), json_array(1))
from c1
where c < 100)
select a from c1 where c > 99;`)

err := tk.QueryToErr(`select json_array(a, 1) from t`)
require.Error(t, err)
// FIXME: mysql client shows the error.
//err = tk.QueryToErr(`select json_objectagg(1, a) from t;`)
//require.Error(t, err)
err = tk.QueryToErr(`select json_object(1, a) from t;`)
require.Error(t, err)
err = tk.QueryToErr(`select json_set(a, concat('$', repeat('[0]', 100)), json_array(json_array(3))) from t;`)
require.Error(t, err)
err = tk.QueryToErr(`select json_array_append(a, concat('$', repeat('[0]', 100)), 1) from t;`)
require.Error(t, err)
// FIXME: mysql client shows the error.
//err = tk.QueryToErr(`select json_arrayagg(a) from t;`)
//require.Error(t, err)
}

func TestCastJSONTimeDuration(t *testing.T) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
Expand Down
28 changes: 21 additions & 7 deletions types/json_binary.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ import (

var jsonZero = CreateBinaryJSON(uint64(0))

const maxJSONDepth = 100

// BinaryJSON represents a binary encoded JSON object.
// It can be randomly accessed without deserialization.
type BinaryJSON struct {
Expand Down Expand Up @@ -514,14 +516,12 @@ func (bj *BinaryJSON) UnmarshalJSON(data []byte) error {
if err != nil {
return errors.Trace(err)
}
buf := make([]byte, 0, len(data))
var typeCode JSONTypeCode
typeCode, buf, err = appendBinaryJSON(buf, in)
newBj, err := CreateBinaryJSONWithCheck(in)
if err != nil {
return errors.Trace(err)
}
bj.TypeCode = typeCode
bj.Value = buf
bj.TypeCode = newBj.TypeCode
bj.Value = newBj.Value
return nil
}

Expand Down Expand Up @@ -557,11 +557,25 @@ func (bj BinaryJSON) HashValue(buf []byte) []byte {

// CreateBinaryJSON creates a BinaryJSON from interface.
func CreateBinaryJSON(in interface{}) BinaryJSON {
typeCode, buf, err := appendBinaryJSON(nil, in)
bj, err := CreateBinaryJSONWithCheck(in)
if err != nil {
panic(err)
}
return BinaryJSON{TypeCode: typeCode, Value: buf}
return bj
}

// CreateBinaryJSONWithCheck creates a BinaryJSON from interface with error check.
func CreateBinaryJSONWithCheck(in interface{}) (BinaryJSON, error) {
typeCode, buf, err := appendBinaryJSON(nil, in)
if err != nil {
return BinaryJSON{}, err
}
bj := BinaryJSON{TypeCode: typeCode, Value: buf}
// GetElemDepth always returns +1.
if bj.GetElemDepth()-1 > maxJSONDepth {
return BinaryJSON{}, ErrJSONDocumentTooDeep
}
return bj, nil
}

func appendBinaryJSON(buf []byte, in interface{}) (JSONTypeCode, []byte, error) {
Expand Down
3 changes: 3 additions & 0 deletions types/json_binary_functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@ func (bj BinaryJSON) Modify(pathExprList []JSONPathExpression, values []BinaryJS
return BinaryJSON{}, modifier.err
}
}
if bj.GetElemDepth()-1 > maxJSONDepth {
return bj, ErrJSONDocumentTooDeep
}
return bj, nil
}

Expand Down
2 changes: 2 additions & 0 deletions types/json_constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ var (
ErrInvalidJSONContainsPathType = dbterror.ClassJSON.NewStd(mysql.ErrInvalidJSONContainsPathType)
// ErrJSONDocumentNULLKey means that json's key is null
ErrJSONDocumentNULLKey = dbterror.ClassJSON.NewStd(mysql.ErrJSONDocumentNULLKey)
// ErrJSONDocumentTooDeep means that json's depth is too deep.
ErrJSONDocumentTooDeep = dbterror.ClassJSON.NewStd(mysql.ErrJSONDocumentTooDeep)
// ErrJSONObjectKeyTooLong means JSON object with key length >= 65536 which is not yet supported.
ErrJSONObjectKeyTooLong = dbterror.ClassTypes.NewStdErr(mysql.ErrJSONObjectKeyTooLong, mysql.MySQLErrName[mysql.ErrJSONObjectKeyTooLong])
// ErrInvalidJSONPathArrayCell means invalid JSON path for an array cell.
Expand Down