DF-5578 / ufm5578_shim.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 | /* * DF-5578 shim -- deliberately its own compilation unit (family standard, * see DF-5455 run.1.fold.log: a same-TU inlinable shim let GCC fold the * provably-uninitialized load to a constant; production's usbd_do_request * is an opaque cross-module macro/call, so the shim must be opaque too). * * It reproduces the exact usbd_do_request_flags() completion semantics for * the 1-byte vendor control-IN that ufm_do_req() issues (ufm.c:185-191, * flags=0 via the plain usbd_do_request macro at usbdi.h:511-512): * * mode 0 "short": device answers the DATA stage with a zero-length * packet, status stage OK. For flags=0 the stack * converts this to USB_ERR_SHORT_XFER * (usb_transfer.c:2979-2985: actlen 8 < sumlen 9, * short_xfer_ok NOT set -- the default EP0 template * usb_control_ep_cfg carries no short-ok flags, * usb_transfer.c:76-88) -> ufm_do_req() maps it to * ENXIO (ufm.c:196-198) and BOTH callers discard * `ret' (ufm.c:253-256, 288-291). PRODUCTION * CONTAINMENT -- this is what a real evil radio * gets on the current stack. * mode 1 "timeout": device never answers; xfer->error = USB_ERR_TIMEOUT * (usb_request.c:697-704). Same containment. * mode 2 "full": device returns the full 1-byte DATA stage; the * stack copies it out (usb_request.c:680-681) and * returns 0. Happy path / request-capture mode. * mode 3 "short-ok": COUNTERFACTUAL -- the DF-1062 delta. What the * SAME call site would observe if ufm passed * USB_SHORT_XFER_OK (as uchcom.c:398 does) or used * usbd_do_request_proc with actlen=NULL (uchcom's * ucom_cfg_do_request, usb_serial.h:220): a short * DATA stage completes with err==0 and NOTHING is * copied -- the stack's own zero-pad-on-short only * exists behind a non-NULL actlen * (usb_request.c:782-787). ufm_do_req() then * returns 0 and *retbuf receives stale kernel * stack. NOT reachable for ufm today (that is * the finding's containment claim); exercised to * demonstrate what one flag of drift turns it into. * * Every mode also records the request tuple (bmRequestType/bRequest/ * wValue/wIndex/wLength) into a capture log -- the "synthetic usbd * request capture" family evidence. */ #include <sys/param.h> #include <sys/systm.h> #include <sys/bus.h> #include <bus/u4b/usb.h> #include <bus/u4b/usbdi.h> int ufm_shim_mode = 0; int ufm_shim_requests = 0; uint8_t ufm_shim_devbyte = 0x5c; /* mode 2 answer */ struct ufm_shim_cap { uint8_t bm; uint8_t breq; uint16_t wval; uint16_t widx; uint16_t wlen; int mode; int err; }; /* prototypes (appease -Wmissing-prototypes; definitions below) */ void ufm_shim_cap_reset(void); int ufm_shim_cap_dump(struct ufm_shim_cap *, int); usb_error_t ufm_shim_request(struct usb_device_request *, uint8_t *); #define UFM_SHIM_CAPMAX 64 static struct ufm_shim_cap ufm_shim_log[UFM_SHIM_CAPMAX]; static int ufm_shim_logn; void ufm_shim_cap_reset(void) { ufm_shim_logn = 0; } int ufm_shim_cap_dump(struct ufm_shim_cap *out, int max) { int n = ufm_shim_logn; if (n > max) n = max; for (int i = 0; i < n; ++i) out[i] = ufm_shim_log[i]; return (ufm_shim_logn); } usb_error_t ufm_shim_request(struct usb_device_request *req, uint8_t *data) { struct ufm_shim_cap *cap; usb_error_t err = 0; if (ufm_shim_logn < UFM_SHIM_CAPMAX) { cap = &ufm_shim_log[ufm_shim_logn++]; cap->bm = req->bmRequestType; cap->breq = req->bRequest; cap->wval = UGETW(req->wValue); cap->widx = UGETW(req->wIndex); cap->wlen = UGETW(req->wLength); cap->mode = ufm_shim_mode; } else { cap = NULL; } ufm_shim_requests++; switch (ufm_shim_mode) { case 0: /* short ZLP -> SHORT_XFER (error) */ err = USB_ERR_SHORT_XFER; /* *data deliberately untouched */ break; case 1: /* silence -> TIMEOUT (error) */ err = USB_ERR_TIMEOUT; break; case 2: /* full 1-byte DATA stage */ *data = ufm_shim_devbyte; err = 0; break; case 3: /* COUNTERFACTUAL short-ok success */ err = 0; /* *data deliberately untouched */ break; default: err = USB_ERR_INVAL; break; } if (cap) cap->err = err; return (err); } |