dbus: run in native sandbox
All checks were successful
Test / Create distribution (push) Successful in 24s
Test / Fortify (push) Successful in 2m31s
Test / Fpkg (push) Successful in 3m25s
Test / Data race detector (push) Successful in 4m5s
Test / Flake checks (push) Successful in 53s

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
Ophestra 2025-03-17 00:13:14 +09:00
parent bc54db54d2
commit 44277dc0f1
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
6 changed files with 122 additions and 124 deletions

View File

@ -1,6 +1,7 @@
package dbus_test
import (
"bytes"
"context"
"errors"
"fmt"
@ -12,6 +13,8 @@ import (
"git.gensokyo.uk/security/fortify/dbus"
"git.gensokyo.uk/security/fortify/helper"
"git.gensokyo.uk/security/fortify/internal"
"git.gensokyo.uk/security/fortify/internal/sandbox"
)
func TestNew(t *testing.T) {
@ -103,6 +106,10 @@ func TestProxy_Seal(t *testing.T) {
}
func TestProxy_Start_Wait_Close_String(t *testing.T) {
oldWaitDelay := helper.WaitDelay
helper.WaitDelay = 16 * time.Second
t.Cleanup(func() { helper.WaitDelay = oldWaitDelay })
t.Run("sandbox", func(t *testing.T) {
proxyName := dbus.ProxyName
dbus.ProxyName = os.Args[0]
@ -112,7 +119,7 @@ func TestProxy_Start_Wait_Close_String(t *testing.T) {
t.Run("direct", func(t *testing.T) { testProxyStartWaitCloseString(t, false) })
}
func testProxyStartWaitCloseString(t *testing.T, sandbox bool) {
func testProxyStartWaitCloseString(t *testing.T, useSandbox bool) {
for id, tc := range testCasePairs() {
// this test does not test errors
if tc[0].wantErr {
@ -130,25 +137,28 @@ func testProxyStartWaitCloseString(t *testing.T, sandbox bool) {
t.Run("proxy for "+id, func(t *testing.T) {
p := dbus.New(tc[0].bus, tc[1].bus)
p.CmdF = func(cmd *exec.Cmd) {
wantArgv0 := dbus.ProxyName
if sandbox {
wantArgv0 = "bwrap"
}
if cmd.Args[0] != wantArgv0 {
panic(fmt.Sprintf("unexpected argv0 %q", os.Args[0]))
}
cmd.Err = nil
cmd.Path = os.Args[0]
if sandbox {
cmd.Args = append([]string{os.Args[0], "-test.run=TestHelperStub", "--"},
append(cmd.Args[:5], append([]string{"-test.run=TestHelperStub", "--"}, cmd.Args[5:]...)...)...)
cmd.Env = append(cmd.Env, "GO_TEST_FORTIFY_BWRAP_STUB_TYPE=dbus")
p.CommandContext = func(ctx context.Context) (cmd *exec.Cmd) {
return exec.CommandContext(ctx, os.Args[0], "-test.v",
"-test.run=TestHelperInit", "--", "init")
}
p.CmdF = func(v any) {
if useSandbox {
container := v.(*sandbox.Container)
if container.Args[0] != dbus.ProxyName {
panic(fmt.Sprintf("unexpected argv0 %q", os.Args[0]))
}
container.Args = append([]string{os.Args[0], "-test.run=TestHelperStub", "--"}, container.Args[1:]...)
} else {
cmd := v.(*exec.Cmd)
if cmd.Args[0] != dbus.ProxyName {
panic(fmt.Sprintf("unexpected argv0 %q", os.Args[0]))
}
cmd.Err = nil
cmd.Path = os.Args[0]
cmd.Args = append([]string{os.Args[0], "-test.run=TestHelperStub", "--"}, cmd.Args[1:]...)
}
}
p.FilterF = func(v []byte) []byte { return bytes.SplitN(v, []byte("TestHelperInit\n"), 2)[1] }
output := new(strings.Builder)
t.Run("unsealed", func(t *testing.T) {
@ -163,7 +173,7 @@ func testProxyStartWaitCloseString(t *testing.T, sandbox bool) {
t.Run("start", func(t *testing.T) {
want := "proxy not sealed"
if err := p.Start(context.Background(), nil, sandbox); err == nil || err.Error() != want {
if err := p.Start(context.Background(), nil, useSandbox); err == nil || err.Error() != want {
t.Errorf("Start() error = %v, wantErr %q",
err, errors.New(want))
return
@ -200,18 +210,15 @@ func testProxyStartWaitCloseString(t *testing.T, sandbox bool) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := p.Start(ctx, output, sandbox); err != nil {
if err := p.Start(ctx, output, useSandbox); err != nil {
t.Fatalf("Start(nil, nil) error = %v",
err)
}
t.Run("string", func(t *testing.T) {
wantSubstr := fmt.Sprintf("%s -test.run=TestHelperStub -- --args=3 --fd=4", os.Args[0])
if sandbox {
wantSubstr = fmt.Sprintf(
"%s -test.run=TestHelperStub -- bwrap --args 6 -- %s -test.run=TestHelperStub -- --args=3 --fd=4",
os.Args[0], os.Args[0],
)
if useSandbox {
wantSubstr = fmt.Sprintf(`argv: ["%s" "-test.run=TestHelperStub" "--" "--args=3" "--fd=4"], flags: 0x0, seccomp: 0x3e`, os.Args[0])
}
if got := p.String(); !strings.Contains(got, wantSubstr) {
t.Errorf("String() = %v, want %v",
@ -232,3 +239,10 @@ func testProxyStartWaitCloseString(t *testing.T, sandbox bool) {
})
}
}
func TestHelperInit(t *testing.T) {
if len(os.Args) != 5 || os.Args[4] != "init" {
return
}
sandbox.Init(internal.Exit)
}

View File

@ -14,12 +14,13 @@ import (
"syscall"
"git.gensokyo.uk/security/fortify/helper"
"git.gensokyo.uk/security/fortify/helper/bwrap"
"git.gensokyo.uk/security/fortify/internal/sandbox"
"git.gensokyo.uk/security/fortify/ldd"
"git.gensokyo.uk/security/fortify/seccomp"
)
// Start launches the D-Bus proxy.
func (p *Proxy) Start(ctx context.Context, output io.Writer, sandbox bool) error {
func (p *Proxy) Start(ctx context.Context, output io.Writer, useSandbox bool) error {
p.lock.Lock()
defer p.lock.Unlock()
@ -30,11 +31,15 @@ func (p *Proxy) Start(ctx context.Context, output io.Writer, sandbox bool) error
var h helper.Helper
c, cancel := context.WithCancelCause(ctx)
if !sandbox {
if !useSandbox {
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
if p.CmdF != nil {
p.CmdF(cmd)
}
if output != nil {
cmd.Stdout, cmd.Stderr = output, output
}
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
cmd.Env = make([]string, 0)
}, nil)
} else {
@ -47,64 +52,65 @@ func (p *Proxy) Start(ctx context.Context, output io.Writer, sandbox bool) error
}
}
bc := &bwrap.Config{
Hostname: "fortify-dbus",
Chdir: "/",
Syscall: &bwrap.SyscallPolicy{DenyDevel: true, Multiarch: true},
Clearenv: true,
NewSession: true,
DieWithParent: true,
var libPaths []string
if entries, err := ldd.ExecFilter(ctx, p.CommandContext, p.FilterF, toolPath); err != nil {
return err
} else {
libPaths = ldd.Path(entries)
}
// these lib paths are unpredictable, so mount them first so they cannot cover anything
if toolPath != os.Args[0] {
if entries, err := ldd.Exec(ctx, toolPath); err != nil {
return err
} else {
for _, name := range ldd.Path(entries) {
bc.Bind(name, name)
}
}
}
// upstream bus directories
upstreamPaths := make([]string, 0, 2)
for _, as := range []string{p.session[0], p.system[0]} {
if len(as) > 0 && strings.HasPrefix(as, "unix:path=/") {
// leave / intact
upstreamPaths = append(upstreamPaths, path.Dir(as[10:]))
}
}
slices.Sort(upstreamPaths)
upstreamPaths = slices.Compact(upstreamPaths)
for _, name := range upstreamPaths {
bc.Bind(name, name)
}
// parent directories of bind paths
sockDirPaths := make([]string, 0, 2)
if d := path.Dir(p.session[1]); path.IsAbs(d) {
sockDirPaths = append(sockDirPaths, d)
}
if d := path.Dir(p.system[1]); path.IsAbs(d) {
sockDirPaths = append(sockDirPaths, d)
}
slices.Sort(sockDirPaths)
sockDirPaths = slices.Compact(sockDirPaths)
for _, name := range sockDirPaths {
bc.Bind(name, name, false, true)
}
// xdg-dbus-proxy bin path
binPath := path.Dir(toolPath)
bc.Bind(binPath, binPath)
h = helper.MustNewBwrap(c, toolPath,
h = helper.New(
c, toolPath,
p.seal, true,
argF, func(cmd *exec.Cmd) { cmdF(cmd, output, p.CmdF) },
nil,
bc, nil,
)
p.bwrap = bc
argF, func(container *sandbox.Container) {
container.Seccomp |= seccomp.FlagMultiarch
container.Hostname = "fortify-dbus"
container.CommandContext = p.CommandContext
if output != nil {
container.Stdout, container.Stderr = output, output
}
if p.CmdF != nil {
p.CmdF(container)
}
// these lib paths are unpredictable, so mount them first so they cannot cover anything
for _, name := range libPaths {
container.Bind(name, name, 0)
}
// upstream bus directories
upstreamPaths := make([]string, 0, 2)
for _, as := range []string{p.session[0], p.system[0]} {
if len(as) > 0 && strings.HasPrefix(as, "unix:path=/") {
// leave / intact
upstreamPaths = append(upstreamPaths, path.Dir(as[10:]))
}
}
slices.Sort(upstreamPaths)
upstreamPaths = slices.Compact(upstreamPaths)
for _, name := range upstreamPaths {
container.Bind(name, name, 0)
}
// parent directories of bind paths
sockDirPaths := make([]string, 0, 2)
if d := path.Dir(p.session[1]); path.IsAbs(d) {
sockDirPaths = append(sockDirPaths, d)
}
if d := path.Dir(p.system[1]); path.IsAbs(d) {
sockDirPaths = append(sockDirPaths, d)
}
slices.Sort(sockDirPaths)
sockDirPaths = slices.Compact(sockDirPaths)
for _, name := range sockDirPaths {
container.Bind(name, name, sandbox.BindWritable)
}
// xdg-dbus-proxy bin path
binPath := path.Dir(toolPath)
container.Bind(binPath, binPath, 0)
}, nil)
}
if err := h.Start(); err != nil {
@ -170,13 +176,3 @@ func argF(argsFd, statFd int) []string {
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)
}
}

View File

@ -9,7 +9,6 @@ import (
"sync"
"git.gensokyo.uk/security/fortify/helper"
"git.gensokyo.uk/security/fortify/helper/bwrap"
)
// ProxyName is the file name or path to the proxy program.
@ -20,16 +19,18 @@ var ProxyName = "xdg-dbus-proxy"
// Once sealed, configuration changes will no longer be possible and attempting to do so will result in a panic.
type Proxy struct {
helper helper.Helper
bwrap *bwrap.Config
ctx context.Context
cancel context.CancelCauseFunc
name string
session [2]string
system [2]string
CmdF func(cmd *exec.Cmd)
CmdF func(any)
sysP bool
CommandContext func(ctx context.Context) (cmd *exec.Cmd)
FilterF func([]byte) []byte
seal io.WriterTo
lock sync.RWMutex
}

View File

@ -7,9 +7,9 @@ import (
)
const (
sampleHostPath = "/run/user/1971/bus"
sampleHostPath = "/tmp/bus"
sampleHostAddr = "unix:path=" + sampleHostPath
sampleBindPath = "/tmp/fortify.1971/12622d846cc3fe7b4c10359d01f0eb47/bus"
sampleBindPath = "/tmp/proxied_bus"
)
var samples = []dbusTestCase{

View File

@ -5,7 +5,6 @@ import (
"fmt"
"io"
"os"
"path"
"strconv"
"strings"
"syscall"
@ -137,27 +136,7 @@ func bwrapStub() {
AsInit: true,
}
efp := new(proc.ExtraFilesPre)
if t, ok := os.LookupEnv("GO_TEST_FORTIFY_BWRAP_STUB_TYPE"); ok {
switch t {
case "dbus":
sc.Net = false
sc.Hostname = "fortify-dbus"
sc.Chdir = "/"
sc.Syscall = &bwrap.SyscallPolicy{DenyDevel: true, Multiarch: true}
sc.AsInit = false
sc.
Bind("/run/user/1971", "/run/user/1971").
Bind("/tmp/fortify.1971/12622d846cc3fe7b4c10359d01f0eb47", "/tmp/fortify.1971/12622d846cc3fe7b4c10359d01f0eb47", false, true).
Bind(path.Dir(os.Args[0]), path.Dir(os.Args[0]))
// manipulate extra files list so fd ends up as 5
efp.Append()
efp.Append()
}
}
if _, err := MustNewCheckedArgs(sc.Args(nil, efp, new([]proc.File))).
if _, err := MustNewCheckedArgs(sc.Args(nil, new(proc.ExtraFilesPre), new([]proc.File))).
WriteTo(want); err != nil {
panic("cannot read want: " + err.Error())
}

View File

@ -88,7 +88,7 @@ func (d *DBus) apply(sys *I) error {
return sys.wrapErrSuffix(err,
"cannot start message bus proxy:")
}
sys.println("starting message bus proxy:", d.proxy)
sys.println("starting message bus proxy", d.proxy)
return nil
}
@ -139,7 +139,15 @@ func (s *scanToFmsg) write(p []byte, a int) (int, error) {
return a + n, nil
} else {
n, _ := s.msg.Write(p[:i])
s.msgbuf = append(s.msgbuf, s.msg.String())
// allow container init messages through
v := s.msg.String()
if strings.HasPrefix(v, "init: ") {
log.Println("(dbus) " + v)
} else {
s.msgbuf = append(s.msgbuf, v)
}
s.msg.Reset()
return s.write(p[i+1:], a+n+1)
}
@ -148,7 +156,7 @@ func (s *scanToFmsg) write(p []byte, a int) (int, error) {
func (s *scanToFmsg) Dump() {
s.mu.RLock()
for _, msg := range s.msgbuf {
log.Println(msg)
log.Println("(dbus) " + msg)
}
s.mu.RUnlock()
}