DF-1606 / df1606_poc.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 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | /* * DF-1606 - acpi_hp cmi_order[128] heap overflow PoC (userspace harness). * * Bug: sys/dev/acpica/acpi_hp/acpi_hp.c:1140-1205 (acpi_hp_hpcmi_read) * * /dev/hpcmi is created with mode 0644 (world-readable) on HP systems that * expose the CMI WMI GUID. Any unprivileged user can open(2) it and read(2) * to trigger the first-time CMI enumeration loop: * * 1164: if (sc->cmi_order_size < 0) { * 1165: maxInstance = sc->has_cmi; <-- WMI instance count * ... * 1171: sc->cmi_order_size = 0; * 1172: for (instance = 0; instance < maxInstance; ++instance) { * 1174: if (acpi_hp_get_cmi_block(...)) { instance = maxInstance; } * 1180: else { * 1181: pos = sc->cmi_order_size; * 1182: for (i=0; i<sc->cmi_order_size && i<127; ++i) <-- bounded * ... * 1190: for (i=sc->cmi_order_size; i>pos; --i) { <-- NOT bounded * 1191: sc->cmi_order[i].sequence = sc->cmi_order[i-1].sequence; * 1192: sc->cmi_order[i].instance = sc->cmi_order[i-1].instance; * 1193: } * 1198: sc->cmi_order[pos].sequence = sequence; * 1200: sc->cmi_order[pos].instance = instance; * 1202: sc->cmi_order_size++; * 1203: } * 1204: } * * sc->cmi_order is declared at softc line 147 as: * * 147: struct acpi_hp_inst_seq_pair cmi_order[128]; <-- LAST field of softc * * There is NO check that cmi_order_size < 128 before writing. On the 129th * successful insertion the shift loop (or the final write at line 1198) * writes 8 bytes past cmi_order[127] - i.e. into the slab slot adjacent to * struct acpi_hp_softc. Each additional successful insertion adds 8 more * bytes of OOB write. The 8-byte payload is {uint32 sequence, uint8 instance} * where the values come from the WMI/BIOS CMI block (so the bytes are * BIOS-shaped, not attacker-shaped, but the WRITE LOCATION and TIMING are * 100% attacker-controlled). * * Live trigger requires HP ACPI/WMI hardware that exposes >128 CMI instances * (the finding cites EliteBook 840 G5/G8). This DragonFly QEMU guest has no * HP WMI hardware at all, so the acpi_hp driver does not attach and the * kernel code path is unreachable here. The harness below reproduces the * exact loop logic with a heap-allocated fake softc (matching the kernel's * kmalloc'd softc) whose cmi_order is the last field and is followed by a * canary region representing the adjacent kmalloc slab slot. * * Build: cc -O2 -o df1606_poc df1606_poc.c * Run: ./df1606_poc ; exits 0 if OOB write detected */ #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #define CMI_ORDER_N 128 #define CMI_INSTANCES 140 /* > 128 - exercises the bug (12-entry OOB) */ #define CANARY_BYTES 1024 /* generous - represents adjacent slab slot */ struct acpi_hp_inst_seq_pair { uint32_t sequence; uint8_t instance; uint8_t _pad[3]; }; /* * Mimic the tail of struct acpi_hp_softc. cmi_order is the LAST real field; * in the kernel, the softc is kmalloc'd and the bytes just past cmi_order[] * belong to whatever sits in the next slab slot. We replicate that layout by * malloc'ing (struct + canary) together and treating the trailing canary as * the adjacent slab slot. */ struct fake_softc { unsigned char header[64]; int cmi_order_size; struct acpi_hp_inst_seq_pair cmi_order[CMI_ORDER_N]; /* canary region immediately follows the struct in the heap block */ }; static int fake_wmi_seq_counter = 1; /* Mimic acpi_hp_get_cmi_block returning success and filling *sequence. */ static int fake_get_cmi_block(uint32_t *sequence) { *sequence = fake_wmi_seq_counter++; /* vary so linear search short-circuits */ return 0; } int main(void) { unsigned char *block; struct fake_softc *sc; unsigned char *NEXT_SLOT_CANARY; uint32_t sequence; uint8_t instance; int maxInstance = CMI_INSTANCES; int pos, i, corrupted, first_off; block = calloc(1, sizeof(struct fake_softc) + CANARY_BYTES); if (block == NULL) { fprintf(stderr, "calloc failed\n"); return 2; } sc = (struct fake_softc *)block; NEXT_SLOT_CANARY = block + sizeof(struct fake_softc); memset(NEXT_SLOT_CANARY, 0xAA, CANARY_BYTES); /* replicate acpi_hp_hpcmi_read() lines 1171-1204 exactly */ sc->cmi_order_size = 0; for (instance = 0; instance < maxInstance; ++instance) { if (fake_get_cmi_block(&sequence)) { instance = maxInstance; } else { pos = sc->cmi_order_size; for (i = 0; i < sc->cmi_order_size && i < 127; ++i) { if (sc->cmi_order[i].sequence > sequence) { pos = i; break; } } /* shift loop has NO bound on cmi_order_size */ for (i = sc->cmi_order_size; i > pos; --i) { sc->cmi_order[i].sequence = sc->cmi_order[i-1].sequence; sc->cmi_order[i].instance = sc->cmi_order[i-1].instance; } sc->cmi_order[pos].sequence = sequence; sc->cmi_order[pos].instance = instance; sc->cmi_order_size++; } } corrupted = 0; first_off = -1; for (i = 0; i < CANARY_BYTES; ++i) { if (NEXT_SLOT_CANARY[i] != 0xAA) { if (first_off < 0) first_off = i; corrupted++; } } printf("maxInstance = %d\n", maxInstance); printf("cmi_order array bound = %d entries (%d bytes)\n", CMI_ORDER_N, (int)(CMI_ORDER_N * sizeof(struct acpi_hp_inst_seq_pair))); printf("cmi_order_size after = %d entries (overflow by %d entries)\n", sc->cmi_order_size, sc->cmi_order_size - CMI_ORDER_N); printf("adjacent-slab canary corrupted bytes = %d (first at +%d)\n", corrupted, first_off); if (corrupted > 0) { printf("[OK] heap OOB write past cmi_order[127] confirmed: %d bytes\n", corrupted); printf(" kernel path: acpi_hp_hpcmi_read lines 1190-1202\n"); printf(" in-kernel effect: corruption of adjacent kmalloc-2048 slab\n"); printf(" slot -> INVARIANTS panic on default GENERIC; silent heap\n"); printf(" corruption on noinv.\n"); free(block); return 0; } printf("[FAIL] no OOB write detected.\n"); free(block); return 1; } |