Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,8 @@ protected JsonParser _createParser(char[] data, int offset, int len, IOContext c

@Override
protected AvroParser _createParser(byte[] data, int offset, int len, IOContext ctxt) throws IOException {
// [core#1548] Validate doc length up front for fixed buffers
_streamReadConstraints.validateDocumentLength(len);
// !!! 21-Apr-2017, tatu: make configurable
return new JacksonAvroParserImpl(ctxt, _parserFeatures, _avroParserFeatures,
// return new ApacheAvroParserImpl(ctxt, _parserFeatures, _avroParserFeatures,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ protected AvroParser _createParser(InputStream in, IOContext ctxt) throws IOExce

@Override
protected AvroParser _createParser(byte[] data, int offset, int len, IOContext ctxt) throws IOException {
// [core#1548] Validate doc length up front for fixed buffers
_streamReadConstraints.validateDocumentLength(len);
return new ApacheAvroParserImpl(ctxt, _parserFeatures, _avroParserFeatures,
_avroRecyclerPool.acquireAndLinkPooled(),
_objectCodec, data, offset, len);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.fasterxml.jackson.dataformat.avro.apacheimpl;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
Expand Down Expand Up @@ -94,6 +95,14 @@ public ApacheAvroParserImpl(IOContext ctxt, int parserFeatures, int avroFeatures

_apacheCodecRecycler = apacheCodecRecycler;
final boolean buffering = Feature.AVRO_BUFFERING.enabledIn(avroFeatures);
// [dataformats-binary#785] Apache decoder does its own buffering, so document
// length constraint has to be applied by counting bytes it pulls from the stream.
// But since a buffering decoder may read ahead of the actual decoding position
// by up to its buffer size, that much slack is needed to avoid false positives
if (_streamReadConstraints.hasMaxDocumentLength()) {
in = new LengthCheckingInputStream(in, _streamReadConstraints,
buffering ? DECODER_FACTORY.getConfiguredBufferSize() : 0);
}
BinaryDecoder decoderToReuse = apacheCodecRecycler.acquireDecoder();
_decoder = buffering
? DECODER_FACTORY.binaryDecoder(in, decoderToReuse)
Expand Down Expand Up @@ -412,4 +421,81 @@ protected JsonToken setString(String str) {
_textValue = str;
return JsonToken.VALUE_STRING;
}

/*
/**********************************************************
/* Helper classes
/**********************************************************
*/

/**
* {@link InputStream} wrapper that applies {@link StreamReadConstraints#validateDocumentLength}
* to the number of bytes read so far, for use with Apache {@link BinaryDecoder} which
* reads from the stream directly.
*<p>
* Note that a buffering {@link BinaryDecoder} pulls content from the stream ahead of
* the actual decoding position, by up to its buffer size: bytes that have been read
* but not (yet) decoded must not count towards document length, or a document well
* within the limit could be rejected. Since the decoder does not expose the number of
* bytes it has actually consumed, the buffer size is allowed as slack; the check is
* hence approximate (as with the non-Apache decoder), but only ever in the direction
* of allowing slightly too much.
*
* @since 2.18.11
*/
private final static class LengthCheckingInputStream extends FilterInputStream
{
private final StreamReadConstraints _constraints;

/**
* Maximum number of bytes decoder may have read but not yet decoded.
*/
private final int _readAheadSlack;

private long _bytesRead;

LengthCheckingInputStream(InputStream in, StreamReadConstraints constraints,
int readAheadSlack) {
super(in);
_constraints = constraints;
_readAheadSlack = readAheadSlack;
}

@Override
public int read() throws IOException {
int b = in.read();
if (b >= 0) {
++_bytesRead;
_validateLength();
}
return b;
}

@Override
public int read(byte[] b, int off, int len) throws IOException {
int count = in.read(b, off, len);
if (count > 0) {
_bytesRead += count;
_validateLength();
}
return count;
}

@Override
public long skip(long n) throws IOException {
long count = in.skip(n);
if (count > 0) {
_bytesRead += count;
_validateLength();
}
return count;
}

private void _validateLength() throws IOException {
long decoded = _bytesRead - _readAheadSlack;
if (decoded > 0L) {
_constraints.validateDocumentLength(decoded);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@ public JsonToken nextToken() throws IOException
_enumIndex = -1;
_binaryValue = null;
JsonToken t = _avroContext.nextToken();
_currToken = t;
return t;
return _nullSafeUpdateToken(t);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,7 @@ public void decodeString() throws IOException {
}

if (len > (_inputEnd - _inputPtr)) {
_validateValueLength(len);
// or if not, could we read?
if (len >= _inputBuffer.length) {
// If not enough space, need handling similar to chunked
Expand All @@ -599,6 +600,7 @@ public void skipString() throws IOException {
}
return;
}
_validateValueLength(len);
_skip(len);
}

Expand Down Expand Up @@ -789,6 +791,7 @@ public JsonToken decodeBytes() throws IOException {
}
_binaryValue = NO_BYTES;
} else {
_validateValueLength(len);
byte[] b = new byte[len];
// this is simple raw read, safe to use:
_read(b, 0, len);
Expand All @@ -806,12 +809,14 @@ public void skipBytes() throws IOException {
}
_binaryValue = NO_BYTES;
} else {
_validateValueLength(len);
_skip(len);
}
}

@Override
public JsonToken decodeFixed(int size) throws IOException {
_validateValueLength(size);
byte[] data = new byte[size];
_read(data, 0, size);
_binaryValue = data;
Expand All @@ -820,9 +825,21 @@ public JsonToken decodeFixed(int size) throws IOException {

@Override
public void skipFixed(int size) throws IOException {
_validateValueLength(size);
_skip(size);
}

/**
* Helper method for checking that length of a value about to be read (or skipped)
* will not push total document length past maximum allowed: needed to avoid
* allocating (or reading) content that would fail the check in any case.
*
* @since 2.18.11
*/
private final void _validateValueLength(long len) throws IOException {
_streamReadConstraints.validateDocumentLength(_currInputProcessed + _inputPtr + len);
}

private final void _read(byte[] target, int offset, int len) throws IOException
{
int ptr = _inputPtr;
Expand All @@ -837,6 +854,9 @@ private final void _read(byte[] target, int offset, int len) throws IOException
_inputPtr = ptr + available;
offset += available;
int left = len - available;
// 18-Sep-2026, tatu: [dataformats-binary#785] Bytes read directly from
// input source bypass `_loadMore()` so need explicit accounting
_markBufferConsumed();
// and rest we can read straight from input
do {
int count = _inputStream.read(target, offset, left);
Expand All @@ -845,9 +865,35 @@ private final void _read(byte[] target, int offset, int len) throws IOException
}
offset += count;
left -= count;
_currInputProcessed += count;
_streamReadConstraints.validateDocumentLength(_currInputProcessed);
} while (left > 0);
}

/**
* Helper method for skipping up to specified number of bytes from the underlying
* input source.
*<p>
* Note that {@link InputStream#skip} is allowed to return 0 without being at
* end-of-input -- and some implementations never skip anything -- so a zero
* return can not be taken to mean end-of-input: reading is used to both make
* progress and to find out whether end-of-input was actually reached.
* Skipped content is read in the (already consumed) input buffer and discarded.
*
* @return Number of bytes skipped; 0 or less to indicate end-of-input
*
* @since 2.18.11
*/
private final long _skipFromInput(long left) throws IOException
{
long skipped = _inputStream.skip(left);
if (skipped > 0L) {
return skipped;
}
int toRead = (int) Math.min(left, _inputBuffer.length);
return _inputStream.read(_inputBuffer, 0, toRead);
}

private final void _skip(int len) throws IOException
{
int ptr = _inputPtr;
Expand All @@ -859,12 +905,17 @@ private final void _skip(int len) throws IOException
}
_inputPtr = _inputEnd; // mark all used, whatever it was
if (_inputStream != null) {
// 18-Sep-2026, tatu: [dataformats-binary#785] Bytes skipped directly from
// input source bypass `_loadMore()` so need explicit accounting
_markBufferConsumed();
do {
int skipped = (int) _inputStream.skip(left);
if (skipped < 0) {
long skipped = _skipFromInput(left);
if (skipped <= 0L) { // real end-of-input
break;
}
left -= skipped;
left -= (int) skipped;
_currInputProcessed += skipped;
_streamReadConstraints.validateDocumentLength(_currInputProcessed);
} while (left > 0);
}
if (left > 0) {
Expand All @@ -883,12 +934,17 @@ private final void _skipL(long len) throws IOException
}
_inputPtr = _inputEnd; // mark all used, whatever it was
if (_inputStream != null) {
// 18-Sep-2026, tatu: [dataformats-binary#785] Bytes skipped directly from
// input source bypass `_loadMore()` so need explicit accounting
_markBufferConsumed();
do {
int skipped = (int) _inputStream.skip(left);
if (skipped < 0) {
long skipped = _skipFromInput(left);
if (skipped <= 0L) { // real end-of-input
break;
}
left -= skipped;
_currInputProcessed += skipped;
_streamReadConstraints.validateDocumentLength(_currInputProcessed);
} while (left > 0L);
}
if (left > 0L) {
Expand Down Expand Up @@ -1054,12 +1110,27 @@ private final void _skipByteGuaranteed() throws IOException
_inputPtr += 1;
}

/**
* Helper method called when the input buffer has been fully consumed and
* content will next be read (or skipped) straight from the underlying
* {@link java.io.InputStream}, bypassing the buffer: needs to flush the
* buffered byte count into {@code _currInputProcessed} so that both location
* information and document length checks remain accurate.
*
* @since 2.18.11
*/
protected final void _markBufferConsumed() {
_currInputProcessed += _inputEnd;
_inputPtr = _inputEnd = 0;
}

protected final boolean _loadMore() throws IOException
{
//_currInputRowStart -= _inputEnd;
if (_inputStream != null) {
int count = _inputStream.read(_inputBuffer, 0, _inputBuffer.length);
_currInputProcessed += _inputEnd;
_streamReadConstraints.validateDocumentLength(_currInputProcessed);
_inputPtr = 0;
if (count > 0) {
_inputEnd = count;
Expand All @@ -1086,6 +1157,7 @@ protected final void _loadToHaveAtLeast(int minAvailable) throws IOException
// Need to move remaining data in front?
int amount = _inputEnd - _inputPtr;
_currInputProcessed += _inputPtr;
_streamReadConstraints.validateDocumentLength(_currInputProcessed);
if (_inputPtr > 0) {
if (amount > 0) {
//_currInputRowStart -= _inputPtr;
Expand Down
Loading
Loading