-
Notifications
You must be signed in to change notification settings - Fork 604
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
Bug report: Due to incorrect use of implicit conversion(as
), some cases do not work in 32-bit operating systems.
#971
Comments
I did a quick test and noticed that the eth tests also fail when using |
This is a nice catch! I quickly searched for |
If you use the
Different running results will be generated in 32-bit and 64-bit systems, which violates the consensus of Ethereum. For approach 1The only difficulty is that in
We need to convert the usize indexer into u64 indexer. However, the addressing space of usize on a 32-bit machine is only u32::MAX . Anyway, we are not really allocating u64::MAX memory on a 32-bit machine because of the MMU.In golang , the slice can accept an uint64 type numeric as indexer. Unfortunately, rust doesn't have this feature.https://github.com/ethereum/go-ethereum/blob/daa2e5d6a66833b9834b60a3a46835610bbde99a/core/vm/memory.go#L35 // Set sets offset + size to value
func (m *Memory) Set(offset, size uint64, value []byte) {
// It's possible the offset is greater than 0 and size equals 0. This is because
// the calcMemSize (common.go) could potentially return 0 when size is zero (NO-OP)
if size > 0 {
// length of store may never be less than offset + size.
// The store should be resized PRIOR to setting the memory
if offset+size > uint64(len(m.store)) {
panic("invalid memory: store empty")
}
copy(m.store[offset:offset+size], value)
}
} So, my suggestion is We can use two vectors as high and low bits. pub struct Memory {
low: Vec<u8>,
high: Vec<u8>,
}
impl Memory {
pub fn set_byte(&mut self, index: u64, byte: u8) {
if index > usize::MAX {
// store byte in high field
} else {
// store byte in low field
}
}
} |
@dyxushuai I think you are overthinking things. It is not possible to use more than u32 size of memory as the gas calculation limits it (and would throw OutOfGas), that is why we are casting U256 to usize or saturated usize. Those macros are made especially for this. The idea would be to modify these macros: revm/crates/interpreter/src/instructions/macros.rs Lines 199 to 218 in 23cbac4
|
Cool, I can help with this fix |
Root cause
I found
revm
used the implicit conversionas
without checking the overflow, like:revm/crates/interpreter/src/instructions/macros.rs
Line 216 in 23cbac4
revm/crates/interpreter/src/instructions/macros.rs
Line 201 in 23cbac4
and so on.
In
revm
we usually convertu64
tousize
. Therefore, it's perfectly fine when running on a 64-bit OS. However, in a 32-bit OS, the rules are different, and it will silently overflow and return unsound values.Case study;TLDR
Failed case from
zeth
: risc0/zeth#52What happened?
When you track the traces of the
revm
execution, you will find:The binary running in the risc0-vm has over 4 traces, but it will terminate and return
OutOfGas
when reaching theRETURNDATACOPY
opcode if run outside of it. Both of them have the same program, but what the difference between them?The
RETURNDATACOPY
has 3 parameters1:The gas cost of this opcode will be
864691128455135253
, which is far greater than the gas limit.We discovered that the subsequent
PUSH1
opcode has 42949651810 remaining gas, whereas the previous one had 42949651813, indicating a cost of only three gases. This is equivalent to the static gas ofRETURNDATACOPY
. Therefore, the total gas was not calculated using thesize
value (dynamic gas was missing).revm/crates/interpreter/src/instructions/system.rs
Lines 111 to 130 in 23cbac4
The only reason here is that
gas::verylowcopy_cost(len as u64)
returns 3, which should not be. Unless there are some issues with therisc0-vm
orriscv32im-risc0-zkvm-elf
target. However, no matter how the code is modified, this bug can be reproduced stably and can largely eliminate some UBs.revm/crates/interpreter/src/gas/calc.rs
Lines 107 to 111 in 23cbac4
So, I found the line get
len
return 0.revm/crates/interpreter/src/instructions/system.rs
Line 114 in 23cbac4
revm/crates/interpreter/src/instructions/macros.rs
Lines 205 to 218 in 23cbac4
Since the value of
len
is9223372036854776000
, which exceedsusize::MAX
in a 32-bit OS, an overflow occurred and returned a value of 0.How to fix?
We cannot resolve this issue without making significant modifications to the codebase. Because
revm
used lots ofusize
, like the index&offset ofMemory
, all opcodes that use thelen
and so on.Approach 1
I think a good way is to align with the implementation of
geth
2, all of these places, we use theu64
instead ofusize
, like use au64
indexedMemory
.https://github.com/ethereum/go-ethereum/blob/master/core/vm/memory.go#L35
Approach 2
Let it fail fast, only return an error in cases of casting overflow, such as
CastOVerflow
.To prevent developers from using the
as
keyword for casting, we can add a#![warn(clippy::cast_lossless)]
compile attribute to perform a Clippy lint check3.And it should be:
However, this will cause inconsistent behavior between 32-bit and 64-bit operating systems.
Conclusion
In any case, it is crucial to avoid using
as
.Footnotes
https://www.evm.codes/#3e?fork=shanghai ↩
https://github.com/ethereum/go-ethereum ↩
https://rust-lang.github.io/rust-clippy/master/#/cast_lossless ↩
The text was updated successfully, but these errors were encountered: