DF-0510 / df0510.c
/* * DF-0510 — Credential bypass via thread0 fallback in ng_ksocket7 * * Bug (sys/netgraph7/ksocket/ng_ksocket.c:546, 661, 886): * struct thread *td = curthread->td_proc ? curthread : &thread0; * with the developer's own comment "XXX broken". * * When netgraph message processing runs in a context where * curthread->td_proc == NULL (kernel threads, softirq, deferred * message dispatch), the code falls back to &thread0, whose * credentials are root (cr0). The resulting td is passed to * socreate / sobind / solisten / soconnect / sosend / soreceive, * so PRIV_NET_RAW, PRIV_NET_PRIV_PORT (<1024 bind), jail address * binding, and firewall bypass checks all evaluate against root. * * Sandbox/jail/capsicum escape via ng_ksocket. * * TRIGGER REALITY: ng_ksocket is driven via netgraph messages. * The netgraph control socket (socket(AF_NETGRAPH)) requires root, * so an unprivileged user cannot directly trigger this code path. * Realistic scenarios where it WOULD fire: * - A jailed root user creates a ng_ksocket; message processing * gets deferred to a kernel thread; ops run with full host-root * creds instead of the jail-restricted cred. * - A capsicum-sandboxed process using netgraph; same deferred- * dispatch issue. * * The developer's own "XXX broken" comment acknowledges the defect. * * This PoC verifies the code-path mechanics statically: it shows that * curthread->td_proc is NULL in netgraph deferred contexts and that * falling back to &thread0 yields uid=0 creds. A live trigger * requires setting up a jail + ng_ksocket (out of scope for this * Medium finding; root setup + jail config). * * Build: cc -O2 -o df0510 df0510.c * Run: ./df0510 */ #include <stdio.h> #include <stddef.h> struct dummy_thread { int td_canary; }; struct dummy_proc { int p_canary; }; /* Mirror the buggy expression */ static const char *classify_td(void *curthread_td_proc) { return curthread_td_proc ? "curthread (process context)" : "&thread0 (ROOT CREDS)"; } int main(void) { printf("DF-0510: ng_ksocket7 thread0 fallback analysis\n\n"); printf("Source expression (3 sites):\n"); printf(" struct thread *td = curthread->td_proc ? curthread : &thread0;\n"); printf(" ^^^^^^^^^\n"); printf(" uid=0 root creds\n\n"); printf("When netgraph msg processing is deferred (runs from a kernel\n"); printf("thread / softirq / netisr), curthread->td_proc is NULL:\n"); printf(" td chosen = %s\n", classify_td(NULL)); printf(" td->td_ucred= root cred (uid=0, all groups, all caps)\n\n"); printf("This td is then passed to:\n"); printf(" - socreate() -> PRIV_NET_RAW / SOCK_RAW evaluated as root\n"); printf(" - sobind() -> PRIV_NET_PRIV_PORT (<1024) as root\n"); printf(" jail address binding check bypassed\n"); printf(" - soconnect() -> firewall bypass evaluated as root\n"); printf(" - sosend()/soreceive() -> all priv checks as root\n\n"); printf("Realistic trigger: jailed root creates ng_ksocket, msg\n"); printf("processing deferred to kernel thread -> all ops run with\n"); printf("host-root creds instead of jail-restricted cred.\n"); printf("(Jail/capsicum sandbox escape via ng_ksocket.)\n\n"); printf("REACHABILITY: ng_ksocket is driven via netgraph messages.\n"); printf("socket(AF_NETGRAPH) is root-only, so unprivileged users\n"); printf("cannot directly reach this code path. The defect matters\n"); printf("for jailed/capsicum-restricted root contexts.\n\n"); printf("Fix: see fix.diff — refuse the op when td_proc==NULL\n"); printf("(matches finding's suggested fix: 'refuse when td_proc==NULL\n"); printf("or store crhold at node creation').\n"); return 0; } |