Skip to content

Commit e36455c

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 3f6fe9f commit e36455c

File tree

2 files changed

+54
-0
lines changed

2 files changed

+54
-0
lines changed

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

+23
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,12 @@
4747
*/
4848
class JsonValueWriter {
4949

50+
private static final int DEFAULT_MAX_NESTING_DEPTH = 1000;
51+
5052
private final Appendable out;
5153

54+
private final int maxNestingDepth;
55+
5256
private MemberPath path = MemberPath.ROOT;
5357

5458
private final Deque<JsonWriterFiltersAndProcessors> filtersAndProcessors = new ArrayDeque<>();
@@ -60,7 +64,18 @@ class JsonValueWriter {
6064
* @param out the {@link Appendable} used to receive the JSON output
6165
*/
6266
JsonValueWriter(Appendable out) {
67+
this(out, DEFAULT_MAX_NESTING_DEPTH);
68+
}
69+
70+
/**
71+
* Create a new {@link JsonValueWriter} instance.
72+
* @param out the {@link Appendable} used to receive the JSON output
73+
* @param maxNestingDepth the maximum allowed nesting depth for JSON objects and
74+
* arrays
75+
*/
76+
JsonValueWriter(Appendable out, int maxNestingDepth) {
6377
this.out = out;
78+
this.maxNestingDepth = maxNestingDepth;
6479
}
6580

6681
void pushProcessors(JsonWriterFiltersAndProcessors jsonProcessors) {
@@ -144,6 +159,7 @@ else if (value instanceof Number || value instanceof Boolean) {
144159
*/
145160
void start(Series series) {
146161
if (series != null) {
162+
validateNestingDepth();
147163
this.activeSeries.push(new ActiveSeries(series));
148164
append(series.openChar);
149165
}
@@ -271,6 +287,13 @@ private void writeString(Object value) {
271287
}
272288
}
273289

290+
private void validateNestingDepth() {
291+
if (this.activeSeries.size() > this.maxNestingDepth) {
292+
throw new IllegalStateException("JSON nesting depth (%s) exceeds maximum depth of %s (current path: %s)"
293+
.formatted(this.activeSeries.size(), this.maxNestingDepth, this.path));
294+
}
295+
}
296+
274297
private void append(String value) {
275298
try {
276299
this.out.append(value);

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

+31
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.io.File;
2020
import java.nio.file.Path;
21+
import java.util.ArrayList;
2122
import java.util.LinkedHashMap;
2223
import java.util.LinkedHashSet;
2324
import java.util.List;
@@ -253,6 +254,36 @@ void writeJavaNioPathShouldBeSerializedAsString() {
253254
.isEqualTo(quoted("a\\%1$sb\\%1$sc".formatted(File.separator)));
254255
}
255256

257+
@Test
258+
void illegalStateExceptionShouldBeThrownWhenCollectionExceededNestingDepth() {
259+
JsonValueWriter writer = new JsonValueWriter(new StringBuilder(), 128);
260+
List<Object> list = new ArrayList<>();
261+
list.add(list);
262+
assertThatIllegalStateException().isThrownBy(() -> writer.write(list))
263+
.withMessageStartingWith(
264+
"JSON nesting depth (129) exceeds maximum depth of 128 (current path: [0][0][0][0][0][0][0][0][0][0][0][0]");
265+
}
266+
267+
@Test
268+
void illegalStateExceptionShouldBeThrownWhenMapExceededNestingDepth() {
269+
JsonValueWriter writer = new JsonValueWriter(new StringBuilder(), 128);
270+
Map<String, Object> map = new LinkedHashMap<>();
271+
map.put("foo", Map.of("bar", map));
272+
assertThatIllegalStateException().isThrownBy(() -> writer.write(map))
273+
.withMessageStartingWith(
274+
"JSON nesting depth (129) exceeds maximum depth of 128 (current path: foo.bar.foo.bar.foo.bar.foo");
275+
}
276+
277+
@Test
278+
void illegalStateExceptionShouldBeThrownWhenIterableExceededNestingDepth() {
279+
JsonValueWriter writer = new JsonValueWriter(new StringBuilder(), 128);
280+
List<Object> list = new ArrayList<>();
281+
list.add(list);
282+
assertThatIllegalStateException().isThrownBy(() -> writer.write((Iterable<Object>) list::iterator))
283+
.withMessageStartingWith(
284+
"JSON nesting depth (129) exceeds maximum depth of 128 (current path: [0][0][0][0][0][0][0][0][0][0][0][0]");
285+
}
286+
256287
private <V> String write(V value) {
257288
return doWrite((valueWriter) -> valueWriter.write(value));
258289
}

0 commit comments

Comments
 (0)