sealevel: adds memcpy and memmove syscalls & tests

This commit is contained in:
smcio 2023-07-04 15:35:01 +01:00
parent f4de38e3fe
commit d7a6ac2b7f
7 changed files with 263 additions and 1 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -2,4 +2,6 @@ package sealevel
const (
CUSyscallBaseCost = 100
CUMemOpBaseCost = 10
CuCpiBytesPerUnit = 250
)

View File

@ -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

View File

@ -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
}

View File

@ -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)