Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: reachable panic due to out-of-memory in modexp precompile #689

Merged
merged 36 commits into from
Mar 30, 2023
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
3c98fa7
chore: trigger `capacity overflow` and `attempt to add with overflow`…
Feb 15, 2023
73a6d4b
fix: tests and add extra edge cases tests
Feb 22, 2023
1a1bed7
Merge branch 'develop' into fix/0x3bfc/audit-aur-07
0x3bfc Mar 2, 2023
083b561
Merge branch 'develop' into fix/0x3bfc/audit-aur-07
0x3bfc Mar 9, 2023
0fdc250
fix: clippy error
Mar 9, 2023
642952a
Merge branch 'develop' into fix/0x3bfc/audit-aur-07
0x3bfc Mar 15, 2023
45c75cf
fix: update checks for out of memory and capacity errors
Mar 17, 2023
c0d7dd7
fix: revert to old gas estimate
Mar 17, 2023
7a1b5a7
fix: minor test refactor
Mar 21, 2023
c7d2420
Merge branch 'develop' into fix/0x3bfc/audit-aur-07
0x3bfc Mar 21, 2023
cf97350
fix: oom but it fails with capacity overflow
Mar 22, 2023
dca1d2c
fix: add capacity error check
Mar 22, 2023
9eb7aa6
Merge branch 'develop' into fix/0x3bfc/audit-aur-07
0x3bfc Mar 23, 2023
0affec6
fix: never returns errors from precompiles
Mar 23, 2023
eae6dd4
chore: clean up and add more tests
Mar 24, 2023
202a90c
chore: more tests
Mar 24, 2023
f9ba325
refactor: zero modulus check
Mar 24, 2023
3a30ff0
fix: remove unncessary check
Mar 24, 2023
34c53d6
Update engine-precompiles/src/modexp.rs
0x3bfc Mar 24, 2023
18cef85
Update engine-precompiles/src/modexp.rs
0x3bfc Mar 24, 2023
92e51d7
Merge branch 'develop' into fix/0x3bfc/audit-aur-07
0x3bfc Mar 27, 2023
2ed42b6
fix: add helper function for test inputs
Mar 27, 2023
e4ffd83
fix: add integration tests
Mar 27, 2023
074e3d8
Update engine-precompiles/src/modexp.rs
0x3bfc Mar 27, 2023
5e64b90
Update engine-precompiles/src/modexp.rs
0x3bfc Mar 27, 2023
08623e1
Update engine-precompiles/src/modexp.rs
0x3bfc Mar 27, 2023
0651421
Update engine-precompiles/src/modexp.rs
0x3bfc Mar 27, 2023
986d974
Update engine-precompiles/src/modexp.rs
0x3bfc Mar 27, 2023
305505a
Update engine-precompiles/src/modexp.rs
0x3bfc Mar 27, 2023
27f0fe2
fix: `StandardPrecompiles.sol` compilation errors
Mar 28, 2023
61af4ca
fix: revert to old implementation of `StandardPrecompiles`
Mar 28, 2023
790d6b6
fix: integration tests
Mar 28, 2023
f78acd3
Merge branch 'develop' into fix/0x3bfc/audit-aur-07
0x3bfc Mar 28, 2023
0f7fd7c
Merge branch 'develop' into fix/0x3bfc/audit-aur-07
0x3bfc Mar 28, 2023
b7116b8
refactor: `generate_modexp_test_input`
Mar 29, 2023
daa1af1
fix: adding struct for test input
Mar 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 173 additions & 55 deletions engine-precompiles/src/modexp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,15 @@ impl<HF: HardFork> ModExp<HF> {
let mod_start = exp_end;

let base = parse_bytes(input, base_start, base_len, BigUint::from_bytes_be);
vimpunk marked this conversation as resolved.
Show resolved Hide resolved
let exponent = parse_bytes(input, exp_start, exp_len, BigUint::from_bytes_be);
let modulus = parse_bytes(input, mod_start, mod_len, BigUint::from_bytes_be);

let output = {
let computed_result = if modulus == BigUint::from(0u32) {
Vec::new()
} else {
// The OOM panic is no longer possible because if the modulus is non-zero
// then the required gas prevents passing a huge exponent.
let exponent = parse_bytes(input, exp_start, exp_len, BigUint::from_bytes_be);
0x3bfc marked this conversation as resolved.
Show resolved Hide resolved
base.modpow(&exponent, &modulus).to_bytes_be()
};
// The result must be the same length as the input modulus.
Expand Down Expand Up @@ -229,6 +231,24 @@ mod tests {
// Byzantium tests: https://github.com/holiman/go-ethereum/blob/master/core/vm/testdata/precompiles/modexp.json
// Berlin tests:https://github.com/holiman/go-ethereum/blob/master/core/vm/testdata/precompiles/modexp_eip2565.json

fn generate_modexp_test_input(
base_len: U256,
exp_len: U256,
mod_len: U256,
base: U256,
exp: U256,
modulus: U256,
) -> Vec<u8> {
let mut input: Vec<u8> = Vec::new();
0x3bfc marked this conversation as resolved.
Show resolved Hide resolved
input.extend(u256_to_arr(&base_len));
input.extend(u256_to_arr(&exp_len));
input.extend(u256_to_arr(&mod_len));
input.extend(u256_to_arr(&base));
input.extend(u256_to_arr(&exp));
input.extend(u256_to_arr(&modulus));
input
}

struct Test {
input: &'static str,
expected: &'static str,
Expand Down Expand Up @@ -459,38 +479,28 @@ mod tests {

#[test]
fn test_berlin_modexp_big_input() {
let base_len = U256::from(4);
let exp_len = U256::from(u64::MAX);
let mod_len = U256::from(4);
let base: u32 = 1;
let exp = U256::MAX;

let mut input: Vec<u8> = Vec::new();
input.extend(u256_to_arr(&base_len));
input.extend(u256_to_arr(&exp_len));
input.extend(u256_to_arr(&mod_len));
input.extend(base.to_be_bytes());
input.extend(u256_to_arr(&exp));

let input = generate_modexp_test_input(
vimpunk marked this conversation as resolved.
Show resolved Hide resolved
U256::from(4),
U256::from(u64::MAX),
U256::from(4),
U256::from(1),
U256::MAX,
U256::MAX,
);
// completes without any overflow
ModExp::<Berlin>::required_gas(&input).unwrap();
}

#[test]
fn test_berlin_modexp_bigger_input() {
let base_len = U256::MAX;
let exp_len = U256::MAX;
let mod_len = U256::MAX;
let base: u32 = 1;
let exp = U256::MAX;

let mut input: Vec<u8> = Vec::new();
input.extend(u256_to_arr(&base_len));
input.extend(u256_to_arr(&exp_len));
input.extend(u256_to_arr(&mod_len));
input.extend(base.to_be_bytes());
input.extend(u256_to_arr(&exp));

let input = generate_modexp_test_input(
U256::MAX,
U256::MAX,
U256::MAX,
U256::from(1),
U256::MAX,
U256::MAX,
);
// completes without any overflow
ModExp::<Berlin>::required_gas(&input).unwrap();
}
Expand Down Expand Up @@ -519,20 +529,14 @@ mod tests {

#[test]
fn test_modexp_not_overflow() {
let base_len = U256::from(0);
let exp_len = U256::from(4294967200usize); // `usize::MAX` - 95
let mod_len = U256::from(0);
let base = U256::MAX;
let exp = U256::MAX;
let modulus = U256::MAX;

let mut input: Vec<u8> = Vec::new();
input.extend(u256_to_arr(&base_len));
input.extend(u256_to_arr(&exp_len));
input.extend(u256_to_arr(&mod_len));
input.extend(u256_to_arr(&base));
input.extend(u256_to_arr(&exp));
input.extend(u256_to_arr(&modulus));
let input = generate_modexp_test_input(
U256::from(0),
U256::from(usize::MAX - 95),
U256::from(0),
U256::MAX,
U256::MAX,
U256::MAX,
);

let res = ModExp::<Byzantium>::new()
.run(&input, Some(EthGas::new(100_000)), &new_context(), false)
Expand All @@ -557,20 +561,14 @@ mod tests {

#[test]
fn test_zero_base_len_zero_mod_len() {
let base_len = U256::from(0);
let exp_len = U256::from(1);
let mod_len = U256::from(0);
let base = U256::from(1);
let exp = U256::from(1);
let modulus = U256::from(1);

let mut input: Vec<u8> = Vec::new();
input.extend(u256_to_arr(&base_len));
input.extend(u256_to_arr(&exp_len));
input.extend(u256_to_arr(&mod_len));
input.extend(u256_to_arr(&base));
input.extend(u256_to_arr(&exp));
input.extend(u256_to_arr(&modulus));
let input = generate_modexp_test_input(
U256::from(0),
U256::from(1),
U256::from(0),
U256::from(1),
U256::from(1),
U256::from(1),
);

let res = ModExp::<Byzantium>::new()
.run(&input, Some(EthGas::new(100_000)), &new_context(), false)
Expand All @@ -592,4 +590,124 @@ mod tests {
let min_gas = EthGas::new(200);
assert_eq!(gas, min_gas);
}

#[test]
fn test_max_exp_zero_base_zero_mod() {
let input = hex::decode("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffff9f0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
let res = ModExp::<Byzantium>::new().run(
&input.unwrap(),
Some(EthGas::new(100_000)),
&new_context(),
false,
);
assert!(res.is_ok());
}

#[test]
fn test_max_exp_max_base_zero_mod() {
let input = hex::decode("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000ffffffffffffff9f0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
let res = ModExp::<Byzantium>::new().run(
&input.unwrap(),
Some(EthGas::new(100_000)),
&new_context(),
false,
);
assert_eq!(Err(ExitError::OutOfGas), res);
}

#[test]
fn test_max_exp_max_base_non_zero_mod() {
let input = hex::decode("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000ffffffffffffff9f0000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000001");
let res = ModExp::<Byzantium>::new().run(
&input.unwrap(),
Some(EthGas::new(100_000)),
&new_context(),
false,
);
assert_eq!(Err(ExitError::OutOfGas), res);
}

#[test]
fn test_max_exp_max_base_max_mod() {
let input = hex::decode("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000ffffffffffffff9fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
let res = ModExp::<Byzantium>::new().run(
&input.unwrap(),
Some(EthGas::new(100_000)),
&new_context(),
false,
);
assert_eq!(Err(ExitError::OutOfGas), res);
}

#[test]
fn test_max_exp_non_zero_base_zero_mod() {
let input = hex::decode("0000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffff9f0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
let res = ModExp::<Byzantium>::new().run(
&input.unwrap(),
Some(EthGas::new(100_000)),
&new_context(),
false,
);
assert_eq!(Err(ExitError::OutOfGas), res);
}

#[test]
fn test_max_exp_zero_base_non_zero_mod() {
vimpunk marked this conversation as resolved.
Show resolved Hide resolved
let input = hex::decode("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffff9f00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000001");
let res = ModExp::<Byzantium>::new().run(
&input.unwrap(),
Some(EthGas::new(100_000)),
&new_context(),
false,
);
assert_eq!(Err(ExitError::OutOfGas), res);
}

#[test]
fn test_modexp_no_oom_with_isize_max() {
birchmd marked this conversation as resolved.
Show resolved Hide resolved
// this test should not panic if exp_len is `isize::MAX`
let input = generate_modexp_test_input(
U256::from(0),
U256::from(isize::MAX),
U256::from(0),
U256::MAX,
U256::MAX,
U256::MAX,
);
let res =
ModExp::<Berlin>::new().run(&input, Some(EthGas::new(100_000)), &new_context(), false);
assert!(res.is_ok());
}

#[test]
fn test_modexp_capacity_overflow() {
// this test should not panic with capacity overflow
let input = generate_modexp_test_input(
U256::from(0),
U256::from(usize::MAX - 96),
U256::from(0),
U256::MAX,
U256::MAX,
U256::MAX,
);
let res =
ModExp::<Berlin>::new().run(&input, Some(EthGas::new(100_000)), &new_context(), false);
assert!(res.is_ok());
}

#[test]
fn test_modexp_add_to_overflow() {
// This test should not panicing with capacity overflow
let input = generate_modexp_test_input(
U256::from(0),
U256::from(usize::MAX),
U256::from(0),
U256::MAX,
U256::MAX,
U256::MAX,
);
let res =
ModExp::<Berlin>::new().run(&input, Some(EthGas::new(100_000)), &new_context(), false);
assert!(res.is_ok());
}
}
1 change: 1 addition & 0 deletions engine-tests/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod eth_connector;
mod ghsa_3p69_m8gg_fwmf;
#[cfg(feature = "meta-call")]
mod meta_parsing;
pub mod modexp;
mod multisender;
mod one_inch;
mod pausable_precompiles;
Expand Down
51 changes: 51 additions & 0 deletions engine-tests/src/tests/modexp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use super::sanity::initialize_transfer;
use crate::prelude::Wei;
use crate::prelude::{Address, U256};
use crate::test_utils::{self, AuroraRunner, Signer};

const MODEXP_ADDRESS: Address = aurora_engine_precompiles::make_address(0, 5);

#[test]
fn test_modexp_oom() {
let (mut runner, mut signer, _) = initialize_transfer();

let inputs = [
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007fffffff0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // exp_len: isize::MAX
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // exp_len: usize::MAX
];

let outputs = [Vec::new(), Vec::new()];

for (input, output) in inputs.iter().zip(outputs.iter()) {
check_wasm_modexp(
&mut runner,
&mut signer,
hex::decode(input).unwrap(),
output,
);
}
}

fn check_wasm_modexp(
runner: &mut AuroraRunner,
signer: &mut Signer,
input: Vec<u8>,
expected_output: &[u8],
) {
let wasm_result = runner
.submit_with_signer(signer, |nonce| {
aurora_engine_transactions::legacy::TransactionLegacy {
nonce,
gas_price: U256::zero(),
gas_limit: u64::MAX.into(),
to: Some(MODEXP_ADDRESS),
value: Wei::zero(),
data: input,
}
})
.unwrap();
assert_eq!(
expected_output,
test_utils::unwrap_success_slice(&wasm_result),
);
}