From 000607da5fca0e101cb43db9b1c1b466e3292d35 Mon Sep 17 00:00:00 2001 From: Ophestra Umiker Date: Tue, 24 Sep 2024 16:11:08 +0900 Subject: helper: separate helper args fd builder from dbus This method of passing arguments is used in bubblewrap as well as other tools, this commit separates the argument builder/writer to the helper package and generalise it as an interface. Signed-off-by: Ophestra Umiker --- dbus/dbus.go | 84 ++++++++++++++++++++++++++++++++++++++++++++++++ dbus/run.go | 2 +- dbus/setup.go | 84 ------------------------------------------------ helper/args.go | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ helper/helper.go | 4 +++ 5 files changed, 186 insertions(+), 85 deletions(-) create mode 100644 dbus/dbus.go delete mode 100644 dbus/setup.go create mode 100644 helper/args.go create mode 100644 helper/helper.go diff --git a/dbus/dbus.go b/dbus/dbus.go new file mode 100644 index 00000000..d0e82e17 --- /dev/null +++ b/dbus/dbus.go @@ -0,0 +1,84 @@ +package dbus + +import ( + "errors" + "os" + "os/exec" + "sync" + + "git.ophivana.moe/cat/fortify/helper" +) + +// Proxy holds references to a xdg-dbus-proxy process, and should never be copied. +// Once sealed, configuration changes will no longer be possible and attempting to do so will result in a panic. +type Proxy struct { + cmd *exec.Cmd + + statP [2]*os.File + argsP [2]*os.File + + path string + session [2]string + system [2]string + + wait *chan error + read *chan error + ready *chan bool + + seal helper.Args + lock sync.RWMutex +} + +func (p *Proxy) String() string { + if p == nil { + return "(invalid dbus proxy)" + } + + p.lock.RLock() + defer p.lock.RUnlock() + + if p.cmd != nil { + return p.cmd.String() + } + + if p.seal != nil { + return p.seal.String() + } + + return "(unsealed dbus proxy)" +} + +// Seal seals the Proxy instance. +func (p *Proxy) Seal(session, system *Config) error { + p.lock.Lock() + defer p.lock.Unlock() + + if p.seal != nil { + panic("dbus proxy sealed twice") + } + + if session == nil && system == nil { + return errors.New("no configuration to seal") + } + + seal := helper.NewArgs() + + var args []string + if session != nil { + args = append(args, session.Args(p.session)...) + } + if system != nil { + args = append(args, system.Args(p.system)...) + } + if err := seal.Seal(args); err != nil { + return err + } + + p.seal = seal + return nil +} + +// New returns a reference to a new unsealed Proxy. +func New(binPath string, session, system [2]string) *Proxy { + return &Proxy{path: binPath, session: session, system: system} +} diff --git a/dbus/run.go b/dbus/run.go index 315b31de..cc776063 100644 --- a/dbus/run.go +++ b/dbus/run.go @@ -43,7 +43,7 @@ func (p *Proxy) Start(ready *chan bool) error { statsP, argsP := p.statP[0], p.argsP[1] - if _, err := argsP.Write([]byte(*p.seal)); err != nil { + if _, err := p.seal.WriteTo(argsP); err != nil { if err1 := p.cmd.Process.Kill(); err1 != nil { panic(err1) } diff --git a/dbus/setup.go b/dbus/setup.go deleted file mode 100644 index 2fb7c797..00000000 --- a/dbus/setup.go +++ /dev/null @@ -1,84 +0,0 @@ -package dbus - -import ( - "errors" - "os" - "os/exec" - "strings" - "sync" -) - -// Proxy holds references to a xdg-dbus-proxy process, and should never be copied. -// Once sealed, configuration changes will no longer be possible and attempting to do so will result in a panic. -type Proxy struct { - cmd *exec.Cmd - - statP [2]*os.File - argsP [2]*os.File - - path string - session [2]string - system [2]string - - wait *chan error - read *chan error - ready *chan bool - - seal *string - lock sync.RWMutex -} - -func (p *Proxy) String() string { - if p == nil { - return "(invalid dbus proxy)" - } - - p.lock.RLock() - defer p.lock.RUnlock() - - if p.cmd != nil { - return p.cmd.String() - } - - if p.seal != nil { - return *p.seal - } - - return "(unsealed dbus proxy)" -} - -// Seal seals the Proxy instance. -func (p *Proxy) Seal(session, system *Config) error { - p.lock.Lock() - defer p.lock.Unlock() - - if p.seal != nil { - panic("dbus proxy sealed twice") - } - - if session == nil && system == nil { - return errors.New("no configuration to seal") - } - - seal := strings.Builder{} - - if session != nil { - if err := session.buildSeal(&seal, p.session); err != nil { - return err - } - } - if system != nil { - if err := system.buildSeal(&seal, p.system); err != nil { - return err - } - } - - v := seal.String() - p.seal = &v - return nil -} - -// New returns a reference to a new unsealed Proxy. -func New(binPath string, session, system [2]string) *Proxy { - return &Proxy{path: binPath, session: session, system: system} -} diff --git a/helper/args.go b/helper/args.go new file mode 100644 index 00000000..bbb66f0c --- /dev/null +++ b/helper/args.go @@ -0,0 +1,97 @@ +package helper + +import ( + "bytes" + "errors" + "fmt" + "io" + "strings" + "sync" +) + +var ( + ErrContainsNull = errors.New("argument contains null character") +) + +// Args is sealed with a slice of arguments for writing to the helper args FD. +// The sealing args is checked to not contain null characters. +// Attempting to seal an instance twice will cause a panic. +type Args interface { + Seal(args []string) error + io.WriterTo + fmt.Stringer +} + +// argsFD implements Args for helpers expecting null terminated arguments to a file descriptor. +// argsFD must not be copied after first use. +type argsFD struct { + seal []byte + sync.RWMutex +} + +func (a *argsFD) Seal(args []string) error { + a.Lock() + defer a.Unlock() + + if a.seal != nil { + panic("args sealed twice") + } + + seal := bytes.Buffer{} + + n := 0 + for _, arg := range args { + // reject argument strings containing null + if hasNull(arg) { + return ErrContainsNull + } + + // accumulate buffer size + n += len(arg) + 1 + } + seal.Grow(n) + + // write null terminated arguments + for _, arg := range args { + seal.WriteString(arg) + seal.WriteByte('\x00') + } + + a.seal = seal.Bytes() + return nil +} + +func (a *argsFD) WriteTo(w io.Writer) (int64, error) { + if a.seal == nil { + panic("attempted to activate unsealed args") + } + + n, err := w.Write(a.seal) + return int64(n), err +} + +func (a *argsFD) String() string { + if a == nil { + return "(invalid helper args)" + } + + if a.seal == nil { + return "(unsealed helper args)" + } + + return strings.ReplaceAll(string(a.seal), "\x00", " ") +} + +func hasNull(s string) bool { + for _, b := range s { + if b == '\x00' { + return true + } + } + return false +} + +// NewArgs returns a new instance of Args +func NewArgs() Args { + return new(argsFD) +} diff --git a/helper/helper.go b/helper/helper.go new file mode 100644 index 00000000..d9bcc7f4 --- /dev/null +++ b/helper/helper.go @@ -0,0 +1,4 @@ +/* +Package helper runs external helpers and manages their status and args FDs. +*/ +package helper -- cgit v1.3.1