aboutsummaryrefslogtreecommitdiffhomepage
path: root/helper
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-03-16 02:57:46 +0900
committerOphestra <cat@gensokyo.uk>2025-03-16 02:57:46 +0900
commit42de09e896e083f2bac0b7293483dcd0b403fd64 (patch)
treeb23f8d13d6354f1740b0c18af4d13c7ab86e6398 /helper
parent1576fea8a33a165562deff6b0d8adda35ad62244 (diff)
helper: implement native container backend
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'helper')
-rw-r--r--helper/container.go75
-rw-r--r--helper/container_test.go55
2 files changed, 130 insertions, 0 deletions
diff --git a/helper/container.go b/helper/container.go
new file mode 100644
index 00000000..4eb57c3b
--- /dev/null
+++ b/helper/container.go
@@ -0,0 +1,75 @@
+package helper
+
+import (
+ "context"
+ "errors"
+ "io"
+ "os"
+ "slices"
+ "sync"
+
+ "git.gensokyo.uk/security/fortify/helper/proc"
+ "git.gensokyo.uk/security/fortify/internal/sandbox"
+)
+
+// New initialises a Helper instance with wt as the null-terminated argument writer.
+func New(
+ ctx context.Context,
+ name string,
+ wt io.WriterTo,
+ stat bool,
+ argF func(argsFd, statFd int) []string,
+ cmdF func(container *sandbox.Container),
+ extraFiles []*os.File,
+) Helper {
+ var args []string
+ h := new(helperContainer)
+ h.helperFiles, args = newHelperFiles(ctx, wt, stat, argF, extraFiles)
+ h.Container = sandbox.New(ctx, name, args...)
+ h.WaitDelay = WaitDelay
+ if cmdF != nil {
+ cmdF(h.Container)
+ }
+ return h
+}
+
+// helperContainer provides a [sandbox.Container] wrapper around helper ipc.
+type helperContainer struct {
+ started bool
+
+ mu sync.Mutex
+ *helperFiles
+ *sandbox.Container
+}
+
+func (h *helperContainer) Start() error {
+ h.mu.Lock()
+ defer h.mu.Unlock()
+
+ if h.started {
+ return errors.New("helper: already started")
+ }
+ h.started = true
+
+ h.Env = slices.Grow(h.Env, 2)
+ if h.useArgsFd {
+ h.Env = append(h.Env, FortifyHelper+"=1")
+ } else {
+ h.Env = append(h.Env, FortifyHelper+"=0")
+ }
+ if h.useStatFd {
+ h.Env = append(h.Env, FortifyStatus+"=1")
+
+ // stat is populated on fulfill
+ h.Cancel = func() error { return h.stat.Close() }
+ } else {
+ h.Env = append(h.Env, FortifyStatus+"=0")
+ }
+
+ return proc.Fulfill(h.helperFiles.ctx, &h.ExtraFiles, func() error {
+ if err := h.Container.Start(); err != nil {
+ return err
+ }
+ return h.Container.Serve()
+ }, h.files, h.extraFiles)
+}
diff --git a/helper/container_test.go b/helper/container_test.go
new file mode 100644
index 00000000..4dc88da5
--- /dev/null
+++ b/helper/container_test.go
@@ -0,0 +1,55 @@
+package helper_test
+
+import (
+ "context"
+ "io"
+ "os"
+ "os/exec"
+ "testing"
+
+ "git.gensokyo.uk/security/fortify/helper"
+ "git.gensokyo.uk/security/fortify/internal"
+ "git.gensokyo.uk/security/fortify/internal/sandbox"
+)
+
+func TestContainer(t *testing.T) {
+ t.Run("start empty container", func(t *testing.T) {
+ h := helper.New(context.Background(), "/nonexistent", argsWt, false, argF, nil, nil)
+
+ wantErr := "sandbox: starting an empty container"
+ if err := h.Start(); err == nil || err.Error() != wantErr {
+ t.Errorf("Start: error = %v, wantErr %q",
+ err, wantErr)
+ }
+ })
+
+ t.Run("valid new helper nil check", func(t *testing.T) {
+ if got := helper.New(context.TODO(), "fortify", argsWt, false, argF, nil, nil); got == nil {
+ t.Errorf("New(%q, %q) got nil",
+ argsWt, "fortify")
+ return
+ }
+ })
+
+ t.Run("implementation compliance", func(t *testing.T) {
+ testHelper(t, func(ctx context.Context, setOutput func(stdoutP, stderrP *io.Writer), stat bool) helper.Helper {
+ return helper.New(ctx, os.Args[0], argsWt, stat, argF, func(container *sandbox.Container) {
+ setOutput(&container.Stdout, &container.Stderr)
+ container.CommandContext = func(ctx context.Context) (cmd *exec.Cmd) {
+ return exec.CommandContext(ctx, os.Args[0], "-test.v",
+ "-test.run=TestHelperInit", "--", "init")
+ }
+ container.Bind("/", "/", 0)
+ container.Proc("/proc")
+ container.Dev("/dev")
+ }, nil)
+ })
+ })
+}
+
+func TestHelperInit(t *testing.T) {
+ if len(os.Args) != 5 || os.Args[4] != "init" {
+ return
+ }
+ sandbox.Init(internal.Exit)
+}