#1063: bound BigDecimal→BigInteger expansion in objectToBigInteger (completes CVE-2026-59171 fix)#1067
Conversation
…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>
|
|
@mechko Looks good, but a bit more work is needed.
The existing API methods should be updated to call the new methods with a default config instance. For example: Leaving this PR open for a day in case you have time to address this. Otherwise, I can add the changes in a day or so. Let me know if you have any questions. |
|
@stleary To your first point, from what I can see (and the LLM I'm using), nothing in However, we could introduce a similar guard there (to keep it symmetric), but it would change the behaviour without the resource-exhaustion angle we have in Let me know what's your preference (leaving it the way it is, or implementing a similar guard for reasons of symmetry). The other refactoring you mentioned can be implemented easily I assume, I can do that as well. |
|
@mechko, OK to leave |
…/optBigInteger Per review on stleary#1067: - objectToBigInteger(val, dflt, JSONParserConfiguration) uses cfg.getMaxNumberLength() for the digit-count guard; -1 disables it. Existing 2-arg form delegates with a default config. - New public overloads on JSONObject and JSONArray: getBigInteger(key, cfg) / optBigInteger(key, dflt, cfg). Existing methods delegate with a default config. - objectToBigDecimal left unchanged (no expansion path; agreed on PR). - Tests cover default (1000), raised (2000), lowered (5), disabled (-1), null config, and JSONArray overloads. Co-Authored-By: Claude <noreply@anthropic.com>
|
@stleary Done — pushed in 703ac34.
|



Follow-up to #1065; see this comment on #1063 for the residual vector. Targets
max-number-length-configper @stleary's request.Problem
JSONObject.objectToBigIntegercallsBigDecimal.toBigInteger()with no bound on the resulting magnitude. A short exponent-notation literal such as1e100000000(11 chars — well under themaxNumberLengthguard) is stored compactly as aBigDecimalat parse time, then expanded to a ~100 000 000-digitBigIntegerwhen the caller invokesgetBigInteger/optBigInteger, stalling the thread for tens of seconds or throwingOutOfMemoryError.Repro against
max-number-length-config@ 90563eb (pre-patch):Fix
Before calling
.toBigInteger(), reject anyBigDecimalwhose integer part would exceedParserConfiguration.DEFAULT_MAX_NUMBER_LENGTHdecimal digits:Applied at both sink sites in
objectToBigInteger(JSONObject.java:1402and:1432).precision()andscale()are O(1) metadata reads, so the check is free.JSONArray.{get,opt}BigIntegerdelegate to the same helper, so all four public accessors are covered.Uses the
DEFAULT_MAX_NUMBER_LENGTHconstant introduced on this branch so the parse-time and accessor-time ceilings stay in sync. The accessors don't carry aParserConfiguration, 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:optBigInteger(key, dflt)dfltgetBigInteger(key)JSONException("… is not a BigInteger …")Matches how the method already handles other unconvertible values (non-finite doubles, unparseable strings). The
Double/Floatbranch needs no guard — max finitedouble≈ 1.8e308 → 309 digits.Verification
Post-patch, same reproducer:
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}→optBigIntegerreturnsnull,getBigIntegerthrows, both in 0 msput("x", "1e100000000")behaves identicallyJSONArrayaccessor covered{"x":1e999}still returns10^999correctly🤖 Generated with Claude Code