Skip to content

#1063: bound BigDecimal→BigInteger expansion in objectToBigInteger (completes CVE-2026-59171 fix)#1067

Open
mechko wants to merge 2 commits into
stleary:max-number-length-configfrom
mechko:1063-biginteger-exponent-dos
Open

#1063: bound BigDecimal→BigInteger expansion in objectToBigInteger (completes CVE-2026-59171 fix)#1067
mechko wants to merge 2 commits into
stleary:max-number-length-configfrom
mechko:1063-biginteger-exponent-dos

Conversation

@mechko

@mechko mechko commented Jul 14, 2026

Copy link
Copy Markdown

Follow-up to #1065; see this comment on #1063 for the residual vector. Targets max-number-length-config per @stleary's request.

Problem

JSONObject.objectToBigInteger calls BigDecimal.toBigInteger() with no bound on the resulting magnitude. A short exponent-notation literal such as 1e100000000 (11 chars — well under the maxNumberLength guard) is stored compactly as a BigDecimal at parse time, then expanded to a ~100 000 000-digit BigInteger when the caller invokes getBigInteger / optBigInteger, stalling the thread for tens of seconds or throwing OutOfMemoryError.

Repro against max-number-length-config @ 90563eb (pre-patch):

$ timeout 30 java -Xmx512m -cp target/classes:. Repro ; echo EXIT=$?
parsed: java.math.BigDecimal
EXIT=124          # killed after 30 s

Fix

Before calling .toBigInteger(), reject any BigDecimal whose integer part would exceed ParserConfiguration.DEFAULT_MAX_NUMBER_LENGTH decimal digits:

if ((long) bd.precision() - bd.scale() > ParserConfiguration.DEFAULT_MAX_NUMBER_LENGTH) {
    return defaultValue;
}

Applied at both sink sites in objectToBigInteger (JSONObject.java:1402 and :1432). precision() and scale() are O(1) metadata reads, so the check is free. JSONArray.{get,opt}BigInteger delegate to the same helper, so all four public accessors are covered.

Uses the DEFAULT_MAX_NUMBER_LENGTH constant introduced on this branch so the parse-time and accessor-time ceilings stay in sync. The accessors don't carry a ParserConfiguration, so per-instance configuration is left as follow-up (#1066) to keep this change minimal for the release window.

Behaviour change

For a value whose integer part exceeds DEFAULT_MAX_NUMBER_LENGTH (1000) digits:

Call Before After
optBigInteger(key, dflt) returns (slowly / OOM for large exp) returns dflt
getBigInteger(key) returns (slowly / OOM for large exp) throws JSONException("… is not a BigInteger …")

Matches how the method already handles other unconvertible values (non-finite doubles, unparseable strings). The Double/Float branch needs no guard — max finite double ≈ 1.8e308 → 309 digits.

Verification

Post-patch, same reproducer:

parsed: java.math.BigDecimal
threw: org.json.JSONException: JSONObject["x"] is not a BigInteger (class java.math.BigDecimal : 1E+100000000).
elapsed ms: 0
optBigInteger: null

mvn test: 788 run, 0 fail, 0 error, 6 skipped (pre-existing).

Tests

Added JSONObjectTest.getBigIntegerHugeExponentReturnsDefault (@Test(timeout = 5000) so a regression fails fast rather than hanging CI):

  • {"x":1e100000000}optBigInteger returns null, getBigInteger throws, both in 0 ms
  • String path put("x", "1e100000000") behaves identically
  • JSONArray accessor covered
  • Boundary: {"x":1e999} still returns 10^999 correctly

🤖 Generated with Claude Code

…teger

Completes the CVE-2026-59171 fix started in ab92bb9 / stleary#1065. The
1000-char length guard in stringToValue admits short exponent-notation
literals (e.g. 1e100000000, 11 chars) which are stored compactly as
BigDecimal and only expand when getBigInteger/optBigInteger calls
BigDecimal.toBigInteger(), materialising ~10^8 digits and stalling the
thread or throwing OOM.

Guard both toBigInteger() sites in objectToBigInteger by rejecting any
BigDecimal whose integer part would exceed
ParserConfiguration.DEFAULT_MAX_NUMBER_LENGTH decimal digits
(precision() - scale(), both O(1) reads). Returns defaultValue on
overflow, matching the method's existing behaviour for non-finite and
unparseable values.

Covers JSONObject.getBigInteger/optBigInteger and
JSONArray.getBigInteger/optBigInteger (all delegate to this helper).

Adds JSONObjectTest.getBigIntegerHugeExponentReturnsDefault with a 5s
timeout so a regression fails fast rather than hanging CI.

Co-Authored-By: Claude <noreply@anthropic.com>
…ud java:S108)

Co-Authored-By: Claude <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant