DF-1020 / harness.c
/* * DF-1020 harness โ fw_bus_explore_callback unconditional OOB csrrom write * * BUG: fw_bus_explore_callback() at sys/bus/firewire/firewire.c:1500 writes * fc->ongodev->csrrom[(fc->ongoaddr - CSRROMOFF)/4] = ntohl(rfp->mode.rresq.data); * UNCONDITIONALLY before the bounds check at :1558: * if((fc->ongoaddr - CSRROMOFF) > CSRROMSIZE) goto nextnode; * * At :1520, fc->ongoaddr += csrreg->val * 4, where csrreg->val is a 24-bit * field set from device data (via the write at :1500). ongoaddr is a 16-bit * bitfield (firewirereg.h:107-109), so csrreg->val * 4 can wrap it. After * wrapping, the next response write at :1500 indexes csrrom[] out of bounds. * * csrrom is u_int32_t[256] (CSRROMSIZE/4 = 0x400/4 = 256) in struct fw_device * (firewirereg.h:54). Writes past index 255 corrupt: * - fw_device.rcnt, fw_device.fc (kernel pointer!), fw_device.status, * fw_device.link (STAILQ next pointer) * - Adjacent heap objects * * A malicious FireWire device replies to bus exploration RREQQ reads at * offset 0x418+ with key=0x81 (CROM_UDIR) and a crafted val to wrap * ongoaddr, then sends data to be written at the wrapped (OOB) address. * * This harness demonstrates the wrapping arithmetic. Runtime trigger * requires FireWire hardware โ this QEMU guest has none. * * Build: cc -o harness harness.c * Run: ./harness */ #include <stdio.h> #include <stdint.h> #define CSRROMOFF 0x400 #define CSRROMSIZE 0x400 int main(void) { /* Simulate ongoaddr as a 16-bit bitfield (firewirereg.h:109) */ uint32_t ongoaddr = CSRROMOFF; /* starts at Config ROM base */ uint32_t csrrom_entries = CSRROMSIZE / 4; /* 256 u_int32_t */ printf("DF-1020: fw_bus_explore_callback OOB csrrom write\n"); printf(" CSRROMOFF: 0x%X\n", CSRROMOFF); printf(" CSRROMSIZE: 0x%X\n", CSRROMSIZE); printf(" csrrom[]: %u entries (u_int32_t)\n", csrrom_entries); printf(" ongoaddr: 16-bit bitfield (firewirereg.h:109)\n\n"); /* Simulate normal exploration: read 4 entries (16 bytes of Config ROM) */ printf(" Normal exploration (first 4 entries):\n"); for (int i = 0; i < 4; i++) { int idx = (ongoaddr - CSRROMOFF) / 4; printf(" csrrom[%d] at ongoaddr=0x%X โ OK\n", idx, ongoaddr); ongoaddr += 4; } /* Now simulate a malicious device sending csrreg with key=0x81, val crafted */ printf("\n Malicious device sends key=0x81 (CROM_UDIR), val=0x3FFF:\n"); /* csrreg is { val:24, key:8 } on LE, so the u_int32_t = val | (key<<24) */ uint32_t device_data = 0x3FFF; /* val=0x3FFF, key=0x00 initially */ /* On the wire after ntohl: key would be in high byte */ /* But we read it from csrrom which was set from ntohl(rfp->mode.rresq.data) */ uint32_t csrreg_val = 0x3FFF; /* 24-bit val */ printf(" csrreg->val = 0x%X (24-bit, device-controlled)\n", csrreg_val); /* ongoaddr += csrreg->val * 4 โ with 16-bit truncation */ uint32_t new_ongoaddr = (ongoaddr + csrreg_val * 4) & 0xFFFF; printf(" ongoaddr += val*4 = 0x%X + 0x%X = 0x%X\n", ongoaddr, csrreg_val * 4, ongoaddr + csrreg_val * 4); printf(" After 16-bit truncation: ongoaddr = 0x%X\n", new_ongoaddr); ongoaddr = new_ongoaddr; /* Next write at line 1500 uses this wrapped ongoaddr */ if (ongoaddr >= CSRROMOFF) { int idx = (ongoaddr - CSRROMOFF) / 4; if (idx >= (int)csrrom_entries) { printf("\n *** OOB WRITE: csrrom[%d] (max valid index: %d) ***\n", idx, csrrom_entries - 1); printf(" Overflows past csrrom[256] into rcnt, fc (kernel ptr!),\n"); printf(" status, link โ adjacent fw_device / heap.\n"); } } else { /* ongoaddr < CSRROMOFF: (ongoaddr - CSRROMOFF) is a huge u_int32_t */ uint32_t raw_idx = (ongoaddr - CSRROMOFF) / 4; printf("\n *** OOB WRITE: ongoaddr=0x%X < CSRROMOFF=0x%X ***\n", ongoaddr, CSRROMOFF); printf(" (ongoaddr - CSRROMOFF) = 0x%X (unsigned underflow)\n", ongoaddr - CSRROMOFF); printf(" index = 0x%X = %u โ massive OOB write into kernel heap\n", raw_idx, raw_idx); } printf("\n Bounds check at :1558 is AFTER the write at :1500 โ too late.\n"); printf(" No authentication needed: malicious FireWire device on the bus.\n"); return 0; } |