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 <cat@ophivana.moe>
This commit is contained in:
Ophestra Umiker 2024-09-24 16:11:08 +09:00
parent 1cb90c0840
commit 000607da5f
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
4 changed files with 114 additions and 13 deletions

View File

@ -4,8 +4,9 @@ import (
"errors" "errors"
"os" "os"
"os/exec" "os/exec"
"strings"
"sync" "sync"
"git.ophivana.moe/cat/fortify/helper"
) )
// Proxy holds references to a xdg-dbus-proxy process, and should never be copied. // Proxy holds references to a xdg-dbus-proxy process, and should never be copied.
@ -24,7 +25,7 @@ type Proxy struct {
read *chan error read *chan error
ready *chan bool ready *chan bool
seal *string seal helper.Args
lock sync.RWMutex lock sync.RWMutex
} }
@ -41,7 +42,7 @@ func (p *Proxy) String() string {
} }
if p.seal != nil { if p.seal != nil {
return *p.seal return p.seal.String()
} }
return "(unsealed dbus proxy)" return "(unsealed dbus proxy)"
@ -60,21 +61,20 @@ func (p *Proxy) Seal(session, system *Config) error {
return errors.New("no configuration to seal") return errors.New("no configuration to seal")
} }
seal := strings.Builder{} seal := helper.NewArgs()
var args []string
if session != nil { if session != nil {
if err := session.buildSeal(&seal, p.session); err != nil { args = append(args, session.Args(p.session)...)
return err
}
} }
if system != nil { if system != nil {
if err := system.buildSeal(&seal, p.system); err != nil { args = append(args, system.Args(p.system)...)
return err }
} if err := seal.Seal(args); err != nil {
return err
} }
v := seal.String() p.seal = seal
p.seal = &v
return nil return nil
} }

View File

@ -43,7 +43,7 @@ func (p *Proxy) Start(ready *chan bool) error {
statsP, argsP := p.statP[0], p.argsP[1] 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 { if err1 := p.cmd.Process.Kill(); err1 != nil {
panic(err1) panic(err1)
} }

97
helper/args.go Normal file
View File

@ -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)
}

4
helper/helper.go Normal file
View File

@ -0,0 +1,4 @@
/*
Package helper runs external helpers and manages their status and args FDs.
*/
package helper