⬢ DragonFlyBSD Kernel Audit
DF-5407 / uplcom_stall_harness.c
← back to finding ↓ download raw
/*
 * DF-5407 KLD harness -- production-code demonstration of the uplcom
 * attach-time control-request chain stall (USB explore thread DoS).
 *
 * WHAT IT PROVES (with the production uplcom attach chain compiled in
 * VERBATIM, extracted at build time by build.sh from the guest's clean
 * /usr/src snapshot of sys/bus/u4b/serial/uplcom.c):
 *
 *   uplcom_attach() runs, on the per-bus USB explore thread
 *   (uhub_explore_sub usb_hub.c:1103 -> usb_probe_and_attach
 *   usb_device.c:1316-1406), 14 sequential SYNCHRONOUS EP0 control
 *   requests before the device finishes attaching:
 *
 *     1 x uplcom_reset()            uplcom.c:412  (usbd_do_request)
 *    13 x uplcom_pl2303_init()      uplcom.c:433 -> 497-525
 *                                    (10 at 502-511, 1 at 515/517,
 *                                     2 at 521-522, via
 *                                     uplcom_pl2303_do() 475-495)
 *
 *   Every request is usbd_do_request() -> USB_DEFAULT_TIMEOUT = 5000 ms
 *   (usbdi.h:87,511-512), clamped [50,30000] ms (usb_request.c:424-430).
 *   USB puts NO protocol bound on how long a device may NAK a control
 *   transfer's data/status stage; the only bound is that host timeout,
 *   enforced at usb_request.c:700-708 while the requester sleeps
 *   UNINTERRUPTIBLY in the WXFER loop (lksleep flags 0,
 *   usb_request.c:633-636).  So an evil PL2303-VID:PID device that
 *   answers every request successfully after just-under-5000 ms pins the
 *   explore thread -- which serially handles ALL hub events for the bus,
 *   including unplug processing -- for ~14 x 5 s = ~70 s per insertion,
 *   repeatably (a gadget can drop off the bus and re-enumerate itself).
 *
 *   Slow-but-successful is the amplifier: a device that simply times out
 *   fails request #1 and the || chain at uplcom.c:502-511 aborts after
 *   one 5 s timeout; a device that SUCCEEDS slowly keeps the chain going
 *   through all 14 requests.
 *
 * HOW: only the transport is intercepted.  After the real u4b headers,
 * usbd_do_request()/usbd_do_request_flags() are re-targeted to
 * hx_evil_request(), an evil-device model that sleeps (uninterruptibly,
 * mirroring the WXFER wait) for timeout-100 ms and then returns success.
 * The three production functions are then #included verbatim (renamed
 * hx_a_*); a second copy extracted from the fix.diff-patched file is
 #included as hx_b_* for the fix-mechanism phase.
 *
 * PHASE A (baseline, production code): hx_a_reset() + hx_a_init() with
 *   the 5000 ms default -- EXPECT ~14 x 4900 ms ~= 68600 ms pinned and
 *   exactly 14 EP0 requests.
 * PHASE B (fix.diff mechanism: 250 ms per-request bound at attach):
 *   hx_b_reset() + hx_b_init() -- EXPECT ~14 x 150 ms ~= 2100 ms.
 */
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/lock.h>
#include <sys/condvar.h>
#include <sys/time.h>
#include <sys/types.h>

#include <sys/bus.h>
#include <bus/u4b/usb.h>
#include <bus/u4b/usbdi.h>
#include <bus/u4b/usbdi_util.h>
#include <bus/u4b/usb_cdc.h>
#include <bus/u4b/usb_process.h>
#include <bus/u4b/serial/usb_serial.h>

/* The extracted production bodies use DPRINTF (uplcom.c:491); compiled
 * with debug output off, exactly like the guest's non-USB_DEBUG kernel. */
#define	DPRINTF(...) do { } while (0)
#define	DPRINTFN(...) do { } while (0)

static int hx_nreq;			/* requests served this run */
static int hx_overrun;			/* wLength > 4 seen (buf[4] safety) */
static char hx_phase;

/*
 * Evil-PL2303 device model: answers every EP0 request SUCCESSFULLY after
 * just-under-timeout ms of NAK-ing (USB-legal; only the host's timeout
 * bounds it, usb_request.c:700-708).
 */
static int
hx_evil_request(struct usb_device_request *req, void *buf, int timeout_ms)
{
	int latency = timeout_ms - 100;	/* just under the host timeout */

	if (UGETW(req->wLength) > 4)
		hx_overrun++;	/* would overflow uplcom_pl2303_do's buf[4] */

	hx_nreq++;
	kprintf("HX5407[%c]:   EP0 req #%d: type=0x%02x req=0x%02x "
	    "val=0x%04x idx=0x%04x len=%d  (device NAKs %d ms, then OK)\n",
	    hx_phase, hx_nreq, req->bmRequestType, req->bRequest,
	    UGETW(req->wValue), UGETW(req->wIndex), UGETW(req->wLength),
	    latency);

	/* mirrors the uninterruptible WXFER wait (usb_request.c:633-636) */
	if (latency > 0)
		tsleep(&hx_nreq, 0, "WXFER", USB_MS_TO_TICKS(latency));

	if ((req->bmRequestType & UT_READ) && (buf != NULL) &&
	    (UGETW(req->wLength) > 0))
		*(uint8_t *)buf = 0x00;	/* 1 status byte, like real PL2303 */

	return (0);			/* success -- just slow */
}

/* Retarget the production transport (AFTER the real headers). */
#undef	usbd_do_request
#undef	usbd_do_request_flags
#define	usbd_do_request(u, m, r, d) \
	hx_evil_request((r), (d), USB_DEFAULT_TIMEOUT)
#define	usbd_do_request_flags(u, m, r, d, f, a, t) \
	hx_evil_request((r), (d), (t))

#include "hx_softc.h"		/* production softc + defines, uplcom.c:114-159 */
#include "hx_a_funcs.h"		/* production uplcom_reset/_do/_init, VERBATIM */
#include "hx_b_funcs.h"		/* same three functions after fix.diff */

static struct uplcom_softc hx_sc;

static int64_t
hx_ms(struct timeval *t0, struct timeval *t1)
{
	return ((int64_t)(t1->tv_sec - t0->tv_sec) * 1000 +
	    (t1->tv_usec - t0->tv_usec) / 1000);
}

/*
 * Runs the exact uplcom_attach() request chain -- uplcom.c:412
 * (uplcom_reset) + uplcom.c:433 (uplcom_pl2303_init) -- against the evil
 * device model and reports how long the calling thread is pinned.  At
 * real attach time that caller is the per-bus USB explore thread.
 */
static void
hx_chain(int fixed, int chiptype)
{
	struct timeval t0, t1;
	int n0 = hx_nreq;

	hx_phase = fixed ? 'B' : 'A';
	memset(&hx_sc, 0, sizeof(hx_sc));
	hx_sc.sc_data_iface_no = 1;	/* device-supplied, uplcom.c:395 */

	kprintf("HX5407[%c]: uplcom_attach chain enter "
	    "(uplcom.c:412 uplcom_reset + uplcom.c:433 uplcom_pl2303_init, "
	    "chiptype=%s)\n", hx_phase,
	    chiptype == TYPE_PL2303HX ? "PL2303HX" : "PL2303");

	microtime(&t0);
	if (fixed) {
		hx_b_reset(&hx_sc, NULL);
		hx_b_init(NULL, chiptype);
	} else {
		hx_a_reset(&hx_sc, NULL);
		hx_a_init(NULL, chiptype);
	}
	microtime(&t1);

	kprintf("HX5407[%c]: uplcom_attach chain DONE: %jd ms pinned, "
	    "%d sync EP0 requests (14 expected: 1 reset + 13 init)\n",
	    hx_phase, (intmax_t)hx_ms(&t0, &t1), hx_nreq - n0);
}

static int
hx_modevent(module_t mod, int type, void *data)
{
	int error = 0;

	switch (type) {
	case MOD_LOAD:
		kprintf("HX5407: DF-5407 uplcom attach-chain stall harness, "
		    "hz=%d, USB_DEFAULT_TIMEOUT=%d ms\n", hz,
		    USB_DEFAULT_TIMEOUT);

		hx_chain(0, TYPE_PL2303HX);	/* A: production, 5 s timeout */
		hx_chain(1, TYPE_PL2303HX);	/* B: fix.diff, 250 ms bound */

		kprintf("HX5407: harness complete (wLength>4 overruns: %d)\n",
		    hx_overrun);
		break;
	case MOD_UNLOAD:
		break;
	default:
		error = EOPNOTSUPP;
		break;
	}
	return (error);
}

static moduledata_t hx_mod = { "uplcomstall", hx_modevent, 0 };
DECLARE_MODULE(uplcomstall, hx_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_DEPEND(uplcomstall, usb, 1, 1, 1);