CVE-2022-21685 (Rust, published 2022-01-14T17:15Z)
Description: value: Frontier is Substrate's Ethereum compatibility layer. Prior to commit number `8a93fdc6c9f4eb1d2f2a11b7ff1d12d70bf5a664`, a bug in Frontier's MODEXP precompile implementation can cause an integer underflow in certain conditions. This will cause a node crash for debug builds. For release builds (and production WebAssembly binaries), the impact is limited as it can only cause a normal EVM out-of-gas. Users who do not use MODEXP precompile in their runtime are not impacted. A patch is available in pull request #549.
Severity: MEDIUM (CVSS v3 base score: 6.5)
Weakness: CWE-191 \u2014 Integer Underflow (Wrap or Wraparound)
The product subtracts one value from another, such that the result is less than the minimum allowable integer value, which produces a value that is not equal to the correct result.
Repository: https://github.com/paritytech/frontier
Files changed:
frame/evm/precompile/modexp/src/lib.rs
Commit message: Handle 0 exponent with fudged length correctly in ModExp (#549)

* Handle 0 exponent with fudged length correctly in ModExp

* cargo fmt

* Revert to following EIP-2565 strictly, subtract after adding terms to prevent underflow

* Update frame/evm/precompile/modexp/src/lib.rs

Clean up test case

Co-authored-by: Wei Tang <accounts@that.world>

* More revert

* cargo fmt

* Prefer expect to match

Co-authored-by: Wei Tang <accounts@that.world>

Vulnerable code:
		// TODO: prevent/handle overflow
				(8 * (exp_length - 32)) + ((exponent.bitand(max_256_bit_uint)).bits() - 1);
// NOTE: input sizes are arbitrarily large (up to 256 bits), with the expectation
				exit_status: ExitError::Other("unreasonably large exponent length".into()),
			// TODO: we could technically avoid reading base first...

Fixed code:
		// 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
			// 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)
				(8 * (exp_length - 32)) + exponent.bitand(max_256_bit_uint).bits() - 1;
// NOTE: input sizes are bound to 1024 bytes, with the expectation
				exit_status: ExitError::Other("unreasonably large modulus length".into()),

	#[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);
	}