diff --git a/sandbox/container.go b/sandbox/container.go index 70929bb..f2595c3 100644 --- a/sandbox/container.go +++ b/sandbox/container.go @@ -165,7 +165,7 @@ func (p *Container) Start() error { syscall.CLONE_NEWNS, // remain privileged for setup - AmbientCaps: []uintptr{CAP_SYS_ADMIN}, + AmbientCaps: []uintptr{CAP_SYS_ADMIN, CAP_SETPCAP}, UseCgroupFD: p.Cgroup != nil, } diff --git a/sandbox/init.go b/sandbox/init.go index 1116767..437eb7a 100644 --- a/sandbox/init.go +++ b/sandbox/init.go @@ -108,6 +108,9 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) { } } + // cache sysctl before pivot_root + LastCap() + /* set up mount points from intermediate root */ @@ -217,15 +220,21 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) { load seccomp filter */ - if _, _, err := syscall.Syscall(PR_SET_NO_NEW_PRIVS, 1, 0, 0); err != 0 { - log.Fatalf("prctl(PR_SET_NO_NEW_PRIVS): %v", err) + if _, _, errno := syscall.Syscall(PR_SET_NO_NEW_PRIVS, 1, 0, 0); errno != 0 { + log.Fatalf("prctl(PR_SET_NO_NEW_PRIVS): %v", errno) + } + if _, _, errno := syscall.Syscall(syscall.SYS_PRCTL, PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0); errno != 0 { + log.Fatalf("cannot clear the ambient capability set: %v", errno) + } + for i := uintptr(0); i <= LastCap(); i++ { + if _, _, errno := syscall.Syscall(syscall.SYS_PRCTL, syscall.PR_CAPBSET_DROP, i, 0); errno != 0 { + log.Fatalf("cannot drop capability: %v", errno) + } } if err := seccomp.Load(params.Flags.seccomp(params.Seccomp)); err != nil { log.Fatalf("cannot load syscall filter: %v", err) } - /* at this point CAP_SYS_ADMIN can be dropped, however it is kept for now as it does not increase attack surface */ - /* pass through extra files */ diff --git a/sandbox/syscall.go b/sandbox/syscall.go index d477dbc..a92fa17 100644 --- a/sandbox/syscall.go +++ b/sandbox/syscall.go @@ -3,9 +3,15 @@ package sandbox import "syscall" const ( - O_PATH = 0x200000 + O_PATH = 0x200000 + PR_SET_NO_NEW_PRIVS = 0x26 - CAP_SYS_ADMIN = 0x15 + + PR_CAP_AMBIENT = 47 + PR_CAP_AMBIENT_CLEAR_ALL = 4 + + CAP_SYS_ADMIN = 0x15 + CAP_SETPCAP = 8 ) const ( @@ -15,7 +21,7 @@ const ( func SetDumpable(dumpable uintptr) error { // linux/sched/coredump.h - if _, _, errno := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_DUMPABLE, dumpable, 0); errno != 0 { + if _, _, errno := syscall.Syscall(syscall.SYS_PRCTL, syscall.PR_SET_DUMPABLE, dumpable, 0); errno != 0 { return errno } diff --git a/test/test.py b/test/test.py index 1b3bef0..9288bb8 100644 --- a/test/test.py +++ b/test/test.py @@ -99,6 +99,13 @@ print(denyOutputVerbose) # Fail direct fsu call: print(machine.fail("sudo -u alice -i fsu")) +# Verify capabilities/securebits in user namespace: +print(machine.succeed("sudo -u alice -i fortify run capsh --has-no-new-privs")) +print(machine.fail("sudo -u alice -i fortify run capsh --has-a=CAP_SYS_ADMIN")) +print(machine.fail("sudo -u alice -i fortify run capsh --has-b=CAP_SYS_ADMIN")) +print(machine.fail("sudo -u alice -i fortify run capsh --has-p=CAP_SYS_ADMIN")) +print(machine.fail("sudo -u alice -i fortify run umount -R /dev")) + # Verify PrintBaseError behaviour: if denyOutput != "fsu: uid 1001 is not in the fsurc file\n": raise Exception(f"unexpected deny output:\n{denyOutput}")