Skip to content

Commit

Permalink
Handle 0 exponent with fudged length correctly in ModExp (polkadot-ev…
Browse files Browse the repository at this point in the history
  • Loading branch information
tgmichel committed Jan 13, 2022
1 parent ae8c6e2 commit c76d66b
Showing 1 changed file with 50 additions and 4 deletions.
54 changes: 50 additions & 4 deletions frame/evm/precompile/modexp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,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
}

Expand All @@ -60,8 +63,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)
Expand All @@ -86,7 +98,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.
Expand Down Expand Up @@ -154,7 +166,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 {
Expand Down Expand Up @@ -397,4 +408,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)
.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);
}
}

0 comments on commit c76d66b

Please sign in to comment.