diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-11-12 23:03:22 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-11-12 23:03:22 +0900 |
| commit | b5630f6883c874d458d9ae3c666cfb7ae5e3121d (patch) | |
| tree | e2c488165211b1f68b9eaef8469d92a81ba37f43 /test/sandbox | |
| parent | 17ffdb2dcf3bb5c8edf277a017cecd8e71a2e26b (diff) | |
test: move package sandbox internal
This should never be used outside vm tests.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'test/sandbox')
| -rw-r--r-- | test/sandbox/assert.go | 272 | ||||
| -rw-r--r-- | test/sandbox/assert_test.go | 34 | ||||
| -rw-r--r-- | test/sandbox/fs.go | 102 | ||||
| -rw-r--r-- | test/sandbox/fs_test.go | 80 | ||||
| -rw-r--r-- | test/sandbox/mount.go | 159 | ||||
| -rw-r--r-- | test/sandbox/mount_test.go | 144 | ||||
| -rw-r--r-- | test/sandbox/ptrace.go | 87 | ||||
| -rw-r--r-- | test/sandbox/seccomp.go | 44 | ||||
| -rw-r--r-- | test/sandbox/tool/main.go | 2 | ||||
| -rw-r--r-- | test/sandbox/tool/package.nix | 4 |
10 files changed, 3 insertions, 925 deletions
diff --git a/test/sandbox/assert.go b/test/sandbox/assert.go deleted file mode 100644 index 82c928b0..00000000 --- a/test/sandbox/assert.go +++ /dev/null @@ -1,272 +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 ( - "crypto/sha512" - "encoding/hex" - "encoding/json" - "errors" - "io/fs" - "log" - "net" - "os" - "path" - "syscall" -) - -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 *FS `json:"fs"` - Mount []*MountinfoEntry `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 !path.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 := path.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(".", 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 - for ent := range m.Entries() { - 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 := CheckFilter(pid, want) - if err == nil { - return - } - - var perr *ptraceError - if !errors.As(err, &perr) { - fatalf("%s", err) - } - switch perr.op { - case "PTRACE_ATTACH": - fatalf("cannot attach to process %d: %v", pid, err) - case "PTRACE_SECCOMP_GET_FILTER": - if perr.errno == 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 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) - } 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 { - m := NewMountinfo(name) - if err := m.Parse(); err != nil { - fatalf("%v", err) - panic("unreachable") - } - return m -} diff --git a/test/sandbox/assert_test.go b/test/sandbox/assert_test.go deleted file mode 100644 index efeb7cb3..00000000 --- a/test/sandbox/assert_test.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build testtool - -package sandbox - -import ( - "encoding/json" - "os" - "path" - "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 = path.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/sandbox/fs.go b/test/sandbox/fs.go deleted file mode 100644 index 36d6c6d3..00000000 --- a/test/sandbox/fs.go +++ /dev/null @@ -1,102 +0,0 @@ -//go:build testtool - -package sandbox - -import ( - "errors" - "fmt" - "io/fs" - "path" - "strings" -) - -var ( - ErrFSBadLength = errors.New("bad dir length") - ErrFSBadData = errors.New("data differs") - ErrFSBadMode = errors.New("mode differs") - ErrFSInvalidEnt = errors.New("invalid entry condition") -) - -type FS struct { - Mode fs.FileMode `json:"mode"` - Dir map[string]*FS `json:"dir"` - Data *string `json:"data"` -} - -func printDir(prefix string, dir []fs.DirEntry) { - names := make([]string, len(dir)) - for i, ent := range dir { - name := ent.Name() - if ent.IsDir() { - name += "/" - } - names[i] = fmt.Sprintf("%q", name) - } - printf("[FAIL] d %s: %s", prefix, strings.Join(names, " ")) -} - -func (s *FS) Compare(prefix string, e fs.FS) error { - if s.Data != nil { - if s.Dir != nil { - panic("invalid state") - } - panic("invalid compare call") - } - - if s.Dir == nil { - printf("[ OK ] s %s", prefix) - return nil - } - - var dir []fs.DirEntry - if d, err := fs.ReadDir(e, prefix); err != nil { - return err - } else if len(d) != len(s.Dir) { - printDir(prefix, d) - return ErrFSBadLength - } else { - dir = d - } - - for _, got := range dir { - name := got.Name() - - if want, ok := s.Dir[name]; !ok { - printDir(prefix, dir) - return fs.ErrNotExist - } else if want.Dir != nil && !got.IsDir() { - printDir(prefix, dir) - return ErrFSInvalidEnt - } else { - name = path.Join(prefix, name) - - if fi, err := got.Info(); err != nil { - return err - } else if fi.Mode() != want.Mode { - printf("[FAIL] m %s: %x, want %x", - name, uint32(fi.Mode()), uint32(want.Mode)) - return ErrFSBadMode - } - - if want.Data != nil { - if want.Dir != nil { - panic("invalid state") - } - if v, err := fs.ReadFile(e, name); err != nil { - return err - } else if string(v) != *want.Data { - printf("[FAIL] f %s", name) - printf("got: %s", v) - printf("want: %s", *want.Data) - return ErrFSBadData - } - printf("[ OK ] f %s", name) - } else if err := want.Compare(name, e); err != nil { - return err - } - } - } - printf("[ OK ] d %s", prefix) - - return nil -} diff --git a/test/sandbox/fs_test.go b/test/sandbox/fs_test.go deleted file mode 100644 index 8dd920b0..00000000 --- a/test/sandbox/fs_test.go +++ /dev/null @@ -1,80 +0,0 @@ -//go:build testtool - -package sandbox_test - -import ( - "errors" - "fmt" - "io/fs" - "strings" - "testing" - "testing/fstest" - - "hakurei.app/test/sandbox" -) - -var ( - fsPasswdSample = "u0_a20:x:65534:65534:Hakurei:/var/lib/persist/module/hakurei/u0/a20:/run/current-system/sw/bin/zsh" - fsGroupSample = "hakurei:x:65534:" -) - -func TestCompare(t *testing.T) { - testCases := []struct { - name string - - sample fstest.MapFS - want *sandbox.FS - wantOut string - wantErr error - }{ - {"skip", fstest.MapFS{}, &sandbox.FS{}, "[ OK ] s .\x00", nil}, - {"simple pass", fstest.MapFS{".hakurei": {Mode: 0x800001ed}}, - &sandbox.FS{Dir: map[string]*sandbox.FS{".hakurei": {Mode: 0x800001ed}}}, - "[ OK ] s .hakurei\x00[ OK ] d .\x00", nil}, - {"bad length", fstest.MapFS{".hakurei": {Mode: 0x800001ed}}, - &sandbox.FS{Dir: make(map[string]*sandbox.FS)}, - "[FAIL] d .: \".hakurei/\"\x00", sandbox.ErrFSBadLength}, - {"top level bad mode", fstest.MapFS{".hakurei": {Mode: 0x800001ed}}, - &sandbox.FS{Dir: map[string]*sandbox.FS{".hakurei": {Mode: 0xdeadbeef}}}, - "[FAIL] m .hakurei: 800001ed, want deadbeef\x00", sandbox.ErrFSBadMode}, - {"invalid entry condition", fstest.MapFS{"test": {Data: []byte{'0'}, Mode: 0644}}, - &sandbox.FS{Dir: map[string]*sandbox.FS{"test": {Dir: make(map[string]*sandbox.FS)}}}, - "[FAIL] d .: \"test\"\x00", sandbox.ErrFSInvalidEnt}, - {"nonexistent", fstest.MapFS{"test": {Data: []byte{'0'}, Mode: 0644}}, - &sandbox.FS{Dir: map[string]*sandbox.FS{".test": {}}}, - "[FAIL] d .: \"test\"\x00", fs.ErrNotExist}, - {"file", fstest.MapFS{"etc": {Mode: 0x800001c0}, - "etc/passwd": {Data: []byte(fsPasswdSample), Mode: 0644}, - "etc/group": {Data: []byte(fsGroupSample), Mode: 0644}, - }, &sandbox.FS{Dir: map[string]*sandbox.FS{"etc": {Mode: 0x800001c0, Dir: map[string]*sandbox.FS{ - "passwd": {Mode: 0x1a4, Data: &fsPasswdSample}, - "group": {Mode: 0x1a4, Data: &fsGroupSample}, - }}}}, "[ OK ] f etc/group\x00[ OK ] f etc/passwd\x00[ OK ] d etc\x00[ OK ] d .\x00", nil}, - {"file differ", fstest.MapFS{"etc": {Mode: 0x800001c0}, - "etc/passwd": {Data: []byte(fsPasswdSample), Mode: 0644}, - "etc/group": {Data: []byte(fsGroupSample), Mode: 0644}, - }, &sandbox.FS{Dir: map[string]*sandbox.FS{"etc": {Mode: 0x800001c0, Dir: map[string]*sandbox.FS{ - "passwd": {Mode: 0x1a4, Data: &fsGroupSample}, - "group": {Mode: 0x1a4, Data: &fsGroupSample}, - }}}}, "[ OK ] f etc/group\x00[FAIL] f etc/passwd\x00got: u0_a20:x:65534:65534:Hakurei:/var/lib/persist/module/hakurei/u0/a20:/run/current-system/sw/bin/zsh\x00want: hakurei:x:65534:\x00", sandbox.ErrFSBadData}, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - gotOut := new(strings.Builder) - oldPrint := sandbox.SwapPrint(func(format string, v ...any) { _, _ = fmt.Fprintf(gotOut, format+"\x00", v...) }) - t.Cleanup(func() { sandbox.SwapPrint(oldPrint) }) - - err := tc.want.Compare(".", tc.sample) - if !errors.Is(err, tc.wantErr) { - t.Errorf("Compare: error = %v; wantErr %v", - err, tc.wantErr) - } - - if gotOut.String() != tc.wantOut { - t.Errorf("Compare: output %q; want %q", - gotOut, tc.wantOut) - } - }) - } -} diff --git a/test/sandbox/mount.go b/test/sandbox/mount.go deleted file mode 100644 index 791a68b9..00000000 --- a/test/sandbox/mount.go +++ /dev/null @@ -1,159 +0,0 @@ -//go:build testtool - -package sandbox - -/* -#cgo linux pkg-config: --static mount - -#include <stdlib.h> -#include <stdio.h> -#include <libmount.h> - -const char *HAKUREI_MOUNTINFO_PATH = "/proc/self/mountinfo"; -*/ -import "C" - -import ( - "errors" - "fmt" - "iter" - "runtime" - "sync" - "unsafe" -) - -var ( - ErrMountinfoParse = errors.New("invalid mountinfo records") - ErrMountinfoIter = errors.New("cannot allocate iterator") - ErrMountinfoFault = errors.New("cannot iterate on filesystems") -) - -type ( - Mountinfo struct { - mu sync.RWMutex - p string - err error - - tb *C.struct_libmnt_table - itr *C.struct_libmnt_iter - - fs *C.struct_libmnt_fs - } - - // MountinfoEntry represents deterministic mountinfo parts of a libmnt_fs entry. - MountinfoEntry struct { - // mount ID: a unique ID for the mount (may be reused after umount(2)). - ID int `json:"id"` - // parent ID: the ID of the parent mount (or of self for the root of this mount namespace's mount tree). - Parent int `json:"parent"` - // root: the pathname of the directory in the filesystem which forms the root of this mount. - Root string `json:"root"` - // mount point: the pathname of the mount point relative to the process's root directory. - Target string `json:"target"` - // mount options: per-mount options (see mount(2)). - VfsOptstr string `json:"vfs_optstr"` - // filesystem type: the filesystem type in the form "type[.subtype]". - FsType string `json:"fstype"` - // mount source: filesystem-specific information or "none". - Source string `json:"source"` - // super options: per-superblock options (see mount(2)). - FsOptstr string `json:"fs_optstr"` - } -) - -func (m *Mountinfo) copy(v *MountinfoEntry) { - if m.fs == nil { - panic("invalid entry") - } - v.ID = int(C.mnt_fs_get_id(m.fs)) - v.Parent = int(C.mnt_fs_get_parent_id(m.fs)) - v.Root = C.GoString(C.mnt_fs_get_root(m.fs)) - v.Target = C.GoString(C.mnt_fs_get_target(m.fs)) - v.VfsOptstr = C.GoString(C.mnt_fs_get_vfs_options(m.fs)) - v.FsType = C.GoString(C.mnt_fs_get_fstype(m.fs)) - v.Source = C.GoString(C.mnt_fs_get_source(m.fs)) - v.FsOptstr = C.GoString(C.mnt_fs_get_fs_options(m.fs)) -} - -func NewMountinfo(p string) *Mountinfo { m := new(Mountinfo); m.p = p; return m } - -func (m *Mountinfo) Err() error { m.mu.RLock(); defer m.mu.RUnlock(); return m.err } - -func (m *Mountinfo) Parse() error { - m.mu.Lock() - defer m.mu.Unlock() - - if m.tb != nil { - panic("open called twice") - } - - if m.p == "" { - m.tb = C.mnt_new_table_from_file(C.HAKUREI_MOUNTINFO_PATH) - } else { - name := C.CString(m.p) - m.tb = C.mnt_new_table_from_file(name) - C.free(unsafe.Pointer(name)) - } - if m.tb == nil { - return ErrMountinfoParse - } - m.itr = C.mnt_new_iter(C.MNT_ITER_FORWARD) - if m.itr == nil { - C.mnt_unref_table(m.tb) - return ErrMountinfoIter - } - - runtime.SetFinalizer(m, (*Mountinfo).Unref) - return nil -} - -func (m *Mountinfo) Unref() { - m.mu.Lock() - defer m.mu.Unlock() - - if m.tb == nil { - panic("unref called before parse") - } - - C.mnt_unref_table(m.tb) - C.mnt_free_iter(m.itr) - runtime.SetFinalizer(m, nil) -} - -func (m *Mountinfo) Entries() iter.Seq[*MountinfoEntry] { - return func(yield func(*MountinfoEntry) bool) { - m.mu.Lock() - defer m.mu.Unlock() - - C.mnt_reset_iter(m.itr, -1) - - var rc C.int - ent := new(MountinfoEntry) - for rc = C.mnt_table_next_fs(m.tb, m.itr, &m.fs); rc == 0; rc = C.mnt_table_next_fs(m.tb, m.itr, &m.fs) { - m.copy(ent) - if !yield(ent) { - return - } - } - if rc < 0 { - m.err = ErrMountinfoFault - return - } - } -} - -func (e *MountinfoEntry) EqualWithIgnore(want *MountinfoEntry, ignore string) bool { - return (e.ID == want.ID || want.ID == -1) && - (e.Parent == want.Parent || want.Parent == -1) && - (e.Root == want.Root || want.Root == ignore) && - (e.Target == want.Target || want.Target == ignore) && - (e.VfsOptstr == want.VfsOptstr || want.VfsOptstr == ignore) && - (e.FsType == want.FsType || want.FsType == ignore) && - (e.Source == want.Source || want.Source == ignore) && - (e.FsOptstr == want.FsOptstr || want.FsOptstr == ignore) -} - -func (e *MountinfoEntry) String() string { - return fmt.Sprintf("%d %d %s %s %s %s %s %s", - e.ID, e.Parent, e.Root, e.Target, e.VfsOptstr, e.FsType, e.Source, e.FsOptstr) -} diff --git a/test/sandbox/mount_test.go b/test/sandbox/mount_test.go deleted file mode 100644 index 7d6b0df1..00000000 --- a/test/sandbox/mount_test.go +++ /dev/null @@ -1,144 +0,0 @@ -//go:build testtool - -package sandbox_test - -import ( - "os" - "path" - "testing" - - "hakurei.app/test/sandbox" -) - -func TestMountinfo(t *testing.T) { - testCases := []struct { - name string - - sample string - want []*sandbox.MountinfoEntry - }{ - {"util-linux", `15 20 0:3 / /proc rw,relatime - proc /proc rw -16 20 0:15 / /sys rw,relatime - sysfs /sys rw -17 20 0:5 / /dev rw,relatime - devtmpfs udev rw,size=1983516k,nr_inodes=495879,mode=755 -18 17 0:10 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=000 -19 17 0:16 / /dev/shm rw,relatime - tmpfs tmpfs rw -20 1 8:4 / / rw,noatime - ext3 /dev/sda4 rw,errors=continue,user_xattr,acl,barrier=0,data=ordered -21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755 -22 21 0:18 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd -23 21 0:19 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpuset -24 21 0:20 / /sys/fs/cgroup/ns rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,ns -25 21 0:21 / /sys/fs/cgroup/cpu rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpu -26 21 0:22 / /sys/fs/cgroup/cpuacct rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpuacct -27 21 0:23 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,memory -28 21 0:24 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,devices -29 21 0:25 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,freezer -30 21 0:26 / /sys/fs/cgroup/net_cls rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,net_cls -31 21 0:27 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,blkio -32 16 0:28 / /sys/kernel/security rw,relatime - autofs systemd-1 rw,fd=22,pgrp=1,timeout=300,minproto=5,maxproto=5,direct -33 17 0:29 / /dev/hugepages rw,relatime - autofs systemd-1 rw,fd=23,pgrp=1,timeout=300,minproto=5,maxproto=5,direct -34 16 0:30 / /sys/kernel/debug rw,relatime - autofs systemd-1 rw,fd=24,pgrp=1,timeout=300,minproto=5,maxproto=5,direct -35 15 0:31 / /proc/sys/fs/binfmt_misc rw,relatime - autofs systemd-1 rw,fd=25,pgrp=1,timeout=300,minproto=5,maxproto=5,direct -36 17 0:32 / /dev/mqueue rw,relatime - autofs systemd-1 rw,fd=26,pgrp=1,timeout=300,minproto=5,maxproto=5,direct -37 15 0:14 / /proc/bus/usb rw,relatime - usbfs /proc/bus/usb rw -38 33 0:33 / /dev/hugepages rw,relatime - hugetlbfs hugetlbfs rw -39 36 0:12 / /dev/mqueue rw,relatime - mqueue mqueue rw -40 20 8:6 / /boot rw,noatime - ext3 /dev/sda6 rw,errors=continue,barrier=0,data=ordered -41 20 253:0 / /home/kzak rw,noatime - ext4 /dev/mapper/kzak-home rw,barrier=1,data=ordered -42 35 0:34 / /proc/sys/fs/binfmt_misc rw,relatime - binfmt_misc none rw -43 16 0:35 / /sys/fs/fuse/connections rw,relatime - fusectl fusectl rw -44 41 0:36 / /home/kzak/.gvfs rw,nosuid,nodev,relatime - fuse.gvfs-fuse-daemon gvfs-fuse-daemon rw,user_id=500,group_id=500 -45 20 0:37 / /var/lib/nfs/rpc_pipefs rw,relatime - rpc_pipefs sunrpc rw -47 20 0:38 / /mnt/sounds rw,relatime - cifs //foo.home/bar/ rw,unc=\\foo.home\bar,username=kzak,domain=SRGROUP,uid=0,noforceuid,gid=0,noforcegid,addr=192.168.111.1,posixpaths,serverino,acl,rsize=16384,wsize=57344 -49 20 0:56 / /mnt/test/foobar rw,relatime,nosymfollow shared:323 - tmpfs tmpfs rw`, []*sandbox.MountinfoEntry{ - e(15, 20, "/", "/proc", "rw,relatime", "proc", "/proc", "rw"), - e(16, 20, "/", "/sys", "rw,relatime", "sysfs", "/sys", "rw"), - e(17, 20, "/", "/dev", "rw,relatime", "devtmpfs", "udev", "rw,size=1983516k,nr_inodes=495879,mode=755"), - e(18, 17, "/", "/dev/pts", "rw,relatime", "devpts", "devpts", "rw,gid=5,mode=620,ptmxmode=000"), - e(19, 17, "/", "/dev/shm", "rw,relatime", "tmpfs", "tmpfs", "rw"), - e(20, 1, "/", "/", "rw,noatime", "ext3", "/dev/sda4", "rw,errors=continue,user_xattr,acl,barrier=0,data=ordered"), - e(21, 16, "/", "/sys/fs/cgroup", "rw,nosuid,nodev,noexec,relatime", "tmpfs", "tmpfs", "rw,mode=755"), - e(22, 21, "/", "/sys/fs/cgroup/systemd", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd"), - e(23, 21, "/", "/sys/fs/cgroup/cpuset", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,cpuset"), - e(24, 21, "/", "/sys/fs/cgroup/ns", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,ns"), - e(25, 21, "/", "/sys/fs/cgroup/cpu", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,cpu"), - e(26, 21, "/", "/sys/fs/cgroup/cpuacct", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,cpuacct"), - e(27, 21, "/", "/sys/fs/cgroup/memory", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,memory"), - e(28, 21, "/", "/sys/fs/cgroup/devices", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,devices"), - e(29, 21, "/", "/sys/fs/cgroup/freezer", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,freezer"), - e(30, 21, "/", "/sys/fs/cgroup/net_cls", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,net_cls"), - e(31, 21, "/", "/sys/fs/cgroup/blkio", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,blkio"), - e(32, 16, "/", "/sys/kernel/security", "rw,relatime", "autofs", "systemd-1", "rw,fd=22,pgrp=1,timeout=300,minproto=5,maxproto=5,direct"), - e(33, 17, "/", "/dev/hugepages", "rw,relatime", "autofs", "systemd-1", "rw,fd=23,pgrp=1,timeout=300,minproto=5,maxproto=5,direct"), - e(34, 16, "/", "/sys/kernel/debug", "rw,relatime", "autofs", "systemd-1", "rw,fd=24,pgrp=1,timeout=300,minproto=5,maxproto=5,direct"), - e(35, 15, "/", "/proc/sys/fs/binfmt_misc", "rw,relatime", "autofs", "systemd-1", "rw,fd=25,pgrp=1,timeout=300,minproto=5,maxproto=5,direct"), - e(36, 17, "/", "/dev/mqueue", "rw,relatime", "autofs", "systemd-1", "rw,fd=26,pgrp=1,timeout=300,minproto=5,maxproto=5,direct"), - e(37, 15, "/", "/proc/bus/usb", "rw,relatime", "usbfs", "/proc/bus/usb", "rw"), - e(38, 33, "/", "/dev/hugepages", "rw,relatime", "hugetlbfs", "hugetlbfs", "rw"), - e(39, 36, "/", "/dev/mqueue", "rw,relatime", "mqueue", "mqueue", "rw"), - e(40, 20, "/", "/boot", "rw,noatime", "ext3", "/dev/sda6", "rw,errors=continue,barrier=0,data=ordered"), - e(41, 20, "/", "/home/kzak", "rw,noatime", "ext4", "/dev/mapper/kzak-home", "rw,barrier=1,data=ordered"), - e(42, 35, "/", "/proc/sys/fs/binfmt_misc", "rw,relatime", "binfmt_misc", "none", "rw"), - e(43, 16, "/", "/sys/fs/fuse/connections", "rw,relatime", "fusectl", "fusectl", "rw"), - e(44, 41, "/", "/home/kzak/.gvfs", "rw,nosuid,nodev,relatime", "fuse.gvfs-fuse-daemon", "gvfs-fuse-daemon", "rw,user_id=500,group_id=500"), - e(45, 20, "/", "/var/lib/nfs/rpc_pipefs", "rw,relatime", "rpc_pipefs", "sunrpc", "rw"), - e(47, 20, "/", "/mnt/sounds", "rw,relatime", "cifs", "//foo.home/bar/", "rw,unc=\\\\foo.home\\bar,username=kzak,domain=SRGROUP,uid=0,noforceuid,gid=0,noforcegid,addr=192.168.111.1,posixpaths,serverino,acl,rsize=16384,wsize=57344"), - e(49, 20, "/", "/mnt/test/foobar", "rw,relatime,nosymfollow", "tmpfs", "tmpfs", "rw"), - }}, - } - - for _, tc := range testCases { - name := path.Join(t.TempDir(), "sample") - if err := os.WriteFile(name, []byte(tc.sample), 0400); err != nil { - t.Fatalf("cannot write sample: %v", err) - } - - t.Run(tc.name, func(t *testing.T) { - m := sandbox.NewMountinfo(name) - if err := m.Parse(); err != nil { - t.Fatalf("Parse: error = %v", err) - } - - i := 0 - for ent := range m.Entries() { - if i == len(tc.want) { - t.Errorf("Entries: got more than %d entries", i) - t.FailNow() - } - if !ent.EqualWithIgnore(tc.want[i], "\x00") { - t.Errorf("Entries: entry %d\n got: %#v\nwant: %#v", i, - ent, &tc.want[i]) - t.FailNow() - } else { - t.Logf("%s", ent) - } - - i++ - } - - if err := m.Err(); err != nil { - t.Fatalf("Mountinfo: error = %v", err) - } - - m.Unref() - }) - - if err := os.Remove(name); err != nil { - t.Fatalf("cannot remove %q: %v", name, err) - } - } -} - -func e( - id, parent int, root, target, vfsOptstr string, fsType, source, fsOptstr string, -) *sandbox.MountinfoEntry { - return &sandbox.MountinfoEntry{ - ID: id, - Parent: parent, - Root: root, - Target: target, - VfsOptstr: vfsOptstr, - FsType: fsType, - Source: source, - FsOptstr: fsOptstr, - } -} diff --git a/test/sandbox/ptrace.go b/test/sandbox/ptrace.go deleted file mode 100644 index 8272ff5b..00000000 --- a/test/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/sandbox/seccomp.go b/test/sandbox/seccomp.go deleted file mode 100644 index 7df781d2..00000000 --- a/test/sandbox/seccomp.go +++ /dev/null @@ -1,44 +0,0 @@ -//go:build testtool - -package sandbox - -import ( - "os" - "syscall" -) - -/* -#include <sys/quota.h> -*/ -import "C" - -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 -} diff --git a/test/sandbox/tool/main.go b/test/sandbox/tool/main.go index ef3325de..889142d4 100644 --- a/test/sandbox/tool/main.go +++ b/test/sandbox/tool/main.go @@ -12,7 +12,7 @@ import ( "strings" "syscall" - "hakurei.app/test/sandbox" + "hakurei.app/test/internal/sandbox" ) var ( diff --git a/test/sandbox/tool/package.nix b/test/sandbox/tool/package.nix index 92829b29..13e18272 100644 --- a/test/sandbox/tool/package.nix +++ b/test/sandbox/tool/package.nix @@ -12,7 +12,7 @@ buildGoModule rec { src = builtins.path { name = "${pname}-src"; - path = lib.cleanSource ../.; + path = lib.cleanSource ../../.; filter = path: type: (type == "directory") || (type == "regular" && lib.hasSuffix ".go" path); }; vendorHash = null; @@ -23,7 +23,7 @@ buildGoModule rec { nativeBuildInputs = [ pkg-config ]; preBuild = '' - go mod init hakurei.app/test/sandbox >& /dev/null + go mod init hakurei.app/test >& /dev/null ''; postInstall = '' |
