diff --git a/dbus/dbus_test.go b/dbus/dbus_test.go index 57a441a..0b264dc 100644 --- a/dbus/dbus_test.go +++ b/dbus/dbus_test.go @@ -3,6 +3,9 @@ package dbus_test import ( "context" "errors" + "fmt" + "os" + "os/exec" "strings" "testing" "time" @@ -100,12 +103,13 @@ func TestProxy_Seal(t *testing.T) { } func TestProxy_Start_Wait_Close_String(t *testing.T) { - t.Run("sandboxed", func(t *testing.T) { + t.Run("sandbox", func(t *testing.T) { + proxyName := dbus.ProxyName + dbus.ProxyName = os.Args[0] + t.Cleanup(func() { dbus.ProxyName = proxyName }) testProxyStartWaitCloseString(t, true) }) - t.Run("direct", func(t *testing.T) { - testProxyStartWaitCloseString(t, false) - }) + t.Run("direct", func(t *testing.T) { testProxyStartWaitCloseString(t, false) }) } func testProxyStartWaitCloseString(t *testing.T, sandbox bool) { @@ -125,14 +129,30 @@ func testProxyStartWaitCloseString(t *testing.T, sandbox bool) { }) t.Run("proxy for "+id, func(t *testing.T) { - helper.InternalReplaceExecCommand(t) - overridePath(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") + } else { + cmd.Args = append([]string{os.Args[0], "-test.run=TestHelperStub", "--"}, cmd.Args[1:]...) + } + } output := new(strings.Builder) - t.Run("unsealed behaviour of "+id, func(t *testing.T) { - t.Run("unsealed string of "+id, func(t *testing.T) { + t.Run("unsealed", func(t *testing.T) { + t.Run("string", func(t *testing.T) { want := "(unsealed dbus proxy)" if got := p.String(); got != want { t.Errorf("String() = %v, want %v", @@ -141,7 +161,7 @@ func testProxyStartWaitCloseString(t *testing.T, sandbox bool) { } }) - t.Run("unsealed start of "+id, func(t *testing.T) { + t.Run("start", func(t *testing.T) { want := "proxy not sealed" if err := p.Start(context.Background(), nil, sandbox); err == nil || err.Error() != want { t.Errorf("Start() error = %v, wantErr %q", @@ -150,7 +170,7 @@ func testProxyStartWaitCloseString(t *testing.T, sandbox bool) { } }) - t.Run("unsealed wait of "+id, func(t *testing.T) { + t.Run("wait", func(t *testing.T) { wantErr := "dbus: not started" if err := p.Wait(); err == nil || err.Error() != wantErr { t.Errorf("Wait() error = %v, wantErr %v", @@ -168,7 +188,7 @@ func testProxyStartWaitCloseString(t *testing.T, sandbox bool) { } }) - t.Run("sealed behaviour of "+id, func(t *testing.T) { + t.Run("sealed", func(t *testing.T) { want := strings.Join(append(tc[0].want, tc[1].want...), " ") if got := p.String(); got != want { t.Errorf("String() = %v, want %v", @@ -176,7 +196,7 @@ func testProxyStartWaitCloseString(t *testing.T, sandbox bool) { return } - t.Run("sealed start of "+id, func(t *testing.T) { + t.Run("start", func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -185,8 +205,14 @@ func testProxyStartWaitCloseString(t *testing.T, sandbox bool) { err) } - t.Run("started string of "+id, func(t *testing.T) { - wantSubstr := dbus.ProxyName + " --args=" + 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 got := p.String(); !strings.Contains(got, wantSubstr) { t.Errorf("String() = %v, want %v", p.String(), wantSubstr) @@ -194,7 +220,7 @@ func testProxyStartWaitCloseString(t *testing.T, sandbox bool) { } }) - t.Run("started wait of "+id, func(t *testing.T) { + t.Run("wait", func(t *testing.T) { p.Close() if err := p.Wait(); err != nil { t.Errorf("Wait() error = %v\noutput: %s", @@ -206,11 +232,3 @@ func testProxyStartWaitCloseString(t *testing.T, sandbox bool) { }) } } - -func overridePath(t *testing.T) { - proxyName := dbus.ProxyName - dbus.ProxyName = "/nonexistent-xdg-dbus-proxy" - t.Cleanup(func() { - dbus.ProxyName = proxyName - }) -} diff --git a/dbus/run.go b/dbus/proc.go similarity index 67% rename from dbus/run.go rename to dbus/proc.go index 30b06f4..379f9fd 100644 --- a/dbus/run.go +++ b/dbus/proc.go @@ -8,6 +8,7 @@ import ( "os/exec" "path" "path/filepath" + "slices" "strconv" "strings" "syscall" @@ -26,25 +27,12 @@ func (p *Proxy) Start(ctx context.Context, output io.Writer, sandbox bool) error return errors.New("proxy not sealed") } - var ( - h helper.Helper - - argF = func(argsFd, statFd int) []string { - if statFd == -1 { - return []string{"--args=" + strconv.Itoa(argsFd)} - } else { - return []string{"--args=" + strconv.Itoa(argsFd), "--fd=" + strconv.Itoa(statFd)} - } - } - ) + var h helper.Helper c, cancel := context.WithCancelCause(ctx) if !sandbox { h = helper.NewDirect(c, p.name, p.seal, true, argF, func(cmd *exec.Cmd) { - cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} - if output != nil { - cmd.Stdout, cmd.Stderr = output, output - } + cmdF(cmd, output, p.CmdF) // xdg-dbus-proxy does not need to inherit the environment cmd.Env = make([]string, 0) @@ -62,7 +50,7 @@ func (p *Proxy) Start(ctx context.Context, output io.Writer, sandbox bool) error // resolve libraries by parsing ldd output var proxyDeps []*ldd.Entry - if toolPath != "/nonexistent-xdg-dbus-proxy" { + if toolPath != os.Args[0] { if l, err := ldd.Exec(ctx, toolPath); err != nil { return err } else { @@ -71,7 +59,6 @@ func (p *Proxy) Start(ctx context.Context, output io.Writer, sandbox bool) error } bc := &bwrap.Config{ - Unshare: nil, Hostname: "fortify-dbus", Chdir: "/", Syscall: &bwrap.SyscallPolicy{DenyDevel: true, Multiarch: true}, @@ -81,28 +68,35 @@ func (p *Proxy) Start(ctx context.Context, output io.Writer, sandbox bool) error } // resolve proxy socket directories - bindTarget := make(map[string]struct{}, 2) + bindTargetM := make(map[string]struct{}, 2) + for _, ps := range []string{p.session[1], p.system[1]} { if pd := path.Dir(ps); len(pd) > 0 { if pd[0] == '/' { - bindTarget[pd] = struct{}{} + bindTargetM[pd] = struct{}{} } } } - for k := range bindTarget { - bc.Bind(k, k, false, true) + + bindTarget := make([]string, 0, len(bindTargetM)) + for k := range bindTargetM { + bindTarget = append(bindTarget, k) + } + slices.Sort(bindTarget) + for _, name := range bindTarget { + bc.Bind(name, name, false, true) } - roBindTarget := make(map[string]struct{}, 2+1+len(proxyDeps)) + roBindTargetM := make(map[string]struct{}, 2+1+len(proxyDeps)) // xdb-dbus-proxy bin and dependencies - roBindTarget[path.Dir(toolPath)] = struct{}{} + roBindTargetM[path.Dir(toolPath)] = struct{}{} for _, ent := range proxyDeps { if path.IsAbs(ent.Path) { - roBindTarget[path.Dir(ent.Path)] = struct{}{} + roBindTargetM[path.Dir(ent.Path)] = struct{}{} } if path.IsAbs(ent.Name) { - roBindTarget[path.Dir(ent.Name)] = struct{}{} + roBindTargetM[path.Dir(ent.Name)] = struct{}{} } } @@ -110,20 +104,25 @@ func (p *Proxy) Start(ctx context.Context, output io.Writer, sandbox bool) error for _, as := range []string{p.session[0], p.system[0]} { if len(as) > 0 && strings.HasPrefix(as, "unix:path=/") { // leave / intact - roBindTarget[path.Dir(as[10:])] = struct{}{} + roBindTargetM[path.Dir(as[10:])] = struct{}{} } } - for k := range roBindTarget { - bc.Bind(k, k) + roBindTarget := make([]string, 0, len(roBindTargetM)) + for k := range roBindTargetM { + roBindTarget = append(roBindTarget, k) + } + slices.Sort(roBindTarget) + for _, name := range roBindTarget { + bc.Bind(name, name) } - h = helper.MustNewBwrap(c, toolPath, p.seal, true, argF, func(cmd *exec.Cmd) { - cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} - if output != nil { - cmd.Stdout, cmd.Stderr = output, output - } - }, nil, bc, nil) + h = helper.MustNewBwrap(c, toolPath, + p.seal, true, + argF, func(cmd *exec.Cmd) { cmdF(cmd, output, p.CmdF) }, + nil, + bc, nil, + ) p.bwrap = bc } @@ -182,3 +181,21 @@ func (p *Proxy) Close() { p.cancel(proxyClosed) p.cancel = nil } + +func argF(argsFd, statFd int) []string { + if statFd == -1 { + return []string{"--args=" + strconv.Itoa(argsFd)} + } else { + 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 5e92e34..e7785fd 100644 --- a/dbus/proxy.go +++ b/dbus/proxy.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "os/exec" "sync" "git.gensokyo.uk/security/fortify/helper" @@ -26,6 +27,7 @@ type Proxy struct { name string session [2]string system [2]string + CmdF func(cmd *exec.Cmd) sysP bool seal io.WriterTo diff --git a/dbus/samples_test.go b/dbus/samples_test.go index 653a02f..75ed022 100644 --- a/dbus/samples_test.go +++ b/dbus/samples_test.go @@ -6,6 +6,12 @@ import ( "git.gensokyo.uk/security/fortify/dbus" ) +const ( + sampleHostPath = "/run/user/1971/bus" + sampleHostAddr = "unix:path=" + sampleHostPath + sampleBindPath = "/tmp/fortify.1971/12622d846cc3fe7b4c10359d01f0eb47/bus" +) + var samples = []dbusTestCase{ { "org.chromium.Chromium", &dbus.Config{ @@ -19,10 +25,10 @@ var samples = []dbusTestCase{ Log: false, Filter: true, }, false, false, - [2]string{"unix:path=/run/user/1971/bus", "/tmp/fortify.1971/12622d846cc3fe7b4c10359d01f0eb47/bus"}, + [2]string{sampleHostAddr, sampleBindPath}, []string{ - "unix:path=/run/user/1971/bus", - "/tmp/fortify.1971/12622d846cc3fe7b4c10359d01f0eb47/bus", + sampleHostAddr, + sampleBindPath, "--filter", "--talk=org.freedesktop.Notifications", "--talk=org.freedesktop.FileManager1", @@ -48,9 +54,10 @@ var samples = []dbusTestCase{ Log: false, Filter: true, }, false, false, - [2]string{"unix:path=/run/dbus/system_bus_socket", "/tmp/fortify.1971/12622d846cc3fe7b4c10359d01f0eb47/system_bus_socket"}, - []string{"unix:path=/run/dbus/system_bus_socket", - "/tmp/fortify.1971/12622d846cc3fe7b4c10359d01f0eb47/system_bus_socket", + [2]string{sampleHostAddr, sampleBindPath}, + []string{ + sampleHostAddr, + sampleBindPath, "--filter", "--talk=org.bluez", "--talk=org.freedesktop.Avahi", @@ -68,10 +75,10 @@ var samples = []dbusTestCase{ Log: false, Filter: true, }, false, false, - [2]string{"unix:path=/run/user/1971/bus", "/tmp/fortify.1971/34c24f16a0d791d28835ededaf446033/bus"}, + [2]string{sampleHostAddr, sampleBindPath}, []string{ - "unix:path=/run/user/1971/bus", - "/tmp/fortify.1971/34c24f16a0d791d28835ededaf446033/bus", + sampleHostAddr, + sampleBindPath, "--filter", "--talk=org.freedesktop.Notifications", "--talk=org.kde.StatusNotifierWatcher", @@ -91,10 +98,10 @@ var samples = []dbusTestCase{ Log: true, Filter: true, }, false, false, - [2]string{"unix:path=/run/user/1971/bus", "/tmp/fortify.1971/5da7845287a936efbc2fa75d7d81e501/bus"}, + [2]string{sampleHostAddr, sampleBindPath}, []string{ - "unix:path=/run/user/1971/bus", - "/tmp/fortify.1971/5da7845287a936efbc2fa75d7d81e501/bus", + sampleHostAddr, + sampleBindPath, "--filter", "--see=uk.gensokyo.CrashTestDummy1", "--talk=org.freedesktop.Notifications", @@ -114,10 +121,10 @@ var samples = []dbusTestCase{ Log: true, Filter: true, }, false, true, - [2]string{"unix:path=/run/user/1971/bus", "/tmp/fortify.1971/5da7845287a936efbc2fa75d7d81e501/bus"}, + [2]string{sampleHostAddr, sampleBindPath}, []string{ - "unix:path=/run/user/1971/bus", - "/tmp/fortify.1971/5da7845287a936efbc2fa75d7d81e501/bus", + sampleHostAddr, + sampleBindPath, "--filter", "--see=uk.gensokyo.CrashTestDummy", "--talk=org.freedesktop.Notifications", diff --git a/dbus/stub_test.go b/dbus/stub_test.go index b83deb2..13a7d1d 100644 --- a/dbus/stub_test.go +++ b/dbus/stub_test.go @@ -6,6 +6,4 @@ import ( "git.gensokyo.uk/security/fortify/helper" ) -func TestHelperChildStub(t *testing.T) { - helper.InternalChildStub() -} +func TestHelperStub(t *testing.T) { helper.InternalHelperStub() } diff --git a/helper/bwrap_test.go b/helper/bwrap_test.go index 550ff34..be0a465 100644 --- a/helper/bwrap_test.go +++ b/helper/bwrap_test.go @@ -3,6 +3,8 @@ package helper_test import ( "context" "errors" + "fmt" + "io" "os" "os/exec" "strings" @@ -17,7 +19,7 @@ func TestBwrap(t *testing.T) { sc := &bwrap.Config{ Net: true, Hostname: "localhost", - Chdir: "/nonexistent", + Chdir: "/proc/nonexistent", Clearenv: true, NewSession: true, DieWithParent: true, @@ -26,14 +28,12 @@ func TestBwrap(t *testing.T) { t.Run("nonexistent bwrap name", func(t *testing.T) { bubblewrapName := helper.BubblewrapName - helper.BubblewrapName = "/nonexistent" - t.Cleanup(func() { - helper.BubblewrapName = bubblewrapName - }) + helper.BubblewrapName = "/proc/nonexistent" + t.Cleanup(func() { helper.BubblewrapName = bubblewrapName }) h := helper.MustNewBwrap( context.Background(), - "fortify", + "false", argsWt, false, argF, nil, nil, @@ -49,14 +49,14 @@ func TestBwrap(t *testing.T) { t.Run("valid new helper nil check", func(t *testing.T) { if got := helper.MustNewBwrap( context.TODO(), - "fortify", + "false", argsWt, false, argF, nil, nil, sc, nil, ); got == nil { t.Errorf("MustNewBwrap(%#v, %#v, %#v) got nil", - sc, argsWt, "fortify") + sc, argsWt, "false") return } }) @@ -72,7 +72,7 @@ func TestBwrap(t *testing.T) { helper.MustNewBwrap( context.TODO(), - "fortify", + "false", argsWt, false, argF, nil, nil, @@ -81,15 +81,13 @@ func TestBwrap(t *testing.T) { }) t.Run("start without pipes", func(t *testing.T) { - helper.InternalReplaceExecCommand(t) - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() stdout, stderr := new(strings.Builder), new(strings.Builder) h := helper.MustNewBwrap( - ctx, "crash-test-dummy", + ctx, os.Args[0], nil, false, - argFChecked, func(cmd *exec.Cmd) { cmd.Stdout, cmd.Stderr = stdout, stderr }, + argFChecked, func(cmd *exec.Cmd) { cmd.Stdout, cmd.Stderr = stdout, stderr; hijackBwrap(cmd) }, nil, sc, nil, ) @@ -107,14 +105,23 @@ func TestBwrap(t *testing.T) { }) t.Run("implementation compliance", func(t *testing.T) { - testHelper(t, func(ctx context.Context, cmdF func(cmd *exec.Cmd), stat bool) helper.Helper { + testHelper(t, func(ctx context.Context, setOutput func(stdoutP, stderrP *io.Writer), stat bool) helper.Helper { return helper.MustNewBwrap( - ctx, "crash-test-dummy", + ctx, os.Args[0], argsWt, stat, - argF, cmdF, + argF, func(cmd *exec.Cmd) { setOutput(&cmd.Stdout, &cmd.Stderr); hijackBwrap(cmd) }, nil, sc, nil, ) - }) + }, "exec") }) } + +func hijackBwrap(cmd *exec.Cmd) { + if cmd.Args[0] != "bwrap" { + panic(fmt.Sprintf("unexpected argv0 %q", cmd.Args[0])) + } + cmd.Err = nil + cmd.Path = os.Args[0] + cmd.Args = append([]string{os.Args[0], "-test.run=TestHelperStub", "--"}, cmd.Args...) +} diff --git a/helper/cmd.go b/helper/cmd.go index 5b66afe..4fe978a 100644 --- a/helper/cmd.go +++ b/helper/cmd.go @@ -42,7 +42,7 @@ func newHelperCmd( ) (cmd *helperCmd, args []string) { cmd = new(helperCmd) cmd.helperFiles, args = newHelperFiles(ctx, wt, stat, argF, extraFiles) - cmd.Cmd = commandContext(ctx, name) + cmd.Cmd = exec.CommandContext(ctx, name) cmd.Cmd.Cancel = func() error { return cmd.Process.Signal(syscall.SIGTERM) } cmd.WaitDelay = WaitDelay return diff --git a/helper/cmd_test.go b/helper/cmd_test.go index 60c0caf..9c64098 100644 --- a/helper/cmd_test.go +++ b/helper/cmd_test.go @@ -3,6 +3,7 @@ package helper_test import ( "context" "errors" + "io" "os" "os/exec" "testing" @@ -10,9 +11,9 @@ import ( "git.gensokyo.uk/security/fortify/helper" ) -func TestDirect(t *testing.T) { +func TestCmd(t *testing.T) { t.Run("start non-existent helper path", func(t *testing.T) { - h := helper.NewDirect(context.Background(), "/nonexistent", argsWt, false, argF, nil, nil) + h := helper.NewDirect(context.Background(), "/proc/nonexistent", argsWt, false, argF, nil, nil) if err := h.Start(); !errors.Is(err, os.ErrNotExist) { t.Errorf("Start: error = %v, wantErr %v", @@ -22,15 +23,17 @@ func TestDirect(t *testing.T) { t.Run("valid new helper nil check", func(t *testing.T) { if got := helper.NewDirect(context.TODO(), "fortify", argsWt, false, argF, nil, nil); got == nil { - t.Errorf("New(%q, %q) got nil", + t.Errorf("NewDirect(%q, %q) got nil", argsWt, "fortify") return } }) t.Run("implementation compliance", func(t *testing.T) { - testHelper(t, func(ctx context.Context, cmdF func(cmd *exec.Cmd), stat bool) helper.Helper { - return helper.NewDirect(ctx, "crash-test-dummy", argsWt, stat, argF, cmdF, nil) - }) + testHelper(t, func(ctx context.Context, setOutput func(stdoutP, stderrP *io.Writer), stat bool) helper.Helper { + return helper.NewDirect(ctx, os.Args[0], argsWt, stat, argF, func(cmd *exec.Cmd) { + setOutput(&cmd.Stdout, &cmd.Stderr) + }, nil) + }, "exec") }) } diff --git a/helper/helper.go b/helper/helper.go index ac45ac7..8a25fee 100644 --- a/helper/helper.go +++ b/helper/helper.go @@ -6,17 +6,12 @@ import ( "fmt" "io" "os" - "os/exec" "time" "git.gensokyo.uk/security/fortify/helper/proc" ) -var ( - WaitDelay = 2 * time.Second - - commandContext = exec.CommandContext -) +var WaitDelay = 2 * time.Second const ( // FortifyHelper is set to 1 when args fd is enabled and 0 otherwise. diff --git a/helper/helper_test.go b/helper/helper_test.go index 3274fe5..4bc98b5 100644 --- a/helper/helper_test.go +++ b/helper/helper_test.go @@ -4,7 +4,7 @@ import ( "context" "errors" "fmt" - "os/exec" + "io" "strconv" "strings" "testing" @@ -36,7 +36,8 @@ func argF(argsFd, statFd int) []string { } func argFChecked(argsFd, statFd int) (args []string) { - args = make([]string, 0, 4) + args = make([]string, 0, 6) + args = append(args, "-test.run=TestHelperStub", "--") if argsFd > -1 { args = append(args, "--args", strconv.Itoa(argsFd)) } @@ -47,13 +48,14 @@ func argFChecked(argsFd, statFd int) (args []string) { } // this function tests an implementation of the helper.Helper interface -func testHelper(t *testing.T, createHelper func(ctx context.Context, cmdF func(cmd *exec.Cmd), stat bool) helper.Helper) { - helper.InternalReplaceExecCommand(t) - +func testHelper(t *testing.T, + createHelper func(ctx context.Context, setOutput func(stdoutP, stderrP *io.Writer), stat bool) helper.Helper, + prefix string, +) { t.Run("start helper with status channel and wait", func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) stdout, stderr := new(strings.Builder), new(strings.Builder) - h := createHelper(ctx, func(cmd *exec.Cmd) { cmd.Stdout, cmd.Stderr = stdout, stderr }, true) + h := createHelper(ctx, func(stdoutP, stderrP *io.Writer) { *stdoutP, *stderrP = stdout, stderr }, true) t.Run("wait not yet started helper", func(t *testing.T) { defer func() { @@ -75,7 +77,7 @@ func testHelper(t *testing.T, createHelper func(ctx context.Context, cmdF func(c cancel() t.Run("start already started helper", func(t *testing.T) { - wantErr := "exec: already started" + wantErr := prefix + ": already started" if err := h.Start(); err != nil && err.Error() != wantErr { t.Errorf("Start: error = %v, wantErr %v", err, wantErr) @@ -108,7 +110,7 @@ func testHelper(t *testing.T, createHelper func(ctx context.Context, cmdF func(c ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() stdout, stderr := new(strings.Builder), new(strings.Builder) - h := createHelper(ctx, func(cmd *exec.Cmd) { cmd.Stdout, cmd.Stderr = stdout, stderr }, false) + h := createHelper(ctx, func(stdoutP, stderrP *io.Writer) { *stdoutP, *stderrP = stdout, stderr }, false) if err := h.Start(); err != nil { t.Errorf("Start() error = %v", diff --git a/helper/stub.go b/helper/stub.go index 12daa9f..f64263c 100644 --- a/helper/stub.go +++ b/helper/stub.go @@ -1,25 +1,24 @@ package helper import ( - "context" "flag" "fmt" "io" "os" - "os/exec" + "path" + "slices" "strconv" "strings" "syscall" - "testing" "git.gensokyo.uk/security/fortify/helper/bwrap" "git.gensokyo.uk/security/fortify/helper/proc" "git.gensokyo.uk/security/fortify/internal" ) -// InternalChildStub is an internal function but exported because it is cross-package; +// InternalHelperStub is an internal function but exported because it is cross-package; // it is part of the implementation of the helper stub. -func InternalChildStub() { +func InternalHelperStub() { // this test mocks the helper process var ap, sp string if v, ok := os.LookupEnv(FortifyHelper); !ok { @@ -33,32 +32,15 @@ func InternalChildStub() { sp = v } - switch os.Args[3] { - case "bwrap": + if len(os.Args) > 3 && os.Args[3] == "bwrap" { bwrapStub() - default: - genericStub(flagRestoreFiles(4, ap, sp)) + } else { + genericStub(flagRestoreFiles(3, ap, sp)) } internal.Exit(0) } -// InternalReplaceExecCommand is an internal function but exported because it is cross-package; -// it is part of the implementation of the helper stub. -func InternalReplaceExecCommand(t *testing.T) { - t.Cleanup(func() { commandContext = exec.CommandContext }) - - // replace execCommand to have the resulting *exec.Cmd launch TestHelperChildStub - commandContext = func(ctx context.Context, name string, arg ...string) *exec.Cmd { - // pass through nonexistent path - if name == "/nonexistent" && len(arg) == 0 { - return exec.CommandContext(ctx, name) - } - - return exec.CommandContext(ctx, os.Args[0], append([]string{"-test.run=TestHelperChildStub", "--", name}, arg...)...) - } -} - func newFile(fd int, name, p string) *os.File { present := false switch p { @@ -149,26 +131,54 @@ func bwrapStub() { sc := &bwrap.Config{ Net: true, Hostname: "localhost", - Chdir: "/nonexistent", + Chdir: "/proc/nonexistent", Clearenv: true, NewSession: true, DieWithParent: true, AsInit: true, } - if _, err := MustNewCheckedArgs(sc.Args(nil, new(proc.ExtraFilesPre), new([]proc.File))). + + 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 + + bindTarget := []string{"/tmp/fortify.1971/12622d846cc3fe7b4c10359d01f0eb47"} + slices.Sort(bindTarget) + for _, name := range bindTarget { + sc.Bind(name, name, false, true) + } + roBindTarget := []string{"/run/user/1971", path.Dir(os.Args[0])} + slices.Sort(roBindTarget) + for _, name := range roBindTarget { + sc.Bind(name, name) + } + + // manipulate extra files list so fd ends up as 5 + efp.Append() + efp.Append() + } + } + + if _, err := MustNewCheckedArgs(sc.Args(nil, efp, new([]proc.File))). WriteTo(want); err != nil { panic("cannot read want: " + err.Error()) } - if len(flag.CommandLine.Args()) > 0 && flag.CommandLine.Args()[0] == "crash-test-dummy" && got.String() != want.String() { + if got.String() != want.String() { panic("bad bwrap args\ngot: " + got.String() + "\nwant: " + want.String()) } }() if err := syscall.Exec( - os.Args[0], - append([]string{os.Args[0], "-test.run=TestHelperChildStub", "--"}, flag.CommandLine.Args()...), + flag.CommandLine.Args()[0], + flag.CommandLine.Args(), os.Environ()); err != nil { - panic("cannot start general stub: " + err.Error()) + panic("cannot start helper stub: " + err.Error()) } } diff --git a/helper/stub_test.go b/helper/stub_test.go index 7720c0e..1de830e 100644 --- a/helper/stub_test.go +++ b/helper/stub_test.go @@ -6,6 +6,4 @@ import ( "git.gensokyo.uk/security/fortify/helper" ) -func TestHelperChildStub(t *testing.T) { - helper.InternalChildStub() -} +func TestHelperStub(t *testing.T) { helper.InternalHelperStub() }