Skip to content

Commit ea08a97

Browse files
committed
Add protection against StackOverflowError in JsonValueWriter
This commit adds validation for the maximum JSON nesting depth in the JsonValueWriter. This helps prevent StackOverflowError that can potentially occur due to excessive recursion when dealing with deeply nested JSON structures. Signed-off-by: Dmytro Nosan <[email protected]>
1 parent e06244d commit ea08a97

File tree

2 files changed

+41
-2
lines changed

2 files changed

+41
-2
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/json/JsonValueWriter.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -46,6 +46,8 @@
4646
*/
4747
class JsonValueWriter {
4848

49+
private static final int MAX_NESTING_DEPTH = 1000;
50+
4951
private final Appendable out;
5052

5153
private MemberPath path = MemberPath.ROOT;
@@ -140,6 +142,7 @@ else if (value instanceof Number || value instanceof Boolean) {
140142
*/
141143
void start(Series series) {
142144
if (series != null) {
145+
validateNestingDepth();
143146
this.activeSeries.push(new ActiveSeries(series));
144147
append(series.openChar);
145148
}
@@ -267,6 +270,13 @@ private void writeString(Object value) {
267270
}
268271
}
269272

273+
private void validateNestingDepth() {
274+
if (this.activeSeries.size() > MAX_NESTING_DEPTH) {
275+
throw new IllegalStateException("JSON nesting depth (%s) exceeds maximum depth of %s (current path: %s)"
276+
.formatted(this.activeSeries.size(), MAX_NESTING_DEPTH, this.path));
277+
}
278+
}
279+
270280
private void append(String value) {
271281
try {
272282
this.out.append(value);

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/json/JsonValueWriterTests.java

+30-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.boot.json;
1818

19+
import java.util.ArrayList;
1920
import java.util.LinkedHashMap;
2021
import java.util.LinkedHashSet;
2122
import java.util.List;
@@ -240,6 +241,34 @@ void endWhenNotStartedThrowsException() {
240241
.isThrownBy(() -> valueWriter.end(Series.ARRAY)));
241242
}
242243

244+
@Test
245+
void illegalStateExceptionShouldBeThrownWhenCollectionExceededNestingDepth() {
246+
List<Object> list = new ArrayList<>();
247+
list.add(list);
248+
doWrite((valueWriter) -> assertThatIllegalStateException().isThrownBy(() -> valueWriter.write(list))
249+
.withMessageStartingWith(
250+
"JSON nesting depth (1001) exceeds maximum depth of 1000 (current path: [0][0][0][0][0][0][0][0][0][0][0][0]"));
251+
}
252+
253+
@Test
254+
void illegalStateExceptionShouldBeThrownWhenMapExceededNestingDepth() {
255+
Map<String, Object> map = new LinkedHashMap<>();
256+
map.put("foo", Map.of("bar", map));
257+
doWrite((valueWriter) -> assertThatIllegalStateException().isThrownBy(() -> valueWriter.write(map))
258+
.withMessageStartingWith(
259+
"JSON nesting depth (1001) exceeds maximum depth of 1000 (current path: foo.bar.foo.bar.foo.bar.foo"));
260+
}
261+
262+
@Test
263+
void illegalStateExceptionShouldBeThrownWhenIterableExceededNestingDepth() {
264+
List<Object> list = new ArrayList<>();
265+
list.add(list);
266+
doWrite((valueWriter) -> assertThatIllegalStateException()
267+
.isThrownBy(() -> valueWriter.write((Iterable<Object>) list::iterator))
268+
.withMessageStartingWith(
269+
"JSON nesting depth (1001) exceeds maximum depth of 1000 (current path: [0][0][0][0][0][0][0][0][0][0][0][0]"));
270+
}
271+
243272
private <V> String write(V value) {
244273
return doWrite((valueWriter) -> valueWriter.write(value));
245274
}

0 commit comments

Comments
 (0)