DF-1356 / harness.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 | /* * DF-1356 harness: heap overflow in AMR_CMD_PASS ioctl via unchecked * user-controlled CDB length (amr.c amr_ioctl). * * OBJECT-LEVEL proof of the primitive. The amr (LSI MegaRAID) driver does * not attach on the audit QEMU guest (no MegaRAID HBA present), and even on * a host with the controller /dev/amrN is created UID_ROOT GID_OPERATOR mode * 0600 (S_IRUSR|S_IWUSR only), so this is a root->kernel path. The vulnerable * code path is therefore not runtime-reachable here. This harness replicates * the EXACT kernel struct layout (union amr_ccb / struct amr_passthrough) and * the exact unchecked bcopy from amr_ioctl to PROVE the heap-overflow * primitive is real. * * Cited path: sys/dev/raid/amr/amr.c:832 (len = au_cmd[2]; user u8 0..255) * sys/dev/raid/amr/amr.c:833 (_ap->ap_cdb_length = len;) * sys/dev/raid/amr/amr.c:834 (bcopy(au_cmd+3, _ap->ap_cdb, len);) * struct: sys/dev/raid/amr/amrreg.h:489 (ap_cdb[AMR_MAX_CDB_LEN=10]) * sys/dev/raid/amr/amrreg.h:90 (AMR_MAX_CDB_LEN 0x0a) * sys/dev/raid/amr/amrvar.h:110 (union amr_ccb { ...; uint8_t bytes[128]; }) * perms: sys/dev/raid/amr/amr.c:261 (make_dev UID_ROOT GID_OPERATOR S_IRUSR|S_IWUSR) * * Build: cc -O2 -o harness harness.c * Run: ./harness */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <stddef.h> #include <string.h> #define AMR_MAX_CDB_LEN 10 /* amrreg.h:90 0x0a */ #define AMR_MAX_REQ_SENSE 32 /* amrreg.h:92 0x20 */ /* Faithful copy of struct amr_passthrough (amrreg.h:478), __packed. */ struct amr_passthrough { uint8_t ap_timeout_etc; /* bitfields: timeout:3,ars:1,dummy:3,islogical:1 */ uint8_t ap_logical_drive_no; uint8_t ap_channel; uint8_t ap_scsi_id; uint8_t ap_queue_tag; uint8_t ap_queue_action; uint8_t ap_cdb[AMR_MAX_CDB_LEN]; /* offset 6, 10 bytes */ uint8_t ap_cdb_length; uint8_t ap_request_sense_length; uint8_t ap_request_sense_area[AMR_MAX_REQ_SENSE]; uint8_t ap_no_sg_elements; uint8_t ap_scsi_status; uint8_t pad[2]; /* __packed keeps u32 aligned naturally */ uint32_t ap_data_transfer_address; uint32_t ap_data_transfer_length; } __attribute__((packed)); /* Faithful copy of union amr_ccb (amrvar.h:110): 128-byte DMA-coherent alloc. */ #define AMR_CCB_SIZE 128 union amr_ccb { struct amr_passthrough ccb_pthru; uint8_t bytes[AMR_CCB_SIZE]; }; int main(void) { /* In the kernel, ac->ac_ccb points at one 128-byte DMA-coherent ccb out * of a cluster of such ccbs. Overflowing ap_cdb corrupts the fields after * it (sense area, data transfer addr/len) and, for len large enough, runs * past the 128-byte ccb into the adjacent ccb / DMA memory. We model that * with two adjacent ccbs + a guard region of canaries. */ uint8_t *region = calloc(1, AMR_CCB_SIZE * 2 + 64); /* ccb0 | ccb1 | guard */ if (!region) { perror("calloc"); return 1; } union amr_ccb *ccb0 = (union amr_ccb *)(region); union amr_ccb *ccb1 = (union amr_ccb *)(region + AMR_CCB_SIZE); uint8_t *guard = region + AMR_CCB_SIZE * 2; memset(guard, 0xAA, 64); /* attacker au_cmd buffer: byte[2] = len (the CDB length), bytes[3..] = CDB * payload. amr.c:832 len = au_cmd[2]; amr.c:834 bcopy(au_cmd+3, ap_cdb, len) */ uint8_t au_cmd[256 + 8]; memset(au_cmd, 0x41, sizeof(au_cmd)); /* attacker-controlled content */ printf("[DF-1356] union amr_ccb = %d bytes; ap_cdb at offset %zu, len=%d\n", AMR_CCB_SIZE, offsetof(struct amr_passthrough, ap_cdb), AMR_MAX_CDB_LEN); printf("[DF-1356] ap_passthrough packed size = %zu bytes\n", sizeof(struct amr_passthrough)); /* ===== Trigger the bug exactly as amr.c:825-834 ===== */ struct amr_passthrough *_ap = &ccb0->ccb_pthru; bzero(_ap, sizeof(*_ap)); int len = au_cmd[2] = 255; /* user u8, far beyond AMR_MAX_CDB_LEN */ _ap->ap_cdb_length = len; bcopy(au_cmd + 3, _ap->ap_cdb, len); /* amr.c:834 — the overflow */ printf("[DF-1356] attacker len=%u (au_cmd[2]); AMR_MAX_CDB_LEN=%d -> " "%d bytes overflow past ap_cdb\n", len, AMR_MAX_CDB_LEN, len - AMR_MAX_CDB_LEN); /* What got corrupted inside ccb0 (past ap_cdb)? */ printf("[DF-1356] ccb0.ap_request_sense_length now = 0x%02x (attacker byte)\n", _ap->ap_request_sense_length); printf("[DF-1356] ccb0.ap_data_transfer_address now = 0x%08x " "(DMA ptr corrupted)\n", _ap->ap_data_transfer_address); printf("[DF-1356] ccb0.ap_data_transfer_length now = 0x%08x\n", _ap->ap_data_transfer_length); /* Did the 255-byte bcopy run past the 60-byte passthrough struct / the * 128-byte ccb union into ccb1 and the guard? */ unsigned overflow_past_union = 0; for (unsigned o = sizeof(struct amr_passthrough); o < AMR_CCB_SIZE; o++) if (ccb0->bytes[o] == 0x41) overflow_past_union++; unsigned ccb1_hits = 0, guard_hits = 0; for (unsigned o = 0; o < AMR_CCB_SIZE; o++) if (ccb1->bytes[o] == 0x41) ccb1_hits++; for (unsigned o = 0; o < 64; o++) if (guard[o] != 0xAA) guard_hits++; printf("[DF-1356] bytes written past ap_passthrough but inside ccb0: %u\n", overflow_past_union); printf("[DF-1356] ADJACENT ccb1 corrupted: %s (%u attacker bytes landed in it)\n", ccb1_hits ? "YES -> next DMA ccb overwritten" : "no", ccb1_hits); printf("[DF-1356] guard region past ccb1 corrupted: %s (%u bytes)\n", guard_hits ? "YES" : "no", guard_hits); /* ===== Show the fix ===== */ printf("[DF-1356] FIX: with 'if (len > AMR_MAX_CDB_LEN) len = AMR_MAX_CDB_LEN;' " "before the bcopy, write stays inside ap_cdb[10] -> no overflow\n"); int bad = (overflow_past_union > 0 || ccb1_hits > 0 || guard_hits > 0); free(region); return bad ? 0 : 2; /* exit 0 == primitive demonstrated */ } |