From 61b2fcc4510641ffd691d8e5a82e968b458f0cb9 Mon Sep 17 00:00:00 2001 From: "Andrew G. Morgan" Date: Sat, 28 Aug 2021 15:58:16 -0700 Subject: Make sudotest more robust against untestable environments I'm setting up some testing environments and they are not all created equal. Signed-off-by: Andrew G. Morgan --- doc/capsh.1 | 7 +++++++ go/Makefile | 2 +- go/try-launching.go | 4 +++- libcap/cap_flag.c | 1 - progs/capsh.c | 13 +++++++++++++ progs/quicktest.sh | 12 ++++++------ tests/uns_test.c | 11 +++++++++++ 7 files changed, 41 insertions(+), 9 deletions(-) diff --git a/doc/capsh.1 b/doc/capsh.1 index e309438..9bed928 100644 --- a/doc/capsh.1 +++ b/doc/capsh.1 @@ -279,6 +279,13 @@ vector has capability .B xxx raised. .TP +.BI \-\-has\-b= xxx +Exit with status 1 unless the +.I bounding +set vector has capability +.B xxx +enabled. +.TP .BI \-\-iab= xxx Attempts to set the IAB tuple of inheritable capability vectors. The text conventions used for \fIxxx\fP are those of diff --git a/go/Makefile b/go/Makefile index ce464d1..854f0e3 100644 --- a/go/Makefile +++ b/go/Makefile @@ -110,7 +110,7 @@ endif # requiring that the hosting kernel supports user namespaces for the # regular test case. sudotest: test ../progs/tcapsh-static b210613 - ./gowns --ns -- -c "echo gowns runs with user namespace" + ../progs/tcapsh-static --has-b=cap_sys_admin || exit 0 && ./gowns --ns -- -c "echo gowns runs with user namespace" ./try-launching ifeq ($(CGO_REQUIRED),0) ./try-launching-cgo diff --git a/go/try-launching.go b/go/try-launching.go index 9f20e6b..b09b254 100644 --- a/go/try-launching.go +++ b/go/try-launching.go @@ -20,6 +20,8 @@ func tryLaunching() { } root := cwd[:strings.LastIndex(cwd, "/")] + hasSysAdmin, _ := cap.GetBound(cap.SYS_ADMIN) + vs := []struct { args []string fail bool @@ -38,7 +40,7 @@ func tryLaunching() { uid: 123, gid: 456, groups: []int{1, 2, 3}, - fail: syscall.Getuid() != 0, + fail: syscall.Getuid() != 0 || !hasSysAdmin, }, { args: []string{"/ok"}, diff --git a/libcap/cap_flag.c b/libcap/cap_flag.c index 1f561f7..9df1842 100644 --- a/libcap/cap_flag.c +++ b/libcap/cap_flag.c @@ -14,7 +14,6 @@ * returned as the contents of *raised. The capability is from one of * the sets stored in cap_d as specified by set and value */ - int cap_get_flag(cap_t cap_d, cap_value_t value, cap_flag_t set, cap_flag_value_t *raised) { diff --git a/progs/capsh.c b/progs/capsh.c index 763c08d..0cf8b1e 100644 --- a/progs/capsh.c +++ b/progs/capsh.c @@ -967,6 +967,17 @@ int main(int argc, char *argv[], char *envp[]) fprintf(stderr, "cap[%s] not in ambient vector\n", argv[i]+8); exit(1); } + } else if (!strncmp("--has-b=", argv[i], 8)) { + cap_value_t cap; + if (cap_from_name(argv[i]+8, &cap) < 0) { + fprintf(stderr, "cap[%s] not recognized by library\n", + argv[i] + 8); + exit(1); + } + if (!cap_get_bound(cap)) { + fprintf(stderr, "cap[%s] not in bounding vector\n", argv[i]+8); + exit(1); + } } else if (!strncmp("--is-uid=", argv[i], 9)) { unsigned value; uid_t uid; @@ -1075,11 +1086,13 @@ int main(int argc, char *argv[], char *envp[]) " --current show current caps and IAB vectors\n" " --decode=xxx decode a hex string to a list of caps\n" " --delamb=xxx remove xxx,... capabilities from ambient\n" + " --drop=xxx drop xxx,... caps from bounding set\n" " --explain=xxx explain what capability xxx permits\n" " --forkfor= fork and make child sleep for sec\n" " --gid= set gid to (hint: id )\n" " --groups=g,... set the supplemental groups\n" " --has-a=xxx exit 1 if capability xxx not ambient\n" + " --has-b=xxx exit 1 if capability xxx not dropped\n" " --has-ambient exit 1 unless ambient vector supported\n" " --has-i=xxx exit 1 if capability xxx not inheritable\n" " --has-p=xxx exit 1 if capability xxx not permitted\n" diff --git a/progs/quicktest.sh b/progs/quicktest.sh index ba64ab5..ebb7567 100755 --- a/progs/quicktest.sh +++ b/progs/quicktest.sh @@ -79,7 +79,7 @@ fail_capsh --mode=NOPRIV --print --mode=PURE1E fail_capsh --user=nobody --mode=NOPRIV --print -- ./privileged # simple IAB setting (no ambient) in pure1e mode. -pass_capsh --mode=PURE1E --iab='!%cap_chown,cap_sys_admin' +pass_capsh --mode=PURE1E --iab='!%cap_chown,cap_setuid' # Explore keep_caps support pass_capsh --keep=0 --keep=1 --keep=0 --keep=1 --print @@ -94,14 +94,14 @@ pass_capsh --keep=0 --keep=1 --keep=0 --keep=1 --print # from setuid root to capable luser (as per wireshark/dumpcap 0.99.7) # This test is subtle. It is testing that a change to self, dropping # euid=0 back to that of the luser keeps capabilities. -pass_capsh --uid=1 -- -c "./tcapsh --keep=1 --caps=\"cap_net_raw,cap_net_admin=ip\" --print --uid=1 --print --caps=\"cap_net_raw,cap_net_admin=pie\" --print" +pass_capsh --uid=1 -- -c "./tcapsh --keep=1 --caps=\"cap_net_raw,cap_net_bind_service=ip\" --print --uid=1 --print --caps=\"cap_net_raw,cap_net_bind_service=pie\" --print" # this test is a change of user to a new user, note we need to raise # the cap_setuid capability (libcap has a function for that) in this case. -pass_capsh --uid=1 -- -c "./tcapsh --caps=\"cap_net_raw,cap_net_admin=ip cap_setuid=p\" --print --cap-uid=2 --print --caps=\"cap_net_raw,cap_net_admin=pie\" --print" +pass_capsh --uid=1 -- -c "./tcapsh --caps=\"cap_net_raw,cap_net_bind_service=ip cap_setuid=p\" --print --cap-uid=2 --print --caps=\"cap_net_raw,cap_net_bind_service=pie\" --print" # This fails, on 2.6.24, but shouldn't -pass_capsh --uid=1 -- -c "./tcapsh --keep=1 --caps=\"cap_net_raw,cap_net_admin=ip\" --uid=1 --forkfor=10 --caps= --print --killit=9 --print" +pass_capsh --uid=1 -- -c "./tcapsh --keep=1 --caps=\"cap_net_raw,cap_net_bind_service=ip\" --uid=1 --forkfor=10 --caps= --print --killit=9 --print" # only continue with these if --secbits is supported ./capsh --secbits=0x2f > /dev/null 2>&1 @@ -214,8 +214,8 @@ EOF pass_capsh --keep=1 --uid=$nouid --inh=cap_setuid --addamb=cap_setuid -- -c "./privileged --print --uid=1" # validate IAB setting with an ambient capability - pass_capsh --iab='!%cap_chown,^cap_setpcap,cap_sys_admin' - fail_capsh --mode=PURE1E --iab='!%cap_chown,^cap_sys_admin' + pass_capsh --iab='!%cap_chown,^cap_setpcap,cap_setuid' + fail_capsh --mode=PURE1E --iab='!%cap_chown,^cap_setuid' fi /bin/rm -f ./privileged diff --git a/tests/uns_test.c b/tests/uns_test.c index a1dbde0..3fe73af 100644 --- a/tests/uns_test.c +++ b/tests/uns_test.c @@ -62,6 +62,17 @@ int main(int argc, char **argv) static const char id_map[] = "0 1 1\n1 2 1\n2 0 1\n3 3 49999997\n"; cap_value_t fscap = CAP_SETFCAP; cap_t orig = cap_get_proc(); + cap_flag_value_t present; + + if (cap_get_flag(orig, CAP_SYS_ADMIN, CAP_EFFECTIVE, &present) != 0) { + perror("failed to read a capability flag"); + exit(1); + } + if (present != CAP_SET) { + fprintf(stderr, + "environment missing cap_sys_admin - exploit not testable\n"); + exit(0); + } /* Run with this one lowered */ cap_set_flag(orig, CAP_EFFECTIVE, 1, &fscap, CAP_CLEAR); -- cgit v1.2.1