CVE-2022-42004: Deserialization of Untrusted Data (CWE-502)
value: In FasterXML jackson-databind before 2.13.4, resource exhaustion can occur because of a lack of a check in BeanDeserializer._deserializeFromArray to prevent use of deeply nested arrays. An application is vulnerable only with certain customized choices for deserialization.
CWE description: The product deserializes untrusted data without sufficiently verifying that the resulting data will be valid.
CVSS v3 base score: 7.5
Published: 2022-10-02T05:15Z
Repository: https://github.com/FasterXML/jackson-databind
Language: Java
Files changed: release-notes/VERSION-2.x, src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java, src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java, src/test/java/com/fasterxml/jackson/databind/deser/dos/DeepArrayWrappingForDeser3582Test.java
Commit date: 2022-08-23 20:54:42 -0700
Commit message: Fix #3582

Diff: diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x
index 420daf97cd..1b956c650a 100644
--- a/release-notes/VERSION-2.x
+++ b/release-notes/VERSION-2.x
@@ -11,10 +11,12 @@ Project: jackson-databind
  (reported by Jason H)
  (fix suggested by gsinghlulu@github)
 #3565: `Arrays.asList()` value deserialization has changed from mutable to
   immutable in 2.13
  (reported by JonasWilms@github)
+#3582: Add check in `BeanDeserializer._deserializeFromArray()` to prevent
+  use of deeply nested arrays
 
 2.13.3 (14-May-2022)
 
 #3412: Version 2.13.2 uses `Method.getParameterCount()` which is not supported on
   Android before API 26
diff --git a/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java b/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java
index f305ff5e38..b7cf2ac5b6 100644
--- a/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java
+++ b/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java
@@ -315,12 +315,14 @@ public enum DeserializationFeature implements ConfigFeature
     /**
      * Feature that determines whether it is acceptable to coerce single value array (in JSON)
      * values to the corresponding value type.  This is basically the opposite of the {@link #ACCEPT_SINGLE_VALUE_AS_ARRAY}
      * feature.  If more than one value is found in the array, a JsonMappingException is thrown.
      * <p>
+     * NOTE: only <b>single</b> wrapper Array is allowed: if multiple attempted, exception
+     * will be thrown.
      * 
-     * Feature is disabled by default
+     * Feature is disabled by default.
      * @since 2.4
      */
     UNWRAP_SINGLE_VALUE_ARRAYS(false),
 
     /**
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java
index 943aa98aca..b752d06edb 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java
@@ -6,10 +6,11 @@ import java.util.*;
 import com.fasterxml.jackson.core.*;
 import com.fasterxml.jackson.databind.*;
 import com.fasterxml.jackson.databind.cfg.CoercionAction;
 import com.fasterxml.jackson.databind.deser.impl.*;
 import com.fasterxml.jackson.databind.deser.impl.ReadableObjectId.Referring;
+import com.fasterxml.jackson.databind.util.ClassUtil;
 import com.fasterxml.jackson.databind.util.IgnorePropertiesUtil;
 import com.fasterxml.jackson.databind.util.NameTransformer;
 import com.fasterxml.jackson.databind.util.TokenBuffer;
 
 /**
@@ -626,10 +627,19 @@ public class BeanDeserializer
                 default:
                 }
                 return ctxt.handleUnexpectedToken(getValueType(ctxt), JsonToken.START_ARRAY, p, null);
             }
             if (unwrap) {
+                // 23-Aug-2022, tatu: To prevent unbounded nested arrays, we better
+                //   check there is NOT another START_ARRAY lurking there..
+                if (p.nextToken() == JsonToken.START_ARRAY) {
+                    JavaType targetType = getValueType(ctxt);
+                    return ctxt.handleUnexpectedToken(targetType, JsonToken.START_ARRAY, p,
+"Cannot deserialize value of type %s from deeply-nested JSON Array: only single wrapper allowed with `%s`",
+                            ClassUtil.getTypeDescription(targetType),
+                                    "DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS");
+                }
                 final Object value = deserialize(p, ctxt);
                 if (p.nextToken() != JsonToken.END_ARRAY) {
                     handleMissingEndArrayForSingle(p, ctxt);
                 }
                 return value;
diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/dos/DeepArrayWrappingForDeser3582Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/dos/DeepArrayWrappingForDeser3582Test.java
index 91444f6a8c..3cfe1b3b77 100644
--- a/src/test/java/com/fasterxml/jackson/databind/deser/dos/DeepArrayWrappingForDeser3582Test.java
+++ b/src/test/java/com/fasterxml/jackson/databind/deser/dos/DeepArrayWrappingForDeser3582Test.java
@@ -1,25 +1,30 @@
 package com.fasterxml.jackson.databind.deser.dos;
 
 import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
 
 public class DeepArrayWrappingForDeser3582Test extends BaseMapTest
 {
-    // 23-Aug-2022, tatu: Before fix, fails with 5000
-    //    (but passes with 2000)
-//    private final static int TOO_DEEP_NESTING = 4999;
-    private final static int TOO_DEEP_NESTING = 999;
+    // 23-Aug-2022, tatu: Before fix, failed with 5000
+    private final static int TOO_DEEP_NESTING = 9999;
 
     private final ObjectMapper MAPPER = jsonMapperBuilder()
             .enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
             .build();
 
     public void testArrayWrapping() throws Exception
     {
         final String doc = _nestedDoc(TOO_DEEP_NESTING, "[ ", "] ", "{}");
-        Point p = MAPPER.readValue(doc, Point.class);
-        assertNotNull(p);
+        try {
+            MAPPER.readValue(doc, Point.class);
+            fail("Should not pass");
+        } catch (MismatchedInputException e) {
+            verifyException(e, "Cannot deserialize");
+            verifyException(e, "nested JSON Array");
+            verifyException(e, "only single");
+        }
     }
 
     private String _nestedDoc(int nesting, String open, String close, String content) {
         StringBuilder sb = new StringBuilder(nesting * (open.length() + close.length()));
         for (int i = 0; i < nesting; ++i) {

Vulnerable code:      * Feature is disabled by default
    // 23-Aug-2022, tatu: Before fix, fails with 5000
    //    (but passes with 2000)
//    private final static int TOO_DEEP_NESTING = 4999;
    private final static int TOO_DEEP_NESTING = 999;
        Point p = MAPPER.readValue(doc, Point.class);
        assertNotNull(p);
Fixed code: #3582: Add check in `BeanDeserializer._deserializeFromArray()` to prevent
  use of deeply nested arrays
     * NOTE: only <b>single</b> wrapper Array is allowed: if multiple attempted, exception
     * will be thrown.
     * Feature is disabled by default.
import com.fasterxml.jackson.databind.util.ClassUtil;
                // 23-Aug-2022, tatu: To prevent unbounded nested arrays, we better
                //   check there is NOT another START_ARRAY lurking there..
                if (p.nextToken() == JsonToken.START_ARRAY) {
                    JavaType targetType = getValueType(ctxt);
                    return ctxt.handleUnexpectedToken(targetType, JsonToken.START_ARRAY, p,
"Cannot deserialize value of type %s from deeply-nested JSON Array: only single wrapper allowed with `%s`",
                            ClassUtil.getTypeDescription(targetType),
                                    "DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS");
                }
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
    // 23-Aug-2022, tatu: Before fix, failed with 5000
    private final static int TOO_DEEP_NESTING = 9999;
        try {
            MAPPER.readValue(doc, Point.class);
            fail("Should not pass");
        } catch (MismatchedInputException e) {
            verifyException(e, "Cannot deserialize");
            verifyException(e, "nested JSON Array");
            verifyException(e, "only single");
        }