aboutsummaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/internal/testsuite/testsuite.go134
-rw-r--r--test/sandbox/main.go57
-rw-r--r--test/sandbox/testdata/mapuid.go4
-rw-r--r--test/sharefs/main.go73
4 files changed, 139 insertions, 129 deletions
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) {