diff --git a/rvgo/fast/vm.go b/rvgo/fast/vm.go index 64a009ee..e8cc4813 100644 --- a/rvgo/fast/vm.go +++ b/rvgo/fast/vm.go @@ -204,39 +204,6 @@ func (inst *InstrumentedState) riscvStep() (outErr error) { return out } - storeMemUnaligned := func(addr U64, size U64, value U256, proofIndexL uint8, proofIndexR uint8, verifyL bool, verifyR bool) { - if size > 32 { - revertWithCode(riscv.ErrStoreExceeds32Bytes, fmt.Errorf("cannot store more than 32 bytes: %d", size)) - } - var bytez [32]byte - binary.LittleEndian.PutUint64(bytez[:8], value[0]) - binary.LittleEndian.PutUint64(bytez[8:16], value[1]) - binary.LittleEndian.PutUint64(bytez[16:24], value[2]) - binary.LittleEndian.PutUint64(bytez[24:], value[3]) - - leftAddr := addr &^ 31 - if verifyL { - inst.trackMemAccess(leftAddr, proofIndexL) - } - inst.verifyMemChange(leftAddr, proofIndexL) - if (addr+size-1)&^31 == addr&^31 { // if aligned - s.Memory.SetUnaligned(addr, bytez[:size]) - return - } - if proofIndexR == 0xff { - revertWithCode(riscv.ErrUnexpectedRProofStoreUnaligned, fmt.Errorf("unexpected need for right-side proof %d in storeMemUnaligned", proofIndexR)) - } - // if not aligned - rightAddr := leftAddr + 32 - leftSize := rightAddr - addr - s.Memory.SetUnaligned(addr, bytez[:leftSize]) - if verifyR { - inst.trackMemAccess(rightAddr, proofIndexR) - } - inst.verifyMemChange(rightAddr, proofIndexR) - s.Memory.SetUnaligned(rightAddr, bytez[leftSize:size]) - } - storeMem := func(addr U64, size U64, value U64, proofIndexL uint8, proofIndexR uint8, verifyL bool, verifyR bool) { if size > 8 { revertWithCode(riscv.ErrStoreExceeds8Bytes, fmt.Errorf("cannot store more than 8 bytes: %d", size)) @@ -368,22 +335,10 @@ func (inst *InstrumentedState) riscvStep() (outErr error) { sysCall := func() { a7 := getRegister(toU64(17)) switch a7 { - 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 riscv.SysExitGroup: // exit-group a0 := getRegister(toU64(10)) setExitCode(uint8(a0)) setExited() - 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 riscv.SysMmap: // mmap // A0 = addr (hint) addr := getRegister(toU64(10)) @@ -480,90 +435,6 @@ func (inst *InstrumentedState) riscvStep() (outErr error) { } setRegister(toU64(10), n) setRegister(toU64(11), errCode) - case riscv.SysFcntl: // fcntl - file descriptor manipulation / info lookup - fd := getRegister(toU64(10)) // A0 = fd - cmd := getRegister(toU64(11)) // A1 = cmd - var out U64 - var errCode U64 - switch cmd { - case 0x1: // F_GETFD: get file descriptor flags - switch fd { - case 0: // stdin - out = toU64(0) // no flag set - case 1: // stdout - out = toU64(0) // no flag set - case 2: // stderr - out = toU64(0) // no flag set - case 3: // hint-read - out = toU64(0) // no flag set - case 4: // hint-write - out = toU64(0) // no flag set - case 5: // pre-image read - out = toU64(0) // no flag set - case 6: // pre-image write - out = toU64(0) // no flag set - default: - out = u64Mask() - errCode = toU64(0x4d) //EBADF - } - case 0x3: // F_GETFL: get file descriptor flags - switch fd { - case 0: // stdin - out = toU64(0) // O_RDONLY - case 1: // stdout - out = toU64(1) // O_WRONLY - case 2: // stderr - out = toU64(1) // O_WRONLY - case 3: // hint-read - out = toU64(0) // O_RDONLY - case 4: // hint-write - out = toU64(1) // O_WRONLY - case 5: // pre-image read - out = toU64(0) // O_RDONLY - case 6: // pre-image write - out = toU64(1) // O_WRONLY - default: - out = u64Mask() - errCode = toU64(0x4d) // EBADF - } - default: // no other commands: don't allow changing flags, duplicating FDs, etc. - out = u64Mask() - errCode = toU64(0x16) // EINVAL (cmd not recognized by this kernel) - } - setRegister(toU64(10), out) - setRegister(toU64(11), errCode) // EBADF - 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 riscv.SysClockGettime: // clock_gettime - addr := getRegister(toU64(11)) // addr of timespec struct - // write 1337s + 42ns as time - value := or(shortToU256(1337), shl(shortToU256(64), toU256(42))) - storeMemUnaligned(addr, toU64(16), value, 1, 2, true, true) - setRegister(toU64(10), toU64(0)) - setRegister(toU64(11), toU64(0)) - case riscv.SysClone: // clone - not supported - setRegister(toU64(10), toU64(1)) - setRegister(toU64(11), toU64(0)) - case riscv.SysGetrlimit: // getrlimit - res := getRegister(toU64(10)) - addr := getRegister(toU64(11)) - switch res { - case 0x7: // RLIMIT_NOFILE - // first 8 bytes: soft limit. 1024 file handles max open - // second 8 bytes: hard limit - storeMemUnaligned(addr, toU64(16), or(shortToU256(1024), shl(toU256(64), shortToU256(1024))), 1, 2, true, true) - setRegister(toU64(10), toU64(0)) - setRegister(toU64(11), toU64(0)) - default: - revertWithCode(riscv.ErrUnrecognizedResource, &UnrecognizedResourceErr{Resource: res}) - } - case riscv.SysPrlimit64: // prlimit64 -- unsupported, we have getrlimit, is prlimit64 even called? - revertWithCode(riscv.ErrInvalidSyscall, &UnsupportedSyscallErr{SyscallNum: a7}) - case riscv.SysFutex: // futex - not supported, for now - revertWithCode(riscv.ErrInvalidSyscall, &UnsupportedSyscallErr{SyscallNum: a7}) - case riscv.SysNanosleep: // nanosleep - not supported, for now - revertWithCode(riscv.ErrInvalidSyscall, &UnsupportedSyscallErr{SyscallNum: a7}) default: // Ignore(no-op) unsupported system calls setRegister(toU64(10), toU64(0)) diff --git a/rvgo/riscv/constants.go b/rvgo/riscv/constants.go index 9360e868..4074cb01 100644 --- a/rvgo/riscv/constants.go +++ b/rvgo/riscv/constants.go @@ -1,35 +1,10 @@ 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 diff --git a/rvgo/slow/vm.go b/rvgo/slow/vm.go index 90ba0f17..298147d0 100644 --- a/rvgo/slow/vm.go +++ b/rvgo/slow/vm.go @@ -568,22 +568,10 @@ func Step(calldata []byte, po PreimageOracle) (stateHash common.Hash, outErr err sysCall := func() { a7 := getRegister(toU64(17)) switch a7.val() { - case riscv.SysExit: // exit the calling thread. No multi-thread support yet, so just exit. - a0 := getRegister(toU64(10)) - setExitCode(uint8(a0.val())) - setExited() - // program stops here, no need to change registers. case riscv.SysExitGroup: // exit-group a0 := getRegister(toU64(10)) setExitCode(uint8(a0.val())) setExited() - 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 riscv.SysMmap: // mmap // A0 = addr (hint) addr := getRegister(toU64(10)) @@ -660,90 +648,6 @@ func Step(calldata []byte, po PreimageOracle) (stateHash common.Hash, outErr err } setRegister(toU64(10), n) setRegister(toU64(11), errCode) - case riscv.SysFcntl: // fcntl - file descriptor manipulation / info lookup - fd := getRegister(toU64(10)) // A0 = fd - cmd := getRegister(toU64(11)) // A1 = cmd - var out U64 - var errCode U64 - switch cmd.val() { - case 0x1: // F_GETFD: get file descriptor flags - switch fd.val() { - case 0: // stdin - out = toU64(0) // no flag set - case 1: // stdout - out = toU64(0) // no flag set - case 2: // stderr - out = toU64(0) // no flag set - case 3: // hint-read - out = toU64(0) // no flag set - case 4: // hint-write - out = toU64(0) // no flag set - case 5: // pre-image read - out = toU64(0) // no flag set - case 6: // pre-image write - out = toU64(0) // no flag set - default: - out = u64Mask() - errCode = toU64(0x4d) //EBADF - } - case 0x3: // F_GETFL: get file descriptor flags - switch fd.val() { - case 0: // stdin - out = toU64(0) // O_RDONLY - case 1: // stdout - out = toU64(1) // O_WRONLY - case 2: // stderr - out = toU64(1) // O_WRONLY - case 3: // hint-read - out = toU64(0) // O_RDONLY - case 4: // hint-write - out = toU64(1) // O_WRONLY - case 5: // pre-image read - out = toU64(0) // O_RDONLY - case 6: // pre-image write - out = toU64(1) // O_WRONLY - default: - out = u64Mask() - errCode = toU64(0x4d) // EBADF - } - default: // no other commands: don't allow changing flags, duplicating FDs, etc. - out = u64Mask() - errCode = toU64(0x16) // EINVAL (cmd not recognized by this kernel) - } - setRegister(toU64(10), out) - setRegister(toU64(11), errCode) // EBADF - 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 riscv.SysClockGettime: // clock_gettime - addr := getRegister(toU64(11)) // addr of timespec struct - // write 1337s + 42ns as time - value := or(shortToU256(1337), shl(shortToU256(64), toU256(42))) - storeMemUnaligned(addr, toU64(16), value, 1, 2) - setRegister(toU64(10), toU64(0)) - setRegister(toU64(11), toU64(0)) - case riscv.SysClone: // clone - not supported - setRegister(toU64(10), toU64(1)) - setRegister(toU64(11), toU64(0)) - case riscv.SysGetrlimit: // getrlimit - res := getRegister(toU64(10)) - addr := getRegister(toU64(11)) - switch res.val() { - case 0x7: // RLIMIT_NOFILE - // first 8 bytes: soft limit. 1024 file handles max open - // second 8 bytes: hard limit - storeMemUnaligned(addr, toU64(16), or(shortToU256(1024), shl(toU256(64), shortToU256(1024))), 1, 2) - setRegister(toU64(10), toU64(0)) - setRegister(toU64(11), toU64(0)) - default: - revertWithCode(riscv.ErrUnrecognizedResource, &UnrecognizedResourceErr{Resource: res}) - } - case riscv.SysPrlimit64: // prlimit64 -- unsupported, we have getrlimit, is prlimit64 even called? - revertWithCode(riscv.ErrInvalidSyscall, &UnsupportedSyscallErr{SyscallNum: a7}) - case riscv.SysFutex: // futex - not supported, for now - revertWithCode(riscv.ErrInvalidSyscall, &UnsupportedSyscallErr{SyscallNum: a7}) - case riscv.SysNanosleep: // nanosleep - not supported, for now - revertWithCode(riscv.ErrInvalidSyscall, &UnsupportedSyscallErr{SyscallNum: a7}) default: // Ignore(no-op) unsupported system calls setRegister(toU64(10), toU64(0)) diff --git a/rvgo/test/syscall_test.go b/rvgo/test/syscall_test.go index 93362977..7b697c76 100644 --- a/rvgo/test/syscall_test.go +++ b/rvgo/test/syscall_test.go @@ -74,11 +74,7 @@ func errCodeToByte32(errCode uint64) []byte { func TestStateSyscallUnsupported(t *testing.T) { contracts := testContracts(t) addrs := testAddrs - syscalls := []int{ - riscv.SysPrlimit64, - riscv.SysFutex, - riscv.SysNanosleep, - } + syscalls := []int{ 0xFF } for _, syscall := range syscalls { t.Run(fmt.Sprintf("sys_%d", syscall), func(t *testing.T) { @@ -282,7 +278,7 @@ func FuzzStateSyscallExit(f *testing.F) { contracts := testContracts(f) addrs := testAddrs - syscalls := []int{riscv.SysExit, riscv.SysExitGroup} + syscalls := []int{riscv.SysExitGroup} testExit := func(t *testing.T, syscall int, exitCode uint8, pc uint64, step uint64) { pc = pc & 0xFF_FF_FF_FF_FF_FF_FF_FC // align PC @@ -326,48 +322,6 @@ func FuzzStateSyscallExit(f *testing.F) { }) } -func FuzzStateSyscallBrk(f *testing.F) { - contracts := testContracts(f) - addrs := testAddrs - - f.Fuzz(func(t *testing.T, pc, step uint64) { - pc = pc & 0xFF_FF_FF_FF_FF_FF_FF_FC // align PC - state := &fast.VMState{ - PC: pc, - Heap: 0, - ExitCode: 0, - Exited: false, - Memory: fast.NewMemory(), - LoadReservation: 0, - Registers: [32]uint64{17: riscv.SysBrk}, - Step: step, - } - state.Memory.SetUnaligned(pc, syscallInsn) - preStateRoot := state.Memory.MerkleRoot() - expectedRegisters := state.Registers - expectedRegisters[10] = 1 << 30 - expectedRegisters[11] = 0 - - fastState := fast.NewInstrumentedState(state, nil, os.Stdout, os.Stderr) - stepWitness, err := fastState.Step(true) - require.NoError(t, err) - require.False(t, stepWitness.HasPreimage()) - - require.Equal(t, pc+4, state.PC) // PC must advance - require.Equal(t, uint64(0), state.Heap) - require.Equal(t, uint64(0), state.LoadReservation) - require.Equal(t, uint8(0), state.ExitCode) // ExitCode must be set - require.Equal(t, false, state.Exited) // Must not be exited - require.Equal(t, preStateRoot, state.Memory.MerkleRoot()) - require.Equal(t, step+1, state.Step) // Step must advance - require.Equal(t, expectedRegisters, state.Registers) - - fastPost := state.EncodeWitness() - runEVM(t, contracts, addrs, stepWitness, fastPost, nil) - runSlow(t, stepWitness, fastPost, nil, nil) - }) -} - func FuzzStateSyscallMmap(f *testing.F) { contracts := testContracts(f) addrs := testAddrs @@ -422,361 +376,6 @@ func FuzzStateSyscallMmap(f *testing.F) { }) } -func FuzzStateSyscallFcntl(f *testing.F) { - contracts := testContracts(f) - addrs := testAddrs - - testFcntl := func(t *testing.T, fd, cmd, pc, step, out, errCode uint64) { - pc = pc & 0xFF_FF_FF_FF_FF_FF_FF_FC // align PC - state := &fast.VMState{ - PC: pc, - Heap: 0, - ExitCode: 0, - Exited: false, - Memory: fast.NewMemory(), - LoadReservation: 0, - Registers: [32]uint64{17: riscv.SysFcntl, 10: fd, 11: cmd}, - Step: step, - } - state.Memory.SetUnaligned(pc, syscallInsn) - preStateRoot := state.Memory.MerkleRoot() - expectedRegisters := state.Registers - expectedRegisters[10] = out - expectedRegisters[11] = errCode - - fastState := fast.NewInstrumentedState(state, nil, os.Stdout, os.Stderr) - stepWitness, err := fastState.Step(true) - require.NoError(t, err) - require.False(t, stepWitness.HasPreimage()) - - require.Equal(t, pc+4, state.PC) // PC must advance - require.Equal(t, uint64(0), state.Heap) - require.Equal(t, uint64(0), state.LoadReservation) - require.Equal(t, uint8(0), state.ExitCode) // ExitCode must be set - require.Equal(t, false, state.Exited) // Must not be exited - require.Equal(t, preStateRoot, state.Memory.MerkleRoot()) - require.Equal(t, step+1, state.Step) // Step must advance - require.Equal(t, expectedRegisters, state.Registers) - - fastPost := state.EncodeWitness() - runEVM(t, contracts, addrs, stepWitness, fastPost, nil) - runSlow(t, stepWitness, fastPost, nil, nil) - } - - f.Fuzz(func(t *testing.T, fd uint64, cmd uint64, pc uint64, step uint64) { - // Test F_GETFL for O_RDONLY fds - for _, fd := range []uint64{0, 3, 5} { - testFcntl(t, fd, 3, pc, step, 0, 0) - } - // Test F_GETFL for O_WRONLY fds - for _, fd := range []uint64{1, 2, 4, 6} { - testFcntl(t, fd, 3, pc, step, 1, 0) - } - // Test F_GETFL for unsupported fds - // Add 7 to fd to ensure fd > 6 - testFcntl(t, fd+7, 3, pc, step, 0xFFFF_FFFF_FFFF_FFFF, 0x4d) - - // Test F_GETFD - for _, fd := range []uint64{0, 1, 2, 3, 4, 5, 6} { - testFcntl(t, fd, 1, pc, step, 0, 0) - } - - // Test F_GETFD for unsupported fds - // Add 7 to fd to ensure fd > 6 - testFcntl(t, fd+7, 1, pc, step, 0xFFFF_FFFF_FFFF_FFFF, 0x4d) - - // Test other commands - if cmd == 3 || cmd == 1 { - // Set arbitrary commands if cmd is F_GETFL - cmd = 4 - } - testFcntl(t, fd, cmd, pc, step, 0xFFFF_FFFF_FFFF_FFFF, 0x16) - }) -} - -func FuzzStateSyscallOpenat(f *testing.F) { - contracts := testContracts(f) - addrs := testAddrs - - f.Fuzz(func(t *testing.T, pc, step uint64) { - pc = pc & 0xFF_FF_FF_FF_FF_FF_FF_FC // align PC - state := &fast.VMState{ - PC: pc, - Heap: 0, - ExitCode: 0, - Exited: false, - Memory: fast.NewMemory(), - LoadReservation: 0, - Registers: [32]uint64{17: riscv.SysOpenat}, - Step: step, - } - state.Memory.SetUnaligned(pc, syscallInsn) - preStateRoot := state.Memory.MerkleRoot() - expectedRegisters := state.Registers - expectedRegisters[10] = 0xFFFF_FFFF_FFFF_FFFF - expectedRegisters[11] = 0xd - - fastState := fast.NewInstrumentedState(state, nil, os.Stdout, os.Stderr) - stepWitness, err := fastState.Step(true) - require.NoError(t, err) - require.False(t, stepWitness.HasPreimage()) - - require.Equal(t, pc+4, state.PC) // PC must advance - require.Equal(t, uint64(0), state.Heap) - require.Equal(t, uint64(0), state.LoadReservation) - require.Equal(t, uint8(0), state.ExitCode) // ExitCode must be set - require.Equal(t, false, state.Exited) // Must not be exited - require.Equal(t, preStateRoot, state.Memory.MerkleRoot()) - require.Equal(t, step+1, state.Step) // Step must advance - require.Equal(t, expectedRegisters, state.Registers) - - fastPost := state.EncodeWitness() - runEVM(t, contracts, addrs, stepWitness, fastPost, nil) - runSlow(t, stepWitness, fastPost, nil, nil) - }) -} - -func FuzzStateSyscallClockGettime(f *testing.F) { - contracts := testContracts(f) - addrs := testAddrs - - f.Fuzz(func(t *testing.T, addr, pc, step uint64) { - pc = pc & 0xFF_FF_FF_FF_FF_FF_FF_FC // align PC - state := &fast.VMState{ - PC: pc, - Heap: 0, - ExitCode: 0, - Exited: false, - Memory: fast.NewMemory(), - LoadReservation: 0, - Registers: [32]uint64{17: riscv.SysClockGettime, 11: addr}, - Step: step, - } - state.Memory.SetUnaligned(pc, syscallInsn) - expectedRegisters := state.Registers - expectedRegisters[11] = 0 - - fastState := fast.NewInstrumentedState(state, nil, os.Stdout, os.Stderr) - stepWitness, err := fastState.Step(true) - require.NoError(t, err) - require.False(t, stepWitness.HasPreimage()) - - postMemory := fast.NewMemory() - postMemory.SetUnaligned(pc, syscallInsn) - var bytes [8]byte - binary.LittleEndian.PutUint64(bytes[:], 1337) - postMemory.SetUnaligned(addr, bytes[:]) - postMemory.SetUnaligned(addr+8, []byte{42, 0, 0, 0, 0, 0, 0, 0}) - - require.Equal(t, pc+4, state.PC) // PC must advance - require.Equal(t, uint64(0), state.Heap) - require.Equal(t, uint64(0), state.LoadReservation) - require.Equal(t, uint8(0), state.ExitCode) // ExitCode must be set - require.Equal(t, false, state.Exited) // Must not be exited - require.Equal(t, state.Memory.MerkleRoot(), postMemory.MerkleRoot()) - require.Equal(t, step+1, state.Step) // Step must advance - require.Equal(t, expectedRegisters, state.Registers) - - fastPost := state.EncodeWitness() - runEVM(t, contracts, addrs, stepWitness, fastPost, nil) - runSlow(t, stepWitness, fastPost, nil, nil) - }) -} - -func FuzzStateSyscallClone(f *testing.F) { - contracts := testContracts(f) - addrs := testAddrs - - f.Fuzz(func(t *testing.T, pc, step uint64) { - pc = pc & 0xFF_FF_FF_FF_FF_FF_FF_FC // align PC - state := &fast.VMState{ - PC: pc, - Heap: 0, - ExitCode: 0, - Exited: false, - Memory: fast.NewMemory(), - LoadReservation: 0, - Registers: [32]uint64{17: riscv.SysClone}, - Step: step, - } - state.Memory.SetUnaligned(pc, syscallInsn) - preStateRoot := state.Memory.MerkleRoot() - expectedRegisters := state.Registers - expectedRegisters[10] = 1 - expectedRegisters[11] = 0 - - fastState := fast.NewInstrumentedState(state, nil, os.Stdout, os.Stderr) - stepWitness, err := fastState.Step(true) - require.NoError(t, err) - require.False(t, stepWitness.HasPreimage()) - - require.Equal(t, pc+4, state.PC) // PC must advance - require.Equal(t, uint64(0), state.Heap) - require.Equal(t, uint64(0), state.LoadReservation) - require.Equal(t, uint8(0), state.ExitCode) // ExitCode must be set - require.Equal(t, false, state.Exited) // Must not be exited - require.Equal(t, preStateRoot, state.Memory.MerkleRoot()) - require.Equal(t, step+1, state.Step) // Step must advance - require.Equal(t, expectedRegisters, state.Registers) - - fastPost := state.EncodeWitness() - runEVM(t, contracts, addrs, stepWitness, fastPost, nil) - runSlow(t, stepWitness, fastPost, nil, nil) - }) -} - -func FuzzStateSyscallGetrlimit(f *testing.F) { - contracts := testContracts(f) - addrs := testAddrs - - testGetrlimit := func(t *testing.T, addr, pc, step uint64) { - pc = pc & 0xFF_FF_FF_FF_FF_FF_FF_FC // align PC - state := &fast.VMState{ - PC: pc, - Heap: 0, - ExitCode: 0, - Exited: false, - Memory: fast.NewMemory(), - LoadReservation: 0, - Registers: [32]uint64{17: riscv.SysGetrlimit, 10: 7, 11: addr}, - Step: step, - } - state.Memory.SetUnaligned(pc, syscallInsn) - expectedRegisters := state.Registers - expectedRegisters[10] = 0 - expectedRegisters[11] = 0 - - fastState := fast.NewInstrumentedState(state, nil, os.Stdout, os.Stderr) - stepWitness, err := fastState.Step(true) - require.NoError(t, err) - require.False(t, stepWitness.HasPreimage()) - - postMemory := fast.NewMemory() - postMemory.SetUnaligned(pc, syscallInsn) - var bytes [8]byte - binary.LittleEndian.PutUint64(bytes[:], 1024) - postMemory.SetUnaligned(addr, bytes[:]) - postMemory.SetUnaligned(addr+8, bytes[:]) - - require.Equal(t, pc+4, state.PC) // PC must advance - require.Equal(t, uint64(0), state.Heap) - require.Equal(t, uint64(0), state.LoadReservation) - require.Equal(t, uint8(0), state.ExitCode) // ExitCode must be set - require.Equal(t, false, state.Exited) // Must not be exited - require.Equal(t, state.Memory.MerkleRoot(), postMemory.MerkleRoot()) - require.Equal(t, step+1, state.Step) // Step must advance - require.Equal(t, expectedRegisters, state.Registers) - - fastPost := state.EncodeWitness() - runEVM(t, contracts, addrs, stepWitness, fastPost, nil) - runSlow(t, stepWitness, fastPost, nil, nil) - } - - testGetrlimitErr := func(t *testing.T, res, addr, pc, step uint64) { - pc = pc & 0xFF_FF_FF_FF_FF_FF_FF_FC // align PC - addr = addr &^ 31 - state := &fast.VMState{ - PC: pc, - Heap: 0, - ExitCode: 0, - Exited: false, - Memory: fast.NewMemory(), - LoadReservation: 0, - Registers: [32]uint64{17: riscv.SysGetrlimit, 10: res, 11: addr}, - Step: 0, - } - state.Memory.SetUnaligned(pc, syscallInsn) - - fastState := fast.NewInstrumentedState(state, nil, os.Stdout, os.Stderr) - stepWitness, err := fastState.Step(true) - var fastSyscallErr *fast.UnrecognizedResourceErr - require.ErrorAs(t, err, &fastSyscallErr) - - runEVM(t, contracts, addrs, stepWitness, nil, errCodeToByte32(riscv.ErrUnrecognizedResource)) - - var slowSyscallErr *slow.UnrecognizedResourceErr - runSlow(t, stepWitness, nil, nil, &slowSyscallErr) - } - - f.Fuzz(func(t *testing.T, res, addr, pc, step uint64) { - // Test RLIMIT_NOFILE - testGetrlimit(t, addr, pc, step) - - // Test other resources - if res == 7 { - // Set arbitrary resource if res is RLIMIT_NOFILE - res = 8 - } - testGetrlimitErr(t, res, addr, pc, step) - }) -} - -func FuzzStateSyscallNoop(f *testing.F) { - contracts := testContracts(f) - addrs := testAddrs - - syscalls := []int{ - riscv.SysSchedGetaffinity, - riscv.SysSchedYield, - riscv.SysRtSigprocmask, - riscv.SysSigaltstack, - riscv.SysGettid, - riscv.SysRtSigaction, - riscv.SysMadvise, - riscv.SysEpollCreate1, - riscv.SysEpollCtl, - riscv.SysPipe2, - riscv.SysReadlinnkat, - riscv.SysNewfstatat, - riscv.SysNewuname, - riscv.SysMunmap, - riscv.SysGetRandom, - } - - testNoop := func(t *testing.T, syscall int, arg uint64, pc uint64, step uint64) { - pc = pc & 0xFF_FF_FF_FF_FF_FF_FF_FC // align PC - state := &fast.VMState{ - PC: pc, - Heap: 0, - ExitCode: 0, - Exited: false, - Memory: fast.NewMemory(), - LoadReservation: 0, - Registers: [32]uint64{17: uint64(syscall), 10: arg}, - Step: step, - } - state.Memory.SetUnaligned(pc, syscallInsn) - preStateRoot := state.Memory.MerkleRoot() - expectedRegisters := state.Registers - expectedRegisters[10] = 0 - expectedRegisters[11] = 0 - - fastState := fast.NewInstrumentedState(state, nil, os.Stdout, os.Stderr) - stepWitness, err := fastState.Step(true) - require.NoError(t, err) - require.False(t, stepWitness.HasPreimage()) - - require.Equal(t, pc+4, state.PC) // PC must advance - require.Equal(t, uint64(0), state.Heap) - require.Equal(t, uint64(0), state.LoadReservation) - require.Equal(t, uint8(0), state.ExitCode) - require.Equal(t, false, state.Exited) - require.Equal(t, preStateRoot, state.Memory.MerkleRoot()) - require.Equal(t, step+1, state.Step) // Step must advance - require.Equal(t, expectedRegisters, state.Registers) - - fastPost := state.EncodeWitness() - runEVM(t, contracts, addrs, stepWitness, fastPost, nil) - runSlow(t, stepWitness, fastPost, nil, nil) - } - - f.Fuzz(func(t *testing.T, arg uint64, pc uint64, step uint64) { - for _, syscall := range syscalls { - testNoop(t, syscall, arg, pc, step) - } - }) -} - func FuzzStateSyscallRead(f *testing.F) { contracts := testContracts(f) addrs := testAddrs diff --git a/rvsol/src/RISCV.sol b/rvsol/src/RISCV.sol index 4467c0f0..7959ecde 100644 --- a/rvsol/src/RISCV.sol +++ b/rvsol/src/RISCV.sol @@ -869,29 +869,12 @@ contract RISCV { function sysCall(localContext_) { let a7 := getRegister(toU64(17)) switch a7 - case 93 { - // exit the calling thread. No multi-thread support yet, so just exit. - let a0 := getRegister(toU64(10)) - setExitCode(and(a0, 0xff)) - setExited() - // program stops here, no need to change registers. - } case 94 { // exit-group let a0 := getRegister(toU64(10)) setExitCode(and(a0, 0xff)) setExited() } - case 214 { - // 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 - let 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 // A0 = addr (hint) @@ -987,141 +970,6 @@ contract RISCV { setRegister(toU64(10), n) setRegister(toU64(11), errCode) } - case 25 { - // fcntl - file descriptor manipulation / info lookup - let fd := getRegister(toU64(10)) // A0 = fd - let cmd := getRegister(toU64(11)) // A1 = cmd - let out := 0 - let errCode := 0 - switch cmd - case 0x1 { - // F_GETFD: get file descriptor flags - switch fd - case 0 { - // stdin - out := toU64(0) // no flag set - } - case 1 { - // stdout - out := toU64(0) // no flag set - } - case 2 { - // stderr - out := toU64(0) // no flag set - } - case 3 { - // hint-read - out := toU64(0) // no flag set - } - case 4 { - // hint-write - out := toU64(0) // no flag set - } - case 5 { - // pre-image read - out := toU64(0) // no flag set - } - case 6 { - // pre-image write - out := toU64(0) // no flag set - } - default { - out := u64Mask() - errCode := toU64(0x4d) //EBADF - } - } - case 0x3 { - // F_GETFL: get file descriptor flags - switch fd - case 0 { - // stdin - out := toU64(0) // O_RDONLY - } - case 1 { - // stdout - out := toU64(1) // O_WRONLY - } - case 2 { - // stderr - out := toU64(1) // O_WRONLY - } - case 3 { - // hint-read - out := toU64(0) // O_RDONLY - } - case 4 { - // hint-write - out := toU64(1) // O_WRONLY - } - case 5 { - // pre-image read - out := toU64(0) // O_RDONLY - } - case 6 { - // pre-image write - out := toU64(1) // O_WRONLY - } - default { - out := u64Mask() - errCode := toU64(0x4d) // EBADF - } - } - default { - // no other commands: don't allow changing flags, duplicating FDs, etc. - out := u64Mask() - errCode := toU64(0x16) // EINVAL (cmd not recognized by this kernel) - } - 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 - setRegister(toU64(10), u64Mask()) - setRegister(toU64(11), toU64(0xd)) // EACCES - no access allowed - } - case 113 { - // clock_gettime - let addr := getRegister(toU64(11)) // addr of timespec struct - // write 1337s + 42ns as time - let value := or(shortToU256(1337), shl(shortToU256(64), toU256(42))) - storeMemUnaligned(addr, toU64(16), value, 1, 2) - setRegister(toU64(10), toU64(0)) - setRegister(toU64(11), toU64(0)) - } - case 220 { - // clone - not supported - setRegister(toU64(10), toU64(1)) - setRegister(toU64(11), toU64(0)) - } - case 163 { - // getrlimit - let res := getRegister(toU64(10)) - let addr := getRegister(toU64(11)) - switch res - case 0x7 { - // RLIMIT_NOFILE - // first 8 bytes: soft limit. 1024 file handles max open - // second 8 bytes: hard limit - storeMemUnaligned( - addr, toU64(16), or(shortToU256(1024), shl(toU256(64), shortToU256(1024))), 1, 2 - ) - setRegister(toU64(10), toU64(0)) - setRegister(toU64(11), toU64(0)) - } - default { revertWithCode(0xf0012) } // unrecognized resource limit lookup - } - case 261 { - // prlimit64 -- unsupported, we have getrlimit, is prlimit64 even called? - revertWithCode(0xf001ca11) // unsupported system call - } - case 422 { - // futex - not supported, for now - revertWithCode(0xf001ca11) // unsupported system call - } - case 101 { - // nanosleep - not supported, for now - revertWithCode(0xf001ca11) // unsupported system call - } default { // Ignore(no-op) unsupported system calls setRegister(toU64(10), toU64(0)) diff --git a/rvsol/test/RISCV.t.sol b/rvsol/test/RISCV.t.sol index 27a75662..5c58a553 100644 --- a/rvsol/test/RISCV.t.sol +++ b/rvsol/test/RISCV.t.sol @@ -1596,19 +1596,20 @@ contract RISCV_Test is CommonTest { } function test_ecall_succeeds() public { - // some syscalls are not supported - // lets choose unsupported syscall clone just for testing functionality uint16 imm = 0x0; uint32 insn = encodeIType(0x73, 0, 0, 0, imm); // ecall uint64 pc = 0x1337; (State memory state, bytes memory proof) = constructRISCVState(pc, insn); - state.registers[17] = 220; // syscall number of clone + state.registers[10] = 1; + state.registers[17] = 94; // syscall number of exit-group bytes memory encodedState = encodeState(state); State memory expect; expect.memRoot = state.memRoot; expect.pc = state.pc + 4; expect.step = state.step + 1; + expect.exited = true; + expect.exitCode = 1; expect.registers[10] = 1; expect.registers[11] = 0; expect.registers[17] = state.registers[17]; @@ -2378,18 +2379,6 @@ contract RISCV_Test is CommonTest { riscv.step(encodedState, proof, 0); } - function test_unrecognized_resource_limit() public { - uint16 imm = 0x0; - uint32 insn = encodeIType(0x73, 0, 0, 0, imm); // ecall - (State memory state, bytes memory proof) = constructRISCVState(0, insn); - state.registers[17] = 163; - state.registers[10] = 0; - bytes memory encodedState = encodeState(state); - - vm.expectRevert(hex"00000000000000000000000000000000000000000000000000000000000f0012"); - riscv.step(encodedState, proof, 0); - } - function test_invalid_amo_size() public { uint32 insn; uint8 funct3 = 0x1; // invalid amo size