From 0895a22d294cba44ba15cea9236a59c31be373d2 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 8 Jul 2023 11:22:40 +0100 Subject: [PATCH 1/9] police against cyclic data --- .../dataformat/cbor/CBORGenerator.java | 7 +++++ .../dataformat/cbor/CBORWriteContext.java | 1 + .../cbor/gen/dos/CyclicDataSerTest.java | 29 +++++++++++++++++++ .../dataformat/smile/SmileGenerator.java | 7 +++++ .../dataformat/smile/SmileWriteContext.java | 1 + .../smile/gen/dos/CyclicDataSerTest.java | 29 +++++++++++++++++++ 6 files changed, 74 insertions(+) create mode 100644 cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/gen/dos/CyclicDataSerTest.java create mode 100644 smile/src/test/java/com/fasterxml/jackson/dataformat/smile/gen/dos/CyclicDataSerTest.java diff --git a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java index 0a158e377..af34336a9 100644 --- a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java +++ b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java @@ -570,6 +570,7 @@ public final void writeFieldId(long id) throws IOException { public final void writeStartArray() throws IOException { _verifyValueWrite("start an array"); _streamWriteContext = _streamWriteContext.createChildArrayContext(null); + streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth()); if (_elementCountsPtr > 0) { _pushRemainingElements(); } @@ -581,6 +582,7 @@ public final void writeStartArray() throws IOException { public void writeStartArray(Object forValue) throws IOException { _verifyValueWrite("start an array"); _streamWriteContext = _streamWriteContext.createChildArrayContext(forValue); + streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth()); if (_elementCountsPtr > 0) { _pushRemainingElements(); } @@ -596,6 +598,7 @@ public void writeStartArray(Object forValue) throws IOException { public void writeStartArray(Object forValue, int elementsToWrite) throws IOException { _verifyValueWrite("start an array"); _streamWriteContext = _streamWriteContext.createChildArrayContext(forValue); + streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth()); _pushRemainingElements(); _currentRemainingElements = elementsToWrite; _writeLengthMarker(PREFIX_TYPE_ARRAY, elementsToWrite); @@ -606,6 +609,7 @@ public void writeStartArray(Object forValue, int elementsToWrite) throws IOExcep public void writeStartArray(int elementsToWrite) throws IOException { _verifyValueWrite("start an array"); _streamWriteContext = _streamWriteContext.createChildArrayContext(null); + streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth()); _pushRemainingElements(); _currentRemainingElements = elementsToWrite; _writeLengthMarker(PREFIX_TYPE_ARRAY, elementsToWrite); @@ -624,6 +628,7 @@ public final void writeEndArray() throws IOException { public final void writeStartObject() throws IOException { _verifyValueWrite("start an object"); _streamWriteContext = _streamWriteContext.createChildObjectContext(null); + streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth()); if (_elementCountsPtr > 0) { _pushRemainingElements(); } @@ -636,6 +641,7 @@ public final void writeStartObject() throws IOException { public final void writeStartObject(Object forValue) throws IOException { _verifyValueWrite("start an object"); CBORWriteContext ctxt = _streamWriteContext.createChildObjectContext(forValue); + streamWriteConstraints().validateNestingDepth(ctxt.getNestingDepth()); _streamWriteContext = ctxt; if (_elementCountsPtr > 0) { _pushRemainingElements(); @@ -652,6 +658,7 @@ public final void writeStartObject(int elementsToWrite) throws IOException { public void writeStartObject(Object forValue, int elementsToWrite) throws IOException { _verifyValueWrite("start an object"); _streamWriteContext = _streamWriteContext.createChildObjectContext(forValue); + streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth()); _pushRemainingElements(); _currentRemainingElements = elementsToWrite; _writeLengthMarker(PREFIX_TYPE_OBJECT, elementsToWrite); diff --git a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORWriteContext.java b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORWriteContext.java index 292acbd18..66434a2f7 100644 --- a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORWriteContext.java +++ b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORWriteContext.java @@ -65,6 +65,7 @@ protected CBORWriteContext(int type, CBORWriteContext parent, DupDetector dups, super(); _type = type; _parent = parent; + _nestingDepth = parent == null ? 0 : parent._nestingDepth + 1; _dups = dups; _index = -1; _currentValue = currentValue; diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/gen/dos/CyclicDataSerTest.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/gen/dos/CyclicDataSerTest.java new file mode 100644 index 000000000..2eaba4764 --- /dev/null +++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/gen/dos/CyclicDataSerTest.java @@ -0,0 +1,29 @@ +package com.fasterxml.jackson.dataformat.cbor.gen.dos; + +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.cbor.CBORTestBase; + +import java.util.ArrayList; +import java.util.List; + +/** + * Simple unit tests to verify that we fail gracefully if you attempt to serialize + * data that is cyclic (eg a list that contains itself). + */ +public class CyclicDataSerTest extends CBORTestBase +{ + private final ObjectMapper MAPPER = cborMapper(); + + public void testListWithSelfReference() throws Exception { + List list = new ArrayList<>(); + list.add(list); + try { + MAPPER.writeValueAsBytes(list); + fail("expected JsonMappingException"); + } catch (JsonMappingException jmex) { + assertTrue("JsonMappingException message is as expected?", + jmex.getMessage().startsWith("Document nesting depth (1001) exceeds the maximum allowed")); + } + } +} diff --git a/smile/src/main/java/com/fasterxml/jackson/dataformat/smile/SmileGenerator.java b/smile/src/main/java/com/fasterxml/jackson/dataformat/smile/SmileGenerator.java index 4f9c0126b..f5680fab8 100644 --- a/smile/src/main/java/com/fasterxml/jackson/dataformat/smile/SmileGenerator.java +++ b/smile/src/main/java/com/fasterxml/jackson/dataformat/smile/SmileGenerator.java @@ -628,6 +628,7 @@ public final void writeStartArray() throws IOException { _verifyValueWrite("start an array"); _streamWriteContext = _streamWriteContext.createChildArrayContext(null); + streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth()); _writeByte(TOKEN_LITERAL_START_ARRAY); } @@ -636,6 +637,7 @@ public final void writeStartArray(Object forValue) throws IOException { _verifyValueWrite("start an array"); _streamWriteContext = _streamWriteContext.createChildArrayContext(forValue); + streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth()); _writeByte(TOKEN_LITERAL_START_ARRAY); } @@ -644,6 +646,7 @@ public final void writeStartArray(Object forValue, int elementsToWrite) throws I { _verifyValueWrite("start an array"); _streamWriteContext = _streamWriteContext.createChildArrayContext(forValue); + streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth()); _writeByte(TOKEN_LITERAL_START_ARRAY); } @@ -653,6 +656,7 @@ public final void writeStartArray(int size) throws IOException { _verifyValueWrite("start an array"); _streamWriteContext = _streamWriteContext.createChildArrayContext(null); + streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth()); _writeByte(TOKEN_LITERAL_START_ARRAY); } @@ -671,6 +675,7 @@ public final void writeStartObject() throws IOException { _verifyValueWrite("start an object"); _streamWriteContext = _streamWriteContext.createChildObjectContext(null); + streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth()); _writeByte(TOKEN_LITERAL_START_OBJECT); } @@ -679,6 +684,7 @@ public final void writeStartObject(Object forValue) throws IOException { _verifyValueWrite("start an object"); SmileWriteContext ctxt = _streamWriteContext.createChildObjectContext(forValue); + streamWriteConstraints().validateNestingDepth(ctxt.getNestingDepth()); _streamWriteContext = ctxt; _writeByte(TOKEN_LITERAL_START_OBJECT); } @@ -687,6 +693,7 @@ public final void writeStartObject(Object forValue) throws IOException public void writeStartObject(Object forValue, int elementsToWrite) throws IOException { _verifyValueWrite("start an object"); SmileWriteContext ctxt = _streamWriteContext.createChildObjectContext(forValue); + streamWriteConstraints().validateNestingDepth(ctxt.getNestingDepth()); _streamWriteContext = ctxt; _writeByte(TOKEN_LITERAL_START_OBJECT); } diff --git a/smile/src/main/java/com/fasterxml/jackson/dataformat/smile/SmileWriteContext.java b/smile/src/main/java/com/fasterxml/jackson/dataformat/smile/SmileWriteContext.java index 5b88c1f45..b7dd6b2a0 100644 --- a/smile/src/main/java/com/fasterxml/jackson/dataformat/smile/SmileWriteContext.java +++ b/smile/src/main/java/com/fasterxml/jackson/dataformat/smile/SmileWriteContext.java @@ -61,6 +61,7 @@ protected SmileWriteContext(int type, SmileWriteContext parent, DupDetector dups super(); _type = type; _parent = parent; + _nestingDepth = parent == null ? 0 : parent._nestingDepth + 1; _dups = dups; _index = -1; _currentValue = currentValue; diff --git a/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/gen/dos/CyclicDataSerTest.java b/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/gen/dos/CyclicDataSerTest.java new file mode 100644 index 000000000..a2bc61ae8 --- /dev/null +++ b/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/gen/dos/CyclicDataSerTest.java @@ -0,0 +1,29 @@ +package com.fasterxml.jackson.dataformat.smile.gen.dos; + +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.smile.BaseTestForSmile; + +import java.util.ArrayList; +import java.util.List; + +/** + * Simple unit tests to verify that we fail gracefully if you attempt to serialize + * data that is cyclic (eg a list that contains itself). + */ +public class CyclicDataSerTest extends BaseTestForSmile +{ + private final ObjectMapper MAPPER = smileMapper(); + + public void testListWithSelfReference() throws Exception { + List list = new ArrayList<>(); + list.add(list); + try { + MAPPER.writeValueAsBytes(list); + fail("expected JsonMappingException"); + } catch (JsonMappingException jmex) { + assertTrue("JsonMappingException message is as expected?", + jmex.getMessage().startsWith("Document nesting depth (1001) exceeds the maximum allowed")); + } + } +} From 1f97482a94eca09b26aba18eba9fec58a54c00ac Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 8 Jul 2023 11:40:46 +0100 Subject: [PATCH 2/9] Create CyclicDataSerTest.java --- .../avro/dos/CyclicDataSerTest.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 avro/src/test/java/com/fasterxml/jackson/dataformat/avro/dos/CyclicDataSerTest.java diff --git a/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/dos/CyclicDataSerTest.java b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/dos/CyclicDataSerTest.java new file mode 100644 index 000000000..046780177 --- /dev/null +++ b/avro/src/test/java/com/fasterxml/jackson/dataformat/avro/dos/CyclicDataSerTest.java @@ -0,0 +1,46 @@ +package com.fasterxml.jackson.dataformat.avro.dos; + +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; +import com.fasterxml.jackson.dataformat.avro.AvroMapper; +import com.fasterxml.jackson.dataformat.avro.AvroSchema; +import com.fasterxml.jackson.dataformat.avro.AvroTestBase; + +/** + * Simple unit tests to verify that we fail gracefully if you attempt to serialize + * data that is cyclic (eg a list that contains itself). + */ +public class CyclicDataSerTest extends AvroTestBase +{ + + public static class Bean + { + Bean _next; + final String _name; + + public Bean(Bean next, String name) { + _next = next; + _name = name; + } + + public Bean getNext() { return _next; } + public String getName() { return _name; } + + public void assignNext(Bean n) { _next = n; } + } + + private final AvroMapper MAPPER = getMapper(); + + public void testCyclic() throws Exception { + Bean bean = new Bean(null, "123"); + bean.assignNext(bean); + try { + AvroSchema schema = MAPPER.schemaFor(Bean.class); + MAPPER.writer(schema).writeValueAsBytes(bean); + fail("expected InvalidDefinitionException"); + } catch (InvalidDefinitionException idex) { + assertTrue("InvalidDefinitionException message is as expected?", + idex.getMessage().startsWith("Direct self-reference leading to cycle")); + } + } +} From 185373b2ec17a5bf2d3718d7d711f46b0eb1b925 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 8 Jul 2023 11:45:53 +0100 Subject: [PATCH 3/9] ion test --- .../jackson/dataformat/ion/IonGenerator.java | 3 ++ .../dataformat/ion/IonWriteContext.java | 2 +- .../dataformat/ion/dos/CyclicDataSerTest.java | 34 +++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 ion/src/test/java/com/fasterxml/jackson/dataformat/ion/dos/CyclicDataSerTest.java diff --git a/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonGenerator.java b/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonGenerator.java index 841f57914..517095064 100644 --- a/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonGenerator.java +++ b/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonGenerator.java @@ -524,6 +524,7 @@ protected void _writeFieldName(String value) throws IOException { public void writeStartArray() throws IOException { _verifyValueWrite("start an array"); // <-- copied from UTF8JsonGenerator _writeContext = _writeContext.createChildArrayContext(); // <-- copied from UTF8JsonGenerator + streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth()); _writer.stepIn(IonType.LIST); } @@ -531,6 +532,7 @@ public void writeStartArray() throws IOException { public void writeStartObject() throws IOException { _verifyValueWrite("start an object"); // <-- copied from UTF8JsonGenerator _writeContext = _writeContext.createChildObjectContext(); // <-- copied from UTF8JsonGenerator + streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth()); _writer.stepIn(IonType.STRUCT); } @@ -540,6 +542,7 @@ public void writeStartObject() throws IOException { public void writeStartSexp() throws IOException { _verifyValueWrite("start a sexp"); _writeContext = ((IonWriteContext) _writeContext).createChildSexpContext(); + streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth()); _writer.stepIn(IonType.SEXP); } diff --git a/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonWriteContext.java b/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonWriteContext.java index 450a3e30a..0171c87c7 100644 --- a/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonWriteContext.java +++ b/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonWriteContext.java @@ -26,7 +26,7 @@ * context to track that. Sexp handling is modeled after arrays. */ public class IonWriteContext extends JsonWriteContext { - // Both contstants are in the tens instead of the ones to avoid conflict with the native + // Both constants are in the tens instead of the ones to avoid conflict with the native // Jackson ones // Ion-specific contexts diff --git a/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/dos/CyclicDataSerTest.java b/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/dos/CyclicDataSerTest.java new file mode 100644 index 000000000..e1485c206 --- /dev/null +++ b/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/dos/CyclicDataSerTest.java @@ -0,0 +1,34 @@ +package com.fasterxml.jackson.dataformat.ion.dos; + +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.ion.IonObjectMapper; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * Simple unit tests to verify that we fail gracefully if you attempt to serialize + * data that is cyclic (eg a list that contains itself). + */ +public class CyclicDataSerTest +{ + private final ObjectMapper MAPPER = IonObjectMapper.builderForTextualWriters().build(); + + @Test + public void testListWithSelfReference() throws Exception { + List list = new ArrayList<>(); + list.add(list); + try { + MAPPER.writeValueAsBytes(list); + fail("expected JsonMappingException"); + } catch (JsonMappingException jmex) { + assertTrue("JsonMappingException message is as expected?", + jmex.getMessage().startsWith("Document nesting depth (1001) exceeds the maximum allowed")); + } + } +} From e408e30fbe100c4aca6a6319e0a899bb65bd398d Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 8 Jul 2023 12:17:10 +0100 Subject: [PATCH 4/9] broken tests --- .../jackson/dataformat/cbor/CBORGenerator.java | 5 +++++ .../cbor/parse/dos/DeepNestingCBORParserTest.java | 11 +++++++++-- .../jackson/dataformat/ion/IonGenerator.java | 6 ++++++ .../dataformat/protobuf/ProtobufGenerator.java | 5 +++++ .../jackson/dataformat/smile/SmileGenerator.java | 5 +++++ .../smile/parse/dos/DeepNestingSmileParserTest.java | 12 ++++++++++-- 6 files changed, 40 insertions(+), 4 deletions(-) diff --git a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java index af34336a9..562392e6e 100644 --- a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java +++ b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java @@ -379,6 +379,11 @@ public JacksonFeatureSet getWriteCapabilities() { /********************************************************** */ + @Override + public StreamWriteConstraints streamWriteConstraints() { + return _ioContext.streamWriteConstraints(); + } + /** * No way (or need) to indent anything, so let's block any attempts. (should * we throw an exception instead?) diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/dos/DeepNestingCBORParserTest.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/dos/DeepNestingCBORParserTest.java index 8705f8c6e..d4ccdc61a 100644 --- a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/dos/DeepNestingCBORParserTest.java +++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/dos/DeepNestingCBORParserTest.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.StreamReadConstraints; +import com.fasterxml.jackson.core.StreamWriteConstraints; import com.fasterxml.jackson.core.exc.StreamConstraintsException; import com.fasterxml.jackson.dataformat.cbor.CBORFactory; import com.fasterxml.jackson.dataformat.cbor.CBORTestBase; @@ -83,7 +84,10 @@ public void testDeeplyNestedArraysWithUnconstrainedMapper() throws Exception } private void genDeepDoc(final ByteArrayOutputStream out, final int depth) throws IOException { - try (JsonGenerator gen = cborGenerator(out)) { + CBORFactory cborFactory = cborFactoryBuilder() + .streamWriteConstraints(StreamWriteConstraints.builder().maxNestingDepth(Integer.MAX_VALUE).build()) + .build(); + try (JsonGenerator gen = cborGenerator(cborFactory, out)) { for (int i = 0; i < depth; i++) { gen.writeStartObject(); gen.writeFieldName("a"); @@ -96,7 +100,10 @@ private void genDeepDoc(final ByteArrayOutputStream out, final int depth) throws } private void genDeepArrayDoc(final ByteArrayOutputStream out, final int depth) throws IOException { - try (JsonGenerator gen = cborGenerator(out)) { + CBORFactory cborFactory = cborFactoryBuilder() + .streamWriteConstraints(StreamWriteConstraints.builder().maxNestingDepth(Integer.MAX_VALUE).build()) + .build(); + try (JsonGenerator gen = cborGenerator(cborFactory, out)) { for (int i = 0; i < depth; i++) { gen.writeStartObject(); gen.writeFieldName("a"); diff --git a/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonGenerator.java b/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonGenerator.java index 517095064..15ed4b214 100644 --- a/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonGenerator.java +++ b/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonGenerator.java @@ -26,6 +26,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.core.StreamWriteCapability; +import com.fasterxml.jackson.core.StreamWriteConstraints; import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.core.base.GeneratorBase; import com.fasterxml.jackson.core.io.IOContext; @@ -148,6 +149,11 @@ public Version version() { return PackageVersion.VERSION; } + @Override + public StreamWriteConstraints streamWriteConstraints() { + return _ioContext.streamWriteConstraints(); + } + /* /********************************************************************** /* JsonGenerator implementation: state handling diff --git a/protobuf/src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufGenerator.java b/protobuf/src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufGenerator.java index 065595b3e..53eb2433c 100644 --- a/protobuf/src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufGenerator.java +++ b/protobuf/src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufGenerator.java @@ -155,6 +155,11 @@ public void setSchema(ProtobufSchema schema) _pbContext = _rootContext = ProtobufWriteContext.createRootContext(schema.getRootType()); } + @Override + public StreamWriteConstraints streamWriteConstraints() { + return _ioContext.streamWriteConstraints(); + } + @Override // since 2.13 public Object currentValue() { return _pbContext.getCurrentValue(); diff --git a/smile/src/main/java/com/fasterxml/jackson/dataformat/smile/SmileGenerator.java b/smile/src/main/java/com/fasterxml/jackson/dataformat/smile/SmileGenerator.java index f5680fab8..4ddbb150f 100644 --- a/smile/src/main/java/com/fasterxml/jackson/dataformat/smile/SmileGenerator.java +++ b/smile/src/main/java/com/fasterxml/jackson/dataformat/smile/SmileGenerator.java @@ -586,6 +586,11 @@ public SmileGenerator configure(Feature f, boolean state) { return this; } + @Override + public StreamWriteConstraints streamWriteConstraints() { + return _ioContext.streamWriteConstraints(); + } + /* /********************************************************** /* Extended API, other diff --git a/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/parse/dos/DeepNestingSmileParserTest.java b/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/parse/dos/DeepNestingSmileParserTest.java index dd60e155c..02acc30c7 100644 --- a/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/parse/dos/DeepNestingSmileParserTest.java +++ b/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/parse/dos/DeepNestingSmileParserTest.java @@ -4,9 +4,11 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.StreamReadConstraints; +import com.fasterxml.jackson.core.StreamWriteConstraints; import com.fasterxml.jackson.core.exc.StreamConstraintsException; import com.fasterxml.jackson.dataformat.smile.BaseTestForSmile; import com.fasterxml.jackson.dataformat.smile.SmileFactory; +import com.fasterxml.jackson.dataformat.smile.SmileFactoryBuilder; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -83,7 +85,10 @@ public void testDeeplyNestedArraysWithUnconstrainedMapper() throws Exception } private void genDeepDoc(final ByteArrayOutputStream out, final int depth) throws IOException { - try (JsonGenerator gen = smileGenerator(out, true)) { + SmileFactory smileFactory = SmileFactory.builder() + .streamWriteConstraints(StreamWriteConstraints.builder().maxNestingDepth(Integer.MAX_VALUE).build()) + .build(); + try (JsonGenerator gen = smileGenerator(smileFactory, out, true)) { for (int i = 0; i < depth; i++) { gen.writeStartObject(); gen.writeFieldName("a"); @@ -96,7 +101,10 @@ private void genDeepDoc(final ByteArrayOutputStream out, final int depth) throws } private void genDeepArrayDoc(final ByteArrayOutputStream out, final int depth) throws IOException { - try (JsonGenerator gen = smileGenerator(out, true)) { + SmileFactory smileFactory = SmileFactory.builder() + .streamWriteConstraints(StreamWriteConstraints.builder().maxNestingDepth(Integer.MAX_VALUE).build()) + .build(); + try (JsonGenerator gen = smileGenerator(smileFactory, out, true)) { for (int i = 0; i < depth; i++) { gen.writeStartObject(); gen.writeFieldName("a"); From a84ff8bcdb0aa0cf9d7a1c84cfcf90916206ba3f Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 8 Jul 2023 12:19:07 +0100 Subject: [PATCH 5/9] Update AvroGenerator.java --- .../fasterxml/jackson/dataformat/avro/AvroGenerator.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroGenerator.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroGenerator.java index 18ce25b23..cd24f4f8e 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroGenerator.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroGenerator.java @@ -25,7 +25,7 @@ public enum Feature { /** * Feature that can be disabled to prevent Avro from buffering any more - * data then absolutely necessary. + * data than absolutely necessary. * This affects buffering by underlying codec. * Note that disabling buffer is likely to reduce performance if the underlying * input/output is unbuffered. @@ -157,6 +157,11 @@ public void setSchema(AvroSchema schema) schema.getAvroSchema(), _encoder); } + @Override + public StreamWriteConstraints streamWriteConstraints() { + return _ioContext.streamWriteConstraints(); + } + /* /********************************************************** /* Versioned From 2d085166d0aa0742ad2b65c345f36f0bd9a0c052 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 8 Jul 2023 12:24:17 +0100 Subject: [PATCH 6/9] protobuf code --- .../jackson/dataformat/protobuf/ProtobufGenerator.java | 2 ++ .../jackson/dataformat/protobuf/ProtobufWriteContext.java | 1 + 2 files changed, 3 insertions(+) diff --git a/protobuf/src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufGenerator.java b/protobuf/src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufGenerator.java index 53eb2433c..dc0b523d4 100644 --- a/protobuf/src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufGenerator.java +++ b/protobuf/src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufGenerator.java @@ -418,6 +418,7 @@ public final void writeStartArray() throws IOException // NOTE: do NOT clear _currField; needed for actual element type _pbContext = _pbContext.createChildArrayContext(); + streamWriteConstraints().validateNestingDepth(_pbContext.getNestingDepth()); _writeTag = !_currField.packed; /* Unpacked vs packed: if unpacked, nothing special is needed, since it * is equivalent to just replicating same field N times. @@ -486,6 +487,7 @@ public final void writeStartObject() throws IOException // but do NOT clear next field here _inObject = true; } + streamWriteConstraints().validateNestingDepth(_pbContext.getNestingDepth()); // even if within array, object fields use tags _writeTag = true; } diff --git a/protobuf/src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufWriteContext.java b/protobuf/src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufWriteContext.java index 93177b1a1..eb6df991d 100644 --- a/protobuf/src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufWriteContext.java +++ b/protobuf/src/main/java/com/fasterxml/jackson/dataformat/protobuf/ProtobufWriteContext.java @@ -49,6 +49,7 @@ protected ProtobufWriteContext(int type, ProtobufWriteContext parent, super(); _type = type; _parent = parent; + _nestingDepth = parent == null ? 0 : parent._nestingDepth + 1; _message = msg; } From 83a67494dfff600e4f5bc42ff7481cf69c93f03d Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 8 Jul 2023 12:32:10 +0100 Subject: [PATCH 7/9] Update DeepNestingProtobufParserTest.java --- .../protobuf/dos/DeepNestingProtobufParserTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/protobuf/src/test/java/com/fasterxml/jackson/dataformat/protobuf/dos/DeepNestingProtobufParserTest.java b/protobuf/src/test/java/com/fasterxml/jackson/dataformat/protobuf/dos/DeepNestingProtobufParserTest.java index af5787670..006b45745 100644 --- a/protobuf/src/test/java/com/fasterxml/jackson/dataformat/protobuf/dos/DeepNestingProtobufParserTest.java +++ b/protobuf/src/test/java/com/fasterxml/jackson/dataformat/protobuf/dos/DeepNestingProtobufParserTest.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.StreamReadConstraints; +import com.fasterxml.jackson.core.StreamWriteConstraints; import com.fasterxml.jackson.core.exc.StreamConstraintsException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -31,6 +32,7 @@ public Node(int id, Node next) { { ProtobufFactory f = ProtobufFactory.builder() .streamReadConstraints(StreamReadConstraints.builder().maxNestingDepth(Integer.MAX_VALUE).build()) + .streamWriteConstraints(StreamWriteConstraints.builder().maxNestingDepth(Integer.MAX_VALUE).build()) .build(); MAPPER_UNLIMITED = new ProtobufMapper(f); } @@ -71,7 +73,7 @@ private byte[] genDeepDoc(int depth) throws Exception { while (--depth > 0) { node = new Node(depth, node); } - return DEFAULT_MAPPER.writer(NODE_SCHEMA) + return MAPPER_UNLIMITED.writer(NODE_SCHEMA) .writeValueAsBytes(node); } From 260f50e823c27ec6a91e5afa52d361bf96a209b6 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 8 Jul 2023 13:36:22 +0100 Subject: [PATCH 8/9] future proof tests --- .../dataformat/cbor/gen/dos/CyclicDataSerTest.java | 5 ++++- .../cbor/parse/dos/DeepNestingCBORParserTest.java | 12 ++++++++---- .../dataformat/ion/dos/CyclicDataSerTest.java | 5 ++++- .../protobuf/dos/DeepNestingProtobufParserTest.java | 6 ++++-- .../dataformat/smile/gen/dos/CyclicDataSerTest.java | 5 ++++- .../smile/parse/dos/DeepNestingSmileParserTest.java | 13 ++++++++----- 6 files changed, 32 insertions(+), 14 deletions(-) diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/gen/dos/CyclicDataSerTest.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/gen/dos/CyclicDataSerTest.java index 2eaba4764..386e91d15 100644 --- a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/gen/dos/CyclicDataSerTest.java +++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/gen/dos/CyclicDataSerTest.java @@ -1,5 +1,6 @@ package com.fasterxml.jackson.dataformat.cbor.gen.dos; +import com.fasterxml.jackson.core.StreamWriteConstraints; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.cbor.CBORTestBase; @@ -22,8 +23,10 @@ public void testListWithSelfReference() throws Exception { MAPPER.writeValueAsBytes(list); fail("expected JsonMappingException"); } catch (JsonMappingException jmex) { + String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed", + StreamWriteConstraints.DEFAULT_MAX_DEPTH + 1); assertTrue("JsonMappingException message is as expected?", - jmex.getMessage().startsWith("Document nesting depth (1001) exceeds the maximum allowed")); + jmex.getMessage().startsWith(exceptionPrefix)); } } } diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/dos/DeepNestingCBORParserTest.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/dos/DeepNestingCBORParserTest.java index d4ccdc61a..8ba1eac61 100644 --- a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/dos/DeepNestingCBORParserTest.java +++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/dos/DeepNestingCBORParserTest.java @@ -29,8 +29,10 @@ public void testDeeplyNestedObjects() throws Exception } fail("expected StreamConstraintsException"); } catch (StreamConstraintsException e) { - assertTrue("unexpected message: " + e.getMessage(), - e.getMessage().startsWith("Document nesting depth (1001) exceeds the maximum allowed")); + String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed", + StreamReadConstraints.DEFAULT_MAX_DEPTH + 1); + assertTrue("JsonMappingException message is as expected?", + e.getMessage().startsWith(exceptionPrefix)); } } @@ -62,8 +64,10 @@ public void testDeeplyNestedArrays() throws Exception } fail("expected StreamConstraintsException"); } catch (StreamConstraintsException e) { - assertTrue("unexpected message: " + e.getMessage(), - e.getMessage().startsWith("Document nesting depth (1001) exceeds the maximum allowed")); + String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed", + StreamReadConstraints.DEFAULT_MAX_DEPTH + 1); + assertTrue("JsonMappingException message is as expected?", + e.getMessage().startsWith(exceptionPrefix)); } } diff --git a/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/dos/CyclicDataSerTest.java b/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/dos/CyclicDataSerTest.java index e1485c206..b83525264 100644 --- a/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/dos/CyclicDataSerTest.java +++ b/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/dos/CyclicDataSerTest.java @@ -1,5 +1,6 @@ package com.fasterxml.jackson.dataformat.ion.dos; +import com.fasterxml.jackson.core.StreamWriteConstraints; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.ion.IonObjectMapper; @@ -27,8 +28,10 @@ public void testListWithSelfReference() throws Exception { MAPPER.writeValueAsBytes(list); fail("expected JsonMappingException"); } catch (JsonMappingException jmex) { + String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed", + StreamWriteConstraints.DEFAULT_MAX_DEPTH + 1); assertTrue("JsonMappingException message is as expected?", - jmex.getMessage().startsWith("Document nesting depth (1001) exceeds the maximum allowed")); + jmex.getMessage().startsWith(exceptionPrefix)); } } } diff --git a/protobuf/src/test/java/com/fasterxml/jackson/dataformat/protobuf/dos/DeepNestingProtobufParserTest.java b/protobuf/src/test/java/com/fasterxml/jackson/dataformat/protobuf/dos/DeepNestingProtobufParserTest.java index 006b45745..5a16bdbe4 100644 --- a/protobuf/src/test/java/com/fasterxml/jackson/dataformat/protobuf/dos/DeepNestingProtobufParserTest.java +++ b/protobuf/src/test/java/com/fasterxml/jackson/dataformat/protobuf/dos/DeepNestingProtobufParserTest.java @@ -62,8 +62,10 @@ public void testDeeplyNestedObjectsLowLimits() throws Exception while (p.nextToken() != null) { } fail("expected StreamConstraintsException"); } catch (StreamConstraintsException e) { - assertTrue("unexpected message: " + e.getMessage(), - e.getMessage().startsWith("Document nesting depth (1001) exceeds the maximum allowed")); + String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed", + StreamReadConstraints.DEFAULT_MAX_DEPTH + 1); + assertTrue("JsonMappingException message is as expected?", + e.getMessage().startsWith(exceptionPrefix)); } } diff --git a/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/gen/dos/CyclicDataSerTest.java b/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/gen/dos/CyclicDataSerTest.java index a2bc61ae8..3315a3e4e 100644 --- a/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/gen/dos/CyclicDataSerTest.java +++ b/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/gen/dos/CyclicDataSerTest.java @@ -1,5 +1,6 @@ package com.fasterxml.jackson.dataformat.smile.gen.dos; +import com.fasterxml.jackson.core.StreamWriteConstraints; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.smile.BaseTestForSmile; @@ -22,8 +23,10 @@ public void testListWithSelfReference() throws Exception { MAPPER.writeValueAsBytes(list); fail("expected JsonMappingException"); } catch (JsonMappingException jmex) { + String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed", + StreamWriteConstraints.DEFAULT_MAX_DEPTH + 1); assertTrue("JsonMappingException message is as expected?", - jmex.getMessage().startsWith("Document nesting depth (1001) exceeds the maximum allowed")); + jmex.getMessage().startsWith(exceptionPrefix)); } } } diff --git a/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/parse/dos/DeepNestingSmileParserTest.java b/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/parse/dos/DeepNestingSmileParserTest.java index 02acc30c7..0151b979b 100644 --- a/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/parse/dos/DeepNestingSmileParserTest.java +++ b/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/parse/dos/DeepNestingSmileParserTest.java @@ -8,7 +8,6 @@ import com.fasterxml.jackson.core.exc.StreamConstraintsException; import com.fasterxml.jackson.dataformat.smile.BaseTestForSmile; import com.fasterxml.jackson.dataformat.smile.SmileFactory; -import com.fasterxml.jackson.dataformat.smile.SmileFactoryBuilder; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -30,8 +29,10 @@ public void testDeeplyNestedObjects() throws Exception } fail("expected StreamConstraintsException"); } catch (StreamConstraintsException e) { - assertTrue("unexpected message: " + e.getMessage(), - e.getMessage().startsWith("Document nesting depth (1001) exceeds the maximum allowed")); + String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed", + StreamReadConstraints.DEFAULT_MAX_DEPTH + 1); + assertTrue("JsonMappingException message is as expected?", + e.getMessage().startsWith(exceptionPrefix)); } } @@ -63,8 +64,10 @@ public void testDeeplyNestedArrays() throws Exception } fail("expected StreamConstraintsException"); } catch (StreamConstraintsException e) { - assertTrue("unexpected message: " + e.getMessage(), - e.getMessage().startsWith("Document nesting depth (1001) exceeds the maximum allowed")); + String exceptionPrefix = String.format("Document nesting depth (%d) exceeds the maximum allowed", + StreamReadConstraints.DEFAULT_MAX_DEPTH + 1); + assertTrue("JsonMappingException message is as expected?", + e.getMessage().startsWith(exceptionPrefix)); } } From 2d93cdbe10b33dd07864d60a6bfdabcdbcc254aa Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 8 Jul 2023 13:54:33 +0100 Subject: [PATCH 9/9] avro check --- .../com/fasterxml/jackson/dataformat/avro/AvroGenerator.java | 3 +++ .../jackson/dataformat/avro/ser/AvroWriteContext.java | 1 + 2 files changed, 4 insertions(+) diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroGenerator.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroGenerator.java index cd24f4f8e..b30dcf3b6 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroGenerator.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroGenerator.java @@ -379,6 +379,7 @@ public void close() throws IOException @Override public final void writeStartArray() throws IOException { _avroContext = _avroContext.createChildArrayContext(null); + streamWriteConstraints().validateNestingDepth(_avroContext.getNestingDepth()); _complete = false; } @@ -397,12 +398,14 @@ public final void writeEndArray() throws IOException @Override public final void writeStartObject() throws IOException { _avroContext = _avroContext.createChildObjectContext(null); + streamWriteConstraints().validateNestingDepth(_avroContext.getNestingDepth()); _complete = false; } @Override public void writeStartObject(Object forValue) throws IOException { _avroContext = _avroContext.createChildObjectContext(forValue); + streamWriteConstraints().validateNestingDepth(_avroContext.getNestingDepth()); _complete = false; } diff --git a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroWriteContext.java b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroWriteContext.java index 008056ef0..2aa93cb16 100644 --- a/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroWriteContext.java +++ b/avro/src/main/java/com/fasterxml/jackson/dataformat/avro/ser/AvroWriteContext.java @@ -52,6 +52,7 @@ protected AvroWriteContext(int type, AvroWriteContext parent, super(); _type = type; _parent = parent; + _nestingDepth = parent == null ? 0 : parent._nestingDepth + 1; _generator = generator; _schema = schema; _currentValue = currValue;