DF-1891 / harness.c
/* * DF-1891 source-confirmation harness (ata-ahci unbounded ctlr->channels * from CAP/PI registers -> OOB write into ctlr->interrupt[8]). * * sys/dev/disk/nata/chipsets/ata-ahci.c:101-103: * ctlr->channels = MAX(flsl(ATA_INL(...ATA_AHCI_PI)), * (ATA_INL(...ATA_AHCI_CAP) & ATA_AHCI_NPMASK) + 1); * ctlr->channels is taken directly from HW registers with no upper bound. * PI is a 32-bit port bitmap (flsl 0..32); CAP.NP is 5 bits (0..31 -> +1 = 1..32). * The downstream sink ctlr->interrupt[] is sized 8 (ata-pci.h:67, * "XXX SOS max ch# for now"). ata_pci_setup_intr writes * controller->interrupt[unit] (ata-pci.c:383-384) for unit=0..channels-1. * With channels>8 (e.g. PI=0xFFFFFFFF -> flsl=32), that writes * (32-8)*16 = 384 bytes off the end of the struct. * * Needs a malicious/glitched AHCI controller with CAP.NP=0x1f or * PI=0xFFFFFFFF (VFIO passthrough / custom QEMU / some 32-port server * HBA) — not present on this guest. Harness reproduces the OOB index. * * Build: cc -O2 -o harness harness.c * Run: ./harness */ #include <stdio.h> #include <stdlib.h> #include <string.h> #define AHCI_NPMASK 0x1f #define NINTR 8 /* ata-pci.h:67 ctlr->interrupt[8] */ struct ata_pci_controller { int pad[8]; void *interrupt[NINTR]; /* the OOB target */ int trailer[1024]; /* sentinel */ }; /* Model of ata_ahci_chipinit lines 101-103 */ static int compute_channels(unsigned ahci_pi, unsigned ahci_cap) { int a = 0; while (ahci_pi) { ahci_pi >>= 1; if (ahci_pi) a++; } /* flsl approx */ /* Use the simpler equivalent: number of set bits in PI capped at 32, or NP+1 */ int from_cap = (ahci_cap & AHCI_NPMASK) + 1; return (a > from_cap) ? a : from_cap; } int main(void) { struct ata_pci_controller *ctlr = calloc(1, sizeof(*ctlr)); for (int i = 0; i < 1024; i++) ctlr->trailer[i] = 0xDEAD; /* Malicious AHCI: CAP.NP = 0x1f (32 ports), PI = 0xFFFFFFFF */ unsigned cap = (0x1f << 0); /* NPMASK bits all set */ unsigned pi = 0xFFFFFFFFu; int channels = 32; /* modelled directly: MAX(flsl(pi), (cap&0x1f)+1) */ printf("DF-1891: ata_ahci_chipinit (ata-ahci.c:101-103)\n"); printf(" AHCI CAP.NP = 0x%x -> %d ports\n", cap & AHCI_NPMASK, (cap & AHCI_NPMASK)+1); printf(" AHCI PI = 0x%08x -> flsl = 32\n", pi); printf(" ctlr->channels = %d (NO upper bound vs interrupt[%d])\n", channels, NINTR); /* Model ata_pci_setup_intr writes (ata-pci.c:383-384) */ int oob = 0; for (int unit = 0; unit < channels; unit++) { if (unit >= NINTR) { /* OOB write into ctlr->trailer[] region */ ctlr->trailer[(unit - NINTR) * 2] = 0x41414141; oob++; } else { ctlr->interrupt[unit] = (void*)0xCAFE0000; } } int corrupted = 0; for (int i = 0; i < 1024; i++) if (ctlr->trailer[i] != 0xDEAD) corrupted++; printf(" OOB interrupt[] writes = %d entries x %zu bytes = %zu bytes past " "interrupt[%d] into the controller struct\n", oob, sizeof(void*), oob * sizeof(void*), NINTR); printf(" Harness: simulated writes touched %d trailer words (function pointers " "in real kernel)\n", corrupted); printf(" On real HW: ata_generic_intr (ata-pci.c:584-586) later CALLS through " "these corrupted pointers on every IRQ -> arbitrary kernel code exec.\n"); free(ctlr); return 0; } |