Skip to content

Commit

Permalink
Throw JsonMappingException for deeply nested JSON (FasterXML#2816, CV…
Browse files Browse the repository at this point in the history
  • Loading branch information
TaylorSMarks authored Mar 22, 2022
1 parent 0ede935 commit fcfc499
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,10 @@ public static class Vanilla
{
private static final long serialVersionUID = 1L;

// Arbitrarily chosen.
// Introduced to resolve CVE-2020-36518 and as a temporary hotfix for #2816
private static final int MAX_DEPTH = 1000;

public final static Vanilla std = new Vanilla();

// @since 2.9
Expand Down Expand Up @@ -693,64 +697,76 @@ public Boolean supportsUpdate(DeserializationConfig config) {
}

@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return deserialize(p, ctxt, 0);
}

private Object deserialize(JsonParser p, DeserializationContext ctxt, int depth) throws IOException
{
switch (p.currentTokenId()) {
case JsonTokenId.ID_START_OBJECT:
{
case JsonTokenId.ID_START_OBJECT: {
JsonToken t = p.nextToken();
if (t == JsonToken.END_OBJECT) {
return new LinkedHashMap<String,Object>(2);
return new LinkedHashMap<String, Object>(2);
}
}
case JsonTokenId.ID_FIELD_NAME:
return mapObject(p, ctxt);
case JsonTokenId.ID_START_ARRAY:
{
case JsonTokenId.ID_FIELD_NAME:
if (depth > MAX_DEPTH) {
throw new JsonParseException(p, "JSON is too deeply nested.");
}

return mapObject(p, ctxt, depth);
case JsonTokenId.ID_START_ARRAY: {
JsonToken t = p.nextToken();
if (t == JsonToken.END_ARRAY) { // and empty one too
if (ctxt.isEnabled(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY)) {
if (ctxt.isEnabled(
DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY)) {
return NO_OBJECTS;
}
return new ArrayList<Object>(2);
}
}
if (ctxt.isEnabled(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY)) {
return mapArrayToArray(p, ctxt);
}
return mapArray(p, ctxt);
case JsonTokenId.ID_EMBEDDED_OBJECT:
return p.getEmbeddedObject();
case JsonTokenId.ID_STRING:
return p.getText();

case JsonTokenId.ID_NUMBER_INT:
if (ctxt.hasSomeOfFeatures(F_MASK_INT_COERCIONS)) {
return _coerceIntegral(p, ctxt);
if (depth > MAX_DEPTH) {
throw new JsonParseException(p, "JSON is too deeply nested.");
}
return p.getNumberValue(); // should be optimal, whatever it is

case JsonTokenId.ID_NUMBER_FLOAT:
if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) {
return p.getDecimalValue();
if (ctxt.isEnabled(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY)) {
return mapArrayToArray(p, ctxt, depth);
}
return p.getNumberValue();
return mapArray(p, ctxt, depth);
case JsonTokenId.ID_EMBEDDED_OBJECT:
return p.getEmbeddedObject();
case JsonTokenId.ID_STRING:
return p.getText();

case JsonTokenId.ID_NUMBER_INT:
if (ctxt.hasSomeOfFeatures(F_MASK_INT_COERCIONS)) {
return _coerceIntegral(p, ctxt);
}
return p.getNumberValue(); // should be optimal, whatever it is

case JsonTokenId.ID_TRUE:
return Boolean.TRUE;
case JsonTokenId.ID_FALSE:
return Boolean.FALSE;
case JsonTokenId.ID_NUMBER_FLOAT:
if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) {
return p.getDecimalValue();
}
return p.getNumberValue();

case JsonTokenId.ID_END_OBJECT:
// 28-Oct-2015, tatu: [databind#989] We may also be given END_OBJECT (similar to FIELD_NAME),
// if caller has advanced to the first token of Object, but for empty Object
return new LinkedHashMap<String,Object>(2);
case JsonTokenId.ID_TRUE:
return Boolean.TRUE;
case JsonTokenId.ID_FALSE:
return Boolean.FALSE;

case JsonTokenId.ID_NULL: // 08-Nov-2016, tatu: yes, occurs
return null;
case JsonTokenId.ID_END_OBJECT:
// 28-Oct-2015, tatu: [databind#989] We may also be given END_OBJECT (similar to FIELD_NAME),
// if caller has advanced to the first token of Object, but for empty Object
return new LinkedHashMap<String, Object>(2);

//case JsonTokenId.ID_END_ARRAY: // invalid
default:
case JsonTokenId.ID_NULL: // 08-Nov-2016, tatu: yes, occurs
return null;

//case JsonTokenId.ID_END_ARRAY: // invalid
default:
}
return ctxt.handleUnexpectedToken(Object.class, p);
}
Expand Down Expand Up @@ -858,15 +874,15 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt, Object into
return deserialize(p, ctxt);
}

protected Object mapArray(JsonParser p, DeserializationContext ctxt) throws IOException
protected Object mapArray(JsonParser p, DeserializationContext ctxt, int depth) throws IOException
{
Object value = deserialize(p, ctxt);
Object value = deserialize(p, ctxt, depth + 1);
if (p.nextToken() == JsonToken.END_ARRAY) {
ArrayList<Object> l = new ArrayList<Object>(2);
l.add(value);
return l;
}
Object value2 = deserialize(p, ctxt);
Object value2 = deserialize(p, ctxt, depth + 1);
if (p.nextToken() == JsonToken.END_ARRAY) {
ArrayList<Object> l = new ArrayList<Object>(2);
l.add(value);
Expand All @@ -880,7 +896,7 @@ protected Object mapArray(JsonParser p, DeserializationContext ctxt) throws IOEx
values[ptr++] = value2;
int totalSize = ptr;
do {
value = deserialize(p, ctxt);
value = deserialize(p, ctxt, depth + 1);
++totalSize;
if (ptr >= values.length) {
values = buffer.appendCompletedChunk(values);
Expand All @@ -897,12 +913,12 @@ protected Object mapArray(JsonParser p, DeserializationContext ctxt) throws IOEx
/**
* Method called to map a JSON Array into a Java Object array (Object[]).
*/
protected Object[] mapArrayToArray(JsonParser p, DeserializationContext ctxt) throws IOException {
protected Object[] mapArrayToArray(JsonParser p, DeserializationContext ctxt, int depth) throws IOException {
ObjectBuffer buffer = ctxt.leaseObjectBuffer();
Object[] values = buffer.resetAndStart();
int ptr = 0;
do {
Object value = deserialize(p, ctxt);
Object value = deserialize(p, ctxt, depth + 1);
if (ptr >= values.length) {
values = buffer.appendCompletedChunk(values);
ptr = 0;
Expand All @@ -915,13 +931,13 @@ protected Object[] mapArrayToArray(JsonParser p, DeserializationContext ctxt) th
/**
* Method called to map a JSON Object into a Java value.
*/
protected Object mapObject(JsonParser p, DeserializationContext ctxt) throws IOException
protected Object mapObject(JsonParser p, DeserializationContext ctxt, int depth) throws IOException
{
// will point to FIELD_NAME at this point, guaranteed
// 19-Jul-2021, tatu: Was incorrectly using "getText()" before 2.13, fixed for 2.13.0
String key1 = p.currentName();
p.nextToken();
Object value1 = deserialize(p, ctxt);
Object value1 = deserialize(p, ctxt, depth + 1);

String key2 = p.nextFieldName();
if (key2 == null) { // single entry; but we want modifiable
Expand All @@ -930,7 +946,7 @@ protected Object mapObject(JsonParser p, DeserializationContext ctxt) throws IOE
return result;
}
p.nextToken();
Object value2 = deserialize(p, ctxt);
Object value2 = deserialize(p, ctxt, depth + 1);

String key = p.nextFieldName();
if (key == null) {
Expand All @@ -952,7 +968,7 @@ protected Object mapObject(JsonParser p, DeserializationContext ctxt) throws IOE

do {
p.nextToken();
final Object newValue = deserialize(p, ctxt);
final Object newValue = deserialize(p, ctxt, depth + 1);
final Object oldValue = result.put(key, newValue);
if (oldValue != null) {
return _mapObjectWithDups(p, ctxt, result, key, oldValue, newValue,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.fasterxml.jackson.databind.deser;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.BaseMapTest;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
import java.util.Map;

public class DeepNestingUntypedDeserTest extends BaseMapTest
{
// 28-Mar-2021, tatu: Currently 3000 fails for untyped/Object,
// 4000 for untyped/Array
private final static int TOO_DEEP_NESTING = 4000;
private final static int NOT_TOO_DEEP = 1000;

private final ObjectMapper MAPPER = newJsonMapper();

public void testTooDeepUntypedWithArray() throws Exception
{
final String doc = _nestedDoc(TOO_DEEP_NESTING, "[ ", "] ");
try {
MAPPER.readValue(doc, Object.class);
fail("Should have thrown an exception.");
} catch (JsonParseException jpe) {
assertTrue(jpe.getMessage().startsWith("JSON is too deeply nested."));
}
}

public void testUntypedWithArray() throws Exception
{
final String doc = _nestedDoc(NOT_TOO_DEEP, "[ ", "] ");
Object ob = MAPPER.readValue(doc, Object.class);
assertTrue(ob instanceof List<?>);
}

public void testTooDeepUntypedWithObject() throws Exception
{
final String doc = "{"+_nestedDoc(TOO_DEEP_NESTING, "\"x\":{", "} ") + "}";
try {
MAPPER.readValue(doc, Object.class);
fail("Should have thrown an exception.");
} catch (JsonParseException jpe) {
assertTrue(jpe.getMessage().startsWith("JSON is too deeply nested."));
}
}

public void testUntypedWithObject() throws Exception
{
final String doc = "{"+_nestedDoc(NOT_TOO_DEEP, "\"x\":{", "} ") + "}";
Object ob = MAPPER.readValue(doc, Object.class);
assertTrue(ob instanceof Map<?, ?>);
}

private String _nestedDoc(int nesting, String open, String close) {
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");
}
}
for (int i = 0; i < nesting; ++i) {
sb.append(close);
if ((i & 31) == 0) {
sb.append("\n");
}
}
return sb.toString();
}
}

0 comments on commit fcfc499

Please sign in to comment.