Detailed changes
@@ -56,13 +56,6 @@ func main() {
flag.Parse()
args := flag.Args()
- // Apply seccomp filter early, before spawning any child processes.
- // This prevents child processes from killing shelley.
- // Turns out this doesn't work, because it blocks sudo, which we want to work.
- // if err := seccomp.BlockKillSelf(); err != nil {
- // slog.Info("seccomp filter not installed", "error", err)
- // }
-
if len(args) == 0 {
flag.Usage()
os.Exit(1)
@@ -1,108 +0,0 @@
-//go:build linux
-
-package main
-
-import (
- "fmt"
- "os"
- "os/exec"
- "strconv"
- "strings"
- "testing"
-
- "shelley.exe.dev/seccomp"
-)
-
-// TestSeccompIntegration tests that the seccomp filter is installed
-// automatically and prevents child processes from killing the parent.
-func TestSeccompIntegration(t *testing.T) {
- if os.Getenv("TEST_SECCOMP_HELPER") == "1" {
- runSeccompHelper(t)
- return
- }
-
- // Re-exec this test in a subprocess
- cmd := exec.Command(os.Args[0], "-test.run=TestSeccompIntegration$", "-test.v")
- cmd.Env = append(os.Environ(), "TEST_SECCOMP_HELPER=1")
- output, err := cmd.CombinedOutput()
- t.Logf("Helper output:\n%s", output)
- if err != nil {
- t.Fatalf("Helper failed: %v", err)
- }
-}
-
-func runSeccompHelper(t *testing.T) {
- pid := os.Getpid()
- t.Logf("Helper PID: %d", pid)
-
- // Install seccomp filter (same as -seccomp flag does in main)
- if err := seccomp.BlockKillSelf(); err != nil {
- t.Fatalf("BlockKillSelf failed: %v", err)
- }
- t.Log("Seccomp filter installed")
-
- // Spawn a child that tries to kill us
- script := fmt.Sprintf("kill -TERM %d 2>&1; echo exit=$?", pid)
- cmd := exec.Command("sh", "-c", script)
- output, _ := cmd.CombinedOutput()
- t.Logf("Kill attempt output: %s", output)
-
- // Verify the kill was blocked (output should contain "Operation not permitted" or exit=1)
- outStr := string(output)
- if !strings.Contains(outStr, "Operation not permitted") && !strings.Contains(outStr, "exit=1") {
- t.Fatalf("Expected kill to fail with Operation not permitted, got: %s", outStr)
- }
-
- t.Log("SUCCESS: Child's kill attempt was blocked")
-}
-
-// TestSeccompPreservesKillOthers verifies that with seccomp enabled,
-// we can still kill other processes (not ourselves).
-func TestSeccompPreservesKillOthers(t *testing.T) {
- if os.Getenv("TEST_SECCOMP_KILL_OTHERS") == "1" {
- runSeccompKillOthersHelper(t)
- return
- }
-
- // Re-exec this test in a subprocess
- cmd := exec.Command(os.Args[0], "-test.run=TestSeccompPreservesKillOthers$", "-test.v")
- cmd.Env = append(os.Environ(), "TEST_SECCOMP_KILL_OTHERS=1")
- output, err := cmd.CombinedOutput()
- t.Logf("Helper output:\n%s", output)
- if err != nil {
- t.Fatalf("Helper failed: %v", err)
- }
-}
-
-func runSeccompKillOthersHelper(t *testing.T) {
- // Install seccomp filter
- if err := seccomp.BlockKillSelf(); err != nil {
- t.Fatalf("BlockKillSelf failed: %v", err)
- }
- t.Log("Seccomp filter installed")
-
- // Start a sleep process
- sleepCmd := exec.Command("sleep", "60")
- if err := sleepCmd.Start(); err != nil {
- t.Fatalf("Failed to start sleep: %v", err)
- }
- sleepPid := sleepCmd.Process.Pid
- t.Logf("Started sleep process with PID %d", sleepPid)
-
- // Kill the sleep process via a child shell - this should work
- script := fmt.Sprintf("kill -TERM %d 2>&1; echo exit=$?", sleepPid)
- cmd := exec.Command("sh", "-c", script)
- output, _ := cmd.CombinedOutput()
- t.Logf("Kill output: %s", output)
-
- // Verify the sleep process was killed (exit=0)
- if !strings.Contains(string(output), "exit=0") {
- t.Fatalf("Expected kill to succeed, got: %s", output)
- }
-
- sleepCmd.Wait()
- t.Log("SUCCESS: Killing other processes still works")
-}
-
-// Silence unused import warning
-var _ = strconv.Itoa
@@ -1,13 +0,0 @@
-//go:build linux && amd64
-
-package seccomp
-
-import "golang.org/x/sys/unix"
-
-const (
- auditArch = unix.AUDIT_ARCH_X86_64
- sysKill = 62
- sysTkill = 200
- sysTgkill = 234
- sysPidfdSendSignal = 424
-)
@@ -1,13 +0,0 @@
-//go:build linux && arm64
-
-package seccomp
-
-import "golang.org/x/sys/unix"
-
-const (
- auditArch = unix.AUDIT_ARCH_AARCH64
- sysKill = 129
- sysTkill = 130
- sysTgkill = 131
- sysPidfdSendSignal = 424
-)
@@ -1,132 +0,0 @@
-//go:build linux
-
-// Package seccomp provides a seccomp filter to prevent child processes
-// from killing the parent process.
-//
-// Note: We use raw BPF instead of github.com/seccomp/libseccomp-golang
-// because that library requires cgo and links against libseccomp.
-// This pure-Go implementation avoids the cgo dependency.
-package seccomp
-
-import (
- "fmt"
- "os"
- "unsafe"
-
- "golang.org/x/sys/unix"
-)
-
-// BPF instruction constants
-const (
- bpfLD = 0x00
- bpfW = 0x00
- bpfABS = 0x20
- bpfJMP = 0x05
- bpfJEQ = 0x10
- bpfRET = 0x06
- bpfK = 0x00
-)
-
-// seccomp_data offsets
-const (
- offsetNr = 0 // syscall number (int, 4 bytes)
- offsetArch = 4 // architecture (u32, 4 bytes)
- offsetArgs = 16 // args[0] starts at offset 16 (u64 each)
-)
-
-// bpfStmt creates a BPF statement (no jump targets)
-func bpfStmt(code uint16, k uint32) unix.SockFilter {
- return unix.SockFilter{Code: code, Jt: 0, Jf: 0, K: k}
-}
-
-// bpfJump creates a BPF jump instruction
-func bpfJump(code uint16, k uint32, jt, jf uint8) unix.SockFilter {
- return unix.SockFilter{Code: code, Jt: jt, Jf: jf, K: k}
-}
-
-// BlockKillSelf installs a seccomp filter that prevents any process from
-// sending signals to the current process via kill(2) and related syscalls
-// (tkill, tgkill).
-// This must be called before spawning child processes.
-// The filter is inherited by child processes.
-//
-// The filter is installed with SECCOMP_FILTER_FLAG_TSYNC to synchronize
-// across all threads in the process, ensuring child processes spawned
-// from any goroutine will inherit the filter.
-func BlockKillSelf() error {
- pid := uint32(os.Getpid())
- // Negative PID in two's complement (for blocking kill(-pid, sig) which
- // sends signals to the process group)
- negPid := uint32(-int32(pid))
-
- // Build BPF filter program that blocks kill/tkill/tgkill
- // when arg0 (target pid) matches our pid or -pid.
- //
- // The filter structure:
- // 1. Load and check architecture
- // 2. Load syscall number
- // 3. Check if it's one of the signal-sending syscalls
- // 4. If so, check if arg0 == our pid OR arg0 == -our pid
- // 5. If targeting us, return EPERM; otherwise allow
- filter := []unix.SockFilter{
- // [0] Load architecture
- bpfStmt(bpfLD|bpfW|bpfABS, offsetArch),
- // [1] If not our arch, jump to allow (end of filter)
- bpfJump(bpfJMP|bpfJEQ|bpfK, auditArch, 0, 12), // skip to ALLOW at [14]
-
- // [2] Load syscall number
- bpfStmt(bpfLD|bpfW|bpfABS, offsetNr),
-
- // [3] Check for kill
- bpfJump(bpfJMP|bpfJEQ|bpfK, sysKill, 4, 0), // match -> check pid at [8]
- // [4] Check for tkill
- bpfJump(bpfJMP|bpfJEQ|bpfK, sysTkill, 3, 0), // match -> check pid at [8]
- // [5] Check for tgkill (arg0 is tgid, arg2 is tid - we check arg0)
- bpfJump(bpfJMP|bpfJEQ|bpfK, sysTgkill, 2, 0), // match -> check pid at [8]
-
- // [6-7] Jump to allow for non-matching syscalls
- bpfJump(bpfJMP|bpfJEQ|bpfK, 0xFFFFFFFF, 0, 7), // never matches, always jumps to ALLOW at [14]
- bpfStmt(bpfRET|bpfK, unix.SECCOMP_RET_ALLOW), // [7] unreachable filler
-
- // [8] Load first argument (target PID) - lower 32 bits
- bpfStmt(bpfLD|bpfW|bpfABS, offsetArgs),
- // [9] Check if target PID matches our PID (positive)
- bpfJump(bpfJMP|bpfJEQ|bpfK, pid, 3, 0), // if our pid, jump to EPERM at [13]
- // [10] Check if target PID matches -our PID (for process group kills)
- bpfJump(bpfJMP|bpfJEQ|bpfK, negPid, 2, 0), // if -our pid, jump to EPERM at [13]
-
- // [11] Not targeting us, allow
- bpfStmt(bpfRET|bpfK, unix.SECCOMP_RET_ALLOW),
-
- // [12] Unreachable filler
- bpfStmt(bpfRET|bpfK, unix.SECCOMP_RET_ALLOW),
-
- // [13] Return EPERM for signal syscalls targeting our process
- bpfStmt(bpfRET|bpfK, unix.SECCOMP_RET_ERRNO|uint32(unix.EPERM)),
-
- // [14] Allow the syscall
- bpfStmt(bpfRET|bpfK, unix.SECCOMP_RET_ALLOW),
- }
-
- // Set NO_NEW_PRIVS to allow unprivileged seccomp
- if err := unix.Prctl(unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); err != nil {
- return fmt.Errorf("prctl(PR_SET_NO_NEW_PRIVS): %w", err)
- }
-
- // Install the seccomp filter
- prog := unix.SockFprog{
- Len: uint16(len(filter)),
- Filter: &filter[0],
- }
-
- // Use seccomp() syscall with SECCOMP_FILTER_FLAG_TSYNC to apply the filter
- // to all threads in the process. This ensures that child processes spawned
- // from any goroutine (which may run on different OS threads) will inherit
- // the filter.
- _, _, errno := unix.Syscall(unix.SYS_SECCOMP, unix.SECCOMP_SET_MODE_FILTER, unix.SECCOMP_FILTER_FLAG_TSYNC, uintptr(unsafe.Pointer(&prog)))
- if errno != 0 {
- return fmt.Errorf("seccomp(SECCOMP_SET_MODE_FILTER, TSYNC): %w", errno)
- }
-
- return nil
-}
@@ -1,180 +0,0 @@
-//go:build linux
-
-package seccomp
-
-import (
- "fmt"
- "os"
- "os/exec"
- "strconv"
- "strings"
- "syscall"
- "testing"
-)
-
-func TestBlockKillSelf(t *testing.T) {
- // This test must run in a subprocess because seccomp filters are inherited
- // by child processes and cannot be removed once installed.
- if os.Getenv("TEST_SECCOMP_SUBPROCESS") == "1" {
- runSeccompTestSubprocess(t)
- return
- }
-
- // Re-exec this test in a subprocess
- cmd := exec.Command(os.Args[0], "-test.run=TestBlockKillSelf$", "-test.v")
- cmd.Env = append(os.Environ(), "TEST_SECCOMP_SUBPROCESS=1")
- output, err := cmd.CombinedOutput()
- t.Logf("Subprocess output:\n%s", output)
- if err != nil {
- t.Fatalf("Subprocess failed: %v", err)
- }
-}
-
-func runSeccompTestSubprocess(t *testing.T) {
- pid := os.Getpid()
- t.Logf("Running seccomp test in subprocess with PID %d", pid)
-
- // Install the seccomp filter
- if err := BlockKillSelf(); err != nil {
- t.Fatalf("BlockKillSelf failed: %v", err)
- }
- t.Log("Seccomp filter installed")
-
- // Now spawn a child process that tries to kill us
- // We use a shell command because we need a separate process
- cmd := exec.Command("sh", "-c", "kill -TERM "+strconv.Itoa(pid)+" 2>&1; echo exit_code=$?")
- output, _ := cmd.CombinedOutput()
- t.Logf("Kill attempt output: %s", output)
-
- // The kill should have failed with EPERM
- // If we're still alive, the seccomp filter worked!
- t.Log("We survived the kill attempt!")
-
- // Also verify we can still kill other things (like a sleep process)
- sleepCmd := exec.Command("sleep", "60")
- if err := sleepCmd.Start(); err != nil {
- t.Fatalf("Failed to start sleep: %v", err)
- }
- sleepPid := sleepCmd.Process.Pid
-
- // Kill the sleep process - this should work
- if err := syscall.Kill(sleepPid, syscall.SIGTERM); err != nil {
- t.Errorf("Failed to kill sleep process: %v", err)
- }
- sleepCmd.Wait()
- t.Logf("Successfully killed sleep process %d", sleepPid)
-
- // Try to kill ourselves directly - this should fail
- err := syscall.Kill(pid, syscall.SIGTERM)
- if err == nil {
- t.Fatal("Expected kill of self to fail, but it succeeded")
- }
- if err != syscall.EPERM {
- t.Fatalf("Expected EPERM, got %v", err)
- }
- t.Logf("Kill of self correctly returned EPERM")
-
- // Try to kill using negative PID (process group kill) - this should also fail
- err = syscall.Kill(-pid, syscall.SIGTERM)
- if err == nil {
- t.Fatal("Expected kill of -self to fail, but it succeeded")
- }
- if err != syscall.EPERM {
- t.Fatalf("Expected EPERM for negative PID, got %v", err)
- }
- t.Logf("Kill of -self correctly returned EPERM")
-}
-
-func TestBlockKillSelf_ChildCannotKillParent(t *testing.T) {
- // This is the main test: verify that after installing seccomp,
- // a child process cannot kill the parent (shelley) process.
- if os.Getenv("TEST_SECCOMP_CHILD_SUBPROCESS") == "1" {
- runChildCannotKillParentSubprocess(t)
- return
- }
-
- // Re-exec this test in a subprocess
- cmd := exec.Command(os.Args[0], "-test.run=TestBlockKillSelf_ChildCannotKillParent$", "-test.v")
- cmd.Env = append(os.Environ(), "TEST_SECCOMP_CHILD_SUBPROCESS=1")
- output, err := cmd.CombinedOutput()
- t.Logf("Subprocess output:\n%s", output)
- if err != nil {
- t.Fatalf("Subprocess failed: %v", err)
- }
-}
-
-func runChildCannotKillParentSubprocess(t *testing.T) {
- pid := os.Getpid()
- t.Logf("Parent process PID: %d", pid)
-
- // Install the seccomp filter BEFORE spawning children
- if err := BlockKillSelf(); err != nil {
- t.Fatalf("BlockKillSelf failed: %v", err)
- }
- t.Log("Seccomp filter installed in parent")
-
- // Spawn a child process that tries to kill the parent using positive PID
- // The child inherits the seccomp filter, which blocks kill(parent_pid, ...)
- script := fmt.Sprintf(`
-echo "Child attempting to kill parent PID %d"
-kill -TERM %d 2>&1
-result=$?
-echo "kill exit code: $result"
-if [ $result -ne 0 ]; then
- echo "SUCCESS: kill was blocked"
- exit 0
-else
- echo "FAILURE: kill succeeded (parent should be dead)"
- exit 1
-fi
-`, pid, pid)
-
- cmd := exec.Command("sh", "-c", script)
- output, err := cmd.CombinedOutput()
- t.Logf("Child output (positive PID):\n%s", output)
-
- // Check that the child reported success (kill was blocked)
- if err != nil {
- t.Fatalf("Child process reported failure (positive PID): %v", err)
- }
-
- // Verify the output contains our success message
- if !strings.Contains(string(output), "SUCCESS: kill was blocked") {
- t.Fatalf("Expected success message in output (positive PID)")
- }
-
- // We're still alive!
- t.Logf("Parent (PID %d) survived child's positive PID kill attempt", pid)
-
- // Now test with negative PID (process group kill)
- negScript := fmt.Sprintf(`
-echo "Child attempting to kill parent process group with PID -%d"
-kill -TERM -%d 2>&1
-result=$?
-echo "kill exit code: $result"
-if [ $result -ne 0 ]; then
- echo "SUCCESS: kill -pid was blocked"
- exit 0
-else
- echo "FAILURE: kill -pid succeeded (parent should be dead)"
- exit 1
-fi
-`, pid, pid)
-
- negCmd := exec.Command("sh", "-c", negScript)
- negOutput, negErr := negCmd.CombinedOutput()
- t.Logf("Child output (negative PID):\n%s", negOutput)
-
- // Check that the child reported success (kill was blocked)
- if negErr != nil {
- t.Fatalf("Child process reported failure (negative PID): %v", negErr)
- }
-
- // Verify the output contains our success message
- if !strings.Contains(string(negOutput), "SUCCESS: kill -pid was blocked") {
- t.Fatalf("Expected success message in output (negative PID)")
- }
-
- // We're still alive!
- t.Logf("Parent (PID %d) survived child's negative PID kill attempt", pid)
-}
@@ -1,9 +0,0 @@
-//go:build !linux
-
-package seccomp
-
-// BlockKillSelf is a no-op on non-Linux systems.
-// Seccomp is a Linux-specific feature.
-func BlockKillSelf() error {
- return nil
-}