From ff3cfbb437795c7fee547b8340014071625a61a8 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Fri, 28 Mar 2025 02:24:27 +0900 Subject: [PATCH] test/sandbox: check seccomp outcome This is as ugly as it is because it has to have CAP_SYS_ADMIN and not be in seccomp mode. Signed-off-by: Ophestra --- test/configuration.nix | 3 +++ test/sandbox/assert.go | 31 +++++++++++++++++++++++++++++++ test/sandbox/case/main.go | 32 +++++++++++++++++++++++++++++++- test/test.py | 6 ++++++ 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/test/configuration.nix b/test/configuration.nix index 6f94170..fa1ecca 100644 --- a/test/configuration.nix +++ b/test/configuration.nix @@ -40,6 +40,9 @@ in # For D-Bus tests: libnotify mako + + # For checking seccomp outcome: + testCases._testProgram ]; variables = { diff --git a/test/sandbox/assert.go b/test/sandbox/assert.go index c4b59fd..50d078e 100644 --- a/test/sandbox/assert.go +++ b/test/sandbox/assert.go @@ -7,10 +7,14 @@ in the public sandbox/vfs package. Files in this package are excluded by the bui package sandbox import ( + "crypto/sha512" + "encoding/hex" "encoding/json" + "errors" "io/fs" "log" "os" + "syscall" ) var ( @@ -124,6 +128,33 @@ func (t *T) MustCheck(want *TestCase) { } } +func MustCheckFilter(pid int, want string) { + if err := ptraceAttach(pid); err != nil { + fatalf("cannot attach to process %d: %v", pid, err) + } + buf, err := getFilter[[8]byte](pid, 0) + if err0 := ptraceDetach(pid); err0 != nil { + printf("cannot detach from process %d: %v", pid, err0) + } + if err != nil { + if errors.Is(err, syscall.ENOENT) { + fatalf("seccomp filter not installed for process %d", pid) + } + fatalf("cannot get filter: %v", err) + } + + h := sha512.New() + for _, b := range buf { + h.Write(b[:]) + } + + if got := hex.EncodeToString(h.Sum(nil)); got != want { + fatalf("[FAIL] %s", got) + } else { + printf("[ OK ] %s", got) + } +} + func mustDecode(wantFilePath string, v any) { if f, err := os.Open(wantFilePath); err != nil { fatalf("cannot open %q: %v", wantFilePath, err) diff --git a/test/sandbox/case/main.go b/test/sandbox/case/main.go index 59c80ef..4c639b2 100644 --- a/test/sandbox/case/main.go +++ b/test/sandbox/case/main.go @@ -1,9 +1,39 @@ package main import ( + "log" "os" + "strconv" + "strings" "git.gensokyo.uk/security/fortify/test/sandbox" ) -func main() { (&sandbox.T{FS: os.DirFS("/")}).MustCheckFile(os.Args[1], "/tmp/sandbox-ok") } +func main() { + log.SetFlags(0) + log.SetPrefix("test: ") + + if len(os.Args) < 2 { + log.Fatal("invalid argument") + } + + switch os.Args[1] { + case "filter": + if len(os.Args) != 4 { + log.Fatal("invalid argument") + } + + if pid, err := strconv.Atoi(strings.TrimSpace(os.Args[2])); err != nil { + log.Fatalf("%s", err) + } else if pid < 1 { + log.Fatalf("%d out of range", pid) + } else { + sandbox.MustCheckFilter(pid, os.Args[3]) + return + } + + default: + (&sandbox.T{FS: os.DirFS("/")}).MustCheckFile(os.Args[1], "/tmp/sandbox-ok") + return + } +} diff --git a/test/test.py b/test/test.py index f013718..baf074e 100644 --- a/test/test.py +++ b/test/test.py @@ -99,6 +99,12 @@ print(denyOutputVerbose) # Fail direct fsu call: print(machine.fail("sudo -u alice -i fsu")) +# Check seccomp outcome: +swaymsg("exec fortify run cat") +pid = int(machine.wait_until_succeeds("pgrep -U 1000000 -x cat", timeout=5)) +print(machine.succeed(f"fortify-test filter {pid} c698b081ff957afe17a6d94374537d37f2a63f6f9dd75da7546542407a9e32476ebda3312ba7785d7f618542bcfaf27ca27dcc2dddba852069d28bcfe8cad39a &>/dev/stdout", timeout=5)) +machine.succeed(f"kill -TERM {pid}") + # Verify capabilities/securebits in user namespace: print(machine.succeed("sudo -u alice -i fortify run capsh --print")) print(machine.succeed("sudo -u alice -i fortify run capsh --has-no-new-privs"))