โฌข DragonFlyBSD Kernel Audit
DF-5341 / pap_trust.c
โ† back to finding โ†“ download raw
/*
 * DF-5341 โ€” mptable_map() trusts firmware table pointers with no
 * signature / checksum / physical-range validation (mptable.c:501-539).
 *
 * The floating-pointer scan matches ONLY the 4-byte "_MP_" signature
 * (mptable.c:375).  mptable_map() then:
 *   - maps 16 bytes at the hit address and reads fps->pap, checking
 *     ONLY pap != 0                       (mptable.c:511-512),
 *   - maps 44 bytes at that arbitrary 32-bit PA and adopts
 *     base_table_length from it           (mptable.c:517-518),
 *   - re-maps base_table_length (up to 65535) bytes there and the
 *     whole table is parsed               (mptable.c:531).
 *
 * NEVER checked, anywhere in the file:
 *   - MPFPS checksum byte (fps->checksum, struct mptable.c:93)
 *   - MPFPS spec_rev / length sanity
 *   - MPCTH "PCMP" signature (struct mptable.c:103) โ€” grep: never read
 *   - MPCTH checksum (mptable.c:106) โ€” grep: never read
 *   - that pap / pap+base_table_length lie in mapped RAM
 *
 * pmap_mapdev_attr() (pmap.c:6192-6218) installs PTEs for ANY physical
 * address with no bounds check, so every one of these firmware values
 * becomes a kernel read of an arbitrary physical address at early boot,
 * and the parsed garbage drives CPU/IOAPIC enumeration.
 *
 * This harness is a decision-table reproducer: it enumerates pap values
 * the current code accepts and where mapdev lands for them.
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>

typedef uint32_t u_int32_t;
typedef uint16_t u_short;

/* the ONLY pap validation in mptable_map (mptable.c:512) */
static int
pap_accepted(u_int32_t pap)
{
	return pap != 0;
}

/* pmap_mapdev_attr (pmap.c:6192) accepts any pa โ€” modeled, plus the
 * span computation the caller relies on */
static void
show_map(const char *what, u_int32_t pap, u_short base_table_length)
{
	uint64_t lo = pap, hi = (uint64_t)pap + base_table_length;

	printf("  %-28s pap=0x%08x len=%-6u map span [0x%09llx..0x%09llx)%s\n",
	    what, pap, base_table_length,
	    (unsigned long long)lo, (unsigned long long)hi,
	    (hi > 0x100000000ULL) ? "  ** CROSSES THE 4GB WRAP **" : "");
}

int
main(void)
{
	struct { u_int32_t pap; const char *what; } cases[] = {
		{ 0x0009FC00, "sane (EBDA table)" },
		{ 0x000F0000, "BIOS ROM area" },
		{ 0xFEBC0000, "MMIO hole" },
		{ 0xA0000000, "PCI MMIO" },
		{ 0xFFFFFF00, "top of 4GB - 0x100" },
		{ 0xFFFFFFFF, "last byte of 4G" },
		{ 0xDEADBEEF, "total garbage" },
	};
	unsigned i;

	printf("pap values accepted by mptable_map() as-is (only check: pap != 0, "
	    "mptable.c:512):\n");
	for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
		printf("  pap=0x%08x (%-20s) -> %s\n", cases[i].pap,
		    cases[i].what,
		    pap_accepted(cases[i].pap) ?
		    "ACCEPTED, mapped + parsed as MP config table" :
		    "rejected");
	}

	printf("\nresulting pmap_mapdev spans (pmap.c:6192 accepts ANY pa):\n");
	show_map("sane", 0x0009FC00, 0x2A);
	show_map("garbage pap", 0xDEADBEEF, 0xFFFF);
	show_map("4GB-crossing", 0xFFFFFF00, 0x1000);

	printf("\nvalidation the file NEVER performs (audited by grep of "
	    "sys/platform/pc64/x86_64/mptable.c):\n");
	printf("  MPFPS checksum byte:      0 reads of fps->checksum\n");
	printf("  MPCTH 'PCMP' signature:   0 reads of cth->signature\n");
	printf("  MPCTH checksum:           0 reads of cth->checksum\n");
	printf("  pap/pap+len in RAM:       no range check exists\n");
	return 0;
}