Integer underflow in Frontier
Description
An integer underflow in Frontier's MODEXP precompile can crash debug nodes or cause out-of-gas in release builds; fixed in commit 8a93fdc.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
An integer underflow in Frontier's MODEXP precompile can crash debug nodes or cause out-of-gas in release builds; fixed in commit 8a93fdc.
Vulnerability
An integer underflow bug exists in Frontier's MODEXP precompile implementation prior to commit 8a93fdc6c9f4eb1d2f2a11b7ff1d12d70bf5a664. The bug occurs in gas cost calculation when exponent length is manipulated (e.g., zero exponent with a fudged length), leading to an underflow in the subtraction (8 * (exp_length - 32)) + exponent.bitand(max_256_bit_uint).bits() - 1 [2]. Affected versions are all Frontier builds before this commit; users not using the MODEXP precompile are not impacted [1].
Exploitation
An attacker must be able to craft and submit a transaction that invokes the MODEXP precompile with specially crafted input parameters, specifically a zero exponent combined with a manipulated ("fudged") exponent length that triggers the integer underflow. No special network position or authentication beyond normal user-level access is required, provided the runtime includes the MODEXP precompile [1][3].
Impact
In debug builds, the integer underflow causes a node crash (panic). In release builds (and production WebAssembly binaries), the underflow results in an incorrect gas calculation that leads to an out-of-gas error, aborting the EVM execution without broader node instability. The attacker does not gain arbitrary code execution or data access; the impact is denial of service (crash or transaction failure) [1].
Mitigation
The fix is available in commit 8a93fdc6c9f4eb1d2f2a11b7ff1d12d70bf5a664 (Pull Request #549), which corrects the arithmetic to prevent underflow by subtracting after adding the terms [2][3]. All Frontier users should update to a version containing this commit. There is no known workaround other than disabling the MODEXP precompile if patching is not immediately possible [1].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
pallet-evm-precompile-modexpcrates.io | <= 1.0.0 | — |
Affected products
2- Range: < 8a93fdc
Patches
18a93fdc6c9f4Handle 0 exponent with fudged length correctly in ModExp (#549)
1 file changed · +51 −5
frame/evm/precompile/modexp/src/lib.rs+51 −5 modified@@ -47,7 +47,10 @@ fn calculate_gas_cost( words += 1; } - // TODO: prevent/handle overflow + // Note: can't overflow because we take words to be some u64 value / 8, which is + // necessarily less than sqrt(u64::MAX). + // Additionally, both base_length and mod_length are bounded to 1024, so this has + // an upper bound of roughly (1024 / 8) squared words * words } @@ -63,8 +66,17 @@ fn calculate_gas_cost( let bytes: [u8; 32] = [0xFF; 32]; let max_256_bit_uint = BigUint::from_bytes_be(&bytes); + // from the EIP spec: + // (8 * (exp_length - 32)) + ((exponent & (2**256 - 1)).bit_length() - 1) + // + // Notes: + // * exp_length is bounded to 1024 and is > 32 + // * exponent can be zero, so we subtract 1 after adding the other terms (whose sum + // must be > 0) + // * the addition can't overflow because the terms are both capped at roughly + // 8 * max size of exp_length (1024) iteration_count = - (8 * (exp_length - 32)) + ((exponent.bitand(max_256_bit_uint)).bits() - 1); + (8 * (exp_length - 32)) + exponent.bitand(max_256_bit_uint).bits() - 1; } max(iteration_count, 1) @@ -89,7 +101,7 @@ fn calculate_gas_cost( // 6) modulus, size as described above // // -// NOTE: input sizes are arbitrarily large (up to 256 bits), with the expectation +// NOTE: input sizes are bound to 1024 bytes, with the expectation // that gas limits would be applied before actual computation. // // maximum stack size will also prevent abuse. @@ -133,7 +145,7 @@ impl Precompile for Modexp { let mod_len_big = BigUint::from_bytes_be(&buf); if mod_len_big > max_size_big { return Err(PrecompileFailure::Error { - exit_status: ExitError::Other("unreasonably large exponent length".into()), + exit_status: ExitError::Other("unreasonably large modulus length".into()), }); } @@ -162,7 +174,6 @@ impl Precompile for Modexp { let exponent = BigUint::from_bytes_be(&input[exp_start..exp_start + exp_len]); // do our gas accounting - // TODO: we could technically avoid reading base first... let gas_cost = calculate_gas_cost(base_len as u64, exp_len as u64, mod_len as u64, &exponent); if let Some(gas_left) = target_gas { @@ -423,4 +434,39 @@ mod tests { } } } + + #[test] + fn test_zero_exp_with_33_length() { + // This is a regression test which ensures that the 'iteration_count' calculation + // in 'calculate_iteration_count' cannot underflow. + // + // In debug mode, this underflow could cause a panic. Otherwise, it causes N**0 to + // be calculated at more-than-normal expense. + // + // TODO: cite security advisory + + let input = vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + ]; + + let cost: u64 = 100000; + + let context: Context = Context { + address: Default::default(), + caller: Default::default(), + apparent_value: From::from(0), + }; + + let precompile_result = Modexp::execute(&input, Some(cost), &context, false) + .expect("Modexp::execute() returned error"); + + assert_eq!(precompile_result.output.len(), 1); // should be same length as mod + let result = BigUint::from_bytes_be(&precompile_result.output[..]); + let expected = BigUint::parse_bytes(b"0", 10).unwrap(); + assert_eq!(result, expected); + } }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-cjg2-2fjg-fph4ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-21685ghsaADVISORY
- github.com/paritytech/frontier/commit/8a93fdc6c9f4eb1d2f2a11b7ff1d12d70bf5a664ghsax_refsource_MISCWEB
- github.com/paritytech/frontier/pull/549ghsax_refsource_MISCWEB
- github.com/paritytech/frontier/security/advisories/GHSA-cjg2-2fjg-fph4ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.