helper: eliminate commandContext replacement
All checks were successful
Test / Create distribution (push) Successful in 26s
Test / Fortify (push) Successful in 2m44s
Test / Fpkg (push) Successful in 3m42s
Test / Data race detector (push) Successful in 3m51s
Test / Flake checks (push) Successful in 57s

This is done more cleanly by modifying Args in cmdF.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
Ophestra 2025-03-15 23:57:44 +09:00
parent bac4e67867
commit 6e7ddb2d2e
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
12 changed files with 205 additions and 148 deletions

View File

@ -3,6 +3,9 @@ package dbus_test
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"os"
"os/exec"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -100,12 +103,13 @@ func TestProxy_Seal(t *testing.T) {
} }
func TestProxy_Start_Wait_Close_String(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) testProxyStartWaitCloseString(t, true)
}) })
t.Run("direct", func(t *testing.T) { t.Run("direct", func(t *testing.T) { testProxyStartWaitCloseString(t, false) })
testProxyStartWaitCloseString(t, false)
})
} }
func testProxyStartWaitCloseString(t *testing.T, sandbox bool) { 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) { t.Run("proxy for "+id, func(t *testing.T) {
helper.InternalReplaceExecCommand(t)
overridePath(t)
p := dbus.New(tc[0].bus, tc[1].bus) 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) output := new(strings.Builder)
t.Run("unsealed behaviour of "+id, func(t *testing.T) { t.Run("unsealed", func(t *testing.T) {
t.Run("unsealed string of "+id, func(t *testing.T) { t.Run("string", func(t *testing.T) {
want := "(unsealed dbus proxy)" want := "(unsealed dbus proxy)"
if got := p.String(); got != want { if got := p.String(); got != want {
t.Errorf("String() = %v, want %v", 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" want := "proxy not sealed"
if err := p.Start(context.Background(), nil, sandbox); err == nil || err.Error() != want { if err := p.Start(context.Background(), nil, sandbox); err == nil || err.Error() != want {
t.Errorf("Start() error = %v, wantErr %q", 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" wantErr := "dbus: not started"
if err := p.Wait(); err == nil || err.Error() != wantErr { if err := p.Wait(); err == nil || err.Error() != wantErr {
t.Errorf("Wait() error = %v, wantErr %v", 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...), " ") want := strings.Join(append(tc[0].want, tc[1].want...), " ")
if got := p.String(); got != want { if got := p.String(); got != want {
t.Errorf("String() = %v, want %v", t.Errorf("String() = %v, want %v",
@ -176,7 +196,7 @@ func testProxyStartWaitCloseString(t *testing.T, sandbox bool) {
return 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) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
@ -185,8 +205,14 @@ func testProxyStartWaitCloseString(t *testing.T, sandbox bool) {
err) err)
} }
t.Run("started string of "+id, func(t *testing.T) { t.Run("string", func(t *testing.T) {
wantSubstr := dbus.ProxyName + " --args=" 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) { if got := p.String(); !strings.Contains(got, wantSubstr) {
t.Errorf("String() = %v, want %v", t.Errorf("String() = %v, want %v",
p.String(), wantSubstr) 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() p.Close()
if err := p.Wait(); err != nil { if err := p.Wait(); err != nil {
t.Errorf("Wait() error = %v\noutput: %s", 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
})
}

View File

@ -8,6 +8,7 @@ import (
"os/exec" "os/exec"
"path" "path"
"path/filepath" "path/filepath"
"slices"
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
@ -26,25 +27,12 @@ func (p *Proxy) Start(ctx context.Context, output io.Writer, sandbox bool) error
return errors.New("proxy not sealed") return errors.New("proxy not sealed")
} }
var ( var h helper.Helper
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)}
}
}
)
c, cancel := context.WithCancelCause(ctx) c, cancel := context.WithCancelCause(ctx)
if !sandbox { if !sandbox {
h = helper.NewDirect(c, p.name, p.seal, true, argF, func(cmd *exec.Cmd) { h = helper.NewDirect(c, p.name, p.seal, true, argF, func(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} cmdF(cmd, output, p.CmdF)
if output != nil {
cmd.Stdout, cmd.Stderr = output, output
}
// xdg-dbus-proxy does not need to inherit the environment // xdg-dbus-proxy does not need to inherit the environment
cmd.Env = make([]string, 0) 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 // resolve libraries by parsing ldd output
var proxyDeps []*ldd.Entry var proxyDeps []*ldd.Entry
if toolPath != "/nonexistent-xdg-dbus-proxy" { if toolPath != os.Args[0] {
if l, err := ldd.Exec(ctx, toolPath); err != nil { if l, err := ldd.Exec(ctx, toolPath); err != nil {
return err return err
} else { } else {
@ -71,7 +59,6 @@ func (p *Proxy) Start(ctx context.Context, output io.Writer, sandbox bool) error
} }
bc := &bwrap.Config{ bc := &bwrap.Config{
Unshare: nil,
Hostname: "fortify-dbus", Hostname: "fortify-dbus",
Chdir: "/", Chdir: "/",
Syscall: &bwrap.SyscallPolicy{DenyDevel: true, Multiarch: true}, 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 // 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]} { for _, ps := range []string{p.session[1], p.system[1]} {
if pd := path.Dir(ps); len(pd) > 0 { if pd := path.Dir(ps); len(pd) > 0 {
if 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 // xdb-dbus-proxy bin and dependencies
roBindTarget[path.Dir(toolPath)] = struct{}{} roBindTargetM[path.Dir(toolPath)] = struct{}{}
for _, ent := range proxyDeps { for _, ent := range proxyDeps {
if path.IsAbs(ent.Path) { if path.IsAbs(ent.Path) {
roBindTarget[path.Dir(ent.Path)] = struct{}{} roBindTargetM[path.Dir(ent.Path)] = struct{}{}
} }
if path.IsAbs(ent.Name) { 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]} { for _, as := range []string{p.session[0], p.system[0]} {
if len(as) > 0 && strings.HasPrefix(as, "unix:path=/") { if len(as) > 0 && strings.HasPrefix(as, "unix:path=/") {
// leave / intact // leave / intact
roBindTarget[path.Dir(as[10:])] = struct{}{} roBindTargetM[path.Dir(as[10:])] = struct{}{}
} }
} }
for k := range roBindTarget { roBindTarget := make([]string, 0, len(roBindTargetM))
bc.Bind(k, k) 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) { h = helper.MustNewBwrap(c, toolPath,
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} p.seal, true,
if output != nil { argF, func(cmd *exec.Cmd) { cmdF(cmd, output, p.CmdF) },
cmd.Stdout, cmd.Stderr = output, output nil,
} bc, nil,
}, nil, bc, nil) )
p.bwrap = bc p.bwrap = bc
} }
@ -182,3 +181,21 @@ func (p *Proxy) Close() {
p.cancel(proxyClosed) p.cancel(proxyClosed)
p.cancel = nil 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)
}
}

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"os/exec"
"sync" "sync"
"git.gensokyo.uk/security/fortify/helper" "git.gensokyo.uk/security/fortify/helper"
@ -26,6 +27,7 @@ type Proxy struct {
name string name string
session [2]string session [2]string
system [2]string system [2]string
CmdF func(cmd *exec.Cmd)
sysP bool sysP bool
seal io.WriterTo seal io.WriterTo

View File

@ -6,6 +6,12 @@ import (
"git.gensokyo.uk/security/fortify/dbus" "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{ var samples = []dbusTestCase{
{ {
"org.chromium.Chromium", &dbus.Config{ "org.chromium.Chromium", &dbus.Config{
@ -19,10 +25,10 @@ var samples = []dbusTestCase{
Log: false, Log: false,
Filter: true, Filter: true,
}, false, false, }, false, false,
[2]string{"unix:path=/run/user/1971/bus", "/tmp/fortify.1971/12622d846cc3fe7b4c10359d01f0eb47/bus"}, [2]string{sampleHostAddr, sampleBindPath},
[]string{ []string{
"unix:path=/run/user/1971/bus", sampleHostAddr,
"/tmp/fortify.1971/12622d846cc3fe7b4c10359d01f0eb47/bus", sampleBindPath,
"--filter", "--filter",
"--talk=org.freedesktop.Notifications", "--talk=org.freedesktop.Notifications",
"--talk=org.freedesktop.FileManager1", "--talk=org.freedesktop.FileManager1",
@ -48,9 +54,10 @@ var samples = []dbusTestCase{
Log: false, Log: false,
Filter: true, Filter: true,
}, false, false, }, false, false,
[2]string{"unix:path=/run/dbus/system_bus_socket", "/tmp/fortify.1971/12622d846cc3fe7b4c10359d01f0eb47/system_bus_socket"}, [2]string{sampleHostAddr, sampleBindPath},
[]string{"unix:path=/run/dbus/system_bus_socket", []string{
"/tmp/fortify.1971/12622d846cc3fe7b4c10359d01f0eb47/system_bus_socket", sampleHostAddr,
sampleBindPath,
"--filter", "--filter",
"--talk=org.bluez", "--talk=org.bluez",
"--talk=org.freedesktop.Avahi", "--talk=org.freedesktop.Avahi",
@ -68,10 +75,10 @@ var samples = []dbusTestCase{
Log: false, Log: false,
Filter: true, Filter: true,
}, false, false, }, false, false,
[2]string{"unix:path=/run/user/1971/bus", "/tmp/fortify.1971/34c24f16a0d791d28835ededaf446033/bus"}, [2]string{sampleHostAddr, sampleBindPath},
[]string{ []string{
"unix:path=/run/user/1971/bus", sampleHostAddr,
"/tmp/fortify.1971/34c24f16a0d791d28835ededaf446033/bus", sampleBindPath,
"--filter", "--filter",
"--talk=org.freedesktop.Notifications", "--talk=org.freedesktop.Notifications",
"--talk=org.kde.StatusNotifierWatcher", "--talk=org.kde.StatusNotifierWatcher",
@ -91,10 +98,10 @@ var samples = []dbusTestCase{
Log: true, Log: true,
Filter: true, Filter: true,
}, false, false, }, false, false,
[2]string{"unix:path=/run/user/1971/bus", "/tmp/fortify.1971/5da7845287a936efbc2fa75d7d81e501/bus"}, [2]string{sampleHostAddr, sampleBindPath},
[]string{ []string{
"unix:path=/run/user/1971/bus", sampleHostAddr,
"/tmp/fortify.1971/5da7845287a936efbc2fa75d7d81e501/bus", sampleBindPath,
"--filter", "--filter",
"--see=uk.gensokyo.CrashTestDummy1", "--see=uk.gensokyo.CrashTestDummy1",
"--talk=org.freedesktop.Notifications", "--talk=org.freedesktop.Notifications",
@ -114,10 +121,10 @@ var samples = []dbusTestCase{
Log: true, Log: true,
Filter: true, Filter: true,
}, false, true, }, false, true,
[2]string{"unix:path=/run/user/1971/bus", "/tmp/fortify.1971/5da7845287a936efbc2fa75d7d81e501/bus"}, [2]string{sampleHostAddr, sampleBindPath},
[]string{ []string{
"unix:path=/run/user/1971/bus", sampleHostAddr,
"/tmp/fortify.1971/5da7845287a936efbc2fa75d7d81e501/bus", sampleBindPath,
"--filter", "--filter",
"--see=uk.gensokyo.CrashTestDummy", "--see=uk.gensokyo.CrashTestDummy",
"--talk=org.freedesktop.Notifications", "--talk=org.freedesktop.Notifications",

View File

@ -6,6 +6,4 @@ import (
"git.gensokyo.uk/security/fortify/helper" "git.gensokyo.uk/security/fortify/helper"
) )
func TestHelperChildStub(t *testing.T) { func TestHelperStub(t *testing.T) { helper.InternalHelperStub() }
helper.InternalChildStub()
}

View File

@ -3,6 +3,8 @@ package helper_test
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"io"
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
@ -17,7 +19,7 @@ func TestBwrap(t *testing.T) {
sc := &bwrap.Config{ sc := &bwrap.Config{
Net: true, Net: true,
Hostname: "localhost", Hostname: "localhost",
Chdir: "/nonexistent", Chdir: "/proc/nonexistent",
Clearenv: true, Clearenv: true,
NewSession: true, NewSession: true,
DieWithParent: true, DieWithParent: true,
@ -26,14 +28,12 @@ func TestBwrap(t *testing.T) {
t.Run("nonexistent bwrap name", func(t *testing.T) { t.Run("nonexistent bwrap name", func(t *testing.T) {
bubblewrapName := helper.BubblewrapName bubblewrapName := helper.BubblewrapName
helper.BubblewrapName = "/nonexistent" helper.BubblewrapName = "/proc/nonexistent"
t.Cleanup(func() { t.Cleanup(func() { helper.BubblewrapName = bubblewrapName })
helper.BubblewrapName = bubblewrapName
})
h := helper.MustNewBwrap( h := helper.MustNewBwrap(
context.Background(), context.Background(),
"fortify", "false",
argsWt, false, argsWt, false,
argF, nil, argF, nil,
nil, nil,
@ -49,14 +49,14 @@ func TestBwrap(t *testing.T) {
t.Run("valid new helper nil check", func(t *testing.T) { t.Run("valid new helper nil check", func(t *testing.T) {
if got := helper.MustNewBwrap( if got := helper.MustNewBwrap(
context.TODO(), context.TODO(),
"fortify", "false",
argsWt, false, argsWt, false,
argF, nil, argF, nil,
nil, nil,
sc, nil, sc, nil,
); got == nil { ); got == nil {
t.Errorf("MustNewBwrap(%#v, %#v, %#v) got nil", t.Errorf("MustNewBwrap(%#v, %#v, %#v) got nil",
sc, argsWt, "fortify") sc, argsWt, "false")
return return
} }
}) })
@ -72,7 +72,7 @@ func TestBwrap(t *testing.T) {
helper.MustNewBwrap( helper.MustNewBwrap(
context.TODO(), context.TODO(),
"fortify", "false",
argsWt, false, argsWt, false,
argF, nil, argF, nil,
nil, nil,
@ -81,15 +81,13 @@ func TestBwrap(t *testing.T) {
}) })
t.Run("start without pipes", func(t *testing.T) { t.Run("start without pipes", func(t *testing.T) {
helper.InternalReplaceExecCommand(t)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
stdout, stderr := new(strings.Builder), new(strings.Builder) stdout, stderr := new(strings.Builder), new(strings.Builder)
h := helper.MustNewBwrap( h := helper.MustNewBwrap(
ctx, "crash-test-dummy", ctx, os.Args[0],
nil, false, 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, nil,
sc, nil, sc, nil,
) )
@ -107,14 +105,23 @@ func TestBwrap(t *testing.T) {
}) })
t.Run("implementation compliance", func(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( return helper.MustNewBwrap(
ctx, "crash-test-dummy", ctx, os.Args[0],
argsWt, stat, argsWt, stat,
argF, cmdF, argF, func(cmd *exec.Cmd) { setOutput(&cmd.Stdout, &cmd.Stderr); hijackBwrap(cmd) },
nil, nil,
sc, 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...)
}

View File

@ -42,7 +42,7 @@ func newHelperCmd(
) (cmd *helperCmd, args []string) { ) (cmd *helperCmd, args []string) {
cmd = new(helperCmd) cmd = new(helperCmd)
cmd.helperFiles, args = newHelperFiles(ctx, wt, stat, argF, extraFiles) 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.Cmd.Cancel = func() error { return cmd.Process.Signal(syscall.SIGTERM) }
cmd.WaitDelay = WaitDelay cmd.WaitDelay = WaitDelay
return return

View File

@ -3,6 +3,7 @@ package helper_test
import ( import (
"context" "context"
"errors" "errors"
"io"
"os" "os"
"os/exec" "os/exec"
"testing" "testing"
@ -10,9 +11,9 @@ import (
"git.gensokyo.uk/security/fortify/helper" "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) { 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) { if err := h.Start(); !errors.Is(err, os.ErrNotExist) {
t.Errorf("Start: error = %v, wantErr %v", 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) { 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 { 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") argsWt, "fortify")
return return
} }
}) })
t.Run("implementation compliance", func(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.NewDirect(ctx, "crash-test-dummy", argsWt, stat, argF, cmdF, nil) return helper.NewDirect(ctx, os.Args[0], argsWt, stat, argF, func(cmd *exec.Cmd) {
}) setOutput(&cmd.Stdout, &cmd.Stderr)
}, nil)
}, "exec")
}) })
} }

View File

@ -6,17 +6,12 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"os/exec"
"time" "time"
"git.gensokyo.uk/security/fortify/helper/proc" "git.gensokyo.uk/security/fortify/helper/proc"
) )
var ( var WaitDelay = 2 * time.Second
WaitDelay = 2 * time.Second
commandContext = exec.CommandContext
)
const ( const (
// FortifyHelper is set to 1 when args fd is enabled and 0 otherwise. // FortifyHelper is set to 1 when args fd is enabled and 0 otherwise.

View File

@ -4,7 +4,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"os/exec" "io"
"strconv" "strconv"
"strings" "strings"
"testing" "testing"
@ -36,7 +36,8 @@ func argF(argsFd, statFd int) []string {
} }
func argFChecked(argsFd, statFd int) (args []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 { if argsFd > -1 {
args = append(args, "--args", strconv.Itoa(argsFd)) 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 // 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) { func testHelper(t *testing.T,
helper.InternalReplaceExecCommand(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) { t.Run("start helper with status channel and wait", func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
stdout, stderr := new(strings.Builder), new(strings.Builder) 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) { t.Run("wait not yet started helper", func(t *testing.T) {
defer func() { defer func() {
@ -75,7 +77,7 @@ func testHelper(t *testing.T, createHelper func(ctx context.Context, cmdF func(c
cancel() cancel()
t.Run("start already started helper", func(t *testing.T) { 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 { if err := h.Start(); err != nil && err.Error() != wantErr {
t.Errorf("Start: error = %v, wantErr %v", t.Errorf("Start: error = %v, wantErr %v",
err, wantErr) 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) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
stdout, stderr := new(strings.Builder), new(strings.Builder) 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 { if err := h.Start(); err != nil {
t.Errorf("Start() error = %v", t.Errorf("Start() error = %v",

View File

@ -1,25 +1,24 @@
package helper package helper
import ( import (
"context"
"flag" "flag"
"fmt" "fmt"
"io" "io"
"os" "os"
"os/exec" "path"
"slices"
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
"testing"
"git.gensokyo.uk/security/fortify/helper/bwrap" "git.gensokyo.uk/security/fortify/helper/bwrap"
"git.gensokyo.uk/security/fortify/helper/proc" "git.gensokyo.uk/security/fortify/helper/proc"
"git.gensokyo.uk/security/fortify/internal" "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. // it is part of the implementation of the helper stub.
func InternalChildStub() { func InternalHelperStub() {
// this test mocks the helper process // this test mocks the helper process
var ap, sp string var ap, sp string
if v, ok := os.LookupEnv(FortifyHelper); !ok { if v, ok := os.LookupEnv(FortifyHelper); !ok {
@ -33,32 +32,15 @@ func InternalChildStub() {
sp = v sp = v
} }
switch os.Args[3] { if len(os.Args) > 3 && os.Args[3] == "bwrap" {
case "bwrap":
bwrapStub() bwrapStub()
default: } else {
genericStub(flagRestoreFiles(4, ap, sp)) genericStub(flagRestoreFiles(3, ap, sp))
} }
internal.Exit(0) 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 { func newFile(fd int, name, p string) *os.File {
present := false present := false
switch p { switch p {
@ -149,26 +131,54 @@ func bwrapStub() {
sc := &bwrap.Config{ sc := &bwrap.Config{
Net: true, Net: true,
Hostname: "localhost", Hostname: "localhost",
Chdir: "/nonexistent", Chdir: "/proc/nonexistent",
Clearenv: true, Clearenv: true,
NewSession: true, NewSession: true,
DieWithParent: true, DieWithParent: true,
AsInit: 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 { WriteTo(want); err != nil {
panic("cannot read want: " + err.Error()) 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()) panic("bad bwrap args\ngot: " + got.String() + "\nwant: " + want.String())
} }
}() }()
if err := syscall.Exec( if err := syscall.Exec(
os.Args[0], flag.CommandLine.Args()[0],
append([]string{os.Args[0], "-test.run=TestHelperChildStub", "--"}, flag.CommandLine.Args()...), flag.CommandLine.Args(),
os.Environ()); err != nil { os.Environ()); err != nil {
panic("cannot start general stub: " + err.Error()) panic("cannot start helper stub: " + err.Error())
} }
} }

View File

@ -6,6 +6,4 @@ import (
"git.gensokyo.uk/security/fortify/helper" "git.gensokyo.uk/security/fortify/helper"
) )
func TestHelperChildStub(t *testing.T) { func TestHelperStub(t *testing.T) { helper.InternalHelperStub() }
helper.InternalChildStub()
}