diff --git a/dbus/setup.go b/dbus/dbus.go similarity index 80% rename from dbus/setup.go rename to dbus/dbus.go index 2fb7c79..d0e82e1 100644 --- a/dbus/setup.go +++ b/dbus/dbus.go @@ -4,8 +4,9 @@ import ( "errors" "os" "os/exec" - "strings" "sync" + + "git.ophivana.moe/cat/fortify/helper" ) // Proxy holds references to a xdg-dbus-proxy process, and should never be copied. @@ -24,7 +25,7 @@ type Proxy struct { read *chan error ready *chan bool - seal *string + seal helper.Args lock sync.RWMutex } @@ -41,7 +42,7 @@ func (p *Proxy) String() string { } if p.seal != nil { - return *p.seal + return p.seal.String() } return "(unsealed dbus proxy)" @@ -60,21 +61,20 @@ func (p *Proxy) Seal(session, system *Config) error { return errors.New("no configuration to seal") } - seal := strings.Builder{} + seal := helper.NewArgs() + var args []string if session != nil { - if err := session.buildSeal(&seal, p.session); err != nil { - return err - } + args = append(args, session.Args(p.session)...) } if system != nil { - if err := system.buildSeal(&seal, p.system); err != nil { - return err - } + args = append(args, system.Args(p.system)...) + } + if err := seal.Seal(args); err != nil { + return err } - v := seal.String() - p.seal = &v + p.seal = seal return nil } diff --git a/dbus/run.go b/dbus/run.go index 315b31d..cc77606 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/helper/args.go b/helper/args.go new file mode 100644 index 0000000..bbb66f0 --- /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 0000000..d9bcc7f --- /dev/null +++ b/helper/helper.go @@ -0,0 +1,4 @@ +/* +Package helper runs external helpers and manages their status and args FDs. +*/ +package helper