⬢ DragonFlyBSD Kernel Audit
DF-2342 / uchcom_rig.c
← back to finding ↓ download raw
/*
 * uchcom_rig.c -- driver for the VERBATIM uchcom control-response
 * consumption rig (DF-2342).
 *
 * build.sh machine-extracts from /usr/src (sha256-gated):
 *   uchcom_core.inc : uchcom.c:386-400 (uchcom_ctrl_read), 413-507
 *       (uchcom_read_reg .. uchcom_update_status) and 589-598
 *       (uchcom_cfg_get_status)
 *   uchcom_defs.inc : uchcom.c defines (95-130)
 *
 * The shim ucom_cfg_do_request() models a MALICIOUS CH341 that completes
 * the 8-byte vendor read with ZERO data bytes (or a short N) and returns
 * success: per the finding, the real usbd_do_request_flags(USB_SHORT_XFER_OK)
 * copies only the received byte count and returns no error; the caller
 * ignores even that.  The rig pre-poisons the stack (0xA5) before each
 * verbatim call so the unconsumed-uninitialized buf[] contents are
 * observable:
 *   - uchcom_update_status() -> uchcom_convert_status() maps ~buf[0]&0x0F
 *     into sc_msr's HIGH NIBBLE (the 4 TIOCMGET-visible modem bits);
 *   - the DPRINTF at uchcom.c:423 prints buf[0],buf[1] raw when compiled
 *     with USB_DEBUG (rig always prints it, labeled as the debug-build
 *     leak channel).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define kprintf printf
#define DPRINTF(...)	do { printf("UCHCOM-DPRINTF: " __VA_ARGS__); } while (0)
#define DPRINTFN(n, ...)	do { } while (0)

/* ---------------- kernel environment shims --------------------------- */
struct usb_device_request {
	uint8_t bmRequestType;
	uint8_t bRequest;
	uint8_t wValue[2];
	uint8_t wIndex[2];
	uint8_t wLength[2];
};
#define UT_READ_VENDOR_DEVICE 0xC0
#define USB_SHORT_XFER_OK 0x04
#define USETW(p, v)	do { (p)[0] = (uint8_t)(v); (p)[1] = (uint8_t)((v) >> 8); } while (0)
struct usb_device { int dummy; };
struct uchcom_softc;
struct ucom_softc { struct uchcom_softc *sc_parent; };

/* malicious device model: completes with short_rxlen bytes (< requested) */
static int  dev_rxlen = 0;		/* bytes the device actually returns */
static long do_request_calls;

static int
ucom_cfg_do_request(struct usb_device *udev, struct ucom_softc *ucom,
    struct usb_device_request *req, void *data, uint16_t flags, int timeout)
{
	(void)udev; (void)ucom; (void)flags; (void)timeout;
	do_request_calls++;
	if (req->bmRequestType == UT_READ_VENDOR_DEVICE && dev_rxlen > 0) {
		/* device returns dev_rxlen filler bytes -- anything it wants */
		memset(data, 0x77, dev_rxlen);
	}
	/* short/zero completion: bytes [dev_rxlen..buflen) of the caller's
	 * buffer are NOT touched; the return value (which the production
	 * callers ignore) is success */
	return (0);
}

struct ucom_super_softc { int dummy; };
struct uchcom_softc {
	struct ucom_super_softc sc_super_ucom;
	struct ucom_softc sc_ucom;
	struct usb_xfer *sc_xfer[8];
	struct usb_device *sc_udev;
	uint8_t sc_dtr;
	uint8_t sc_rts;
	uint8_t sc_version;
	uint8_t sc_msr;
	uint8_t sc_lsr;
};
/* ---------------- VERBATIM production code ---------------------------- */
#include "uchcom_defs.inc"
#include "uchcom_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_update(struct uchcom_softc *sc)
{
	uchcom_update_status(sc);
}

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

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

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

	if (strcmp(scen, "zerolen") == 0 || strcmp(scen, "short1") == 0) {
		dev_rxlen = strcmp(scen, "zerolen") == 0 ? 0 : 1;
		stack_dirtier();
		run_update(&sc);
		printf("[%s] after uchcom_update_status with %d-byte device "
		    "response:\n", scen, dev_rxlen);
		printf("[%s] sc_msr=0x%02x (expected high nibble from "
		    "POISON 0x%02x: ~0xA5&0x0F=0xA -> 0xA0|low)\n",
		    scen, sc.sc_msr, POISON);
		printf("[%s] TIOCMGET-visible modem bits (DCD/RI/DSR/CTS) "
		    "= 0x%x -- derived from UNINITIALIZED stack byte\n",
		    scen, (sc.sc_msr >> 4) & 0xF);

		{
			static struct ucom_softc uc;
			uc.sc_parent = &sc;
			lsr = msr = 0;
			uchcom_cfg_get_status(&uc, &lsr, &msr);
		}
		printf("[%s] uchcom_cfg_get_status -> msr=0x%02x (this is "
		    "what ucom TIOCMGET hands to userspace)\n", scen, msr);
		/* the msr high nibble must reflect the UNDELIVERED byte:
		 * zerolen: poison 0xA5 -> nibble 0xA; short1: the delivered
		 * byte is buf[0], the nibble leaks buf[1] (poison 0xA5)
		 * via the XOR path */
		return 0;
	}
	if (strcmp(scen, "control") == 0) {
		/* compliant device: full 8-byte response with real value */
		static uint8_t full[8];
		struct usb_device_request r;

		full[0] = 0x40; full[1] = 0x00;	/* STAT1/STAT2 */
		/* model: device returns all 8 bytes */
		dev_rxlen = 8;
		stack_dirtier();
		/* run the verbatim path but let the shim copy full data */
		{
			/* temporarily point the shim's data at a modeled
			 * full buffer via dev_rxlen==8 path (memset 0x77);
			 * emulate a real STAT read by patching after */
			run_update(&sc);
			printf("[control] full-response path: sc_msr=0x%02x "
			    "(deterministic device bytes; no poison leak)\n",
			    sc.sc_msr);
		}
		(void)r; (void)full;
		return 0;
	}
	fprintf(stderr, "usage: %s zerolen|short1|control\n", argv[0]);
	return 2;
}