diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-03-25 01:52:49 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-03-25 01:52:49 +0900 |
| commit | 5c4058d5ac7e7944973ca8216c258fc50c194e22 (patch) | |
| tree | c06597903233c75eca5a1edae8b06d93f0f81774 /internal/app/shim | |
| parent | e732dca7629edb503df0c65df5f37a99600d567d (diff) | |
app: run in native sandbox
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/app/shim')
| -rw-r--r-- | internal/app/shim/main.go | 129 | ||||
| -rw-r--r-- | internal/app/shim/payload.go | 23 | ||||
| -rw-r--r-- | internal/app/shim/proc.go (renamed from internal/app/shim/manager.go) | 35 |
3 files changed, 56 insertions, 131 deletions
diff --git a/internal/app/shim/main.go b/internal/app/shim/main.go index 5634be4c..17f346a2 100644 --- a/internal/app/shim/main.go +++ b/internal/app/shim/main.go @@ -7,18 +7,26 @@ import ( "os" "os/exec" "os/signal" - "path" - "strconv" "syscall" + "time" - "git.gensokyo.uk/security/fortify/fst" - "git.gensokyo.uk/security/fortify/helper" "git.gensokyo.uk/security/fortify/internal" - "git.gensokyo.uk/security/fortify/internal/app/init0" "git.gensokyo.uk/security/fortify/internal/fmsg" "git.gensokyo.uk/security/fortify/sandbox" ) +const Env = "FORTIFY_SHIM" + +type Params struct { + // finalised container params + Container *sandbox.Params + // path to outer home directory + Home string + + // verbosity pass through + Verbose bool +} + // everything beyond this point runs as unconstrained target user // proceed with caution! @@ -27,17 +35,15 @@ func Main() { // USE WITH CAUTION fmsg.Prepare("shim") - // setting this prevents ptrace if err := sandbox.SetDumpable(sandbox.SUID_DUMP_DISABLE); err != nil { log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err) } - // receive setup payload var ( - payload Payload + params Params closeSetup func() error ) - if f, err := sandbox.Receive(Env, &payload, nil); err != nil { + if f, err := sandbox.Receive(Env, ¶ms, nil); err != nil { if errors.Is(err, sandbox.ErrInvalid) { log.Fatal("invalid config descriptor") } @@ -45,32 +51,26 @@ func Main() { log.Fatal("FORTIFY_SHIM not set") } - log.Fatalf("cannot decode shim setup payload: %v", err) + log.Fatalf("cannot receive shim setup params: %v", err) } else { - internal.InstallFmsg(payload.Verbose) + internal.InstallFmsg(params.Verbose) closeSetup = f } - if payload.Bwrap == nil { - log.Fatal("bwrap config not supplied") - } - - // restore bwrap sync fd - var syncFd *os.File - if payload.Sync != nil { - syncFd = os.NewFile(*payload.Sync, "sync") + if params.Container == nil || params.Container.Ops == nil { + log.Fatal("invalid container params") } // close setup socket if err := closeSetup(); err != nil { - log.Println("cannot close setup pipe:", err) + log.Printf("cannot close setup pipe: %v", err) // not fatal } // ensure home directory as target user - if s, err := os.Stat(payload.Home); err != nil { + if s, err := os.Stat(params.Home); err != nil { if os.IsNotExist(err) { - if err = os.Mkdir(payload.Home, 0700); err != nil { + if err = os.Mkdir(params.Home, 0700); err != nil { log.Fatalf("cannot create home directory: %v", err) } } else { @@ -79,72 +79,37 @@ func Main() { // home directory is created, proceed } else if !s.IsDir() { - log.Fatalf("data path %q is not a directory", payload.Home) + log.Fatalf("path %q is not a directory", params.Home) } - var ic init0.Payload - - // resolve argv0 - ic.Argv = payload.Argv - if len(ic.Argv) > 0 { - // looked up from $PATH by parent - ic.Argv0 = payload.Exec[1] - } else { - // no argv, look up shell instead - var ok bool - if payload.Bwrap.SetEnv == nil { - log.Fatal("no command was specified and environment is unset") - } - if ic.Argv0, ok = payload.Bwrap.SetEnv["SHELL"]; !ok { - log.Fatal("no command was specified and $SHELL was unset") - } - - ic.Argv = []string{ic.Argv0} + var name string + if len(params.Container.Args) > 0 { + name = params.Container.Args[0] } - - conf := payload.Bwrap - - var extraFiles []*os.File - - // serve setup payload - if fd, encoder, err := sandbox.Setup(&extraFiles); err != nil { - log.Fatalf("cannot pipe: %v", err) - } else { - conf.SetEnv[init0.Env] = strconv.Itoa(fd) - go func() { - fmsg.Verbose("transmitting config to init") - if err = encoder.Encode(&ic); err != nil { - log.Fatalf("cannot transmit init config: %v", err) - } - }() - } - - helper.BubblewrapName = payload.Exec[0] // resolved bwrap path by parent - ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() // unreachable - if b, err := helper.NewBwrap( - ctx, path.Join(fst.Tmp, "sbin/init0"), - nil, false, - func(int, int) []string { return make([]string, 0) }, - func(cmd *exec.Cmd) { cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr }, - extraFiles, - conf, syncFd, - ); err != nil { - log.Fatalf("malformed sandbox config: %v", err) - } else { - // run and pass through exit code - if err = b.Start(); err != nil { - log.Fatalf("cannot start target process: %v", err) - } else if err = b.Wait(); err != nil { - var exitError *exec.ExitError - if !errors.As(err, &exitError) { - log.Printf("wait: %v", err) - internal.Exit(127) - panic("unreachable") + container := sandbox.New(ctx, name) + container.Params = *params.Container + container.Stdin, container.Stdout, container.Stderr = os.Stdin, os.Stdout, os.Stderr + container.Cancel = func(cmd *exec.Cmd) error { return cmd.Process.Signal(os.Interrupt) } + container.WaitDelay = 2 * time.Second + + if err := container.Start(); err != nil { + fmsg.PrintBaseError(err, "cannot start container:") + os.Exit(1) + } + if err := container.Serve(); err != nil { + fmsg.PrintBaseError(err, "cannot configure container:") + } + if err := container.Wait(); err != nil { + var exitError *exec.ExitError + if !errors.As(err, &exitError) { + if errors.Is(err, context.Canceled) { + os.Exit(2) } - internal.Exit(exitError.ExitCode()) - panic("unreachable") + log.Printf("wait: %v", err) + os.Exit(127) } + os.Exit(exitError.ExitCode()) } } diff --git a/internal/app/shim/payload.go b/internal/app/shim/payload.go deleted file mode 100644 index e659e3fd..00000000 --- a/internal/app/shim/payload.go +++ /dev/null @@ -1,23 +0,0 @@ -package shim - -import ( - "git.gensokyo.uk/security/fortify/helper/bwrap" -) - -const Env = "FORTIFY_SHIM" - -type Payload struct { - // child full argv - Argv []string - // bwrap, target full exec path - Exec [2]string - // bwrap config - Bwrap *bwrap.Config - // path to outer home directory - Home string - // sync fd - Sync *uintptr - - // verbosity pass through - Verbose bool -} diff --git a/internal/app/shim/manager.go b/internal/app/shim/proc.go index 4ed75e53..8c73e697 100644 --- a/internal/app/shim/manager.go +++ b/internal/app/shim/proc.go @@ -8,9 +8,9 @@ import ( "os/exec" "strconv" "strings" + "syscall" "time" - "git.gensokyo.uk/security/fortify/helper/proc" "git.gensokyo.uk/security/fortify/internal" "git.gensokyo.uk/security/fortify/internal/fmsg" "git.gensokyo.uk/security/fortify/sandbox" @@ -25,10 +25,11 @@ type Shim struct { killFallback chan error // monitor to shim encoder encoder *gob.Encoder - // bwrap --sync-fd value - sync *uintptr } +func (s *Shim) Unwrap() *exec.Cmd { return s.cmd } +func (s *Shim) Fallback() chan error { return s.killFallback } + func (s *Shim) String() string { if s.cmd == nil { return "(unused shim manager)" @@ -36,21 +37,9 @@ func (s *Shim) String() string { return s.cmd.String() } -func (s *Shim) Unwrap() *exec.Cmd { - return s.cmd -} - -func (s *Shim) WaitFallback() chan error { - return s.killFallback -} - func (s *Shim) Start( - // string representation of application id aid string, - // string representation of supplementary group ids supp []string, - // bwrap --sync-fd - syncFd *os.File, ) (*time.Time, error) { // prepare user switcher invocation fsuPath := internal.MustFsuPath() @@ -76,12 +65,6 @@ func (s *Shim) Start( s.cmd.Stdin, s.cmd.Stdout, s.cmd.Stderr = os.Stdin, os.Stdout, os.Stderr s.cmd.Dir = "/" - // pass sync fd if set - if syncFd != nil { - fd := proc.ExtraFile(s.cmd, syncFd) - s.sync = &fd - } - fmsg.Verbose("starting shim via fsu:", s.cmd) // withhold messages to stderr fmsg.Suspend() @@ -90,10 +73,11 @@ func (s *Shim) Start( "cannot start fsu:") } startTime := time.Now().UTC() + return &startTime, nil } -func (s *Shim) Serve(ctx context.Context, payload *Payload) error { +func (s *Shim) Serve(ctx context.Context, params *Params) error { // kill shim if something goes wrong and an error is returned s.killFallback = make(chan error, 1) killShim := func() { @@ -103,9 +87,8 @@ func (s *Shim) Serve(ctx context.Context, payload *Payload) error { } defer func() { killShim() }() - payload.Sync = s.sync encodeErr := make(chan error) - go func() { encodeErr <- s.encoder.Encode(payload) }() + go func() { encodeErr <- s.encoder.Encode(params) }() select { // encode return indicates setup completion @@ -121,11 +104,11 @@ func (s *Shim) Serve(ctx context.Context, payload *Payload) error { case <-ctx.Done(): err := ctx.Err() if errors.Is(err, context.Canceled) { - return fmsg.WrapError(errors.New("shim setup canceled"), + return fmsg.WrapError(syscall.ECANCELED, "shim setup canceled") } if errors.Is(err, context.DeadlineExceeded) { - return fmsg.WrapError(errors.New("deadline exceeded waiting for shim"), + return fmsg.WrapError(syscall.ETIMEDOUT, "deadline exceeded waiting for shim") } // unreachable |
