#38319 [BC-Insight] Edge case difference for GETH and NETHERMIND when calculating memory expansion gas
Submitted on Dec 30th 2024 at 22:58:53 UTC by @Omik for Attackathon | Ethereum Protocol
Report ID: #38319
Report Type: Blockchain/DLT
Report severity: Insight
Target: https://github.com/NethermindEth/nethermind
Impacts:
Unintended permanent chain split affecting greater than or equal to 25% of the network, requiring hard fork (network partition requiring hard fork)
Description
Brief/Intro
Hey There, Merry christmas and happy new year!!!
I found out that there is a discrepancy in the way nethermind handles gas calculation for memory expansion, instead of limiting word size to uint32 max like GETH EL, its limiting the word size to int32 max, which is way lower than uint32 max.
This attack is an edge case when the gas limit is higher than 30M. Fortunately, the current Ethereum gas limit is set to 30M, Therefore the possibility of this attack to occur is small.
Based on the things that you concerned about, this attack falls under this statement
Vulnerability Details
In EVM every opcode is executed with the same flow pattern, roughly the steps for each opcode that interact with memory were:
calculate the memory size
based on the memory size, calculate the gas
pay the gas
resize the memory based on its size from step 1
execute the opcode
In GETH there is a hardcoded value that is being used while calculating the gas, this value is "0x1FFFFFFFE0" (https://github.com/ethereum/go-ethereum/blob/master/core/vm/gas_table.go#L39), and it's being used to make sure that the word size never goes beyond "0xFFFFFFFF" or uint32 max, because if the word size goes beyond uint32 max the gas calculation will overflow. (https://github.com/ethereum/go-ethereum/blob/master/core/vm/gas_table.go#L34-L38)
This limitation is also applied in Nethermind, However, instead of limiting the word size to uint32 max like GETH, Nethermind limits the word size only to (0x7FFFFFFF) int32 max (https://github.com/NethermindEth/nethermind/blob/master/src/Nethermind/Nethermind.Evm/EvmPooledMemory.cs#L303).
Therefore, if an opcode that interacts with a memory tries to expand the memory between 0x80000000 - 0xFFFFFFFF, Nethermind will marked the opcode execution as out of gas (https://github.com/NethermindEth/nethermind/blob/master/src/Nethermind/Nethermind.Evm/EvmPooledMemory.cs#L303-L307), and GETH will continue the execution and not marked it as out of gas.
Impact Details
Since Nethermind will fail the transaction as out of gas, and GETH will continue the execution and not fail the transaction, both of these clients will have a different state root, and this could trigger Nethermind and GETH clients to split.
GETH and Nethermind are 2 of the largest execution clients in Ethereum based on https://clientdiversity.org/?utm_source=immunefi and https://ethernodes.org/?utm_source=immunefi
Link to Proof of Concept
https://gist.github.com/GibranAkbaromiL/2f6246d5ba3c35031ae68169376a743a
Proof of Concept
Proof of Concept
To reproduce the behavior:
clone the goevmlab repo https://github.com/holiman/goevmlab
build the docker image, this is to prepare the binary for each evms. (I'm getting errors for building a lot of evms, that's why I only test the most common evm client which are GETH and Nethermind, and the eels to know the intended spec)
get the state test from this gist link https://gist.github.com/GibranAkbaromiL/2f6246d5ba3c35031ae68169376a743a
run the docker image
run these commands
Due to hardware limitations on my end GETH and EELS state tests output "killed", this is because of OOM (Out of memory). This means that it already passed the gas calculation steps, and fails when trying to actually expand the memory in my/your machine. However, for Nethermind the execution goes out of gas immediately, without encountering an OOM error, Because the opcodes execution ends in the gas calculation steps, and didn't even try to expand the memory on my/your machine.
And if you try to access the traces for GETH you notice that the execution didn't give the full traces, and stops at MLOAD, this is because of the OOM that you encountered due to hardware limitation. However, Nethermind will still give you the full traces of the transaction, because it didn't encounter the OOM.
Was this helpful?