//go:build testsuite // The sandbox test program runs cmd/hakurei with configurations simulating // several common workloads and inspects the resulting container states. package main import ( "bytes" "context" "encoding/json" "io" "log" "os" "os/exec" "path/filepath" "slices" "strconv" "strings" "sync" "sync/atomic" "syscall" "hakurei.app/check" "hakurei.app/fhs" "hakurei.app/hst" "hakurei.app/internal/store" "hakurei.app/test/internal/testsuite" "hakurei.app/test/sandbox/testdata" ) // mustScanFor continuously scans the proc filesystem and calls f for each entry // visited. func mustScanFor(f func(ps *testsuite.StatScanner) bool) int { var ps testsuite.StatScanner for ps.Scan() { if f(&ps) { break } } if err := ps.Err(); err != nil { log.Fatal(err) } return ps.Stat().PID } // mustStart starts a hakurei container and returns the pid of a process within // the container. This process must be terminated by the caller. func mustStart( ctx context.Context, serial uint64, cred *syscall.Credential, files ...*os.File, ) (pid int, done <-chan error) { _serial := strconv.FormatUint(serial, 10) _, done = testsuite.MustStartWith( ctx, cred, nil, files, "hakurei", "exec", "sleep", "infinity", _serial, ) var stat syscall.Stat_t pid = mustScanFor(func(s *testsuite.StatScanner) bool { select { case err := <-done: if err == nil { log.Fatal("test process terminated unexpectedly") } log.Fatal(err) default: break } if s.Stat().Comm != "sleep" { return false } if args, err := s.Stat().Args(); err != nil { if testsuite.IsNotExist(err) { return false } log.Fatal(err) } else if !slices.Equal(args, []string{ "sleep", "infinity", _serial, }) { return false } if err := s.Stat().Stat(&stat); err != nil { if testsuite.IsNotExist(err) { return false } log.Fatal(err) } id := hst.ToUser[uint32](0, 0) if stat.Uid != id || stat.Gid != id { return false } return true }) return } func main() { go testsuite.ReceiveSignals() // the signal handler does not wait for termination ctx := context.Background() cred := syscall.Credential{Uid: 1000, Gid: 100} if err := os.MkdirAll("/opt/test-helper/bin", 0755); err != nil { log.Fatal(err) } var testToolDone <-chan error { cmd := exec.Command( "go", "build", "-o", "/opt/test-helper/bin", "-tags=tester", "-trimpath", "./test/sandbox/tester", ) cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr testToolDone = testsuite.MustStart(cmd) } var wg sync.WaitGroup defer wg.Wait() var serial atomic.Uint64 newSerial := func() uint64 { serial.Add(1); return serial.Load() } testsuite.MustRun( &cred, nil, "hakurei", "exec", "capsh", "--print", ) wg.Go(func() { defer log.Println("validated capabilities/securebits in user namespace") testsuite.MustRun( &cred, nil, "hakurei", "exec", "capsh", "--has-no-new-privs", ) for _, p := range []byte{'a', 'b', 'i', 'p'} { testsuite.MustFail( &cred, nil, "hakurei", "exec", "capsh", "--has-"+string(p)+"=CAP_SYS_ADMIN", ) } testsuite.MustFail( &cred, nil, "hakurei", "exec", "umount", "-R", "/dev", ) }) wg.Go(func() { defer log.Println("validated pd seccomp outcome") c, cancel := context.WithCancel(ctx) defer cancel() pid, done := mustStart(c, newSerial(), &cred) testsuite.MustCheckFilter(pid, testdata.SumPD) if err := testsuite.FilterTerminated(<-done); err != nil { log.Fatal(err) } }) wg.Go(func() { defer log.Println("validated fd leak") c, cancel := context.WithCancel(ctx) defer cancel() pid, done := mustStart(c, newSerial(), &cred, os.Stdin, os.Stdout, os.Stderr) prefix := filepath.Join(fhs.Proc, strconv.Itoa(pid), "fd") var fail bool if entries, err := os.ReadDir(prefix); err != nil { log.Fatal(err.Error()) } else { for _, ent := range entries { var fd int if fd, err = strconv.Atoi(ent.Name()); err != nil { log.Fatal(err.Error()) } // skip standard streams if fd <= 2 { continue } fail = true var d string if d, err = os.Readlink(filepath.Join( prefix, ent.Name(), )); err != nil { log.Fatal(err.Error()) } log.Printf("extra fd %d -> %s", fd, d) } } if fail { log.Fatal("file descriptors leaked") } if err := syscall.Kill(pid, syscall.SIGTERM); err != nil { log.Fatalf("cannot terminate anchor: %v", err) } else if err = testsuite.FilterTerminated(<-done); err != nil { log.Fatal(err) } }) if err := os.MkdirAll(testsuite.XDGRuntimeDir, 0700); err != nil { log.Fatal(err) } else if err = os.Chown(testsuite.XDGRuntimeDir, 1000, 1000); err != nil { log.Fatal(err) } var swg sync.WaitGroup defer swg.Wait() dbusEnv := testsuite.MustStartSessionBus(&cred) testsuite.MustStartSway(&swg, &cred, dbusEnv) defer testsuite.TerminateSway(&cred) testsuite.MustStartPipeWire(&cred, dbusEnv) if err := <-testToolDone; err != nil { log.Fatal(err) } log.Println("created test helper") s := store.New(check.MustAbs("/tmp/hakurei.0/state")) for name, tc := range testdata.All() { wg.Go(func() { cmd := exec.Command( "script", "/dev/null", "-E", "always", "-qec", "hakurei run "+ "--identifier-fd=5"+ " 4 1>&3", ) cmd.SysProcAttr = &syscall.SysProcAttr{ Pdeathsig: syscall.SIGTERM, Credential: &cred, } var output bytes.Buffer cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, &output, &output cmd.Env = []string{ "PATH=" + os.Getenv("PATH"), "TERM=xterm", testsuite.XDGRuntimeEnv, testsuite.WaylandEnv, "DISPLAY=:0", dbusEnv, } var err error var notify, _notify, _conf, conf, ident, _ident *os.File if notify, _notify, err = os.Pipe(); err != nil { log.Fatal(err) } cmd.ExtraFiles = append(cmd.ExtraFiles, _notify) if _conf, conf, err = os.Pipe(); err != nil { log.Fatal(err) } cmd.ExtraFiles = append(cmd.ExtraFiles, _conf) if ident, _ident, err = os.Pipe(); err != nil { log.Fatal(err) } cmd.ExtraFiles = append(cmd.ExtraFiles, _ident) done := testsuite.MustStart(cmd) wg.Go(func() { _err := <-done log.Printf("completed test case %s\n%s", name, output.String()) if _err != nil { log.Fatalf("test case %s: %v", name, _err) } }) if err = json.NewEncoder(conf).Encode(&tc.Hakurei); err != nil { log.Fatal(err) } else if err = conf.Close(); err != nil { log.Fatal(err) } var id hst.ID if _, err = io.ReadFull(ident, id[:]); err != nil { log.Fatal(err) } else if err = ident.Close(); err != nil { log.Fatal(err) } if _, err = io.ReadFull(notify, make([]byte, 8)); err != nil { log.Fatal(err) } else if err = notify.Close(); err != nil { log.Fatal(err) } var ( ok bool p hst.State ) entries, copyError := s.All() for entry := range entries { if entry.ID == id { ok = true if _, err = entry.Load(&p, nil); err != nil { log.Fatal(err) } break } } if err = copyError(); err != nil { log.Fatal(err) } if !ok { log.Fatalf("instance %s is not present in store", id) } var stat syscall.Stat_t pid := mustScanFor(func(ps *testsuite.StatScanner) bool { select { case err = <-done: if err == nil { log.Fatal("test process terminated unexpectedly") } log.Fatal(err) default: break } if ps.Stat().Comm != "test-helper" { return false } var args []string if args, err = ps.Stat().Args(); err != nil { if testsuite.IsNotExist(err) { return false } log.Fatal(err) } else if !slices.Equal(args, tc.Hakurei.Container.Args) { return false } if err = ps.Stat().Stat(&stat); err != nil { if testsuite.IsNotExist(err) { return false } log.Fatal(err) } uid := hst.ToUser[uint32](0, uint32(tc.Hakurei.Identity)) if stat.Uid != uid || stat.Gid != uid { return false } var t []byte if t, err = os.ReadFile(filepath.Join( fhs.Proc, strconv.Itoa(ps.Stat().PPID), "stat", )); err != nil { if testsuite.IsNotExist(err) { return false } log.Fatal(err) } var _stat testsuite.Stat if err = _stat.UnmarshalText(t); err != nil { log.Fatal(err) } if _stat.PPID != p.ShimPID { return false } return true }) testsuite.MustCheckFilter( pid, tc.Sum, ) }) } wg.Wait() if dents, err := os.ReadDir("/tmp"); err != nil { log.Fatal(err) } else { for _, dent := range dents { if name := dent.Name(); strings.HasPrefix(name, ".hakurei-shim-") { log.Fatalf("leftover shim work dir %q", name) } } } }