Protected against JsonValueWriter stack overflow
Add validation for the maximum JSON nesting depth in the JsonValueWriter. This helps prevent a StackOverflowError that can potentially occur due to excessive recursion when dealing with deeply nested JSON structures. See gh-44627 Signed-off-by: Dmytro Nosan <dimanosan@gmail.com>
This commit is contained in:
committed by
Phillip Webb
parent
b2e65f665a
commit
b5e0eed8e7
@@ -47,8 +47,12 @@ import org.springframework.util.function.ThrowingConsumer;
|
||||
*/
|
||||
class JsonValueWriter {
|
||||
|
||||
private static final int DEFAULT_MAX_NESTING_DEPTH = 1000;
|
||||
|
||||
private final Appendable out;
|
||||
|
||||
private final int maxNestingDepth;
|
||||
|
||||
private MemberPath path = MemberPath.ROOT;
|
||||
|
||||
private final Deque<JsonWriterFiltersAndProcessors> filtersAndProcessors = new ArrayDeque<>();
|
||||
@@ -60,7 +64,18 @@ class JsonValueWriter {
|
||||
* @param out the {@link Appendable} used to receive the JSON output
|
||||
*/
|
||||
JsonValueWriter(Appendable out) {
|
||||
this(out, DEFAULT_MAX_NESTING_DEPTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link JsonValueWriter} instance.
|
||||
* @param out the {@link Appendable} used to receive the JSON output
|
||||
* @param maxNestingDepth the maximum allowed nesting depth for JSON objects and
|
||||
* arrays
|
||||
*/
|
||||
JsonValueWriter(Appendable out, int maxNestingDepth) {
|
||||
this.out = out;
|
||||
this.maxNestingDepth = maxNestingDepth;
|
||||
}
|
||||
|
||||
void pushProcessors(JsonWriterFiltersAndProcessors jsonProcessors) {
|
||||
@@ -145,6 +160,7 @@ class JsonValueWriter {
|
||||
*/
|
||||
void start(Series series) {
|
||||
if (series != null) {
|
||||
validateNestingDepth();
|
||||
this.activeSeries.push(new ActiveSeries(series));
|
||||
append(series.openChar);
|
||||
}
|
||||
@@ -272,6 +288,13 @@ class JsonValueWriter {
|
||||
}
|
||||
}
|
||||
|
||||
private void validateNestingDepth() {
|
||||
if (this.activeSeries.size() > this.maxNestingDepth) {
|
||||
throw new IllegalStateException("JSON nesting depth (%s) exceeds maximum depth of %s (current path: %s)"
|
||||
.formatted(this.activeSeries.size(), this.maxNestingDepth, this.path));
|
||||
}
|
||||
}
|
||||
|
||||
private void append(String value) {
|
||||
try {
|
||||
this.out.append(value);
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.boot.json;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
@@ -253,6 +254,36 @@ class JsonValueWriterTests {
|
||||
.isEqualTo(quoted("a\\%1$sb\\%1$sc".formatted(File.separator)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void illegalStateExceptionShouldBeThrownWhenCollectionExceededNestingDepth() {
|
||||
JsonValueWriter writer = new JsonValueWriter(new StringBuilder(), 128);
|
||||
List<Object> list = new ArrayList<>();
|
||||
list.add(list);
|
||||
assertThatIllegalStateException().isThrownBy(() -> writer.write(list))
|
||||
.withMessageStartingWith(
|
||||
"JSON nesting depth (129) exceeds maximum depth of 128 (current path: [0][0][0][0][0][0][0][0][0][0][0][0]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void illegalStateExceptionShouldBeThrownWhenMapExceededNestingDepth() {
|
||||
JsonValueWriter writer = new JsonValueWriter(new StringBuilder(), 128);
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("foo", Map.of("bar", map));
|
||||
assertThatIllegalStateException().isThrownBy(() -> writer.write(map))
|
||||
.withMessageStartingWith(
|
||||
"JSON nesting depth (129) exceeds maximum depth of 128 (current path: foo.bar.foo.bar.foo.bar.foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void illegalStateExceptionShouldBeThrownWhenIterableExceededNestingDepth() {
|
||||
JsonValueWriter writer = new JsonValueWriter(new StringBuilder(), 128);
|
||||
List<Object> list = new ArrayList<>();
|
||||
list.add(list);
|
||||
assertThatIllegalStateException().isThrownBy(() -> writer.write((Iterable<Object>) list::iterator))
|
||||
.withMessageStartingWith(
|
||||
"JSON nesting depth (129) exceeds maximum depth of 128 (current path: [0][0][0][0][0][0][0][0][0][0][0][0]");
|
||||
}
|
||||
|
||||
private <V> String write(V value) {
|
||||
return doWrite((valueWriter) -> valueWriter.write(value));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user