aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/app/setuid/process.go
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-04-12 13:56:41 +0900
committerOphestra <cat@gensokyo.uk>2025-04-12 13:56:41 +0900
commit6309469e933a31a300fbf16d8e77f48dcee402d3 (patch)
tree8f8a72ee02b3ca15b104a6e55c9379e20f8d7e8a /internal/app/setuid/process.go
parent0d7c1a9a4356614f035225aeb24e66421879a99b (diff)
app/instance: wrap internal implementation
This reduces the scope of the fst package, which was growing questionably large. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/app/setuid/process.go')
-rw-r--r--internal/app/setuid/process.go195
1 files changed, 0 insertions, 195 deletions
diff --git a/internal/app/setuid/process.go b/internal/app/setuid/process.go
deleted file mode 100644
index e730225e..00000000
--- a/internal/app/setuid/process.go
+++ /dev/null
@@ -1,195 +0,0 @@
-package setuid
-
-import (
- "context"
- "encoding/gob"
- "errors"
- "log"
- "os"
- "os/exec"
- "strconv"
- "strings"
- "syscall"
- "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/state"
- "git.gensokyo.uk/security/fortify/sandbox"
- "git.gensokyo.uk/security/fortify/system"
-)
-
-const shimWaitTimeout = 5 * time.Second
-
-func (seal *outcome) Run(rs *fst.RunState) error {
- if !seal.f.CompareAndSwap(false, true) {
- // run does much more than just starting a process; calling it twice, even if the first call fails, will result
- // in inconsistent state that is impossible to clean up; return here to limit damage and hopefully give the
- // other Run a chance to return
- return errors.New("outcome: attempted to run twice")
- }
-
- if rs == nil {
- panic("invalid state")
- }
-
- // read comp value early to allow for early failure
- fsuPath := internal.MustFsuPath()
-
- if err := seal.sys.Commit(seal.ctx); err != nil {
- return err
- }
- store := state.NewMulti(seal.runDirPath)
- deferredStoreFunc := func(c state.Cursor) error { return nil } // noop until state in store
- defer func() {
- var revertErr error
- storeErr := new(StateStoreError)
- storeErr.Inner, storeErr.DoErr = store.Do(seal.user.aid.unwrap(), func(c state.Cursor) {
- revertErr = func() error {
- storeErr.InnerErr = deferredStoreFunc(c)
-
- var rt system.Enablement
- ec := system.Process
- if states, err := c.Load(); err != nil {
- // revert per-process state here to limit damage
- storeErr.OpErr = err
- return seal.sys.Revert((*system.Criteria)(&ec))
- } else {
- if l := len(states); l == 0 {
- ec |= system.User
- } else {
- fmsg.Verbosef("found %d instances, cleaning up without user-scoped operations", l)
- }
-
- // accumulate enablements of remaining launchers
- for i, s := range states {
- if s.Config != nil {
- rt |= s.Config.Confinement.Enablements
- } else {
- log.Printf("state entry %d does not contain config", i)
- }
- }
- }
- ec |= rt ^ (system.EWayland | system.EX11 | system.EDBus | system.EPulse)
- if fmsg.Load() {
- if ec > 0 {
- fmsg.Verbose("reverting operations scope", system.TypeString(ec))
- }
- }
-
- return seal.sys.Revert((*system.Criteria)(&ec))
- }()
- })
- storeErr.save(revertErr, store.Close())
- rs.RevertErr = storeErr.equiv("error during cleanup:")
- }()
-
- ctx, cancel := context.WithCancel(seal.ctx)
- defer cancel()
- cmd := exec.CommandContext(ctx, fsuPath)
- cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
- cmd.Dir = "/" // container init enters final working directory
- // shim runs in the same session as monitor; see shim.go for behaviour
- cmd.Cancel = func() error { return cmd.Process.Signal(syscall.SIGCONT) }
-
- var e *gob.Encoder
- if fd, encoder, err := sandbox.Setup(&cmd.ExtraFiles); err != nil {
- return fmsg.WrapErrorSuffix(err,
- "cannot create shim setup pipe:")
- } else {
- e = encoder
- cmd.Env = []string{
- // passed through to shim by fsu
- shimEnv + "=" + strconv.Itoa(fd),
- // interpreted by fsu
- "FORTIFY_APP_ID=" + seal.user.aid.String(),
- }
- }
-
- if len(seal.user.supp) > 0 {
- fmsg.Verbosef("attaching supplementary group ids %s", seal.user.supp)
- // interpreted by fsu
- cmd.Env = append(cmd.Env, "FORTIFY_GROUPS="+strings.Join(seal.user.supp, " "))
- }
-
- fmsg.Verbosef("setuid helper at %s", fsuPath)
- fmsg.Suspend()
- if err := cmd.Start(); err != nil {
- return fmsg.WrapErrorSuffix(err,
- "cannot start setuid wrapper:")
- }
- rs.SetStart()
-
- // this prevents blocking forever on an early failure
- waitErr, setupErr := make(chan error, 1), make(chan error, 1)
- go func() { waitErr <- cmd.Wait(); cancel() }()
- go func() { setupErr <- e.Encode(&shimParams{os.Getpid(), seal.container, seal.user.data, fmsg.Load()}) }()
-
- select {
- case err := <-setupErr:
- if err != nil {
- fmsg.Resume()
- return fmsg.WrapErrorSuffix(err,
- "cannot transmit shim config:")
- }
-
- case <-ctx.Done():
- fmsg.Resume()
- return fmsg.WrapError(syscall.ECANCELED,
- "shim setup canceled")
- }
-
- // returned after blocking on waitErr
- var earlyStoreErr = new(StateStoreError)
- {
- // shim accepted setup payload, create process state
- sd := state.State{
- ID: seal.id.unwrap(),
- PID: cmd.Process.Pid,
- Time: *rs.Time,
- }
- earlyStoreErr.Inner, earlyStoreErr.DoErr = store.Do(seal.user.aid.unwrap(), func(c state.Cursor) {
- earlyStoreErr.InnerErr = c.Save(&sd, seal.ct)
- })
- }
-
- // state in store at this point, destroy defunct state entry on return
- deferredStoreFunc = func(c state.Cursor) error { return c.Destroy(seal.id.unwrap()) }
-
- waitTimeout := make(chan struct{})
- go func() { <-seal.ctx.Done(); time.Sleep(shimWaitTimeout); close(waitTimeout) }()
-
- select {
- case rs.WaitErr = <-waitErr:
- rs.WaitStatus = cmd.ProcessState.Sys().(syscall.WaitStatus)
- if fmsg.Load() {
- switch {
- case rs.Exited():
- fmsg.Verbosef("process %d exited with code %d", cmd.Process.Pid, rs.ExitStatus())
- case rs.CoreDump():
- fmsg.Verbosef("process %d dumped core", cmd.Process.Pid)
- case rs.Signaled():
- fmsg.Verbosef("process %d got %s", cmd.Process.Pid, rs.Signal())
- default:
- fmsg.Verbosef("process %d exited with status %#x", cmd.Process.Pid, rs.WaitStatus)
- }
- }
- case <-waitTimeout:
- rs.WaitErr = syscall.ETIMEDOUT
- fmsg.Resume()
- log.Printf("process %d did not terminate", cmd.Process.Pid)
- }
-
- fmsg.Resume()
- if seal.sync != nil {
- if err := seal.sync.Close(); err != nil {
- log.Printf("cannot close wayland security context: %v", err)
- }
- }
- if seal.dbusMsg != nil {
- seal.dbusMsg()
- }
-
- return earlyStoreErr.equiv("cannot save process state:")
-}