| 0/0 |
2026/05/18 15:10 |
flow |
assessment-security |
59m
Results: map[DenialOfService:true Explanation:**Analysis of the Bug**
The bug is a Use-After-Free (UAF) vulnerability in the USB HID device driver
(`hiddev`), specifically involving a race condition between device disconnection
(`hiddev_disconnect`) and file descriptor closure (`hiddev_release`).
1. **Root Cause**:
When a USB HID device is unplugged, the kernel invokes `hiddev_disconnect`. This
function locks the `hiddev->existancelock` mutex, marks the device as
non-existent (`hiddev->exist = 0`), and if the device is currently open by a
userspace process, it unlocks the mutex and returns.
Concurrently, if the userspace process closes the device, `hiddev_release` is
invoked. It locks the same `existancelock` mutex, decrements the open count, and
if the count reaches 0 and the device no longer exists, it unlocks the mutex and
frees the `hiddev` structure via `kfree()`.
The race occurs because `hiddev_disconnect` can be preempted or delayed inside
the slowpath of `mutex_unlock()`. While it is still technically inside the
unlock routine (trying to acquire the internal `wait_lock` of the mutex to wake
up waiters), `hiddev_release` can wake up, acquire the mutex, finish its
execution, and free the `hiddev` object. When `hiddev_disconnect` resumes, it
accesses the `wait_lock` of the now-freed mutex, leading to a UAF.
2. **Exploitability**:
The UAF occurs on a spinlock (`wait_lock`) embedded within the freed object.
When the kernel attempts to lock or unlock this spinlock, it will write to the
freed memory. If an attacker can reallocate this memory with a controlled object
before the spinlock operation occurs, they can corrupt the new object. This type
of memory corruption is a classic primitive that can be leveraged for local
privilege escalation (LPE). Thus, it is highly exploitable.
3. **Trigger Vectors**:
- **Peripheral Trigger**: Yes. A local attacker with physical access can plug in
a malicious USB device. The device can spoof its descriptors to appear as a
device that a root daemon (such as `upowerd` for UPS devices or `nut`) will
automatically open. The malicious USB device can then simulate a sudden
disconnect (dropping off the bus) to trigger `hiddev_disconnect`, while the
daemon concurrently closes the device upon receiving a read error, perfectly
aligning the race condition.
- **Unprivileged / User Namespaces**: No. By default, `/dev/usb/hiddevX` nodes
are restricted to `root` (0600 permissions). An unprivileged user cannot open
the device to set up the race condition. Furthermore, triggering the disconnect
requires either physical access to unplug the device or root privileges to
unbind the driver via sysfs or use USB gadget/dummy subsystems. User namespaces
do not grant access to physical device nodes or driver unbinding.
- **Remote / Network / Filesystem / VM**: No. The bug is strictly tied to the
USB HID subsystem and requires local USB device state manipulation.
**Conclusion**
This is a severe memory corruption vulnerability (UAF) that can lead to Denial
of Service (kernel crash) or Privilege Escalation. However, it is constrained to
local attackers who can plug in malicious USB devices (Peripheral Trigger) or
users who already possess root privileges. Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/05/18 15:10 |
action |
syz-repro-to-c-repro |
0m
Results:
|
| 2/1 |
2026/05/18 15:10 |
action |
kernel-checkouter |
1m
Results: map[KernelSrc:/app/workdir/cache/src/b407e36884e568ba3a530d7884859f9ca6443b7c]
|
| 3/1 |
2026/05/18 15:11 |
action |
kernel-builder |
28m
Results: map[KernelObj:/app/workdir/cache/build/feed80300518ec1735ff11088d24dc5f9be7bf88]
|
| 4/1 |
2026/05/18 15:40 |
action |
codesearch-prepare |
12m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/05/18 15:53 |
agent |
expert |
16m
Model:
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Instruction:
You are an experienced Linux kernel security engineer. Your task is to analyze given kernel bug report
and determine its security impact based on the following dimensions.
Use the provided tools to examine the source code, check for capability checks (e.g., capable(), ns_capable()),
and understand the nature of the bug. Analyze the given kernel build and configuration.
You can check the kernel config by grepping ".config" file; you can check kernel cmdline by greeping
".config" file for "CONFIG_CMDLINE=". Assume sysctl parameters have default values.
But analyze for the corresponding production build w/o debugging tools enabled (like KASAN, KMSAN, UBSAN).
Don't make assumptions; verify them with source code access. Try different strategies when analyzing the bug:
- think of ways in which the vulnerable code is unreachable
- or the other way around: try to come up with different ideas of how an unprivileged user can reach the bug
If still unsure err on the side of the bug being non-exploitable/not-accessible.
In the final reply, provide a reasoning for your assessment.
Analysis dimensions:
* Exploitable:
Determine if the bug can result in memory corruption or elevated privileges.
Memory safety issues are almost always exploitable (KASAN or UBSAN reports for use-after-free, out-of-bounds;
refcounting issues, corrupted lists, etc). When kernel is crashing on a completly wild pointer access
(e.g. user-space address, or non-canonical address, but not on NULL or address corresponding to KASAN shadow
for NULL address), including both data accesses and control tranfers, that's also usually implies possibility
of exploitation. Such reports usually say "unable to handle kernel paging request".
Uses of uninitialized values detected by KMSAN may be exploitable b/c attacker frequently can affect uninit
values with spraying techniques. However, for these exploitabability depends on how exactly the uninit value
is used in the code, and what it affects.
Think of what happens after the bug is triggered. Some bugs cause kernel panic and halt execution,
they are harder to exploit. For example, BUG reports halts the kernel. However, WARNING reports don't halt
execution in production builds. Debug bug detection tools (like KASAN, KMSAN, KCSAN, UBSAN) are also not enabled
in production builds, so attacker can freely exploit these bugs w/o being detected by these tools.
If you see an integer overflow, think how the overflowed value used later (if it's used as allocation size,
or an array index). If you see an out-of-bounds read, think if it's followed by an out-of-bounds write as well.
Some KCSAN data-races may be exploitable by skilled attackers as well. Think what data structures got corrupted
as the result of data races and how. However, note that kernel has lots of "benign" data races that don't lead
to any runtime misbehavior at all.
* Denial Of Service:
Determine if the bug can result in denial-of-service. Most bugs can, since they cause system crash,
hangs, deadlocks, or resource leaks. This is mostly applicable to WARNING bugs that won't cause system crash
in production. For these think what will be consequences of the violation of the kernel assumptions flagged
by the WARNING. In some cases the unexpected condition is also properly handled by the normal control flow
(e.g. with "if (WARN_ON(...))"), these won't cause denial-of-service. If the condition is not handled,
then it may or may not cause denial-of-service.
* Accessible From Unprivileged Processes:
Determine if the bug can be reached from a typical (non-root) user process that does NOT have any special capabilities
(like CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON) or access to device nodes restricted to root.
Assume that unprivileged_bpf_disabled=1, that is eBPF loading is not accessible. However, cBPF (classical BPF)
is still accessible to non-root processes.
Assume that user namespaces are not accessible, that is, the process cannot get the mentioned capabilities even
within a new user namespace (checked by ns_capable() function in the kernel sources).
* Accessible From User Namespaces:
Determine if the bug can be reached within a user-namespace where the process has all capabilities
(including CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON). Such capabilities are checked with ns_capable()
function in the kernel sources.
* VM Guest Trigger:
Determine if the bug can be triggered from the context of a typical KVM guest (e.g., set up by a QEMU VMM).
Consider accesses to standard Linux host paravirtualized features (virtio-blk, virtio-net, etc.),
and handling of VM exits in the KVM code.
* VM Host Trigger in The Confidetial Computing Context:
Determine if the bug can be triggered in a confidential computing guest kernel from the context of a KVM host.
Consider access to standard Linux guest paravirtualized features (virtio-blk, virtio-net, etc.).
* Ethernet Network Trigger:
Determine if the bug can be triggered by processing ingress network Ethernet traffic, either directly (network stack)
or via drivers exposed to network data.
* Other Remote Trigger:
Determine if the bug can be triggered by processing remote traffic other than Ethernet (Wifi, Bluetooth, NFC, etc).
* Peripheral Trigger:
Determine if the bug can be triggered via an untrusted peripheral device that can be physically plugged
into a system, such as a USB device or a niche hardware driver handling external hardware inputs.
This is particularly important for mobile and desktop environments where users can plug in unknown devices.
* Malicious Filesystem Trigger:
Determine if the bug can be triggered by the kernel mounting and parsing a malicious filesystem image.
This is highly critical for Desktop and Mobile environments where external media or downloaded images
might be auto-mounted.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt:
The kernel bug report is:
usb 6-1: USB disconnect, device number 25
==================================================================
BUG: KASAN: slab-use-after-free in debug_spin_lock_before kernel/locking/spinlock_debug.c:86 [inline]
BUG: KASAN: slab-use-after-free in do_raw_spin_lock+0x23b/0x260 kernel/locking/spinlock_debug.c:115
Read of size 4 at addr ffff8880289f981c by task kworker/1:3/6000
CPU: 1 UID: 0 PID: 6000 Comm: kworker/1:3 Tainted: G L syzkaller #0 PREEMPT(full)
Tainted: [L]=SOFTLOCKUP
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Workqueue: usb_hub_wq hub_event
Call Trace:
<TASK>
__dump_stack lib/dump_stack.c:94 [inline]
dump_stack_lvl+0x100/0x190 lib/dump_stack.c:120
print_address_description mm/kasan/report.c:378 [inline]
print_report+0x156/0x4c9 mm/kasan/report.c:482
kasan_report+0xdf/0x1e0 mm/kasan/report.c:595
debug_spin_lock_before kernel/locking/spinlock_debug.c:86 [inline]
do_raw_spin_lock+0x23b/0x260 kernel/locking/spinlock_debug.c:115
__raw_spin_lock_irqsave include/linux/spinlock_api_smp.h:133 [inline]
_raw_spin_lock_irqsave+0x42/0x60 kernel/locking/spinlock.c:162
__mutex_unlock_slowpath+0x18b/0x790 kernel/locking/mutex.c:963
hiddev_disconnect+0x15b/0x1d0 drivers/hid/usbhid/hiddev.c:940
hid_disconnect+0xbe/0x1b0 drivers/hid/hid-core.c:2353
hid_hw_stop drivers/hid/hid-core.c:2400 [inline]
hid_device_remove+0x1b2/0x270 drivers/hid/hid-core.c:2834
device_remove+0xcb/0x180 drivers/base/dd.c:631
__device_release_driver drivers/base/dd.c:1344 [inline]
device_release_driver_internal+0x44e/0x620 drivers/base/dd.c:1367
bus_remove_device+0x2bc/0x560 drivers/base/bus.c:657
device_del+0x376/0x9b0 drivers/base/core.c:3880
hid_remove_device drivers/hid/hid-core.c:3009 [inline]
hid_destroy_device+0x19c/0x240 drivers/hid/hid-core.c:3031
usbhid_disconnect+0xa0/0xe0 drivers/hid/usbhid/hid-core.c:1477
usb_unbind_interface+0x1dd/0x9e0 drivers/usb/core/driver.c:458
device_remove drivers/base/dd.c:633 [inline]
device_remove+0x12a/0x180 drivers/base/dd.c:625
__device_release_driver drivers/base/dd.c:1344 [inline]
device_release_driver_internal+0x44e/0x620 drivers/base/dd.c:1367
bus_remove_device+0x2bc/0x560 drivers/base/bus.c:657
device_del+0x376/0x9b0 drivers/base/core.c:3880
usb_disable_device+0x367/0x810 drivers/usb/core/message.c:1476
usb_disconnect+0x2e2/0x9a0 drivers/usb/core/hub.c:2345
hub_port_connect drivers/usb/core/hub.c:5407 [inline]
hub_port_connect_change drivers/usb/core/hub.c:5707 [inline]
port_event drivers/usb/core/hub.c:5871 [inline]
hub_event+0x1d0c/0x4af0 drivers/usb/core/hub.c:5953
process_one_work+0xa23/0x19a0 kernel/workqueue.c:3276
process_scheduled_works kernel/workqueue.c:3359 [inline]
worker_thread+0x5ef/0xe50 kernel/workqueue.c:3440
kthread+0x370/0x450 kernel/kthread.c:436
ret_from_fork+0x754/0xd80 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
Allocated by task 24:
kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
kasan_save_track+0x14/0x30 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0xaa/0xb0 mm/kasan/common.c:415
kmalloc_noprof include/linux/slab.h:950 [inline]
kzalloc_noprof include/linux/slab.h:1188 [inline]
hiddev_connect+0x259/0x5d0 drivers/hid/usbhid/hiddev.c:893
hid_connect+0x241/0x1750 drivers/hid/hid-core.c:2267
hid_hw_start+0xaa/0x140 drivers/hid/hid-core.c:2380
plantronics_probe+0x2b9/0x3a0 drivers/hid/hid-plantronics.c:209
__hid_device_probe drivers/hid/hid-core.c:2776 [inline]
hid_device_probe+0x50e/0x800 drivers/hid/hid-core.c:2813
call_driver_probe drivers/base/dd.c:643 [inline]
really_probe+0x241/0xa60 drivers/base/dd.c:721
__driver_probe_device+0x1de/0x400 drivers/base/dd.c:863
driver_probe_device+0x4c/0x1b0 drivers/base/dd.c:893
__device_attach_driver+0x1df/0x340 drivers/base/dd.c:1021
bus_for_each_drv+0x159/0x1e0 drivers/base/bus.c:500
__device_attach+0x1e4/0x4d0 drivers/base/dd.c:1093
device_initial_probe+0xaf/0xd0 drivers/base/dd.c:1148
bus_probe_device+0x64/0x160 drivers/base/bus.c:613
device_add+0x11d9/0x1950 drivers/base/core.c:3691
hid_add_device+0x2bf/0x440 drivers/hid/hid-core.c:2952
usbhid_probe+0xd57/0x1350 drivers/hid/usbhid/hid-core.c:1450
usb_probe_interface+0x303/0x8f0 drivers/usb/core/driver.c:396
call_driver_probe drivers/base/dd.c:643 [inline]
really_probe+0x241/0xa60 drivers/base/dd.c:721
__driver_probe_device+0x1de/0x400 drivers/base/dd.c:863
driver_probe_device+0x4c/0x1b0 drivers/base/dd.c:893
__device_attach_driver+0x1df/0x340 drivers/base/dd.c:1021
bus_for_each_drv+0x159/0x1e0 drivers/base/bus.c:500
__device_attach+0x1e4/0x4d0 drivers/base/dd.c:1093
device_initial_probe+0xaf/0xd0 drivers/base/dd.c:1148
bus_probe_device+0x64/0x160 drivers/base/bus.c:613
device_add+0x11d9/0x1950 drivers/base/core.c:3691
usb_set_configuration+0xd97/0x1c60 drivers/usb/core/message.c:2266
usb_generic_driver_probe+0xa1/0xe0 drivers/usb/core/generic.c:250
usb_probe_device+0xef/0x400 drivers/usb/core/driver.c:291
call_driver_probe drivers/base/dd.c:643 [inline]
really_probe+0x241/0xa60 drivers/base/dd.c:721
__driver_probe_device+0x1de/0x400 drivers/base/dd.c:863
driver_probe_device+0x4c/0x1b0 drivers/base/dd.c:893
__device_attach_driver+0x1df/0x340 drivers/base/dd.c:1021
bus_for_each_drv+0x159/0x1e0 drivers/base/bus.c:500
__device_attach+0x1e4/0x4d0 drivers/base/dd.c:1093
device_initial_probe+0xaf/0xd0 drivers/base/dd.c:1148
bus_probe_device+0x64/0x160 drivers/base/bus.c:613
device_add+0x11d9/0x1950 drivers/base/core.c:3691
usb_new_device.cold+0x685/0x115c drivers/usb/core/hub.c:2695
hub_port_connect drivers/usb/core/hub.c:5567 [inline]
hub_port_connect_change drivers/usb/core/hub.c:5707 [inline]
port_event drivers/usb/core/hub.c:5871 [inline]
hub_event+0x314d/0x4af0 drivers/usb/core/hub.c:5953
process_one_work+0xa23/0x19a0 kernel/workqueue.c:3276
process_scheduled_works kernel/workqueue.c:3359 [inline]
worker_thread+0x5ef/0xe50 kernel/workqueue.c:3440
kthread+0x370/0x450 kernel/kthread.c:436
ret_from_fork+0x754/0xd80 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
Freed by task 11803:
kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
kasan_save_track+0x14/0x30 mm/kasan/common.c:78
kasan_save_free_info+0x3b/0x70 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5f/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2685 [inline]
slab_free mm/slub.c:6165 [inline]
kfree+0x1f6/0x6b0 mm/slub.c:6483
hiddev_release+0x40d/0x520 drivers/hid/usbhid/hiddev.c:232
__fput+0x3ff/0xb40 fs/file_table.c:469
task_work_run+0x150/0x240 kernel/task_work.c:233
resume_user_mode_work include/linux/resume_user_mode.h:50 [inline]
__exit_to_user_mode_loop kernel/entry/common.c:67 [inline]
exit_to_user_mode_loop+0x100/0x4a0 kernel/entry/common.c:98
__exit_to_user_mode_prepare include/linux/irq-entry-common.h:226 [inline]
syscall_exit_to_user_mode_prepare include/linux/irq-entry-common.h:256 [inline]
syscall_exit_to_user_mode include/linux/entry-common.h:325 [inline]
do_syscall_64+0x67c/0xf80 arch/x86/entry/syscall_64.c:100
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff8880289f9800
which belongs to the cache kmalloc-512 of size 512
The buggy address is located 28 bytes inside of
freed 512-byte region [ffff8880289f9800, ffff8880289f9a00)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0xffff8880289f8c00 pfn:0x289f8
head: order:2 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0xfff00000000240(workingset|head|node=0|zone=1|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 00fff00000000240 ffff88801b842c80 ffffea0000b3f710 ffffea00015f9410
raw: ffff8880289f8c00 0000000800100008 00000000f5000000 0000000000000000
head: 00fff00000000240 ffff88801b842c80 ffffea0000b3f710 ffffea00015f9410
head: ffff8880289f8c00 0000000800100008 00000000f5000000 0000000000000000
head: 00fff00000000002 ffffea0000a27e01 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000004
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 2, migratetype Unmovable, gfp_mask 0x1d20c0(__GFP_IO|__GFP_FS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC|__GFP_HARDWALL), pid 10245, tgid 10245 (udevd), ts 189371739713, free_ts 189294134261
set_page_owner include/linux/page_owner.h:32 [inline]
post_alloc_hook+0x153/0x170 mm/page_alloc.c:1889
prep_new_page mm/page_alloc.c:1897 [inline]
get_page_from_freelist+0x111d/0x3140 mm/page_alloc.c:3962
__alloc_frozen_pages_noprof+0x27c/0x2ba0 mm/page_alloc.c:5250
alloc_slab_page mm/slub.c:3292 [inline]
allocate_slab mm/slub.c:3481 [inline]
new_slab+0xa6/0x6b0 mm/slub.c:3539
refill_objects+0x26b/0x400 mm/slub.c:7175
refill_sheaf mm/slub.c:2812 [inline]
__pcs_replace_empty_main+0x1ab/0x660 mm/slub.c:4615
alloc_from_pcs mm/slub.c:4717 [inline]
slab_alloc_node mm/slub.c:4851 [inline]
__kmalloc_cache_noprof+0x493/0x6f0 mm/slub.c:5375
kmalloc_noprof include/linux/slab.h:950 [inline]
kzalloc_noprof include/linux/slab.h:1188 [inline]
kernfs_fop_open+0x23d/0xd50 fs/kernfs/file.c:641
do_dentry_open+0x6d8/0x1660 fs/open.c:949
vfs_open+0x82/0x3f0 fs/open.c:1081
do_open fs/namei.c:4671 [inline]
path_openat+0x208c/0x31a0 fs/namei.c:4830
do_file_open+0x20e/0x430 fs/namei.c:4859
do_sys_openat2+0x10d/0x1e0 fs/open.c:1366
do_sys_open fs/open.c:1372 [inline]
__do_sys_openat fs/open.c:1388 [inline]
__se_sys_openat fs/open.c:1383 [inline]
__x64_sys_openat+0x12d/0x210 fs/open.c:1383
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x106/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 11125 tgid 11125 stack trace:
reset_page_owner include/linux/page_owner.h:25 [inline]
__free_pages_prepare mm/page_alloc.c:1433 [inline]
__free_frozen_pages+0x7e1/0x10d0 mm/page_alloc.c:2978
stack_depot_save_flags+0x435/0x9d0 lib/stackdepot.c:735
kasan_save_stack+0x3f/0x50 mm/kasan/common.c:58
kasan_save_track+0x14/0x30 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0xaa/0xb0 mm/kasan/common.c:415
kmalloc_noprof include/linux/slab.h:950 [inline]
kzalloc_noprof include/linux/slab.h:1188 [inline]
ida_alloc_range+0x464/0x830 lib/idr.c:420
ida_alloc include/linux/idr.h:293 [inline]
create_worker+0x99/0x750 kernel/workqueue.c:2796
maybe_create_worker kernel/workqueue.c:3075 [inline]
manage_workers kernel/workqueue.c:3152 [inline]
worker_thread+0x8e2/0xe50 kernel/workqueue.c:3415
kthread+0x370/0x450 kernel/kthread.c:436
ret_from_fork+0x754/0xd80 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
Memory state around the buggy address:
ffff8880289f9700: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
ffff8880289f9780: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
>ffff8880289f9800: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff8880289f9880: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff8880289f9900: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
Reply: **Analysis of the Bug**
The bug is a Use-After-Free (UAF) vulnerability in the USB HID device driver (`hiddev`), specifically involving a race condition between device disconnection (`hiddev_disconnect`) and file descriptor closure (`hiddev_release`).
1. **Root Cause**:
When a USB HID device is unplugged, the kernel invokes `hiddev_disconnect`. This function locks the `hiddev->existancelock` mutex, marks the device as non-existent (`hiddev->exist = 0`), and if the device is currently open by a userspace process, it unlocks the mutex and returns.
Concurrently, if the userspace process closes the device, `hiddev_release` is invoked. It locks the same `existancelock` mutex, decrements the open count, and if the count reaches 0 and the device no longer exists, it unlocks the mutex and frees the `hiddev` structure via `kfree()`.
The race occurs because `hiddev_disconnect` can be preempted or delayed inside the slowpath of `mutex_unlock()`. While it is still technically inside the unlock routine (trying to acquire the internal `wait_lock` of the mutex to wake up waiters), `hiddev_release` can wake up, acquire the mutex, finish its execution, and free the `hiddev` object. When `hiddev_disconnect` resumes, it accesses the `wait_lock` of the now-freed mutex, leading to a UAF.
2. **Exploitability**:
The UAF occurs on a spinlock (`wait_lock`) embedded within the freed object. When the kernel attempts to lock or unlock this spinlock, it will write to the freed memory. If an attacker can reallocate this memory with a controlled object before the spinlock operation occurs, they can corrupt the new object. This type of memory corruption is a classic primitive that can be leveraged for local privilege escalation (LPE). Thus, it is highly exploitable.
3. **Trigger Vectors**:
- **Peripheral Trigger**: Yes. A local attacker with physical access can plug in a malicious USB device. The device can spoof its descriptors to appear as a device that a root daemon (such as `upowerd` for UPS devices or `nut`) will automatically open. The malicious USB device can then simulate a sudden disconnect (dropping off the bus) to trigger `hiddev_disconnect`, while the daemon concurrently closes the device upon receiving a read error, perfectly aligning the race condition.
- **Unprivileged / User Namespaces**: No. By default, `/dev/usb/hiddevX` nodes are restricted to `root` (0600 permissions). An unprivileged user cannot open the device to set up the race condition. Furthermore, triggering the disconnect requires either physical access to unplug the device or root privileges to unbind the driver via sysfs or use USB gadget/dummy subsystems. User namespaces do not grant access to physical device nodes or driver unbinding.
- **Remote / Network / Filesystem / VM**: No. The bug is strictly tied to the USB HID subsystem and requires local USB device state manipulation.
**Conclusion**
This is a severe memory corruption vulnerability (UAF) that can lead to Denial of Service (kernel crash) or Privilege Escalation. However, it is constrained to local attackers who can plug in malicious USB devices (Peripheral Trigger) or users who already possess root privileges.
|
| 6/2 |
2026/05/18 15:53 |
llm |
expert |
0m
Model:
Tokens:
input: 10167
output: 44
thoughts: 455
Thoughts: **Analyzing a KASAN Slab-Use-After-Free Report**
Okay, I'm looking at a KASAN slab-use-after-free report. The crash is happening within the spinlock acquisition path. Specifically, it goes from `debug_spin_lock_before` down through `do_raw_spin_lock`, `__raw_spin_lock_irqsave`, `_raw_spin_lock_irqsave`, and then to `__mutex_unlock_slowpath`. Now, that's interesting because `__mutex_unlock_slowpath` is called from `hiddev_disconnect`. My immediate thought is, let's take a closer look at `hiddev_disconnect`.
The call trace confirms that `hiddev_disconnect` is the culprit, ultimately called through `hid_disconnect`, `hid_device_remove`, and all the supporting infrastructure.
The report also details that the memory was freed by `hiddev_release`, specifically that function, and then by `__fput`.
Conversely, the allocation seems to have been made by `hiddev_connect`, and its call chain which includes `hid_connect`, `hid_hw_start`, and the `plantronics_probe` function. This points towards a connection between these functions in `drivers/hid/usbhid/hiddev.c` and a specific USB HID device, likely a Plantronics device.
Therefore, the next step is to examine the code in `drivers/hid/usbhid/hiddev.c` thoroughly, specifically the interaction between `hiddev_connect`, `hiddev_disconnect`, and `hiddev_release`. It looks like there's a use-after-free scenario within the HID device disconnection and release sequence, potentially related to the Plantronics probe.
|
| 7/2 |
2026/05/18 15:53 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/hid/usbhid/hiddev.c IncludeLines:true Name:hiddev_disconnect]
Results: map[SourceCode: 927: void hiddev_disconnect(struct hid_device *hid)
928: {
929: struct hiddev *hiddev = hid->hiddev;
930: struct usbhid_device *usbhid = hid->driver_data;
931:
932: usb_deregister_dev(usbhid->intf, &hiddev_class);
933:
934: mutex_lock(&hiddev->existancelock);
935: hiddev->exist = 0;
936:
937: if (hiddev->open) {
938: hid_hw_close(hiddev->hid);
939: wake_up_interruptible(&hiddev->wait);
940: mutex_unlock(&hiddev->existancelock);
941: } else {
942: mutex_unlock(&hiddev->existancelock);
943: kfree(hiddev);
944: }
945: }
SourceFile:drivers/hid/usbhid/hiddev.c]
|
| 8/2 |
2026/05/18 15:53 |
llm |
expert |
0m
Model:
Tokens:
input: 11356
output: 44
thoughts: 92
Thoughts: Okay, let me think this through.
**Debugging a Kernel Crash**
Alright, the stack trace points to line 940 in `hiddev_disconnect`, specifically the `mutex_unlock(&hiddev->existancelock)` call. That's a classic sign of trying to access a freed object, isn't it? Let's trace this back. The crash occurs in `hiddev_disconnect`. So, that `mutex_unlock` happens *if* `hiddev->open` is true. Hmm, that suggests a race condition or a use-after-free scenario. The real problem is likely the object itself.
Now, where could `hiddev` have been freed? My intuition says `hiddev_release` is the key. Let's dive into that function and see what it does. I need to understand the lifecycle of this `hiddev` object.
|
| 9/2 |
2026/05/18 15:54 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/hid/usbhid/hiddev.c IncludeLines:true Name:hiddev_release]
Results: map[SourceCode: 213: /*
214: * release file op
215: */
216: static int hiddev_release(struct inode * inode, struct file * file)
217: {
218: struct hiddev_list *list = file->private_data;
219: unsigned long flags;
220:
221: spin_lock_irqsave(&list->hiddev->list_lock, flags);
222: list_del(&list->node);
223: spin_unlock_irqrestore(&list->hiddev->list_lock, flags);
224:
225: mutex_lock(&list->hiddev->existancelock);
226: if (!--list->hiddev->open) {
227: if (list->hiddev->exist) {
228: hid_hw_close(list->hiddev->hid);
229: hid_hw_power(list->hiddev->hid, PM_HINT_NORMAL);
230: } else {
231: mutex_unlock(&list->hiddev->existancelock);
232: kfree(list->hiddev);
233: vfree(list);
234: return 0;
235: }
236: }
237:
238: mutex_unlock(&list->hiddev->existancelock);
239: vfree(list);
240:
241: return 0;
242: }
SourceFile:drivers/hid/usbhid/hiddev.c]
|
| 10/2 |
2026/05/18 15:54 |
llm |
expert |
0m
Model:
Tokens:
input: 12138
output: 43
thoughts: 813
Thoughts: **Analyzing a Potential Race Condition in HID Device Disconnection**
Okay, here's what's running through my head. I'm looking at `hiddev_release` and `hiddev_disconnect`, specifically focusing on the interaction with `hiddev->existancelock`. The critical point is if `list->hiddev->exist` is false, in `hiddev_release`, we unlock the mutex, free `list->hiddev`, and return. `hiddev_disconnect` locks the same `existancelock`, sets `hiddev->exist = 0`. If the device is open, it also unlocks the `existancelock`.
I see a potential race here. Let's say both functions are running concurrently.
Suppose `hiddev->open` is 1.
Thread 1, running `hiddev_disconnect`, locks the `existancelock`, sets `hiddev->exist = 0`, and the `if (hiddev->open)` branch is taken.
Thread 2, the `hiddev_release` function, is now waiting for the lock on the `existancelock`. Thread 1 now calls `mutex_unlock(&hiddev->existancelock);`.
The concern is if Thread 2 gets the lock immediately after Thread 1 unlocks it and proceeds to free the memory that contains the mutex. Then, Thread 1, still inside the `mutex_unlock` function, may attempt to access freed memory. That seems like a recipe for disaster. But, wait, `mutex_unlock` itself shouldn't access the memory *after* unlocking...
Okay, let's look at the crash trace. Ah, the trace shows that the crash is in `__mutex_unlock_slowpath`. That changes things. `__mutex_unlock_slowpath` *does* access the lock. So, we're seeing Thread 1 unlock the mutex, and *while* it's in the process of unlocking (specifically in `__mutex_unlock_slowpath`), Thread 2 grabs the lock, frees the mutex, and then Thread 1 hits the now-freed mutex while still in the unlocking process. Bingo! This is the race condition. It looks like Thread 2's freeing of the mutex while Thread 1 is in a critical part of the unlock process is the root cause. This has to be the bug.
|
| 11/2 |
2026/05/18 15:55 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/locking/mutex.c IncludeLines:true Name:__mutex_unlock_slowpath]
Results: map[SourceCode: 928: /*
929: * Release the lock, slowpath:
930: */
931: static noinline void __sched __mutex_unlock_slowpath(struct mutex *lock, unsigned long ip)
932: {
933: struct task_struct *next = NULL;
934: DEFINE_WAKE_Q(wake_q);
935: unsigned long owner;
936: unsigned long flags;
937:
938: mutex_release(&lock->dep_map, ip);
939:
940: /*
941: * Release the lock before (potentially) taking the spinlock such that
942: * other contenders can get on with things ASAP.
943: *
944: * Except when HANDOFF, in that case we must not clear the owner field,
945: * but instead set it to the top waiter.
946: */
947: owner = atomic_long_read(&lock->owner);
948: for (;;) {
949: MUTEX_WARN_ON(__owner_task(owner) != current);
950: MUTEX_WARN_ON(owner & MUTEX_FLAG_PICKUP);
951:
952: if (owner & MUTEX_FLAG_HANDOFF)
953: break;
954:
955: if (atomic_long_try_cmpxchg_release(&lock->owner, &owner, __owner_flags(owner))) {
956: if (owner & MUTEX_FLAG_WAITERS)
957: break;
958:
959: return;
960: }
961: }
962:
963: raw_spin_lock_irqsave(&lock->wait_lock, flags);
964: debug_mutex_unlock(lock);
965: if (!list_empty(&lock->wait_list)) {
966: /* get the first entry from the wait-list: */
967: struct mutex_waiter *waiter =
968: list_first_entry(&lock->wait_list,
969: struct mutex_waiter, list);
970:
971: next = waiter->task;
972:
973: debug_mutex_wake_waiter(lock, waiter);
974: __clear_task_blocked_on(next, lock);
975: wake_q_add(&wake_q, next);
976: }
977:
978: if (owner & MUTEX_FLAG_HANDOFF)
979: __mutex_handoff(lock, next);
980:
981: raw_spin_unlock_irqrestore_wake(&lock->wait_lock, flags, &wake_q);
982: }
SourceFile:kernel/locking/mutex.c]
|
| 12/2 |
2026/05/18 15:55 |
llm |
expert |
1m
Model:
Tokens:
input: 14322
output: 46
thoughts: 1994
Thoughts: **Exploiting a Use-After-Free in the HID Device Driver**
Okay, here's the breakdown of this potential use-after-free vulnerability in the HID device driver, as I see it. It looks like it boils down to a race condition between `hiddev_disconnect` and `hiddev_release`.
The core issue stems from how the mutex `hiddev->existancelock` is used during device disconnect and release operations. In `__mutex_unlock_slowpath`, the code attempts a compare-and-exchange on the `lock->owner` field, and if it succeeds *and* `MUTEX_FLAG_WAITERS` is set, it breaks the loop. This means there's a waiter, but the lock is released. Another thread (let's call it Thread 2), could potentially sneak in, acquire the lock, do its thing, and then free the memory containing the mutex. Meanwhile, Thread 1 continues its execution path, assuming the mutex is still valid, and tries to lock `wait_lock`, which is now invalid.
Specifically, in `hiddev_disconnect`, we have:
1. A thread acquires `hiddev->existancelock`.
2. `hiddev->exist` is set to 0.
3. The check `hiddev->open` is 1
And in `hiddev_release` a separate thread could concurrently:
1. Acquire `hiddev->existancelock` and block due to the previous lock.
2. `list->hiddev->open` becomes 0.
3. `list->hiddev->exist` is also 0.
4. Release the mutex, and free `hiddev`.
Thread 1 then gets to `raw_spin_lock_irqsave(&lock->wait_lock, flags)` and crashes because it's accessing freed memory.
This is a classic use-after-free, where the mutex is freed prematurely. The crux is that one thread (disconnect) is freeing the memory while another (release) is in the middle of unlocking the mutex.
So, is it exploitable? Definitely. This is not just a read, but a *write* use-after-free. `raw_spin_lock_irqsave` will be writing to the spinlock field of the now freed memory. Memory corruption is a much more serious problem, usually leading to full exploitability.
The key question is whether an unprivileged user can trigger this. `hiddev_release` is triggered by `close()`, and `hiddev_disconnect` is triggered when the device is unplugged. Since unprivileged users often do not have direct access to devices like `/dev/usb/hiddevX`, it's not a slam dunk. The file must be opened before being closed, to have the race condition present, and most likely `/dev/usb/hiddevX` would not be accessible.
However, the "peripheral trigger" makes it interesting. If a user unplugs a USB device, that triggers `hiddev_disconnect`. For the race to happen, the device has to be open. A system daemon might automatically open the `hiddev` device (e.g. `upowerd` or `nut`) which would trigger a crash when the device is unplugged, because of the race condition. Therefore, if the system opens the device, and the attacker unplugs it, then it is a viable exploit.
|
| 13/2 |
2026/05/18 15:57 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile:drivers/hid/usbhid/hiddev.c IncludeSnippetLines:0 Name:hiddev_fops]
Results: map[References:<nil> TruncatedOutput:false]
|
| 14/2 |
2026/05/18 15:57 |
llm |
expert |
0m
Model:
Tokens:
input: 17121
output: 45
thoughts: 17
Thoughts: Here's my thought process, summarized as you requested:
**Checking for `hiddev_fops` Registration**
Okay, before I proceed any further with this, I need to make absolutely sure that the `hiddev_fops` structure has been properly registered. I can't just assume it's there; that's a recipe for disaster in kernel land. I need to explicitly verify its registration state. I'll need to double-check my previous code or the relevant initialization routines to confirm whether this crucial file operation structure is actively registered within the appropriate subsystem. A quick check here saves me a massive headache later on if I make an erroneous assumption. I must ensure the infrastructure is solid before building on it.
|
| 15/2 |
2026/05/18 15:57 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/hid/usbhid/hiddev.c IncludeLines:true Name:hiddev_fops]
Results: map[SourceCode: 847: static const struct file_operations hiddev_fops = {
848: .owner = THIS_MODULE,
849: .read = hiddev_read,
850: .write = hiddev_write,
851: .poll = hiddev_poll,
852: .open = hiddev_open,
853: .release = hiddev_release,
854: .unlocked_ioctl = hiddev_ioctl,
855: .fasync = hiddev_fasync,
856: .compat_ioctl = compat_ptr_ioctl,
857: .llseek = noop_llseek,
858: };
SourceFile:drivers/hid/usbhid/hiddev.c]
|
| 16/2 |
2026/05/18 15:57 |
llm |
expert |
0m
Model:
Tokens:
input: 17565
output: 46
thoughts: 15
Thoughts: Here's my thought process, summarized as you requested:
**Investigating `hiddev_fops` Usage**
Okay, so I need to understand the role of `hiddev_fops`. My initial instinct is to track down its points of application. Let's start with a thorough search for where this structure is actually *used* within the codebase. I need to see where it's being referenced. I'm going to look for any instances of that exact identifier – `hiddev_fops` – to get a handle on the function calls. This will help me build a mental map of its context and purpose within the HID (Human Interface Device) subsystem. I'm assuming it's related to the file operations table for HID devices. Finding the calls and locations is the first step!
|
| 17/2 |
2026/05/18 15:58 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile:drivers/hid/usbhid/hiddev.c IncludeSnippetLines:0 Name:hiddev_fops]
Results: map[References:<nil> TruncatedOutput:false]
|
| 18/2 |
2026/05/18 15:58 |
llm |
expert |
0m
Model:
Tokens:
input: 17812
output: 44
thoughts: 12
Thoughts: Okay, here's my thought process as described:
**Identifying a Potential Keyword**
Hmm, where to begin? Okay, let's see... a specific function or class name is probably the best place to start when I am looking for something. The term "hidden class" is too general, it could refer to many things. I need something more specific to focus the search, something that the development team might have used. I should start by looking for something like... `hiddev_class`. That seems like a good starting point.
|
| 19/2 |
2026/05/18 15:58 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/hid/usbhid/hiddev.c IncludeLines:true Name:hiddev_class]
Results: map[SourceCode: 865: static struct usb_class_driver hiddev_class = {
866: .name = "hiddev%d",
867: .devnode = hiddev_devnode,
868: .fops = &hiddev_fops,
869: .minor_base = HIDDEV_MINOR_BASE,
870: };
SourceFile:drivers/hid/usbhid/hiddev.c]
|
| 20/2 |
2026/05/18 15:58 |
llm |
expert |
1m
Model:
Tokens:
input: 18116
output: 43
thoughts: 2526
Thoughts: **Vulnerability Analysis: USB HID Driver - Use-After-Free**
Okay, so this is a USB class driver, specifically the HID (Human Interface Device) driver. The core issue is a Use-After-Free vulnerability related to a mutex lock. Let me walk through my thoughts on this.
The core of the problem lies in the `hiddev_disconnect` and `hiddev_release` functions. Specifically, when `raw_spin_lock_irqsave(&lock->wait_lock, flags)` is called within the mutex unlock *after* the `mutex_unlock` slowpath has conceptually "released" the lock, but before the interrupt-safe spinlock is acquired, on a freed mutex, memory corruption occurs. If the memory is subsequently reallocated to another object before the `raw_spin_lock_irqsave`, the driver will corrupt that new object. This can lead to privilege escalation (if the overwritten memory contains sensitive data or code pointers) or even arbitrary code execution. So yes, it's exploitable.
A Denial of Service (DoS) is also possible because the memory corruption can crash the kernel.
Now, accessibility. Can an unprivileged user trigger this directly? Not easily. The typical path involves opening `/dev/usb/hiddevX`. By default, this device node is created with root permissions. Unless there are specific udev rules or the user is in a special group, they cannot open the device. The prompt specifically assumes unprivileged_bpf_disabled=1 and no user namespaces, so we're assuming restricted access to the device node. Therefore, an unprivileged user cannot.
User namespaces are also out, as they don't grant access to root-owned device nodes in the initial namespace.
Regarding triggering from a VM guest, the standard approach is to evaluate whether a guest can trigger the bug in the *host* kernel. This isn't a paravirtualized feature, so direct access via virtio or VM exits is out. While a guest *could* trigger it within its *own* kernel if it has the `hiddev` driver running, and can plug/unplug virtual devices, that's not what's being asked. A confidential computing host-trigger scenario seems unlikely, requiring precise timing and specific guest behavior that isn't a typical attack surface. So, for the guest trigger, I think the answer is No.
Ethernet network triggers are irrelevant here, this is USB HID.
The real target is a malicious USB peripheral. A malicious USB device can be physically plugged into a system, enumerating as a HID device. A system daemon (like `upowerd` or `nut`, for instance) might automatically open `/dev/usb/hiddevX`. Then the malicious USB device can simulate a disconnect (by dropping off the bus). This triggers `hiddev_disconnect`. Concurrently, the daemon *might* close the device (e.g., because it gets a read error due to the disconnect). This triggers `hiddev_release`. The disconnect and release operations can race, leading to a Use-After-Free. So, a peripheral trigger is definitely a viable scenario, and yes, it's a PeripheralTrigger: Yes.
Malicious filesystem triggers are not applicable here.
Let's double-check the race condition. `hiddev_disconnect` attempts to lock a mutex, sets a flag, calls a close function, wakes up a thread, and *then* unlocks the mutex. Meanwhile, `hiddev_release` attempts to lock the same mutex, decrements a count, checks a flag, calls close function and frees an object. The key is that the mutex unlock in `hiddev_disconnect` is where the race happens. During the slowpath of the unlock, the mutex is conceptually "free" but the code is *still* using the freed mutex object. Between the release of the lock and the interrupt-save lock, Thread 2, which was waiting for the lock, can acquire the lock, complete its section, unlock and *kfree* the `hiddev` object. Then Thread 1 wakes up and attempts to lock `wait_lock` on freed memory. Boom.
The fix would typically involve a reference count (like `kref`) for the `hiddev` object to ensure it isn't freed prematurely, or a design that prevents a race condition on the wait lock. The current `hiddev->open` counter attempts to act as a refcount, but doesn't handle the case where the disconnecting thread is holding a reference.
It looks like the fundamental issue is that `hiddev_disconnect` frees the `hiddev` object if there are no open file descriptors *or* in the race condition with `hiddev_release`. The mutex unlock in `hiddev_disconnect` in the *race* condition is the core.
It'
|
| 21/2 |
2026/05/18 15:59 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/hid/uhid.c IncludeLines:true Name:uhid_char_write]
Results: map[SourceCode: 720: static ssize_t uhid_char_write(struct file *file, const char __user *buffer,
721: size_t count, loff_t *ppos)
722: {
723: struct uhid_device *uhid = file->private_data;
724: int ret;
725: size_t len;
726:
727: /* we need at least the "type" member of uhid_event */
728: if (count < sizeof(__u32))
729: return -EINVAL;
730:
731: ret = mutex_lock_interruptible(&uhid->devlock);
732: if (ret)
733: return ret;
734:
735: memset(&uhid->input_buf, 0, sizeof(uhid->input_buf));
736: len = min(count, sizeof(uhid->input_buf));
737:
738: ret = uhid_event_from_user(buffer, len, &uhid->input_buf);
739: if (ret)
740: goto unlock;
741:
742: switch (uhid->input_buf.type) {
743: case UHID_CREATE:
744: /*
745: * 'struct uhid_create_req' contains a __user pointer which is
746: * copied from, so it's unsafe to allow this with elevated
747: * privileges (e.g. from a setuid binary) or via kernel_write().
748: */
749: if (file->f_cred != current_cred()) {
750: pr_err_once("UHID_CREATE from different security context by process %d (%s), this is not allowed.\n",
751: task_tgid_vnr(current), current->comm);
752: ret = -EACCES;
753: goto unlock;
754: }
755: ret = uhid_dev_create(uhid, &uhid->input_buf);
756: break;
757: case UHID_CREATE2:
758: ret = uhid_dev_create2(uhid, &uhid->input_buf);
759: break;
760: case UHID_DESTROY:
761: ret = uhid_dev_destroy(uhid);
762: break;
763: case UHID_INPUT:
764: ret = uhid_dev_input(uhid, &uhid->input_buf);
765: break;
766: case UHID_INPUT2:
767: ret = uhid_dev_input2(uhid, &uhid->input_buf);
768: break;
769: case UHID_GET_REPORT_REPLY:
770: ret = uhid_dev_get_report_reply(uhid, &uhid->input_buf);
771: break;
772: case UHID_SET_REPORT_REPLY:
773: ret = uhid_dev_set_report_reply(uhid, &uhid->input_buf);
774: break;
775: default:
776: ret = -EOPNOTSUPP;
777: }
778:
779: unlock:
780: mutex_unlock(&uhid->devlock);
781:
782: /* return "count" not "len" to not confuse the caller */
783: return ret ? ret : count;
784: }
SourceFile:drivers/hid/uhid.c]
|
| 22/2 |
2026/05/18 15:59 |
llm |
expert |
0m
Model:
Tokens:
input: 22763
output: 44
thoughts: 163
Thoughts: **My Assessment of Virtual HID Device Creation**
Okay, so the user's asking about creating a virtual HID device, which immediately makes me think about `/dev/uhid`. My initial thought is, "Can they even *use* `/dev/uhid`?" Because, let's be realistic, it's almost always root-owned with restrictive permissions, typically root:root 0600. That's the baseline.
Then, my mind jumps to the common use case - Bluetooth daemons like `bluez`. They need HID access, and they run as root precisely *because* they need to work with devices like `/dev/uhid`. So, again, probably *no* direct access for an unprivileged user.
But then I consider user namespaces. Could they somehow work around the permissions there? No, devices nodes are generally blocked unless they are explicitly chowned or bind mounted, which both would require the initial root permissions. So that is also a dead end.
Okay, let's backtrack a bit. Am I totally on the right track here? What about `hiddev`? Is that only for USB HID devices? I have to look up the documentation on this. My thinking is, let's check `hiddev_connect`. That will be my next step.
|
| 23/2 |
2026/05/18 16:00 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/hid/usbhid/hiddev.c IncludeLines:true Name:hiddev_connect]
Results: map[SourceCode: 872: /*
873: * This is where hid.c calls us to connect a hid device to the hiddev driver
874: */
875: int hiddev_connect(struct hid_device *hid, unsigned int force)
876: {
877: struct hiddev *hiddev;
878: struct usbhid_device *usbhid = hid->driver_data;
879: int retval;
880:
881: if (!force) {
882: unsigned int i;
883: for (i = 0; i < hid->maxcollection; i++)
884: if (hid->collection[i].type ==
885: HID_COLLECTION_APPLICATION &&
886: !IS_INPUT_APPLICATION(hid->collection[i].usage))
887: break;
888:
889: if (i == hid->maxcollection)
890: return -EINVAL;
891: }
892:
893: if (!(hiddev = kzalloc_obj(struct hiddev)))
894: return -ENOMEM;
895:
896: init_waitqueue_head(&hiddev->wait);
897: INIT_LIST_HEAD(&hiddev->list);
898: spin_lock_init(&hiddev->list_lock);
899: mutex_init(&hiddev->existancelock);
900: hid->hiddev = hiddev;
901: hiddev->hid = hid;
902: hiddev->exist = 1;
903: retval = usb_register_dev(usbhid->intf, &hiddev_class);
904: if (retval) {
905: hid_err(hid, "Not able to get a minor for this device\n");
906: hid->hiddev = NULL;
907: kfree(hiddev);
908: return retval;
909: }
910:
911: /*
912: * If HID_QUIRK_NO_INIT_REPORTS is set, make sure we don't initialize
913: * the reports.
914: */
915: hiddev->initialized = hid->quirks & HID_QUIRK_NO_INIT_REPORTS;
916:
917: hiddev->minor = usbhid->intf->minor;
918:
919: return 0;
920: }
SourceFile:drivers/hid/usbhid/hiddev.c]
|
| 24/2 |
2026/05/18 16:00 |
llm |
expert |
0m
Model:
Tokens:
input: 23981
output: 41
thoughts: 184
Thoughts: **Analyzing the HID Device Connection Flow**
Okay, so I'm tracing the HID device connection process. `hiddev_connect` is called by `hid_connect`, specifically within `drivers/hid/hid-core.c`. My initial thought is to follow the code's path. Now, I see something that's making me pause. This `hiddev_connect` function uses `usbhid->intf`. Hmm, that suggests a pretty strong assumption: that the HID device being connected *is* a USB HID device. The code snippet makes that explicit:
```c
struct usbhid_device *usbhid = hid->driver_data;
...
retval = usb_register_dev(usbhid->intf, &hiddev_class);
```
The issue is this: `hid->driver_data` isn't always a `struct usbhid_device`. It's set by the specific transport driver. If we're dealing with an I2C, Bluetooth, or even a virtual "uhid" device, `hid->driver_data` will be a completely different structure, specific to *that* transport. So, calling `usb_register_dev` with the assumption of USB context is bound to lead to a problem.
The key question then is: Is `hid_connect` *exclusively* calling `hiddev_connect` for USB devices? It's essential to understand the branching logic within `hid_connect` to verify if this USB-centric assumption is indeed the case. If not, this could mean that other types of HID devices are being treated as USB. This would be a crucial point to investigate further, since this design choice would indicate a potential vulnerability.
|
| 25/2 |
2026/05/18 16:01 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/hid/hid-core.c IncludeLines:true Name:hid_connect]
Results: map[SourceCode:2237: int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
2238: {
2239: static const char *types[] = { "Device", "Pointer", "Mouse", "Device",
2240: "Joystick", "Gamepad", "Keyboard", "Keypad",
2241: "Multi-Axis Controller"
2242: };
2243: const char *type, *bus;
2244: char buf[64] = "";
2245: unsigned int i;
2246: int len;
2247: int ret;
2248:
2249: ret = hid_bpf_connect_device(hdev);
2250: if (ret)
2251: return ret;
2252:
2253: if (hdev->quirks & HID_QUIRK_HIDDEV_FORCE)
2254: connect_mask |= (HID_CONNECT_HIDDEV_FORCE | HID_CONNECT_HIDDEV);
2255: if (hdev->quirks & HID_QUIRK_HIDINPUT_FORCE)
2256: connect_mask |= HID_CONNECT_HIDINPUT_FORCE;
2257: if (hdev->bus != BUS_USB)
2258: connect_mask &= ~HID_CONNECT_HIDDEV;
2259: if (hid_hiddev(hdev))
2260: connect_mask |= HID_CONNECT_HIDDEV_FORCE;
2261:
2262: if ((connect_mask & HID_CONNECT_HIDINPUT) && !hidinput_connect(hdev,
2263: connect_mask & HID_CONNECT_HIDINPUT_FORCE))
2264: hdev->claimed |= HID_CLAIMED_INPUT;
2265:
2266: if ((connect_mask & HID_CONNECT_HIDDEV) && hdev->hiddev_connect &&
2267: !hdev->hiddev_connect(hdev,
2268: connect_mask & HID_CONNECT_HIDDEV_FORCE))
2269: hdev->claimed |= HID_CLAIMED_HIDDEV;
2270: if ((connect_mask & HID_CONNECT_HIDRAW) && !hidraw_connect(hdev))
2271: hdev->claimed |= HID_CLAIMED_HIDRAW;
2272:
2273: if (connect_mask & HID_CONNECT_DRIVER)
2274: hdev->claimed |= HID_CLAIMED_DRIVER;
2275:
2276: /* Drivers with the ->raw_event callback set are not required to connect
2277: * to any other listener. */
2278: if (!hdev->claimed && !hdev->driver->raw_event) {
2279: hid_err(hdev, "device has no listeners, quitting\n");
2280: return -ENODEV;
2281: }
2282:
2283: hid_process_ordering(hdev);
2284:
2285: if ((hdev->claimed & HID_CLAIMED_INPUT) &&
2286: (connect_mask & HID_CONNECT_FF) && hdev->ff_init)
2287: hdev->ff_init(hdev);
2288:
2289: len = 0;
2290: if (hdev->claimed & HID_CLAIMED_INPUT)
2291: len += sprintf(buf + len, "input");
2292: if (hdev->claimed & HID_CLAIMED_HIDDEV)
2293: len += sprintf(buf + len, "%shiddev%d", len ? "," : "",
2294: ((struct hiddev *)hdev->hiddev)->minor);
2295: if (hdev->claimed & HID_CLAIMED_HIDRAW)
2296: len += sprintf(buf + len, "%shidraw%d", len ? "," : "",
2297: ((struct hidraw *)hdev->hidraw)->minor);
2298:
2299: type = "Device";
2300: for (i = 0; i < hdev->maxcollection; i++) {
2301: struct hid_collection *col = &hdev->collection[i];
2302: if (col->type == HID_COLLECTION_APPLICATION &&
2303: (col->usage & HID_USAGE_PAGE) == HID_UP_GENDESK &&
2304: (col->usage & 0xffff) < ARRAY_SIZE(types)) {
2305: type = types[col->usage & 0xffff];
2306: break;
2307: }
2308: }
2309:
2310: switch (hdev->bus) {
2311: case BUS_USB:
2312: bus = "USB";
2313: break;
2314: case BUS_BLUETOOTH:
2315: bus = "BLUETOOTH";
2316: break;
2317: case BUS_I2C:
2318: bus = "I2C";
2319: break;
2320: case BUS_SDW:
2321: bus = "SOUNDWIRE";
2322: break;
2323: case BUS_VIRTUAL:
2324: bus = "VIRTUAL";
2325: break;
2326: case BUS_INTEL_ISHTP:
2327: case BUS_AMD_SFH:
2328: bus = "SENSOR HUB";
2329: break;
2330: default:
2331: bus = "<UNKNOWN>";
2332: }
2333:
2334: ret = device_create_file(&hdev->dev, &dev_attr_country);
2335: if (ret)
2336: hid_warn(hdev,
2337: "can't create sysfs country code attribute err: %d\n", ret);
2338:
2339: hid_info(hdev, "%s: %s HID v%x.%02x %s [%s] on %s\n",
2340: buf, bus, hdev->version >> 8, hdev->version & 0xff,
2341: type, hdev->name, hdev->phys);
2342:
2343: return 0;
2344: }
SourceFile:drivers/hid/hid-core.c]
|
| 26/2 |
2026/05/18 16:01 |
llm |
expert |
0m
Model:
Tokens:
input: 26337
output: 44
thoughts: 99
Thoughts: **Analyzing HID Device Connection Logic**
Okay, so I'm looking at this `hid_connect` function... Specifically, I'm noting this conditional: `if (hdev->bus != BUS_USB) connect_mask &= ~HID_CONNECT_HIDDEV;`. Right, so this means the `hiddev` interface is *only* enabled if the HID device's bus type is USB. Makes sense, a bit of an optimization probably.
Now, my initial thought is, "Aha! `uhid` can't trigger this bit of the connection code because `uhid` typically uses a `BUS_VIRTUAL` bus type (or whatever the user defines)." But wait a second... This brings up an important question. Can `uhid` actually specify `BUS_USB`? If it *can*, then this code path *could* be relevant for a `uhid` device after all. I need to investigate whether that bus type is settable within the `uhid` framework. That's the key to understanding this particular code's interaction with `uhid`.
|
| 27/2 |
2026/05/18 16:02 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/hid/uhid.c IncludeLines:true Name:uhid_dev_create2]
Results: map[SourceCode: 489: static int uhid_dev_create2(struct uhid_device *uhid,
490: const struct uhid_event *ev)
491: {
492: struct hid_device *hid;
493: size_t rd_size;
494: void *rd_data;
495: int ret;
496:
497: if (uhid->hid)
498: return -EALREADY;
499:
500: rd_size = ev->u.create2.rd_size;
501: if (rd_size <= 0 || rd_size > HID_MAX_DESCRIPTOR_SIZE)
502: return -EINVAL;
503:
504: rd_data = kmemdup(ev->u.create2.rd_data, rd_size, GFP_KERNEL);
505: if (!rd_data)
506: return -ENOMEM;
507:
508: uhid->rd_size = rd_size;
509: uhid->rd_data = rd_data;
510:
511: hid = hid_allocate_device();
512: if (IS_ERR(hid)) {
513: ret = PTR_ERR(hid);
514: goto err_free;
515: }
516:
517: BUILD_BUG_ON(sizeof(hid->name) != sizeof(ev->u.create2.name));
518: strscpy(hid->name, ev->u.create2.name, sizeof(hid->name));
519: BUILD_BUG_ON(sizeof(hid->phys) != sizeof(ev->u.create2.phys));
520: strscpy(hid->phys, ev->u.create2.phys, sizeof(hid->phys));
521: BUILD_BUG_ON(sizeof(hid->uniq) != sizeof(ev->u.create2.uniq));
522: strscpy(hid->uniq, ev->u.create2.uniq, sizeof(hid->uniq));
523:
524: hid->ll_driver = &uhid_hid_driver;
525: hid->bus = ev->u.create2.bus;
526: hid->vendor = ev->u.create2.vendor;
527: hid->product = ev->u.create2.product;
528: hid->version = ev->u.create2.version;
529: hid->country = ev->u.create2.country;
530: hid->driver_data = uhid;
531: hid->dev.parent = uhid_misc.this_device;
532:
533: uhid->hid = hid;
534: uhid->running = true;
535:
536: /* Adding of a HID device is done through a worker, to allow HID drivers
537: * which use feature requests during .probe to work, without they would
538: * be blocked on devlock, which is held by uhid_char_write.
539: */
540: schedule_work(&uhid->worker);
541:
542: return 0;
543:
544: err_free:
545: kfree(uhid->rd_data);
546: uhid->rd_data = NULL;
547: uhid->rd_size = 0;
548: return ret;
549: }
SourceFile:drivers/hid/uhid.c]
|
| 28/2 |
2026/05/18 16:02 |
llm |
expert |
0m
Model:
Tokens:
input: 27726
output: 41
thoughts: 185
Thoughts: **Uh-Oh: USB-Specific Assumptions in HID Connect!**
Okay, so I'm looking at this `uhid` code, and I see this line: `hid->bus = ev->u.create2.bus;`. Aha! So `uhid` *can* specify `BUS_USB`! That's interesting. I know that if `uhid` sets the bus to `BUS_USB`, then `hid_connect` will try to connect a `hiddev` device.
But hang on a second... this is where things get tricky. I remember that the `hiddev_connect` function has a major assumption. It expects the `hid->driver_data` pointer to be pointing to a `struct usbhid_device *`! Let me just quickly grab the relevant bit of the code:
```c
struct usbhid_device *usbhid = hid->driver_data;
...
retval = usb_register_dev(usbhid->intf, &hiddev_class);
```
Right, there it is. Now, if `uhid` *is* used, and the bus is set to `BUS_USB`, then `hid->driver_data` will point to a `struct uhid_device *` instead! Which means that when `hiddev_connect` gets to that line, it's going to try to dereference some garbage pointer in the `uhid` struct as if it were a valid `usbhid_device`, specifically the `usbhid->intf` field. And that's not going to end well. This is going to crash hard, I bet. I need to go look into the `uhid` struct definition now, to see what that field actually represents.
|
| 29/2 |
2026/05/18 16:03 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/hid/uhid.c IncludeLines:true Name:uhid_device]
Results: map[SourceCode: 29: struct uhid_device {
30: struct mutex devlock;
31:
32: /* This flag tracks whether the HID device is usable for commands from
33: * userspace. The flag is already set before hid_add_device(), which
34: * runs in workqueue context, to allow hid_add_device() to communicate
35: * with userspace.
36: * However, if hid_add_device() fails, the flag is cleared without
37: * holding devlock.
38: * We guarantee that if @running changes from true to false while you're
39: * holding @devlock, it's still fine to access @hid.
40: */
41: bool running;
42:
43: __u8 *rd_data;
44: uint rd_size;
45:
46: /* When this is NULL, userspace may use UHID_CREATE/UHID_CREATE2. */
47: struct hid_device *hid;
48: struct uhid_event input_buf;
49:
50: wait_queue_head_t waitq;
51: spinlock_t qlock;
52: __u8 head;
53: __u8 tail;
54: struct uhid_event *outq[UHID_BUFSIZE];
55:
56: /* blocking GET_REPORT support; state changes protected by qlock */
57: struct mutex report_lock;
58: wait_queue_head_t report_wait;
59: bool report_running;
60: u32 report_id;
61: u32 report_type;
62: struct uhid_event report_buf;
63: struct work_struct worker;
64: };
SourceFile:drivers/hid/uhid.c]
|
| 30/2 |
2026/05/18 16:03 |
llm |
expert |
0m
Model:
Tokens:
input: 28853
output: 44
thoughts: 59
Thoughts: Okay, here's my interpretation of that thought process, from an expert's perspective:
**Deconstructing a Potential Type-Punning Bug in `hiddev_connect`**
Alright, I'm looking at this `hiddev_connect` function. It looks like it's taking a `struct uhid_device *` and, hold on, it's *casting* that to a `struct usbhid_device *`! And then, the code goes on to access `intf`. Okay, alarm bells are starting to ring. The intent is to extract the `intf` member, but let's think this through carefully.
The whole point of using a type like `uhid_device` suggests a more generic "user-space HID" device, while `usbhid_device` likely encapsulates details specific to USB HID devices, right?
The concern is this: if the `hiddev_connect` function is trying to treat something that *might* just be a generic `uhid_device` as a `usbhid_device` and then accessing that members as if they are `intf`, this is a classic type-punning hazard. We're effectively assuming a specific memory layout based on `usbhid_device`, but there's no guarantee the `uhid_device` actually *has* the `intf` member at the same offset. We need to check the actual data structure layout.
We need to be certain about how the structures are defined and whether this kind of casting is legitimate, or whether it might lead to reading garbage, or worse, triggering a crash. It needs serious scrutiny!
|
| 31/2 |
2026/05/18 16:03 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/hid/usbhid/usbhid.h IncludeLines:true Name:usbhid_device]
Results: map[SourceCode: 56: struct usbhid_device {
57: struct hid_device *hid; /* pointer to corresponding HID dev */
58:
59: struct usb_interface *intf; /* USB interface */
60: int ifnum; /* USB interface number */
61:
62: unsigned int bufsize; /* URB buffer size */
63:
64: struct urb *urbin; /* Input URB */
65: char *inbuf; /* Input buffer */
66: dma_addr_t inbuf_dma; /* Input buffer dma */
67:
68: struct urb *urbctrl; /* Control URB */
69: struct usb_ctrlrequest *cr; /* Control request struct */
70: struct hid_control_fifo ctrl[HID_CONTROL_FIFO_SIZE]; /* Control fifo */
71: unsigned char ctrlhead, ctrltail; /* Control fifo head & tail */
72: char *ctrlbuf; /* Control buffer */
73: dma_addr_t ctrlbuf_dma; /* Control buffer dma */
74: unsigned long last_ctrl; /* record of last output for timeouts */
75:
76: struct urb *urbout; /* Output URB */
77: struct hid_output_fifo out[HID_CONTROL_FIFO_SIZE]; /* Output pipe fifo */
78: unsigned char outhead, outtail; /* Output pipe fifo head & tail */
79: char *outbuf; /* Output buffer */
80: dma_addr_t outbuf_dma; /* Output buffer dma */
81: unsigned long last_out; /* record of last output for timeouts */
82:
83: struct mutex mutex; /* start/stop/open/close */
84: spinlock_t lock; /* fifo spinlock */
85: unsigned long iofl; /* I/O flags (CTRL_RUNNING, OUT_RUNNING) */
86: struct timer_list io_retry; /* Retry timer */
87: unsigned long stop_retry; /* Time to give up, in jiffies */
88: unsigned int retry_delay; /* Delay length in ms */
89: struct work_struct reset_work; /* Task context for resets */
90: wait_queue_head_t wait; /* For sleeping */
91: };
SourceFile:drivers/hid/usbhid/usbhid.h]
|
| 32/2 |
2026/05/18 16:03 |
llm |
expert |
1m
Model:
Tokens:
input: 29993
output: 45
thoughts: 500
Thoughts: **Analyzing the USB HID Driver Vulnerability**
Okay, let's break this down. My initial thought process revolves around a potential memory overlap within the `usbhid_device` structure. `intf` is the second pointer, which raises a red flag because in the analogous `uhid_device` struct, that second field is something entirely different. Wait a second, the `devlock` mutex comes in at offset 8, and a mutex is a relatively large structure itself, likely at least 32 bytes on a 64-bit system. Given where `intf` sits within the `usbhid_device` struct, `intf` and fields within the `devlock` mutex are overlapping!
This is a problem because if `hiddev_connect` tries to use `usb_register_dev` with `usbhid->intf`, it will immediately crash because it's using garbage, invalid memory. So, my initial thought that `uhid` devices could trigger it is wrong, it crashes way earlier.
Now, my mind shifts to alternate trigger scenarios. How *else* could a `BUS_USB` HID device be created? `hid-vhci` is an option, but that's not standard. Gadgets? No, gadgets are *seen* by the host, not the other way around. If the attacker plugs in a USB device, the host sees a *real* USB device. So, the only way to get this bug to fire seems to be via a real USB device. or a virtual one using USB/IP which requires root.
This all points to Peripheral Trigger being the only viable vector, especially for an unprivileged user, if they have physical access. This lines up with the prompt's analysis.
Now, I'm re-evaluating the race condition. Could an unprivileged user ever get access to `/dev/usb/hiddevX`? By default, these devices are created with root:root permissions and a restrictive 0600 mode. A quick check of `hiddev_devnode` is needed here to rule out any potential vulnerabilities.
|
| 33/2 |
2026/05/18 16:05 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/hid/usbhid/hiddev.c IncludeLines:true Name:hiddev_devnode]
Results: map[SourceCode: 860: static char *hiddev_devnode(const struct device *dev, umode_t *mode)
861: {
862: return kasprintf(GFP_KERNEL, "usb/%s", dev_name(dev));
863: }
SourceFile:drivers/hid/usbhid/hiddev.c]
|
| 34/2 |
2026/05/18 16:05 |
llm |
expert |
0m
Model:
Tokens:
input: 31087
output: 44
thoughts: 255
Thoughts: **Analyzing the hiddev Device and Potential Vulnerabilities**
Okay, so looking at this, it seems the code simply assigns the device name as `usb/hiddevX`. No specific mode is set, so it defaults to `0600` owned by `root:root`. This immediately tells me that unprivileged users are unable to directly access and open the device. That's a standard security practice, but it's important to understand the implications.
Now, a key question pops into my head: can an unprivileged user still somehow trigger a bug related to the device *without* actually opening it? I need to analyze the code path more carefully. Specifically, I'm focusing on what happens in `hiddev_disconnect`.
If `hiddev->open` is 0, the code in `hiddev_disconnect` seems to bypass the standard cleanup: it just calls `kfree(hiddev)`. No call to `hid_hw_close`, no waking up of the interruptible wait queue, nothing. This implies that if `hiddev` is disconnected *before* it's opened, the normal cleanup routine doesn't happen.
Is there a race condition if `hiddev->open` is 0? The key thing here is the concurrency. If `hiddev->open` is 0, `hiddev_release` isn't running, because it's only called when a file descriptor has been closed - a consequence of being open. Could `hiddev_open` potentially be running concurrently? If so, this could be where a vulnerability lies. That's the part to scrutinize. I'll need to examine the `hiddev_open` function and the surrounding context for possible race conditions.
|
| 35/2 |
2026/05/18 16:06 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/hid/usbhid/hiddev.c IncludeLines:true Name:hiddev_open]
Results: map[SourceCode: 284: /*
285: * open file op
286: */
287: static int hiddev_open(struct inode *inode, struct file *file)
288: {
289: struct usb_interface *intf;
290: struct hid_device *hid;
291: struct hiddev *hiddev;
292: int res;
293:
294: intf = usbhid_find_interface(iminor(inode));
295: if (!intf)
296: return -ENODEV;
297:
298: hid = usb_get_intfdata(intf);
299: hiddev = hid->hiddev;
300:
301: mutex_lock(&hiddev->existancelock);
302: res = hiddev->exist ? __hiddev_open(hiddev, file) : -ENODEV;
303: mutex_unlock(&hiddev->existancelock);
304:
305: return res;
306: }
SourceFile:drivers/hid/usbhid/hiddev.c]
|
| 36/2 |
2026/05/18 16:06 |
llm |
expert |
0m
Model:
Tokens:
input: 32090
output: 42
thoughts: 1478
Thoughts: **Analyzing a Potential Use-After-Free Vulnerability in `hiddev`**
Okay, so I'm digging into this `hiddev` code and I see a potential race condition between `hiddev_disconnect` and `hiddev_open`. The crux of it is this: `hiddev_disconnect` might free the `hiddev` object while `hiddev_open` is still trying to use it, leading to a use-after-free.
Here's how I'm breaking it down in my mind:
I've got two threads running concurrently. Thread 1 is the disconnect, Thread 2 is the open. Thread 1 grabs the `existancelock`, sets `hiddev->exist` to 0, checks if the device is open (which it won't be in this scenario), then unlocks the lock, and immediately `kfree`s the `hiddev` struct. Thread 2, however, is stuck, waiting for that same `existancelock`.
The issue arises because `mutex_unlock` has a slow path. If `mutex_unlock` has waiters, it doesn't immediately release the lock. Instead, it signals the waiter and *then* releases the lock. The problem is that Thread 1 *can* `kfree` before that actual `mutex_unlock` returns, meaning the data that Thread 2 is trying to access might have already been freed. It gets worse: Thread 2 acquires the freed memory's address and continues execution, accessing `hiddev->exist` and then calling `mutex_unlock` on an invalid memory location! That's a textbook use-after-free scenario.
Initially, I'm thinking, "Wait, this is bad, really bad!" I'm trying to figure out how this is triggered. I'm thinking, "Who's calling `hiddev_open` and `hiddev_disconnect`?" I realize it has to do with how the device gets opened.
An unprivileged user shouldn't be able to trigger this directly because file permissions get in the way. The VFS layer would stop them before the vulnerable code is even reached. `hiddev_ioctl` and `hiddev_read` are also protected by the need for an open file descriptor.
So, I rule out direct exploitation by unprivileged users. I dig deeper, wondering: could a daemon running with elevated privileges (like `upowerd` or `nut`) open/close the device at the right time? A malicious USB device *could* cause a disconnect event at the same time a privileged user is opening/closing the device. That's a definite attack vector. So, a malicious peripheral can trigger this.
Then, I have a moment of doubt. I check again whether an unprivileged user can open it by mistake: `logind`, `udev`, and `uaccess` could give access. I remember that some devices *do* get granted `uaccess` in certain configurations, like gamepads or VR headsets. If a malicious device masquerades as one of these, an unprivileged user on the console *could* open the device.
Then I am thinking, what about a FIDO token? A malicious USB device could spoof its vendor/product ID to match a device that gets `uaccess`. The code uses `hidraw` not `hiddev` but it does *create* `hiddev` for *all* HID devices. `hid_connect` creates a mask depending on whether a `hiddev` connection exists or not.
So, it's a Use-After-Free vulnerability, and the root cause is the race between `hiddev_disconnect` and `hiddev_open`. A privileged user opening/closing the device *concurrently* with a disconnect event is the primary trigger.
And then a carefully crafted malicious USB device is definitely a trigger!
|
| 37/2 |
2026/05/18 16:07 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/hid/hid-core.c IncludeLines:true Name:hid_hiddev]
Results: map[SourceCode:2199: static bool hid_hiddev(struct hid_device *hdev)
2200: {
2201: return !!hid_match_id(hdev, hid_hiddev_list);
2202: }
SourceFile:drivers/hid/hid-core.c]
|
| 38/2 |
2026/05/18 16:07 |
llm |
expert |
0m
Model:
Tokens:
input: 34518
output: 44
thoughts: 141
Thoughts: **Analyzing the HID Device Connection Logic**
Okay, let me break this down. First, I'm focusing on `hid_hiddev_list`. The text says this list specifically contains devices that *force* the use of `hiddev`. That's a key distinction. Now, the curious part is the behavior of `hiddev` itself. It gets connected by default, assuming `connect_mask` has the `HID_CONNECT_HIDDEV` bit set.
Looking deeper, `hid_connect` uses the `connect_mask` variable. Specifically, if a device has the `HID_QUIRK_HIDDEV_FORCE` quirk, the code sets both `HID_CONNECT_HIDDEV_FORCE` and `HID_CONNECT_HIDDEV` bits in `connect_mask`. This seems like a direct override, essentially forcing `hiddev` to be used for those specific devices. The use of the term "force" is important.
So, `connect_mask` is a crucial control. My next question is, who actually calls `hid_connect`? I need to trace this back. Ah, here it is: `hid_hw_start` calls `hid_connect`. That's the entry point for the device's connection attempt. This makes sense: `hid_hw_start` sets up the connection and uses the mask to control the connection behavior of `hiddev`.
|
| 39/2 |
2026/05/18 16:07 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile:drivers/hid/hid-core.c IncludeSnippetLines:0 Name:hid_hw_start]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:__hid_device_probe SourceFile:drivers/hid/hid-core.c SourceLine:2780] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:a4_probe SourceFile:drivers/hid/hid-a4tech.c SourceLine:134] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:alps_probe SourceFile:drivers/hid/hid-alps.c SourceLine:814] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:apple_probe SourceFile:drivers/hid/hid-apple.c SourceLine:960] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:appleir_probe SourceFile:drivers/hid/hid-appleir.c SourceLine:306] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:aqc_probe SourceFile:drivers/hwmon/aquacomputer_d5next.c SourceLine:1536] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:arvo_probe SourceFile:drivers/hid/hid-roccat-arvo.c SourceLine:359] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:asus_probe SourceFile:drivers/hid/hid-asus.c SourceLine:1291] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ax_probe SourceFile:drivers/hid/hid-axff.c SourceLine:139] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:belkin_probe SourceFile:drivers/hid/hid-belkin.c SourceLine:59] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:betop_probe SourceFile:drivers/hid/hid-betopff.c SourceLine:136] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:bigben_probe SourceFile:drivers/hid/hid-bigbenff.c SourceLine:392] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ccp_probe SourceFile:drivers/hwmon/corsair-cpro.c SourceLine:609] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ch_probe SourceFile:drivers/hid/hid-chicony.c SourceLine:127] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:cmhid_probe SourceFile:drivers/hid/hid-cmedia.c SourceLine:165] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:corsair_probe SourceFile:drivers/hid/hid-corsair.c SourceLine:575] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:corsair_void_probe SourceFile:drivers/hid/hid-corsair-void.c SourceLine:697] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:corsairpsu_probe SourceFile:drivers/hwmon/corsair-psu.c SourceLine:782] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:cougar_probe SourceFile:drivers/hid/hid-cougar.c SourceLine:218] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:cp2112_probe SourceFile:drivers/hid/hid-cp2112.c SourceLine:1235] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:cp_probe SourceFile:drivers/hid/hid-cypress.c SourceLine:141] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:creative_sb0540_probe SourceFile:drivers/hid/hid-creative-sb0540.c SourceLine:245] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:dr_probe SourceFile:drivers/hid/hid-dr.c SourceLine:281] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:elan_probe SourceFile:drivers/hid/hid-elan.c SourceLine:479] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:elo_probe SourceFile:drivers/hid/hid-elo.c SourceLine:250] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ems_probe SourceFile:drivers/hid/hid-emsff.c SourceLine:116] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ft260_probe SourceFile:drivers/hid/hid-ft260.c SourceLine:979] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ga_probe SourceFile:drivers/hid/hid-gaff.c SourceLine:146] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:gfrm_probe SourceFile:drivers/hid/hid-gfrm.c SourceLine:121] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:glorious_probe SourceFile:drivers/hid/hid-glorious.c SourceLine:75] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:gt683r_led_probe SourceFile:drivers/hid/hid-gt683r.c SourceLine:249] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:hid_generic_probe SourceFile:drivers/hid/hid-generic.c SourceLine:70] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:hidled_probe SourceFile:drivers/hid/hid-led.c SourceLine:487] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:hidpp_probe SourceFile:drivers/hid/hid-logitech-hidpp.c SourceLine:4406] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:hidpp_probe SourceFile:drivers/hid/hid-logitech-hidpp.c SourceLine:4445] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:holtek_kbd_probe SourceFile:drivers/hid/hid-holtek-kbd.c SourceLine:155] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:holtek_mouse_probe SourceFile:drivers/hid/hid-holtek-mouse.c SourceLine:79] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:holtek_probe SourceFile:drivers/hid/hid-holtekff.c SourceLine:191] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:isku_probe SourceFile:drivers/hid/hid-roccat-isku.c SourceLine:339] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ite_probe SourceFile:drivers/hid/hid-ite.c SourceLine:103] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:kone_probe SourceFile:drivers/hid/hid-roccat-kone.c SourceLine:764] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:koneplus_probe SourceFile:drivers/hid/hid-roccat-koneplus.c SourceLine:446] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:konepure_probe SourceFile:drivers/hid/hid-roccat-konepure.c SourceLine:148] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:kovaplus_probe SourceFile:drivers/hid/hid-roccat-kovaplus.c SourceLine:516] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:kraken2_probe SourceFile:drivers/hwmon/nzxt-kraken2.c SourceLine:154] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:kye_probe SourceFile:drivers/hid/hid-kye.c SourceLine:585] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:lenovo_probe SourceFile:drivers/hid/hid-lenovo.c SourceLine:1387] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:letsketch_probe SourceFile:drivers/hid/hid-letsketch.c SourceLine:305] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:lg_g15_probe SourceFile:drivers/hid/hid-lg-g15.c SourceLine:1149] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:lg_g15_probe SourceFile:drivers/hid/hid-lg-g15.c SourceLine:1217] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:lg_probe SourceFile:drivers/hid/hid-lg.c SourceLine:792] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:logi_dj_probe SourceFile:drivers/hid/hid-logitech-dj.c SourceLine:1902] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:logi_dj_probe SourceFile:drivers/hid/hid-logitech-dj.c SourceLine:1943] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:lua_probe SourceFile:drivers/hid/hid-roccat-lua.c SourceLine:172] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:magicmouse_probe SourceFile:drivers/hid/hid-magicmouse.c SourceLine:879] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:mcp2200_probe SourceFile:drivers/hid/hid-mcp2200.c SourceLine:337] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:mcp2221_probe SourceFile:drivers/hid/hid-mcp2221.c SourceLine:1242] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:mf_probe SourceFile:drivers/hid/hid-mf.c SourceLine:131] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ms_probe SourceFile:drivers/hid/hid-microsoft.c SourceLine:391] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:mt_probe SourceFile:drivers/hid/hid-multitouch.c SourceLine:2028] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:mwctrl_probe SourceFile:drivers/hid/hid-megaworld.c SourceLine:98] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:nintendo_hid_probe SourceFile:drivers/hid/hid-nintendo.c SourceLine:2674] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ntrig_probe SourceFile:drivers/hid/hid-ntrig.c SourceLine:928] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:nzxt_smart2_hid_probe SourceFile:drivers/hwmon/nzxt-smart2.c SourceLine:747] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:picolcd_probe SourceFile:drivers/hid/hid-picolcd_core.c SourceLine:552] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:pk_probe SourceFile:drivers/hid/hid-prodikeys.c SourceLine:821] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:pl_probe SourceFile:drivers/hid/hid-petalynx.c SourceLine:79] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:pl_probe SourceFile:drivers/hid/hid-pl.c SourceLine:191] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:plantronics_probe SourceFile:drivers/hid/hid-plantronics.c SourceLine:209] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ps_probe SourceFile:drivers/hid/hid-playstation.c SourceLine:2841] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:pxrc_probe SourceFile:drivers/hid/hid-pxrc.c SourceLine:86] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:pyra_probe SourceFile:drivers/hid/hid-roccat-pyra.c SourceLine:464] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:razer_probe SourceFile:drivers/hid/hid-razer.c SourceLine:102] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:retrode_probe SourceFile:drivers/hid/hid-retrode.c SourceLine:75] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:rmi_probe SourceFile:drivers/hid/hid-rmi.c SourceLine:730] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ryos_probe SourceFile:drivers/hid/hid-roccat-ryos.c SourceLine:156] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:saitek_probe SourceFile:drivers/hid/hid-saitek.c SourceLine:60] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:samsung_probe SourceFile:drivers/hid/hid-samsung.c SourceLine:532] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:savu_probe SourceFile:drivers/hid/hid-roccat-savu.c SourceLine:128] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:sensor_hub_probe SourceFile:drivers/hid/hid-sensor-hub.c SourceLine:633] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:shield_probe SourceFile:drivers/hid/hid-nvidia-shield.c SourceLine:1073] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:sjoy_probe SourceFile:drivers/hid/hid-sjoy.c SourceLine:130] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:sony_probe SourceFile:drivers/hid/hid-sony.c SourceLine:2210] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:stadia_probe SourceFile:drivers/hid/hid-google-stadiaff.c SourceLine:114] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:steam_probe SourceFile:drivers/hid/hid-steam.c SourceLine:1225] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:steam_probe SourceFile:drivers/hid/hid-steam.c SourceLine:1231] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:steam_probe SourceFile:drivers/hid/hid-steam.c SourceLine:1253] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:steelseries_probe SourceFile:drivers/hid/hid-steelseries.c SourceLine:562] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:steelseries_srws1_probe SourceFile:drivers/hid/hid-steelseries.c SourceLine:275] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:thrustmaster_probe SourceFile:drivers/hid/hid-thrustmaster.c SourceLine:304] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:tm_probe SourceFile:drivers/hid/hid-tmff.c SourceLine:223] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:u2fzero_probe SourceFile:drivers/hid/hid-u2fzero.c SourceLine:340] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:uclogic_probe SourceFile:drivers/hid/hid-uclogic-core.c SourceLine:257] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:udraw_probe SourceFile:drivers/hid/hid-udraw-ps3.c SourceLine:445] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:vivaldi_probe SourceFile:drivers/hid/hid-vivaldi.c SourceLine:33] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:vrc2_probe SourceFile:drivers/hid/hid-vrc2.c SourceLine:66] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:wacom_parse_and_register SourceFile:drivers/hid/wacom_sys.c SourceLine:2451] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:wacom_probe SourceFile:drivers/hid/wacom_sys.c SourceLine:2877] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:waterforce_probe SourceFile:drivers/hwmon/gigabyte_waterforce.c SourceLine:340] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:wiimote_hid_probe SourceFile:drivers/hid/hid-wiimote-core.c SourceLine:1809] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:zc_probe SourceFile:drivers/hid/hid-zydacron.c SourceLine:183] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:zp_probe SourceFile:drivers/hid/hid-zpff.c SourceLine:114]] TruncatedOutput:false]
|
| 40/2 |
2026/05/18 16:07 |
llm |
expert |
1m
Model:
Tokens:
input: 40390
output: 66
thoughts: 2457
Thoughts: **Analysis of the HIDDEV USB Disconnect Vulnerability**
Okay, let's break this down systematically. The core issue revolves around a Use-After-Free (UAF) vulnerability in the HIDDEV driver when handling USB device disconnects. My first thought is: `plantronics_probe` calls `hid_hw_start`, which by default connects `hiddev` for *every* USB HID device. That's the key. This means any malicious USB device plugged in gets a `hiddev` node. If a root daemon, such as `upowerd` or `nut`, opens this malicious device, an unplug triggers a UAF. **Peripheral Trigger: Yes.**
Now, can an unprivileged user exploit this? The crux is whether they can open `/dev/usb/hiddevX`. By default, it's root-owned. `udev` rules *might* grant `uaccess` to certain devices (FIDO tokens, VR headsets, some gamepads). If the malicious device spoofs the vendor/product ID of one of these, then yes, `uaccess` is granted. The user then *could* open the device. But even if they can't open it, they can still trigger the bug by letting a root daemon open the malicious device, which is an even simpler attack.
Let's delve into `udev` further. Does `uaccess` apply to `hiddev` or just the `hidraw` node? Usually, `TAG+="uaccess"` is on the USB parent or hidraw device, but `systemd-logind` sets ACLs. If `hiddev` gets the ACL, the user *can* open it. Even if they don't get the ACL, if the user has physical access, they are a local attacker. The question is if they can trigger the bug *without* root access *and* without special access to device nodes. If `hiddev` is root restricted, and they spoof the device ID, they are getting access to a device node *not* restricted to root. But they don't even need access to open it.
Consider `upowerd` specifically, since it's a daemon. Does it open `hiddev`? Yes, if the malicious USB device reports a "Power Device" usage page. Once `upowerd` opens it, a physical disconnect will trigger the bug. So, **Unprivileged: No**, because a disconnect *requires* physical access. An unprivileged user with an open `hiddev` file descriptor can't even trigger a disconnect without physical access. `hiddev` has no disconnect ioctl.
Now let's consider the `uhid` path. This is a potential attack vector, if the attacker can create a `BUS_USB` device via `/dev/uhid`. `hid_connect` calls `hiddev_connect`, which attempts to cast `uhid`'s data to a `usbhid_device`, which is the problem. In `uhid_device` struct, `intf` offset 8 points into a mutex; `wait_lock` is part of the mutex struct. Reading this garbage pointer (or NULL) causes a NULL or wild pointer dereference, which crashes the kernel. So, the `uhid` path is a crash, not a UAF. Therefore, this is not a viable attack path.
Therefore, we have:
* **Exploitable: Yes** (UAF, memory corruption)
* **DenialOfService: Yes** (Kernel crash)
* **Unprivileged: No** (Requires physical access, or root to unbind driver)
* **UserNamespace: No**
* **VMGuestTrigger: No**
* **VMHostTrigger: No**
* **NetworkTrigger: No**
* **RemoteTrigger: No**
* **PeripheralTrigger: Yes** (Physical malicious USB device)
* **FilesystemTrigger: No**
Let's quickly check if there are other triggers.
`hiddev_disconnect` is called from `hid_disconnect`.
`hid_disconnect` is called from `hid_hw_stop`.
`hid_hw_stop` is called from `hid_device_remove`.
`hid_device_remove` is called when the HID driver is unbound from the HID device.
Can an unprivileged user unbind a driver? No.
Can an unprivileged user cause a HID device to be removed? Not if it's a `BUS_USB` device. Bluetooth HID devices (`BUS_BLUETOOTH`) do not get a `hiddev` node.
The bug report says: ` usb 6-1: USB disconnect, device number 25`. This confirms a USB disconnect trigger. Syzkaller uses `usb/gadget` or `dummy
|
| 41/2 |
2026/05/18 16:08 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 42/2 |
2026/05/18 16:08 |
llm |
expert |
1m
Model:
Tokens:
input: 44002
output: 719
thoughts: 385
Thoughts: **My Analysis of the `hiddev` Driver UAF Vulnerability**
Okay, the analysis is complete, and it's time to craft the final response. This looks like a classic Use-After-Free (UAF) bug, and it's in the `hiddev` driver. Specifically, the vulnerability resides in the `hiddev_disconnect` function.
Here's how it shakes out: when a USB HID device gets disconnected, the system calls `hiddev_disconnect`. This function grabs the `hiddev->existancelock`, sets `hiddev->exist` to zero, and if the device is currently open (checked by `hiddev->open > 0`), it unlocks the mutex and exits. Concurrently, if the last file descriptor associated with the device gets closed, `hiddev_release` gets called. This function, crucially, also acquires the *same* `existancelock`, decrements `hiddev->open`, and if `hiddev->open` is zero and `hiddev->exist` is also zero, it frees the `hiddev` object using `kfree`.
The race condition is obvious: `hiddev_disconnect` might be in the slow part of `mutex_unlock` when `hiddev_release` swoops in, acquires the lock, unlocks it, and frees the `hiddev` object. Then, `hiddev_disconnect` will attempt to access the freed memory, specifically `wait_lock` of the mutex, resulting in a UAF write, which is a classic memory corruption.
This memory corruption has significant implications – privilege escalation is certainly possible, or at minimum, a Denial of Service via a kernel crash is likely. The trigger for this vulnerability is the plugging and unplugging of USB HID devices. An unprivileged user can't do this without physical access or root-level controls like `dummy_hcd` or driver unbinding, which seems to limit the scope of the attack somewhat. However, a malicious USB device can easily trigger it. It can spoof a device that's *automatically* opened by a system daemon like `upowerd` or `nut`, and then the attacker just needs to simulate a disconnect at the right time. Therefore, this is clearly a Peripheral Trigger, and an attacker with physical access or the ability to inject malicious USB devices can readily exploit this.
|
| 43/1 |
2026/05/18 16:09 |
action |
format-explanation |
0m
Results: map[Explanation:**Analysis of the Bug**
The bug is a Use-After-Free (UAF) vulnerability in the USB HID device driver
(`hiddev`), specifically involving a race condition between device disconnection
(`hiddev_disconnect`) and file descriptor closure (`hiddev_release`).
1. **Root Cause**:
When a USB HID device is unplugged, the kernel invokes `hiddev_disconnect`. This
function locks the `hiddev->existancelock` mutex, marks the device as
non-existent (`hiddev->exist = 0`), and if the device is currently open by a
userspace process, it unlocks the mutex and returns.
Concurrently, if the userspace process closes the device, `hiddev_release` is
invoked. It locks the same `existancelock` mutex, decrements the open count, and
if the count reaches 0 and the device no longer exists, it unlocks the mutex and
frees the `hiddev` structure via `kfree()`.
The race occurs because `hiddev_disconnect` can be preempted or delayed inside
the slowpath of `mutex_unlock()`. While it is still technically inside the
unlock routine (trying to acquire the internal `wait_lock` of the mutex to wake
up waiters), `hiddev_release` can wake up, acquire the mutex, finish its
execution, and free the `hiddev` object. When `hiddev_disconnect` resumes, it
accesses the `wait_lock` of the now-freed mutex, leading to a UAF.
2. **Exploitability**:
The UAF occurs on a spinlock (`wait_lock`) embedded within the freed object.
When the kernel attempts to lock or unlock this spinlock, it will write to the
freed memory. If an attacker can reallocate this memory with a controlled object
before the spinlock operation occurs, they can corrupt the new object. This type
of memory corruption is a classic primitive that can be leveraged for local
privilege escalation (LPE). Thus, it is highly exploitable.
3. **Trigger Vectors**:
- **Peripheral Trigger**: Yes. A local attacker with physical access can plug in
a malicious USB device. The device can spoof its descriptors to appear as a
device that a root daemon (such as `upowerd` for UPS devices or `nut`) will
automatically open. The malicious USB device can then simulate a sudden
disconnect (dropping off the bus) to trigger `hiddev_disconnect`, while the
daemon concurrently closes the device upon receiving a read error, perfectly
aligning the race condition.
- **Unprivileged / User Namespaces**: No. By default, `/dev/usb/hiddevX` nodes
are restricted to `root` (0600 permissions). An unprivileged user cannot open
the device to set up the race condition. Furthermore, triggering the disconnect
requires either physical access to unplug the device or root privileges to
unbind the driver via sysfs or use USB gadget/dummy subsystems. User namespaces
do not grant access to physical device nodes or driver unbinding.
- **Remote / Network / Filesystem / VM**: No. The bug is strictly tied to the
USB HID subsystem and requires local USB device state manipulation.
**Conclusion**
This is a severe memory corruption vulnerability (UAF) that can lead to Denial
of Service (kernel crash) or Privilege Escalation. However, it is constrained to
local attackers who can plug in malicious USB devices (Peripheral Trigger) or
users who already possess root privileges.]
|