Skip to content

Commit 1777be4

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 1777be4

File tree

2 files changed

+56
-2
lines changed

2 files changed

+56
-2
lines changed

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

+24-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,8 +46,12 @@
4646
*/
4747
class JsonValueWriter {
4848

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

53+
private final int maxNestingDepth;
54+
5155
private MemberPath path = MemberPath.ROOT;
5256

5357
private final Deque<JsonWriterFiltersAndProcessors> filtersAndProcessors = new ArrayDeque<>();
@@ -59,7 +63,18 @@ class JsonValueWriter {
5963
* @param out the {@link Appendable} used to receive the JSON output
6064
*/
6165
JsonValueWriter(Appendable out) {
66+
this(out, DEFAULT_MAX_NESTING_DEPTH);
67+
}
68+
69+
/**
70+
* Create a new {@link JsonValueWriter} instance.
71+
* @param out the {@link Appendable} used to receive the JSON output
72+
* @param maxNestingDepth the maximum allowed nesting depth for JSON objects and
73+
* arrays
74+
*/
75+
JsonValueWriter(Appendable out, int maxNestingDepth) {
6276
this.out = out;
77+
this.maxNestingDepth = maxNestingDepth;
6378
}
6479

6580
void pushProcessors(JsonWriterFiltersAndProcessors jsonProcessors) {
@@ -140,6 +155,7 @@ else if (value instanceof Number || value instanceof Boolean) {
140155
*/
141156
void start(Series series) {
142157
if (series != null) {
158+
validateNestingDepth();
143159
this.activeSeries.push(new ActiveSeries(series));
144160
append(series.openChar);
145161
}
@@ -267,6 +283,13 @@ private void writeString(Object value) {
267283
}
268284
}
269285

286+
private void validateNestingDepth() {
287+
if (this.activeSeries.size() > this.maxNestingDepth) {
288+
throw new IllegalStateException("JSON nesting depth (%s) exceeds maximum depth of %s (current path: %s)"
289+
.formatted(this.activeSeries.size(), this.maxNestingDepth, this.path));
290+
}
291+
}
292+
270293
private void append(String value) {
271294
try {
272295
this.out.append(value);

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

+32-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,36 @@ void endWhenNotStartedThrowsException() {
240241
.isThrownBy(() -> valueWriter.end(Series.ARRAY)));
241242
}
242243

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

0 commit comments

Comments
 (0)