This is an automated email from the ASF dual-hosted git repository. coheigea pushed a commit to branch 3.6.x-fixes in repository https://gitbox.apache.org/repos/asf/cxf.git
commit 858617654772bbda32f17251ebe9e02ec1a4898d Author: Colm O hEigeartaigh <[email protected]> AuthorDate: Wed Jun 3 12:57:17 2026 +0100 Add default constraints on the number of JSON objects/array entries that can be parsed (#3172) (cherry picked from commit 2b8e8b18ddb5f82dd28e34c48cfd2e194910a6a8) (cherry picked from commit afd8a4a010e234536c30cce5e2dfb7bc04ad6d02) --- .../json/basic/JsonMapObjectReaderWriter.java | 43 +++++++++++++++- .../json/basic/JsonMapObjectReaderWriterTest.java | 58 ++++++++++++++++++++++ 2 files changed, 99 insertions(+), 2 deletions(-) diff --git a/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java b/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java index 117c6692c4c..1cb5a503ba0 100644 --- a/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java +++ b/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java @@ -25,6 +25,7 @@ import java.io.UncheckedIOException; import java.util.*; import org.apache.cxf.common.util.StringUtils; +import org.apache.cxf.common.util.SystemPropertyAction; import org.apache.cxf.helpers.IOUtils; @@ -37,6 +38,10 @@ public class JsonMapObjectReaderWriter { * thread stack with unbounded recursion. */ static final int MAX_RECURSION_DEPTH = 500; + static final int DEFAULT_MAX_OBJECT_KEYS = 10_000; + static final int DEFAULT_MAX_ARRAY_ELEMENTS = 10_000; + static final String MAX_OBJECT_KEYS_PROPERTY = "org.apache.cxf.jaxrs.json.basic.maxObjectKeys"; + static final String MAX_ARRAY_ELEMENTS_PROPERTY = "org.apache.cxf.jaxrs.json.basic.maxArrayElements"; private static final Set<Character> ESCAPED_CHARS; private static final char DQUOTE = '"'; private static final char COMMA = ','; @@ -48,6 +53,8 @@ public class JsonMapObjectReaderWriter { private static final char ESCAPE = '\\'; private static final String NULL_VALUE = "null"; private boolean format; + private final int maxObjectKeys; + private final int maxArrayElements; static { Set<Character> chars = new HashSet<>(); @@ -63,10 +70,25 @@ public class JsonMapObjectReaderWriter { } public JsonMapObjectReaderWriter() { - + this(false); } public JsonMapObjectReaderWriter(boolean format) { this.format = format; + this.maxObjectKeys = readConfiguredPositiveLimit(MAX_OBJECT_KEYS_PROPERTY, DEFAULT_MAX_OBJECT_KEYS); + this.maxArrayElements = readConfiguredPositiveLimit(MAX_ARRAY_ELEMENTS_PROPERTY, DEFAULT_MAX_ARRAY_ELEMENTS); + } + + private static int readConfiguredPositiveLimit(String propertyName, int defaultValue) { + String configured = SystemPropertyAction.getPropertyOrNull(propertyName); + if (configured == null) { + return defaultValue; + } + try { + int parsed = Integer.parseInt(configured.trim()); + return parsed > 0 ? parsed : defaultValue; + } catch (NumberFormatException ex) { + return defaultValue; + } } public String toJson(JsonMapObject obj) { @@ -138,7 +160,6 @@ public class JsonMapObjectReaderWriter { out.append(ARRAY_END); } - @SuppressWarnings("unchecked") protected void toJsonInternal(Output out, Object value, boolean hasNext) { toJsonInternal(out, value, hasNext, 0); } @@ -225,6 +246,7 @@ public class JsonMapObjectReaderWriter { throw new UncheckedIOException(new IOException( "JSON nesting depth exceeds maximum of " + MAX_RECURSION_DEPTH)); } + int keyCount = 0; for (int i = 0; i < json.length(); i++) { if (Character.isWhitespace(json.charAt(i))) { continue; @@ -250,20 +272,28 @@ public class JsonMapObjectReaderWriter { MapSettable nextMap = new MapSettable(); readJsonObjectAsSettable(nextMap, newJson, depth + 1); values.put(name, nextMap.map); + keyCount++; i = closingIndex + 1; } else if (json.charAt(sepIndex + j) == ARRAY_START) { int closingIndex = getClosingIndex(json, ARRAY_START, ARRAY_END, sepIndex + j); closingIndex = requireClosingIndex(closingIndex, ARRAY_START, ARRAY_END); String newJson = json.substring(sepIndex + j + 1, closingIndex); values.put(name, internalFromJsonAsList(name, newJson, depth + 1)); + keyCount++; i = closingIndex + 1; } else { int commaIndex = getCommaIndex(json, sepIndex + j); Object value = readPrimitiveValue(name, json, sepIndex + j, commaIndex); values.put(name, value); + keyCount++; i = commaIndex + 1; } + if (keyCount > maxObjectKeys) { + throw new UncheckedIOException(new IOException( + "JSON object key count exceeds maximum of " + maxObjectKeys)); + } + } } @@ -277,6 +307,7 @@ public class JsonMapObjectReaderWriter { "JSON nesting depth exceeds maximum of " + MAX_RECURSION_DEPTH)); } List<Object> values = new LinkedList<>(); + int elementCount = 0; for (int i = 0; i < json.length(); i++) { if (Character.isWhitespace(json.charAt(i))) { continue; @@ -287,18 +318,26 @@ public class JsonMapObjectReaderWriter { MapSettable nextMap = new MapSettable(); readJsonObjectAsSettable(nextMap, json.substring(i + 1, closingIndex), depth + 1); values.add(nextMap.map); + elementCount++; i = closingIndex + 1; } else if (json.charAt(i) == ARRAY_START) { int closingIndex = getClosingIndex(json, ARRAY_START, ARRAY_END, i); closingIndex = requireClosingIndex(closingIndex, ARRAY_START, ARRAY_END); values.add(internalFromJsonAsList(name, json.substring(i + 1, closingIndex), depth + 1)); + elementCount++; i = closingIndex + 1; } else { int commaIndex = getCommaIndex(json, i); Object value = readPrimitiveValue(name, json, i, commaIndex); values.add(value); + elementCount++; i = commaIndex; } + + if (elementCount > maxArrayElements) { + throw new UncheckedIOException(new IOException( + "JSON array element count exceeds maximum of " + maxArrayElements)); + } } return values; diff --git a/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java b/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java index 124a9b1669a..aca672531cd 100644 --- a/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java +++ b/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java @@ -442,6 +442,64 @@ public class JsonMapObjectReaderWriterTest { new JsonMapObjectReaderWriter().toJson(createNestedMap(20000)); } + @Test(expected = UncheckedIOException.class) + public void testObjectKeyLimitExceededThrowsUncheckedIOException() { + StringBuilder sb = new StringBuilder(JsonMapObjectReaderWriter.DEFAULT_MAX_OBJECT_KEYS * 10); + sb.append('{'); + for (int i = 0; i <= JsonMapObjectReaderWriter.DEFAULT_MAX_OBJECT_KEYS; i++) { + if (i > 0) { + sb.append(','); + } + sb.append('"').append('k').append(i).append('"').append(':').append('1'); + } + sb.append('}'); + + new JsonMapObjectReaderWriter().fromJson(sb.toString()); + } + + @Test(expected = UncheckedIOException.class) + public void testArrayElementLimitExceededThrowsUncheckedIOException() { + StringBuilder sb = new StringBuilder(JsonMapObjectReaderWriter.DEFAULT_MAX_ARRAY_ELEMENTS * 3); + sb.append('{').append('"').append('a').append('"').append(':').append('['); + for (int i = 0; i <= JsonMapObjectReaderWriter.DEFAULT_MAX_ARRAY_ELEMENTS; i++) { + if (i > 0) { + sb.append(','); + } + sb.append('1'); + } + sb.append(']').append('}'); + + new JsonMapObjectReaderWriter().fromJson(sb.toString()); + } + + @Test + public void testConfiguredObjectAndArrayLimits() { + System.setProperty(JsonMapObjectReaderWriter.MAX_OBJECT_KEYS_PROPERTY, "2"); + System.setProperty(JsonMapObjectReaderWriter.MAX_ARRAY_ELEMENTS_PROPERTY, "2"); + try { + JsonMapObjectReaderWriter rw = new JsonMapObjectReaderWriter(); + rw.fromJson("{\"a\":1,\"b\":2}"); + rw.fromJson("{\"a\":[1,2]}"); + + try { + rw.fromJson("{\"a\":1,\"b\":2,\"c\":3}"); + fail("Expected object key count limit exception"); + } catch (UncheckedIOException ex) { + // expected + } + + try { + rw.fromJson("{\"a\":[1,2,3]}"); + fail("Expected array element count limit exception"); + } catch (UncheckedIOException ex) { + // expected + } + } finally { + System.clearProperty(JsonMapObjectReaderWriter.MAX_OBJECT_KEYS_PROPERTY); + System.clearProperty(JsonMapObjectReaderWriter.MAX_ARRAY_ELEMENTS_PROPERTY); + } + } + private Map<String, Object> createNestedMap(int depth) { Map<String, Object> root = new HashMap<>(); Map<String, Object> current = root;
