DF-5455 / mo5455_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 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | /* * DF-5455 KLD harness -- production-code demonstration of the umoscom * (MosChip MCS7703) uninitialized-stack read on failed/short vendor * control-IN, and its flow into the TIOCMGET modem-status bits. * * WHAT IT PROVES: * * umoscom_cfg_read() (umoscom.c:539-557) declares `uint8_t val;` * UNINITIALIZED, issues a 1-byte vendor control-IN through * ucom_cfg_do_request() (usb_serial.h:220-221 -> usbd_do_request_proc, * actlen hard-coded NULL) and RETURNS val REGARDLESS of the * usb_error_t result, which is discarded. * * Core semantics (traced, usb_request.c): * - timeout / stall / cancel: the WXFER loop breaks on * xfer->error BEFORE the usbd_copy_out() at :680-681 -- *ptr is * never written (usb_request.c:642-650); * - a "successful" control-IN with a zero-length data stage copies * nothing (temp == 0 at :666-683) -- *ptr never written; * - a short (0-byte of 1-byte) data stage with short_xfer_ok NOT set * (umoscom passes flags=0) completes with USB_ERR_SHORT_XFER -> * same no-copy break. * In ALL of those cases umoscom_cfg_read() returns one byte of stale * kernel stack. * * The byte has a live consumer, unlike the uvscom DF-5448 sibling: * umoscom_cfg_get_status() (umoscom.c:495-522) * msr = umoscom_cfg_read(sc, UMOSCOM_MSR); <-- :507 * if (msr & UMOSCOM_MSR_CTS) *p_msr |= SER_CTS; <-- :511-512 * if (msr & UMOSCOM_MSR_CD) *p_msr |= SER_DCD; <-- :514-515 * if (msr & UMOSCOM_MSR_RI) *p_msr |= SER_RI; <-- :517-518 * if (msr & UMOSCOM_MSR_RTS) *p_msr |= SER_DSR; <-- :520-521 * -> ucom_cfg_status_change() stores it: sc->sc_msr = new_msr * (usb_serial.c:1527) * -> ucom_modem()/TIOCMGET: if (sc->sc_msr & SER_CTS) sigon |= SER_CTS * ... (usb_serial.c:1321-1332) -> ucom_totio() maps to * TIOCM_CTS/TIOCM_CD/TIOCM_RI/TIOCM_DSR (usb_serial.c:1259-1270) * and TIOCMGET copies the int out to userspace * (usb_serial.c:1209-1211). * => bits 4..7 of one uninitialized kernel-stack byte are disclosed to * an unprivileged TIOCMGET reader, 4 bits per status refresh, on * every control-read failure (evil device: just stay silent on the * MSR vendor read; flaky device: timeout/stall). * * HOW IT RUNS PRODUCTION CODE: * * This module #includes "umoscom_prod_guarded.c" -- a byte-identical * copy of /usr/src/sys/bus/u4b/serial/umoscom.c except ONE guard hunk * (prod_guard.diff in this pack): the ucom_cfg_do_request macro is * rerouted AFTER its usb_serial.h definition to mo_shim_request(), * which reproduces the traced failure semantics above by NOT touching * the caller's buffer. Everything else -- the DRIVER_MODULE * registration (inert: no MCS7703 exists on the guest, so nothing ever * probes/attaches) included -- is compiled verbatim, and every * production function, umoscom_cfg_read() and umoscom_cfg_get_status() * among them, is called directly. * * The stack slot is made deterministic with a recursive poison frame * (mo_poison) called at the same depth right before the production * chain, mimicking stale stack contents from prior ucom-task work * (termios copies, request buffers, pointers). * * Counterfactual: mo_cfg_read_fixed() is the same function with the * ONE-LINE FIX (`uint8_t val = 0;`) -- under identical poisoning it * must report zero SER bits. */ #include <sys/param.h> #define UMOSCOM_HARNESS 1 #include "umoscom_prod_guarded.c" /* * Shim state (the shim body itself is in mo5455_shim.c -- a SEPARATE * compilation unit, exactly like production's usbd_do_request_proc: * as long as the compiler cannot see that *ptr is never written, the * uninitialized read in umoscom_cfg_read() compiles to a real stack * load. When the shim was in the same TU, GCC -O2 folded the * provably-uninitialized load to a constant 0 -- run.1.fold.log in this * pack documents that artifact. The -Werror=uninitialized diagnostic * fired either way (build.werror_evidence.log). * * Shim modes: * mode 0: data/status stage never answered -> USB_ERR_TIMEOUT * (usb_request.c:642-650: break BEFORE the copy-out) * mode 1: zero-length data stage, status stage OK -> SUCCESS with * nothing copied (usb_request.c:666-683) * Both leave *ptr untouched. The returned usb_error_t is discarded by * production umoscom_cfg_read() either way (umoscom.c:551-556). */ extern int mo_shim_mode; extern int mo_shim_requests; static struct umoscom_softc mo_sc; /* zeroed: softc stand-in */ static struct ucom_softc mo_ucom; /* sc_parent wired to mo_sc */ static uint8_t mo_msr_out; /* SER_* bits observed */ static uint8_t mo_lsr_out; /* * The production call chain at depth D (what ucom_cfg_status_change, * usb_serial.c:1515-1518, does -- new_msr/new_lsr start at 0). */ static void __noinline mo_prod_status(void) { uint8_t lsr = 0; /* usb_serial.c:1516 */ uint8_t msr = 0; /* usb_serial.c:1515 */ umoscom_cfg_get_status(&mo_ucom, &lsr, &msr); /* PRODUCTION */ mo_lsr_out = lsr; mo_msr_out = msr; } /* * Counterfactual: verbatim umoscom_cfg_read() + umoscom_cfg_get_status() * bodies with the one-line fix applied (`uint8_t val = 0;`). */ static uint8_t __noinline mo_cfg_read_fixed(struct umoscom_softc *sc, uint16_t reg) { struct usb_device_request req; uint8_t val = 0; /* <-- THE FIX */ req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = UMOSCOM_READ; USETW(req.wValue, 0); USETW(req.wIndex, reg); USETW(req.wLength, 1); ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, &val, 0, 1000); return (val); } static void __noinline mo_prod_status_fixed(void) { uint8_t lsr = 0; uint8_t msr = 0; uint8_t v; v = mo_cfg_read_fixed(&mo_sc, UMOSCOM_LSR); /* value discarded */ v = mo_cfg_read_fixed(&mo_sc, UMOSCOM_MSR); if (v & UMOSCOM_MSR_CTS) msr |= SER_CTS; if (v & UMOSCOM_MSR_CD) msr |= SER_DCD; if (v & UMOSCOM_MSR_RI) msr |= SER_RI; if (v & UMOSCOM_MSR_RTS) msr |= SER_DSR; mo_lsr_out = lsr; mo_msr_out = msr; } /* * Deterministic stale-stack stand-in: scribble a known pattern over the * stack region the production frames will reuse. */ static void __noinline mo_poison(uint8_t pat, int depth) { volatile uint8_t pad[256]; int i; for (i = 0; i < 256; ++i) pad[i] = pat; if (depth > 0) mo_poison(pat, depth - 1); } /* SER_* -> TIOCM_* exactly as ucom_totio() (usb_serial.c:1246-1273) */ static int mo_totio(uint8_t ser) { int rbits = 0; SET(rbits, TIOCM_LE); if (ISSET(ser, SER_DTR)) SET(rbits, TIOCM_DTR); if (ISSET(ser, SER_RTS)) SET(rbits, TIOCM_RTS); if (ISSET(ser, SER_CTS)) SET(rbits, TIOCM_CTS); if (ISSET(ser, SER_DCD)) SET(rbits, TIOCM_CD); if (ISSET(ser, SER_DSR)) SET(rbits, TIOCM_DSR); if (ISSET(ser, SER_RI)) SET(rbits, TIOCM_RI); return (rbits); } static uint8_t mo_predict_ser(uint8_t pat) { uint8_t ser = 0; /* the four umoscom_cfg_get_status translations, umoscom.c:511-521 */ if (pat & UMOSCOM_MSR_CTS) ser |= SER_CTS; if (pat & UMOSCOM_MSR_CD) ser |= SER_DCD; if (pat & UMOSCOM_MSR_RI) ser |= SER_RI; if (pat & UMOSCOM_MSR_RTS) ser |= SER_DSR; return (ser); } static int mo_run_once(uint8_t pat) { int match = 0; mo_shim_requests = 0; mo_shim_mode = 0; /* silent device: timeout */ mo_poison(pat, 6); mo_prod_status(); kprintf("MO5455: pat=0x%02x mode=timeout : PROD msr=0x%02x " "(predict 0x%02x) TIOCM after ucom_totio=0%o %s\n", pat, mo_msr_out, mo_predict_ser(pat), mo_totio(mo_msr_out), mo_msr_out == mo_predict_ser(pat) ? "MATCH" : "MISMATCH"); if (mo_msr_out == mo_predict_ser(pat)) match++; mo_shim_mode = 1; /* zero-length data stage */ mo_poison(pat, 6); mo_prod_status(); kprintf("MO5455: pat=0x%02x mode=0-len : PROD msr=0x%02x " "(predict 0x%02x) TIOCM after ucom_totio=0%o %s\n", pat, mo_msr_out, mo_predict_ser(pat), mo_totio(mo_msr_out), mo_msr_out == mo_predict_ser(pat) ? "MATCH" : "MISMATCH"); if (mo_msr_out == mo_predict_ser(pat)) match++; /* counterfactual with the one-line fix */ mo_shim_mode = 0; mo_poison(pat, 6); mo_prod_status_fixed(); kprintf("MO5455: pat=0x%02x FIXED (val=0) : msr=0x%02x %s\n", pat, mo_msr_out, mo_msr_out == 0 ? "(clean)" : "(LEAK SURVIVES FIX?!)"); kprintf("MO5455: (2 control reads consumed per status call; " "shim saw %d requests)\n", mo_shim_requests); return (match); } static int mo_modevent(module_t mod, int type, void *data) { static const uint8_t pats[] = { 0x00, 0xff, 0xa5, 0x5a, 0x0f, 0xf0 }; int i, matches = 0, total = 0; int error = 0; switch (type) { case MOD_LOAD: mo_ucom.sc_parent = &mo_sc; kprintf("MO5455: DF-5455 umoscom uninit-read harness: " "production umoscom.c compiled in " "(guarded copy, 1 hunk, see prod_guard.diff)\n"); for (i = 0; i < (int)(sizeof(pats)/sizeof(pats[0])); ++i) { matches += mo_run_once(pats[i]); total += 2; } kprintf("MO5455: RESULT: production umoscom_cfg_read -> " "cfg_get_status -> SER/TIOCM bits: %d/%d pattern-match " "across failure modes%s\n", matches, total, matches == total ? " -- UNINITIALIZED STACK DISCLOSED " "THROUGH MODEM-STATUS BITS" : ""); break; case MOD_UNLOAD: break; default: error = EOPNOTSUPP; break; } return (error); } static moduledata_t mo5455_mod = { "umoscomunleak", mo_modevent, 0 }; DECLARE_MODULE(umoscomunleak, mo5455_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); MODULE_DEPEND(umoscomunleak, ucom, 1, 1, 1); MODULE_DEPEND(umoscomunleak, usb, 1, 1, 1); |