Commit 64649169 authored by OptimismBot's avatar OptimismBot Committed by GitHub

Merge pull request #6284 from ethereum-optimism/inphi/cannon-vmfuzz

cannon: Differential Fuzzing syscalls
parents 51fea1ff bc24831c
...@@ -861,6 +861,18 @@ jobs: ...@@ -861,6 +861,18 @@ jobs:
command: make fuzz command: make fuzz
working_directory: op-chain-ops working_directory: op-chain-ops
fuzz-cannon:
docker:
- image: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest
steps:
- checkout
- check-changed:
patterns: cannon,packages/contracts-bedrock/contracts/cannon
- run:
name: Fuzz
command: make fuzz
working_directory: cannon
depcheck: depcheck:
docker: docker:
- image: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest - image: us-docker.pkg.dev/oplabs-tools-artifacts/images/ci-builder:latest
...@@ -1415,6 +1427,7 @@ workflows: ...@@ -1415,6 +1427,7 @@ workflows:
- go-mod-tidy - go-mod-tidy
- fuzz-op-node - fuzz-op-node
- fuzz-op-chain-ops - fuzz-op-chain-ops
- fuzz-cannon
- bedrock-markdown - bedrock-markdown
- go-lint: - go-lint:
name: op-batcher-lint name: op-batcher-lint
......
...@@ -23,8 +23,20 @@ test: elf ...@@ -23,8 +23,20 @@ test: elf
lint: lint:
golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint --timeout 5m -e "errors.As" -e "errors.Is" golangci-lint run -E goimports,sqlclosecheck,bodyclose,asciicheck,misspell,errorlint --timeout 5m -e "errors.As" -e "errors.Is"
fuzz:
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallBrk ./mipsevm
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallClone ./mipsevm
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallMmap ./mipsevm
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallExitGroup ./mipsevm
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallFnctl ./mipsevm
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateHintRead ./mipsevm
go test -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStatePreimageRead ./mipsevm
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateHintWrite ./mipsevm
go test -run NOTAREALTEST -v -fuzztime 20s -fuzz=FuzzStatePreimageWrite ./mipsevm
.PHONY: \ .PHONY: \
cannon \ cannon \
clean \ clean \
test \ test \
lint lint \
fuzz
...@@ -13,6 +13,7 @@ import ( ...@@ -13,6 +13,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/eth/tracers/logger"
...@@ -21,7 +22,7 @@ import ( ...@@ -21,7 +22,7 @@ import (
"github.com/ethereum-optimism/optimism/op-chain-ops/srcmap" "github.com/ethereum-optimism/optimism/op-chain-ops/srcmap"
) )
func testContractsSetup(t *testing.T) (*Contracts, *Addresses) { func testContractsSetup(t require.TestingT) (*Contracts, *Addresses) {
contracts, err := LoadContracts() contracts, err := LoadContracts()
require.NoError(t, err) require.NoError(t, err)
...@@ -48,6 +49,53 @@ func MarkdownTracer() vm.EVMLogger { ...@@ -48,6 +49,53 @@ func MarkdownTracer() vm.EVMLogger {
return logger.NewMarkdownLogger(&logger.Config{}, os.Stdout) return logger.NewMarkdownLogger(&logger.Config{}, os.Stdout)
} }
type MIPSEVM struct {
env *vm.EVM
evmState *state.StateDB
addrs *Addresses
}
func NewMIPSEVM(contracts *Contracts, addrs *Addresses) *MIPSEVM {
env, evmState := NewEVMEnv(contracts, addrs)
return &MIPSEVM{env, evmState, addrs}
}
func (m *MIPSEVM) SetTracer(tracer vm.EVMLogger) {
m.env.Config.Tracer = tracer
}
// Step is a pure function that computes the poststate from the VM state encoded in the StepWitness.
func (m *MIPSEVM) Step(t *testing.T, stepWitness *StepWitness) []byte {
sender := common.Address{0x13, 0x37}
startingGas := uint64(30_000_000)
// we take a snapshot so we can clean up the state, and isolate the logs of this instruction run.
snap := m.env.StateDB.Snapshot()
if stepWitness.HasPreimage() {
t.Logf("reading preimage key %x at offset %d", stepWitness.PreimageKey, stepWitness.PreimageOffset)
poInput, err := stepWitness.EncodePreimageOracleInput()
require.NoError(t, err, "encode preimage oracle input")
_, leftOverGas, err := m.env.Call(vm.AccountRef(m.addrs.Sender), m.addrs.Oracle, poInput, startingGas, big.NewInt(0))
require.NoErrorf(t, err, "evm should not fail, took %d gas", startingGas-leftOverGas)
}
input := stepWitness.EncodeStepInput()
ret, leftOverGas, err := m.env.Call(vm.AccountRef(sender), m.addrs.MIPS, input, startingGas, big.NewInt(0))
require.NoError(t, err, "evm should not fail")
require.Len(t, ret, 32, "expecting 32-byte state hash")
// remember state hash, to check it against state
postHash := common.Hash(*(*[32]byte)(ret))
logs := m.evmState.Logs()
require.Equal(t, 1, len(logs), "expecting a log with post-state")
evmPost := logs[0].Data
require.Equal(t, crypto.Keccak256Hash(evmPost), postHash, "logged state must be accurate")
m.env.StateDB.RevertToSnapshot(snap)
t.Logf("EVM step took %d gas, and returned stateHash %s", startingGas-leftOverGas, postHash)
return evmPost
}
func TestEVM(t *testing.T) { func TestEVM(t *testing.T) {
testFiles, err := os.ReadDir("open_mips_tests/test/bin") testFiles, err := os.ReadDir("open_mips_tests/test/bin")
require.NoError(t, err) require.NoError(t, err)
...@@ -55,7 +103,6 @@ func TestEVM(t *testing.T) { ...@@ -55,7 +103,6 @@ func TestEVM(t *testing.T) {
contracts, addrs := testContractsSetup(t) contracts, addrs := testContractsSetup(t)
var tracer vm.EVMLogger // no-tracer by default, but see SourceMapTracer and MarkdownTracer var tracer vm.EVMLogger // no-tracer by default, but see SourceMapTracer and MarkdownTracer
//tracer = SourceMapTracer(t, contracts, addrs) //tracer = SourceMapTracer(t, contracts, addrs)
sender := common.Address{0x13, 0x37}
for _, f := range testFiles { for _, f := range testFiles {
t.Run(f.Name(), func(t *testing.T) { t.Run(f.Name(), func(t *testing.T) {
...@@ -66,8 +113,8 @@ func TestEVM(t *testing.T) { ...@@ -66,8 +113,8 @@ func TestEVM(t *testing.T) {
// Short-circuit early for exit_group.bin // Short-circuit early for exit_group.bin
exitGroup := f.Name() == "exit_group.bin" exitGroup := f.Name() == "exit_group.bin"
env, evmState := NewEVMEnv(contracts, addrs) evm := NewMIPSEVM(contracts, addrs)
env.Config.Tracer = tracer evm.SetTracer(tracer)
fn := path.Join("open_mips_tests/test/bin", f.Name()) fn := path.Join("open_mips_tests/test/bin", f.Name())
programMem, err := os.ReadFile(fn) programMem, err := os.ReadFile(fn)
...@@ -79,58 +126,31 @@ func TestEVM(t *testing.T) { ...@@ -79,58 +126,31 @@ func TestEVM(t *testing.T) {
// set the return address ($ra) to jump into when test completes // set the return address ($ra) to jump into when test completes
state.Registers[31] = endAddr state.Registers[31] = endAddr
us := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr) goState := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
for i := 0; i < 1000; i++ { for i := 0; i < 1000; i++ {
if us.state.PC == endAddr { if goState.state.PC == endAddr {
break break
} }
if exitGroup && us.state.Exited { if exitGroup && goState.state.Exited {
break break
} }
insn := state.Memory.GetMemory(state.PC) insn := state.Memory.GetMemory(state.PC)
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn) t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn)
stepWitness, err := us.Step(true) stepWitness, err := goState.Step(true)
require.NoError(t, err) require.NoError(t, err)
input := stepWitness.EncodeStepInput() evmPost := evm.Step(t, stepWitness)
startingGas := uint64(30_000_000)
// we take a snapshot so we can clean up the state, and isolate the logs of this instruction run.
snap := env.StateDB.Snapshot()
// prepare pre-image oracle data, if any
if stepWitness.HasPreimage() {
t.Logf("reading preimage key %x at offset %d", stepWitness.PreimageKey, stepWitness.PreimageOffset)
poInput, err := stepWitness.EncodePreimageOracleInput()
require.NoError(t, err, "encode preimage oracle input")
_, leftOverGas, err := env.Call(vm.AccountRef(addrs.Sender), addrs.Oracle, poInput, startingGas, big.NewInt(0))
require.NoErrorf(t, err, "evm should not fail, took %d gas", startingGas-leftOverGas)
}
ret, leftOverGas, err := env.Call(vm.AccountRef(sender), addrs.MIPS, input, startingGas, big.NewInt(0))
require.NoError(t, err, "evm should not fail")
require.Len(t, ret, 32, "expecting 32-byte state hash")
// remember state hash, to check it against state
postHash := common.Hash(*(*[32]byte)(ret))
logs := evmState.Logs()
require.Equal(t, 1, len(logs), "expecting a log with post-state")
evmPost := logs[0].Data
require.Equal(t, crypto.Keccak256Hash(evmPost), postHash, "logged state must be accurate")
env.StateDB.RevertToSnapshot(snap)
t.Logf("EVM step took %d gas, and returned stateHash %s", startingGas-leftOverGas, postHash)
// verify the post-state matches. // verify the post-state matches.
// TODO: maybe more readable to decode the evmPost state, and do attribute-wise comparison. // TODO: maybe more readable to decode the evmPost state, and do attribute-wise comparison.
uniPost := us.state.EncodeWitness() goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(uniPost).String(), hexutil.Bytes(evmPost).String(), require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM") "mipsevm produced different state than EVM")
} }
if exitGroup { if exitGroup {
require.NotEqual(t, uint32(endAddr), us.state.PC, "must not reach end") require.NotEqual(t, uint32(endAddr), goState.state.PC, "must not reach end")
require.True(t, us.state.Exited, "must set exited state") require.True(t, goState.state.Exited, "must set exited state")
require.Equal(t, uint8(1), us.state.ExitCode, "must exit with 1") require.Equal(t, uint8(1), goState.state.ExitCode, "must exit with 1")
} else { } else {
require.Equal(t, uint32(endAddr), state.PC, "must reach end") require.Equal(t, uint32(endAddr), state.PC, "must reach end")
// inspect test result // inspect test result
...@@ -160,8 +180,8 @@ func TestEVMFault(t *testing.T) { ...@@ -160,8 +180,8 @@ func TestEVMFault(t *testing.T) {
// set the return address ($ra) to jump into when test completes // set the return address ($ra) to jump into when test completes
state.Registers[31] = endAddr state.Registers[31] = endAddr
us := NewInstrumentedState(state, nil, os.Stdout, os.Stderr) goState := NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
require.Panics(t, func() { _, _ = us.Step(true) }, "must panic on illegal instruction") require.Panics(t, func() { _, _ = goState.Step(true) }, "must panic on illegal instruction")
insnProof := initialState.Memory.MerkleProof(0) insnProof := initialState.Memory.MerkleProof(0)
stepWitness := &StepWitness{ stepWitness := &StepWitness{
...@@ -181,7 +201,6 @@ func TestHelloEVM(t *testing.T) { ...@@ -181,7 +201,6 @@ func TestHelloEVM(t *testing.T) {
contracts, addrs := testContractsSetup(t) contracts, addrs := testContractsSetup(t)
var tracer vm.EVMLogger // no-tracer by default, but see SourceMapTracer and MarkdownTracer var tracer vm.EVMLogger // no-tracer by default, but see SourceMapTracer and MarkdownTracer
//tracer = SourceMapTracer(t, contracts, addrs) //tracer = SourceMapTracer(t, contracts, addrs)
sender := common.Address{0x13, 0x37}
elfProgram, err := elf.Open("../example/bin/hello.elf") elfProgram, err := elf.Open("../example/bin/hello.elf")
require.NoError(t, err, "open ELF file") require.NoError(t, err, "open ELF file")
...@@ -194,14 +213,11 @@ func TestHelloEVM(t *testing.T) { ...@@ -194,14 +213,11 @@ func TestHelloEVM(t *testing.T) {
require.NoError(t, PatchStack(state), "add initial stack") require.NoError(t, PatchStack(state), "add initial stack")
var stdOutBuf, stdErrBuf bytes.Buffer var stdOutBuf, stdErrBuf bytes.Buffer
us := NewInstrumentedState(state, nil, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr)) goState := NewInstrumentedState(state, nil, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr))
env, evmState := NewEVMEnv(contracts, addrs)
env.Config.Tracer = tracer
start := time.Now() start := time.Now()
for i := 0; i < 400_000; i++ { for i := 0; i < 400_000; i++ {
if us.state.Exited { if goState.state.Exited {
break break
} }
insn := state.Memory.GetMemory(state.PC) insn := state.Memory.GetMemory(state.PC)
...@@ -209,30 +225,16 @@ func TestHelloEVM(t *testing.T) { ...@@ -209,30 +225,16 @@ func TestHelloEVM(t *testing.T) {
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn) t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn)
} }
stepWitness, err := us.Step(true) evm := NewMIPSEVM(contracts, addrs)
require.NoError(t, err) evm.SetTracer(tracer)
input := stepWitness.EncodeStepInput()
startingGas := uint64(30_000_000)
// we take a snapshot so we can clean up the state, and isolate the logs of this instruction run.
snap := env.StateDB.Snapshot()
ret, leftOverGas, err := env.Call(vm.AccountRef(sender), addrs.MIPS, input, startingGas, big.NewInt(0))
require.NoErrorf(t, err, "evm should not fail, took %d gas", startingGas-leftOverGas)
require.Len(t, ret, 32, "expecting 32-byte state hash")
// remember state hash, to check it against state
postHash := common.Hash(*(*[32]byte)(ret))
logs := evmState.Logs()
require.Equal(t, 1, len(logs), "expecting a log with post-state")
evmPost := logs[0].Data
require.Equal(t, crypto.Keccak256Hash(evmPost), postHash, "logged state must be accurate")
env.StateDB.RevertToSnapshot(snap)
//t.Logf("EVM step took %d gas, and returned stateHash %s", startingGas-leftOverGas, postHash)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
evmPost := evm.Step(t, stepWitness)
// verify the post-state matches. // verify the post-state matches.
// TODO: maybe more readable to decode the evmPost state, and do attribute-wise comparison. // TODO: maybe more readable to decode the evmPost state, and do attribute-wise comparison.
uniPost := us.state.EncodeWitness() goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(uniPost).String(), hexutil.Bytes(evmPost).String(), require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM") "mipsevm produced different state than EVM")
} }
end := time.Now() end := time.Now()
...@@ -264,13 +266,10 @@ func TestClaimEVM(t *testing.T) { ...@@ -264,13 +266,10 @@ func TestClaimEVM(t *testing.T) {
oracle, expectedStdOut, expectedStdErr := claimTestOracle(t) oracle, expectedStdOut, expectedStdErr := claimTestOracle(t)
var stdOutBuf, stdErrBuf bytes.Buffer var stdOutBuf, stdErrBuf bytes.Buffer
us := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr)) goState := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr))
env, evmState := NewEVMEnv(contracts, addrs)
env.Config.Tracer = tracer
for i := 0; i < 2000_000; i++ { for i := 0; i < 2000_000; i++ {
if us.state.Exited { if goState.state.Exited {
break break
} }
...@@ -279,32 +278,16 @@ func TestClaimEVM(t *testing.T) { ...@@ -279,32 +278,16 @@ func TestClaimEVM(t *testing.T) {
t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn) t.Logf("step: %4d pc: 0x%08x insn: 0x%08x", state.Step, state.PC, insn)
} }
stepWitness, err := us.Step(true) stepWitness, err := goState.Step(true)
require.NoError(t, err) require.NoError(t, err)
input := stepWitness.EncodeStepInput()
startingGas := uint64(30_000_000)
// we take a snapshot so we can clean up the state, and isolate the logs of this instruction run.
snap := env.StateDB.Snapshot()
// prepare pre-image oracle data, if any
if stepWitness.HasPreimage() {
poInput, err := stepWitness.EncodePreimageOracleInput()
require.NoError(t, err, "encode preimage oracle input")
_, leftOverGas, err := env.Call(vm.AccountRef(addrs.Sender), addrs.Oracle, poInput, startingGas, big.NewInt(0))
require.NoErrorf(t, err, "evm should not fail, took %d gas", startingGas-leftOverGas)
}
ret, leftOverGas, err := env.Call(vm.AccountRef(addrs.Sender), addrs.MIPS, input, startingGas, big.NewInt(0)) evm := NewMIPSEVM(contracts, addrs)
require.NoErrorf(t, err, "evm should not fail, took %d gas", startingGas-leftOverGas) evm.SetTracer(tracer)
require.Len(t, ret, 32, "expecting 32-byte state hash") evmPost := evm.Step(t, stepWitness)
// remember state hash, to check it against state
postHash := common.Hash(*(*[32]byte)(ret)) goPost := goState.state.EncodeWitness()
logs := evmState.Logs() require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
require.Equal(t, 1, len(logs), "expecting a log with post-state") "mipsevm produced different state than EVM")
evmPost := logs[0].Data
require.Equal(t, crypto.Keccak256Hash(evmPost), postHash, "logged state must be accurate")
env.StateDB.RevertToSnapshot(snap)
} }
require.True(t, state.Exited, "must complete program") require.True(t, state.Exited, "must complete program")
......
package mipsevm
import (
"os"
"testing"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
)
const syscallInsn = uint32(0x00_00_00_0c)
func FuzzStateSyscallBrk(f *testing.F) {
contracts, addrs := testContractsSetup(f)
f.Fuzz(func(t *testing.T, pc uint32, step uint64, preimageOffset uint32) {
pc = pc & 0xFF_FF_FF_FC // align PC
nextPC := pc + 4
state := &State{
PC: pc,
NextPC: nextPC,
LO: 0,
HI: 0,
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysBrk},
Step: step,
PreimageKey: common.Hash{},
PreimageOffset: preimageOffset,
}
state.Memory.SetMemory(pc, syscallInsn)
preStateRoot := state.Memory.MerkleRoot()
expectedRegisters := state.Registers
expectedRegisters[2] = 0x4000_0000
goState := NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, pc+4, state.PC)
require.Equal(t, nextPC+4, state.NextPC)
require.Equal(t, uint32(0), state.LO)
require.Equal(t, uint32(0), state.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, expectedRegisters, state.Registers)
require.Equal(t, step+1, state.Step)
require.Equal(t, common.Hash{}, state.PreimageKey)
require.Equal(t, preimageOffset, state.PreimageOffset)
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness)
goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStateSyscallClone(f *testing.F) {
contracts, addrs := testContractsSetup(f)
f.Fuzz(func(t *testing.T, pc uint32, step uint64, preimageOffset uint32) {
pc = pc & 0xFF_FF_FF_FC // align PC
nextPC := pc + 4
state := &State{
PC: pc,
NextPC: nextPC,
LO: 0,
HI: 0,
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysClone},
Step: step,
PreimageOffset: preimageOffset,
}
state.Memory.SetMemory(pc, syscallInsn)
preStateRoot := state.Memory.MerkleRoot()
expectedRegisters := state.Registers
expectedRegisters[2] = 0x1
goState := NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, pc+4, state.PC)
require.Equal(t, nextPC+4, state.NextPC)
require.Equal(t, uint32(0), state.LO)
require.Equal(t, uint32(0), state.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, expectedRegisters, state.Registers)
require.Equal(t, step+1, state.Step)
require.Equal(t, common.Hash{}, state.PreimageKey)
require.Equal(t, preimageOffset, state.PreimageOffset)
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness)
goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStateSyscallMmap(f *testing.F) {
contracts, addrs := testContractsSetup(f)
f.Fuzz(func(t *testing.T, addr uint32, siz uint32, heap uint32) {
state := &State{
PC: 0,
NextPC: 4,
LO: 0,
HI: 0,
Heap: heap,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysMmap, 4: addr, 5: siz},
Step: 0,
PreimageOffset: 0,
}
state.Memory.SetMemory(0, syscallInsn)
preStateRoot := state.Memory.MerkleRoot()
preStateRegisters := state.Registers
goState := NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.PC)
require.Equal(t, uint32(8), state.NextPC)
require.Equal(t, uint32(0), state.LO)
require.Equal(t, uint32(0), state.HI)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, uint64(1), state.Step)
require.Equal(t, common.Hash{}, state.PreimageKey)
require.Equal(t, uint32(0), state.PreimageOffset)
if addr == 0 {
expectedRegisters := preStateRegisters
expectedRegisters[2] = heap
require.Equal(t, expectedRegisters, state.Registers)
sizAlign := siz
if sizAlign&PageAddrMask != 0 { // adjust size to align with page size
sizAlign = siz + PageSize - (siz & PageAddrMask)
}
require.Equal(t, uint32(heap+sizAlign), state.Heap)
} else {
expectedRegisters := preStateRegisters
expectedRegisters[2] = addr
require.Equal(t, expectedRegisters, state.Registers)
require.Equal(t, uint32(heap), state.Heap)
}
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness)
goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStateSyscallExitGroup(f *testing.F) {
contracts, addrs := testContractsSetup(f)
f.Fuzz(func(t *testing.T, exitCode uint8, pc uint32, step uint64) {
pc = pc & 0xFF_FF_FF_FC // align PC
nextPC := pc + 4
state := &State{
PC: pc,
NextPC: nextPC,
LO: 0,
HI: 0,
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysExitGroup, 4: uint32(exitCode)},
Step: step,
PreimageOffset: 0,
}
state.Memory.SetMemory(pc, syscallInsn)
preStateRoot := state.Memory.MerkleRoot()
preStateRegisters := state.Registers
goState := NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, pc, state.PC)
require.Equal(t, nextPC, state.NextPC)
require.Equal(t, uint32(0), state.LO)
require.Equal(t, uint32(0), state.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(exitCode), state.ExitCode)
require.Equal(t, true, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, preStateRegisters, state.Registers)
require.Equal(t, step+1, state.Step)
require.Equal(t, common.Hash{}, state.PreimageKey)
require.Equal(t, uint32(0), state.PreimageOffset)
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness)
goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStateSyscallFnctl(f *testing.F) {
contracts, addrs := testContractsSetup(f)
f.Fuzz(func(t *testing.T, fd uint32, cmd uint32) {
state := &State{
PC: 0,
NextPC: 4,
LO: 0,
HI: 0,
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysFcntl, 4: fd, 5: cmd},
Step: 0,
PreimageOffset: 0,
}
state.Memory.SetMemory(0, syscallInsn)
preStateRoot := state.Memory.MerkleRoot()
preStateRegisters := state.Registers
goState := NewInstrumentedState(state, nil, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.PC)
require.Equal(t, uint32(8), state.NextPC)
require.Equal(t, uint32(0), state.LO)
require.Equal(t, uint32(0), state.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, uint64(1), state.Step)
require.Equal(t, common.Hash{}, state.PreimageKey)
require.Equal(t, uint32(0), state.PreimageOffset)
if cmd == 3 {
expectedRegisters := preStateRegisters
switch fd {
case fdStdin, fdPreimageRead, fdHintRead:
expectedRegisters[2] = 0
case fdStdout, fdStderr, fdPreimageWrite, fdHintWrite:
expectedRegisters[2] = 1
default:
expectedRegisters[2] = 0xFF_FF_FF_FF
expectedRegisters[7] = MipsEBADF
}
require.Equal(t, expectedRegisters, state.Registers)
} else {
expectedRegisters := preStateRegisters
expectedRegisters[2] = 0xFF_FF_FF_FF
expectedRegisters[7] = MipsEINVAL
require.Equal(t, expectedRegisters, state.Registers)
}
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness)
goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStateHintRead(f *testing.F) {
contracts, addrs := testContractsSetup(f)
f.Fuzz(func(t *testing.T, addr uint32, count uint32) {
preimageData := []byte("hello world")
state := &State{
PC: 0,
NextPC: 4,
LO: 0,
HI: 0,
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysRead, 4: fdHintRead, 5: addr, 6: count},
Step: 0,
PreimageKey: preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey(),
PreimageOffset: 0,
}
state.Memory.SetMemory(0, syscallInsn)
preStatePreimageKey := state.PreimageKey
preStateRoot := state.Memory.MerkleRoot()
expectedRegisters := state.Registers
expectedRegisters[2] = count
oracle := staticOracle(t, preimageData) // only used for hinting
goState := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.PC)
require.Equal(t, uint32(8), state.NextPC)
require.Equal(t, uint32(0), state.LO)
require.Equal(t, uint32(0), state.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, uint64(1), state.Step)
require.Equal(t, preStatePreimageKey, state.PreimageKey)
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness)
goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStatePreimageRead(f *testing.F) {
contracts, addrs := testContractsSetup(f)
f.Fuzz(func(t *testing.T, addr uint32, count uint32, preimageOffset uint32) {
preimageData := []byte("hello world")
if preimageOffset >= uint32(len(preimageData)) {
t.SkipNow()
}
state := &State{
PC: 0,
NextPC: 4,
LO: 0,
HI: 0,
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysRead, 4: fdPreimageRead, 5: addr, 6: count},
Step: 0,
PreimageKey: preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey(),
PreimageOffset: preimageOffset,
}
state.Memory.SetMemory(0, syscallInsn)
preStatePreimageKey := state.PreimageKey
preStateRoot := state.Memory.MerkleRoot()
writeLen := count
if writeLen > 4 {
writeLen = 4
}
if preimageOffset+writeLen > uint32(8+len(preimageData)) {
writeLen = uint32(8+len(preimageData)) - preimageOffset
}
oracle := staticOracle(t, preimageData)
goState := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.True(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.PC)
require.Equal(t, uint32(8), state.NextPC)
require.Equal(t, uint32(0), state.LO)
require.Equal(t, uint32(0), state.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
if writeLen > 0 {
// Memory may be unchanged if we're writing the first zero-valued 7 bytes of the pre-image.
//require.NotEqual(t, preStateRoot, state.Memory.MerkleRoot())
require.Greater(t, state.PreimageOffset, preimageOffset)
} else {
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, state.PreimageOffset, preimageOffset)
}
require.Equal(t, uint64(1), state.Step)
require.Equal(t, preStatePreimageKey, state.PreimageKey)
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness)
goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStateHintWrite(f *testing.F) {
contracts, addrs := testContractsSetup(f)
f.Fuzz(func(t *testing.T, addr uint32, count uint32) {
preimageData := []byte("hello world")
state := &State{
PC: 0,
NextPC: 4,
LO: 0,
HI: 0,
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysWrite, 4: fdHintWrite, 5: addr, 6: count},
Step: 0,
PreimageKey: preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey(),
PreimageOffset: 0,
// This is only used by mips.go. The reads a zeroed page-sized buffer when reading hint data from memory.
// We pre-allocate a buffer for the read hint data to be copied into.
LastHint: make(hexutil.Bytes, PageSize),
}
state.Memory.SetMemory(0, syscallInsn)
preStatePreimageKey := state.PreimageKey
preStateRoot := state.Memory.MerkleRoot()
expectedRegisters := state.Registers
expectedRegisters[2] = count
oracle := staticOracle(t, preimageData) // only used for hinting
goState := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.PC)
require.Equal(t, uint32(8), state.NextPC)
require.Equal(t, uint32(0), state.LO)
require.Equal(t, uint32(0), state.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, uint64(1), state.Step)
require.Equal(t, preStatePreimageKey, state.PreimageKey)
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness)
goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
func FuzzStatePreimageWrite(f *testing.F) {
contracts, addrs := testContractsSetup(f)
f.Fuzz(func(t *testing.T, addr uint32, count uint32) {
preimageData := []byte("hello world")
state := &State{
PC: 0,
NextPC: 4,
LO: 0,
HI: 0,
Heap: 0,
ExitCode: 0,
Exited: false,
Memory: NewMemory(),
Registers: [32]uint32{2: sysWrite, 4: fdPreimageWrite, 5: addr, 6: count},
Step: 0,
PreimageKey: preimage.Keccak256Key(crypto.Keccak256Hash(preimageData)).PreimageKey(),
PreimageOffset: 128,
}
state.Memory.SetMemory(0, syscallInsn)
preStateRoot := state.Memory.MerkleRoot()
expectedRegisters := state.Registers
sz := 4 - (addr & 0x3)
if sz < count {
sz = count
}
expectedRegisters[2] = sz
oracle := staticOracle(t, preimageData)
goState := NewInstrumentedState(state, oracle, os.Stdout, os.Stderr)
stepWitness, err := goState.Step(true)
require.NoError(t, err)
require.False(t, stepWitness.HasPreimage())
require.Equal(t, uint32(4), state.PC)
require.Equal(t, uint32(8), state.NextPC)
require.Equal(t, uint32(0), state.LO)
require.Equal(t, uint32(0), state.HI)
require.Equal(t, uint32(0), state.Heap)
require.Equal(t, uint8(0), state.ExitCode)
require.Equal(t, false, state.Exited)
require.Equal(t, preStateRoot, state.Memory.MerkleRoot())
require.Equal(t, uint64(1), state.Step)
require.Equal(t, uint32(0), state.PreimageOffset)
evm := NewMIPSEVM(contracts, addrs)
evmPost := evm.Step(t, stepWitness)
goPost := goState.state.EncodeWitness()
require.Equal(t, hexutil.Bytes(goPost).String(), hexutil.Bytes(evmPost).String(),
"mipsevm produced different state than EVM")
})
}
...@@ -6,6 +6,16 @@ import ( ...@@ -6,6 +6,16 @@ import (
"io" "io"
) )
const (
sysMmap = 4090
sysBrk = 4045
sysClone = 4120
sysExitGroup = 4246
sysRead = 4003
sysWrite = 4004
sysFcntl = 4055
)
func (m *InstrumentedState) readPreimage(key [32]byte, offset uint32) (dat [32]byte, datLen uint32) { func (m *InstrumentedState) readPreimage(key [32]byte, offset uint32) (dat [32]byte, datLen uint32) {
preimage := m.lastPreimage preimage := m.lastPreimage
if key != m.lastPreimageKey { if key != m.lastPreimageKey {
...@@ -43,7 +53,7 @@ func (m *InstrumentedState) handleSyscall() error { ...@@ -43,7 +53,7 @@ func (m *InstrumentedState) handleSyscall() error {
//fmt.Printf("syscall: %d\n", syscallNum) //fmt.Printf("syscall: %d\n", syscallNum)
switch syscallNum { switch syscallNum {
case 4090: // mmap case sysMmap:
sz := a1 sz := a1
if sz&PageAddrMask != 0 { // adjust size to align with page size if sz&PageAddrMask != 0 { // adjust size to align with page size
sz += PageSize - (sz & PageAddrMask) sz += PageSize - (sz & PageAddrMask)
...@@ -56,15 +66,15 @@ func (m *InstrumentedState) handleSyscall() error { ...@@ -56,15 +66,15 @@ func (m *InstrumentedState) handleSyscall() error {
v0 = a0 v0 = a0
//fmt.Printf("mmap hint 0x%x size 0x%x\n", v0, sz) //fmt.Printf("mmap hint 0x%x size 0x%x\n", v0, sz)
} }
case 4045: // brk case sysBrk:
v0 = 0x40000000 v0 = 0x40000000
case 4120: // clone (not supported) case sysClone: // clone (not supported)
v0 = 1 v0 = 1
case 4246: // exit_group case sysExitGroup:
m.state.Exited = true m.state.Exited = true
m.state.ExitCode = uint8(a0) m.state.ExitCode = uint8(a0)
return nil return nil
case 4003: // read case sysRead:
// args: a0 = fd, a1 = addr, a2 = count // args: a0 = fd, a1 = addr, a2 = count
// returns: v0 = read, v1 = err code // returns: v0 = read, v1 = err code
switch a0 { switch a0 {
...@@ -98,7 +108,7 @@ func (m *InstrumentedState) handleSyscall() error { ...@@ -98,7 +108,7 @@ func (m *InstrumentedState) handleSyscall() error {
v0 = 0xFFffFFff v0 = 0xFFffFFff
v1 = MipsEBADF v1 = MipsEBADF
} }
case 4004: // write case sysWrite:
// args: a0 = fd, a1 = addr, a2 = count // args: a0 = fd, a1 = addr, a2 = count
// returns: v0 = written, v1 = err code // returns: v0 = written, v1 = err code
switch a0 { switch a0 {
...@@ -144,7 +154,7 @@ func (m *InstrumentedState) handleSyscall() error { ...@@ -144,7 +154,7 @@ func (m *InstrumentedState) handleSyscall() error {
v0 = 0xFFffFFff v0 = 0xFFffFFff
v1 = MipsEBADF v1 = MipsEBADF
} }
case 4055: // fcntl case sysFcntl:
// args: a0 = fd, a1 = cmd // args: a0 = fd, a1 = cmd
if a1 == 3 { // F_GETFL: get file descriptor flags if a1 == 3 { // F_GETFL: get file descriptor flags
switch a0 { switch a0 {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment