sealevel: adds memcpy and memmove syscalls & tests
This commit is contained in:
parent
f4de38e3fe
commit
d7a6ac2b7f
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -2,4 +2,6 @@ package sealevel
|
|||
|
||||
const (
|
||||
CUSyscallBaseCost = 100
|
||||
CUMemOpBaseCost = 10
|
||||
CuCpiBytesPerUnit = 250
|
||||
)
|
||||
|
|
|
@ -82,6 +82,197 @@ func TestInterpreter_Noop(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
// The TestInterpreter_Memcpy_Strings_Match tests that memcpy works as expected
|
||||
// by running an SBPF program that uses the memcpy syscall to copy a string
|
||||
// literal to a stack buffer, before testing for equality using memcmp.
|
||||
// The expected result is that the two match.
|
||||
func TestInterpreter_Memcpy_Strings_Match(t *testing.T) {
|
||||
loader, err := loader.NewLoaderFromBytes(fixtures.Load(t, "sbpf", "memcpy_and_memmove_test_matched.so"))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, loader)
|
||||
|
||||
program, err := loader.Load()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, program)
|
||||
|
||||
require.NoError(t, program.Verify())
|
||||
|
||||
syscalls := sbpf.NewSyscallRegistry()
|
||||
syscalls.Register("sol_log_", SyscallLog)
|
||||
syscalls.Register("log_64", SyscallLog64)
|
||||
syscalls.Register("my_copy", SyscallMemcpy)
|
||||
|
||||
var log LogRecorder
|
||||
|
||||
interpreter := sbpf.NewInterpreter(program, &sbpf.VMOpts{
|
||||
HeapSize: 32 * 1024,
|
||||
Input: nil,
|
||||
MaxCU: 10000,
|
||||
Syscalls: syscalls,
|
||||
Context: &Execution{Log: &log},
|
||||
})
|
||||
require.NotNil(t, interpreter)
|
||||
|
||||
err = interpreter.Run()
|
||||
assert.Equal(t, log.Logs, []string{
|
||||
"Program log: Strings matched after copy.",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// The TestInterpreter_Memcpy_Do_Not_Match tests that memcpy works as expected
|
||||
// by running an SBPF program that uses the memcpy syscall to copy a string
|
||||
// literal to a stack buffer, with the destination then modified before testing
|
||||
// for equality using memcmp. The expected result is that the two do NOT match,
|
||||
// because of the modification before comparison.
|
||||
func TestInterpreter_Memcpy_Do_Not_Match(t *testing.T) {
|
||||
loader, err := loader.NewLoaderFromBytes(fixtures.Load(t, "sbpf", "memcpy_and_memmove_test_not_matched.so"))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, loader)
|
||||
|
||||
program, err := loader.Load()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, program)
|
||||
|
||||
require.NoError(t, program.Verify())
|
||||
|
||||
syscalls := sbpf.NewSyscallRegistry()
|
||||
syscalls.Register("sol_log_", SyscallLog)
|
||||
syscalls.Register("log_64", SyscallLog64)
|
||||
syscalls.Register("my_copy", SyscallMemcpy)
|
||||
|
||||
var log LogRecorder
|
||||
|
||||
interpreter := sbpf.NewInterpreter(program, &sbpf.VMOpts{
|
||||
HeapSize: 32 * 1024,
|
||||
Input: nil,
|
||||
MaxCU: 10000,
|
||||
Syscalls: syscalls,
|
||||
Context: &Execution{Log: &log},
|
||||
})
|
||||
require.NotNil(t, interpreter)
|
||||
|
||||
err = interpreter.Run()
|
||||
assert.Equal(t, log.Logs, []string{
|
||||
"Program log: Strings did not match after copy.",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// The TestInterpreter_Memmove_Strings_Match tests that memove works as expected
|
||||
// by running an SBPF program that uses the memcpy syscall to copy a string
|
||||
// literal to a stack buffer, before testing for equality using memcmp.
|
||||
// The expected result is that the two match.
|
||||
func TestInterpreter_Memmove_Strings_Match(t *testing.T) {
|
||||
loader, err := loader.NewLoaderFromBytes(fixtures.Load(t, "sbpf", "memcpy_and_memmove_test_matched.so"))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, loader)
|
||||
|
||||
program, err := loader.Load()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, program)
|
||||
|
||||
require.NoError(t, program.Verify())
|
||||
|
||||
syscalls := sbpf.NewSyscallRegistry()
|
||||
syscalls.Register("sol_log_", SyscallLog)
|
||||
syscalls.Register("log_64", SyscallLog64)
|
||||
syscalls.Register("my_copy", SyscallMemmove)
|
||||
|
||||
var log LogRecorder
|
||||
|
||||
interpreter := sbpf.NewInterpreter(program, &sbpf.VMOpts{
|
||||
HeapSize: 32 * 1024,
|
||||
Input: nil,
|
||||
MaxCU: 10000,
|
||||
Syscalls: syscalls,
|
||||
Context: &Execution{Log: &log},
|
||||
})
|
||||
require.NotNil(t, interpreter)
|
||||
|
||||
err = interpreter.Run()
|
||||
assert.Equal(t, log.Logs, []string{
|
||||
"Program log: Strings matched after copy.",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// The TestInterpreter_Memmove_Do_Not_Match function tests that memmove works
|
||||
// as expected by running an SBPF program that uses the memcpy syscall to
|
||||
// copy a string literal to a stack buffer, with the destination then
|
||||
// modified before testing for equality using memcmp. The expected result is
|
||||
// that the two do NOT match, because of the modification before comparison.
|
||||
func TestInterpreter_Memmove_Do_Not_Match(t *testing.T) {
|
||||
loader, err := loader.NewLoaderFromBytes(fixtures.Load(t, "sbpf", "memcpy_and_memmove_test_not_matched.so"))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, loader)
|
||||
|
||||
program, err := loader.Load()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, program)
|
||||
|
||||
require.NoError(t, program.Verify())
|
||||
|
||||
syscalls := sbpf.NewSyscallRegistry()
|
||||
syscalls.Register("sol_log_", SyscallLog)
|
||||
syscalls.Register("log_64", SyscallLog64)
|
||||
syscalls.Register("my_copy", SyscallMemmove)
|
||||
|
||||
var log LogRecorder
|
||||
|
||||
interpreter := sbpf.NewInterpreter(program, &sbpf.VMOpts{
|
||||
HeapSize: 32 * 1024,
|
||||
Input: nil,
|
||||
MaxCU: 10000,
|
||||
Syscalls: syscalls,
|
||||
Context: &Execution{Log: &log},
|
||||
})
|
||||
require.NotNil(t, interpreter)
|
||||
|
||||
err = interpreter.Run()
|
||||
assert.Equal(t, log.Logs, []string{
|
||||
"Program log: Strings did not match after copy.",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// The TestInterpreter_Memcpy_Overlapping function tests that memcpy works
|
||||
// as expected by attempting to do a copy involving two overlapping buffers.
|
||||
// The expected result is an "Overlapping copy" error being returned.
|
||||
func TestInterpreter_Memcpy_Overlapping(t *testing.T) {
|
||||
loader, err := loader.NewLoaderFromBytes(fixtures.Load(t, "sbpf", "memcpy_overlapping.so"))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, loader)
|
||||
|
||||
program, err := loader.Load()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, program)
|
||||
|
||||
require.NoError(t, program.Verify())
|
||||
|
||||
syscalls := sbpf.NewSyscallRegistry()
|
||||
syscalls.Register("sol_log_", SyscallLog)
|
||||
syscalls.Register("log_64", SyscallLog64)
|
||||
syscalls.Register("my_copy", SyscallMemcpy)
|
||||
|
||||
var log LogRecorder
|
||||
|
||||
interpreter := sbpf.NewInterpreter(program, &sbpf.VMOpts{
|
||||
HeapSize: 32 * 1024,
|
||||
Input: nil,
|
||||
MaxCU: 10000,
|
||||
Syscalls: syscalls,
|
||||
Context: &Execution{Log: &log},
|
||||
})
|
||||
require.NotNil(t, interpreter)
|
||||
|
||||
err = interpreter.Run()
|
||||
|
||||
// expecting an error here because the src and dst are overlapping in the
|
||||
// program being run.
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
type executeCase struct {
|
||||
Name string
|
||||
Program string
|
||||
|
|
|
@ -12,8 +12,10 @@ func Syscalls() sbpf.SyscallRegistry {
|
|||
reg.Register("abort", SyscallAbort)
|
||||
reg.Register("sol_log_", SyscallLog)
|
||||
reg.Register("sol_log_64_", SyscallLog64)
|
||||
reg.Register("sol_log_compute_uits_", SyscallLogCUs)
|
||||
reg.Register("sol_log_compute_units_", SyscallLogCUs)
|
||||
reg.Register("sol_log_pubkey", SyscallLogPubkey)
|
||||
reg.Register("sol_memcpy_", SyscallMemcpy)
|
||||
reg.Register("sol_memmove_", SyscallMemmove)
|
||||
return reg
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
package sealevel
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"go.firedancer.io/radiance/pkg/sbpf"
|
||||
"go.firedancer.io/radiance/pkg/sbpf/cu"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCopyOverlapping = errors.New("Overlapping copy")
|
||||
)
|
||||
|
||||
func MemOpConsume(cuIn int, n uint64) int {
|
||||
perBytesCost := n / CuCpiBytesPerUnit
|
||||
return cu.ConsumeLowerBound(cuIn, CUMemOpBaseCost, int(perBytesCost))
|
||||
}
|
||||
|
||||
func isNonOverlapping(src, dst, n uint64) bool {
|
||||
if src > dst {
|
||||
return src-dst >= n
|
||||
} else {
|
||||
return dst-src >= n
|
||||
}
|
||||
}
|
||||
|
||||
func memmoveImplInternal(vm sbpf.VM, dst, src, n uint64) (err error) {
|
||||
srcBuf := make([]byte, n)
|
||||
err = vm.Read(src, srcBuf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = vm.Write(dst, srcBuf)
|
||||
return
|
||||
}
|
||||
|
||||
// SyscallMemcpyImpl is the implementation of the memcpy (sol_memcpy_) syscall.
|
||||
// Overlapping src and dst for a given n bytes to be copied results in an error being returned.
|
||||
func SyscallMemcpyImpl(vm sbpf.VM, dst, src, n uint64, cuIn int) (r0 uint64, cuOut int, err error) {
|
||||
cuOut = MemOpConsume(cuIn, n)
|
||||
if cuOut < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// memcpy when src and dst are overlapping results in undefined behaviour,
|
||||
// hence check if there is an overlap and return early with an error if so.
|
||||
if !isNonOverlapping(src, dst, n) {
|
||||
return r0, cuOut, ErrCopyOverlapping
|
||||
}
|
||||
|
||||
err = memmoveImplInternal(vm, dst, src, n)
|
||||
return
|
||||
}
|
||||
|
||||
var SyscallMemcpy = sbpf.SyscallFunc3(SyscallMemcpyImpl)
|
||||
|
||||
// SyscallMemmoveImpl is the implementation for the memmove (sol_memmove_) syscall.
|
||||
func SyscallMemmoveImpl(vm sbpf.VM, dst, src, n uint64, cuIn int) (r0 uint64, cuOut int, err error) {
|
||||
cuOut = MemOpConsume(cuIn, n)
|
||||
if cuOut < 0 {
|
||||
return
|
||||
}
|
||||
err = memmoveImplInternal(vm, dst, src, n)
|
||||
return
|
||||
}
|
||||
|
||||
var SyscallMemmove = sbpf.SyscallFunc3(SyscallMemmoveImpl)
|
Loading…
Reference in New Issue