Skip to content

Commit c57f7be

Browse files
authored
Fixes #392: add max-doc-length validation for cbor, smile (#476)
1 parent 966ac52 commit c57f7be

File tree

9 files changed

+227
-38
lines changed

9 files changed

+227
-38
lines changed

cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORParser.java

+16-7
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,11 @@ public int getFirstTag() {
182182
*/
183183
protected final IOContext _ioContext;
184184

185+
/**
186+
* @since 2.17
187+
*/
188+
protected final StreamReadConstraints _streamReadConstraints;
189+
185190
/**
186191
* Flag that indicates whether parser is closed or not. Gets
187192
* set when parser is either closed by explicit call
@@ -532,11 +537,13 @@ public CBORParser(IOContext ctxt, int parserFeatures, int cborFeatures,
532537

533538
_tokenInputRow = -1;
534539
_tokenInputCol = -1;
540+
541+
_streamReadConstraints = ctxt.streamReadConstraints();
535542
}
536543

537544
@Override
538545
public StreamReadConstraints streamReadConstraints() {
539-
return _ioContext.streamReadConstraints();
546+
return _streamReadConstraints;
540547
}
541548

542549
@Override
@@ -1127,7 +1134,7 @@ protected JsonToken _handleTaggedBinary(TagList tags) throws IOException
11271134
if (_binaryValue.length == 0) {
11281135
_numberBigInt = BigInteger.ZERO;
11291136
} else {
1130-
streamReadConstraints().validateIntegerLength(_binaryValue.length);
1137+
_streamReadConstraints.validateIntegerLength(_binaryValue.length);
11311138
BigInteger nr = new BigInteger(_binaryValue);
11321139
if (neg) {
11331140
nr = nr.negate();
@@ -2163,7 +2170,7 @@ protected void convertNumberToBigInteger() throws IOException
21632170
{
21642171
if ((_numTypesValid & NR_BIGDECIMAL) != 0) {
21652172
// here it'll just get truncated, no exceptions thrown
2166-
streamReadConstraints().validateBigIntegerScale(_numberBigDecimal.scale());
2173+
_streamReadConstraints.validateBigIntegerScale(_numberBigDecimal.scale());
21672174
_numberBigInt = _numberBigDecimal.toBigInteger();
21682175
} else if ((_numTypesValid & NR_LONG) != 0) {
21692176
_numberBigInt = BigInteger.valueOf(_numberLong);
@@ -2232,7 +2239,7 @@ protected void convertNumberToBigDecimal() throws IOException
22322239
if (text == null) {
22332240
_throwInternal();
22342241
}
2235-
streamReadConstraints().validateFPLength(text.length());
2242+
_streamReadConstraints.validateFPLength(text.length());
22362243
_numberBigDecimal = NumberInput.parseBigDecimal(
22372244
text, isEnabled(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER));
22382245
} else if ((_numTypesValid & NR_BIGINT) != 0) {
@@ -3655,7 +3662,7 @@ protected boolean loadMore() throws IOException
36553662
{
36563663
if (_inputStream != null) {
36573664
_currInputProcessed += _inputEnd;
3658-
3665+
_streamReadConstraints.validateDocumentLength(_currInputProcessed);
36593666
int count = _inputStream.read(_inputBuffer, 0, _inputBuffer.length);
36603667
if (count > 0) {
36613668
_inputPtr = 0;
@@ -3697,6 +3704,7 @@ protected final void _loadToHaveAtLeast(int minAvailable) throws IOException
36973704
}
36983705
// Needs to be done here, as per [dataformats-binary#178]
36993706
_currInputProcessed += _inputPtr;
3707+
_streamReadConstraints.validateDocumentLength(_currInputProcessed);
37003708
_inputPtr = 0;
37013709
while (_inputEnd < minAvailable) {
37023710
int count = _inputStream.read(_inputBuffer, _inputEnd, _inputBuffer.length - _inputEnd);
@@ -3731,6 +3739,7 @@ protected final boolean _tryToLoadToHaveAtLeast(int minAvailable) throws IOExcep
37313739
}
37323740
// Needs to be done here, as per [dataformats-binary#178]
37333741
_currInputProcessed += _inputPtr;
3742+
_streamReadConstraints.validateDocumentLength(_currInputProcessed);
37343743
_inputPtr = 0;
37353744
while (_inputEnd < minAvailable) {
37363745
int count = _inputStream.read(_inputBuffer, _inputEnd, _inputBuffer.length - _inputEnd);
@@ -3906,11 +3915,11 @@ private final BigInteger _bigNegative(long l) {
39063915

39073916
private void createChildArrayContext(final int len) throws IOException {
39083917
_streamReadContext = _streamReadContext.createChildArrayContext(len);
3909-
streamReadConstraints().validateNestingDepth(_streamReadContext.getNestingDepth());
3918+
_streamReadConstraints.validateNestingDepth(_streamReadContext.getNestingDepth());
39103919
}
39113920

39123921
private void createChildObjectContext(final int len) throws IOException {
39133922
_streamReadContext = _streamReadContext.createChildObjectContext(len);
3914-
streamReadConstraints().validateNestingDepth(_streamReadContext.getNestingDepth());
3923+
_streamReadConstraints.validateNestingDepth(_streamReadContext.getNestingDepth());
39153924
}
39163925
}

cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/gen/constraints/DeeplyNestedCBORReadWriteTest.java

+32-11
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import com.fasterxml.jackson.databind.JsonNode;
99
import com.fasterxml.jackson.databind.ObjectMapper;
10+
import com.fasterxml.jackson.databind.node.ArrayNode;
1011
import com.fasterxml.jackson.databind.node.ObjectNode;
1112

1213
import com.fasterxml.jackson.dataformat.cbor.CBORFactory;
@@ -26,17 +27,18 @@ public class DeeplyNestedCBORReadWriteTest extends CBORTestBase
2627
.build()
2728
);
2829

29-
public void testDeepNestingRead() throws Exception
30-
{
31-
final byte[] DOC = MAPPER_CONSTRAINED.writeValueAsBytes(createDeepNestedDoc(11));
32-
try (JsonParser p = MAPPER_CONSTRAINED.createParser(DOC)) {
33-
_testDeepNestingRead(p);
34-
}
30+
public void testDeepNestingArrayRead() throws Exception {
31+
_testDeepNestingRead(createDeepNestedArrayDoc(13));
32+
}
33+
34+
public void testDeepNestingObjectRead() throws Exception {
35+
_testDeepNestingRead(createDeepNestedObjectDoc(13));
3536
}
3637

37-
private void _testDeepNestingRead(JsonParser p) throws Exception
38+
private void _testDeepNestingRead(JsonNode docRoot) throws Exception
3839
{
39-
try {
40+
byte[] doc = MAPPER_VANILLA.writeValueAsBytes(docRoot);
41+
try (JsonParser p = MAPPER_CONSTRAINED.createParser(doc)) {
4042
while (p.nextToken() != null) { }
4143
fail("expected StreamConstraintsException");
4244
} catch (StreamConstraintsException e) {
@@ -45,9 +47,16 @@ private void _testDeepNestingRead(JsonParser p) throws Exception
4547
}
4648
}
4749

48-
public void testDeepNestingWrite() throws Exception
50+
public void testDeepNestingArrayWrite() throws Exception {
51+
_testDeepNestingWrite(createDeepNestedArrayDoc(13));
52+
}
53+
54+
public void testDeepNestingObjectWrite() throws Exception {
55+
_testDeepNestingWrite(createDeepNestedObjectDoc(13));
56+
}
57+
58+
private void _testDeepNestingWrite(JsonNode docRoot) throws Exception
4959
{
50-
final JsonNode docRoot = createDeepNestedDoc(13);
5160
try {
5261
MAPPER_CONSTRAINED.writeValueAsBytes(docRoot);
5362
fail("Should not pass");
@@ -57,7 +66,19 @@ public void testDeepNestingWrite() throws Exception
5766
}
5867
}
5968

60-
private JsonNode createDeepNestedDoc(final int depth) throws Exception
69+
private JsonNode createDeepNestedArrayDoc(final int depth) throws Exception
70+
{
71+
final ArrayNode root = MAPPER_VANILLA.createArrayNode();
72+
ArrayNode curr = root;
73+
for (int i = 0; i < depth; ++i) {
74+
curr.add(42);
75+
curr = curr.addArray();
76+
}
77+
curr.add("text");
78+
return root;
79+
}
80+
81+
private JsonNode createDeepNestedObjectDoc(final int depth) throws Exception
6182
{
6283
final ObjectNode root = MAPPER_VANILLA.createObjectNode();
6384
ObjectNode curr = root;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.fasterxml.jackson.dataformat.cbor.gen.constraints;
2+
3+
import java.io.ByteArrayInputStream;
4+
import java.io.ByteArrayOutputStream;
5+
import java.util.UUID;
6+
7+
import com.fasterxml.jackson.core.JsonGenerator;
8+
import com.fasterxml.jackson.core.JsonParser;
9+
import com.fasterxml.jackson.core.StreamReadConstraints;
10+
import com.fasterxml.jackson.core.exc.StreamConstraintsException;
11+
12+
import com.fasterxml.jackson.databind.ObjectMapper;
13+
import com.fasterxml.jackson.dataformat.cbor.CBORFactory;
14+
import com.fasterxml.jackson.dataformat.cbor.CBORTestBase;
15+
16+
public class LongDocumentCBORReadTest extends CBORTestBase
17+
{
18+
private final ObjectMapper MAPPER_VANILLA = cborMapper();
19+
20+
private final ObjectMapper MAPPER_CONSTRAINED = cborMapper(
21+
CBORFactory.builder()
22+
// limit to 100kB doc reads
23+
.streamReadConstraints(StreamReadConstraints.builder()
24+
.maxDocumentLength(50_000)
25+
.build()
26+
).build());
27+
28+
public void testLongDocumentConstraint() throws Exception
29+
{
30+
// Need a bit longer than minimum since checking is approximate, not exact
31+
byte[] doc = createBigDoc(60_000);
32+
// Must read from `InputStream` as validation is during "loadMore()":
33+
try (JsonParser p = MAPPER_CONSTRAINED.createParser(new ByteArrayInputStream(doc))) {
34+
while (p.nextToken() != null) { }
35+
fail("expected StreamConstraintsException");
36+
} catch (StreamConstraintsException e) {
37+
final String msg = e.getMessage();
38+
39+
assertTrue(msg.contains("Document length ("));
40+
assertTrue(msg.contains("exceeds the maximum allowed (50000"));
41+
}
42+
}
43+
44+
private byte[] createBigDoc(final int size) throws Exception
45+
{
46+
ByteArrayOutputStream bytes = new ByteArrayOutputStream(size + 1000);
47+
try (JsonGenerator g = MAPPER_VANILLA.createGenerator(bytes)) {
48+
g.writeStartArray();
49+
50+
do {
51+
g.writeStartObject();
52+
g.writeStringField("id", UUID.randomUUID().toString());
53+
g.writeNumberField("size", bytes.size());
54+
g.writeNumberField("stuff", Long.MAX_VALUE);
55+
g.writeEndObject();
56+
57+
g.flush();
58+
} while (bytes.size() < size);
59+
g.writeEndArray();
60+
}
61+
return bytes.toByteArray();
62+
}
63+
}

release-notes/VERSION-2.x

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ Active maintainers:
1818

1919
#316 (cbor) Uncaught exception in
2020
`com.fasterxml.jackson.dataformat.cbor.CBORParser._finishShortText`
21+
#392: (cbor, smile) Support `StreamReadConstraints.maxDocumentLength`
22+
validation for CBOR, Smile
2123
#417: (ion) `IonReader` classes contain assert statement which could throw
2224
unexpected `AssertionError`
2325
(fix contributed by Arthur C)

smile/src/main/java/com/fasterxml/jackson/dataformat/smile/SmileParser.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ protected final boolean _loadMore() throws IOException
276276
if (_inputStream != null) {
277277
int count = _inputStream.read(_inputBuffer, 0, _inputBuffer.length);
278278
_currInputProcessed += _inputEnd;
279+
_streamReadConstraints.validateDocumentLength(_currInputProcessed);
279280
_inputPtr = 0;
280281
if (count > 0) {
281282
_inputEnd = count;
@@ -334,6 +335,7 @@ protected final int _tryToLoadToHaveAtLeast(int minAvailable) throws IOException
334335
// Need to move remaining data in front?
335336
int amount = _inputEnd - _inputPtr;
336337
_currInputProcessed += _inputPtr;
338+
_streamReadConstraints.validateDocumentLength(_currInputProcessed);
337339
if (amount > 0 && _inputPtr > 0) {
338340
//_currInputRowStart -= _inputPtr;
339341
System.arraycopy(_inputBuffer, _inputPtr, _inputBuffer, 0, amount);
@@ -2246,7 +2248,7 @@ private final void _finishBigInteger() throws IOException
22462248
if (raw.length == 0) {
22472249
_numberBigInt = BigInteger.ZERO;
22482250
} else {
2249-
streamReadConstraints().validateIntegerLength(raw.length);
2251+
_streamReadConstraints.validateIntegerLength(raw.length);
22502252
_numberBigInt = new BigInteger(raw);
22512253
}
22522254
_numTypesValid = NR_BIGINT;
@@ -2294,7 +2296,7 @@ private final void _finishBigDecimal() throws IOException
22942296
if (raw.length == 0) {
22952297
_numberBigDecimal = BigDecimal.ZERO;
22962298
} else {
2297-
streamReadConstraints().validateFPLength(raw.length);
2299+
_streamReadConstraints.validateFPLength(raw.length);
22982300
BigInteger unscaledValue = new BigInteger(raw);
22992301
_numberBigDecimal = new BigDecimal(unscaledValue, scale);
23002302
}
@@ -3129,12 +3131,12 @@ private final JsonToken _eofAsNextToken() throws IOException {
31293131

31303132
private void createChildArrayContext(final int lineNr, final int colNr) throws IOException {
31313133
_streamReadContext = _streamReadContext.createChildArrayContext(lineNr, colNr);
3132-
streamReadConstraints().validateNestingDepth(_streamReadContext.getNestingDepth());
3134+
_streamReadConstraints.validateNestingDepth(_streamReadContext.getNestingDepth());
31333135
}
31343136

31353137
private void createChildObjectContext(final int lineNr, final int colNr) throws IOException {
31363138
_streamReadContext = _streamReadContext.createChildObjectContext(lineNr, colNr);
3137-
streamReadConstraints().validateNestingDepth(_streamReadContext.getNestingDepth());
3139+
_streamReadConstraints.validateNestingDepth(_streamReadContext.getNestingDepth());
31383140
}
31393141
}
31403142

smile/src/main/java/com/fasterxml/jackson/dataformat/smile/SmileParserBase.java

+8-2
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ public abstract class SmileParserBase extends ParserMinimalBase
7171
*/
7272
protected final IOContext _ioContext;
7373

74+
/**
75+
* @since 2.17
76+
*/
77+
protected final StreamReadConstraints _streamReadConstraints;
78+
7479
/**
7580
* Flag that indicates whether parser is closed or not. Gets
7681
* set when parser is either closed by explicit call
@@ -252,6 +257,7 @@ protected SmileParserBase(IOContext ctxt, int parserFeatures, int formatFeatures
252257
super(parserFeatures);
253258
_formatFeatures = formatFeatures;
254259
_ioContext = ctxt;
260+
_streamReadConstraints = ctxt.streamReadConstraints();
255261
_symbols = sym;
256262
_symbolsCanonical = sym.isCanonicalizing();
257263
DupDetector dups = Feature.STRICT_DUPLICATE_DETECTION.enabledIn(parserFeatures)
@@ -262,7 +268,7 @@ protected SmileParserBase(IOContext ctxt, int parserFeatures, int formatFeatures
262268

263269
@Override
264270
public StreamReadConstraints streamReadConstraints() {
265-
return _ioContext.streamReadConstraints();
271+
return _streamReadConstraints;
266272
}
267273

268274
/*
@@ -678,7 +684,7 @@ protected final void convertNumberToBigInteger() throws IOException
678684
{
679685
if ((_numTypesValid & NR_BIGDECIMAL) != 0) {
680686
// here it'll just get truncated, no exceptions thrown
681-
streamReadConstraints().validateBigIntegerScale(_numberBigDecimal.scale());
687+
_streamReadConstraints.validateBigIntegerScale(_numberBigDecimal.scale());
682688
_numberBigInt = _numberBigDecimal.toBigInteger();
683689
} else if ((_numTypesValid & NR_LONG) != 0) {
684690
_numberBigInt = BigInteger.valueOf(_numberLong);

smile/src/main/java/com/fasterxml/jackson/dataformat/smile/async/NonBlockingByteArrayParser.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public void feedInput(byte[] buf, int start, int end) throws IOException
9090
}
9191
// Time to update pointers first
9292
_currInputProcessed += _origBufferLen;
93+
_streamReadConstraints.validateDocumentLength(_currInputProcessed);
9394

9495
// And then update buffer settings
9596
_inputBuffer = buf;
@@ -1291,7 +1292,7 @@ private final JsonToken _finishBigIntBody() throws IOException
12911292
{
12921293
if (_decode7BitEncoded()) { // got it all!
12931294
final byte[] array = _byteArrayBuilder.toByteArray();
1294-
streamReadConstraints().validateIntegerLength(array.length);
1295+
_streamReadConstraints.validateIntegerLength(array.length);
12951296
_numberBigInt = new BigInteger(array);
12961297
_numberType = NumberType.BIG_INTEGER;
12971298
_numTypesValid = NR_BIGINT;
@@ -1440,7 +1441,7 @@ private final JsonToken _finishBigDecimalBody() throws IOException
14401441
// note: scale value is signed, needs zigzag, so:
14411442
final int scale = SmileUtil.zigzagDecode((int) _pending64);
14421443
final byte[] array = _byteArrayBuilder.toByteArray();
1443-
streamReadConstraints().validateFPLength(array.length);
1444+
_streamReadConstraints.validateFPLength(array.length);
14441445
BigInteger bigInt = new BigInteger(array);
14451446
_numberBigDecimal = new BigDecimal(bigInt, scale);
14461447
_numberType = NumberType.BIG_DECIMAL;

0 commit comments

Comments
 (0)