diff --git a/dbus/dbus_test.go b/dbus/dbus_test.go index 0b264dc..b8391c3 100644 --- a/dbus/dbus_test.go +++ b/dbus/dbus_test.go @@ -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) +} diff --git a/dbus/proc.go b/dbus/proc.go index 15f02ac..446ca90 100644 --- a/dbus/proc.go +++ b/dbus/proc.go @@ -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) - } -} diff --git a/dbus/proxy.go b/dbus/proxy.go index e7785fd..209ae57 100644 --- a/dbus/proxy.go +++ b/dbus/proxy.go @@ -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 } diff --git a/dbus/samples_test.go b/dbus/samples_test.go index 75ed022..31c163d 100644 --- a/dbus/samples_test.go +++ b/dbus/samples_test.go @@ -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{ diff --git a/helper/stub.go b/helper/stub.go index 942f150..21ab5f4 100644 --- a/helper/stub.go +++ b/helper/stub.go @@ -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()) } diff --git a/system/dbus.go b/system/dbus.go index 5f62b2e..b44936c 100644 --- a/system/dbus.go +++ b/system/dbus.go @@ -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() }