Skip to content

Commit

Permalink
Fixed #260 (lazy byte[] allocation for longer binary payloads)
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Mar 20, 2021
1 parent 9b82247 commit 5740026
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2558,7 +2558,7 @@ protected byte[] _finishLongContiguousBytes(final int expLen) throws IOException
int count = Math.min(avail, left);
bb.write(_inputBuffer, _inputPtr, count);
_inputPtr += count;
left -= count;
left -= count;
}
return bb.toByteArray();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class Fuzz32173LongTextTest extends CBORTestBase
{
private final ObjectMapper MAPPER = cborMapper();

public void testInvalidShortText() throws Exception
public void testTruncatedLongText() throws Exception
{
final byte[] input = new byte[] {
0x7A, // Text value
Expand Down
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@
<excludes>
<exclude>com/fasterxml/jackson/**/failing/*.java</exclude>
</excludes>
<!-- 20-Mar-2021, tatu: limit heap for tests to catch some
of "too big allocation" cases
-->
<argLine>-Xmx1024m</argLine>
</configuration>
</plugin>
</plugins>
Expand Down
2 changes: 2 additions & 0 deletions release-notes/CREDITS-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ Fabian Meumertzheim (fmeum@github)
* Reported #259: (cbor) Failed to handle case of alleged String with length of
Integer.MAX_VALUE
(2.12.3)
* Reported #260: (smile) Allocate byte[] lazily for longer Smile binary data payloads
(2.12.3)
* Reported #261 (cbor) CBORParser need to validate zero-length byte[] for BigInteger
(2.12.3)

Expand Down
2 changes: 2 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Modules:
(reported by Fabian M)
#259: (cbor) Failed to handle case of alleged String with length of Integer.MAX_VALUE
(reported by Fabian M)
#260: (smile) Allocate byte[] lazily for longer Smile binary data payloads
(reported by Fabian M)
#261 (cbor) CBORParser need to validate zero-length byte[] for BigInteger
(reported by Fabian M)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.io.IOContext;
import com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer;
import com.fasterxml.jackson.core.util.ByteArrayBuilder;

import static com.fasterxml.jackson.dataformat.smile.SmileConstants.BYTE_MARKER_END_OF_STRING;

Expand Down Expand Up @@ -239,9 +240,11 @@ private final byte _nextByteGuaranteed() throws IOException
}

protected final void _loadMoreGuaranteed() throws IOException {
if (!_loadMore()) { _reportInvalidEOF(); }
if (!_loadMore()) {
_reportInvalidEOF();
}
}

protected final boolean _loadMore() throws IOException
{
//_currInputRowStart -= _inputEnd;
Expand Down Expand Up @@ -1895,7 +1898,7 @@ protected final void _finishToken() throws IOException
_binaryValue = _read7BitBinaryWithLength();
return;
case 7: // binary, raw
_finishRawBinary();
_binaryValue = _finishRawBinary();
return;
}
}
Expand Down Expand Up @@ -2403,27 +2406,67 @@ private final void _decodeLongUnicode() throws IOException
_textBuffer.setCurrentLength(outPtr);
}

private final void _finishRawBinary() throws IOException
private final byte[] _finishRawBinary() throws IOException
{
int byteLen = _readUnsignedVInt();
_binaryValue = new byte[byteLen];

// 20-Mar-2021, tatu [dataformats-binary#260]: avoid eager allocation
// for very large content
if (byteLen > LONGEST_NON_CHUNKED_BINARY) {
return _finishLongContiguousBytes(byteLen);
}

final int expLen = byteLen;
final byte[] b = new byte[byteLen];

if (_inputPtr >= _inputEnd) {
if (!_loadMore()) {
_reportIncompleteBinaryRead(expLen, 0);
}
_loadMoreGuaranteed();
}
int ptr = 0;
while (true) {
int toAdd = Math.min(byteLen, _inputEnd - _inputPtr);
System.arraycopy(_inputBuffer, _inputPtr, _binaryValue, ptr, toAdd);
System.arraycopy(_inputBuffer, _inputPtr, b, ptr, toAdd);
_inputPtr += toAdd;
ptr += toAdd;
byteLen -= toAdd;
if (byteLen <= 0) {
return;
return b;
}
if (!_loadMore()) {
_reportIncompleteBinaryRead(expLen, ptr);
}
_loadMoreGuaranteed();
}
}


// @since 2.12.3
protected byte[] _finishLongContiguousBytes(final int expLen) throws IOException
{
int left = expLen;

// 20-Mar-2021, tatu: Let's NOT use recycled instance since we have much
// longer content and there is likely less benefit of trying to recycle
// segments
try (final ByteArrayBuilder bb = new ByteArrayBuilder(LONGEST_NON_CHUNKED_BINARY >> 1)) {
while (left > 0) {
int avail = _inputEnd - _inputPtr;
if (avail <= 0) {
if (!_loadMore()) {
_reportIncompleteBinaryRead(expLen, expLen-left);
}
avail = _inputEnd - _inputPtr;
}
int count = Math.min(avail, left);
bb.write(_inputBuffer, _inputPtr, count);
_inputPtr += count;
left -= count;
}
return bb.toByteArray();
}
}

/*
/**********************************************************
/* Internal methods, skipping
Expand Down Expand Up @@ -2693,6 +2736,13 @@ protected void _reportInvalidOther(int mask, int ptr) throws JsonParseException
_reportInvalidOther(mask);
}

// @since 2.12.3
protected void _reportIncompleteBinaryRead(int expLen, int actLen) throws IOException
{
_reportInvalidEOF(String.format(" for Binary value: expected %d bytes, only found %d",
expLen, actLen), _currToken);
}

/*
/**********************************************************
/* Internal methods, other
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ public abstract class SmileParserBase extends ParserMinimalBase
{
protected final static String[] NO_STRINGS = new String[0];

// 2.12.3: [dataformats-binary#260] Avoid OOME/DoS for bigger binary;
// read only up to 250k
protected final static int LONGEST_NON_CHUNKED_BINARY = 250_000;

/*
/**********************************************************
/* Config
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.fasterxml.jackson.dataformat.smile.fuzz;

import java.io.ByteArrayInputStream;
import java.util.Arrays;

import com.fasterxml.jackson.core.io.JsonEOFException;
import com.fasterxml.jackson.databind.ObjectMapper;

import com.fasterxml.jackson.dataformat.smile.BaseTestForSmile;

// For [dataformats-binary#]
public class Fuzz32180BinaryTest extends BaseTestForSmile
{
private final ObjectMapper MAPPER = smileMapper();

// Payload:
public void testInvalidBinary() throws Exception
{
final byte[] input0 = new byte[] {
0x3A, 0x29, 0x0A, 0x00, // smile signature
(byte) 0xFD, // raw binary
0x0F, 0x7E, 0x20,
0x20, (byte) 0xFF, // 5 byte VInt for 0x7fe4083f (close to Integer.MAX_VALUE)
// and one byte of binary payload
0x00
};
// Let's expand slightly to avoid too early detection, ensure that we are
// not only checking completely missing payload or such
final byte[] input = Arrays.copyOf(input0, 65 * 1024);

try (ByteArrayInputStream bytes = new ByteArrayInputStream(input)) {
try {
/*JsonNode root =*/ MAPPER.readTree(bytes);
} catch (JsonEOFException e) {
verifyException(e, "Unexpected end-of-input for Binary value: expected 2145650751 bytes, only found 66550");
} catch (OutOfMemoryError e) {
// Just to make it easier to see on fail (not ideal but better than nothing)
e.printStackTrace();
throw e;
}
}
}
}

0 comments on commit 5740026

Please sign in to comment.