--- a/services/device/usb/usb_device_handle_impl.cc +++ b/services/device/usb/usb_device_handle_impl.cc @@ -565,9 +565,24 @@ void UsbDeviceHandleImpl::SetConfiguration(int configuration_value, return; } for (Transfer* transfer : transfers_) { transfer->Cancel(); } + // ============================================================================ + // POC RACE WIDENING (Window A): Give the ThreadPool worker time to dequeue + // SetInterfaceAlternateSettingBlocking and reach the blocking + // SetAlternateInterface() Mach IPC at darwin_usb.c:1289 BEFORE we drop the + // InterfaceClaimer refcount. The natural window is: the time between + // (a) UI thread posting to blocking_task_runner_ at line 634, and + // (b) UI thread reaching line 571 below (next Mojo message dispatch). + // On a fast machine the ThreadPool worker may not have been scheduled yet. + // 5ms is well within the 1-10ms SetAlternateInterface block, so by the time + // we proceed, the worker is parked in the Mach IPC and the libusb dev->lock + // is FREE (released at core.c:1635). Our libusb_release_interface() below + // then takes that lock and runs darwin_release_interface() FULLY + // CONCURRENTLY with the parked darwin_set_interface_altsetting(). + // ============================================================================ + usleep(5000); claimed_interfaces_.clear(); blocking_task_runner_->PostTask( --- a/third_party/libusb/src/libusb/os/darwin_usb.c +++ b/third_party/libusb/src/libusb/os/darwin_usb.c @@ -1267,9 +1267,22 @@ static int darwin_release_interface(struct libusb_device_handle *dev_handle, int if (kresult) usbi_warn (HANDLE_CTX (dev_handle), "USBInterfaceClose: %s", darwin_error_str(kresult)); kresult = (*(cInterface->interface))->Release(cInterface->interface); if (kresult != kIOReturnSuccess) usbi_warn (HANDLE_CTX (dev_handle), "Release: %s", darwin_error_str(kresult)); + /* ========================================================================== + * POC RACE WIDENING (Window B): The IOUSBInterfaceInterface550 proxy was + * just freed by Release() above (refcount 1->0, system malloc heap). + * cInterface->interface STILL HOLDS THE DANGLING POINTER until the store + * on the next executable line. The natural window is ~5-10 instructions. + * + * The concurrent darwin_set_interface_altsetting() call on the ThreadPool + * worker, when it returns from SetAlternateInterface (line 1289), will hit + * get_endpoints (line 1294) -> line 1115 fresh load of cInterface->interface. + * If that load lands during this sleep, it reads the DANGLING value, then + * dereferences it (vtable read) -> indirect call. ASAN heap-use-after-free. + * ========================================================================== */ + usleep(100000); /* 100ms -- larger than any SetAlternateInterface latency */ cInterface->interface = NULL;