Python limits decimal integer conversions to bound CPU cost
Python 3.10.7 limits expensive conversions between integers and decimal strings. The CVE-2020-10735 mitigation changes parsing and formatting while retaining unlimited power-of-two conversions.
A decimal number can be short enough to transmit cheaply and expensive enough to occupy an interpreter during conversion. Python's arbitrary-precision integers make that distinction important. The size of the mathematical value is supported, but converting between its binary representation and a decimal string has a computational cost that input length alone can disguise.
Python 3.10.7, released on September 6, adds a configurable digit limit to those conversions. Its changelog identifies the change as a mitigation for CVE-2020-10735 and credits Gregory P. Smith and Christian Heimes with the patch. Operations that previously accepted sufficiently large decimal representations can now raise ValueError.
I regard this as a necessary resource policy with an unusually broad compatibility effect. Python's numeric interface makes large integers convenient, and libraries can encounter conversions well outside the code that first accepts input. A limit inside the interpreter reaches those call sites, including ones an application author may not know about.
The representation determines the cost
The documentation shipped with 3.10.7 explains that CPython stores integers in binary form. Conversion to or from a base that is not a power of two cannot use the linear-time algorithms available for the power-of-two cases. Even the best known decimal algorithms have subquadratic complexity.
The security problem follows from that conversion work. Accepting a string and constructing an integer can consume substantial CPU without invoking an obviously expensive application operation. The documentation gives a large decimal conversion as an example, but the relevant property is the algorithm's growth with input size. The mitigation bounds the conversion before an attacker can demand an arbitrarily expensive instance.
This is not a new maximum integer value. Arithmetic can produce a number whose decimal rendering exceeds the limit. The restriction becomes visible when an operation tries to convert that value to or from the affected string representation. Treating it as a general cap on integer arithmetic would misdescribe both the repair and its compatibility consequences.
Power-of-two bases retain their unlimited conversions. Binary, octal and hexadecimal each align with groups of binary digits. The documentation also includes bases four and thirty-two among the unaffected integer parsing cases. Those conversions do not have the same non-linear cost that motivates the decimal restriction.
The limit applies in both directions
The default maximum is 4,300 digits. A conversion that exceeds the applicable limit raises ValueError. Signs and underscores do not count toward the number of digits, so the policy measures the numeric representation rather than the entire string's character count.
import sys
assert sys.int_info.default_max_str_digits == 4300
sys.set_int_max_str_digits(4300)
try:
int("7" * 4301)
except ValueError:
print("Decimal input exceeds the conversion limit")
The example sets the documented default explicitly to make the relevant condition visible. In a normal process the interpreter uses its configured limit. The compiled-in default and the process's current setting are distinct values, and an embedding application or startup option may have selected another limit.
Output conversions matter just as much. str(integer) and repr(integer) perform decimal conversions, as do ordinary decimal formatting operations. An integer that was accepted earlier can become larger through arithmetic and then fail when it reaches a log message or another formatting call.
The version-specific documentation demonstrates that distinction by squaring a value whose initial decimal representation is permitted. The larger result cannot be converted back to decimal under the same limit. Hexadecimal formatting still succeeds. The arithmetic result exists throughout, even when one textual representation is refused.
That broadens the affected application boundary. A service need not expose an explicit decimal-parser endpoint to encounter the new exception. Diagnostic code can render an integer as part of reporting another event. The security change therefore gives both parsing and formatting a new failure condition that consumers need to understand.
Configuration is interpreter-wide
The release supplies startup configuration through PYTHONINTMAXSTRDIGITS and the -X int_max_str_digits command-line option. When both are present, the command-line setting wins. Configuring the interpreter before startup matters because source parsing and imports can themselves perform decimal integer conversions.
At runtime, sys.get_int_max_str_digits() returns the active value, and sys.set_int_max_str_digits() changes it. The documentation calls this an interpreter-wide limit and says subinterpreters have their own setting. A library changing it affects other code in the same interpreter, so selecting a value belongs to application policy.
The smallest positive configurable value is 640 digits, exposed by sys.int_info.str_digits_check_threshold. Zero disables the limit. Those are separate cases, and zero is not a request to allow no digits. An application that disables the protection accepts responsibility for the expensive conversions that the mitigation otherwise bounds.
The configuration APIs arrived in security patch releases, so the documentation recommends feature detection for code that supports multiple Python patch levels. Checking for the setter before using it avoids assuming that a feature series alone identifies API availability. Security maintenance can add a public control because its mitigation needs an application-visible policy.
I prefer an application entry point to own that policy. A reusable library can document its legitimate numeric requirements without silently changing the conversion budget for unrelated consumers. Otherwise importing a dependency could weaken a limit that the service deliberately configured before accepting requests.
Decimal source literals are inputs too
A low limit can affect source code containing large decimal integer constants. The documentation warns that such a file can fail during parsing, often at import or startup, when an up-to-date compiled file does not already exist. A package-installation step that precompiles source can encounter the same condition.
This is a consequence of implementing the limit at the conversion boundary. The parser still has to turn the source's digits into an integer. That operation has the same representation issue even when the digits come from a trusted source file rather than a network request.
A hexadecimal literal avoids this specific conversion limit. The documentation recommends that representation as a workaround for source containing unusually large constants. It preserves an integer constant without asking the parser to perform the affected decimal conversion, though it changes the representation a maintainer reads.
The distinction between cached bytecode and fresh parsing is operationally relevant. A successful import in an environment with a current compiled file does not establish that the source can be parsed under the same limit. Building or installing the same package in a clean environment can exercise a different path.
This does not mean every application should lower the default to the minimum. The release documentation expects the default to be reasonable for most applications. A different value should follow from the application's accepted inputs and legitimate conversion requirements, since a smaller number can reject valid code as well as hostile requests.
A conversion limit and an application input limit answer different questions. The interpreter counts digits for a particular expensive operation. An application may need a smaller bound because its domain accepts only a narrow range of values. Keeping that domain check makes malformed requests meaningful to the application, while the interpreter limit covers conversion paths that do not have their own domain validation. Conversely, accepting a large input string does not promise that every later decimal formatting operation will succeed. Each direction can reach the same interpreter policy independently.
Choosing another representation changes the contract
The unaffected APIs include int.from_bytes, int.to_bytes, hex, oct and bin. These are useful alternatives when a protocol or storage format permits a binary-oriented representation. They do not make a decimal protocol accept hexadecimal automatically. Representation is part of an interface agreement, and changing it requires the consumer to understand the new form.
Conversions from strings to float or decimal.Decimal are also outside this particular limit. That fact does not make either type a transparent replacement for arbitrary-precision integer semantics. A program must still choose the numeric type its operations require, rather than selecting a different one merely to avoid an exception.
The repair leaves a deliberate distinction between mathematical capability and permitted conversion work. Large integers remain available, while expensive textual conversions acquire a configurable bound. An application that genuinely requires more than 4,300 decimal digits can select a different policy and retain the same arithmetic model.
The default also gives library authors a concrete error to account for. A decimal formatting call can fail because of the representation budget, independently of the correctness of the integer being rendered. Reporting code that handles that case can preserve useful diagnostics without first converting the entire oversized value to a decimal string. The resource limit then applies at the place where the expensive work would otherwise occur.