diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-03-15 23:57:44 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-03-16 00:01:25 +0900 |
| commit | 6e7ddb2d2ec6b163dc2c178bc4d73b5943f51206 (patch) | |
| tree | 37ffe1564782377c1983909362ed6109c3f06b28 /dbus/proc.go | |
| parent | bac4e67867faa7d8274b684575140a65827d3a4c (diff) | |
helper: eliminate commandContext replacement
This is done more cleanly by modifying Args in cmdF.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'dbus/proc.go')
| -rw-r--r-- | dbus/proc.go | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/dbus/proc.go b/dbus/proc.go new file mode 100644 index 00000000..379f9fd3 --- /dev/null +++ b/dbus/proc.go @@ -0,0 +1,201 @@ +package dbus + +import ( + "context" + "errors" + "io" + "os" + "os/exec" + "path" + "path/filepath" + "slices" + "strconv" + "strings" + "syscall" + + "git.gensokyo.uk/security/fortify/helper" + "git.gensokyo.uk/security/fortify/helper/bwrap" + "git.gensokyo.uk/security/fortify/ldd" +) + +// Start launches the D-Bus proxy. +func (p *Proxy) Start(ctx context.Context, output io.Writer, sandbox bool) error { + p.lock.Lock() + defer p.lock.Unlock() + + if p.seal == nil { + return errors.New("proxy not sealed") + } + + var h helper.Helper + + c, cancel := context.WithCancelCause(ctx) + if !sandbox { + h = helper.NewDirect(c, p.name, p.seal, true, argF, func(cmd *exec.Cmd) { + cmdF(cmd, output, p.CmdF) + + // xdg-dbus-proxy does not need to inherit the environment + cmd.Env = make([]string, 0) + }, nil) + } else { + // look up absolute path if name is just a file name + toolPath := p.name + if filepath.Base(p.name) == p.name { + if s, err := exec.LookPath(p.name); err != nil { + return err + } else { + toolPath = s + } + } + + // resolve libraries by parsing ldd output + var proxyDeps []*ldd.Entry + if toolPath != os.Args[0] { + if l, err := ldd.Exec(ctx, toolPath); err != nil { + return err + } else { + proxyDeps = l + } + } + + bc := &bwrap.Config{ + Hostname: "fortify-dbus", + Chdir: "/", + Syscall: &bwrap.SyscallPolicy{DenyDevel: true, Multiarch: true}, + Clearenv: true, + NewSession: true, + DieWithParent: true, + } + + // resolve proxy socket directories + bindTargetM := make(map[string]struct{}, 2) + + for _, ps := range []string{p.session[1], p.system[1]} { + if pd := path.Dir(ps); len(pd) > 0 { + if pd[0] == '/' { + bindTargetM[pd] = struct{}{} + } + } + } + + bindTarget := make([]string, 0, len(bindTargetM)) + for k := range bindTargetM { + bindTarget = append(bindTarget, k) + } + slices.Sort(bindTarget) + for _, name := range bindTarget { + bc.Bind(name, name, false, true) + } + + roBindTargetM := make(map[string]struct{}, 2+1+len(proxyDeps)) + + // xdb-dbus-proxy bin and dependencies + roBindTargetM[path.Dir(toolPath)] = struct{}{} + for _, ent := range proxyDeps { + if path.IsAbs(ent.Path) { + roBindTargetM[path.Dir(ent.Path)] = struct{}{} + } + if path.IsAbs(ent.Name) { + roBindTargetM[path.Dir(ent.Name)] = struct{}{} + } + } + + // resolve upstream bus directories + for _, as := range []string{p.session[0], p.system[0]} { + if len(as) > 0 && strings.HasPrefix(as, "unix:path=/") { + // leave / intact + roBindTargetM[path.Dir(as[10:])] = struct{}{} + } + } + + roBindTarget := make([]string, 0, len(roBindTargetM)) + for k := range roBindTargetM { + roBindTarget = append(roBindTarget, k) + } + slices.Sort(roBindTarget) + for _, name := range roBindTarget { + bc.Bind(name, name) + } + + h = helper.MustNewBwrap(c, toolPath, + p.seal, true, + argF, func(cmd *exec.Cmd) { cmdF(cmd, output, p.CmdF) }, + nil, + bc, nil, + ) + p.bwrap = bc + } + + if err := h.Start(); err != nil { + cancel(err) + return err + } + + p.helper = h + p.ctx = c + p.cancel = cancel + return nil +} + +var proxyClosed = errors.New("proxy closed") + +// Wait blocks until xdg-dbus-proxy exits and releases resources. +func (p *Proxy) Wait() error { + p.lock.RLock() + defer p.lock.RUnlock() + + if p.helper == nil { + return errors.New("dbus: not started") + } + + errs := make([]error, 3) + + errs[0] = p.helper.Wait() + if p.cancel == nil && + errors.Is(errs[0], context.Canceled) && + errors.Is(context.Cause(p.ctx), proxyClosed) { + errs[0] = nil + } + + // ensure socket removal so ephemeral directory is empty at revert + if err := os.Remove(p.session[1]); err != nil && !errors.Is(err, os.ErrNotExist) { + errs[1] = err + } + if p.sysP { + if err := os.Remove(p.system[1]); err != nil && !errors.Is(err, os.ErrNotExist) { + errs[2] = err + } + } + + return errors.Join(errs...) +} + +// Close cancels the context passed to the helper instance attached to xdg-dbus-proxy. +func (p *Proxy) Close() { + p.lock.Lock() + defer p.lock.Unlock() + + if p.cancel == nil { + panic("dbus: not started") + } + p.cancel(proxyClosed) + p.cancel = nil +} + +func argF(argsFd, statFd int) []string { + if statFd == -1 { + return []string{"--args=" + strconv.Itoa(argsFd)} + } else { + return []string{"--args=" + strconv.Itoa(argsFd), "--fd=" + strconv.Itoa(statFd)} + } +} + +func cmdF(cmd *exec.Cmd, output io.Writer, cmdF func(cmd *exec.Cmd)) { + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + if output != nil { + cmd.Stdout, cmd.Stderr = output, output + } + if cmdF != nil { + cmdF(cmd) + } +} |
