⬢ DragonFlyBSD Kernel Audit
DF-2337 / uep_rig.c
← back to finding ↓ download raw
/*
 * uep_rig.c -- driver for the VERBATIM uep_intr_callback() reassembly
 * rig (DF-2337).
 *
 * build.sh machine-extracts from /usr/src (sha256-gated):
 *   uep_core.inc   : uep.c lines 121-293 (get_pkt_len, uep_process_pkt,
 *                    uep_intr_callback) + uep.c:388-393 (uep_put_queue)
 *   uep_defs.inc   : uep.c defines + struct uep_softc (lines 55-98)
 *
 * The kernel environment is emulated:
 *   - usbd_copy_out(pc, 0, buf, len): copies exactly `len` scenario bytes
 *     into the callback's stack buf[17]; bytes [len..16] keep whatever the
 *     "kernel stack" held -- the rig pre-poisons that stack region with
 *     0xA5 via a dirtier function called immediately before.
 *   - usb_fifo_put_data_linear(): records the 5 bytes pushed toward the
 *     user-visible /dev/uepN FIFO.
 *
 * Trigger (finding text): transfer 1 delivers 1 byte 0x80 (REPORT header)
 * -> sc->buf_len=1.  Transfer 2 delivers 1 byte: res = 5-1 = 4, and
 * memcpy(sc->buf+1, buf, 4) reads buf[1..3] which the device NEVER sent ->
 * 3 uninitialized kernel-stack bytes land in sc->buf[2..4] and are pushed
 * to the FIFO.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define kprintf printf
#define DPRINTF(...)	do { } while (0)
#define DPRINTFN(n, ...)	do { } while (0)
#define KKASSERT(x)	do { if (!(x)) abort(); } while (0)

/* ---------------- kernel environment shims --------------------------- */
struct usb_fifo_sc { void *fp[2]; };
struct usb_xfer { int dummy; };
struct usb_page_cache { int dummy; };
struct lock { int x; };
#define USB_FIFO_RX 0
#define USB_ST_SETUP 1
#define USB_ST_TRANSFERRED 2
#define USB_ERR_CANCELLED (-1)
#define UE_DIR_IN 0x80
#define UE_ADDR_ANY 0

static int usb_fifo_put_bytes_max(struct usb_fifo_sc *f)
{ (void)f; return 1; }

static uint8_t fifo_log[4096];
static int fifo_len;

static void
usb_fifo_put_data_linear(struct usb_fifo_sc *f, uint8_t *buf, uint16_t len,
    int what)
{
	(void)f; (void)what;
	if (fifo_len + len < (int)sizeof(fifo_log)) {
		memcpy(fifo_log + fifo_len, buf, len);
		fifo_len += len;
	}
}

/* scenario transfer state */
static uint8_t xfer_data[64];
static int xfer_len;

static void usbd_xfer_status(struct usb_xfer *x, int *a, void *b, void *c,
    void *d) { (void)x; (void)b; (void)c; (void)d; *a = xfer_len; }
static int USB_GET_STATE_(struct usb_xfer *x) { (void)x; return USB_ST_TRANSFERRED; }
#define USB_GET_STATE(x) USB_GET_STATE_((x))
static struct usb_page_cache *usbd_xfer_get_frame(struct usb_xfer *x, int f)
{ (void)x; (void)f; return (struct usb_page_cache *)1; }
static void usbd_copy_out(struct usb_page_cache *pc, uint16_t off,
    uint8_t *buf, uint16_t len)
{
	(void)pc; (void)off;
	/* EXACTLY len bytes: the rest of buf[17] stays poisoned -- this is
	 * the production semantics (short transfer) */
	memcpy(buf, xfer_data, len);
}
static void usbd_xfer_set_frame_len(struct usb_xfer *x, int f, uint16_t l)
{ (void)x; (void)f; (void)l; }
static uint16_t usbd_xfer_max_len(struct usb_xfer *x) { (void)x; return 17; }
static void usbd_transfer_submit(struct usb_xfer *x) { (void)x; }
static void usbd_xfer_set_stall(struct usb_xfer *x) { (void)x; }
static struct uep_softc *usbd_xfer_softc_fake;
static struct uep_softc *usbd_xfer_softc(struct usb_xfer *x)
{ (void)x; return usbd_xfer_softc_fake; }

/* ---------------- VERBATIM production code ---------------------------- */
#include "uep_defs.inc"
typedef int usb_error_t;
static void uep_put_queue(struct uep_softc *, u_char *);
#include "uep_core.inc"

/* ---------------- stack poisoning ------------------------------------- */
#define POISON 0xA5
static void __attribute__((noinline)) stack_dirtier(void)
{
	volatile uint8_t pad[512];
	int i;
	for (i = 0; i < (int)sizeof(pad); i++)
		pad[i] = POISON;
}

static void __attribute__((noinline)) run_callback(void)
{
	uep_intr_callback((struct usb_xfer *)1, 0);
}

/* ---------------- driver ---------------------------------------------- */
static struct uep_softc sc;

static void
one_transfer(uint8_t *bytes, int len)
{
	xfer_data[0] = 0;
	if (len > 0)
		memcpy(xfer_data, bytes, len);
	xfer_len = len;
	stack_dirtier();		/* kernel-stack residue = 0xA5 */
	run_callback();
}

static void
report(const char *tag)
{
	int i;
	printf("[%s] fifo bytes (%d):", tag, fifo_len);
	for (i = 0; i < fifo_len; i++)
		printf(" %02x", fifo_log[i]);
	printf("\n");
}

int
main(int argc, char **argv)
{
	const char *scen = argc == 2 ? argv[1] : "";
	uint8_t b;

	memset(&sc, 0, sizeof(sc));
	usbd_xfer_softc_fake = &sc;

	if (strcmp(scen, "attack") == 0) {
		b = 0x80;
		one_transfer(&b, 1);		/* 1 byte: 0x80 */
		b = 0x55;
		one_transfer(&b, 1);		/* 1 byte: 0x55 */
		report("attack");
		printf("[attack] FIFO should be: 80 55 A5 A5 A5 -- bytes "
		    "[2..4] are kernel-stack residue the device never sent "
		    "(leaked to userspace via /dev/uepN read)\n");
		return (fifo_len == 5 && fifo_log[0] == 0x80 && fifo_log[1] == 0x55
		    && fifo_log[2] == POISON && fifo_log[3] == POISON
		    && fifo_log[4] == POISON) ? 0 : 1;
	}
	if (strcmp(scen, "control") == 0) {
		uint8_t full[5] = { 0x80, 0x11, 0x22, 0x33, 0x44 };
		sc.buf_len = 0;
		one_transfer(full, 5);
		report("control");
		printf("[control] full 5-byte transfer: all FIFO bytes "
		    "device-supplied\n");
		return (fifo_len == 5 && fifo_log[2] == 0x22) ? 0 : 1;
	}
	fprintf(stderr, "usage: %s attack|control\n", argv[0]);
	return 2;
}