Skip to content

Commit abc9b46

Browse files
committed
Merge branch '2.11' into 2.12
2 parents d8c902e + de072d3 commit abc9b46

File tree

5 files changed

+193
-70
lines changed

5 files changed

+193
-70
lines changed

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

Lines changed: 89 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ private Feature(boolean defaultState) {
6464
private final static double MATH_POW_2_10 = Math.pow(2, 10);
6565
private final static double MATH_POW_2_NEG14 = Math.pow(2, -14);
6666

67+
// 2.11.4: [dataformats-binary#186] Avoid OOME/DoS for bigger binary;
68+
// read only up to 250k
69+
protected final static int LONGEST_NON_CHUNKED_BINARY = 250_000;
70+
6771
/*
6872
/**********************************************************
6973
/* Configuration
@@ -1708,13 +1712,15 @@ public int readBinaryValue(Base64Variant b64variant, OutputStream out) throws IO
17081712
}
17091713
}
17101714

1711-
private int _readAndWriteBytes(OutputStream out, int total) throws IOException
1715+
private int _readAndWriteBytes(OutputStream out, final int total) throws IOException
17121716
{
17131717
int left = total;
17141718
while (left > 0) {
17151719
int avail = _inputEnd - _inputPtr;
17161720
if (_inputPtr >= _inputEnd) {
1717-
loadMoreGuaranteed();
1721+
if (!loadMore()) {
1722+
_reportIncompleteBinaryRead(total, total-left);
1723+
}
17181724
avail = _inputEnd - _inputPtr;
17191725
}
17201726
int count = Math.min(avail, left);
@@ -2432,33 +2438,55 @@ private final int _nextChunkedByte2() throws IOException
24322438
// either way, got it now
24332439
return _inputBuffer[_inputPtr++];
24342440
}
2435-
2441+
2442+
/**
2443+
* Helper called to complete reading of binary data ("byte string") in
2444+
* case contents are needed.
2445+
*/
24362446
@SuppressWarnings("resource")
24372447
protected byte[] _finishBytes(int len) throws IOException
24382448
{
2449+
// Chunked?
24392450
// First, simple: non-chunked
2440-
if (len >= 0) {
2451+
if (len <= 0) {
24412452
if (len == 0) {
24422453
return NO_BYTES;
24432454
}
2444-
byte[] b = new byte[len];
2445-
if (_inputPtr >= _inputEnd) {
2446-
loadMoreGuaranteed();
2455+
return _finishChunkedBytes();
2456+
}
2457+
// Non-chunked, contiguous
2458+
if (len > LONGEST_NON_CHUNKED_BINARY) {
2459+
// [dataformats-binary#186]: avoid immediate allocation for longest
2460+
return _finishLongContiguousBytes(len);
2461+
}
2462+
2463+
final byte[] b = new byte[len];
2464+
final int expLen = len;
2465+
if (_inputPtr >= _inputEnd) {
2466+
if (!loadMore()) {
2467+
_reportIncompleteBinaryRead(expLen, 0);
24472468
}
2448-
int ptr = 0;
2449-
while (true) {
2450-
int toAdd = Math.min(len, _inputEnd - _inputPtr);
2451-
System.arraycopy(_inputBuffer, _inputPtr, b, ptr, toAdd);
2452-
_inputPtr += toAdd;
2453-
ptr += toAdd;
2454-
len -= toAdd;
2455-
if (len <= 0) {
2456-
return b;
2457-
}
2458-
loadMoreGuaranteed();
2469+
}
2470+
2471+
int ptr = 0;
2472+
while (true) {
2473+
int toAdd = Math.min(len, _inputEnd - _inputPtr);
2474+
System.arraycopy(_inputBuffer, _inputPtr, b, ptr, toAdd);
2475+
_inputPtr += toAdd;
2476+
ptr += toAdd;
2477+
len -= toAdd;
2478+
if (len <= 0) {
2479+
return b;
2480+
}
2481+
if (!loadMore()) {
2482+
_reportIncompleteBinaryRead(expLen, ptr);
24592483
}
24602484
}
2485+
}
24612486

2487+
// @since 2.12
2488+
protected byte[] _finishChunkedBytes() throws IOException
2489+
{
24622490
// or, if not, chunked...
24632491
ByteArrayBuilder bb = _getByteArrayBuilder();
24642492
while (true) {
@@ -2475,14 +2503,17 @@ protected byte[] _finishBytes(int len) throws IOException
24752503
throw _constructError("Mismatched chunk in chunked content: expected "+CBORConstants.MAJOR_TYPE_BYTES
24762504
+" but encountered "+type);
24772505
}
2478-
len = _decodeExplicitLength(ch & 0x1F);
2506+
int len = _decodeExplicitLength(ch & 0x1F);
24792507
if (len < 0) {
24802508
throw _constructError("Illegal chunked-length indicator within chunked-length value (type "+CBORConstants.MAJOR_TYPE_BYTES+")");
24812509
}
2510+
final int chunkLen = len;
24822511
while (len > 0) {
24832512
int avail = _inputEnd - _inputPtr;
24842513
if (_inputPtr >= _inputEnd) {
2485-
loadMoreGuaranteed();
2514+
if (!loadMore()) {
2515+
_reportIncompleteBinaryRead(chunkLen, chunkLen-len);
2516+
}
24862517
avail = _inputEnd - _inputPtr;
24872518
}
24882519
int count = Math.min(avail, len);
@@ -2493,7 +2524,33 @@ protected byte[] _finishBytes(int len) throws IOException
24932524
}
24942525
return bb.toByteArray();
24952526
}
2496-
2527+
2528+
// @since 2.12
2529+
protected byte[] _finishLongContiguousBytes(final int expLen) throws IOException
2530+
{
2531+
int left = expLen;
2532+
2533+
// 04-Dec-2020, tatu: Let's NOT use recycled instance since we have much
2534+
// longer content and there is likely less benefit of trying to recycle
2535+
// segments
2536+
try (final ByteArrayBuilder bb = new ByteArrayBuilder(LONGEST_NON_CHUNKED_BINARY >> 1)) {
2537+
while (left > 0) {
2538+
int avail = _inputEnd - _inputPtr;
2539+
if (avail <= 0) {
2540+
if (!loadMore()) {
2541+
_reportIncompleteBinaryRead(expLen, expLen-left);
2542+
}
2543+
avail = _inputEnd - _inputPtr;
2544+
}
2545+
int count = Math.min(avail, left);
2546+
bb.write(_inputBuffer, _inputPtr, count);
2547+
_inputPtr += count;
2548+
left -= count;
2549+
}
2550+
return bb.toByteArray();
2551+
}
2552+
}
2553+
24972554
protected final JsonToken _decodeFieldName() throws IOException
24982555
{
24992556
if (_inputPtr >= _inputEnd) {
@@ -2642,9 +2699,8 @@ protected final void _decodeNonStringName(int ch) throws IOException
26422699
} else if (type == CBORConstants.MAJOR_TYPE_INT_NEG) {
26432700
name = _numberToName(ch, true);
26442701
} else if (type == CBORConstants.MAJOR_TYPE_BYTES) {
2645-
/* 08-Sep-2014, tatu: As per [Issue#5], there are codecs
2646-
* (f.ex. Perl module "CBOR::XS") that use Binary data...
2647-
*/
2702+
// 08-Sep-2014, tatu: As per [Issue#5], there are codecs
2703+
// (f.ex. Perl module "CBOR::XS") that use Binary data...
26482704
final int blen = _decodeExplicitLength(ch & 0x1F);
26492705
byte[] b = _finishBytes(blen);
26502706
// TODO: Optimize, if this becomes commonly used & bottleneck; we have
@@ -3249,7 +3305,7 @@ private final int _decodeChunkedUTF8_4(int c) throws IOException
32493305
/**********************************************************
32503306
*/
32513307

3252-
protected final boolean loadMore() throws IOException
3308+
protected boolean loadMore() throws IOException
32533309
{
32543310
if (_inputStream != null) {
32553311
_currInputProcessed += _inputEnd;
@@ -3270,7 +3326,7 @@ protected final boolean loadMore() throws IOException
32703326
return false;
32713327
}
32723328

3273-
protected final void loadMoreGuaranteed() throws IOException {
3329+
protected void loadMoreGuaranteed() throws IOException {
32743330
if (!loadMore()) { _reportInvalidEOF(); }
32753331
}
32763332

@@ -3395,6 +3451,13 @@ protected void _reportInvalidOther(int mask, int ptr) throws JsonParseException
33953451
_reportInvalidOther(mask);
33963452
}
33973453

3454+
// @since 2.12
3455+
protected void _reportIncompleteBinaryRead(int expLen, int actLen) throws IOException
3456+
{
3457+
_reportInvalidEOF(String.format(" for Binary value: expected %d bytes, only found %d",
3458+
expLen, actLen), _currToken);
3459+
}
3460+
33983461
/*
33993462
/**********************************************************
34003463
/* Internal methods, other
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package com.fasterxml.jackson.dataformat.cbor;
2+
3+
import java.io.ByteArrayOutputStream;
4+
5+
import com.fasterxml.jackson.core.JsonParser;
6+
import com.fasterxml.jackson.core.JsonProcessingException;
7+
import com.fasterxml.jackson.core.JsonToken;
8+
9+
import com.fasterxml.jackson.databind.ObjectMapper;
10+
11+
// Mostly for [dataformats-binary#186]: corrupt encoding indicating humongous payload
12+
public class BrokenLongBinary186Test extends CBORTestBase
13+
{
14+
private final ObjectMapper MAPPER = cborMapper();
15+
16+
/*
17+
/**********************************************************************
18+
/* First regular, read-it-all access, from non-chunked
19+
/**********************************************************************
20+
*/
21+
22+
// [dataformats-binary#186]
23+
public void testCorruptVeryLongBinary() throws Exception {
24+
// Let's do about 2 GB to likely trigger failure
25+
_testCorruptLong(1_999_999_999, 95000);
26+
}
27+
28+
// [dataformats-binary#186]
29+
public void testCorruptQuiteLongBinary() throws Exception {
30+
// Value below limit for chunked handling
31+
_testCorruptLong(CBORParser.LONGEST_NON_CHUNKED_BINARY >> 1, 37);
32+
}
33+
34+
private void _testCorruptLong(int allegedLength, int actualIncluded) throws Exception
35+
{
36+
JsonParser p = MAPPER.createParser(_createBrokenDoc(allegedLength, actualIncluded));
37+
assertEquals(JsonToken.VALUE_EMBEDDED_OBJECT, p.nextToken());
38+
try {
39+
p.getBinaryValue();
40+
fail("Should fail");
41+
} catch (JsonProcessingException e) {
42+
verifyException(e, "Unexpected end-of-input for Binary value");
43+
verifyException(e, "expected "+allegedLength+" bytes, only found "+actualIncluded);
44+
}
45+
}
46+
47+
/*
48+
/**********************************************************************
49+
/* And then "streaming" access
50+
/**********************************************************************
51+
*/
52+
53+
// [dataformats-binary#186]
54+
public void testQuiteLongStreaming() throws Exception
55+
{
56+
// Can try bit shorter here, like 500 megs
57+
final int allegedLength = 500_000_000;
58+
59+
JsonParser p = MAPPER.createParser(_createBrokenDoc(allegedLength, 72000));
60+
assertEquals(JsonToken.VALUE_EMBEDDED_OBJECT, p.nextToken());
61+
try {
62+
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
63+
p.readBinaryValue(bytes);
64+
fail("Should fail");
65+
} catch (JsonProcessingException e) {
66+
verifyException(e, "Unexpected end-of-input for Binary value");
67+
verifyException(e, "expected "+allegedLength+" bytes, only found 72000");
68+
}
69+
}
70+
71+
private byte[] _createBrokenDoc(int allegedLength, int actualIncluded) throws Exception
72+
{
73+
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
74+
75+
if (allegedLength > 0xFFFF) {
76+
bytes.write((byte) (CBORConstants.PREFIX_TYPE_BYTES | CBORConstants.SUFFIX_UINT32_ELEMENTS));
77+
bytes.write((byte) (allegedLength >> 24));
78+
bytes.write((byte) (allegedLength >> 16));
79+
bytes.write((byte) (allegedLength >> 8));
80+
bytes.write((byte) allegedLength);
81+
} else { // assume shorter
82+
bytes.write((byte) (CBORConstants.PREFIX_TYPE_BYTES | CBORConstants.SUFFIX_UINT16_ELEMENTS));
83+
bytes.write((byte) (allegedLength >> 8));
84+
bytes.write((byte) allegedLength);
85+
}
86+
// but only include couple of bytes
87+
for (int i = 0; i < actualIncluded; ++i) {
88+
bytes.write((byte) i);
89+
}
90+
return bytes.toByteArray();
91+
}
92+
93+
}

cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/failing/BrokenLongBinary186Test.java

Lines changed: 0 additions & 42 deletions
This file was deleted.

release-notes/CREDITS-2.x

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,11 @@ John (iziamos@github)
116116
(2.10.0)
117117

118118
Paul Adolph (padolph@github)
119-
* Reported #185: Internal parsing of tagged arrays can lead to stack overflow
119+
* Reported #185: (cbor) Internal parsing of tagged arrays can lead to stack overflow
120120
(2.10.1)
121+
* Reported #186: (cbor) Eager allocation of byte buffer can cause `java.lang.OutOfMemoryError`
122+
exception
123+
(2.11.4)
121124

122125
Yanming Zhou (quaff@github)
123126
* Reported #188: Unexpected `MismatchedInputException` for `byte[]` value bound to `String`

release-notes/VERSION-2.x

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,15 @@ Project: jackson-datatypes-binaryModules:
2121
(requested by davidlepilote@github)
2222
- Add Gradle Module Metadata (https://blog.gradle.org/alignment-with-gradle-module-metadata)
2323

24+
2.11.4 (not yet released)
25+
26+
#186: (cbor) Eager allocation of byte buffer can cause `java.lang.OutOfMemoryError`
27+
exception
28+
(reported by Paul A)
29+
2430
2.11.3 (02-Oct-2020)
2531

26-
#219: Cache record names to avoid hitting class loader
32+
#219: (avro) Cache record names to avoid hitting class loader
2733
(contributed by Marcos P)
2834

2935
2.11.2 (02-Aug-2020)

0 commit comments

Comments
 (0)