CVE-2026-32686: Decimal128 Defaults Turn Unbounded Exponent Parsing into a Root-Cause Fix
Summary
The patch changes ericmj/decimal from permissive, effectively unbounded parsing and context defaults to bounded IEEE 754 decimal128 defaults. It adds default limits for parse/cast and output rendering, and changes the default Decimal.Context exponent range to finite values. This directly addresses the resource-exhaustion primitive described in CVE-2026-32686 by rejecting oversized exponents and digit counts before expensive arithmetic alignment or string expansion occurs.
Analysis
Vulnerability
CVE-2026-32686 and the corresponding GHSA advisory describe a denial-of-service condition in ericmj/decimal where unbounded exponent parsing permits attacker-controlled scientific notation values to drive excessive memory allocation. The vulnerable behavior is visible in the pre-patch documentation and code paths: parsing accepted arbitrary exponent magnitude for backwards compatibility, and the default Decimal.Context used emax: :infinity and emin: :infinity, so already-created decimals could carry pathological exponents into later arithmetic.
The commit 6a523f3a73b8c9974540e21c7aa88f1258bb35ae also documents the operational impact: some operations and output formats may allocate memory or CPU proportional to the expanded size of the number. That is the core failure mode for this CVE: a small textual input can induce huge internal bignum alignment or expanded rendering work, leading to BEAM VM OOM or severe resource exhaustion.
defstruct precision: 28,
emax: :infinity,
emin: :infinity,
# parse/1 accepted input without default max_digits/max_exponent limitsBecause the issue originates at object creation and representation boundaries, relying only on arithmetic-time overflow/underflow handling is insufficient. Inputs must be constrained before they become oversized decimals, and rendering paths that can expand exponents must also be bounded.
Patch
The patch in the upstream commit makes three material changes.
First, it introduces bounded defaults for parsing and casting. cast/1 now delegates to a bounded helper using default parse limits, and parse/1 now uses default limits rather than accepting arbitrary exponent and digit counts. The new defaults are IEEE 754 decimal128-aligned: max_digits = 34 and max_exponent = 6144. Explicit arities still allow callers to override or disable limits with :infinity.
Second, it bounds output generation by default. to_string/2 now checks a default maximum digit budget of 6178 before rendering, which the patch explains as precision + emax, sufficient for any in-range decimal128 value even in expansion-prone formats such as :normal and :xsd. This closes a related amplification path where a previously accepted decimal could trigger large output allocation.
Third, it changes the default runtime context from unbounded exponents to decimal128-style finite bounds. In Decimal.Context, the default struct becomes precision: 34, emax: 6_144, and emin: -6_143. Tests that depended on the old unbounded behavior were updated to opt back into :infinity explicitly, which is a strong signal that the new bounded behavior is intentional and systemic rather than cosmetic.
@default_max_digits 34
@default_max_exponent 6_144
@default_to_string_max_digits 6_178
def cast(term), do: cast_with_limits(term, default_parse_limits())
def parse(binary) when is_binary(binary) do
parse_with_limits(binary, default_parse_limits())
end
defstruct precision: 34,
emax: 6_144,
emin: -6_143,These changes align with the advisory guidance in GHSA-RHV4-8758-JX7V and the CVE record at CVE.org: the fix is to prevent pathological exponent values from being accepted and propagated under default usage.
Review
Pros
- The patch addresses the vulnerability at the earliest trust boundary by changing default parse and cast behavior, which is where attacker-controlled strings first become
Decimalvalues. - It also covers a second expansion surface,
to_string, preventing large allocations during rendering of hostile or previously persisted values. - Changing
Decimal.Contextdefaults to finite decimal128 bounds reduces the chance that arithmetic on accepted values can still drift into unbounded exponent behavior. - The implementation is coherent: documentation, defaults, helper functions, and tests were all updated together in the same commit 6a523f3a73b8c9974540e21c7aa88f1258bb35ae.
- Backward-compatibility escape hatches remain available through explicit option overrides and explicit
:infinitysettings, which is useful for trusted internal workloads.
Cons
- This is a behavior-changing patch. Applications that relied on permissive parsing of very large exponents or digit counts will now receive
:erroror rendering exceptions unless they opt out explicitly. - The patch cannot retroactively sanitize already-created or persisted
Decimalvalues if an application explicitly disables limits or loads hostile values from another source. - Security now depends partly on callers not reintroducing the old risk by passing
:infinityfor untrusted input paths. - The review material here shows updated tests around context defaults, but not exhaustive new tests for all parse and rendering rejection edge cases; those may exist outside the provided snippet, but they are not visible in the supplied source digest.
Verdict
Root-cause.
The patch fixes the underlying design flaw by replacing unsafe unbounded defaults with bounded decimal128 defaults across the creation, operation, and rendering lifecycle. The vulnerable condition was not merely an arithmetic bug in one code path; it was the library-wide acceptance and propagation of arbitrarily large exponents. By default-limiting parse, cast, to_string, and Decimal.Context, the patch removes the primary resource-exhaustion primitive described in NVD and GHSA. The remaining risk is mainly opt-out misuse, not an unfixed default-path vulnerability.
Recommended Labs
Try this vulnerability pattern yourself with hands-on labs.
- OPain.go
Best match for this CVE’s defensive theme: validating and constraining untrusted input before it reaches dangerous execution paths. CVE-2026-32686 is an input-driven resource exhaustion issue caused by accepting extreme numeric exponents, and this lab is tagged with CWE-20 and OWASP A03:2021, making it relevant for practicing robust server-side validation and policy enforcement patterns.
- Pollution.py
Useful as a defense-in-depth lab for unsafe parsing and trust-boundary mistakes. While not a direct denial-of-service exercise, it emphasizes strict handling of attacker-controlled input and insecure data shaping, which maps well to patch-review lessons for bounding exponents, rejecting malformed scientific notation, and preventing expensive downstream processing.
- Redirect.js
A lighter hands-on option focused on input validation failures. It is less directly aligned than the first two, but still valuable for reinforcing core secure-coding habits relevant to this CVE: validating format, constraining input ranges, and treating externally supplied values as hostile by default.