libusb: protect devs list with mutex in process_hotplug_event#783
libusb: protect devs list with mutex in process_hotplug_event#783Copilot wants to merge 8 commits intoconnection-callbackfrom
Conversation
…attern Replace broken hidapi_thread_cond_timedwait (which used a fixed absolute timestamp in the past, causing a spin loop) with hidapi_thread_cond_wait using the standard condition variable predicate pattern. - callback_thread: Use cond_wait with proper predicate loop for zero CPU overhead when idle and immediate response on events/shutdown - hid_libusb_hotplug_callback: Protect queue and signal under callback_thread.mutex to prevent lost wakeups - hotplug_thread: Lock mutex before signaling for clean shutdown Agent-Logs-Url: https://github.com/libusb/hidapi/sessions/a47a730b-fbb6-4042-9afb-00e5aa94817d Co-authored-by: Youw <5939659+Youw@users.noreply.github.com>
Agent-Logs-Url: https://github.com/libusb/hidapi/sessions/a47a730b-fbb6-4042-9afb-00e5aa94817d Co-authored-by: Youw <5939659+Youw@users.noreply.github.com>
|
@copilot We used to handle all evens in a queue while holding the lock on a mutex, and now we lock it only when manipulating the queue, but not when handling the event. Is there a benefit in that approach? |
…signal Agent-Logs-Url: https://github.com/libusb/hidapi/sessions/808d88e5-75a9-4fc7-ba46-9cf6042c46d5 Co-authored-by: Youw <5939659+Youw@users.noreply.github.com>
Yes — releasing the lock during event processing avoids two problems:
The tradeoff is that |
|
@copilot I also see that |
…flag Agent-Logs-Url: https://github.com/libusb/hidapi/sessions/4b1a120a-0c8e-45c2-82ce-b7515db6e585 Co-authored-by: Youw <5939659+Youw@users.noreply.github.com>
Done in 3f4721e. |
…ENUMERATE Agent-Logs-Url: https://github.com/libusb/hidapi/sessions/687b4058-4c04-4d5f-8de6-2f38035fd760 Co-authored-by: Youw <5939659+Youw@users.noreply.github.com>
…ess_hotplug_event In process_hotplug_event(), the devs list was modified without holding hid_hotplug_context.mutex. Meanwhile, hid_hotplug_register_callback() iterates devs under that mutex during HID_API_HOTPLUG_ENUMERATE. A device disconnect processed by callback_thread could free a devs entry while the ENUMERATE loop was still traversing it, causing a use-after-free. Fix by wrapping process_hotplug_event() with hid_hotplug_context.mutex. The mutex is recursive, so hid_internal_invoke_callbacks() (which also acquires it) works correctly. This aligns with how the linux backend handles the same scenario. Agent-Logs-Url: https://github.com/libusb/hidapi/sessions/cb1dc652-1f96-457d-8319-55e4a6f6e6f3 Co-authored-by: Youw <5939659+Youw@users.noreply.github.com>
process_hotplug_event()modifiesdevswithout holdinghid_hotplug_context.mutex, whilehid_hotplug_register_callback()iteratesdevsunder that mutex duringHID_API_HOTPLUG_ENUMERATE. A concurrent device disconnect can free a node mid-traversal → use-after-free.The other three backends (linux, windows, mac) already hold their main mutex for the entire event processing block. The libusb backend was the outlier because
callback_threadtreateddevsas exclusively owned, but the ENUMERATE path inregister_callbackviolates that assumption.process_hotplug_event()withpthread_mutex_lock/unlock(&hid_hotplug_context.mutex). The mutex is recursive, so the nested acquire insidehid_internal_invoke_callbacks()is safe.devsfield comment to reflect the actual synchronization contract.🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.