Skip to content

Commit

Permalink
backport Fix #3590 and Fix #3582 (#3622)
Browse files Browse the repository at this point in the history
  • Loading branch information
cesarhernandezgt authored Oct 13, 2022
1 parent 6497f1b commit cd09097
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 14 deletions.
10 changes: 9 additions & 1 deletion release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ Project: jackson-databind
=== Releases ===
------------------------------------------------------------------------

2.12.7.1 (not yest released)

#3582: Add check in `BeanDeserializer._deserializeFromArray()` to prevent
use of deeply nested arrays [CVE-2022-42004]

#3590: Add check in primitive value deserializers to avoid deep wrapper array
nesting wrt `UNWRAP_SINGLE_VALUE_ARRAYS` [CVE-2022-42003]

2.12.7 (26-May-2022)

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

2.12.5 (27-Aug-2021)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,10 @@ public enum DeserializationFeature implements ConfigFeature
* 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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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;
Expand Down Expand Up @@ -630,6 +631,15 @@ protected Object _deserializeFromArray(JsonParser p, DeserializationContext ctxt
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,12 +357,8 @@ protected T _deserializeWrappedValue(JsonParser p, DeserializationContext ctxt)
// 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);
Expand Down Expand Up @@ -413,7 +409,9 @@ protected final boolean _parseBooleanPrimitive(JsonParser p, DeserializationCont
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;
Expand Down Expand Up @@ -582,7 +580,9 @@ protected final byte _parseBytePrimitive(JsonParser p, DeserializationContext ct
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;
Expand Down Expand Up @@ -650,7 +650,9 @@ protected final short _parseShortPrimitive(JsonParser p, DeserializationContext
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;
Expand Down Expand Up @@ -715,7 +717,9 @@ protected final int _parseIntPrimitive(JsonParser p, DeserializationContext ctxt
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;
Expand Down Expand Up @@ -842,7 +846,9 @@ protected final long _parseLongPrimitive(JsonParser p, DeserializationContext ct
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;
Expand Down Expand Up @@ -953,7 +959,9 @@ protected final float _parseFloatPrimitive(JsonParser p, DeserializationContext
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;
Expand Down Expand Up @@ -1058,7 +1066,9 @@ protected final double _parseDoublePrimitive(JsonParser p, DeserializationContex
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;
Expand Down Expand Up @@ -1214,6 +1224,9 @@ protected java.util.Date _parseDateFromArray(JsonParser p, DeserializationContex
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;
Expand Down Expand Up @@ -1990,6 +2003,21 @@ protected void handleMissingEndArrayForSingle(JsonParser p, DeserializationConte
// 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();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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, 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, "[ ", "] ", "{}");
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) {
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();
}

}
Original file line number Diff line number Diff line change
@@ -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();
}

}

0 comments on commit cd09097

Please sign in to comment.