aboutsummaryrefslogtreecommitdiffhomepage
path: root/test/internal/sandbox
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-10-04 01:05:10 +0900
committerOphestra <cat@gensokyo.uk>2026-10-06 19:41:06 +0900
commita7383510fb05abc98b992240cb76ad4b6c598956 (patch)
tree90881e9d6952e050128f1378d66452c7d733d012 /test/internal/sandbox
parentb452e1047ccd3e1826da16416430e6638270155b (diff)
test/sandbox: migrate tests
This significantly improves performance, removing overhead of nix, python, and virtualisation. Running this in an unprivileged container required patching the kernel, but since special runner setup was already needed, that was an acceptable tradeoff. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'test/internal/sandbox')
-rw-r--r--test/internal/sandbox/assert.go247
-rw-r--r--test/internal/sandbox/assert_test.go34
-rw-r--r--test/internal/sandbox/seccomp.go46
3 files changed, 0 insertions, 327 deletions
diff --git a/test/internal/sandbox/assert.go b/test/internal/sandbox/assert.go
deleted file mode 100644
index 1194befb..00000000
--- a/test/internal/sandbox/assert.go
+++ /dev/null
@@ -1,247 +0,0 @@
-//go:build testtool
-
-// Package sandbox provides utilities for checking sandbox outcome.
-//
-// This package must never be used outside integration tests, there is a much
-// better native implementation of mountinfo in the public sandbox/vfs package.
-// Files in this package are excluded by the build system to prevent accidental
-// misuse.
-package sandbox
-
-import (
- "encoding/json"
- "errors"
- "io/fs"
- "log"
- "net"
- "os"
- "path/filepath"
- "syscall"
-
- "hakurei.app/test/internal/mountinfo"
- "hakurei.app/test/internal/testsuite"
-)
-
-var (
- assert = log.New(os.Stderr, "sandbox: ", 0)
- printfFunc = assert.Printf
- fatalfFunc = assert.Fatalf
-)
-
-func printf(format string, v ...any) { printfFunc(format, v...) }
-func fatalf(format string, v ...any) { fatalfFunc(format, v...) }
-
-type TestCase struct {
- Env []string `json:"env"`
- FS *testsuite.FS `json:"fs"`
- Mount []*mountinfo.Entry `json:"mount"`
- Seccomp bool `json:"seccomp"`
-
- TrySocket string `json:"try_socket,omitempty"`
- SocketAbstract bool `json:"socket_abstract,omitempty"`
- SocketPathname bool `json:"socket_pathname,omitempty"`
-}
-
-type T struct {
- FS fs.FS
-
- MountsPath string
-}
-
-func (t *T) MustCheckFile(wantFilePath string) {
- var want *TestCase
- mustDecode(wantFilePath, &want)
- t.MustCheck(want)
-}
-
-func mustAbs(s string) string {
- if !filepath.IsAbs(s) {
- fatalf("[FAIL] %q is not absolute", s)
- panic("unreachable")
- }
- return s
-}
-
-func (t *T) MustCheck(want *TestCase) {
- checkWritableDirPaths := []string{
- "/dev/shm",
- "/tmp",
- os.Getenv("XDG_RUNTIME_DIR"),
- }
- for _, a := range checkWritableDirPaths {
- pathname := filepath.Join(mustAbs(a), ".hakurei-check")
- if err := os.WriteFile(pathname, make([]byte, 1<<8), 0600); err != nil {
- fatalf("[FAIL] %s", err)
- } else if err = os.Remove(pathname); err != nil {
- fatalf("[FAIL] %s", err)
- } else {
- printf("[ OK ] %s is writable", a)
- }
- }
-
- if want.Env != nil {
- var (
- fail bool
- i int
- got string
- )
- for i, got = range os.Environ() {
- if i == len(want.Env) {
- fatalf("got more than %d environment variables", len(want.Env))
- }
- if got != want.Env[i] {
- fail = true
- printf("[FAIL] %s", got)
- } else {
- printf("[ OK ] %s", got)
- }
- }
-
- i++
- if i != len(want.Env) {
- fatalf("got %d environment variables, want %d", i, len(want.Env))
- }
-
- if fail {
- fatalf("[FAIL] some environment variables did not match")
- }
- } else {
- printf("[SKIP] skipping environ check")
- }
-
- if want.FS != nil && t.FS != nil {
- if err := want.FS.Compare(printfFunc, ".", t.FS); err != nil {
- fatalf("%v", err)
- }
- } else {
- printf("[SKIP] skipping fs check")
- }
-
- if want.Mount != nil {
- var fail bool
- m := mustParseMountinfo(t.MountsPath)
- i := 0
- var ent mountinfo.Entry
- for m.Next() {
- m.Copy(&ent)
-
- if i == len(want.Mount) {
- fatalf("got more than %d entries", i)
- }
- if !ent.EqualWithIgnore(want.Mount[i], "//ignore") {
- fail = true
- printf("[FAIL] %s", &ent)
- } else {
- printf("[ OK ] %s", &ent)
- }
-
- i++
- }
- if err := m.Err(); err != nil {
- fatalf("%v", err)
- }
-
- if i != len(want.Mount) {
- fatalf("got %d entries, want %d", i, len(want.Mount))
- }
-
- if fail {
- fatalf("[FAIL] some mount points did not match")
- }
- } else {
- printf("[SKIP] skipping mounts check")
- }
-
- if want.Seccomp {
- if trySyscalls() != nil {
- os.Exit(1)
- }
- } else {
- printf("[SKIP] skipping seccomp check")
- }
-
- if want.TrySocket != "" {
- abstractConn, abstractErr := net.Dial("unix", "@"+want.TrySocket)
- pathnameConn, pathnameErr := net.Dial("unix", want.TrySocket)
- ok := true
-
- if abstractErr == nil {
- if err := abstractConn.Close(); err != nil {
- ok = false
- log.Printf("Close: %v", err)
- }
- }
- if pathnameErr == nil {
- if err := pathnameConn.Close(); err != nil {
- ok = false
- log.Printf("Close: %v", err)
- }
- }
-
- abstractWantErr := error(syscall.EPERM)
- pathnameWantErr := error(syscall.ENOENT)
- if want.SocketAbstract {
- abstractWantErr = nil
- }
- if want.SocketPathname {
- pathnameWantErr = nil
- }
-
- if !errors.Is(abstractErr, abstractWantErr) {
- ok = false
- log.Printf("abstractErr: %v, want %v", abstractErr, abstractWantErr)
- }
- if !errors.Is(pathnameErr, pathnameWantErr) {
- ok = false
- log.Printf("pathnameErr: %v, want %v", pathnameErr, pathnameWantErr)
- }
-
- if !ok {
- os.Exit(1)
- }
- }
-}
-
-func MustCheckFilter(pid int, want string) {
- err := testsuite.CheckFilter(pid, 0, want)
- if err == nil {
- return
- }
-
- e, ok := errors.AsType[*os.SyscallError](err)
- if !ok {
- fatalf("%s", err)
- }
- switch e.Syscall {
- case "PTRACE_ATTACH":
- fatalf("cannot attach to process %d: %v", pid, err)
- case "PTRACE_SECCOMP_GET_FILTER":
- if errors.Is(e.Err, syscall.ENOENT) {
- fatalf("seccomp filter not installed for process %d", pid)
- }
- fatalf("cannot get filter: %v", err)
- default:
- fatalf("cannot check filter: %v", err)
- }
-
- *(*int)(nil) = 0 // not reached
-}
-
-func mustDecode(wantFilePath string, v any) {
- if f, err := os.Open(wantFilePath); err != nil {
- fatalf("cannot open %q: %v", wantFilePath, err)
- } else if err = json.NewDecoder(f).Decode(v); err != nil {
- fatalf("cannot decode %q: %v", wantFilePath, err)
- } else if err = f.Close(); err != nil {
- fatalf("cannot close %q: %v", wantFilePath, err)
- }
-}
-
-func mustParseMountinfo(name string) *mountinfo.Iter {
- m, err := mountinfo.Open(name)
- if err != nil {
- fatalf("%v", err)
- panic("unreachable")
- }
- return m
-}
diff --git a/test/internal/sandbox/assert_test.go b/test/internal/sandbox/assert_test.go
deleted file mode 100644
index 012ae23d..00000000
--- a/test/internal/sandbox/assert_test.go
+++ /dev/null
@@ -1,34 +0,0 @@
-//go:build testtool
-
-package sandbox
-
-import (
- "encoding/json"
- "os"
- "path/filepath"
- "testing"
-)
-
-type F func(format string, v ...any)
-
-func SwapPrint(f F) (old F) { old = printfFunc; printfFunc = f; return }
-func SwapFatal(f F) (old F) { old = fatalfFunc; fatalfFunc = f; return }
-
-func MustWantFile(t *testing.T, v any) (wantFile string) {
- wantFile = filepath.Join(t.TempDir(), "want.json")
- if f, err := os.OpenFile(wantFile, os.O_CREATE|os.O_WRONLY, 0400); err != nil {
- t.Fatalf("cannot create %q: %v", wantFile, err)
- } else if err = json.NewEncoder(f).Encode(v); err != nil {
- t.Fatalf("cannot encode to %q: %v", wantFile, err)
- } else if err = f.Close(); err != nil {
- t.Fatalf("cannot close %q: %v", wantFile, err)
- }
-
- t.Cleanup(func() {
- if err := os.Remove(wantFile); err != nil {
- t.Fatalf("cannot remove %q: %v", wantFile, err)
- }
- })
-
- return
-}
diff --git a/test/internal/sandbox/seccomp.go b/test/internal/sandbox/seccomp.go
deleted file mode 100644
index 1d8cd457..00000000
--- a/test/internal/sandbox/seccomp.go
+++ /dev/null
@@ -1,46 +0,0 @@
-//go:build testtool
-
-package sandbox
-
-import (
- "os"
- "syscall"
-)
-
-/*
-#include <sys/quota.h>
-*/
-import "C"
-
-const NULL = 0
-
-func trySyscalls() error {
- testCases := []struct {
- name string
- errno syscall.Errno
-
- trap, a1, a2, a3, a4, a5, a6 uintptr
- }{
- {"syslog", syscall.EPERM, syscall.SYS_SYSLOG, 0, NULL, NULL, NULL, NULL, NULL},
- {"acct", syscall.EPERM, syscall.SYS_ACCT, 0, NULL, NULL, NULL, NULL, NULL},
- {"quotactl", syscall.EPERM, syscall.SYS_QUOTACTL, C.Q_GETQUOTA, NULL, uintptr(os.Getuid()), NULL, NULL, NULL},
- {"add_key", syscall.EPERM, syscall.SYS_ADD_KEY, NULL, NULL, NULL, NULL, NULL, NULL},
- {"keyctl", syscall.EPERM, syscall.SYS_KEYCTL, NULL, NULL, NULL, NULL, NULL, NULL},
- {"request_key", syscall.EPERM, syscall.SYS_REQUEST_KEY, NULL, NULL, NULL, NULL, NULL, NULL},
- {"move_pages", syscall.EPERM, syscall.SYS_MOVE_PAGES, uintptr(os.Getpid()), NULL, NULL, NULL, NULL, NULL},
- {"mbind", syscall.EPERM, syscall.SYS_MBIND, NULL, NULL, NULL, NULL, NULL, NULL},
- {"get_mempolicy", syscall.EPERM, syscall.SYS_GET_MEMPOLICY, NULL, NULL, NULL, NULL, NULL, NULL},
- {"set_mempolicy", syscall.EPERM, syscall.SYS_SET_MEMPOLICY, NULL, NULL, NULL, NULL, NULL, NULL},
- {"migrate_pages", syscall.EPERM, syscall.SYS_MIGRATE_PAGES, NULL, NULL, NULL, NULL, NULL, NULL},
- }
-
- for _, tc := range testCases {
- if _, _, errno := syscall.Syscall6(tc.trap, tc.a1, tc.a2, tc.a3, tc.a4, tc.a5, tc.a6); errno != tc.errno {
- printf("[FAIL] %s: %v, want %v", tc.name, errno, tc.errno)
- return errno
- }
- printf("[ OK ] %s: %v", tc.name, tc.errno)
- }
-
- return nil
-}