⬢ DragonFlyBSD Kernel Audit
DF-5464 / icu_msiabi_probe.c
← back to finding ↓ download raw
/*
 * DF-5464 evidence (safe phase): drive the ICU MSI/MSI-X ABI entry points
 * directly as a driver (KLD) would.
 *
 * Safe phase (no crash expected on a production kernel):
 *  - msix_alloc/msi_map/msix_release round trip (count=1, legal)
 *  - msi_alloc(zone, 64, 0): 'zone' is a 128-int buffer; only zone[0..31]
 *    are inside the ABI's documented count<=32 contract (icu_abi.c:460).
 *    If words zone[32..63] come back modified, icu_abi_msi_alloc_intern()
 *    wrote past the caller's 32-int array (icu_abi.c:498 'intrs[j] = intr';
 *    the reference caller's array is the on-stack 'int irqs[32]' at
 *    sys/bus/pci/pci.c:1952/2072).
 *
 * On an INVARIANTS kernel this instead panics at the KASSERT
 * ("invalid count 64", icu_abi.c:460), proving the KASSERT is the only
 * guard.  Stock GENERIC ships WITHOUT INVARIANTS (sys/config/GENERIC),
 * as does the audit guest's X86_64_NOINV baseline kernel.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/machintr.h>

extern struct machintr_abi MachIntrABI_ICU;

#define CANARY	0xAAAAAAA

static int zone[128];

static void
probe_msix(void)
{
	uint64_t addr;
	uint32_t data;
	int irq = -1, rc;

	rc = MachIntrABI_ICU.msix_alloc(&irq, 0);
	kprintf("icu_msiabi_probe: msix_alloc(cpu0) rc=%d irq=%d\n", rc, irq);
	if (rc == 0) {
		MachIntrABI_ICU.msi_map(irq, &addr, &data, 0);
		kprintf("icu_msiabi_probe: msi_map addr=0x%llx data=0x%x\n",
		    (unsigned long long)addr, data);
		MachIntrABI_ICU.msix_release(irq, 0);
		kprintf("icu_msiabi_probe: msix_release done\n");
	}
}

static void
probe_count64(void)
{
	int i, hit, rc;

	for (i = 0; i < 128; ++i)
		zone[i] = CANARY;

	kprintf("icu_msiabi_probe: calling ICU msi_alloc(count=64, cpu0) "
	    "(contract max is 32)\n");
	rc = MachIntrABI_ICU.msi_alloc(zone, 64, 0);
	kprintf("icu_msiabi_probe: msi_alloc rc=%d\n", rc);
	if (rc != 0) {
		kprintf("icu_msiabi_probe: alloc failed, no canary test\n");
		return;
	}
	kprintf("icu_msiabi_probe: zone[0..7] =");
	for (i = 0; i < 8; ++i)
		kprintf(" %d", zone[i]);
	kprintf(" (allocated block)\n");

	hit = 0;
	for (i = 32; i < 128; ++i) {
		if (zone[i] != CANARY)
			++hit;
	}
	kprintf("icu_msiabi_probe: %d/96 canary words past zone[31] "
	    "overwritten (32 expected for a 64-count alloc)\n", hit);
	for (i = 32; i < 64; ++i) {
		if (zone[i] != CANARY) {
			kprintf("icu_msiabi_probe:   zone[%d]=%d  <-- OOB "
			    "write past 32-int contract\n", i, zone[i]);
			break;
		}
	}

	/*
	 * Release the full 64-wide view so the maps are restored
	 * (release_intern also reads intrs[0..63], icu_abi.c:533).
	 */
	MachIntrABI_ICU.msi_release(zone, 64, 0);
	kprintf("icu_msiabi_probe: msi_release(64) done\n");
}

static int
icu_msiabi_modevent(module_t mod __unused, int type,
    void *data __unused)
{
	switch (type) {
	case MOD_LOAD:
		kprintf("icu_msiabi_probe: loaded\n");
		probe_msix();
		probe_count64();
		kprintf("icu_msiabi_probe: SAFE PHASE COMPLETE\n");
		return 0;
	case MOD_UNLOAD:
		return 0;
	default:
		return EOPNOTSUPP;
	}
}

static moduledata_t icu_msiabi_probe_mod = {
	"icu_msiabi_probe", icu_msiabi_modevent, NULL
};
DECLARE_MODULE(icu_msiabi_probe, icu_msiabi_probe_mod,
    SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_VERSION(icu_msiabi_probe, 1);