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;

Reply via email to