package sandbox_test import ( "bytes" "context" "encoding/json" "log" "os" "os/exec" "path" "slices" "testing" "time" "git.gensokyo.uk/security/fortify/fst" "git.gensokyo.uk/security/fortify/internal" "git.gensokyo.uk/security/fortify/internal/fmsg" "git.gensokyo.uk/security/fortify/internal/sandbox" "git.gensokyo.uk/security/fortify/ldd" check "git.gensokyo.uk/security/fortify/test/sandbox" ) func TestContainer(t *testing.T) { { oldVerbose := fmsg.Load() fmsg.Store(true) t.Cleanup(func() { fmsg.Store(oldVerbose) }) } testCases := []struct { name string fs *sandbox.Filesystem mnt []*check.Mntent host string }{ {"minimal", new(sandbox.Filesystem), nil, "test-minimal"}, {"tmpfs", new(sandbox.Filesystem). Tmpfs(fst.Tmp, 0, 0755), []*check.Mntent{ {FSName: "tmpfs", Dir: fst.Tmp, Type: "tmpfs", Opts: "\x00"}, }, "test-tmpfs"}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() container := sandbox.New(ctx, os.Args[0], "-test.v", "-test.run=TestHelperCheckContainer", "--", "check", tc.host) container.Hostname = tc.host container.CommandContext = func(ctx context.Context) *exec.Cmd { return exec.CommandContext(ctx, os.Args[0], "-test.v", "-test.run=TestHelperInit", "--", "init") } container.Stdout, container.Stderr = os.Stdout, os.Stderr container.Filesystem = tc.fs if container.Args[5] == "" { if name, err := os.Hostname(); err != nil { t.Fatalf("cannot get hostname: %v", err) } else { container.Args[5] = name } } container. Bind("/tmp", "/tmp", sandbox.BindWritable). Bind(os.Args[0], os.Args[0], 0) // in case test has cgo enabled var libPaths []string if entries, err := ldd.Exec(ctx, os.Args[0]); err != nil { log.Fatalf("ldd: %v", err) } else { libPathsM := make(map[string]struct{}, len(entries)) for _, ent := range entries { if path.IsAbs(ent.Path) { libPathsM[path.Dir(ent.Path)] = struct{}{} } if path.IsAbs(ent.Name) { libPathsM[path.Dir(ent.Name)] = struct{}{} } } libPaths = make([]string, 0, len(libPathsM)) for name := range libPathsM { libPaths = append(libPaths, name) } slices.Sort(libPaths) for _, name := range libPaths { container.Bind(name, name, 0) } } mnt := make([]*check.Mntent, 0, 3+len(libPaths)) mnt = append(mnt, &check.Mntent{FSName: "rootfs", Dir: "/", Type: "tmpfs", Opts: "host_passthrough"}) mnt = append(mnt, tc.mnt...) mnt = append(mnt, &check.Mntent{FSName: "\x00", Dir: "/tmp", Type: "\x00", Opts: "\x00", Freq: -1, Passno: -1}, &check.Mntent{FSName: "\x00", Dir: os.Args[0], Type: "\x00", Opts: "\x00"}) for _, name := range libPaths { mnt = append(mnt, &check.Mntent{FSName: "\x00", Dir: name, Type: "\x00", Opts: "\x00", Freq: -1, Passno: -1}) } mnt = append(mnt, &check.Mntent{FSName: "proc", Dir: "/proc", Type: "proc", Opts: "rw,nosuid,nodev,noexec,relatime"}) mntentWant := new(bytes.Buffer) if err := json.NewEncoder(mntentWant).Encode(mnt); err != nil { t.Fatalf("cannot serialise mntent: %v", err) } container.Stdin = mntentWant if err := container.Start(); err != nil { fmsg.PrintBaseError(err, "start:") t.Fatalf("cannot start container: %v", err) } else if err = container.Serve(); err != nil { fmsg.PrintBaseError(err, "serve:") t.Errorf("cannot serve setup params: %v", err) } if err := container.Wait(); err != nil { fmsg.PrintBaseError(err, "wait:") t.Fatalf("wait: %v", err) } }) } } func TestHelperInit(t *testing.T) { if len(os.Args) != 5 || os.Args[4] != "init" { return } sandbox.Init(internal.Exit) } func TestHelperCheckContainer(t *testing.T) { if len(os.Args) != 6 || os.Args[4] != "check" { return } t.Run("hostname", func(t *testing.T) { if name, err := os.Hostname(); err != nil { t.Fatalf("cannot get hostname: %v", err) } else if name != os.Args[5] { t.Errorf("Hostname: %q, want %q", name, os.Args[5]) } }) t.Run("seccomp", func(t *testing.T) { check.MustAssertSeccomp() }) t.Run("mntent", func(t *testing.T) { check.MustAssertMounts("", "/proc/mounts", "/proc/self/fd/0") }) }