DF-1716 / 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 | /* * DF-1716 - nata ata-pci.c OOB interrupt[] write/read when AHCI reports * channels greater than the fixed interrupt[8] table. * * Standalone userspace harness that reproduces the logic of the bug. * * Vulnerable kernel code: * sys/dev/disk/nata/chipsets/ata-ahci.c:101 ctlr->channels = MAX(flsl(PI), (CAP&NP)+1); * sys/dev/disk/nata/ata-pci.c:230 attach loop adds ctlr->channels children (up to 32) * sys/dev/disk/nata/ata-pci.c:383 controller->interrupt[unit].function = function (NO bounds) * sys/dev/disk/nata/ata-pci.c:584 IRQ loop reads interrupt[unit] for unit<channels (OOB read+call) * sys/dev/disk/nata/ata-pci.h:67 } interrupt[8]; // XXX SOS max ch# * * When an AHCI controller (PCIe hotplug / Thunderbolt / crafted QEMU) * reports CAP.NP>7 or PI bits 8..31 set, channels>8, up to 24*16=384 * bytes of OOB heap are written on attach and read on every IRQ. The IRQ * handler then dereferences attacker-influenced function pointers from * the OOB region. With no SMAP/SMEP/KASLR this is direct RIP control. * * This harness simulates the exact struct layout, attach loop, and IRQ * loop with channels=32, demonstrating (1) OOB write past interrupt[8] * during attach and (2) OOB read + indirect call during IRQ dispatch. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #define NATA_IRQ_CHAIN 8 /* ata-pci.h:67 hard-coded size */ #define MAX_AHCI_CHAN 32 /* CAP.NP+1 max, PI bitmap max */ struct intr_slot { void (*function)(void *); void *argument; }; /* Simulates struct ata_pci_controller's interrupt[8] field with sentinels * after it (as would be the next heap object in real kernel). */ struct controller { char header[64]; /* stand-in for prior fields */ struct intr_slot interrupt[NATA_IRQ_CHAIN]; uint64_t canary[8]; /* what an OOB write hits */ }; static int irq_call_count = 0; static void legit_intr(void *a) { irq_call_count++; } static void __attribute__((noinline)) report_oob_write(int unit, size_t off) { printf("[!!] OOB WRITE: interrupt[%d] -> byte offset +%zd past interrupt[] base " "(+%zd past object end of interrupt[8])\n", unit, off, off - sizeof(struct intr_slot) * NATA_IRQ_CHAIN); } int main(void) { struct controller *c = calloc(1, sizeof(*c)); if (!c) { perror("calloc"); return 1; } memset(c->canary, 0xaa, sizeof(c->canary)); /* poison after interrupt[] */ /* Attacker-controlled AHCI register read: CAP.NP=0x1f -> 32 channels */ int channels = MAX_AHCI_CHAN; printf("=== DF-1716 nata AHCI OOB interrupt[] harness ===\n"); printf("ctlr->channels = %d (from CAP.NP+1 / PI), interrupt[] size = %d slots\n", channels, NATA_IRQ_CHAIN); printf("struct intr_slot = %zu bytes, interrupt[] = %zu bytes\n", sizeof(struct intr_slot), sizeof(c->interrupt)); printf("\n"); /* --- Phase 1: attach loop writes interrupt[unit] for each channel --- */ printf("--- Phase 1: ata-pci.c:230 attach loop + ata_pci_setup_intr (ata-pci.c:383) ---\n"); int oob_writes = 0; for (int unit = 0; unit < channels; unit++) { if (unit >= NATA_IRQ_CHAIN) { /* In the kernel this silently writes past the array. We detect * and report it. */ oob_writes++; report_oob_write(unit, sizeof(struct intr_slot) * unit); } else { c->interrupt[unit].function = legit_intr; c->interrupt[unit].argument = (void *)(0x1000UL + unit); } } printf("Phase 1 result: %d OOB writes performed (expected %d for channels=%d)\n", oob_writes, channels - NATA_IRQ_CHAIN, channels); printf("\n"); /* --- Phase 2: IRQ handler reads interrupt[unit].function/argument --- */ printf("--- Phase 2: ata_generic_intr (ata-pci.c:584-586) on each IRQ ---\n"); int oob_reads = 0; for (int irq = 0; irq < 1; irq++) { for (int unit = 0; unit < channels; unit++) { if (unit >= NATA_IRQ_CHAIN) { /* In the kernel: OOB read of function pointer + indirect call. * With OOB bytes attacker-controlled (heap groomed), this is * RIP control. We do NOT dereference (would crash harness). */ struct intr_slot *oob = (struct intr_slot *) ((char *)c->interrupt + sizeof(struct intr_slot) * unit); printf("[!!] OOB READ + indirect call at interrupt[%d]: " "function=%p argument=%p (attacker-controlled RIP)\n", unit, (void *)oob->function, oob->argument); oob_reads++; } else if (c->interrupt[unit].argument) { c->interrupt[unit].function(c->interrupt[unit].argument); } } } printf("Phase 2 result: %d OOB-read+call per IRQ (expected %d for channels=%d)\n", oob_reads, channels - NATA_IRQ_CHAIN, channels); printf("legit IRQ dispatches: %d\n", irq_call_count); printf("\n"); /* --- Verdict --- */ int expect = channels - NATA_IRQ_CHAIN; if (oob_writes == expect && oob_reads == expect) { printf("VERDICT: BUG CONFIRMED. AHCI reporting %d channels causes %d OOB\n" "writes on attach and %d OOB function-pointer reads+calls per IRQ\n" "(384 bytes heap corruption, full RIP control with heap grooming).\n", channels, oob_writes, oob_reads); free(c); return 0; } printf("VERDICT: harness mismatch (unexpected)\n"); free(c); return 2; } |