diff options
Diffstat (limited to 'test/internal')
| -rw-r--r-- | test/internal/sandbox/assert.go | 40 | ||||
| -rw-r--r-- | test/internal/sandbox/ptrace.go | 87 | ||||
| -rw-r--r-- | test/internal/sandbox/seccomp.go | 2 | ||||
| -rw-r--r-- | test/internal/testsuite/ptrace.go | 136 | ||||
| -rw-r--r-- | test/internal/testsuite/ptrace_test.go | 13 | ||||
| -rw-r--r-- | test/internal/testsuite/testsuite.go | 4 | ||||
| -rw-r--r-- | test/internal/testsuite/testsuite_root.go | 14 |
7 files changed, 170 insertions, 126 deletions
diff --git a/test/internal/sandbox/assert.go b/test/internal/sandbox/assert.go index 2125419b..e2bcc8ae 100644 --- a/test/internal/sandbox/assert.go +++ b/test/internal/sandbox/assert.go @@ -9,8 +9,6 @@ package sandbox import ( - "crypto/sha512" - "encoding/hex" "encoding/json" "errors" "io/fs" @@ -21,6 +19,7 @@ import ( "syscall" "hakurei.app/test/internal/mountinfo" + "hakurei.app/test/internal/testsuite" ) var ( @@ -204,20 +203,20 @@ func (t *T) MustCheck(want *TestCase) { } func MustCheckFilter(pid int, want string) { - err := CheckFilter(pid, want) + err := testsuite.CheckFilter(pid, 0, want) if err == nil { return } - perr, ok := errors.AsType[*ptraceError](err) + e, ok := errors.AsType[*os.SyscallError](err) if !ok { fatalf("%s", err) } - switch perr.op { + switch e.Syscall { case "PTRACE_ATTACH": fatalf("cannot attach to process %d: %v", pid, err) case "PTRACE_SECCOMP_GET_FILTER": - if perr.errno == syscall.ENOENT { + if errors.Is(e.Err, syscall.ENOENT) { fatalf("seccomp filter not installed for process %d", pid) } fatalf("cannot get filter: %v", err) @@ -228,35 +227,6 @@ func MustCheckFilter(pid int, want string) { *(*int)(nil) = 0 // not reached } -func CheckFilter(pid int, want string) error { - if err := ptraceAttach(pid); err != nil { - return err - } - defer func() { - if err := ptraceDetach(pid); err != nil { - printf("cannot detach from process %d: %v", pid, err) - } - }() - - h := sha512.New() - - if buf, err := getFilter[[8]byte](pid, 0); err != nil { - return err - } else { - for _, b := range buf { - h.Write(b[:]) - } - } - - if got := hex.EncodeToString(h.Sum(nil)); got != want { - printf("[FAIL] %s", got) - return syscall.ENOTRECOVERABLE - } else { - printf("[ OK ] %s", got) - return nil - } -} - func mustDecode(wantFilePath string, v any) { if f, err := os.Open(wantFilePath); err != nil { fatalf("cannot open %q: %v", wantFilePath, err) diff --git a/test/internal/sandbox/ptrace.go b/test/internal/sandbox/ptrace.go deleted file mode 100644 index 8272ff5b..00000000 --- a/test/internal/sandbox/ptrace.go +++ /dev/null @@ -1,87 +0,0 @@ -//go:build testtool - -package sandbox - -import ( - "errors" - "fmt" - "syscall" - "unsafe" -) - -const ( - NULL = 0 - - PTRACE_ATTACH = 16 - PTRACE_DETACH = 17 - PTRACE_SECCOMP_GET_FILTER = 0x420c -) - -type ptraceError struct { - op string - errno syscall.Errno -} - -func (p *ptraceError) Error() string { return fmt.Sprintf("%s: %v", p.op, p.errno) } - -func (p *ptraceError) Unwrap() error { - if p.errno == 0 { - return nil - } - return p.errno -} - -func ptrace(op uintptr, pid, addr int, data unsafe.Pointer) (r uintptr, errno syscall.Errno) { - r, _, errno = syscall.Syscall6(syscall.SYS_PTRACE, op, uintptr(pid), uintptr(addr), uintptr(data), NULL, NULL) - return -} - -func ptraceAttach(pid int) error { - if _, errno := ptrace(PTRACE_ATTACH, pid, 0, nil); errno != 0 { - return &ptraceError{"PTRACE_ATTACH", errno} - } - - var status syscall.WaitStatus - for { - if _, err := syscall.Wait4(pid, &status, syscall.WALL, nil); err != nil { - if errors.Is(err, syscall.EINTR) { - continue - } - fatalf("cannot waitpid: %v", err) - } - break - } - - return nil -} - -func ptraceDetach(pid int) error { - if _, errno := ptrace(PTRACE_DETACH, pid, 0, nil); errno != 0 { - return &ptraceError{"PTRACE_DETACH", errno} - } - return nil -} - -type sockFilter struct { /* Filter block */ - code uint16 /* Actual filter code */ - jt uint8 /* Jump true */ - jf uint8 /* Jump false */ - k uint32 /* Generic multiuse field */ -} - -func getFilter[T comparable](pid, index int) ([]T, error) { - if s := unsafe.Sizeof(*new(T)); s != 8 { - panic(fmt.Sprintf("invalid filter block size %d", s)) - } - - var buf []T - if n, errno := ptrace(PTRACE_SECCOMP_GET_FILTER, pid, index, nil); errno != 0 { - return nil, &ptraceError{"PTRACE_SECCOMP_GET_FILTER", errno} - } else { - buf = make([]T, n) - } - if _, errno := ptrace(PTRACE_SECCOMP_GET_FILTER, pid, index, unsafe.Pointer(&buf[0])); errno != 0 { - return nil, &ptraceError{"PTRACE_SECCOMP_GET_FILTER", errno} - } - return buf, nil -} diff --git a/test/internal/sandbox/seccomp.go b/test/internal/sandbox/seccomp.go index 7df781d2..1d8cd457 100644 --- a/test/internal/sandbox/seccomp.go +++ b/test/internal/sandbox/seccomp.go @@ -12,6 +12,8 @@ import ( */ import "C" +const NULL = 0 + func trySyscalls() error { testCases := []struct { name string diff --git a/test/internal/testsuite/ptrace.go b/test/internal/testsuite/ptrace.go new file mode 100644 index 00000000..4fcf1508 --- /dev/null +++ b/test/internal/testsuite/ptrace.go @@ -0,0 +1,136 @@ +package testsuite + +import ( + "crypto/sha512" + "encoding/hex" + "errors" + "fmt" + "os" + "syscall" + "unsafe" +) + +const ( + // _PTRACE_ATTACH attaches to the process specified in pid. + _PTRACE_ATTACH = 16 + // _PTRACE_DETACH restarts the stopped tracee as for PTRACE_CONT, but first + // detaches from it. + _PTRACE_DETACH = 17 + + // _PTRACE_SECCOMP_GET_FILTER allows the tracer to dump the tracee's classic + // BPF filters. + _PTRACE_SECCOMP_GET_FILTER = 0x420c +) + +// ptrace wraps the ptrace syscall. +func ptrace( + op uintptr, + pid, addr int, + data unsafe.Pointer, +) (r uintptr, errno syscall.Errno) { + r, _, errno = syscall.Syscall6( + syscall.SYS_PTRACE, + op, + uintptr(pid), + uintptr(addr), + uintptr(data), + 0, 0, + ) + return +} + +// ptraceAttach attaches to the process referred to by pid. +func ptraceAttach(pid int) error { + if _, errno := ptrace(_PTRACE_ATTACH, pid, 0, nil); errno != 0 { + return os.NewSyscallError("PTRACE_ATTACH", errno) + } + + var status syscall.WaitStatus + for { + if _, err := syscall.Wait4( + pid, + &status, + syscall.WALL, + nil, + ); err != nil { + if errors.Is(err, syscall.EINTR) { + continue + } + return os.NewSyscallError("wait4", err) + } + break + } + + return nil +} + +// ptraceDetach detaches from the attached process referred to by pid. +func ptraceDetach(pid int) error { + if _, errno := ptrace(_PTRACE_DETACH, pid, 0, nil); errno != 0 { + return os.NewSyscallError("PTRACE_DETACH", errno) + } + return nil +} + +// getFilter dumps the specified tracee's cBPF filter at the specified index +// and returns the resulting payload. T must be eight bytes long and must not +// contain pointers. +func getFilter(pid, index int) ([]syscall.SockFilter, error) { + var buf []syscall.SockFilter + if n, errno := ptrace( + _PTRACE_SECCOMP_GET_FILTER, + pid, index, nil, + ); errno != 0 { + return nil, os.NewSyscallError("PTRACE_SECCOMP_GET_FILTER", errno) + } else { + buf = make([]syscall.SockFilter, n) + } + if _, errno := ptrace( + _PTRACE_SECCOMP_GET_FILTER, + pid, index, unsafe.Pointer(&buf[0]), + ); errno != 0 { + return nil, os.NewSyscallError("PTRACE_SECCOMP_GET_FILTER", errno) + } + return buf, nil +} + +// CheckFilter checks the process at pid to have its first filter's contents +// match the sha512 checksum specified in hexadecimal string representation. +func CheckFilter(pid, index int, sum string) (err error) { + if err = ptraceAttach(pid); err != nil { + return + } + defer func() { + if detachErr := ptraceDetach(pid); err == nil { + err = detachErr + } + }() + + var ( + buf []syscall.SockFilter + want []byte + ) + + if want, err = hex.DecodeString(sum); err != nil { + return + } + + h := sha512.New() + if buf, err = getFilter(pid, index); err != nil { + return + } else { + h.Write(unsafe.Slice( + (*byte)(unsafe.Pointer(&buf[0])), + uintptr(len(buf))*unsafe.Sizeof(buf[0]), + )) + } + + if got := h.Sum(nil); string(got) != string(want) { + return fmt.Errorf( + "bad filter\n\t got: %s\n\twant: %s", + hex.EncodeToString(got), + sum, + ) + } + return +} diff --git a/test/internal/testsuite/ptrace_test.go b/test/internal/testsuite/ptrace_test.go new file mode 100644 index 00000000..eaaed131 --- /dev/null +++ b/test/internal/testsuite/ptrace_test.go @@ -0,0 +1,13 @@ +package testsuite + +import ( + "syscall" + "testing" + "unsafe" +) + +func TestBlockSize(t *testing.T) { + if sz := unsafe.Sizeof(syscall.SockFilter{}); sz != 8 { + t.Fatalf("invalid filter block size %d", sz) + } +} diff --git a/test/internal/testsuite/testsuite.go b/test/internal/testsuite/testsuite.go index 9233818d..2173509d 100644 --- a/test/internal/testsuite/testsuite.go +++ b/test/internal/testsuite/testsuite.go @@ -13,10 +13,6 @@ import ( func init() { log.SetFlags(0) log.SetPrefix("testsuite: ") - - if os.Geteuid() != 0 { - log.Fatal("this program must run as root") - } } // ReceiveSignals blocks until a termination signal arrives, and terminates. diff --git a/test/internal/testsuite/testsuite_root.go b/test/internal/testsuite/testsuite_root.go new file mode 100644 index 00000000..830e272c --- /dev/null +++ b/test/internal/testsuite/testsuite_root.go @@ -0,0 +1,14 @@ +//go:build testsuite + +package testsuite + +import ( + "log" + "os" +) + +func init() { + if os.Geteuid() != 0 { + log.Fatal("this program must run as root") + } +} |
