181 lines
4.9 KiB
Go
181 lines
4.9 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"log"
|
|
"os/exec"
|
|
"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/system"
|
|
)
|
|
|
|
const shimSetupTimeout = 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
|
|
panic("attempted to run twice")
|
|
}
|
|
|
|
if rs == nil {
|
|
panic("invalid state")
|
|
}
|
|
|
|
// read comp values early to allow for early failure
|
|
fmsg.Verbosef("version %s", internal.Version())
|
|
fmsg.Verbosef("setuid helper at %s", internal.MustFsuPath())
|
|
|
|
/*
|
|
prepare/revert os state
|
|
*/
|
|
|
|
if err := seal.sys.Commit(seal.ctx); err != nil {
|
|
return err
|
|
}
|
|
store := state.NewMulti(seal.runDirPath)
|
|
deferredStoreFunc := func(c state.Cursor) error { return nil }
|
|
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)
|
|
|
|
/*
|
|
revert app setup transaction
|
|
*/
|
|
|
|
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 {
|
|
fmsg.Verbose("no other launchers active, will clean up globals")
|
|
ec |= system.User
|
|
} else {
|
|
fmsg.Verbosef("found %d active launchers, cleaning up without globals", 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 type", system.TypeString(ec))
|
|
}
|
|
}
|
|
|
|
return seal.sys.Revert((*system.Criteria)(&ec))
|
|
}()
|
|
})
|
|
storeErr.save([]error{revertErr, store.Close()})
|
|
rs.RevertErr = storeErr.equiv("error returned during cleanup:")
|
|
}()
|
|
|
|
/*
|
|
shim process lifecycle
|
|
*/
|
|
|
|
waitErr := make(chan error, 1)
|
|
cmd := new(shimProcess)
|
|
if startTime, err := cmd.Start(
|
|
seal.user.aid.String(),
|
|
seal.user.supp,
|
|
); err != nil {
|
|
return err
|
|
} else {
|
|
// whether/when the fsu process was created
|
|
rs.Time = startTime
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(seal.ctx, shimSetupTimeout)
|
|
defer cancel()
|
|
|
|
go func() {
|
|
waitErr <- cmd.Unwrap().Wait()
|
|
// cancel shim setup in case shim died before receiving payload
|
|
cancel()
|
|
}()
|
|
|
|
if err := cmd.Serve(ctx, &shimParams{
|
|
Container: seal.container,
|
|
Home: seal.user.data,
|
|
|
|
Verbose: fmsg.Load(),
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
// shim accepted setup payload, create process state
|
|
sd := state.State{
|
|
ID: seal.id.unwrap(),
|
|
PID: cmd.Unwrap().Process.Pid,
|
|
Time: *rs.Time,
|
|
}
|
|
var earlyStoreErr = new(StateStoreError) // returned after blocking on waitErr
|
|
earlyStoreErr.Inner, earlyStoreErr.DoErr = store.Do(seal.user.aid.unwrap(), func(c state.Cursor) {
|
|
earlyStoreErr.InnerErr = c.Save(&sd, seal.ct)
|
|
})
|
|
// destroy defunct state entry
|
|
deferredStoreFunc = func(c state.Cursor) error { return c.Destroy(seal.id.unwrap()) }
|
|
|
|
select {
|
|
case err := <-waitErr: // block until fsu/shim returns
|
|
if err != nil {
|
|
var exitError *exec.ExitError
|
|
if !errors.As(err, &exitError) {
|
|
// should be unreachable
|
|
rs.WaitErr = err
|
|
}
|
|
|
|
// store non-zero return code
|
|
rs.ExitCode = exitError.ExitCode()
|
|
} else {
|
|
rs.ExitCode = cmd.Unwrap().ProcessState.ExitCode()
|
|
}
|
|
if fmsg.Load() {
|
|
fmsg.Verbosef("process %d exited with exit code %d", cmd.Unwrap().Process.Pid, rs.ExitCode)
|
|
}
|
|
|
|
// this is reached when a fault makes an already running shim impossible to continue execution
|
|
// however a kill signal could not be delivered (should actually always happen like that since fsu)
|
|
// the effects of this is similar to the alternative exit path and ensures shim death
|
|
case err := <-cmd.Fallback():
|
|
rs.ExitCode = 255
|
|
log.Printf("cannot terminate shim on faulted setup: %v", err)
|
|
|
|
// alternative exit path relying on shim behaviour on monitor process exit
|
|
case <-seal.ctx.Done():
|
|
fmsg.Verbose("alternative exit path selected")
|
|
}
|
|
|
|
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:")
|
|
}
|