⬢ DragonFlyBSD Kernel Audit
DF-2330 / amdsmb_rig.c
← back to finding ↓ download raw
/*
 * amdsmb_rig.c -- driver for the VERBATIM amdsmb_bread verification rig.
 *
 * The production functions (amdsmb_ec_wait_write/ec_wait_read/ec_read/
 * ec_write/amdsmb_wait/amdsmb_bread) are machine-extracted byte-identical
 * from the guest's /usr/src/sys/bus/smbus/amdsmb/amdsmb.c by build.sh
 * (sha256-gated) into amdsmb_core.inc.  This driver provides the kernel
 * environment emulation:
 *
 *   - bus_read_1/bus_write_1 model the AMD-8111 SMBus 2.0 controller I/O
 *     register file behind the EC_DATA/EC_CMD/EC_SC indirect protocol the
 *     verbatim code drives.  The model implements the SMB register file
 *     (PRTCL 0x00 ... ALRM_D 0x26) that a NON-COMPLIANT / MALICIOUS SMBus
 *     slave presents through the controller: BCNT (0x24) reads back 255.
 *   - Every register-file fetch is instrumented: the "logical" SMB register
 *     number fetched via the EC protocol is recorded, so reads beyond the
 *     32-entry data register file (SMB_DATA..SMB_DATA+31 == 0x04..0x23)
 *     are counted and their values are traceable.
 *
 * Build:  sh build.sh   (extract + cc -O2 -o rig amdsmb_rig.c)
 * Run:    ./rig attack | control
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

/* ---------------- kernel environment shims (NOT verbatim) ------------- */
typedef void *device_t;

struct resource { int dummy; };
static struct resource rig_res;

struct lock { int x; };

#define KKASSERT(x)	do { if (!(x)) { \
	printf("RIG KKASSERT failed: %s\n", #x); abort(); } } while (0)
static int lockowned(struct lock *l) { (void)l; return 1; }
static int lockmgr(struct lock *l, int flags) { (void)l; (void)flags; return 0; }
#define LK_EXCLUSIVE	0x80000
#define LK_RELEASE	0x40000
#define LK_CANRECURSE	0x0004

static void DELAY(int d) { (void)d; }
#define kprintf printf
static void device_printf(device_t d, const char *fmt, ...) { (void)d; (void)fmt; }
static void *device_get_softc(device_t d) { return d; }
static device_t rig_dev;

static int amdsmb_debug = 0;
#define	AMDSMB_DEBUG(x)	if (amdsmb_debug) (x)

/* SMBbus error codes (smbconf.h) */
#define SMB_ENOERR	0x0
#define SMB_EINVAL	0x100
#define SMB_ENOACK	0x19
#define SMB_EBUSY	0x40
#define SMB_ETIMEOUT	0x20
#define SMB_ENOTSUPP	0x27
#define SMB_EBUSERR	0x1c

/* ---------------- controller I/O model --------------------------------
 * The verbatim code does EC-protocol accesses: writes EC_CMD(0x04) = 0x80
 * (read) / 0x81 (write), then EC_DATA(0x00) = SMB register number, then
 * reads EC_DATA for the value.  io[] is the I/O port space the resource
 * maps; smbreg[] is the SMB register file the controller fronts.
 */
static uint8_t io[512];			/* port space: EC_DATA 0x00, EC_SC/CMD 0x04 */
static uint8_t smbreg[512];		/* controller SMB register file */

/* instrumentation */
static long   io_reads, io_writes;
static long   smb_fetches;		/* logical SMB register fetches */
static long   smb_fetch_oob;		/* fetches at reg > 0x23 (past data file) */
static int    smb_fetch_max = -1;	/* max logical register fetched */
static uint8_t smb_fetch_vals[512];	/* value served per logical register */
static long   ec_sc_polls;

static void model_reset(uint8_t bcnt)
{
	memset(io, 0, sizeof(io));
	memset(smbreg, 0, sizeof(smbreg));
	/* non-compliant slave delivered `bcnt` bytes; controller stored it */
	smbreg[0x24] = bcnt;
	/* data the slave pushed: the 32-entry data register file only */
	{
		int i;
		for (i = 0; i < 32; i++)
			smbreg[0x04 + i] = (uint8_t)(0xC0 ^ i); /* distinct */
	}
	/* registers beyond the file (BCNT 0x24, ALRM 0x25.., whatever else
	 * the chipset maps there) carry their own distinct values */
	{
		int i;
		for (i = 0x25; i < 512; i++)
			smbreg[i] = (uint8_t)(i ^ 0x5A);
	}
	smbreg[0x00] = 0x00;			/* PRTCL idle -> wait() happy */
	smbreg[0x01] = 0x00;			/* STS = OK */
	/* re-fetch bookkeeping */
	smb_fetches = smb_fetch_oob = 0;
	smb_fetch_max = -1;
	memset(smb_fetch_vals, 0, sizeof(smb_fetch_vals));
	ec_sc_polls = 0;
}

static uint8_t bus_read_1(struct resource *r, int reg)
{
	(void)r;
	io_reads++;
	if (reg == 0x00) {			/* EC_DATA: serves last addressed SMB reg */
		smb_fetches++;
		/* the last EC_DATA write selected the SMB register number */
		{
			int sel = io[0x00];	/* value written to EC_DATA before read */
			uint8_t v = smbreg[sel];
			smb_fetch_vals[sel & 0x1ff] = v;
			if ((sel & 0xff) > 0x23)
				smb_fetch_oob++;
			if ((sel & 0xff) > smb_fetch_max)
				smb_fetch_max = sel & 0xff;
			return v;
		}
	}
	if (reg == 0x04) {			/* EC_SC */
		ec_sc_polls++;
		return 0x01;			/* OBF set, IBF clear: always ready */
	}
	return io[reg];
}

static void bus_write_1(struct resource *r, int reg, uint8_t v)
{
	(void)r;
	io_writes++;
	io[reg] = v;
}

/* ---------------- VERBATIM production code ---------------------------- */
#include "amdsmb_core.inc"

/* ---------------- scenarios ------------------------------------------- */
static void report(const char *tag, int rc, uint8_t count_in,
    uint8_t count_out, char *buf)
{
	int i;
	printf("[%s] rc=%d(SMB_ENOERR=%d) count_in=%u count_out=%u\n",
	    tag, rc, SMB_ENOERR, count_in, count_out);
	printf("[%s] io: reads=%ld writes=%ld ec_sc_polls=%ld | "
	    "smb register fetches=%ld OOB(>0x23 data-file end)=%ld "
	    "max logical reg=0x%02x\n",
	    tag, io_reads, io_writes, ec_sc_polls,
	    smb_fetches, smb_fetch_oob, smb_fetch_max);
	printf("[%s] buf[0..7] =", tag);
	for (i = 0; i < 8; i++)
		printf(" %02x", (uint8_t)buf[i]);
	printf("  buf[30..33] =");
	for (i = 30; i < 34 && i < 255; i++)
		printf(" %02x", (uint8_t)buf[i]);
	printf("\n");
	/* traceability: value the model serves for registers beyond the file */
	if (smb_fetch_max > 0x23) {
		printf("[%s] PROOF: logical SMB registers 0x24..0x%02x were "
		    "fetched via the EC protocol -- beyond the 32-entry data "
		    "register file (SMB_DATA=0x04 .. 0x23)\n", tag, smb_fetch_max);
		printf("[%s] e.g. buf[32] (i=32 -> reg 0x24=BCNT) model value "
		    "0x%02x; buf[33] (reg 0x25=ALRM_A) 0x%02x; "
		    "buf[36] (reg 0x28) 0x%02x\n", tag,
		    smb_fetch_vals[0x24], smb_fetch_vals[0x25],
		    smb_fetch_vals[0x28]);
	}
	if (count_out > count_in)
		printf("[%s] CONTRACT BREAK: *count reports %u but caller "
		    "buffer holds only %u initialized byte(s) -- caller-side "
		    "read of buf[count_in..count_out-1] reads the caller's "
		    "uninitialized buffer\n", tag, count_out, count_in);
}

int
main(int argc, char **argv)
{
	struct amdsmb_softc sc;
	char buf[64];
	uint8_t count;
	int rc;

	memset(&sc, 0, sizeof(sc));
	sc.res = &rig_res;

	if (argc == 2 && strcmp(argv[1], "attack") == 0) {
		model_reset(255);			/* malicious BCNT */
		memset(buf, 0, sizeof(buf));
		count = 32;				/* caller contract: <=32 */
		rc = amdsmb_bread(rig_dev, 0x2D, 0x07, &count, buf);
		report("attack bcnt=255", rc, 32, count, buf);
		return 0;
	}
	if (argc == 2 && strcmp(argv[1], "control") == 0) {
		model_reset(16);			/* compliant slave */
		memset(buf, 0, sizeof(buf));
		count = 32;
		rc = amdsmb_bread(rig_dev, 0x2D, 0x07, &count, buf);
		report("control bcnt=16", rc, 32, count, buf);
		return 0;
	}
	if (argc == 2 && strcmp(argv[1], "edge") == 0) {
		model_reset(33);			/* one past the file */
		memset(buf, 0, sizeof(buf));
		count = 32;
		rc = amdsmb_bread(rig_dev, 0x2D, 0x07, &count, buf);
		report("edge bcnt=33", rc, 32, count, buf);
		return 0;
	}
	fprintf(stderr, "usage: %s attack|control|edge\n", argv[0]);
	return 2;
}