mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-08 22:14:29 +00:00
core/vm: add access cost check (#35261)
This is an edge case found by @weiihann. Under 8038, the cold storage access cost is increased to 3,000 gas, which exceeds the sentry check threshold. Therefore, the sentry check no longer guarantees that the remaining gas is sufficient to cover a cold slot access. Therefore, an additional access affordability check is added to eliminate the potential DoS vector.
This commit is contained in:
parent
e5c5e1897d
commit
cf35a1b6f6
2 changed files with 27 additions and 5 deletions
|
|
@ -21,6 +21,7 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
|
@ -235,3 +236,19 @@ func TestEIP8038SelfdestructAccountWrite(t *testing.T) {
|
|||
t.Fatalf("state gas = %d, want %d", res.UsedStateGas, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEIP8038SStoreAccessGuard covers the affordability check that bails out
|
||||
// before the slot is read once the gas left cannot cover the slot's access cost.
|
||||
// The two PUSH1s cost 6, so a 2506 budget leaves 2500 at the SSTORE: above the
|
||||
// reentrancy sentry (2300) yet below COLD_STORAGE_ACCESS (3000). The guard must
|
||||
// fire, distinguishable from the sentry/charge OOG by its "slot access" message.
|
||||
func TestEIP8038SStoreAccessGuard(t *testing.T) {
|
||||
budget := NewGasBudget(6+params.SstoreSentryGasEIP2200+200, 0)
|
||||
_, _, err := run8038(t, sstore(0, 1), budget, new(uint256.Int), nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected failure: gas left cannot cover cold-slot access")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "not enough gas for slot access") {
|
||||
t.Fatalf("got %q, want the slot-access guard error", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -696,10 +696,10 @@ func gasSStore8037And8038(evm *EVM, contract *Contract, stack *Stack, mem *Memor
|
|||
return GasCosts{}, errors.New("not enough gas for reentrancy sentry")
|
||||
}
|
||||
var (
|
||||
y, x = stack.back(1), stack.peek()
|
||||
slot = common.Hash(x.Bytes32())
|
||||
current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), slot)
|
||||
value = common.Hash(y.Bytes32())
|
||||
y, x = stack.back(1), stack.peek()
|
||||
slot = common.Hash(x.Bytes32())
|
||||
value = common.Hash(y.Bytes32())
|
||||
stateSet = params.StorageCreationSize * evm.Context.CostPerStateByte
|
||||
)
|
||||
// Check slot presence in the access list
|
||||
access := params.WarmStorageReadCostEIP2929
|
||||
|
|
@ -707,7 +707,12 @@ func gasSStore8037And8038(evm *EVM, contract *Contract, stack *Stack, mem *Memor
|
|||
access = params.ColdStorageAccessAmsterdam
|
||||
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
|
||||
}
|
||||
stateSet := params.StorageCreationSize * evm.Context.CostPerStateByte
|
||||
// Check access cost affordability before reading slot
|
||||
if contract.Gas.RegularGas < access {
|
||||
return GasCosts{}, errors.New("not enough gas for slot access")
|
||||
}
|
||||
// Read the slot value for gas cost measurement
|
||||
current, original := evm.StateDB.GetStateAndCommittedState(contract.Address(), slot)
|
||||
|
||||
if current == value { // noop (1)
|
||||
return GasCosts{RegularGas: access}, nil
|
||||
|
|
|
|||
Loading…
Reference in a new issue