Skip to content

Commit

Permalink
Merge pull request #21 from ethereum-optimism/tip/fuzz_evm_test
Browse files Browse the repository at this point in the history
Syscall fuzz test
  • Loading branch information
ImTei authored Mar 18, 2024
2 parents 8ee3d3c + f0856a2 commit 8d7f523
Show file tree
Hide file tree
Showing 9 changed files with 612 additions and 102 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ jobs:
- name: Run tests
run: go test -v ./...
working-directory: rvgo
- name: Fuzz
run: make fuzz
rvsol-tests:
runs-on: ubuntu-latest
timeout-minutes: 20
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
rvgo/bin
rvgo/scripts/go-ffi/go-ffi
rvgo/test/testdata

rvsol/cache
rvsol/out
Expand Down
23 changes: 22 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ build-rvsol:
.PHONY: build-rvsol

build-test:
make -C ./tests/go-tests all
make -C ./tests/go-tests all-test
make -C ./rvgo build-ffi
.PHONY: build-test

Expand All @@ -22,4 +22,25 @@ clean:
test: build
make -C ./rvgo test
make -C ./rvsol test
make fuzz
.PHONY: test

fuzz: build
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallExit ./rvgo/test
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallNoop ./rvgo/test
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateHintRead ./rvgo/test
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStatePreimageRead ./rvgo/test
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateHintWrite ./rvgo/test
go test -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStatePreimageWrite ./rvgo/test

fuzz-mac:
go test -ldflags=-extldflags=-Wl,-ld_classic -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallExit ./rvgo/test
go test -ldflags=-extldflags=-Wl,-ld_classic -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateSyscallNoop ./rvgo/test
go test -ldflags=-extldflags=-Wl,-ld_classic -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateHintRead ./rvgo/test
go test -ldflags=-extldflags=-Wl,-ld_classic -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStatePreimageRead ./rvgo/test
go test -ldflags=-extldflags=-Wl,-ld_classic -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStateHintWrite ./rvgo/test
go test -ldflags=-extldflags=-Wl,-ld_classic -run NOTAREALTEST -v -fuzztime 10s -fuzz=FuzzStatePreimageWrite ./rvgo/test

.PHONY: \
fuzz \
fuzz-mac
105 changes: 55 additions & 50 deletions rvgo/fast/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,30 @@ import (
"encoding/binary"
"fmt"
"io"
)

const (
fdStdin = 0
fdStdout = 1
fdStderr = 2
fdHintRead = 3
fdHintWrite = 4
fdPreimageRead = 5
fdPreimageWrite = 6
"github.com/ethereum-optimism/asterisc/rvgo/riscv"
)

type UnsupportedSyscallErr struct {
SyscallNum U64
}

func (e *UnsupportedSyscallErr) Error() string {
return fmt.Sprintf("unsupported system call: %d", e.SyscallNum)
}

// riscvStep runs a single instruction
// Note: errors are only returned in debugging/tooling modes, not in production use.
func (inst *InstrumentedState) riscvStep() (outErr error) {
var revertCode uint64
defer func() {
if err := recover(); err != nil {
outErr = fmt.Errorf("revert: %v", err)
if errInterface := recover(); errInterface != nil {
if err, ok := errInterface.(error); ok {
outErr = fmt.Errorf("revert: %w", err)
} else {
outErr = fmt.Errorf("revert: %v", err)
}

}
if revertCode != 0 {
outErr = fmt.Errorf("revert %x: %w", revertCode, outErr)
Expand Down Expand Up @@ -347,23 +352,23 @@ func (inst *InstrumentedState) riscvStep() (outErr error) {
sysCall := func() {
a7 := getRegister(toU64(17))
switch a7 {
case 93: // exit the calling thread. No multi-thread support yet, so just exit.
case riscv.SysExit: // exit the calling thread. No multi-thread support yet, so just exit.
a0 := getRegister(toU64(10))
setExitCode(uint8(a0))
setExited()
// program stops here, no need to change registers.
case 94: // exit-group
case riscv.SysExitGroup: // exit-group
a0 := getRegister(toU64(10))
setExitCode(uint8(a0))
setExited()
case 214: // brk
case riscv.SysBrk: // brk
// Go sys_linux_riscv64 runtime will only ever call brk(NULL), i.e. first argument (register a0) set to 0.

// brk(0) changes nothing about the memory, and returns the current page break
v := shl64(toU64(30), toU64(1)) // set program break at 1 GiB
setRegister(toU64(10), v)
setRegister(toU64(11), toU64(0)) // no error
case 222: // mmap
case riscv.SysMmap: // mmap
// A0 = addr (hint)
addr := getRegister(toU64(10))
// A1 = n (length)
Expand Down Expand Up @@ -391,21 +396,21 @@ func (inst *InstrumentedState) riscvStep() (outErr error) {
//fmt.Printf("mmap: 0x%016x (0x%x allowed)\n", addr, length)
}
setRegister(toU64(11), toU64(0)) // no error
case 63: // read
case riscv.SysRead: // read
fd := getRegister(toU64(10)) // A0 = fd
addr := getRegister(toU64(11)) // A1 = *buf addr
count := getRegister(toU64(12)) // A2 = count
var n U64
var errCode U64
switch fd {
case fdStdin: // stdin
case riscv.FdStdin: // stdin
n = toU64(0) // never read anything from stdin
errCode = toU64(0)
case fdHintRead: // hint-read
case riscv.FdHintRead: // hint-read
// say we read it all, to continue execution after reading the hint-write ack response
n = count
errCode = toU64(0)
case fdPreimageRead: // preimage read
case riscv.FdPreimageRead: // preimage read
n = readPreimageValue(addr, count)
errCode = toU64(0)
default:
Expand All @@ -414,28 +419,28 @@ func (inst *InstrumentedState) riscvStep() (outErr error) {
}
setRegister(toU64(10), n)
setRegister(toU64(11), errCode)
case 64: // write
case riscv.SysWrite: // write
fd := getRegister(toU64(10)) // A0 = fd
addr := getRegister(toU64(11)) // A1 = *buf addr
count := getRegister(toU64(12)) // A2 = count
var n U64
var errCode U64
switch fd {
case fdStdout: // stdout
case riscv.FdStdout: // stdout
_, err := io.Copy(inst.stdOut, s.Memory.ReadMemoryRange(addr, count))
if err != nil {
panic(fmt.Errorf("stdout writing err: %w", err))
}
n = count // write completes fully in single instruction step
errCode = toU64(0)
case fdStderr: // stderr
case riscv.FdStderr: // stderr
_, err := io.Copy(inst.stdErr, s.Memory.ReadMemoryRange(addr, count))
if err != nil {
panic(fmt.Errorf("stderr writing err: %w", err))
}
n = count // write completes fully in single instruction step
errCode = toU64(0)
case fdHintWrite: // hint-write
case riscv.FdHintWrite: // hint-write
hintData, _ := io.ReadAll(s.Memory.ReadMemoryRange(addr, count))
s.LastHint = append(inst.state.LastHint, hintData...)
for len(s.LastHint) >= 4 { // process while there is enough data to check if there are any hints
Expand All @@ -450,7 +455,7 @@ func (inst *InstrumentedState) riscvStep() (outErr error) {
}
n = count
errCode = toU64(0)
case fdPreimageWrite: // pre-image key write
case riscv.FdPreimageWrite: // pre-image key write
n = writePreimageKey(addr, count)
errCode = toU64(0) // no error
default: // any other file, including (3) hint read (5) preimage read
Expand All @@ -459,7 +464,7 @@ func (inst *InstrumentedState) riscvStep() (outErr error) {
}
setRegister(toU64(10), n)
setRegister(toU64(11), errCode)
case 25: // fcntl - file descriptor manipulation / info lookup
case riscv.SysFcntl: // fcntl - file descriptor manipulation / info lookup
fd := getRegister(toU64(10)) // A0 = fd
cmd := getRegister(toU64(11)) // A1 = cmd
var out U64
Expand Down Expand Up @@ -491,38 +496,38 @@ func (inst *InstrumentedState) riscvStep() (outErr error) {
}
setRegister(toU64(10), out)
setRegister(toU64(11), errCode) // EBADF
case 56: // openat - the Go linux runtime will try to open optional /sys/kernel files for performance hints
case riscv.SysOpenat: // openat - the Go linux runtime will try to open optional /sys/kernel files for performance hints
setRegister(toU64(10), u64Mask())
setRegister(toU64(11), toU64(0xd)) // EACCES - no access allowed
case 123: // sched_getaffinity - hardcode to indicate affinity with any cpu-set mask
case riscv.SysSchedGetaffinity: // sched_getaffinity - hardcode to indicate affinity with any cpu-set mask
setRegister(toU64(10), toU64(0))
setRegister(toU64(11), toU64(0))
case 124: // sched_yield - nothing to yield, synchronous execution only, for now
case riscv.SysSchedYield: // sched_yield - nothing to yield, synchronous execution only, for now
setRegister(toU64(10), toU64(0))
setRegister(toU64(11), toU64(0))
case 113: // clock_gettime
case riscv.SysClockGettime: // clock_gettime
addr := getRegister(toU64(11)) // addr of timespec struct
// write 1337s + 42ns as time
storeMemUnaligned(addr, toU64(8), shortToU256(1337), 1, 0xff, true, false)
storeMemUnaligned(add64(addr, toU64(8)), toU64(8), toU256(42), 2, 0xff, true, false)
setRegister(toU64(10), toU64(0))
setRegister(toU64(11), toU64(0))
case 135: // rt_sigprocmask - ignore any sigset changes
case riscv.SysRtSigprocmask: // rt_sigprocmask - ignore any sigset changes
setRegister(toU64(10), toU64(0))
setRegister(toU64(11), toU64(0))
case 132: // sigaltstack - ignore any hints of an alternative signal receiving stack addr
case riscv.SysSigaltstack: // sigaltstack - ignore any hints of an alternative signal receiving stack addr
setRegister(toU64(10), toU64(0))
setRegister(toU64(11), toU64(0))
case 178: // gettid - hardcode to 0
case riscv.SysGettid: // gettid - hardcode to 0
setRegister(toU64(10), toU64(0))
setRegister(toU64(11), toU64(0))
case 134: // rt_sigaction - no-op, we never send signals, and thus need no sig handler info
case riscv.SysRtSigaction: // rt_sigaction - no-op, we never send signals, and thus need no sig handler info
setRegister(toU64(10), toU64(0))
setRegister(toU64(11), toU64(0))
case 220: // clone - not supported
case riscv.SysClone: // clone - not supported
setRegister(toU64(10), toU64(1))
setRegister(toU64(11), toU64(0))
case 163: // getrlimit
case riscv.SysGetrlimit: // getrlimit
res := getRegister(toU64(10))
addr := getRegister(toU64(11))
switch res {
Expand All @@ -533,39 +538,39 @@ func (inst *InstrumentedState) riscvStep() (outErr error) {
default:
revertWithCode(0xf0012, fmt.Errorf("unrecognized resource limit lookup: %d", res))
}
case 233: // madvise - ignored
case riscv.SysMadvise: // madvise - ignored
setRegister(toU64(10), toU64(0))
setRegister(toU64(11), toU64(0))
case 20: // epoll_create1 - ignored
case riscv.SysEpollCreate1: // epoll_create1 - ignored
setRegister(toU64(10), toU64(0))
setRegister(toU64(11), toU64(0))
case 21: // epoll_ctl - ignored
case riscv.SysEpollCtl: // epoll_ctl - ignored
setRegister(toU64(10), toU64(0))
setRegister(toU64(11), toU64(0))
case 59: // pipe2 - ignored
case riscv.SysPipe2: // pipe2 - ignored
setRegister(toU64(10), toU64(0))
setRegister(toU64(11), toU64(0))
case 78: // readlinkat - ignored
case riscv.SysReadlinnkat: // readlinkat - ignored
setRegister(toU64(10), toU64(0))
setRegister(toU64(11), toU64(0))
case 79: // newfstatat - ignored
case riscv.SysNewfstatat: // newfstatat - ignored
setRegister(toU64(10), toU64(0))
setRegister(toU64(11), toU64(0))
case 160: // newuname - ignored
case riscv.SysNewuname: // newuname - ignored
setRegister(toU64(10), toU64(0))
setRegister(toU64(11), toU64(0))
case 215: // munmap - ignored
case riscv.SysMunmap: // munmap - ignored
setRegister(toU64(10), toU64(0))
setRegister(toU64(11), toU64(0))
case 278: // getrandom - ignored
case riscv.SysGetRandom: // getrandom - ignored
setRegister(toU64(10), toU64(0))
setRegister(toU64(11), toU64(0))
case 261: // prlimit64 -- unsupported, we have getrlimit, is prlimit64 even called?
revertWithCode(0xf001ca11, fmt.Errorf("unsupported system call: %d", a7))
case 422: // futex - not supported, for now
revertWithCode(0xf001ca11, fmt.Errorf("unsupported system call: %d", a7))
case 101: // nanosleep - not supported, for now
revertWithCode(0xf001ca11, fmt.Errorf("unsupported system call: %d", a7))
case riscv.SysPrlimit64: // prlimit64 -- unsupported, we have getrlimit, is prlimit64 even called?
revertWithCode(0xf001ca11, &UnsupportedSyscallErr{SyscallNum: a7})
case riscv.SysFutex: // futex - not supported, for now
revertWithCode(0xf001ca11, &UnsupportedSyscallErr{SyscallNum: a7})
case riscv.SysNanosleep: // nanosleep - not supported, for now
revertWithCode(0xf001ca11, &UnsupportedSyscallErr{SyscallNum: a7})
default:
revertWithCode(0xf001ca11, fmt.Errorf("unrecognized system call: %d", a7))
}
Expand Down
41 changes: 41 additions & 0 deletions rvgo/riscv/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package riscv

const (
SysExit = 93
SysExitGroup = 94
SysBrk = 214
SysMmap = 222
SysRead = 63
SysWrite = 64
SysFcntl = 25
SysOpenat = 56
SysSchedGetaffinity = 123
SysSchedYield = 124
SysClockGettime = 113
SysRtSigprocmask = 135
SysSigaltstack = 132
SysGettid = 178
SysRtSigaction = 134
SysClone = 220
SysGetrlimit = 163
SysMadvise = 233
SysEpollCreate1 = 20
SysEpollCtl = 21
SysPipe2 = 59
SysReadlinnkat = 78
SysNewfstatat = 79
SysNewuname = 160
SysMunmap = 215
SysGetRandom = 278
SysPrlimit64 = 261
SysFutex = 422
SysNanosleep = 101

FdStdin = 0
FdStdout = 1
FdStderr = 2
FdHintRead = 3
FdHintWrite = 4
FdPreimageRead = 5
FdPreimageWrite = 6
)
Loading

0 comments on commit 8d7f523

Please sign in to comment.