diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-03-13 20:59:03 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-03-13 21:36:26 +0900 |
| commit | 9b1a60b5c9f7a92abb632639117d61c5cbaf74f7 (patch) | |
| tree | 0fa6b8dae30a9a687f0e465c7d0fc5e0f24b48b1 /internal/sandbox/container_test.go | |
| parent | beb3918809f0743e2275f6a3f358f622cea73d6b (diff) | |
sandbox: native container tooling
This should eventually replace bwrap.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/sandbox/container_test.go')
| -rw-r--r-- | internal/sandbox/container_test.go | 149 |
1 files changed, 149 insertions, 0 deletions
diff --git a/internal/sandbox/container_test.go b/internal/sandbox/container_test.go new file mode 100644 index 00000000..648e88c0 --- /dev/null +++ b/internal/sandbox/container_test.go @@ -0,0 +1,149 @@ +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 + ops *sandbox.Ops + mnt []*check.Mntent + host string + }{ + {"minimal", new(sandbox.Ops), nil, "test-minimal"}, + {"tmpfs", + new(sandbox.Ops). + 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.Ops = tc.ops + if container.Args[5] == "" { + if name, err := os.Hostname(); err != nil { + t.Fatalf("cannot get hostname: %v", err) + } else { + container.Args[5] = name + } + } + + container. + Tmpfs("/tmp", 0, 0755). + 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: "tmpfs", Dir: "/tmp", Type: "tmpfs", Opts: "host_passthrough"}, + &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 + + // needs /proc to check mntent + container.Proc("/proc") + + 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") }) +} |
