DF-1125 / 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 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | /* * DF-1125 harness: isp.c 24XX FCP response/sense OOB read (source-trace proof) * * Replicates the EXACT kernel data-flow of isp_handle_intr_response() for the * 24XX (QLogic FC) RQSTYPE_RESPONSE path: * * sys/dev/disk/isp/isp.c:5042 uint8_t qe[QENTRY_LEN]; // QENTRY_LEN = 64 * sys/dev/disk/isp/isp.c:5225 rlen = sp->req_response_len; // unvalidated, FW-controlled * sys/dev/disk/isp/isp.c:5238 snsp = sp->req_rsp_sense; // &qe[36] * sys/dev/disk/isp/isp.c:5239 snsp += rlen; // 36 + rlen -> OOB if rlen>=28 * sys/dev/disk/isp/isp.c:5240 slen = sp->req_sense_len; // unvalidated, FW-controlled * sys/dev/disk/isp/isp.c:5307 if (snsp && slen) XS_SAVE_SENSE(xs, snsp, slen); * -> isp_freebsd.h:483 memcpy(&xs->sense_data, snsp, imin(XS_SNSLEN, slen)); * * isp24xx_statusreq_t (ispmbox.h:654-668) is exactly 64 bytes; req_rsp_sense[28] * is the final field at offset 36. A malicious remote FC target that sends an * FCP_RSP IU with FCP_RSP_LEN (req_response_len) >= 28 drives `snsp` past the * 64-byte stack buffer; XS_SAVE_SENSE then memcpy()s up to 32 bytes from the * wild pointer into xs->sense_data -> kernel stack info leak (small rlen) or * panic from an unmapped page (large rlen). * * This is a userspace replica of that arithmetic, proving the OOB read * concretely. The kernel path itself requires a QLogic 24XX FC HBA + a remote * FC target, neither of which is present on the audit QEMU guest (virtio only). */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #define QENTRY_LEN 64 #define REQ_RSP_SENSE_LEN 28 #define REQ_RSP_SENSE_OFF 36 /* 64 - 28 */ #define XS_SNSLEN 32 /* SSD_SENSELEN / sizeof sense_data */ typedef struct { uint8_t rqs_entry_type; uint8_t rqs_entry_count; uint8_t rqs_seqno; uint8_t rqs_flags; } isphdr_t; typedef struct { isphdr_t req_header; uint32_t req_handle; uint16_t req_completion_status; uint16_t req_oxid; uint32_t req_resid; uint16_t req_reserved0; uint16_t req_state_flags; uint16_t req_reserved1; uint16_t req_scsi_status; uint32_t req_fcp_residual; uint32_t req_sense_len; uint32_t req_response_len; uint8_t req_rsp_sense[REQ_RSP_SENSE_LEN]; } isp24xx_statusreq_t; /* sizeof verification: must be exactly QENTRY_LEN, matching the kernel layout. */ struct { char z[QENTRY_LEN]; } qe_sizeof_check; struct { char z[sizeof(isp24xx_statusreq_t)]; } stat_sizeof_check; /* compile-time assertion that the struct fills the queue entry exactly */ typedef int assert_req_rsp_sense_is_last_field[ REQ_RSP_SENSE_OFF + REQ_RSP_SENSE_LEN == QENTRY_LEN ? 1 : -1]; static size_t imin(size_t a, size_t b) { return a < b ? a : b; } int main(void) { printf("sizeof(isp24xx_statusreq_t) = %zu (QENTRY_LEN=%d)\n", sizeof(isp24xx_statusreq_t), QENTRY_LEN); printf("req_rsp_sense at offset %d, length %d\n", REQ_RSP_SENSE_OFF, REQ_RSP_SENSE_LEN); int oob_cases = 0; /* Try several firmware-controlled rlen values a malicious FC target could * place in the FCP_RSP IU. Each one the kernel accepts unvalidated. */ uint32_t rlens[] = { 0, 4, 27, 28, 32, 64, 0x1000 }; for (size_t c = 0; c < sizeof(rlens)/sizeof(rlens[0]); c++) { uint32_t rlen = rlens[c]; /* qe lives on the kernel stack (isp.c:5042). Model it as a heap block * with a guard region immediately after, so we can detect the OOB. */ uint8_t *qe = calloc(1, QENTRY_LEN); isp24xx_statusreq_t *sp = (isp24xx_statusreq_t *)qe; sp->req_response_len = rlen; sp->req_sense_len = XS_SNSLEN; /* attacker asks for full 32-byte sense */ /* ---- exact kernel arithmetic (isp.c:5238-5240) ---- */ uint8_t *snsp = sp->req_rsp_sense; /* &qe[36] */ snsp += rlen; /* 36 + rlen */ uint32_t slen = sp->req_sense_len; size_t read_off = (size_t)(snsp - qe); size_t copy_n = imin(XS_SNSLEN, slen); size_t read_end = read_off + copy_n; int oob = (read_end > QENTRY_LEN); printf("rlen=%-6u -> snsp offset=%-5zu memcpy=%-2zu bytes read range=[%zu,%zu) %s\n", rlen, read_off, copy_n, read_off, read_end, oob ? ">>> OOB READ PAST qe[64]" : "(in bounds)"); if (oob) oob_cases++; /* For the in-bounds-leak boundary case, actually perform the copy into * a sense buffer to show that attacker-controlled rlen>=28 reads fully * past the stack buffer (the bytes would be adjacent kernel stack). */ if (rlen == 28) { uint8_t sense[XS_SNSLEN] = {0}; /* emulate memcpy(&xs->sense_data, snsp, imin(XS_SNSLEN,slen)). * snsp here points at qe+64; copying 32 bytes is entirely OOB. We * guard the source by mapping a canary right after qe. */ uint8_t *region = calloc(1, QENTRY_LEN + XS_SNSLEN); memset(region + QENTRY_LEN, 0xA1, XS_SNSLEN); /* "adjacent stack" */ uint8_t *snsp_model = region + REQ_RSP_SENSE_OFF + rlen; /* == region+64 */ memcpy(sense, snsp_model, imin(XS_SNSLEN, slen)); printf(" leak demo: sense bytes copied from past qe[64] = "); for (size_t i = 0; i < XS_SNSLEN; i++) printf("%02x", sense[i]); printf(" (all 0xA1 == adjacent-stack marker)\n"); free(region); } free(qe); } printf("\nresult: %d/%zu firmware-controlled rlen values read past the 64-byte stack entry\n", oob_cases, sizeof(rlens)/sizeof(rlens[0])); if (oob_cases > 0) { printf("DF-1125: CONFIRMED OOB read primitive (kernel stack info leak / panic)\n"); } else { printf("DF-1125: NOT reproduced\n"); } /* ---- WITH FIX (fix.diff clamps): rlen<=28, slen<=28-rlen -> 0 OOB ---- */ printf("\n--- WITH FIX (clamp rlen to sizeof(req_rsp_sense), slen to 28-rlen) ---\n"); int fix_oob = 0; for (size_t c = 0; c < sizeof(rlens)/sizeof(rlens[0]); c++) { uint32_t rlen = rlens[c]; uint32_t f_rlen = rlen > REQ_RSP_SENSE_LEN ? REQ_RSP_SENSE_LEN : rlen; uint32_t f_slen = XS_SNSLEN; f_slen = f_slen > (REQ_RSP_SENSE_LEN - f_rlen) ? (REQ_RSP_SENSE_LEN - f_rlen) : f_slen; size_t read_off = REQ_RSP_SENSE_OFF + f_rlen; size_t read_end = read_off + imin(XS_SNSLEN, f_slen); int oob = (read_end > QENTRY_LEN); if (oob) fix_oob++; printf("rlen=%-6u -> clamped rlen=%-3u slen=%-3u read range=[%zu,%zu) %s\n", rlen, f_rlen, f_slen, read_off, read_end, oob ? ">>> STILL OOB" : "(in bounds)"); } printf("FIX result: %d OOB cases remain (expect 0)\n", fix_oob); printf("DF-1125 FIX: %s\n", fix_oob == 0 ? "VALIDATED - clamps eliminate all OOB reads" : "INCOMPLETE"); return (oob_cases > 0) ? 0 : 1; } |