Skip to content

Commit b3f8244

Browse files
committed
Add protection against StackOverflowError in JsonValueWriter
Signed-off-by: Dmytro Nosan <[email protected]>
1 parent d097870 commit b3f8244

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 illegalStateExceptionShouldBeThrownWhenCollectionExceededNestedDepth() {
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 illegalStateExceptionShouldBeThrownWhenMapExceededNestedDepth() {
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 illegalStateExceptionShouldBeThrownWhenIterableExceededNestedDepth() {
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)