Skip to content

Validate raw binary length in non-blocking Smile parser, accumulate long content [CVE-2026-104895] (#825) - #826

Merged
cowtowncoder merged 15 commits into
FasterXML:2.18from
pjfanning:pj/2.18/smile-async-raw-binary
Oct 2, 2026
Merged

cowtowncoder merged 15 commits into
FasterXML:2.18from
pjfanning:pj/2.18/smile-async-raw-binary

Conversation

@pjfanning

@pjfanning pjfanning commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Fixes #825.

Started as a fix for raw binary length handling in the non-blocking Smile parser. After review, it also covers related length validation for other length-prefixed values, and StreamReadConstraints handling in both Smile parsers.

Non-blocking parser (NonBlockingByteArrayParser)

Raw binary (#825)

  • The declared length is never used for up-front allocation. If all content is already available, an exact-size array is allocated and filled with a single copy. If no content is available yet, the parser waits for the next chunk and checks again. Otherwise content is accumulated in a ByteArrayBuilder as it is fed.
  • No more size threshold, and no _binaryValue == null mode flag.

Length validation

  • All unsigned length/scale VInts (raw and 7-bit binary length, BigInteger length, BigDecimal scale and length) are decoded by a single resumable helper. It rejects values that do not fit in 31 bits, as SmileParser._readUnsignedVInt() does. The BigDecimal scale keeps the same 31-bit limit as the blocking parser.
  • Errors use the same "Overflow in VInt (current token ...)" message as the blocking parser, regardless of how input is chunked, naming the type of value being decoded.

Big numbers

Feeding input

  • feedInput() counts the chunk being fed against maxDocumentLength, and validates before changing any state, so a rejected feed leaves the parser unchanged.
  • feedInput() after close() is rejected, and needMoreInput() returns false after close().

Both parsers: maxNumberLength for BigInteger/BigDecimal

  • The declared byte length is validated as soon as it is decoded, before content is read or buffered. Before, a declared length of up to ~2GB was buffered first.
  • If the caller catches the StreamConstraintsException and continues, the content of the failed value is skipped and parsing resumes with the next token. The blocking parser does not read the content unless the caller continues. Repeated access to the failed value throws the failure again (as a new exception).
  • A failed value counts toward maxTokenCount in both parsers. If the token count fails for it, the length failure is attached as suppressed.
  • Declared lengths too large to skip (encoded length > Integer.MAX_VALUE) are reported the same way by both parsers.

Other

  • New SmileParserBase._encoded7BitLength(int) helper, used by both parsers. It also fixes the encoded length reported in the "Unexpected end-of-input for Binary value (7-bit)" message.
  • Blocking _reportInvalidUnsignedVInt() formats bytes as 0x%02X, matching the non-blocking parser.
  • NonBlockingParserBase: new minor state MINOR_VALUE_SKIP_7BIT_BODY.

Tests

  • AsyncRawBinaryLengthTest:
    • huge declared lengths with little content, and length and content fed separately;
    • invalid length/scale VInts for all length fields (fed whole and byte-by-byte, checking message and token type), including the extreme valid BigDecimal scales;
    • feeding after close.
  • AsyncZeroLengthBigNumberTest: zero-length BigInteger/BigDecimal.
  • SimpleBinaryParseTest: long raw values (> 250,000 bytes) fed in small chunks, reusing one parser.
  • constraints/LongBigNumberSmileReadTest:
    • early maxNumberLength failure;
    • continuing after a failure in arrays and objects, with the same results for blocking and non-blocking (fed whole, 1 and 3 bytes at a time);
    • token counting of failed values;
    • repeated access;
    • fail-fast on endless content;
    • huge-length skip parity.
  • constraints/LongDocumentSmileReadTest: non-blocking maxDocumentLength with a single feed, and with a rejected feed.
  • smile tests pass (274).

Related issues found during review (not fixed here)

#830, #831, #832, #833, #834, #835

🤖 Generated with Claude Code

… long content (FasterXML#825)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@cowtowncoder cowtowncoder added this to the 2.18.12 milestone Oct 2, 2026
@cowtowncoder cowtowncoder changed the title (smile) Validate raw binary length in non-blocking parser, accumulate long content (#825) Validate raw binary length in non-blocking Smile parser, accumulate long content [CVE-2026-104895] (#825) Oct 2, 2026
@cowtowncoder
cowtowncoder merged commit 0a2cbbb into FasterXML:2.18 Oct 2, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants