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
3 changes: 3 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ Active maintainers:
#819: (avro) `bytes` value allocated to declared length before checking available content
[CVE-2026-104015]
(fix by @pjfanning, w/ Claude code)
#825: (smile) Non-blocking Smile parser should validate raw binary length and not pre-allocate
full buffer [CVE-2026-104895]
(fix by @pjfanning, w/ Claude code)

2.18.11 (20-Sep-2026)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.Arrays;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.exc.StreamConstraintsException;
import com.fasterxml.jackson.core.io.IOContext;
import com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer;
import com.fasterxml.jackson.core.util.ByteArrayBuilder;
Expand Down Expand Up @@ -135,6 +136,14 @@ private Feature(boolean defaultState) {
*/
protected boolean _tokenIncomplete = false;

/**
* If declared length of current (number) token failed validation, the
* failure (to rethrow on any further access), and declared length
* (for skipping content to get to the next token).
*/
private StreamConstraintsException _numberLengthFailure;
private int _failedNumberByteLength;

/*
/**********************************************************
/* Life-cycle
Expand Down Expand Up @@ -2093,6 +2102,12 @@ protected final void _finishToken() throws IOException

protected final void _finishNumberToken(int tb) throws IOException
{
// If declared length failed validation earlier, fail again: content
// not read, token remains incomplete (to be skipped by nextToken())
if (_numberLengthFailure != null) {
_tokenIncomplete = true;
throw _numberLengthFailureAgain();
}
switch (tb & 0x1F) {
case 4:
_finishInt(); // vint
Expand Down Expand Up @@ -2288,12 +2303,18 @@ private final int _fourBytesToIntSlow() throws IOException

private final void _finishBigInteger() throws IOException
{
final byte[] raw = _read7BitBinaryWithLength();
// Validate declared length before reading (and buffering) content
final int byteLen = _readUnsignedVInt();
try {
_streamReadConstraints.validateIntegerLength(byteLen);
} catch (StreamConstraintsException e) {
throw _recordNumberLengthFailure(byteLen, e);
}
final byte[] raw = _read7BitBinary(byteLen);
// [dataformats-binary#257]: 0-length special case to handle
if (raw.length == 0) {
_numberBigInt = BigInteger.ZERO;
} else {
_streamReadConstraints.validateIntegerLength(raw.length);
_numberBigInt = new BigInteger(raw);
}
_numTypesValid = NR_BIGINT;
Expand Down Expand Up @@ -2336,12 +2357,18 @@ private final void _finishDouble() throws IOException
private final void _finishBigDecimal() throws IOException
{
final int scale = SmileUtil.zigzagDecode(_readUnsignedVInt());
final byte[] raw = _read7BitBinaryWithLength();
// Validate declared length before reading (and buffering) content
final int byteLen = _readUnsignedVInt();
try {
_streamReadConstraints.validateFPLength(byteLen);
} catch (StreamConstraintsException e) {
throw _recordNumberLengthFailure(byteLen, e);
}
final byte[] raw = _read7BitBinary(byteLen);
// [dataformats-binary#257]: 0-length special case to handle
if (raw.length == 0) {
_numberBigDecimal = BigDecimal.ZERO;
} else {
_streamReadConstraints.validateFPLength(raw.length);
BigInteger unscaledValue = new BigInteger(raw);
_numberBigDecimal = new BigDecimal(unscaledValue, scale);
}
Expand Down Expand Up @@ -2435,11 +2462,11 @@ protected final void _reportInvalidUnsignedVInt(int firstCh, int lastCh) throws
{
if (lastCh >= 0) {
_reportError(
"Overflow in VInt (current token %s): 5th byte (0x%2X) of 5-byte sequence must have its highest bit set to indicate end",
"Overflow in VInt (current token %s): 5th byte (0x%02X) of 5-byte sequence must have its highest bit set to indicate end",
currentToken(), lastCh);
}
_reportError(
"Overflow in VInt (current token %s): 1st byte (0x%2X) of 5-byte sequence must have its top 4 bits zeroes",
"Overflow in VInt (current token %s): 1st byte (0x%02X) of 5-byte sequence must have its top 4 bits zeroes",
currentToken(), firstCh);
}

Expand Down Expand Up @@ -2709,8 +2736,11 @@ protected byte[] _finishBinaryRawLong(final int expLen) throws IOException
// followed by encoded data
private final byte[] _read7BitBinaryWithLength() throws IOException
{
final int byteLen = _readUnsignedVInt();
return _read7BitBinary(_readUnsignedVInt());
}

private final byte[] _read7BitBinary(final int byteLen) throws IOException
{
// 20-Mar-2021, tatu [dataformats-binary#260]: avoid eager allocation
// for very large content
if (byteLen > LONGEST_NON_CHUNKED_BINARY) {
Expand Down Expand Up @@ -2850,6 +2880,12 @@ protected byte[] _finishBinary7BitLong(final int expLen) throws IOException
protected void _skipIncomplete() throws IOException
{
_tokenIncomplete = false;
// Token that failed validation: skip its content (length already read)
if (_numberLengthFailure != null) {
_numberLengthFailure = null;
_skip7BitBinary(_failedNumberByteLength);
return;
}
int tb = _typeAsInt;
switch (tb >> 5) {
case 1: // simple literals, numbers
Expand Down Expand Up @@ -2962,25 +2998,42 @@ protected void _skipBytes(int len) throws IOException
*/
protected void _skip7BitBinary() throws IOException
{
int origBytes = _readUnsignedVInt();
// Ok; 8 encoded bytes for 7 payload bytes first
int chunks = origBytes / 7;
int encBytes = chunks * 8;
_skip7BitBinary(_readUnsignedVInt());
}

private void _skip7BitBinary(int origBytes) throws IOException
{
final long encBytes = _encoded7BitLength(origBytes);
// sanity check: not all length markers valid; due to signed int(32)
// calculations maximum length only 7/8 of 2^31
if (encBytes < 0) {
if (encBytes > Integer.MAX_VALUE) {
throw _constructReadException(
"Invalid content: invalid 7-bit binary encoded byte length (0x%X) exceeds maximum valid value",
origBytes);
}

// and for last 0 - 6 bytes, last+1 (except none if no leftovers)
origBytes -= 7 * chunks;
if (origBytes > 0) {
encBytes += 1 + origBytes;
}
_skipBytes(encBytes);
_skipBytes((int) encBytes);
}

// Called when declared length of current (number) token fails validation:
// content is not read but token is left incomplete so that content is skipped
// when moving to the next token (if caller continues). Returns the failure
// for caller to throw
private StreamConstraintsException _recordNumberLengthFailure(int byteLen,
StreamConstraintsException fail)
{
_tokenIncomplete = true;
_numberLengthFailure = fail;
_failedNumberByteLength = byteLen;
return fail;
}

// New exception (same message and location) for repeated access to a value
// whose length failed validation: not the same instance, so that its stack
// trace is for this access, and changes to the earlier one are not carried
private StreamConstraintsException _numberLengthFailureAgain()
{
return new StreamConstraintsException(_numberLengthFailure.getOriginalMessage(),
_numberLengthFailure.getLocation());
}

/*
Expand Down Expand Up @@ -3124,7 +3177,7 @@ protected void _reportIncompleteBinaryRead7Bit(int expLen, int actLen)
throws IOException
{
// Calculate number of bytes needed (1 encoded byte expresses 7 payload bits):
final long encodedLen = (7L + 8L * expLen) / 7L;
final long encodedLen = _encoded7BitLength(expLen);
_reportInvalidEOF(String.format(
" for Binary value (7-bit): expected %d payload bytes (from %d encoded), only decoded %d",
expLen, encodedLen, actLen), currentToken());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,19 @@ protected final void _releaseBuffers() throws IOException {

protected abstract void _releaseBuffers2();

/**
* Helper method for calculating length of 7-bit encoded content, given
* length of raw (decoded) content: 8 encoded bytes for each full 7 bytes,
* and for last 1 - 6 bytes one more than the number of bytes.
* Calculated as {@code long} since may exceed {@code Integer.MAX_VALUE}.
*
* @since 2.18.12
*/
protected static long _encoded7BitLength(int rawLength) {
final int leftover = rawLength % 7;
return (rawLength / 7) * 8L + ((leftover == 0) ? 0 : leftover + 1);
}

@Override public final boolean isClosed() { return _closed; }
@Override public final JsonReadContext getParsingContext() { return _streamReadContext; }

Expand Down
Loading
Loading