โฌข DragonFlyBSD Kernel Audit
DF-1091 / df1091_harness.c
โ† back to finding โ†“ download raw
/*
 * DF-1091 โ€” userspace harness for amdsmb_bread OOB MMIO read pattern.
 *
 * The kernel path (sys/bus/smbus/amdsmb/amdsmb.c:amdsmb_bread) is reachable
 * only via AMD-8111 SMBus hardware (PCI 1022:746a) with a malicious I2C
 * slave on the bus. The audit QEMU guest has NO AMD SMBus (PCI 0:1:3 is
 * Intel PIIX4 PM, class 0x068000 not 0x0c05), so this harness mirrors the
 * vulnerable algorithm byte-for-byte to demonstrate the OOB pattern.
 *
 * The harness models the SMBus EC register file as a 256-byte array
 * (offsets 0x00..0xff) with the 32-byte SMB_DATA window at 0x04..0x23.
 * A malicious slave supplies SMB_BCNT=255. The unpatched algorithm reads
 * SMB_DATA+i for i in [0,255), reading 224 bytes past the SMB_DATA window
 * into adjacent register space. The patched algorithm clamps len to 32.
 *
 * Build:  cc -O0 -o df1091_harness df1091_harness.c
 *   or:   cc -O0 -DFIX -o df1091_harness_fix df1091_harness.c
 * Run:    ./df1091_harness
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define SMB_DATA  0x04
#define SMB_BCNT  0x24
#define EC_SPACE  256

static unsigned char ec_space[EC_SPACE];
static int oob_count;

/* Mirror amdsmb_ec_read: read 1 byte from EC register offset.
 * In real hardware the SMBus EC register file is much smaller than
 * EC_SPACE; reads past the SMB_DATA window land in unrelated ACPI/EC
 * registers and may have side effects. We count any read with
 * offset > SMB_DATA + 31 (i.e. > 0x23) as the OOB bug pattern. */
static unsigned char
ec_read(int off)
{
	if (off > SMB_DATA + 31) {
		oob_count++;
		return 0xff;
	}
	if (off < 0 || off >= EC_SPACE)
		return 0xff;
	return ec_space[off];
}

/* Mirror amdsmb_bread's post-wait body. */
static int
amdsmb_bread_model(unsigned char slave_count, unsigned char *count, char *buf)
{
	unsigned char len, data;
	int i;

	/* entry check uses caller-supplied *count */
	if (*count < 1 || *count > 32)
		return -1;

	/* model amdsmb_wait() success */
	len = slave_count;
	oob_count = 0;

#ifndef FIX
	/* UNPATCHED โ€” loop bound is slave len, no clamp */
	for (i = 0; i < len; i++) {
		data = ec_read(SMB_DATA + i);    /* OOB when i >= 32 */
		if (i < *count)
			buf[i] = data;
	}
#else
	/* PATCHED โ€” clamp len to 32 (SMB_DATA register file size) */
	if (len > 32)
		len = 32;
	for (i = 0; i < len; i++) {
		data = ec_read(SMB_DATA + i);
		if (i < *count)
			buf[i] = data;
	}
#endif
	*count = len;
	return 0;
}

int
main(void)
{
	char buf[1024];
	unsigned char bcount;
	int rc;

	memset(ec_space, 0xAA, sizeof(ec_space));
	ec_space[SMB_BCNT] = 255; /* malicious slave */

	memset(buf, 0, sizeof(buf));
	bcount = 8;  /* caller-supplied initial value */

	printf("=== DF-1091 amdsmb_bread OOB MMIO harness (%s) ===\n",
#ifdef FIX
	    "PATCHED"
#else
	    "UNPATCHED"
#endif
	    );
	printf("caller *count=%u, slave SMB_BCNT=%u\n", bcount, ec_space[SMB_BCNT]);
	rc = amdsmb_bread_model(ec_space[SMB_BCNT], &bcount, buf);
	printf("rc=%d, returned *count=%u (was 8)\n", rc, bcount);
	printf("MMIO reads past SMB_DATA[0..31]: %d\n", oob_count);
	if (bcount > 32)
		printf("RETURNED COUNT > 32: caller (smb.c) will copyout up to %u bytes\n"
		       "  from a buf only 32 bytes were written into -> uninitialized stack leak\n",
		       bcount);
	if (oob_count > 0)
		printf("BUG: %d OOB MMIO reads of adjacent EC registers (SMB_BCNT/SMB_ALRM_*/beyond)\n",
		       oob_count);
	if (oob_count == 0 && bcount <= 32)
		printf("OK: no OOB; returned count clamped to %u\n", bcount);
	return (0);
}