From d5d2c23d5a489bba6be8a75ad4c5d1ac3cda4bb6 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Tue, 6 Oct 2026 21:54:48 +0900 Subject: test/internal/testsuite: directly set credentials There is no reason to keep using sudo here, now that processes are spawned natively. This change removes all use of sudo. Signed-off-by: Ophestra --- .gitea/workflows/test.yml | 2 +- internal/workflows/step.go | 2 - test/internal/testsuite/testsuite.go | 134 +++++++++++++++++++---------------- test/sandbox/main.go | 57 ++++++++------- test/sandbox/testdata/mapuid.go | 4 +- test/sharefs/main.go | 73 +++++++++---------- 6 files changed, 140 insertions(+), 132 deletions(-) diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 4e584550..e4736310 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -1 +1 @@ -{"name":"Test","on":["push"],"jobs":{"hakurei":{"name":"Hakurei (legacy)","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test#checks.x86_64-linux.hakurei"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"hakurei-vm-output","path":"result/*","retention-days":1}}]},"race":{"name":"Hakurei (legacy with race instrument)","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test#checks.x86_64-linux.race"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"hakurei-race-vm-output","path":"result/*","retention-days":1}}]},"sandbox":{"name":"Sandbox","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Set up Go toolchain","uses":"actions/setup-go@v6","with":{"go-version-file":"go.mod"}},{"name":"Install packages","uses":"awalsh128/cache-apt-pkgs-action@v1","with":{"add-repository":"ppa:savoury1/pipewire","packages":"libmount-dev sway xwayland xdg-dbus-proxy pipewire","version":0,"execute_install_scripts":true}},{"name":"Request distribution","id":"dist","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci dist -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Install hakurei","run":"HAKUREI_VERSION=\"$(cat cmd/dist/VERSION)-${{ steps.dist.outputs.rev }}\" \u0026\u0026 tar xf result/hakurei-$HAKUREI_VERSION*-amd64.tar.gz \u0026\u0026 ./hakurei-$HAKUREI_VERSION*-amd64/install.sh \u0026\u0026 sudo -u ubuntu hakurei version \u0026\u0026 echo 'Defaults closefrom_override' \u003e /etc/sudoers.d/closefrom_override \u0026\u0026 mkdir /var/empty"},{"name":"Compile and run test suite","run":"rm -rf result \u0026\u0026 go run -tags=testsuite ./test/sandbox"}]},"sandbox-race":{"name":"Sandbox (with race instrument)","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Set up Go toolchain","uses":"actions/setup-go@v6","with":{"go-version-file":"go.mod"}},{"name":"Install packages","uses":"awalsh128/cache-apt-pkgs-action@v1","with":{"add-repository":"ppa:savoury1/pipewire","packages":"libmount-dev sway xwayland xdg-dbus-proxy pipewire","version":0,"execute_install_scripts":true}},{"name":"Request distribution","id":"dist","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci race -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Install hakurei","run":"HAKUREI_VERSION=\"$(cat cmd/dist/VERSION)-${{ steps.dist.outputs.rev }}\" \u0026\u0026 tar xf result/hakurei-$HAKUREI_VERSION*-amd64.tar.gz \u0026\u0026 ./hakurei-$HAKUREI_VERSION*-amd64/install.sh \u0026\u0026 sudo -u ubuntu hakurei version \u0026\u0026 echo 'Defaults closefrom_override' \u003e /etc/sudoers.d/closefrom_override \u0026\u0026 mkdir /var/empty"},{"name":"Compile and run test suite","run":"rm -rf result \u0026\u0026 go run -tags=testsuite ./test/sandbox"}]},"sharefs":{"name":"ShareFS","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Set up Go toolchain","uses":"actions/setup-go@v6","with":{"go-version-file":"go.mod"}},{"name":"Install packages","uses":"awalsh128/cache-apt-pkgs-action@v1","with":{"add-repository":"","packages":"fuse3 fsmark","version":0,"execute_install_scripts":true}},{"name":"Request distribution","id":"dist","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci dist -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Install hakurei","run":"HAKUREI_VERSION=\"$(cat cmd/dist/VERSION)-${{ steps.dist.outputs.rev }}\" \u0026\u0026 tar xf result/hakurei-$HAKUREI_VERSION*-amd64.tar.gz \u0026\u0026 ./hakurei-$HAKUREI_VERSION*-amd64/install.sh \u0026\u0026 sudo -u ubuntu hakurei version \u0026\u0026 echo 'Defaults closefrom_override' \u003e /etc/sudoers.d/closefrom_override \u0026\u0026 mkdir /var/empty"},{"name":"Mount sharefs","run":"useradd -ru 1023 -md /var/lib/sdcard -k /var/empty -s /sbin/nologin media_rw \u0026\u0026 install -dm0 /sdcard \u0026\u0026 sharefs -o rw,noexec,nosuid,nodev,noatime,allow_other,mkdir,source=/var/lib/sdcard,setuid=1023,setgid=1023 /sdcard"},{"name":"Compile and run test suite","run":"sharefs -V \u0026\u0026 rm -rf result \u0026\u0026 go run -tags=testsuite ./test/sharefs"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"fs_mark","path":"result/*","retention-days":1}}]},"check":{"name":"Flake checks","needs":["hakurei","race"],"runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run checks","run":"nix --print-build-logs --experimental-features 'nix-command flakes' flake check ./test"}]},"dist":{"name":"Create distribution","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Request distribution","id":"dist-test","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci dist -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Upload distribution","uses":"actions/upload-artifact@v3","with":{"name":"dist-${{ steps.dist-test.outputs.rev }}","path":"result/*","retention-days":1}}]}}} +{"name":"Test","on":["push"],"jobs":{"hakurei":{"name":"Hakurei (legacy)","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test#checks.x86_64-linux.hakurei"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"hakurei-vm-output","path":"result/*","retention-days":1}}]},"race":{"name":"Hakurei (legacy with race instrument)","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test#checks.x86_64-linux.race"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"hakurei-race-vm-output","path":"result/*","retention-days":1}}]},"sandbox":{"name":"Sandbox","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Set up Go toolchain","uses":"actions/setup-go@v6","with":{"go-version-file":"go.mod"}},{"name":"Install packages","uses":"awalsh128/cache-apt-pkgs-action@v1","with":{"add-repository":"ppa:savoury1/pipewire","packages":"libmount-dev sway xwayland xdg-dbus-proxy pipewire","version":0,"execute_install_scripts":true}},{"name":"Request distribution","id":"dist","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci dist -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Install hakurei","run":"HAKUREI_VERSION=\"$(cat cmd/dist/VERSION)-${{ steps.dist.outputs.rev }}\" \u0026\u0026 tar xf result/hakurei-$HAKUREI_VERSION*-amd64.tar.gz \u0026\u0026 ./hakurei-$HAKUREI_VERSION*-amd64/install.sh \u0026\u0026 sudo -u ubuntu hakurei version \u0026\u0026 mkdir /var/empty"},{"name":"Compile and run test suite","run":"rm -rf result \u0026\u0026 go run -tags=testsuite ./test/sandbox"}]},"sandbox-race":{"name":"Sandbox (with race instrument)","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Set up Go toolchain","uses":"actions/setup-go@v6","with":{"go-version-file":"go.mod"}},{"name":"Install packages","uses":"awalsh128/cache-apt-pkgs-action@v1","with":{"add-repository":"ppa:savoury1/pipewire","packages":"libmount-dev sway xwayland xdg-dbus-proxy pipewire","version":0,"execute_install_scripts":true}},{"name":"Request distribution","id":"dist","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci race -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Install hakurei","run":"HAKUREI_VERSION=\"$(cat cmd/dist/VERSION)-${{ steps.dist.outputs.rev }}\" \u0026\u0026 tar xf result/hakurei-$HAKUREI_VERSION*-amd64.tar.gz \u0026\u0026 ./hakurei-$HAKUREI_VERSION*-amd64/install.sh \u0026\u0026 sudo -u ubuntu hakurei version \u0026\u0026 mkdir /var/empty"},{"name":"Compile and run test suite","run":"rm -rf result \u0026\u0026 go run -tags=testsuite ./test/sandbox"}]},"sharefs":{"name":"ShareFS","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Set up Go toolchain","uses":"actions/setup-go@v6","with":{"go-version-file":"go.mod"}},{"name":"Install packages","uses":"awalsh128/cache-apt-pkgs-action@v1","with":{"add-repository":"","packages":"fuse3 fsmark","version":0,"execute_install_scripts":true}},{"name":"Request distribution","id":"dist","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci dist -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Install hakurei","run":"HAKUREI_VERSION=\"$(cat cmd/dist/VERSION)-${{ steps.dist.outputs.rev }}\" \u0026\u0026 tar xf result/hakurei-$HAKUREI_VERSION*-amd64.tar.gz \u0026\u0026 ./hakurei-$HAKUREI_VERSION*-amd64/install.sh \u0026\u0026 sudo -u ubuntu hakurei version \u0026\u0026 mkdir /var/empty"},{"name":"Mount sharefs","run":"useradd -ru 1023 -md /var/lib/sdcard -k /var/empty -s /sbin/nologin media_rw \u0026\u0026 install -dm0 /sdcard \u0026\u0026 sharefs -o rw,noexec,nosuid,nodev,noatime,allow_other,mkdir,source=/var/lib/sdcard,setuid=1023,setgid=1023 /sdcard"},{"name":"Compile and run test suite","run":"sharefs -V \u0026\u0026 rm -rf result \u0026\u0026 go run -tags=testsuite ./test/sharefs"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"fs_mark","path":"result/*","retention-days":1}}]},"check":{"name":"Flake checks","needs":["hakurei","race"],"runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run checks","run":"nix --print-build-logs --experimental-features 'nix-command flakes' flake check ./test"}]},"dist":{"name":"Create distribution","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Request distribution","id":"dist-test","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci dist -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Upload distribution","uses":"actions/upload-artifact@v3","with":{"name":"dist-${{ steps.dist-test.outputs.rev }}","path":"result/*","retention-days":1}}]}}} diff --git a/internal/workflows/step.go b/internal/workflows/step.go index 199f82d9..eaf8986d 100644 --- a/internal/workflows/step.go +++ b/internal/workflows/step.go @@ -60,8 +60,6 @@ var install = Step{ "tar xf result/hakurei-$HAKUREI_VERSION*-amd64.tar.gz && " + "./hakurei-$HAKUREI_VERSION*-amd64/install.sh && " + "sudo -u ubuntu hakurei version && " + - "echo 'Defaults closefrom_override' > " + - "/etc/sudoers.d/closefrom_override && " + "mkdir /var/empty", } diff --git a/test/internal/testsuite/testsuite.go b/test/internal/testsuite/testsuite.go index 00eb2f92..456c36b4 100644 --- a/test/internal/testsuite/testsuite.go +++ b/test/internal/testsuite/testsuite.go @@ -13,8 +13,6 @@ import ( "os" "os/exec" "os/signal" - "os/user" - "strconv" "sync" "syscall" "time" @@ -27,20 +25,17 @@ func ReceiveSignals() { log.Fatalf("terminating on signal %s", <-s) } -// GetUser returns the [user.User] structure describing the user with uid 1000. -func GetUser() *user.User { - if u, err := user.LookupId("1000"); err != nil { - log.Fatal(err) - return nil - } else { - return u - } -} - // MustRun runs command and terminates the testsuite on error. -func MustRun(command ...string) { +func MustRun(cred *syscall.Credential, extraEnv []string, command ...string) { cmd := exec.Command(command[0], command[1:]...) cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + cmd.SysProcAttr = &syscall.SysProcAttr{ + Pdeathsig: syscall.SIGKILL, + Credential: cred, + } + if len(extraEnv) != 0 { + cmd.Env = append(cmd.Environ(), extraEnv...) + } if err := cmd.Run(); err != nil { log.Fatal(err) } @@ -52,9 +47,16 @@ var ErrUnexpectedSuccess = errors.New("process unexpectedly exited with code 0") // MustFail runs command and terminates the testsuite if the program fails to // start or exits with code 0. -func MustFail(command ...string) { +func MustFail(cred *syscall.Credential, extraEnv []string, command ...string) { cmd := exec.Command(command[0], command[1:]...) cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + cmd.SysProcAttr = &syscall.SysProcAttr{ + Pdeathsig: syscall.SIGKILL, + Credential: cred, + } + if len(extraEnv) != 0 { + cmd.Env = append(cmd.Environ(), extraEnv...) + } if err := cmd.Run(); err == nil { log.Fatal(ErrUnexpectedSuccess) } else if e, ok := errors.AsType[*exec.ExitError](err); !ok { @@ -64,16 +66,6 @@ func MustFail(command ...string) { } } -// MustRunAs wraps [MustRun] for sudo. -func MustRunAs(username string, command ...string) { - MustRun(append([]string{"sudo", "-u", username}, command...)...) -} - -// MustFailAs wraps [MustFail] for sudo. -func MustFailAs(username string, command ...string) { - MustFail(append([]string{"sudo", "-u", username}, command...)...) -} - // MustStart starts cmd and returns a channel delivering its wait error. func MustStart(cmd *exec.Cmd) (done <-chan error) { if err := cmd.Start(); err != nil { @@ -84,24 +76,24 @@ func MustStart(cmd *exec.Cmd) (done <-chan error) { return d } -// MustStartAs wraps [MustStart] for sudo. -func MustStartAs( +// MustStartWith wraps [MustStart] and creates the [exec.Cmd] object internally. +func MustStartWith( ctx context.Context, - username string, + cred *syscall.Credential, + extraEnv []string, files []*os.File, command ...string, ) (proc *os.Process, done <-chan error) { - sudoArgs := []string{ - "-u", username, - } - if len(files) != 0 { - sudoArgs = append(sudoArgs, "-C", strconv.Itoa(len(files)+4)) - } - sudoArgs = append(sudoArgs, "--") - cmd := exec.CommandContext(ctx, "sudo", append(sudoArgs, command...)...) + cmd := exec.CommandContext(ctx, command[0], command[1:]...) cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr cmd.ExtraFiles = files - cmd.SysProcAttr = &syscall.SysProcAttr{Pdeathsig: syscall.SIGTERM} + cmd.SysProcAttr = &syscall.SysProcAttr{ + Pdeathsig: syscall.SIGTERM, + Credential: cred, + } + if len(extraEnv) != 0 { + cmd.Env = append(cmd.Environ(), extraEnv...) + } return cmd.Process, MustStart(cmd) } @@ -136,9 +128,21 @@ func FilterTerminated(err error) error { } // Poll repeatedly runs command until it succeeds. -func Poll(d time.Duration, command ...string) { +func Poll( + d time.Duration, + cred *syscall.Credential, + extraEnv []string, + command ...string, +) { for range time.NewTicker(d).C { cmd := exec.Command(command[0], command[1:]...) + cmd.SysProcAttr = &syscall.SysProcAttr{ + Pdeathsig: syscall.SIGKILL, + Credential: cred, + } + if len(extraEnv) != 0 { + cmd.Env = append(cmd.Environ(), extraEnv...) + } if err := cmd.Run(); err != nil { if e, ok := errors.AsType[*exec.ExitError](err); ok && e.Exited() { continue @@ -160,15 +164,15 @@ const ( // MustStartSessionBus starts a session bus that is never explicitly terminated. // The test suite is terminated if the session bus daemon terminates. -func MustStartSessionBus(username string) (dbusEnv string) { +func MustStartSessionBus(cred *syscall.Credential) (dbusEnv string) { r, w, err := os.Pipe() if err != nil { log.Fatal(err) } // this is never explicitly terminated - _, done := MustStartAs( - context.Background(), username, []*os.File{w}, + _, done := MustStartWith( + context.Background(), cred, nil, []*os.File{w}, "dbus-daemon", "--print-address=3", "--address=unix:path="+XDGRuntimeDir+"/dbus", @@ -209,16 +213,18 @@ const ( // by calling [TerminateSway]. func MustStartSway( wg *sync.WaitGroup, - username, dbusEnv string, + cred *syscall.Credential, + dbusEnv string, ) { wg.Go(func() { // this is terminated via swaymsg - _, done := MustStartAs( - context.Background(), username, nil, "env", - "WLR_BACKENDS=headless", - XDGRuntimeEnv, - SwayEnv, - dbusEnv, + _, done := MustStartWith( + context.Background(), cred, []string{ + "WLR_BACKENDS=headless", + XDGRuntimeEnv, + SwayEnv, + dbusEnv, + }, nil, "sway", ) if err := <-done; err != nil { @@ -226,23 +232,29 @@ func MustStartSway( } }) - Poll(50*time.Millisecond, "sudo", "-u", username, SwayEnv, "swaymsg") + Poll( + 50*time.Millisecond, + cred, + []string{SwayEnv}, + "swaymsg", + ) log.Printf("sway available via %s", SwayEnv) } // TerminateSway requests for the sway server to terminate via sway IPC. -func TerminateSway(username string) { - MustFailAs(username, SwayEnv, "swaymsg", "exit") +func TerminateSway(cred *syscall.Credential) { + MustFail(cred, []string{SwayEnv}, "swaymsg", "exit") } // MustStartPipeWire starts a PipeWire server that is never explicitly // terminated. The test suite is terminated if the PipeWire server terminates. -func MustStartPipeWire(username, dbusEnv string) { +func MustStartPipeWire(cred *syscall.Credential, dbusEnv string) { // this is never explicitly terminated - _, done := MustStartAs( - context.Background(), username, nil, "env", - XDGRuntimeEnv, - dbusEnv, + _, done := MustStartWith( + context.Background(), cred, []string{ + XDGRuntimeEnv, + dbusEnv, + }, nil, "pipewire", ) @@ -253,17 +265,19 @@ func MustStartPipeWire(username, dbusEnv string) { log.Fatal("pipewire terminated unexpectedly") }() - Poll(50*time.Millisecond, "sudo", "-u", username, + Poll(50*time.Millisecond, cred, []string{ XDGRuntimeEnv, dbusEnv, + }, "wpctl", "status", ) - _, _done := MustStartAs( - context.Background(), username, nil, "env", - XDGRuntimeEnv, - dbusEnv, + _, _done := MustStartWith( + context.Background(), cred, []string{ + XDGRuntimeEnv, + dbusEnv, + }, nil, "wireplumber", ) diff --git a/test/sandbox/main.go b/test/sandbox/main.go index 311b9f58..4961c04f 100644 --- a/test/sandbox/main.go +++ b/test/sandbox/main.go @@ -50,12 +50,12 @@ func mustScanFor(f func(ps *testsuite.StatScanner) bool) int { func mustStart( ctx context.Context, serial uint64, - username string, + cred *syscall.Credential, files ...*os.File, ) (pid int, done <-chan error) { _serial := strconv.FormatUint(serial, 10) - _, done = testsuite.MustStartAs( - ctx, username, files, + _, done = testsuite.MustStartWith( + ctx, cred, nil, files, "hakurei", "exec", "sleep", "infinity", _serial, ) @@ -108,11 +108,11 @@ func mustStart( func main() { go testsuite.ReceiveSignals() - username := testsuite.GetUser().Username // the signal handler does not wait for termination ctx := context.Background() + cred := syscall.Credential{Uid: 1000, Gid: 100} if err := os.MkdirAll("/opt/test-helper/bin", 0755); err != nil { log.Fatal(err) } @@ -136,26 +136,26 @@ func main() { var serial atomic.Uint64 newSerial := func() uint64 { serial.Add(1); return serial.Load() } - testsuite.MustRunAs( - username, "-i", + testsuite.MustRun( + &cred, nil, "hakurei", "exec", "capsh", "--print", ) wg.Go(func() { defer log.Println("validated capabilities/securebits in user namespace") - testsuite.MustRunAs( - username, "-i", + testsuite.MustRun( + &cred, nil, "hakurei", "exec", "capsh", "--has-no-new-privs", ) for _, p := range []byte{'a', 'b', 'i', 'p'} { - testsuite.MustFailAs( - username, "-i", + testsuite.MustFail( + &cred, nil, "hakurei", "exec", "capsh", "--has-"+string(p)+"=CAP_SYS_ADMIN", ) } - testsuite.MustFailAs( - username, "-i", + testsuite.MustFail( + &cred, nil, "hakurei", "exec", "umount", "-R", "/dev", ) }) @@ -166,7 +166,7 @@ func main() { c, cancel := context.WithCancel(ctx) defer cancel() - pid, done := mustStart(c, newSerial(), username) + pid, done := mustStart(c, newSerial(), &cred) testsuite.MustCheckFilter(pid, testdata.SumPD) if err := testsuite.FilterTerminated(<-done); err != nil { log.Fatal(err) @@ -179,7 +179,7 @@ func main() { c, cancel := context.WithCancel(ctx) defer cancel() - pid, done := mustStart(c, newSerial(), username, os.Stdin, os.Stdout, os.Stderr) + pid, done := mustStart(c, newSerial(), &cred, os.Stdin, os.Stdout, os.Stderr) prefix := filepath.Join(fhs.Proc, strconv.Itoa(pid), "fd") var fail bool @@ -227,10 +227,10 @@ func main() { var swg sync.WaitGroup defer swg.Wait() - dbusEnv := testsuite.MustStartSessionBus(username) - testsuite.MustStartSway(&swg, username, dbusEnv) - defer testsuite.TerminateSway(username) - testsuite.MustStartPipeWire(username, dbusEnv) + dbusEnv := testsuite.MustStartSessionBus(&cred) + testsuite.MustStartSway(&swg, &cred, dbusEnv) + defer testsuite.TerminateSway(&cred) + testsuite.MustStartPipeWire(&cred, dbusEnv) if err := <-testToolDone; err != nil { log.Fatal(err) @@ -241,16 +241,6 @@ func main() { for name, tc := range testdata.All() { wg.Go(func() { cmd := exec.Command( - "sudo", - "-u", username, - "-C", "6", - "TERM=xterm", - testsuite.XDGRuntimeEnv, - testsuite.WaylandEnv, - "DISPLAY=:0", - dbusEnv, - "--", - "script", "/dev/null", "-E", "always", "-qec", @@ -259,10 +249,19 @@ func main() { " 4 1>&3", ) cmd.SysProcAttr = &syscall.SysProcAttr{ - Pdeathsig: syscall.SIGTERM, + Pdeathsig: syscall.SIGTERM, + Credential: &cred, } var output bytes.Buffer cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, &output, &output + cmd.Env = []string{ + "PATH=" + os.Getenv("PATH"), + "TERM=xterm", + testsuite.XDGRuntimeEnv, + testsuite.WaylandEnv, + "DISPLAY=:0", + dbusEnv, + } var err error var notify, _notify, _conf, conf, ident, _ident *os.File diff --git a/test/sandbox/testdata/mapuid.go b/test/sandbox/testdata/mapuid.go index fad4ec36..8dee7b7f 100644 --- a/test/sandbox/testdata/mapuid.go +++ b/test/sandbox/testdata/mapuid.go @@ -63,9 +63,9 @@ var _ = TestCase{ "etc": {Mode: os.ModeDir | 0755, Dir: dir{ "passwd": {Mode: 0600, - Data: new("u0_a3:x:1000:1000:Hakurei:/.hakurei:/usr/bin/bash\n")}, + Data: new("u0_a3:x:1000:100:Hakurei:/.hakurei:/usr/bin/bash\n")}, "group": {Mode: 0600, - Data: new("hakurei:x:1000:\n")}, + Data: new("hakurei:x:100:\n")}, }}, "lib64": {Mode: os.ModeDir | 0755, Dir: dir{ diff --git a/test/sharefs/main.go b/test/sharefs/main.go index c2b6cd41..80e6ab09 100644 --- a/test/sharefs/main.go +++ b/test/sharefs/main.go @@ -9,30 +9,28 @@ import ( "log" "os" "os/exec" - "slices" "strings" + "syscall" "hakurei.app/test/internal/testsuite" ) // checkBadOpts invokes cmd/sharefs with the specified options and compares // the resulting error message. -func checkBadOpts(username, opts, want string) { +func checkBadOpts(cred *syscall.Credential, opts, want string) { var buf strings.Builder buf.Grow(len(want)) - sudo := []string{"sudo", "-u", username, "-i", "--"} - if username == "root" { - sudo = nil - } - - a := slices.Concat(sudo, []string{ + cmd := exec.Command( "sharefs", "-f", - "-o", "source=/etc," + opts, + "-o", "source=/etc,"+opts, "/mnt", - }) - cmd := exec.Command(a[0], a[1:]...) + ) + cmd.SysProcAttr = &syscall.SysProcAttr{ + Pdeathsig: syscall.SIGKILL, + Credential: cred, + } cmd.Stderr = &buf err := cmd.Run() if err == nil { @@ -51,8 +49,8 @@ func checkBadOpts(username, opts, want string) { func main() { go testsuite.ReceiveSignals() - username := testsuite.GetUser().Username + cred := syscall.Credential{Uid: 1000, Gid: 100} if err := os.Mkdir("result", 0755); err != nil { log.Fatal(err) } @@ -62,6 +60,7 @@ func main() { defer close(done) testsuite.MustRun( + nil, nil, "fs_mark", "-v", "-d", "/sdcard/fs_mark", @@ -70,25 +69,25 @@ func main() { }() log.Println("checking malformed setuid/setgid representation") - checkBadOpts(username, "setuid=ff", "sharefs: invalid value for option setuid\n") - checkBadOpts(username, "setgid=ff", "sharefs: invalid value for option setgid\n") + checkBadOpts(&cred, "setuid=ff", "sharefs: invalid value for option setuid\n") + checkBadOpts(&cred, "setgid=ff", "sharefs: invalid value for option setgid\n") log.Println("checking bounds check for setuid/setgid") - checkBadOpts(username, "setuid=0", "sharefs: invalid value for option setuid\n") - checkBadOpts(username, "setgid=0", "sharefs: invalid value for option setgid\n") - checkBadOpts(username, "setuid=-1", "sharefs: invalid value for option setuid\n") - checkBadOpts(username, "setgid=-1", "sharefs: invalid value for option setgid\n") + checkBadOpts(&cred, "setuid=0", "sharefs: invalid value for option setuid\n") + checkBadOpts(&cred, "setgid=0", "sharefs: invalid value for option setgid\n") + checkBadOpts(&cred, "setuid=-1", "sharefs: invalid value for option setuid\n") + checkBadOpts(&cred, "setgid=-1", "sharefs: invalid value for option setgid\n") log.Println("checking non-root setuid/setgid") - checkBadOpts(username, "setuid=1023", "sharefs: setuid and setgid has no effect when not starting as root\n") - checkBadOpts(username, "setgid=1023", "sharefs: setuid and setgid has no effect when not starting as root\n") - checkBadOpts(username, "setuid=1023,setgid=1023", "sharefs: setuid and setgid has no effect when not starting as root\n") - checkBadOpts(username, "mkdir", "sharefs: mkdir has no effect when not starting as root\n") + checkBadOpts(&cred, "setuid=1023", "sharefs: setuid and setgid has no effect when not starting as root\n") + checkBadOpts(&cred, "setgid=1023", "sharefs: setuid and setgid has no effect when not starting as root\n") + checkBadOpts(&cred, "setuid=1023,setgid=1023", "sharefs: setuid and setgid has no effect when not starting as root\n") + checkBadOpts(&cred, "mkdir", "sharefs: mkdir has no effect when not starting as root\n") log.Println("checking root without setuid/setgid") - checkBadOpts("root", "allow_other", "sharefs: setuid and setgid must not be 0\n") - checkBadOpts("root", "setuid=1023", "sharefs: setuid and setgid must not be 0\n") - checkBadOpts("root", "setgid=1023", "sharefs: setuid and setgid must not be 0\n") + checkBadOpts(nil, "allow_other", "sharefs: setuid and setgid must not be 0\n") + checkBadOpts(nil, "setuid=1023", "sharefs: setuid and setgid must not be 0\n") + checkBadOpts(nil, "setgid=1023", "sharefs: setuid and setgid must not be 0\n") log.Println("verifying mount point") if err := os.Remove("/mnt"); err != nil { @@ -96,24 +95,22 @@ func main() { } log.Println("checking unprivileged mount/unmount") - testsuite.MustRunAs(username, "-i", "mkdir", "/tmp/sdcard", "/tmp/persistent") - testsuite.MustRunAs(username, "-i", "sharefs", "-o", "source=/tmp/persistent", "/tmp/sdcard") - testsuite.MustRunAs(username, "-i", "touch", "/tmp/sdcard/check") - testsuite.MustRunAs(username, "-i", "umount", "/tmp/sdcard") - testsuite.MustRunAs(username, "-i", "rm", "/tmp/persistent/check") - testsuite.MustRunAs(username, "-i", "rmdir", "/tmp/sdcard", "/tmp/persistent") + testsuite.MustRun(&cred, nil, "mkdir", "/tmp/sdcard", "/tmp/persistent") + testsuite.MustRun(&cred, nil, "sharefs", "-o", "source=/tmp/persistent", "/tmp/sdcard") + testsuite.MustRun(&cred, nil, "touch", "/tmp/sdcard/check") + testsuite.MustRun(&cred, nil, "umount", "/tmp/sdcard") + testsuite.MustRun(&cred, nil, "rm", "/tmp/persistent/check") + testsuite.MustRun(&cred, nil, "rmdir", "/tmp/sdcard", "/tmp/persistent") log.Println("waiting for fs_mark to complete") <-done - const ( - backingDir = "/var/lib/sdcard" - sharefsUser = "media_rw" - ) + const backingDir = "/var/lib/sdcard" + sharefsCred := syscall.Credential{Uid: 1023, Gid: 1023} log.Println("checking permissions") - testsuite.MustRunAs(sharefsUser, "touch", backingDir+"/fs_mark/.check") - testsuite.MustRunAs(sharefsUser, "rm", backingDir+"/fs_mark/.check") - testsuite.MustRunAs(username, "-i", "rm", "-rf", "/sdcard/fs_mark") + testsuite.MustRun(&sharefsCred, nil, "touch", backingDir+"/fs_mark/.check") + testsuite.MustRun(&sharefsCred, nil, "rm", backingDir+"/fs_mark/.check") + testsuite.MustRun(&cred, nil, "rm", "-rf", "/sdcard/fs_mark") if _, err := os.ReadDir(backingDir + "/fs_mark"); err == nil { log.Fatal("fs_mark directory was not removed") } else if !errors.Is(err, os.ErrNotExist) { -- cgit v1.3.1