aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--test/internal/sandbox/assert.go40
-rw-r--r--test/internal/sandbox/ptrace.go87
-rw-r--r--test/internal/sandbox/seccomp.go2
-rw-r--r--test/internal/testsuite/ptrace.go136
-rw-r--r--test/internal/testsuite/ptrace_test.go13
-rw-r--r--test/internal/testsuite/testsuite.go4
-rw-r--r--test/internal/testsuite/testsuite_root.go14
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")
+ }
+}