Skip to content

Commit 84afaa9

Browse files
liggittmvdan
authored andcommitted
encoding/json: limit max nesting depth
Limit the maximum nesting depth when parsing to protect against stack overflow, permitted by https://tools.ietf.org/html/rfc7159#section-9 A nesting depth limit of 10,000 was chosen to be a conservative balance between avoiding stack overflow and avoiding impacting legitimate JSON documents. 10,000 is less than 1% of the experimental stack depth limit with the default stack size: * On 64-bit systems, the default stack limit is 1GB, which allows ~2,800,000 frames of recursive parsing * On 32-bit systems, the default stack limit is 250MB, which allows ~1,100,000 frames of recursive parsing Fixes #31789 Change-Id: I4f5a90e89dcb4ab1a957ad9d02e1fa0efafaccf6 Reviewed-on: https://go-review.googlesource.com/c/go/+/199837 Run-TryBot: Daniel Martí <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Daniel Martí <[email protected]>
1 parent 531b6d3 commit 84afaa9

File tree

2 files changed

+109
-6
lines changed

2 files changed

+109
-6
lines changed

src/encoding/json/decode_test.go

+96
Original file line numberDiff line numberDiff line change
@@ -2431,3 +2431,99 @@ func TestUnmarshalMapWithTextUnmarshalerStringKey(t *testing.T) {
24312431
t.Errorf(`Key "foo" is not existed in map: %v`, p)
24322432
}
24332433
}
2434+
2435+
func TestUnmarshalMaxDepth(t *testing.T) {
2436+
testcases := []struct {
2437+
name string
2438+
data string
2439+
errMaxDepth bool
2440+
}{
2441+
{
2442+
name: "ArrayUnderMaxNestingDepth",
2443+
data: `{"a":` + strings.Repeat(`[`, 10000-1) + strings.Repeat(`]`, 10000-1) + `}`,
2444+
errMaxDepth: false,
2445+
},
2446+
{
2447+
name: "ArrayOverMaxNestingDepth",
2448+
data: `{"a":` + strings.Repeat(`[`, 10000) + strings.Repeat(`]`, 10000) + `}`,
2449+
errMaxDepth: true,
2450+
},
2451+
{
2452+
name: "ArrayOverStackDepth",
2453+
data: `{"a":` + strings.Repeat(`[`, 3000000) + strings.Repeat(`]`, 3000000) + `}`,
2454+
errMaxDepth: true,
2455+
},
2456+
{
2457+
name: "ObjectUnderMaxNestingDepth",
2458+
data: `{"a":` + strings.Repeat(`{"a":`, 10000-1) + `0` + strings.Repeat(`}`, 10000-1) + `}`,
2459+
errMaxDepth: false,
2460+
},
2461+
{
2462+
name: "ObjectOverMaxNestingDepth",
2463+
data: `{"a":` + strings.Repeat(`{"a":`, 10000) + `0` + strings.Repeat(`}`, 10000) + `}`,
2464+
errMaxDepth: true,
2465+
},
2466+
{
2467+
name: "ObjectOverStackDepth",
2468+
data: `{"a":` + strings.Repeat(`{"a":`, 3000000) + `0` + strings.Repeat(`}`, 3000000) + `}`,
2469+
errMaxDepth: true,
2470+
},
2471+
}
2472+
2473+
targets := []struct {
2474+
name string
2475+
newValue func() interface{}
2476+
}{
2477+
{
2478+
name: "unstructured",
2479+
newValue: func() interface{} {
2480+
var v interface{}
2481+
return &v
2482+
},
2483+
},
2484+
{
2485+
name: "typed named field",
2486+
newValue: func() interface{} {
2487+
v := struct {
2488+
A interface{} `json:"a"`
2489+
}{}
2490+
return &v
2491+
},
2492+
},
2493+
{
2494+
name: "typed missing field",
2495+
newValue: func() interface{} {
2496+
v := struct {
2497+
B interface{} `json:"b"`
2498+
}{}
2499+
return &v
2500+
},
2501+
},
2502+
{
2503+
name: "custom unmarshaler",
2504+
newValue: func() interface{} {
2505+
v := unmarshaler{}
2506+
return &v
2507+
},
2508+
},
2509+
}
2510+
2511+
for _, tc := range testcases {
2512+
for _, target := range targets {
2513+
t.Run(target.name+"-"+tc.name, func(t *testing.T) {
2514+
err := Unmarshal([]byte(tc.data), target.newValue())
2515+
if !tc.errMaxDepth {
2516+
if err != nil {
2517+
t.Errorf("unexpected error: %v", err)
2518+
}
2519+
} else {
2520+
if err == nil {
2521+
t.Errorf("expected error containing 'exceeded max depth', got none")
2522+
} else if !strings.Contains(err.Error(), "exceeded max depth") {
2523+
t.Errorf("expected error containing 'exceeded max depth', got: %v", err)
2524+
}
2525+
}
2526+
})
2527+
}
2528+
}
2529+
}

src/encoding/json/scanner.go

+13-6
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ const (
139139
parseArrayValue // parsing array value
140140
)
141141

142+
// This limits the max nesting depth to prevent stack overflow.
143+
// This is permitted by https://tools.ietf.org/html/rfc7159#section-9
144+
const maxNestingDepth = 10000
145+
142146
// reset prepares the scanner for use.
143147
// It must be called before calling s.step.
144148
func (s *scanner) reset() {
@@ -168,8 +172,13 @@ func (s *scanner) eof() int {
168172
}
169173

170174
// pushParseState pushes a new parse state p onto the parse stack.
171-
func (s *scanner) pushParseState(p int) {
172-
s.parseState = append(s.parseState, p)
175+
// an error state is returned if maxNestingDepth was exceeded, otherwise successState is returned.
176+
func (s *scanner) pushParseState(c byte, newParseState int, successState int) int {
177+
s.parseState = append(s.parseState, newParseState)
178+
if len(s.parseState) <= maxNestingDepth {
179+
return successState
180+
}
181+
return s.error(c, "exceeded max depth")
173182
}
174183

175184
// popParseState pops a parse state (already obtained) off the stack
@@ -208,12 +217,10 @@ func stateBeginValue(s *scanner, c byte) int {
208217
switch c {
209218
case '{':
210219
s.step = stateBeginStringOrEmpty
211-
s.pushParseState(parseObjectKey)
212-
return scanBeginObject
220+
return s.pushParseState(c, parseObjectKey, scanBeginObject)
213221
case '[':
214222
s.step = stateBeginValueOrEmpty
215-
s.pushParseState(parseArrayValue)
216-
return scanBeginArray
223+
return s.pushParseState(c, parseArrayValue, scanBeginArray)
217224
case '"':
218225
s.step = stateInString
219226
return scanBeginLiteral

0 commit comments

Comments
 (0)