From 71fa3a8dec53666e4ed1c2c60a1bdcdf4c7bbb35 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Wed, 8 Mar 2023 00:00:17 +0100 Subject: [PATCH] CBOR: support deep nesting constraints (#361) --- .../jackson/dataformat/cbor/CBORParser.java | 20 +++- .../dataformat/cbor/CBORReadContext.java | 1 + .../jackson/dataformat/cbor/CBORTestBase.java | 3 + .../cbor/parse/dos/DeepNestingParserTest.java | 110 ++++++++++++++++++ 4 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/dos/DeepNestingParserTest.java diff --git a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORParser.java b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORParser.java index d0ed5c00e..97be6725d 100644 --- a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORParser.java +++ b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORParser.java @@ -931,7 +931,7 @@ public JsonToken nextToken() throws IOException if (!_tagValues.isEmpty()) { return _handleTaggedArray(_tagValues, len); } - _streamReadContext = _streamReadContext.createChildArrayContext(len); + createChildArrayContext(len); } return (_currToken = JsonToken.START_ARRAY); @@ -940,7 +940,7 @@ public JsonToken nextToken() throws IOException _currToken = JsonToken.START_OBJECT; { int len = _decodeExplicitLength(lowBits); - _streamReadContext = _streamReadContext.createChildObjectContext(len); + createChildObjectContext(len); } return _currToken; @@ -1128,7 +1128,7 @@ protected JsonToken _handleTaggedArray(TagList tags, int len) throws IOException // For simplicity, let's create matching array context -- in perfect // world that wouldn't be necessarily, but in this one there are // some constraints that make it necessary - _streamReadContext = _streamReadContext.createChildArrayContext(len); + createChildArrayContext(len); // BigDecimal is the only thing we know for sure if (!tags.contains(CBORConstants.TAG_DECIMAL_FRACTION)) { @@ -1711,7 +1711,7 @@ public String nextTextValue() throws IOException _currToken = JsonToken.START_ARRAY; { int len = _decodeExplicitLength(lowBits); - _streamReadContext = _streamReadContext.createChildArrayContext(len); + createChildArrayContext(len); } return null; @@ -1719,7 +1719,7 @@ public String nextTextValue() throws IOException _currToken = JsonToken.START_OBJECT; { int len = _decodeExplicitLength(lowBits); - _streamReadContext = _streamReadContext.createChildObjectContext(len); + createChildObjectContext(len); } return null; @@ -4060,4 +4060,14 @@ private final BigInteger _bigNegative(long l) { BigInteger unsignedBase = _bigPositive(l); return unsignedBase.negate().subtract(BigInteger.ONE); } + + private void createChildArrayContext(final int len) throws IOException { + _streamReadContext = _streamReadContext.createChildArrayContext(len); + streamReadConstraints().validateNestingDepth(_streamReadContext.getNestingDepth()); + } + + private void createChildObjectContext(final int len) throws IOException { + _streamReadContext = _streamReadContext.createChildObjectContext(len); + streamReadConstraints().validateNestingDepth(_streamReadContext.getNestingDepth()); + } } diff --git a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORReadContext.java b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORReadContext.java index 0399595d3..0c73ed75c 100644 --- a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORReadContext.java +++ b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORReadContext.java @@ -58,6 +58,7 @@ public CBORReadContext(CBORReadContext parent, DupDetector dups, _type = type; _expEntryCount = expEntryCount; _index = -1; + _nestingDepth = parent == null ? 0 : parent._nestingDepth + 1; } protected void reset(int type, int expEntryCount) diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/CBORTestBase.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/CBORTestBase.java index 8a57810c8..1eef7595c 100644 --- a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/CBORTestBase.java +++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/CBORTestBase.java @@ -23,6 +23,9 @@ public abstract class CBORTestBase protected CBORParser cborParser(ByteArrayOutputStream bytes) throws IOException { return cborParser(bytes.toByteArray()); } + protected CBORParser cborParser(CBORFactory cborFactory, ByteArrayOutputStream bytes) throws IOException { + return cborParser(cborFactory, bytes.toByteArray()); + } protected CBORParser cborParser(byte[] input) throws IOException { return cborParser(cborFactory(), input); diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/dos/DeepNestingParserTest.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/dos/DeepNestingParserTest.java new file mode 100644 index 000000000..38de2d705 --- /dev/null +++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/dos/DeepNestingParserTest.java @@ -0,0 +1,110 @@ +package com.fasterxml.jackson.dataformat.cbor.parse.dos; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.StreamReadConstraints; +import com.fasterxml.jackson.core.exc.StreamConstraintsException; +import com.fasterxml.jackson.dataformat.cbor.CBORFactory; +import com.fasterxml.jackson.dataformat.cbor.CBORTestBase; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * Unit tests for deeply nested JSON + */ +public class DeepNestingParserTest extends CBORTestBase +{ + public void testDeeplyNestedObjects() throws Exception + { + final int depth = 1500; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + genDeepDoc(out, depth); + try (JsonParser jp = cborParser(out)) { + JsonToken jt; + while ((jt = jp.nextToken()) != null) { + + } + fail("expected StreamConstraintsException"); + } catch (StreamConstraintsException e) { + assertEquals("Depth (1001) exceeds the maximum allowed nesting depth (1000)", e.getMessage()); + } + } + + public void testDeeplyNestedObjectsWithUnconstrainedMapper() throws Exception + { + final int depth = 1500; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + genDeepDoc(out, depth); + CBORFactory cborFactory = CBORFactory.builder() + .streamReadConstraints(StreamReadConstraints.builder().maxNestingDepth(Integer.MAX_VALUE).build()) + .build(); + try (JsonParser jp = cborParser(cborFactory, out)) { + JsonToken jt; + while ((jt = jp.nextToken()) != null) { + + } + } + } + + public void testDeeplyNestedArrays() throws Exception + { + final int depth = 750; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + genDeepArrayDoc(out, depth); + try (JsonParser jp = cborParser(out)) { + JsonToken jt; + while ((jt = jp.nextToken()) != null) { + + } + fail("expected StreamConstraintsException"); + } catch (StreamConstraintsException e) { + assertEquals("Depth (1001) exceeds the maximum allowed nesting depth (1000)", e.getMessage()); + } + } + + public void testDeeplyNestedArraysWithUnconstrainedMapper() throws Exception + { + final int depth = 750; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + genDeepArrayDoc(out, depth); + CBORFactory cborFactory = CBORFactory.builder() + .streamReadConstraints(StreamReadConstraints.builder().maxNestingDepth(Integer.MAX_VALUE).build()) + .build(); + try (JsonParser jp = cborParser(cborFactory, out)) { + JsonToken jt; + while ((jt = jp.nextToken()) != null) { + + } + } + } + + private void genDeepDoc(final ByteArrayOutputStream out, final int depth) throws IOException { + try (JsonGenerator gen = cborGenerator(out)) { + for (int i = 0; i < depth; i++) { + gen.writeStartObject(); + gen.writeFieldName("a"); + } + gen.writeString("val"); + for (int i = 0; i < depth; i++) { + gen.writeEndObject(); + } + } + } + + private void genDeepArrayDoc(final ByteArrayOutputStream out, final int depth) throws IOException { + try (JsonGenerator gen = cborGenerator(out)) { + for (int i = 0; i < depth; i++) { + gen.writeStartObject(); + gen.writeFieldName("a"); + gen.writeStartArray(); + } + gen.writeString("val"); + for (int i = 0; i < depth; i++) { + gen.writeEndArray(); + gen.writeEndObject(); + } + } + } +}