Clean up ambient support and add a smoke test for them.

Signed-off-by: Andrew G. Morgan <morgan@kernel.org>
diff --git a/kdebug/test-kernel.sh b/kdebug/test-kernel.sh
index c8ce144..d480a63 100755
--- a/kdebug/test-kernel.sh
+++ b/kdebug/test-kernel.sh
@@ -47,7 +47,7 @@
 file /root/getpcaps $HERE/../progs/getpcaps 0755 0 0
 EOF
 
-COMMANDS="ls ln cp id pwd mkdir rmdir cat rm sh mount umount chmod less"
+COMMANDS="ls ln cp dmesg id pwd mkdir rmdir cat rm sh mount umount chmod less vi"
 for f in $COMMANDS; do
     echo slink /bin/$f /sbin/busybox 0755 0 0 >> fs.conf
 done
diff --git a/libcap/cap_proc.c b/libcap/cap_proc.c
index ffa0d91..f70b0e3 100644
--- a/libcap/cap_proc.c
+++ b/libcap/cap_proc.c
@@ -166,8 +166,8 @@
 	errno = EINVAL;
 	return -1;
     }
-    result = prctl(PR_CAP_AMBIENT, pr_arg(PR_CAP_AMBIENT_RAISE),
-		   pr_arg(cap), pr_arg(val), pr_arg(0));
+    result = prctl(PR_CAP_AMBIENT, pr_arg(val), pr_arg(cap),
+		   pr_arg(0), pr_arg(0));
     if (result < 0) {
 	errno = -result;
 	return -1;
diff --git a/progs/capsh.c b/progs/capsh.c
index 9c907a7..a1d6e2b 100644
--- a/progs/capsh.c
+++ b/progs/capsh.c
@@ -26,9 +26,6 @@
 
 #define MAX_GROUPS       100   /* max number of supplementary groups for user */
 
-static const cap_value_t raise_setpcap[1] = { CAP_SETPCAP };
-static const cap_value_t raise_chroot[1] = { CAP_SYS_CHROOT };
-
 static char *binary(unsigned long value)
 {
     static char string[8*sizeof(unsigned long) + 1];
@@ -140,6 +137,175 @@
     printf("\n");
 }
 
+static const cap_value_t raise_setpcap[1] = { CAP_SETPCAP };
+static const cap_value_t raise_chroot[1] = { CAP_SYS_CHROOT };
+
+static void push_pcap(cap_t *orig_p, cap_t *raised_for_setpcap_p)
+{
+    /*
+     * We need to do this here because --inh=XXX may have reset
+     * orig and it isn't until we are within the --drop code that
+     * we know what the prevailing (orig) pI value is.
+     */
+    *orig_p = cap_get_proc();
+    if (NULL == *orig_p) {
+	perror("Capabilities not available");
+	exit(1);
+    }
+
+    *raised_for_setpcap_p = cap_dup(*orig_p);
+    if (NULL == *raised_for_setpcap_p) {
+	fprintf(stderr, "modification requires CAP_SETPCAP\n");
+	exit(1);
+    }
+    if (cap_set_flag(*raised_for_setpcap_p, CAP_EFFECTIVE, 1,
+		     raise_setpcap, CAP_SET) != 0) {
+	perror("unable to select CAP_SETPCAP");
+	exit(1);
+    }
+}
+
+static void pop_pcap(cap_t orig, cap_t raised_for_setpcap)
+{
+    cap_free(raised_for_setpcap);
+    cap_free(orig);
+}
+
+static void arg_drop(const char *arg_names)
+{
+    char *ptr;
+    cap_t orig, raised_for_setpcap;
+    char *names;
+
+    push_pcap(&orig, &raised_for_setpcap);
+    if (strcmp("all", arg_names) == 0) {
+	unsigned j = 0;
+	while (CAP_IS_SUPPORTED(j)) {
+	    int status;
+	    if (cap_set_proc(raised_for_setpcap) != 0) {
+		perror("unable to raise CAP_SETPCAP for BSET changes");
+		exit(1);
+	    }
+	    status = cap_drop_bound(j);
+	    if (cap_set_proc(orig) != 0) {
+		perror("unable to lower CAP_SETPCAP post BSET change");
+		exit(1);
+	    }
+	    if (status != 0) {
+		char *name_ptr;
+
+		name_ptr = cap_to_name(j);
+		fprintf(stderr, "Unable to drop bounding capability [%s]\n",
+			name_ptr);
+		cap_free(name_ptr);
+		exit(1);
+	    }
+	    j++;
+	}
+	pop_pcap(orig, raised_for_setpcap);
+	return;
+    }
+
+    names = strdup(arg_names);
+    if (NULL == names) {
+	fprintf(stderr, "failed to allocate names\n");
+	exit(1);
+    }
+    for (ptr = names; (ptr = strtok(ptr, ",")); ptr = NULL) {
+	/* find name for token */
+	cap_value_t cap;
+	int status;
+
+	if (cap_from_name(ptr, &cap) != 0) {
+	    fprintf(stderr, "capability [%s] is unknown to libcap\n", ptr);
+	    exit(1);
+	}
+	if (cap_set_proc(raised_for_setpcap) != 0) {
+	    perror("unable to raise CAP_SETPCAP for BSET changes");
+	    exit(1);
+	}
+	status = cap_drop_bound(cap);
+	if (cap_set_proc(orig) != 0) {
+	    perror("unable to lower CAP_SETPCAP post BSET change");
+	    exit(1);
+	}
+	if (status != 0) {
+	    fprintf(stderr, "failed to drop [%s=%u]\n", ptr, cap);
+	    exit(1);
+	}
+    }
+    pop_pcap(orig, raised_for_setpcap);
+    free(names);
+}
+
+static void arg_change_amb(const char *arg_names, cap_flag_value_t set)
+{
+    char *ptr;
+    cap_t orig, raised_for_setpcap;
+    char *names;
+
+    push_pcap(&orig, &raised_for_setpcap);
+    if (strcmp("all", arg_names) == 0) {
+	unsigned j = 0;
+	while (CAP_IS_SUPPORTED(j)) {
+	    int status;
+	    if (cap_set_proc(raised_for_setpcap) != 0) {
+		perror("unable to raise CAP_SETPCAP for AMBIENT changes");
+		exit(1);
+	    }
+	    status = cap_set_ambient(j, set);
+	    if (cap_set_proc(orig) != 0) {
+		perror("unable to lower CAP_SETPCAP post AMBIENT change");
+		exit(1);
+	    }
+	    if (status != 0) {
+		char *name_ptr;
+
+		name_ptr = cap_to_name(j);
+		fprintf(stderr, "Unable to %s ambient capability [%s]\n",
+			set == CAP_CLEAR ? "clear":"raise", name_ptr);
+		cap_free(name_ptr);
+		exit(1);
+	    }
+	    j++;
+	}
+	pop_pcap(orig, raised_for_setpcap);
+	return;
+    }
+
+    names = strdup(arg_names);
+    if (NULL == names) {
+	fprintf(stderr, "failed to allocate names\n");
+	exit(1);
+    }
+    for (ptr = names; (ptr = strtok(ptr, ",")); ptr = NULL) {
+	/* find name for token */
+	cap_value_t cap;
+	int status;
+
+	if (cap_from_name(ptr, &cap) != 0) {
+	    fprintf(stderr, "capability [%s] is unknown to libcap\n", ptr);
+	    exit(1);
+	}
+	if (cap_set_proc(raised_for_setpcap) != 0) {
+	    perror("unable to raise CAP_SETPCAP for AMBIENT changes");
+	    exit(1);
+	}
+	status = cap_set_ambient(cap, set);
+	if (cap_set_proc(orig) != 0) {
+	    perror("unable to lower CAP_SETPCAP post AMBIENT change");
+	    exit(1);
+	}
+	if (status != 0) {
+	    fprintf(stderr, "failed to %s ambient [%s=%u]\n",
+		    set == CAP_CLEAR ? "clear":"raise", ptr, cap);
+	    exit(1);
+	}
+    }
+    pop_pcap(orig, raised_for_setpcap);
+    free(names);
+}
+
 int main(int argc, char *argv[], char *envp[])
 {
     pid_t child;
@@ -149,76 +315,21 @@
 
     for (i=1; i<argc; ++i) {
 	if (!memcmp("--drop=", argv[i], 4)) {
-	    char *ptr;
-	    cap_t orig, raised_for_setpcap;
-
-	    /*
-	     * We need to do this here because --inh=XXX may have reset
-	     * orig and it isn't until we are within the --drop code that
-	     * we know what the prevailing (orig) pI value is.
-	     */
-	    orig = cap_get_proc();
-	    if (orig == NULL) {
-		perror("Capabilities not available");
+	    arg_drop(argv[i]+7);
+	} else if (!strcmp("--has-ambient", argv[i])) {
+	    if (!CAP_AMBIENT_SUPPORTED()) {
+		fprintf(stderr, "ambient set not supported\n");
 		exit(1);
 	    }
-
-	    raised_for_setpcap = cap_dup(orig);
-	    if (raised_for_setpcap == NULL) {
-		fprintf(stderr, "BSET modification requires CAP_SETPCAP\n");
+	} else if (!memcmp("--addamb=", argv[i], 9)) {
+	    arg_change_amb(argv[i]+9, CAP_SET);
+	} else if (!memcmp("--delamb=", argv[i], 9)) {
+	    arg_change_amb(argv[i]+9, CAP_CLEAR);
+	} else if (!memcmp("--noamb", argv[i], 7)) {
+	    if (cap_reset_ambient() != 0) {
+		fprintf(stderr, "failed to reset ambient set\n");
 		exit(1);
 	    }
-
-	    if (cap_set_flag(raised_for_setpcap, CAP_EFFECTIVE, 1,
-			     raise_setpcap, CAP_SET) != 0) {
-		perror("unable to select CAP_SETPCAP");
-		exit(1);
-	    }
-
-	    if (strcmp("all", argv[i]+7) == 0) {
-		unsigned j = 0;
-		while (CAP_IS_SUPPORTED(j)) {
-		    if (cap_drop_bound(j) != 0) {
-			char *name_ptr;
-
-			name_ptr = cap_to_name(j);
-			fprintf(stderr,
-				"Unable to drop bounding capability [%s]\n",
-				name_ptr);
-			cap_free(name_ptr);
-			exit(1);
-		    }
-		    j++;
-		}
-	    } else {
-		for (ptr = argv[i]+7; (ptr = strtok(ptr, ",")); ptr = NULL) {
-		    /* find name for token */
-		    cap_value_t cap;
-		    int status;
-
-		    if (cap_from_name(ptr, &cap) != 0) {
-			fprintf(stderr,
-				"capability [%s] is unknown to libcap\n",
-				ptr);
-			exit(1);
-		    }
-		    if (cap_set_proc(raised_for_setpcap) != 0) {
-			perror("unable to raise CAP_SETPCAP for BSET changes");
-			exit(1);
-		    }
-		    status = prctl(PR_CAPBSET_DROP, cap);
-		    if (cap_set_proc(orig) != 0) {
-			perror("unable to lower CAP_SETPCAP post BSET change");
-			exit(1);
-		    }
-		    if (status) {
-			fprintf(stderr, "failed to drop [%s=%u]\n", ptr, cap);
-			exit(1);
-		    }
-		}
-	    }
-	    cap_free(raised_for_setpcap);
-	    cap_free(orig);
 	} else if (!memcmp("--inh=", argv[i], 6)) {
 	    cap_t all, raised_for_setpcap;
 	    char *text;
@@ -594,6 +705,9 @@
 		   "  --decode=xxx   decode a hex string to a list of caps\n"
 		   "  --supports=xxx exit 1 if capability xxx unsupported\n"
 		   "  --drop=xxx     remove xxx,.. capabilities from bset\n"
+		   "  --addamb=xxx   add xxx,... capabilities to ambient set\n"
+		   "  --delamb=xxx   remove xxx,... capabilities from ambient\n"
+		   "  --noamb=xxx    reset the ambient capabilities\n"
 		   "  --caps=xxx     set caps as per cap_from_text()\n"
 		   "  --inh=xxx      set xxx,.. inheritiable set\n"
 		   "  --secbits=<n>  write a new value for securebits\n"
diff --git a/progs/quicktest.sh b/progs/quicktest.sh
index e8b2c8e..fc7b4cc 100755
--- a/progs/quicktest.sh
+++ b/progs/quicktest.sh
@@ -46,7 +46,7 @@
 
 
 # Make a local non-setuid-0 version of capsh and call it privileged
-cp ./capsh ./privileged && chmod -s ./privileged
+cp ./capsh ./privileged && /bin/chmod -s ./privileged
 if [ $? -ne 0 ]; then
     echo "Failed to copy capsh for capability manipulation"
     exit 1
@@ -67,11 +67,11 @@
 # Explore keep_caps support
 pass_capsh --keep=0 --keep=1 --keep=0 --keep=1 --print
 
-rm -f tcapsh
-cp capsh tcapsh
-chown root.root tcapsh
-chmod u+s tcapsh
-ls -l tcapsh
+/bin/rm -f tcapsh
+/bin/cp capsh tcapsh
+/bin/chown root.root tcapsh
+/bin/chmod u+s tcapsh
+/bin/ls -l tcapsh
 
 # leverage keep caps maintain capabilities accross a change of uid
 # from setuid root to capable luser (as per wireshark/dumpcap 0.99.7)
@@ -98,7 +98,7 @@
 pass_capsh --secbits=10 --keep=0 --keep=1 --print
 fail_capsh --secbits=47 -- -c "./tcapsh --uid=$nouid"
 
-rm -f tcapsh
+/bin/rm -f tcapsh
 
 # Suppress uid=0 privilege
 fail_capsh --secbits=47 --print -- -c "./capsh --uid=$nouid"
@@ -117,10 +117,10 @@
 pass_capsh --secbits=47 --inh=cap_setuid,cap_setgid --drop=cap_setuid \
     --uid=500 --print -- -c "./privileged --uid=$nouid"
 
-rm -f ./privileged
+/bin/rm -f ./privileged
 
 # test that we do not support capabilities on setuid shell-scripts
-cat > hack.sh <<EOF
+/bin/cat > hack.sh <<EOF
 #!/bin/bash
 /usr/bin/id
 mypid=\$\$
@@ -134,20 +134,53 @@
 fi
 exit 0
 EOF
-chmod +xs hack.sh
+/bin/chmod +xs hack.sh
 ./capsh --uid=500 --inh=none --print -- ./hack.sh
 status=$?
-rm -f ./hack.sh
+/bin/rm -f ./hack.sh
 if [ $status -ne 0 ]; then
     echo "shell scripts can have capabilities (bug)"
     exit 1
 fi
 
-# Max lockdown
+# Max lockdown (ie., pure capability model as POSIX.1e intended).
+secbits=0x2f
+if ./capsh --has-ambient ; then
+    secbits="0xef --noamb"
+fi
 pass_capsh --keep=1 --uid=$nouid --caps=cap_setpcap=ep \
-    --drop=all --secbits=0x2f --caps= --print
+    --drop=all --secbits=$secbits --caps= --print
 
 # Verify we can chroot
 pass_capsh --chroot=$(/bin/pwd)
 pass_capsh --chroot=$(/bin/pwd) ==
 fail_capsh --chroot=$(/bin/pwd) -- -c "echo oops"
+
+exit_early () {
+    echo "$*"
+    exit 0
+}
+
+./capsh --has-ambient || exit_early "skipping ambient tests"
+
+# Ambient capabilities (any file can inherit capabilities)
+pass_capsh --noamb
+
+# test that shell scripts can inherit through ambient capabilities
+/bin/cat > hack.sh <<EOF
+#!/bin/bash
+/usr/bin/id
+mypid=\$\$
+caps=\$(./getpcaps \$mypid 2>&1 | /usr/bin/cut -d: -f2)
+if [ "\$caps" != " = cap_setuid+i" ]; then
+  echo "Shell script got [\$caps]"
+  exit 0
+fi
+ls -l \$0
+echo "no capabilities [\$caps] for this shell script"
+exit 1
+EOF
+/bin/chmod +x hack.sh
+pass_capsh --keep=1 --uid=$nouid --inh=cap_setuid --addamb=cap_setuid -- ./hack.sh
+
+/bin/rm -f hack.sh