DF-2342 / uchcom_rig.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 | /* * 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 = ≻ 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; } |