mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-10-20 18:56:17 +00:00
Fix/ny fn native fmp oog (#567)
Bug found by @jubnzv: `--newyork`: a full-width value stored to the free-memory-pointer slot (e.g. a function parameter `>= 2^64`) was narrowed to 64 bits, inserting a checked truncation that consumed all gas. --------- Signed-off-by: xermicus <bigcyrill@hotmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
e2e6514953
commit
61b7ab8521
4 files changed
+64
-11
No files matched your search
@@ -0,0 +1,31 @@
|
||||
/// Soundness PoC (newyork type inference): a value stored to the free-memory-pointer
|
||||
/// slot `0x40` must not be narrowed to a 64-bit pointer width when it is also
|
||||
/// observable at full width elsewhere.
|
||||
///
|
||||
/// `f0`'s parameter `p0` is stored to the FMP slot (`mstore(64, p0)`) — which
|
||||
/// inference treated as a bounded pointer and narrowed to i64 — but `p0` is also
|
||||
/// stored full-width at offset 8 and read back by `mload(24)`. The i64 narrowing
|
||||
/// of the parameter inserts a call-boundary guard that traps (consumes all gas)
|
||||
/// when a caller passes an argument `>= 2^64`. The first call passes `b`
|
||||
/// (`calldataload(32)`, a full 256-bit word), so newyork ran out of gas where EVM
|
||||
/// (and the stock pipeline) execute successfully. The fix records the FMP-stored
|
||||
/// value as an ordinary full-width memory value.
|
||||
object "FnNativeFmpBug" {
|
||||
code { datacopy(0, dataoffset("FnNativeFmpBug_deployed"), datasize("FnNativeFmpBug_deployed")) return(0, datasize("FnNativeFmpBug_deployed")) }
|
||||
object "FnNativeFmpBug_deployed" {
|
||||
code {
|
||||
let a := calldataload(0) let b := calldataload(32) let d := calldataload(96) let r := 0
|
||||
function f0(p0, p1) -> ret {
|
||||
mstore(8, p0)
|
||||
mstore(64, p0)
|
||||
mstore8(8, p1)
|
||||
ret := mload(24)
|
||||
}
|
||||
r := add(mul(r, 0x100000001b3), f0(b, a))
|
||||
r := add(mul(r, 0x100000001b3), f0(2719014241053524200, d))
|
||||
r := add(mul(r, 0x100000001b3), f0(391540675041506305, b))
|
||||
mstore(0, r)
|
||||
return(0, 32)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4769,6 +4769,34 @@ fn array_bounds_probe() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Regression (newyork type inference): a value stored to the free-memory-pointer
|
||||
/// slot `0x40` was narrowed to i64 (treated as a bounded pointer) even when the
|
||||
/// same value is observable at full width elsewhere. `f0`'s parameter `p0` is
|
||||
/// stored to the FMP slot *and* full-width at offset 8 (read by `mload(24)`); the
|
||||
/// i64 narrowing of the parameter inserts a call-boundary guard that traps
|
||||
/// (consumes all gas) for an argument `>= 2^64`. The first call passes `b`
|
||||
/// (`calldataload(32)`, a full 256-bit word), so newyork ran out of gas where EVM
|
||||
/// executes successfully. The fix records the FMP-stored value as an ordinary
|
||||
/// full-width memory value. Compared newyork-PVM vs solc-EVM.
|
||||
#[test]
|
||||
fn fn_native_fmp_store_no_spurious_trap() {
|
||||
let mut actions = instantiate_yul("contracts/FnNativeFmpBug.yul", "FnNativeFmpBug");
|
||||
// Four lane-distinct words; word1 (`b`) is >= 2^64 to exercise the guard.
|
||||
let mut data = Vec::new();
|
||||
for byte in [0x11u8, 0x22, 0x33, 0x44] {
|
||||
data.extend_from_slice(&[byte; 32]);
|
||||
}
|
||||
actions.push(Call {
|
||||
origin: TestAddress::Alice,
|
||||
dest: TestAddress::Instantiated(0),
|
||||
value: 0,
|
||||
gas_limit: Some(GAS_LIMIT),
|
||||
storage_deposit_limit: None,
|
||||
data,
|
||||
});
|
||||
run_differential(actions);
|
||||
}
|
||||
|
||||
/// Regression (newyork dead-store elimination vs `msize`): a store whose memory
|
||||
/// expansion an intervening `msize()` observes must not be eliminated as dead.
|
||||
/// `mstore(288, 1); let r := msize(); mstore(288, 2)` — the dead-store pass
|
||||
|
||||
@@ -1051,6 +1051,7 @@ impl TypeInference {
|
||||
}
|
||||
Statement::For {
|
||||
condition_statements,
|
||||
condition,
|
||||
body,
|
||||
post,
|
||||
..
|
||||
@@ -1058,6 +1059,7 @@ impl TypeInference {
|
||||
let saved = self.in_unconditional_context;
|
||||
self.in_unconditional_context = false;
|
||||
self.collect_uses_statements(condition_statements);
|
||||
self.collect_uses_expression(condition);
|
||||
self.collect_uses_statements(&body.statements);
|
||||
self.collect_uses_statements(&post.statements);
|
||||
self.in_unconditional_context = saved;
|
||||
@@ -1108,19 +1110,10 @@ impl TypeInference {
|
||||
/// walks nested regions so the conditional context is tracked).
|
||||
fn collect_uses_statement(&mut self, statement: &Statement) {
|
||||
match statement {
|
||||
Statement::MStore {
|
||||
offset,
|
||||
value,
|
||||
region,
|
||||
} => {
|
||||
Statement::MStore { offset, value, .. } => {
|
||||
self.record_use(offset.id, UseContext::MemoryOffset);
|
||||
self.narrow_from_use(offset.id, BitWidth::I64);
|
||||
if *region == MemoryRegion::FreePointerSlot {
|
||||
self.record_use(value.id, UseContext::MemoryOffset);
|
||||
self.narrow_from_use(value.id, BitWidth::I64);
|
||||
} else {
|
||||
self.record_use(value.id, UseContext::MemoryValue);
|
||||
}
|
||||
self.record_use(value.id, UseContext::MemoryValue);
|
||||
}
|
||||
Statement::MStore8 { offset, value, .. } => {
|
||||
self.record_use(offset.id, UseContext::MemoryOffset);
|
||||
|
||||
Reference in new issue
Block a user