CVE Patch Review

GHSA-R7WM-3CXJ-WFF9: Root-cause fix for async integer length enforcement in jackson-core

GHSA-R7WM-3CXJ-WFF9 · Updated 2026-07-22 Root-cause

Summary

The patch addresses a parser-state bug in jackson-core's non-blocking UTF-8 JSON parser where StreamReadConstraints.maxNumberLength was not enforced during incremental accumulation of integer digits before token completion. By invoking integer-length validation on the NOT_AVAILABLE yield path, the fix closes the slow-drip async bypass that could otherwise grow the text buffer unbounded and lead to memory exhaustion. The accompanying tests materially improve confidence by covering oversized first chunks, multi-chunk accumulation, near-boundary acceptance, and negative-number handling.

Analysis

Vulnerability

GHSA-R7WM-3CXJ-WFF9 describes an incomplete constraint-enforcement path in jackson-core's async parser: StreamReadConstraints.maxNumberLength could be bypassed when an attacker streamed a numeric token as digit-only chunks without a terminator and without signaling end-of-input. In that state, the non-blocking parser repeatedly returned JsonToken.NOT_AVAILABLE while continuing to accumulate digits into its internal text buffer, but did not validate the accumulated integer length on that yield path. The result is a remotely triggerable memory-growth condition and potential denial of service.

The issue is specifically about consistency between synchronous and asynchronous parsing semantics. The fraction path already had coverage and validation expectations, but the integer streaming path in NonBlockingUtf8JsonParserBase deferred enforcement too late. That made the security control dependent on token completion rather than on incremental accumulation, which is exactly what a slow-drip attacker avoids.

// vulnerable behavior summary from patched location context:
// parser yields NOT_AVAILABLE while integer digits keep accumulating
// but maxNumberLength was not checked on this async suspension path

Relevant sources: the advisory at GitHub Security Advisory, the patch discussion in PR #1611, and the corresponding commits 050b429, 4cdd529, and c5941e5.

Patch

The code change is narrowly targeted in src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingUtf8JsonParserBase.java. The patch adds an integer-length validation call before returning control on the async streaming path:

// [core#1556]: validate accumulated integer length so far before yielding
// NOT_AVAILABLE; otherwise a stream of digit-only chunks can grow the buffer
// past `StreamReadConstraints.maxNumberLength` (sibling of #1555).
_setIntLength(outPtr + negMod);

This is the critical fix. Calling _setIntLength(outPtr + negMod) on the suspension path forces the parser to validate the number length based on the digits accumulated so far, rather than waiting for a delimiter or endOfInput(). The use of negMod is important because it preserves correct semantics for negative numbers by excluding the leading minus sign from the validated digit count.

The test suite added in AsyncNumberLengthConsistencyTest substantially strengthens regression coverage. Based on the provided snippets, it verifies at least four important cases from PR #1611 and the linked commits:

  • oversized integer chunks are rejected promptly while the parser is still returning NOT_AVAILABLE;
  • small chunks accumulate across multiple resumptions and fail at the expected boundary;
  • values just under the configured limit still parse successfully across many chunks;
  • negative integer handling follows the same boundary logic without counting the sign.

The release notes entry also explicitly states the intent: #1611: Apply number-length validator on streaming integer path of async parser, as seen in 050b429.

Review

Pros

  • The patch is aligned with the actual root cause: missing enforcement on the async integer accumulation path, not merely on final token materialization.
  • The fix is minimal and localized, reducing the chance of collateral parser behavior changes.
  • _setIntLength(outPtr + negMod) appears semantically correct for both positive and negative integers because it validates digit count rather than raw buffer length.
  • The new tests are security-relevant rather than superficial. They model the exact adversarial condition: repeated digit-only feeds, no terminator, and no endOfInput().
  • The boundary-oriented tests improve confidence that the parser now fails promptly and does not become over-eager for sub-limit values.

Cons

  • The provided snippets show strong coverage for integer streaming, but only mention the fraction path as a companion behavior; the review material does not show the full fraction/exponent regression matrix, so broader numeric-state consistency still depends on existing tests outside the snippet.
  • The patch is intentionally narrow. If there are other async suspension points that accumulate numeric content through different state transitions, they are not visible in the provided diff and therefore cannot be fully assessed here.
  • The workflow change removing Java 11 from CI is unrelated to the vulnerability fix and slightly reduces compatibility signal for downstream users still validating on that runtime, though it does not weaken the security patch itself.

Verdict

Root-cause.

This patch fixes the enforcement gap at the point where the vulnerability actually occurs: the async parser's integer-digit accumulation path before token completion. It does not rely on downstream mitigations, request shaping, or end-of-input behavior. The added tests directly exercise the slow-drip bypass scenario described by the advisory and validate both prompt rejection and correct non-rejection near the boundary. Based on the supplied diff and tests, this is a technically sound and source-grounded root-cause remediation.

Sources