CVE-2022-42003 (published 2022-10-02T05:15Z)

In FasterXML jackson-databind before versions 2.13.4.1 and 2.12.17.1, resource exhaustion can occur because of a lack of a check in primitive value deserializers to avoid deep wrapper array nesting, when the UNWRAP_SINGLE_VALUE_ARRAYS feature is enabled.

CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-502
cwe_name: Deserialization of Untrusted Data
description: The product deserializes untrusted data without sufficiently verifying that the resulting data will be valid.
extended_description: 
            It is often convenient to serialize objects for communication or to save them for later use. However, deserialized data or code can often be modified without using the provided accessor functions if it does not use cryptography to protect itself. Furthermore, any cryptography would still be client-side security -- which is a dangerous security assumption.
            Data that is untrusted can not be trusted to be well-formed.
            When developers place no restrictions on "gadget chains," or series of instances and method invocations that can self-execute during the deserialization process (i.e., before the object is returned to the caller), it is sometimes possible for attackers to leverage them to perform unauthorized actions, like generating a shell.
         
         

Fix information:
repo_url: https://github.com/FasterXML/jackson-databind
commit_details: repo_url: https://github.com/FasterXML/jackson-databind
author: Tatu Saloranta
author_date: 2022-09-06 17:05:53-07:00
msg: Fix #3590
repository: repo_url: https://github.com/FasterXML/jackson-databind
repo_name: FasterXML/jackson-databind
description: General data-binding package for Jackson (2.x): works on streaming API (core) implementation(s)
repo_language: Java
owner: FasterXML
file_changes: filename: VERSION-2.x
change_type: ModificationType.MODIFY
diff: @@ -57,6 +57,8 @@ Project: jackson-databind
 #3559: Support `null`-valued `Map` fields with "any setter"
 #3568: Change `JsonNode.with(String)` and `withArray(String)` to consider
   argument as `JsonPointer` if valid expression
+#3590: Add check in primitive value deserializers to avoid deep wrapper array
+  nesting wrt `UNWRAP_SINGLE_VALUE_ARRAYS`
 
 2.13.4 (03-Sep-2022)
 

programming_language: Markdown
filename: StdDeserializer.java
change_type: ModificationType.MODIFY
diff: @@ -357,12 +357,8 @@ public abstract class StdDeserializer<T>
         // 23-Mar-2017, tatu: Let's specifically block recursive resolution to avoid
         //   either supporting nested arrays, or to cause infinite looping.
         if (p.hasToken(JsonToken.START_ARRAY)) {
-            String msg = String.format(
-"Cannot deserialize instance of %s out of %s token: nested Arrays not allowed with %s",
-                    ClassUtil.nameOf(_valueClass), JsonToken.START_ARRAY,
-                    "DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS");
             @SuppressWarnings("unchecked")
-            T result = (T) ctxt.handleUnexpectedToken(getValueType(ctxt), p.currentToken(), p, msg);
+            T result = (T) handleNestedArrayForSingle(p, ctxt);
             return result;
         }
         return (T) deserialize(p, ctxt);
@@ -413,7 +409,9 @@ public abstract class StdDeserializer<T>
         case JsonTokenId.ID_START_ARRAY:
             // 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so:
             if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
-                p.nextToken();
+                if (p.nextToken() == JsonToken.START_ARRAY) {
+                    return (boolean) handleNestedArrayForSingle(p, ctxt);
+                }
                 final boolean parsed = _parseBooleanPrimitive(p, ctxt);
                 _verifyEndArrayForSingle(p, ctxt);
                 return parsed;
@@ -582,7 +580,9 @@ public abstract class StdDeserializer<T>
         case JsonTokenId.ID_START_ARRAY: // unwrapping / from-empty-array coercion?
             // 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so:
             if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
-                p.nextToken();
+                if (p.nextToken() == JsonToken.START_ARRAY) {
+                    return (byte) handleNestedArrayForSingle(p, ctxt);
+                }
                 final byte parsed = _parseBytePrimitive(p, ctxt);
                 _verifyEndArrayForSingle(p, ctxt);
                 return parsed;
@@ -652,7 +652,9 @@ public abstract class StdDeserializer<T>
         case JsonTokenId.ID_START_ARRAY:
             // 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so:
             if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
-                p.nextToken();
+                if (p.nextToken() == JsonToken.START_ARRAY) {
+                    return (short) handleNestedArrayForSingle(p, ctxt);
+                }
                 final short parsed = _parseShortPrimitive(p, ctxt);
                 _verifyEndArrayForSingle(p, ctxt);
                 return parsed;
@@ -719,7 +721,9 @@ public abstract class StdDeserializer<T>
             break;
         case JsonTokenId.ID_START_ARRAY:
             if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
-                p.nextToken();
+                if (p.nextToken() == JsonToken.START_ARRAY) {
+                    return (int) handleNestedArrayForSingle(p, ctxt);
+                }
                 final int parsed = _parseIntPrimitive(p, ctxt);
                 _verifyEndArrayForSingle(p, ctxt);
                 return parsed;
@@ -870,7 +874,9 @@ public abstract class StdDeserializer<T>
             break;
         case JsonTokenId.ID_START_ARRAY:
             if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
-                p.nextToken();
+                if (p.nextToken() == JsonToken.START_ARRAY) {
+                    return (long) handleNestedArrayForSingle(p, ctxt);
+                }
                 final long parsed = _parseLongPrimitive(p, ctxt);
                 _verifyEndArrayForSingle(p, ctxt);
                 return parsed;
@@ -1003,7 +1009,9 @@ public abstract class StdDeserializer<T>
             break;
         case JsonTokenId.ID_START_ARRAY:
             if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
-                p.nextToken();
+                if (p.nextToken() == JsonToken.START_ARRAY) {
+                    return (float) handleNestedArrayForSingle(p, ctxt);
+                }
                 final float parsed = _parseFloatPrimitive(p, ctxt);
                 _verifyEndArrayForSingle(p, ctxt);
                 return parsed;
@@ -1132,7 +1140,9 @@ public abstract class StdDeserializer<T>
             break;
         case JsonTokenId.ID_START_ARRAY:
             if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
-                p.nextToken();
+                if (p.nextToken() == JsonToken.START_ARRAY) {
+                    return (double) handleNestedArrayForSingle(p, ctxt);
+                }
                 final double parsed = _parseDoublePrimitive(p, ctxt);
                 _verifyEndArrayForSingle(p, ctxt);
                 return parsed;
@@ -1313,6 +1323,9 @@ public abstract class StdDeserializer<T>
                 default:
                 }
             } else if (unwrap) {
+                if (t == JsonToken.START_ARRAY) {
+                    return (java.util.Date) handleNestedArrayForSingle(p, ctxt);
+                }
                 final Date parsed = _parseDate(p, ctxt);
                 _verifyEndArrayForSingle(p, ctxt);
                 return parsed;
@@ -2109,6 +2122,21 @@ handledType().getName());
         //     but for now just fall through
     }
 
+    /**
+     * Helper method called when detecting a deep(er) nesting of Arrays when trying
+     * to unwrap value for {@code DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS}.
+     *
+     * @since 2.14
+     */
+    protected Object handleNestedArrayForSingle(JsonParser p, DeserializationContext ctxt) throws IOException
+    {
+        String msg = String.format(
+"Cannot deserialize instance of %s out of %s token: nested Arrays not allowed with %s",
+                ClassUtil.nameOf(_valueClass), JsonToken.START_ARRAY,
+                "DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS");
+        return ctxt.handleUnexpectedToken(getValueType(ctxt), p.currentToken(), p, msg);
+    }
+
     protected void _verifyEndArrayForSingle(JsonParser p, DeserializationContext ctxt) throws IOException
     {
         JsonToken t = p.nextToken();

programming_language: Java
filename: DeepArrayWrappingForDeser3590Test.java
change_type: ModificationType.ADD
diff: @@ -0,0 +1,95 @@
+package com.fasterxml.jackson.databind.deser.dos;
+
+import java.util.Date;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+
+public class DeepArrayWrappingForDeser3590Test extends BaseMapTest
+{
+    // 05-Sep-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();
+
+    private final static String TOO_DEEP_DOC = _nestedDoc(TOO_DEEP_NESTING, "[ ", "] ", "123");
+
+    public void testArrayWrappingForBoolean() throws Exception
+    {
+        _testArrayWrappingFor(Boolean.class);
+        _testArrayWrappingFor(Boolean.TYPE);
+    }
+
+    public void testArrayWrappingForByte() throws Exception
+    {
+        _testArrayWrappingFor(Byte.class);
+        _testArrayWrappingFor(Byte.TYPE);
+    }
+
+    public void testArrayWrappingForShort() throws Exception
+    {
+        _testArrayWrappingFor(Short.class);
+        _testArrayWrappingFor(Short.TYPE);
+    }
+
+    public void testArrayWrappingForInt() throws Exception
+    {
+        _testArrayWrappingFor(Integer.class);
+        _testArrayWrappingFor(Integer.TYPE);
+    }
+
+    public void testArrayWrappingForLong() throws Exception
+    {
+        _testArrayWrappingFor(Long.class);
+        _testArrayWrappingFor(Long.TYPE);
+    }
+
+    public void testArrayWrappingForFloat() throws Exception
+    {
+        _testArrayWrappingFor(Float.class);
+        _testArrayWrappingFor(Float.TYPE);
+    }
+
+    public void testArrayWrappingForDouble() throws Exception
+    {
+        _testArrayWrappingFor(Double.class);
+        _testArrayWrappingFor(Double.TYPE);
+    }
+
+    public void testArrayWrappingForDate() throws Exception
+    {
+        _testArrayWrappingFor(Date.class);
+    }
+
+    private void _testArrayWrappingFor(Class<?> cls) throws Exception
+    {
+        try {
+            MAPPER.readValue(TOO_DEEP_DOC, cls);
+            fail("Should not pass");
+        } catch (MismatchedInputException e) {
+            verifyException(e, "Cannot deserialize");
+            verifyException(e, "nested Arrays not allowed");
+        }
+    }
+
+    private static 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) {
+            sb.append(open);
+            if ((i & 31) == 0) {
+                sb.append("\n");
+            }
+        }
+        sb.append("\n").append(content).append("\n");
+        for (int i = 0; i < nesting; ++i) {
+            sb.append(close);
+            if ((i & 31) == 0) {
+                sb.append("\n");
+            }
+        }
+        return sb.toString();
+    }
+
+}

programming_language: Java