DF-5578 / ufm5578_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 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 | /* * DF-5578 KLD harness -- production-code demonstration of the ufm(4) * (Cypress FM radio) EP0 helper's uninitialized-stack read, its runtime * CONTAINMENT on the current stack, and the one-flag counterfactual that * turns it into the DF-1062-class disclosure. * * WHAT IT PROVES: * * ufm_do_req() (ufm.c:176-200) declares `uint8_t buf[1];' UNINITIALIZED, * submits a 1-byte vendor control-IN through the PLAIN usbd_do_request * macro (usbdi.h:511-512: flags=0, actlen=NULL), and then -- BEFORE the * error check -- does `*retbuf = buf[0];' (ufm.c:193-194). Two callers * feed userspace: ufm_get_stat() copies `ret' into the ioctl word * (ufm.c:292 -> copyout via sys_generic.c:730) and ufm_start() branches * on it (ufm.c:257). * * Production containment (traced, and exercised here as modes 0/1): * - short control-IN with flags=0 -> USB_ERR_SHORT_XFER * (usb_transfer.c:2979-2985; the default EP0 template carries no * short-ok flags, usb_transfer.c:76-88) -> ufm_do_req returns ENXIO * -> both callers return EIO and DISCARD `ret' (ufm.c:253-256, * 288-291). err==0 without a 1-byte copy-out is provably * impossible: actlen == sumlen is required for success * (usb_transfer.c:2952-2985) and actlen is summed over completed * frames (usb_transfer.c:2936-2940), so a full 1-byte DATA stage * was DMA'd and copied (usb_request.c:680-681). * * Counterfactual (mode 3) -- the DF-1062 delta: * - the SAME call site with USB_SHORT_XFER_OK (uchcom.c:398) or via * usbd_do_request_proc with actlen=NULL (usb_serial.h:220) yields * err==0 with nothing copied (the zero-pad-on-short only exists * behind a non-NULL actlen, usb_request.c:782-787) -> FM_GET_STAT * hands one stale kernel-stack byte to the ioctl caller. * * HOW IT RUNS PRODUCTION CODE: * * This module #includes "ufm_prod_guarded.c" -- a byte-identical copy * of /usr/src/sys/bus/u4b/misc/ufm.c (sha256 845738f2...) except ONE * guard hunk (prod_guard.diff): the usbd_do_request macro is rerouted * to the cross-TU shim ufm_shim_request() exactly like production's * opaque cross-module call. The real ufm_ioctl() dispatch (including * the FWRITE|FREAD gate at ufm.c:304), ufm_set_freq(), ufm_get_freq(), * ufm_start(), ufm_stop(), ufm_get_stat() and ufm_do_req() are ALL the * verbatim production functions, called through a fabricated * struct usb_fifo whose priv_sc0 is the softc stand-in * (usb_fifo_softc(), usb_dev.c:2499-2502). The DRIVER_MODULE * registration is kept (inert: no Cypress 0x04b4:0x1002 radio exists * on the QEMU guest, so nothing ever probes/attaches). * * Deterministic stale-stack stand-in: recursive poison frames scribble * a known byte over the stack region ufm_do_req()'s buf[0] will reuse; * the matching recursion depth is calibrated once at load. * * Counterfactual fixed twin: verbatim ufm_do_req() body with the * ONE-LINE FIX (`uint8_t buf[1] = {0};') -- under identical poisoning * it must report 0x00 in every mode. */ #include <sys/param.h> #include "ufm_prod_guarded.c" #include <bus/u4b/usb_dev.h> /* struct usb_fifo (public) */ extern int ufm_shim_mode; extern int ufm_shim_requests; extern uint8_t ufm_shim_devbyte; /* request-capture accessors (see ufm5578_shim.c) */ struct ufm_shim_cap { uint8_t bm; uint8_t breq; uint16_t wval; uint16_t widx; uint16_t wlen; int mode; int err; }; extern void ufm_shim_cap_reset(void); extern int ufm_shim_cap_dump(struct ufm_shim_cap *, int); static struct ufm_softc fm_sc; /* zeroed softc stand-in */ static struct usb_fifo fm_fifo; /* fabricated: priv_sc0 = &fm_sc */ static int fm_depth = 6; /* calibrated poison depth */ /* ------------------------------------------------------------------ */ /* production entry points (all statics reachable: we #included the .c) */ static int __noinline fm_prod_ioctl(u_long cmd, void *addr, int fflags) { return (ufm_ioctl(&fm_fifo, cmd, addr, fflags)); /* PRODUCTION */ } static int __noinline fm_prod_do_req(uint8_t request, uint16_t value, uint16_t index, uint8_t *retbuf) { return (ufm_do_req(&fm_sc, request, value, index, retbuf)); /* PROD */ } /* * Counterfactual fixed twin: verbatim ufm_do_req() with the one-line fix. */ static int __noinline fm_prod_do_req_fixed(uint8_t request, uint16_t value, uint16_t index, uint8_t *retbuf) { int error; struct usb_device_request req; uint8_t buf[1] = {0}; /* <-- THE FIX (fix.diff) */ req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = request; USETW(req.wValue, value); USETW(req.wIndex, index); USETW(req.wLength, 1); error = usbd_do_request(fm_sc.sc_udev, NULL, &req, buf); if (retbuf) { *retbuf = buf[0]; } if (error) { return (ENXIO); } return (0); } /* ------------------------------------------------------------------ */ /* deterministic stale-stack stand-in (family standard) */ static void __noinline fm_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) fm_poison(pat, depth - 1); } /* ------------------------------------------------------------------ */ static void fm_calibrate(void) { int d; uint8_t ret = 0; /* * Find the recursion depth at which ufm_do_req()'s buf[0] reuses the * poisoned region (mode 3 = err==0 without copy-out). */ for (d = 1; d <= 14; ++d) { ufm_shim_mode = 3; fm_poison(0xa5, d); ret = 0; (void)fm_prod_do_req(UFM_CMD0, 0x00, 0x24, &ret); if (ret == 0xa5) { fm_depth = d; kprintf("UFM5578: calibrate: poison depth %d MATCHES " "buf[0] slot\n", d); return; } } kprintf("UFM5578: calibrate: NO exact depth match (stack too " "volatile); using depth %d\n", fm_depth); } static int fm_matrix(void) { static const uint8_t pats[] = { 0x00, 0xff, 0xa5, 0x5a, 0x0f, 0xf0 }; int i, rep; int match = 0, total = 0, fixed_clean = 0; /* * Counterfactual (mode 3): err==0, nothing copied -> the production * *retbuf load (ufm.c:193-194) returns the poisoned stack byte, * which ufm_get_stat() would copy to the FM_GET_STAT caller * (ufm.c:292). */ for (i = 0; i < (int)(sizeof(pats) / sizeof(pats[0])); ++i) { for (rep = 0; rep < 3; ++rep) { uint8_t ret = 0xee; ufm_shim_mode = 3; fm_poison(pats[i], fm_depth); (void)fm_prod_do_req(UFM_CMD0, 0x00, 0x24, &ret); kprintf("UFM5578: pat=0x%02x rep=%d mode=short-ok : " "PROD ret=0x%02x %s\n", pats[i], rep, ret, ret == pats[i] ? "MATCH" : "MISMATCH"); if (ret == pats[i]) match++; total++; ret = 0xee; fm_poison(pats[i], fm_depth); (void)fm_prod_do_req_fixed(UFM_CMD0, 0x00, 0x24, &ret); kprintf("UFM5578: pat=0x%02x rep=%d FIXED(buf={0}) : " "ret=0x%02x %s\n", pats[i], rep, ret, ret == 0 ? "(clean)" : "(LEAK SURVIVES FIX?!)"); if (ret == 0) fixed_clean++; } } kprintf("UFM5578: RESULT: counterfactual short-ok: %d/%d " "pattern-match; fixed twin clean %d/18\n", match, total, fixed_clean); return (match == total && fixed_clean == 18); } static void fm_full_chain(void) { int out, rc; /* the privilege gate: O_RDONLY-only fd -> EACCES (ufm.c:304-306) */ out = 0x12345678; rc = fm_prod_ioctl(FM_GET_STAT, &out, FREAD); kprintf("UFM5578: gate fflags=FREAD : rc=%d (%s) " "out=0x%08x\n", rc, rc == EACCES ? "EACCES" : "?", out); /* containment: production short/timeout semantics through the REAL * ioctl dispatch (incl. the 250ms settle sleep at ufm.c:286) */ ufm_shim_mode = 0; /* short ZLP -> SHORT_XFER */ out = 0xcc; rc = fm_prod_ioctl(FM_GET_STAT, &out, FREAD | FWRITE); kprintf("UFM5578: FULL FM_GET_STAT short : rc=%d (%s) out=0x%08x " "%s\n", rc, rc == EIO ? "EIO" : "?", out, (rc == EIO && out == 0xcc) ? "-- LEAK BLOCKED (discarded)" : "-- UNEXPECTED"); ufm_shim_mode = 1; /* silence -> TIMEOUT */ out = 0xcc; rc = fm_prod_ioctl(FM_GET_STAT, &out, FREAD | FWRITE); kprintf("UFM5578: FULL FM_GET_STAT silent : rc=%d (%s) out=0x%08x " "%s\n", rc, rc == EIO ? "EIO" : "?", out, (rc == EIO && out == 0xcc) ? "-- LEAK BLOCKED (discarded)" : "-- UNEXPECTED"); /* happy path: full 1-byte DATA stage */ ufm_shim_mode = 2; out = 0xcc; rc = fm_prod_ioctl(FM_GET_STAT, &out, FREAD | FWRITE); kprintf("UFM5578: FULL FM_GET_STAT full : rc=%d out=0x%08x %s\n", rc, out, (rc == 0 && out == ufm_shim_devbyte) ? "-- device byte delivered" : "-- UNEXPECTED"); /* counterfactual through the full dispatch: err==0, nothing copied; * the stale byte (poison and/or usb_pause/tsleep residue) is handed * to the ioctl caller (ufm.c:292). */ ufm_shim_mode = 3; out = 0xcc; fm_poison(0x81, fm_depth); rc = fm_prod_ioctl(FM_GET_STAT, &out, FREAD | FWRITE); kprintf("UFM5578: FULL FM_GET_STAT shortok: rc=%d out=0x%08x %s\n", rc, out, (rc == 0 && (out & 0xff) != 0xcc) ? "-- STALE STACK BYTE REACHES IOCTL CALLER" : "-- UNEXPECTED"); } static void fm_capture(void) { static const int freqs[] = { 100700000, 87500000, 108000000, 0, 2147483647, -2147483647 - 1 }; struct ufm_shim_cap cap[16]; int out, rc, i, n; /* FM_SET_FREQ / FM_GET_FREQ state lifecycle (ufm.c:202-242) */ for (i = 0; i < (int)(sizeof(freqs) / sizeof(freqs[0])); ++i) { int f = freqs[i]; ufm_shim_cap_reset(); ufm_shim_mode = 2; rc = fm_prod_ioctl(FM_SET_FREQ, &f, FREAD | FWRITE); n = ufm_shim_cap_dump(cap, 16); kprintf("UFM5578: SET_FREQ %d Hz -> rc=%d, %d reqs: " "SET_FREQ bReq=%u wVal=0x%04x wIdx=0x%04x ; " "CMD0 bReq=%u wVal=0x%04x wIdx=0x%04x (all bm=0x%02x " "wLen=%u)\n", f, rc, n, cap[0].breq, cap[0].wval, cap[0].widx, cap[1].breq, cap[1].wval, cap[1].widx, cap[0].bm, cap[0].wlen); out = -1; rc = fm_prod_ioctl(FM_GET_FREQ, &out, FREAD | FWRITE); kprintf("UFM5578: GET_FREQ -> rc=%d val=%d (sc_freq==%d " "%s)\n", rc, out, f, out == f ? "OK" : "RACE?"); } /* FM_START / FM_STOP tuples (ufm.c:244-275) */ ufm_shim_cap_reset(); ufm_shim_mode = 2; rc = fm_prod_ioctl(FM_START, &out, FREAD | FWRITE); n = ufm_shim_cap_dump(cap, 16); kprintf("UFM5578: FM_START rc=%d: [0] bReq=%u 0x%04x/0x%04x " "[1] bReq=%u 0x%04x/0x%04x\n", rc, cap[0].breq, cap[0].wval, cap[0].widx, cap[1].breq, cap[1].wval, cap[1].widx); ufm_shim_cap_reset(); rc = fm_prod_ioctl(FM_STOP, &out, FREAD | FWRITE); n = ufm_shim_cap_dump(cap, 16); kprintf("UFM5578: FM_STOP rc=%d: [0] bReq=%u 0x%04x/0x%04x " "[1] bReq=%u 0x%04x/0x%04x\n", rc, cap[0].breq, cap[0].wval, cap[0].widx, cap[1].breq, cap[1].wval, cap[1].widx); kprintf("UFM5578: (expected per ufm.c: START=CMD0 0x00/0xc7 then " "CMD2 0x01/0x00; STOP=CMD0 0x16/0x1c then CMD2 0x00/0x00; " "SET_FREQ then CMD0 0x96/0xb7)\n"); } static int fm_modevent(module_t mod, int type, void *data) { int error = 0; switch (type) { case MOD_LOAD: /* mirror ufm_attach()'s softc setup (ufm.c:140-146) */ memset(&fm_fifo, 0, sizeof(fm_fifo)); fm_fifo.priv_sc0 = &fm_sc; /* usb_fifo_softc() target */ lockinit(&fm_sc.sc_lock, "ufmh", 0, LK_CANRECURSE); kprintf("UFM5578: DF-5578 ufm uninit-read harness: " "production ufm.c compiled in (guarded copy, 1 hunk, " "see prod_guard.diff; sha256 845738f2...)\n"); kprintf("UFM5578: requests seen by shim so far: %d\n", ufm_shim_requests); fm_capture(); fm_full_chain(); fm_calibrate(); fm_matrix(); kprintf("UFM5578: total shim requests: %d\n", ufm_shim_requests); kprintf("UFM5578: done\n"); break; case MOD_UNLOAD: lockuninit(&fm_sc.sc_lock); break; default: error = EOPNOTSUPP; break; } return (error); } static moduledata_t ufm5578_mod = { "ufmunleak", fm_modevent, 0 }; DECLARE_MODULE(ufmunleak, ufm5578_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); MODULE_DEPEND(ufmunleak, usb, 1, 1, 1); |