Skip to content

Commit cd09097

Browse files
backport Fix #3590 and Fix #3582 (#3622)
1 parent 6497f1b commit cd09097

File tree

6 files changed

+203
-14
lines changed

6 files changed

+203
-14
lines changed

release-notes/VERSION-2.x

+9-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ Project: jackson-databind
44
=== Releases ===
55
------------------------------------------------------------------------
66

7+
2.12.7.1 (not yest released)
8+
9+
#3582: Add check in `BeanDeserializer._deserializeFromArray()` to prevent
10+
use of deeply nested arrays [CVE-2022-42004]
11+
12+
#3590: Add check in primitive value deserializers to avoid deep wrapper array
13+
nesting wrt `UNWRAP_SINGLE_VALUE_ARRAYS` [CVE-2022-42003]
14+
715
2.12.7 (26-May-2022)
816

917
#2816: Optimize UntypedObjectDeserializer wrt recursion [CVE-2020-36518]
@@ -16,7 +24,7 @@ Project: jackson-databind
1624
#3305: ObjectMapper serializes `CharSequence` subtypes as POJO instead of
1725
as String (JDK 15+)
1826
(reported by stevenupton@github; fix suggested by Sergey C)
19-
#3328: Possible DoS issue
27+
#3328: Possible DoS if using JDK serialization to serialize JsonNode
2028

2129
2.12.5 (27-Aug-2021)
2230

src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -318,8 +318,10 @@ public enum DeserializationFeature implements ConfigFeature
318318
* values to the corresponding value type. This is basically the opposite of the {@link #ACCEPT_SINGLE_VALUE_AS_ARRAY}
319319
* feature. If more than one value is found in the array, a JsonMappingException is thrown.
320320
* <p>
321+
* NOTE: only <b>single</b> wrapper Array is allowed: if multiple attempted, exception
322+
* will be thrown.
321323
*
322-
* Feature is disabled by default
324+
* Feature is disabled by default.
323325
* @since 2.4
324326
*/
325327
UNWRAP_SINGLE_VALUE_ARRAYS(false),

src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java

+10
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.fasterxml.jackson.databind.cfg.CoercionAction;
99
import com.fasterxml.jackson.databind.deser.impl.*;
1010
import com.fasterxml.jackson.databind.deser.impl.ReadableObjectId.Referring;
11+
import com.fasterxml.jackson.databind.util.ClassUtil;
1112
import com.fasterxml.jackson.databind.util.IgnorePropertiesUtil;
1213
import com.fasterxml.jackson.databind.util.NameTransformer;
1314
import com.fasterxml.jackson.databind.util.TokenBuffer;
@@ -630,6 +631,15 @@ protected Object _deserializeFromArray(JsonParser p, DeserializationContext ctxt
630631
return ctxt.handleUnexpectedToken(getValueType(ctxt), JsonToken.START_ARRAY, p, null);
631632
}
632633
if (unwrap) {
634+
// 23-Aug-2022, tatu: To prevent unbounded nested arrays, we better
635+
// check there is NOT another START_ARRAY lurking there..
636+
if (p.nextToken() == JsonToken.START_ARRAY) {
637+
JavaType targetType = getValueType(ctxt);
638+
return ctxt.handleUnexpectedToken(targetType, JsonToken.START_ARRAY, p,
639+
"Cannot deserialize value of type %s from deeply-nested JSON Array: only single wrapper allowed with `%s`",
640+
ClassUtil.getTypeDescription(targetType),
641+
"DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS");
642+
}
633643
final Object value = deserialize(p, ctxt);
634644
if (p.nextToken() != JsonToken.END_ARRAY) {
635645
handleMissingEndArrayForSingle(p, ctxt);

src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java

+40-12
Original file line numberDiff line numberDiff line change
@@ -357,12 +357,8 @@ protected T _deserializeWrappedValue(JsonParser p, DeserializationContext ctxt)
357357
// 23-Mar-2017, tatu: Let's specifically block recursive resolution to avoid
358358
// either supporting nested arrays, or to cause infinite looping.
359359
if (p.hasToken(JsonToken.START_ARRAY)) {
360-
String msg = String.format(
361-
"Cannot deserialize instance of %s out of %s token: nested Arrays not allowed with %s",
362-
ClassUtil.nameOf(_valueClass), JsonToken.START_ARRAY,
363-
"DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS");
364360
@SuppressWarnings("unchecked")
365-
T result = (T) ctxt.handleUnexpectedToken(getValueType(ctxt), p.currentToken(), p, msg);
361+
T result = (T) handleNestedArrayForSingle(p, ctxt);
366362
return result;
367363
}
368364
return (T) deserialize(p, ctxt);
@@ -413,7 +409,9 @@ protected final boolean _parseBooleanPrimitive(JsonParser p, DeserializationCont
413409
case JsonTokenId.ID_START_ARRAY:
414410
// 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so:
415411
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
416-
p.nextToken();
412+
if (p.nextToken() == JsonToken.START_ARRAY) {
413+
return (boolean) handleNestedArrayForSingle(p, ctxt);
414+
}
417415
final boolean parsed = _parseBooleanPrimitive(p, ctxt);
418416
_verifyEndArrayForSingle(p, ctxt);
419417
return parsed;
@@ -582,7 +580,9 @@ protected final byte _parseBytePrimitive(JsonParser p, DeserializationContext ct
582580
case JsonTokenId.ID_START_ARRAY: // unwrapping / from-empty-array coercion?
583581
// 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so:
584582
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
585-
p.nextToken();
583+
if (p.nextToken() == JsonToken.START_ARRAY) {
584+
return (byte) handleNestedArrayForSingle(p, ctxt);
585+
}
586586
final byte parsed = _parseBytePrimitive(p, ctxt);
587587
_verifyEndArrayForSingle(p, ctxt);
588588
return parsed;
@@ -650,7 +650,9 @@ protected final short _parseShortPrimitive(JsonParser p, DeserializationContext
650650
case JsonTokenId.ID_START_ARRAY:
651651
// 12-Jun-2020, tatu: For some reason calling `_deserializeFromArray()` won't work so:
652652
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
653-
p.nextToken();
653+
if (p.nextToken() == JsonToken.START_ARRAY) {
654+
return (short) handleNestedArrayForSingle(p, ctxt);
655+
}
654656
final short parsed = _parseShortPrimitive(p, ctxt);
655657
_verifyEndArrayForSingle(p, ctxt);
656658
return parsed;
@@ -715,7 +717,9 @@ protected final int _parseIntPrimitive(JsonParser p, DeserializationContext ctxt
715717
break;
716718
case JsonTokenId.ID_START_ARRAY:
717719
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
718-
p.nextToken();
720+
if (p.nextToken() == JsonToken.START_ARRAY) {
721+
return (int) handleNestedArrayForSingle(p, ctxt);
722+
}
719723
final int parsed = _parseIntPrimitive(p, ctxt);
720724
_verifyEndArrayForSingle(p, ctxt);
721725
return parsed;
@@ -842,7 +846,9 @@ protected final long _parseLongPrimitive(JsonParser p, DeserializationContext ct
842846
break;
843847
case JsonTokenId.ID_START_ARRAY:
844848
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
845-
p.nextToken();
849+
if (p.nextToken() == JsonToken.START_ARRAY) {
850+
return (long) handleNestedArrayForSingle(p, ctxt);
851+
}
846852
final long parsed = _parseLongPrimitive(p, ctxt);
847853
_verifyEndArrayForSingle(p, ctxt);
848854
return parsed;
@@ -953,7 +959,9 @@ protected final float _parseFloatPrimitive(JsonParser p, DeserializationContext
953959
break;
954960
case JsonTokenId.ID_START_ARRAY:
955961
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
956-
p.nextToken();
962+
if (p.nextToken() == JsonToken.START_ARRAY) {
963+
return (float) handleNestedArrayForSingle(p, ctxt);
964+
}
957965
final float parsed = _parseFloatPrimitive(p, ctxt);
958966
_verifyEndArrayForSingle(p, ctxt);
959967
return parsed;
@@ -1058,7 +1066,9 @@ protected final double _parseDoublePrimitive(JsonParser p, DeserializationContex
10581066
break;
10591067
case JsonTokenId.ID_START_ARRAY:
10601068
if (ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) {
1061-
p.nextToken();
1069+
if (p.nextToken() == JsonToken.START_ARRAY) {
1070+
return (double) handleNestedArrayForSingle(p, ctxt);
1071+
}
10621072
final double parsed = _parseDoublePrimitive(p, ctxt);
10631073
_verifyEndArrayForSingle(p, ctxt);
10641074
return parsed;
@@ -1214,6 +1224,9 @@ protected java.util.Date _parseDateFromArray(JsonParser p, DeserializationContex
12141224
default:
12151225
}
12161226
} else if (unwrap) {
1227+
if (t == JsonToken.START_ARRAY) {
1228+
return (java.util.Date) handleNestedArrayForSingle(p, ctxt);
1229+
}
12171230
final Date parsed = _parseDate(p, ctxt);
12181231
_verifyEndArrayForSingle(p, ctxt);
12191232
return parsed;
@@ -1990,6 +2003,21 @@ protected void handleMissingEndArrayForSingle(JsonParser p, DeserializationConte
19902003
// but for now just fall through
19912004
}
19922005

2006+
/**
2007+
* Helper method called when detecting a deep(er) nesting of Arrays when trying
2008+
* to unwrap value for {@code DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS}.
2009+
*
2010+
* @since 2.14
2011+
*/
2012+
protected Object handleNestedArrayForSingle(JsonParser p, DeserializationContext ctxt) throws IOException
2013+
{
2014+
String msg = String.format(
2015+
"Cannot deserialize instance of %s out of %s token: nested Arrays not allowed with %s",
2016+
ClassUtil.nameOf(_valueClass), JsonToken.START_ARRAY,
2017+
"DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS");
2018+
return ctxt.handleUnexpectedToken(getValueType(ctxt), p.currentToken(), p, msg);
2019+
}
2020+
19932021
protected void _verifyEndArrayForSingle(JsonParser p, DeserializationContext ctxt) throws IOException
19942022
{
19952023
JsonToken t = p.nextToken();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.fasterxml.jackson.databind.deser.dos;
2+
3+
import com.fasterxml.jackson.databind.*;
4+
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
5+
6+
public class DeepArrayWrappingForDeser3582Test extends BaseMapTest
7+
{
8+
// 23-Aug-2022, tatu: Before fix, failed with 5000
9+
private final static int TOO_DEEP_NESTING = 9999;
10+
11+
private final ObjectMapper MAPPER = jsonMapperBuilder()
12+
.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
13+
.build();
14+
15+
public void testArrayWrapping() throws Exception
16+
{
17+
final String doc = _nestedDoc(TOO_DEEP_NESTING, "[ ", "] ", "{}");
18+
try {
19+
MAPPER.readValue(doc, Point.class);
20+
fail("Should not pass");
21+
} catch (MismatchedInputException e) {
22+
verifyException(e, "Cannot deserialize");
23+
verifyException(e, "nested JSON Array");
24+
verifyException(e, "only single");
25+
}
26+
}
27+
28+
private String _nestedDoc(int nesting, String open, String close, String content) {
29+
StringBuilder sb = new StringBuilder(nesting * (open.length() + close.length()));
30+
for (int i = 0; i < nesting; ++i) {
31+
sb.append(open);
32+
if ((i & 31) == 0) {
33+
sb.append("\n");
34+
}
35+
}
36+
sb.append("\n").append(content).append("\n");
37+
for (int i = 0; i < nesting; ++i) {
38+
sb.append(close);
39+
if ((i & 31) == 0) {
40+
sb.append("\n");
41+
}
42+
}
43+
return sb.toString();
44+
}
45+
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package com.fasterxml.jackson.databind.deser.dos;
2+
3+
import java.util.Date;
4+
5+
import com.fasterxml.jackson.databind.*;
6+
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
7+
8+
public class DeepArrayWrappingForDeser3590Test extends BaseMapTest
9+
{
10+
// 05-Sep-2022, tatu: Before fix, failed with 5000
11+
private final static int TOO_DEEP_NESTING = 9999;
12+
13+
private final ObjectMapper MAPPER = jsonMapperBuilder()
14+
.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
15+
.build();
16+
17+
private final static String TOO_DEEP_DOC = _nestedDoc(TOO_DEEP_NESTING, "[ ", "] ", "123");
18+
19+
public void testArrayWrappingForBoolean() throws Exception
20+
{
21+
_testArrayWrappingFor(Boolean.class);
22+
_testArrayWrappingFor(Boolean.TYPE);
23+
}
24+
25+
public void testArrayWrappingForByte() throws Exception
26+
{
27+
_testArrayWrappingFor(Byte.class);
28+
_testArrayWrappingFor(Byte.TYPE);
29+
}
30+
31+
public void testArrayWrappingForShort() throws Exception
32+
{
33+
_testArrayWrappingFor(Short.class);
34+
_testArrayWrappingFor(Short.TYPE);
35+
}
36+
37+
public void testArrayWrappingForInt() throws Exception
38+
{
39+
_testArrayWrappingFor(Integer.class);
40+
_testArrayWrappingFor(Integer.TYPE);
41+
}
42+
43+
public void testArrayWrappingForLong() throws Exception
44+
{
45+
_testArrayWrappingFor(Long.class);
46+
_testArrayWrappingFor(Long.TYPE);
47+
}
48+
49+
public void testArrayWrappingForFloat() throws Exception
50+
{
51+
_testArrayWrappingFor(Float.class);
52+
_testArrayWrappingFor(Float.TYPE);
53+
}
54+
55+
public void testArrayWrappingForDouble() throws Exception
56+
{
57+
_testArrayWrappingFor(Double.class);
58+
_testArrayWrappingFor(Double.TYPE);
59+
}
60+
61+
public void testArrayWrappingForDate() throws Exception
62+
{
63+
_testArrayWrappingFor(Date.class);
64+
}
65+
66+
private void _testArrayWrappingFor(Class<?> cls) throws Exception
67+
{
68+
try {
69+
MAPPER.readValue(TOO_DEEP_DOC, cls);
70+
fail("Should not pass");
71+
} catch (MismatchedInputException e) {
72+
verifyException(e, "Cannot deserialize");
73+
verifyException(e, "nested Arrays not allowed");
74+
}
75+
}
76+
77+
private static String _nestedDoc(int nesting, String open, String close, String content) {
78+
StringBuilder sb = new StringBuilder(nesting * (open.length() + close.length()));
79+
for (int i = 0; i < nesting; ++i) {
80+
sb.append(open);
81+
if ((i & 31) == 0) {
82+
sb.append("\n");
83+
}
84+
}
85+
sb.append("\n").append(content).append("\n");
86+
for (int i = 0; i < nesting; ++i) {
87+
sb.append(close);
88+
if ((i & 31) == 0) {
89+
sb.append("\n");
90+
}
91+
}
92+
return sb.toString();
93+
}
94+
95+
}

0 commit comments

Comments
 (0)