helper: use generic extra files interface
All checks were successful
Test / Create distribution (push) Successful in 1m38s
Test / Run NixOS test (push) Successful in 4m36s

This replaces the pipes object and integrates context into helper process lifecycle.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
2025-02-13 23:15:34 +09:00
parent 60c2873750
commit fe7d208cf7
25 changed files with 517 additions and 850 deletions

View File

@@ -46,7 +46,7 @@ func (a *app) Run(ctx context.Context, rs *RunState) error {
}
// startup will go ahead, commit system setup
if err := a.seal.sys.Commit(); err != nil {
if err := a.seal.sys.Commit(ctx); err != nil {
return err
}
a.seal.sys.needRevert = true

View File

@@ -1,10 +1,14 @@
package shim
import (
"context"
"errors"
"os"
"os/exec"
"os/signal"
"path"
"strconv"
"syscall"
"git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/helper"
@@ -138,19 +142,22 @@ func Main() {
); err != nil {
fmsg.Fatalf("malformed sandbox config: %v", err)
} else {
cmd := b.Unwrap()
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
b.Stdin(os.Stdin).Stdout(os.Stdout).Stderr(os.Stderr)
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop() // unreachable
// run and pass through exit code
if err = b.Start(); err != nil {
if err = b.Start(ctx, false); err != nil {
fmsg.Fatalf("cannot start target process: %v", err)
} else if err = b.Wait(); err != nil {
fmsg.VPrintln("wait:", err)
}
if b.Unwrap().ProcessState != nil {
fmsg.Exit(b.Unwrap().ProcessState.ExitCode())
} else {
fmsg.Exit(127)
var exitError *exec.ExitError
if !errors.As(err, &exitError) {
fmsg.Println("wait:", err)
fmsg.Exit(127)
panic("unreachable")
}
fmsg.Exit(exitError.ExitCode())
panic("unreachable")
}
}
}

View File

@@ -3,7 +3,6 @@ package system
import (
"bytes"
"errors"
"os"
"strings"
"sync"
@@ -26,9 +25,6 @@ func (sys *I) MustProxyDBus(sessionPath string, session *dbus.Config, systemPath
func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath string) (func(), error) {
d := new(DBus)
// used by waiting goroutine to notify process exit
d.done = make(chan struct{})
// session bus is mandatory
if session == nil {
return nil, fmsg.WrapError(ErrDBusConfig,
@@ -75,88 +71,34 @@ type DBus struct {
out *scanToFmsg
// whether system bus proxy is enabled
system bool
// notification from goroutine waiting for dbus.Proxy
done chan struct{}
}
func (d *DBus) Type() Enablement {
return Process
}
func (d *DBus) apply(_ *I) error {
func (d *DBus) apply(sys *I) error {
fmsg.VPrintf("session bus proxy on %q for upstream %q", d.proxy.Session()[1], d.proxy.Session()[0])
if d.system {
fmsg.VPrintf("system bus proxy on %q for upstream %q", d.proxy.System()[1], d.proxy.System()[0])
}
// ready channel passed to dbus package
ready := make(chan error, 1)
// background dbus proxy start
if err := d.proxy.Start(ready, d.out, true, true); err != nil {
// this starts the process and blocks until ready
if err := d.proxy.Start(sys.ctx, d.out, true); err != nil {
d.out.Dump()
return fmsg.WrapErrorSuffix(err,
"cannot start message bus proxy:")
}
fmsg.VPrintln("starting message bus proxy:", d.proxy)
if fmsg.Verbose() { // save the extra bwrap arg build when verbose logging is off
fmsg.VPrintln("message bus proxy bwrap args:", d.proxy.BwrapStatic())
}
// background wait for proxy instance and notify completion
go func() {
if err := d.proxy.Wait(); err != nil {
fmsg.Println("message bus proxy exited with error:", err)
go func() { ready <- err }()
} else {
fmsg.VPrintln("message bus proxy exit")
}
// ensure socket removal so ephemeral directory is empty at revert
if err := os.Remove(d.proxy.Session()[1]); err != nil && !errors.Is(err, os.ErrNotExist) {
fmsg.Println("cannot remove dangling session bus socket:", err)
}
if d.system {
if err := os.Remove(d.proxy.System()[1]); err != nil && !errors.Is(err, os.ErrNotExist) {
fmsg.Println("cannot remove dangling system bus socket:", err)
}
}
// notify proxy completion
close(d.done)
}()
// ready is not nil if the proxy process faulted
if err := <-ready; err != nil {
// dump message buffer as caller does not dump this
// in an early fault condition
d.out.Dump()
// note that err here is either an I/O error or a predetermined unexpected behaviour error
return fmsg.WrapErrorSuffix(err,
"message bus proxy fault after start:")
}
fmsg.VPrintln("message bus proxy ready")
return nil
}
func (d *DBus) revert(_ *I, _ *Criteria) error {
// criteria ignored here since dbus is always process-scoped
fmsg.VPrintln("terminating message bus proxy")
if err := d.proxy.Close(); err != nil {
if errors.Is(err, os.ErrClosed) {
return fmsg.WrapError(err,
"message bus proxy already closed")
} else {
return fmsg.WrapErrorSuffix(err,
"cannot stop message bus proxy:")
}
}
// block until proxy wait returns
<-d.done
return nil
d.proxy.Close()
defer fmsg.VPrintln("message bus proxy exit")
return fmsg.WrapErrorSuffix(d.proxy.Wait(), "message bus proxy error:")
}
func (d *DBus) Is(o Op) bool {

View File

@@ -1,6 +1,7 @@
package system
import (
"context"
"errors"
"os"
"sync"
@@ -57,10 +58,14 @@ func TypeString(e Enablement) string {
type I struct {
uid int
ops []Op
sp *os.File
ctx context.Context
// sync fd passed to bwrap
sp *os.File
state [2]bool
lock sync.Mutex
// whether sys has been reverted
state bool
lock sync.Mutex
}
func (sys *I) UID() int {
@@ -85,14 +90,14 @@ func (sys *I) Equal(v *I) bool {
return true
}
func (sys *I) Commit() error {
func (sys *I) Commit(ctx context.Context) error {
sys.lock.Lock()
defer sys.lock.Unlock()
if sys.state[0] {
if sys.ctx != nil {
panic("sys instance committed twice")
}
sys.state[0] = true
sys.ctx = ctx
sp := New(sys.uid)
sp.ops = make([]Op, 0, len(sys.ops)) // prevent copies during commits
@@ -125,10 +130,10 @@ func (sys *I) Revert(ec *Criteria) error {
sys.lock.Lock()
defer sys.lock.Unlock()
if sys.state[1] {
if sys.state {
panic("sys instance reverted twice")
}
sys.state[1] = true
sys.state = true
// collect errors
errs := make([]error, len(sys.ops))