DF-5407 / uplcom_stall_harness.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | /* * 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); |