⬢ DragonFlyBSD Kernel Audit
DF-4690 / cardbus_harness.c
← back to finding ↓ download raw
/*
 * cardbus_harness.c -- DF-4690 / DF-4691 verification harness
 *
 * Compiles the REAL tuple-decoder bodies VERBATIM (anchor-extracted at
 * build time from the guest's /usr/src copy of
 * sys/dev/pccard/cardbus/cardbus_cis.c; md5-verified == audit tree --
 * see build.sh) against minimal stubs for the newbus/pci environment,
 * and drives them with crafted CIS tuple bytes the way
 * cardbus_parse_cis (cardbus_cis.c:670-692) would.
 *
 * DF-4690 (decode_tuple_bar, cardbus_cis.c:293-385):
 *   mode 1: BAR size field 0xFFFFFFF0 with a PCI BAR probe that reads
 *           back 0xffffffff (unimplemented BAR -- exactly the case the
 *           `pci_bar != 0xffffffff` check at :362 excludes) keeps the
 *           attacker-controlled 32-bit CIS size; it is assigned to the
 *           INT parameter `len` (:308) and passed UNCHECKED as the
 *           resource count to resource_list_add (:377) -> truncated to
 *           -16.
 *   mode 2: probe reads back 0x80000000 (only the size bit set);
 *           masked -> 0x80000000 -> ffs()==32 -> `1 << (ffs-1)` =
 *           1 << 31 -- signed overflow UB, manifests as INT_MIN ->
 *           negative resource count.  (Beyond DF-1811, which covers
 *           ffs(0) when the masked probe value is 0.)
 *   mode 3 (contrast, known DF-1811): probe 0x0000000f -> masked 0 ->
 *           ffs(0)==0 -> 1 << -1 UB.
 * DF-4691 (decode_tuple_funce, cardbus_cis.c:257-290):
 *   mode 4: a 2-byte FUNCE/LAN tuple {TPLFE_TYPE_LAN_NID, 6} passes
 *           the `tupledata[1] > sizeof(nid)` check (6 > 6 is false,
 *           :278) and bcopy(:282) copies tupledata[2..7] -- PAST the
 *           tuple's own 2 bytes -- into dinfo->funce.lan.nid, the
 *           card's "MAC address" ivar (PCI_IVAR_ETHADDR,
 *           cardbus.c:327-333).  tupledata[] is the shared
 *           MAXTUPLESIZE parse buffer (bzero'd once at :656, then
 *           overwritten per tuple), so the MAC is fabricated from
 *           STALE bytes of previously-parsed tuples.  Also
 *           `fepresent |= 1 << type` (:286) with type up to 255 is a
 *           UB shift.
 */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <sys/endian.h>

/* ---------------- kernel shims ---------------- */
typedef void *device_t;
#define NELEM(x) (sizeof(x)/sizeof(x[0]))

int cardbus_cis_debug = 0;
#define kprintf printf
#define device_printf(dev, ...) do { } while (0)
#define DEVPRINTF(x) do { } while (0)

struct pci_devinfo { int resources; };	/* only &...resources is taken */
struct cardbus_devinfo {
	struct pci_devinfo pci;
	uint8_t  mprefetchable;
	uint8_t  mbelow1mb;
	uint16_t mfrid;
	uint16_t prodid;
	u_int	 funcid;
	union { struct { uint8_t nid[6]; } lan; } funce;
	uint32_t fepresent;
};
static struct cardbus_devinfo g_dinfo;
static device_t g_child = (device_t)1;
static device_t device_get_ivars(device_t c) { (void)c; return &g_dinfo; }

/* PCI config emulation: the probe write is a no-op; the read returns
 * the value a malicious/odd card would present after a 0xffffffff
 * BAR probe write. */
static uint32_t g_probe_readback;
static void pci_write_config(device_t d, int reg, uint32_t val, int width)
{ (void)d; (void)reg; (void)val; (void)width; }
static uint32_t pci_read_config(device_t d, int reg, int width)
{ (void)d; (void)reg; (void)width; return g_probe_readback; }
static void pci_enable_io(device_t d, int type) { (void)d; (void)type; }

#define SYS_RES_IOPORT  1
#define SYS_RES_MEMORY  2

static int g_add_count;		/* the `count` resource_list_add got */
static void resource_list_add(void *rl, int type, int rid,
    unsigned long start, unsigned long end, int count, int runadj)
{
	(void)rl; (void)type; (void)rid; (void)start; (void)end;
	(void)runadj;
	g_add_count = count;
}

/* constants from cardbusreg.h / pccard_cis.h */
#define CARDBUS_BASE0_REG	0x10
#define BARBIT(RID) (1<<(((RID)-CARDBUS_BASE0_REG)/4))
#define TPL_BAR_REG_ASI_MASK	0x07
#define TPL_BAR_REG_AS		0x80
#define TPL_BAR_REG_PREFETCHABLE 0x40
#define PCCARD_FUNCTION_NETWORK	6
#define PCCARD_TPLFE_TYPE_LAN_NID 0x04

/* ---- VERBATIM kernel code: cardbus_cis.c decode_tuple_funce + ----
 * ---- decode_tuple_bar (see build.sh anchors)                    ---- */
#include "cardbus_extracted.inc"

/* ------------------------------- harness ------------------------------- */
static void run_bar(const char *tag, uint8_t asi, uint32_t cis_len_field,
    uint32_t probe_readback)
{
	uint8_t tupledata[0x400];
	uint32_t off = 0;
	memset(tupledata, 0, sizeof(tupledata));
	tupledata[0] = asi;
	*(uint32_t *)(tupledata + 2) = htole32(cis_len_field);
	g_probe_readback = probe_readback;
	g_add_count = 0x42424242;
	decode_tuple_bar((device_t)1, g_child, 0x07 /*CISTPL_BAR*/, 6,
	    tupledata, 0, &off, NULL);
	printf("%s: asi=%u cis_size_field=0x%08x probe=0x%08x -> "
	       "resource_list_add count = %d (0x%x)%s\n",
	    tag, asi, cis_len_field, probe_readback,
	    g_add_count, (unsigned)g_add_count,
	    g_add_count < 0 ? "  <== NEGATIVE COUNT INTO RESOURCE LIST"
			    : "");
}

int main(int argc, char **argv)
{
	int mode = (argc > 1) ? atoi(argv[1]) : 0;
	int i;

	if (mode == 0 || mode == 1) {
		printf("== DF-4690 mode 1: CIS BAR size kept when probe "
		       "reads 0xffffffff ==\n");
		run_bar("m1", 1 /*BAR0, MEM*/, 0xFFFFFFF0u, 0xffffffffu);
	}
	if (mode == 0 || mode == 2) {
		printf("== DF-4690 mode 2: 1 << (ffs(0x80000000)-1) = "
		       "1 << 31 signed-overflow UB -> INT_MIN ==\n");
		run_bar("m2", 1, 0x00001000u, 0x80000000u);
	}
	if (mode == 0 || mode == 3) {
		printf("== (known DF-1811 contrast) masked probe 0 -> "
		       "ffs(0)-1 ==\n");
		run_bar("m3", 1, 0x00001000u, 0x0000000fu);
	}
	if (mode == 0 || mode == 4) {
		uint8_t tupledata[0x400];
		uint32_t off = 0;
		printf("== DF-4691: LAN NID fabricated from stale "
		       "parse-buffer bytes ==\n");
		/* a previously parsed tuple left payload bytes in the
		 * shared tupledata buffer (cardbus_parse_cis reuses it;
		 * only bzero once at :656) */
		for (i = 0; i < 16; i++)
			tupledata[i] = (uint8_t)(0xE0 + i);
		g_dinfo.funcid = PCCARD_FUNCTION_NETWORK;
		g_dinfo.fepresent = 0;
		memset(g_dinfo.funce.lan.nid, 0, 6);
		/* FUNCE tuple of length 2: type + nidlen, NO nid bytes */
		tupledata[0] = PCCARD_TPLFE_TYPE_LAN_NID;
		tupledata[1] = 6;	/* == sizeof(nid): passes :278 check */
		decode_tuple_funce((device_t)1, g_child, 0x22 /*CISTPL_FUNCE*/,
		    2 /* len */, tupledata, 0, &off, NULL);
		printf("nid (PCI_IVAR_ETHADDR, cardbus.c:333) = "
		       "%02x:%02x:%02x:%02x:%02x:%02x\n",
		       g_dinfo.funce.lan.nid[0], g_dinfo.funce.lan.nid[1],
		       g_dinfo.funce.lan.nid[2], g_dinfo.funce.lan.nid[3],
		       g_dinfo.funce.lan.nid[4], g_dinfo.funce.lan.nid[5]);
		printf("%s\n",
		    (g_dinfo.funce.lan.nid[0] == 0xE2 &&
		     g_dinfo.funce.lan.nid[5] == 0xE7) ?
		    "REPRODUCED: nid = stale bytes 0xE2..0xE7 from the "
		    "PREVIOUS tuple (read past the 2-byte tuple at :282)"
		    : "unexpected nid content");
		/* fepresent |= 1 << type with type up to 255: UB shift */
		tupledata[0] = 200; tupledata[1] = 0;
		decode_tuple_funce((device_t)1, g_child, 0x22, 2,
		    tupledata, 0, &off, NULL);
		printf("fepresent after 1<<200 (UB; x86 masks to 1<<8) = "
		       "0x%x\n", g_dinfo.fepresent);
	}
	return 0;
}