| 0/0 |
2026/07/28 00:58 |
flow |
assessment-security |
40m
Results: map[DenialOfService:true Explanation:**Analysis of the Bug**
**Root Cause:**
The bug is a Use-After-Free (UAF) in `i2c_adapter_depth()` located in
`drivers/i2c/i2c-core-base.c`. This function is used to calculate the lockdep
subclass for the I2C adapter's bus lock by traversing the device tree upwards
via `parent = parent->parent`.
However, it traverses these pointers without holding any references to the
parent devices.
When a physical device (e.g., a USB device) is disconnected, the kernel
unregisters and frees the device structures (USB interface, USB device, root
hub). If a buggy driver fails to unregister its I2C adapter synchronously during
the `disconnect` callback (either by leaking it or unregistering it
asynchronously in a workqueue), the I2C adapter remains alive. Its `dev.parent`
pointer, however, becomes a dangling pointer pointing to the freed parent
device.
If a user holds an open file descriptor to the I2C adapter (or a related device
node exposed by the driver) and issues an I2C transfer (e.g., via
`ioctl(I2C_RDWR)`), `i2c_adapter_depth()` is called. It will traverse the
dangling `parent` pointers, accessing the freed memory of the USB interface, USB
device, and root hub.
**Exploitability:**
**Yes.** This UAF is highly exploitable for privilege escalation.
An attacker can use heap spraying techniques (e.g., via `sk_buff` allocations,
as seen in the syzkaller report) to overwrite the freed `usb_device` structure
with controlled data.
1. The attacker can craft the fake `parent->parent` pointers to safely bypass
the loop in `i2c_adapter_depth()` without crashing.
2. The execution then proceeds to the actual I2C transfer (`master_xfer`), which
will use the attacker-controlled `usb_device` to send USB control messages.
3. The USB core will dereference the fake `dev->bus` to find the Host Controller
Driver (`hcd`), and subsequently call the function pointer at
`hcd->driver->urb_enqueue`.
By controlling these structures, an attacker can hijack the kernel's control
flow and achieve arbitrary code execution (ACE).
**Denial of Service:**
**Yes.** If the attacker does not perfectly control the sprayed memory, the
dangling `parent->parent` pointer will likely point to unmapped or invalid
memory. Dereferencing it in `i2c_adapter_depth()` will cause a page fault and a
kernel panic, leading to a Denial of Service.
**Trigger Mechanisms:**
* **Peripheral Trigger:** **Yes.** The vulnerability requires a buggy USB driver
to bind to a device. An attacker can trigger this by physically plugging in a
malicious USB device (or a specific supported device), waiting for it to bind,
and then unplugging it to force the buggy disconnect path.
* **Unprivileged / User Namespace:** **Yes.** While `/dev/i2c-X` is typically
restricted to root or the `i2c` group, many USB drivers (such as webcams or TV
tuners) expose secondary device nodes (like `/dev/videoX`) that are accessible
to unprivileged users. If the driver translates unprivileged ioctls into I2C
transfers, an unprivileged user can trigger the UAF after the device is
unplugged. Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/07/28 00:58 |
action |
syz-repro-to-c-repro |
0m
Results:
|
| 2/1 |
2026/07/28 00:58 |
action |
kernel-checkouter |
1m
Results: map[KernelSrc:/app/workdir/cache/src/3735123449433f9e6f9fb5b55ddd9994cbcf15f0]
|
| 3/1 |
2026/07/28 01:00 |
action |
kernel-builder |
16m
Results: map[KernelObj:/app/workdir/cache/build/012e0fe694f053ca88d1c6c360ad7ccf1d5378f4]
|
| 4/1 |
2026/07/28 01:16 |
action |
codesearch-prepare |
14m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/07/28 01:31 |
agent |
expert |
8m
Model:
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:true UserNamespace:true 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 grepping
".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).
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, elevated privileges, or an information leak.
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 completely 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 transfers, that 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 exploitability depends on how exactly the uninit value
is used in the code, and what it affects.
Information leaks are exploitable on their own and should be classified as such. A bug that copies kernel
memory contents to userspace (e.g. an out-of-bounds read whose result is returned to the caller, or
uninitialized stack/heap bytes written to a user buffer) is exploitable: it can reveal kernel pointer
values and defeat KASLR, expose sensitive data such as cryptographic keys or other processes' memory, and
serves as a necessary building block in most modern kernel privilege-escalation exploit chains. Do not classify
an information leak as non-exploitable solely because it does not directly cause a memory write or control-flow
hijack; the leak itself is the exploit primitive.
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 Confidential 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.
Don't make assumptions about the kernel source code (it may be different from what you assume it is).
Extensively use the provided code access tools (codesearch-*, git-*, grepper, etc)
to examine the actual source code, and confirm any assumptions.
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:
==================================================================
BUG: KASAN: slab-use-after-free in i2c_adapter_depth drivers/i2c/i2c-core-base.c:1243 [inline]
BUG: KASAN: slab-use-after-free in i2c_adapter_lock_bus+0x5e/0xf0 drivers/i2c/i2c-core-base.c:849
Read of size 8 at addr ffff88802a2bb108 by task syz.4.9335/29812
CPU: 0 UID: 0 PID: 29812 Comm: syz.4.9335 Tainted: G L syzkaller #0 PREEMPT(full)
Tainted: [L]=SOFTLOCKUP
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 07/16/2026
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_address_description+0x55/0x1e0 mm/kasan/report.c:378
print_report+0x58/0x70 mm/kasan/report.c:482
kasan_report+0x117/0x150 mm/kasan/report.c:595
i2c_adapter_depth drivers/i2c/i2c-core-base.c:1243 [inline]
i2c_adapter_lock_bus+0x5e/0xf0 drivers/i2c/i2c-core-base.c:849
i2c_lock_bus include/linux/i2c.h:809 [inline]
__i2c_lock_bus_helper drivers/i2c/i2c-core.h:47 [inline]
i2c_transfer+0xc8/0x2d0 drivers/i2c/i2c-core-base.c:2339
i2cdev_ioctl_rdwr+0x460/0x740 drivers/i2c/i2c-dev.c:306
i2cdev_ioctl+0x6a5/0x880 drivers/i2c/i2c-dev.c:467
vfs_ioctl fs/ioctl.c:51 [inline]
__do_sys_ioctl fs/ioctl.c:597 [inline]
__se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f1f1df9de99
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f1f1edf2028 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 00007f1f1e225fa0 RCX: 00007f1f1df9de99
RDX: 00002000000000c0 RSI: 0000000000000707 RDI: 0000000000000004
RBP: 00007f1f1e033eaf R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f1f1e226038 R14: 00007f1f1e225fa0 R15: 00007ffdb40dc148
</TASK>
Allocated by task 14421:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
kasan_kmalloc include/linux/kasan.h:263 [inline]
__do_kmalloc_node mm/slub.c:5334 [inline]
__kmalloc_node_track_caller_noprof+0x4c3/0x730 mm/slub.c:5471
kmalloc_reserve net/core/skbuff.c:637 [inline]
__alloc_skb+0x2bd/0x7a0 net/core/skbuff.c:715
alloc_skb include/linux/skbuff.h:1384 [inline]
nlmsg_new include/net/netlink.h:1055 [inline]
rtmsg_ifinfo_build_skb+0x7f/0x260 net/core/rtnetlink.c:4524
unregister_netdevice_many_notify+0x1865/0x2150 net/core/dev.c:12449
ops_exit_rtnl_list net/core/net_namespace.c:187 [inline]
ops_undo_list+0x391/0x8d0 net/core/net_namespace.c:248
cleanup_net+0x572/0x810 net/core/net_namespace.c:702
process_one_work kernel/workqueue.c:3322 [inline]
process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405
worker_thread+0xa47/0xfb0 kernel/workqueue.c:3486
kthread+0x388/0x470 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
Freed by task 14421:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2677 [inline]
slab_free mm/slub.c:6377 [inline]
kfree+0x1c5/0x640 mm/slub.c:6692
skb_kfree_head net/core/skbuff.c:1077 [inline]
skb_free_head net/core/skbuff.c:1089 [inline]
skb_release_data+0x85e/0xab0 net/core/skbuff.c:1116
skb_release_all net/core/skbuff.c:1191 [inline]
__kfree_skb+0x5d/0x210 net/core/skbuff.c:1205
netlink_broadcast_filtered+0xd9d/0xea0 net/netlink/af_netlink.c:1541
nlmsg_multicast_filtered include/net/netlink.h:1165 [inline]
nlmsg_multicast include/net/netlink.h:1184 [inline]
nlmsg_notify+0xe3/0x1a0 net/netlink/af_netlink.c:2599
unregister_netdevice_many_notify+0x1b38/0x2150 net/core/dev.c:12472
ops_exit_rtnl_list net/core/net_namespace.c:187 [inline]
ops_undo_list+0x391/0x8d0 net/core/net_namespace.c:248
cleanup_net+0x572/0x810 net/core/net_namespace.c:702
process_one_work kernel/workqueue.c:3322 [inline]
process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405
worker_thread+0xa47/0xfb0 kernel/workqueue.c:3486
kthread+0x388/0x470 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
The buggy address belongs to the object at ffff88802a2bb000
which belongs to the cache kmalloc-2k of size 2048
The buggy address is located 264 bytes inside of
freed 2048-byte region [ffff88802a2bb000, ffff88802a2bb800)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0xffff88802a2bf000 pfn:0x2a2b8
head: order:3 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 ffff88801b005000 ffffea0000c57610 ffffea0001dcdc10
raw: ffff88802a2bf000 0000000800080006 00000000f5000000 0000000000000000
head: 00fff00000000240 ffff88801b005000 ffffea0000c57610 ffffea0001dcdc10
head: ffff88802a2bf000 0000000800080006 00000000f5000000 0000000000000000
head: 00fff00000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd20c0(__GFP_IO|__GFP_FS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 1, tgid 1 (swapper/0), ts 7701870962, free_ts 7543256680
set_page_owner include/linux/page_owner.h:32 [inline]
post_alloc_hook+0x1f9/0x250 mm/page_alloc.c:1859
prep_new_page mm/page_alloc.c:1867 [inline]
get_page_from_freelist+0x21fa/0x2270 mm/page_alloc.c:3946
__alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5304
alloc_slab_page mm/slub.c:3266 [inline]
allocate_slab+0x79/0x5e0 mm/slub.c:3380
new_slab mm/slub.c:3426 [inline]
refill_objects+0x2d5/0x350 mm/slub.c:7310
refill_sheaf mm/slub.c:2804 [inline]
__pcs_replace_empty_main+0x2bf/0x6b0 mm/slub.c:4675
alloc_from_pcs mm/slub.c:4773 [inline]
slab_alloc_node mm/slub.c:4905 [inline]
__kmalloc_cache_noprof+0x3a7/0x660 mm/slub.c:5485
_kmalloc_noprof include/linux/slab.h:988 [inline]
_kzalloc_noprof include/linux/slab.h:1309 [inline]
usb_alloc_dev+0x5f/0xf10 drivers/usb/core/usb.c:651
usb_add_hcd+0x407/0xf80 drivers/usb/core/hcd.c:2880
dummy_hcd_probe+0x134/0x270 drivers/usb/gadget/udc/dummy_hcd.c:2722
platform_probe+0xf9/0x190 drivers/base/platform.c:1439
call_driver_probe drivers/base/dd.c:-1 [inline]
really_probe+0x254/0xae0 drivers/base/dd.c:706
__driver_probe_device+0x1e8/0x360 drivers/base/dd.c:868
driver_probe_device+0x4f/0x240 drivers/base/dd.c:898
__device_attach_driver+0x270/0x410 drivers/base/dd.c:1026
bus_for_each_drv+0x258/0x2f0 drivers/base/bus.c:500
page last free pid 803 tgid 803 stack trace:
reset_page_owner include/linux/page_owner.h:25 [inline]
__free_pages_prepare mm/page_alloc.c:1406 [inline]
free_pages_prepare+0x960/0xa60 mm/page_alloc.c:1451
__free_contig_range_common+0x174/0x340 mm/page_alloc.c:6897
__free_contig_range mm/page_alloc.c:6942 [inline]
free_pages_bulk+0x48/0x120 mm/page_alloc.c:5257
vm_area_free_pages mm/vmalloc.c:3439 [inline]
vfree+0x224/0x4a0 mm/vmalloc.c:3488
delayed_vfree_work+0x55/0x80 mm/vmalloc.c:3392
process_one_work kernel/workqueue.c:3322 [inline]
process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405
worker_thread+0xa47/0xfb0 kernel/workqueue.c:3486
kthread+0x388/0x470 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 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:
ffff88802a2bb000: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88802a2bb080: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff88802a2bb100: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff88802a2bb180: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88802a2bb200: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
Reply: **Analysis of the Bug**
**Root Cause:**
The bug is a Use-After-Free (UAF) in `i2c_adapter_depth()` located in `drivers/i2c/i2c-core-base.c`. This function is used to calculate the lockdep subclass for the I2C adapter's bus lock by traversing the device tree upwards via `parent = parent->parent`.
However, it traverses these pointers without holding any references to the parent devices.
When a physical device (e.g., a USB device) is disconnected, the kernel unregisters and frees the device structures (USB interface, USB device, root hub). If a buggy driver fails to unregister its I2C adapter synchronously during the `disconnect` callback (either by leaking it or unregistering it asynchronously in a workqueue), the I2C adapter remains alive. Its `dev.parent` pointer, however, becomes a dangling pointer pointing to the freed parent device.
If a user holds an open file descriptor to the I2C adapter (or a related device node exposed by the driver) and issues an I2C transfer (e.g., via `ioctl(I2C_RDWR)`), `i2c_adapter_depth()` is called. It will traverse the dangling `parent` pointers, accessing the freed memory of the USB interface, USB device, and root hub.
**Exploitability:**
**Yes.** This UAF is highly exploitable for privilege escalation.
An attacker can use heap spraying techniques (e.g., via `sk_buff` allocations, as seen in the syzkaller report) to overwrite the freed `usb_device` structure with controlled data.
1. The attacker can craft the fake `parent->parent` pointers to safely bypass the loop in `i2c_adapter_depth()` without crashing.
2. The execution then proceeds to the actual I2C transfer (`master_xfer`), which will use the attacker-controlled `usb_device` to send USB control messages.
3. The USB core will dereference the fake `dev->bus` to find the Host Controller Driver (`hcd`), and subsequently call the function pointer at `hcd->driver->urb_enqueue`.
By controlling these structures, an attacker can hijack the kernel's control flow and achieve arbitrary code execution (ACE).
**Denial of Service:**
**Yes.** If the attacker does not perfectly control the sprayed memory, the dangling `parent->parent` pointer will likely point to unmapped or invalid memory. Dereferencing it in `i2c_adapter_depth()` will cause a page fault and a kernel panic, leading to a Denial of Service.
**Trigger Mechanisms:**
* **Peripheral Trigger:** **Yes.** The vulnerability requires a buggy USB driver to bind to a device. An attacker can trigger this by physically plugging in a malicious USB device (or a specific supported device), waiting for it to bind, and then unplugging it to force the buggy disconnect path.
* **Unprivileged / User Namespace:** **Yes.** While `/dev/i2c-X` is typically restricted to root or the `i2c` group, many USB drivers (such as webcams or TV tuners) expose secondary device nodes (like `/dev/videoX`) that are accessible to unprivileged users. If the driver translates unprivileged ioctls into I2C transfers, an unprivileged user can trigger the UAF after the device is unplugged.
|
| 6/2 |
2026/07/28 01:31 |
llm |
expert |
0m
Model:
Tokens:
input: 10343
output: 46
thoughts: 886
Thoughts: **Analyzing a KASAN Slab-Use-After-Free Report**
Okay, I'm looking at a KASAN report, specifically a slab-use-after-free in `i2c_adapter_depth` within `i2c_adapter_lock_bus`. The read is a size 8 access at a specific memory address. Let's see what the stack trace is telling me: the critical functions are involved: `i2c_adapter_depth`, `i2c_adapter_lock_bus`, and the `i2c_transfer` call, likely initiated via an `i2cdev_ioctl` from userspace.
The most intriguing part is that the memory region being accessed, supposedly an `i2c_adapter` structure, was actually allocated as an `sk_buff` data buffer of size 2048, likely via `rtmsg_ifinfo_build_skb`. The allocation trace confirms this, showing the `kmalloc_reserve`, `__alloc_skb`, and finally `rtmsg_ifinfo_build_skb` calls. The same task ID is responsible for allocating and freeing the buffer via the standard `skb` cleanup functions (`skb_free_head`, `skb_release_data`, etc.).
This is a classic memory corruption scenario. Either the original `i2c_adapter` structure was freed and its memory was re-used for the `sk_buff`, and now we're accessing a stale pointer, or the `i2c_adapter` pointer is completely garbage, and we're just accidentally pointing at an `sk_buff` data buffer that was recently freed and then reallocated.
The key observation is that the allocation trace is for the *current* contents of that memory address, which is an `sk_buff` buffer, not the original `i2c_adapter`. This implies either a double-free, a use-after-free and reallocation, or simple pointer corruption. I need to dig into `i2c_adapter_depth` to understand how this incorrect pointer is being used in the first place.
|
| 7/2 |
2026/07/28 01:31 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-base.c Name:i2c_adapter_depth]
Results: map[SourceCode:1237: unsigned int i2c_adapter_depth(struct i2c_adapter *adapter)
1238: {
1239: unsigned int depth = 0;
1240: struct device *parent;
1241:
1242: for (parent = adapter->dev.parent; parent; parent = parent->parent)
1243: if (parent->type == &i2c_adapter_type)
1244: depth++;
1245:
1246: WARN_ONCE(depth >= MAX_LOCKDEP_SUBCLASSES,
1247: "adapter depth exceeds lockdep subclass limit\n");
1248:
1249: return depth;
1250: }
SourceFile:drivers/i2c/i2c-core-base.c]
|
| 8/2 |
2026/07/28 01:31 |
llm |
expert |
0m
Model:
Tokens:
input: 11959
output: 45
thoughts: 64
Thoughts: **Debugging the I2C Adapter Crash**
Okay, the program crashed, and the error points to line 1243: `if (parent->type == &i2c_adapter_type)`. Right, so `parent` is the value of `adapter->dev.parent`. Let me be sure I'm reading this right... Yep, `parent` is a `struct device *`. Let me quickly refresh my mental image of that structure. I need to check the layout of `struct device` to understand how the `type` field is defined. I need to figure out what `parent->type` should actually be in order to debug this effectively.
|
| 9/2 |
2026/07/28 01:31 |
tool |
codesearch-struct-layout |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-base.c FieldOffset:<nil> Name:device]
Results: map[Fields:[map[Name:kobj OffsetBits:0 SizeBits:512] map[Name:parent OffsetBits:512 SizeBits:64] map[Name:p OffsetBits:576 SizeBits:64] map[Name:init_name OffsetBits:640 SizeBits:64] map[Name:type OffsetBits:704 SizeBits:64] map[Name:bus OffsetBits:768 SizeBits:64] map[Name:driver OffsetBits:832 SizeBits:64] map[Name:platform_data OffsetBits:896 SizeBits:64] map[Name:driver_data OffsetBits:960 SizeBits:64] map[Name:driver_override OffsetBits:1024 SizeBits:576] map[Name:mutex OffsetBits:1600 SizeBits:1088] map[Name:links OffsetBits:2688 SizeBits:448] map[Name:power OffsetBits:3136 SizeBits:4416] map[Name:pm_domain OffsetBits:7552 SizeBits:64] map[Name:msi OffsetBits:7616 SizeBits:128] map[Name:dma_mask OffsetBits:7744 SizeBits:64] map[Name:coherent_dma_mask OffsetBits:7808 SizeBits:64] map[Name:bus_dma_limit OffsetBits:7872 SizeBits:64] map[Name:dma_range_map OffsetBits:7936 SizeBits:64] map[Name:dma_parms OffsetBits:8000 SizeBits:64] map[Name:dma_pools OffsetBits:8064 SizeBits:128] map[Name:dma_mem OffsetBits:8192 SizeBits:64] map[Name:cma_area OffsetBits:8256 SizeBits:64] map[Name:dma_io_tlb_mem OffsetBits:8320 SizeBits:64] map[Name:archdata OffsetBits:8384 SizeBits:0] map[Name:of_node OffsetBits:8384 SizeBits:64] map[Name:fwnode OffsetBits:8448 SizeBits:64] map[Name:numa_node OffsetBits:8512 SizeBits:32] map[Name:devt OffsetBits:8544 SizeBits:32] map[Name:id OffsetBits:8576 SizeBits:32] map[Name:devres_lock OffsetBits:8640 SizeBits:512] map[Name:devres_head OffsetBits:9152 SizeBits:128] map[Name:class OffsetBits:9280 SizeBits:64] map[Name:groups OffsetBits:9344 SizeBits:64] map[Name:release OffsetBits:9408 SizeBits:64] map[Name:iommu_group OffsetBits:9472 SizeBits:64] map[Name:iommu OffsetBits:9536 SizeBits:64] map[Name:physical_location OffsetBits:9600 SizeBits:64] map[Name:removable OffsetBits:9664 SizeBits:32] map[Name:flags OffsetBits:9728 SizeBits:64]]]
|
| 10/2 |
2026/07/28 01:31 |
llm |
expert |
0m
Model:
Tokens:
input: 13093
output: 44
thoughts: 171
Thoughts: **Analyzing a Memory Corruption Bug**
Okay, so I'm looking at this crash, a read of size 8 at `ffff88802a2bb108`. The crash is definitely memory corruption; I need to figure out where and why. Let's see... `parent->type` seems to be the culprit, located at an offset of 88 bytes within the `parent` structure. Therefore, the address of `parent` must be `ffff88802a2bb108` minus 88, which is `ffff88802a2bb0b0`.
Now, the object itself starts at `ffff88802a2bb000`, and it's 2048 bytes in size. The buggy read is 264 bytes inside the freed region. That suggests the `parent` object is messed up, specifically it appears to be located 176 bytes into the freed region, the cause of the read. This is looking more and more like a use-after-free situation.
Let me verify that by checking the `struct i2c_adapter` layout...
|
| 11/2 |
2026/07/28 01:31 |
tool |
codesearch-struct-layout |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-base.c Name:i2c_adapter]
Results: map[Fields:[map[Name:owner OffsetBits:0 SizeBits:64] map[Name:class OffsetBits:64 SizeBits:32] map[Name:algo OffsetBits:128 SizeBits:64] map[Name:algo_data OffsetBits:192 SizeBits:64] map[Name:lock_ops OffsetBits:256 SizeBits:64] map[Name:bus_lock OffsetBits:320 SizeBits:1024] map[Name:mux_lock OffsetBits:1344 SizeBits:1024] map[Name:timeout OffsetBits:2368 SizeBits:32] map[Name:retries OffsetBits:2400 SizeBits:32] map[Name:dev OffsetBits:2432 SizeBits:9792] map[Name:locked_flags OffsetBits:12224 SizeBits:64] map[Name:nr OffsetBits:12288 SizeBits:32] map[Name:name OffsetBits:12320 SizeBits:384] map[Name:dev_released OffsetBits:12736 SizeBits:704] map[Name:userspace_clients_lock OffsetBits:13440 SizeBits:1088] map[Name:userspace_clients OffsetBits:14528 SizeBits:128] map[Name:bus_recovery_info OffsetBits:14656 SizeBits:64] map[Name:quirks OffsetBits:14720 SizeBits:64] map[Name:host_notify_domain OffsetBits:14784 SizeBits:64] map[Name:bus_regulator OffsetBits:14848 SizeBits:64] map[Name:debugfs OffsetBits:14912 SizeBits:64] map[Name:addrs_in_instantiation OffsetBits:14976 SizeBits:128]]]
|
| 12/2 |
2026/07/28 01:31 |
llm |
expert |
0m
Model:
Tokens:
input: 14066
output: 32
thoughts: 1817
Thoughts: **Analyzing a Suspected Use-After-Free in i2c_adapter_lock_bus**
Okay, here's what I'm thinking, trying to piece this together. We've got a crash, a read of `parent->type` at `ffff88802a2bb108`. The report says a 2048-byte region at `ffff88802a2bb000` was freed, and the buggy address is 264 bytes (`0x108`) into that region. That tells me the `parent` pointer, which leads to the `type` read, must be within the freed memory. Specifically, if the crash is at `ffff88802a2bb108`, then `parent` would have to be at `ffff88802a2bb0b0` (because `type` is at offset 88 in `struct device`).
The core of the problem is figuring out why `parent` is pointing *inside* freed memory. The call stack shows this happening in `i2c_adapter_lock_bus`, specifically, likely within the `i2c_adapter_depth` function, which iterates up the device tree through `parent` pointers.
My initial thought was that `adapter` itself might be the freed object, with `adapter->dev.parent` then also being compromised. If `adapter` started at `ffff88802a2bb000`, then `adapter->dev.parent` would be at `ffff88802a2bb170`, but the crash is at `ffff88802a2bb108`. However, if *that* were the case, a read from the poisoned location should have triggered KASAN, but that's not what happened.
Wait a second. The report says the crash is at an offset of 264 bytes (`0x108` or `ffff88802a2bb108`) from the freed region, meaning `parent->type` is at `ffff88802a2bb108`. That implies `parent` is at offset `ffff88802a2bb0b0`, which IS inside the freed region, but *before* the offset where we'd read `adapter->dev.parent`!
So, `adapter->dev.parent` is *probably* valid. The problem is that the *value* of `adapter->dev.parent` points into freed memory. The `i2c_adapter_depth` function iterates: `for (parent = adapter->dev.parent; parent; parent = parent->parent)`. This means that if `parent` is at `ffff88802a2bb0b0`, the vulnerability happens when `parent->parent` is accessed, potentially leading to the crash when the code tries to access the `type` field of the invalid `parent->parent`.
Therefore, the critical question is: how did a `struct device` end up at `ffff88802a2bb0b0` *inside* the freed region? Is it the result of a corrupted pointer that was subsequently dereferenced? That is the most likely cause. We need to look for where `adapter->dev.parent` gets set, and *why* it might point to this region. I'd better check any structs containing the `struct device` field to see if the freed memory is related to one of those structures.
|
| 13/2 |
2026/07/28 01:32 |
tool |
grepper |
0m
Args: map[Expression:struct device[ \t]+[a-zA-Z0-9_]+;]
Results: map[Output:
Full output is too long, showing 500 out of 1470 lines.
Use more precise expression if possible.
Documentation/arch/s390/driver-model.rst=84=under include/asm/ccwdev.h::
--
Documentation/arch/s390/driver-model.rst-91- struct ccw_driver *drv;
Documentation/arch/s390/driver-model.rst:92: struct device dev;
Documentation/arch/s390/driver-model.rst-93- int online;
--
Documentation/driver-api/driver-model/overview.rst=46=struct pci_dev now looks like this::
--
Documentation/driver-api/driver-model/overview.rst-50-
Documentation/driver-api/driver-model/overview.rst:51: struct device dev; /* Generic device interface */
Documentation/driver-api/driver-model/overview.rst-52- ...
--
Documentation/driver-api/eisa.rst=137=encapsulated in a 'struct eisa_device' described as follows::
--
Documentation/driver-api/eisa.rst-145- u64 dma_mask;
Documentation/driver-api/eisa.rst:146: struct device dev; /* generic device */
Documentation/driver-api/eisa.rst-147- };
--
Documentation/driver-api/extcon.rst=63=The core structure representing an Extcon device::
--
Documentation/driver-api/extcon.rst-70- /* Internal data */
Documentation/driver-api/extcon.rst:71: struct device dev;
Documentation/driver-api/extcon.rst-72- unsigned int id;
--
Documentation/input/gameport-programming.rst=212=gameport.
--
Documentation/input/gameport-programming.rst-223- struct mutex drv_mutex; /* protects serio->drv so attributes can pin driver */
Documentation/input/gameport-programming.rst:224: struct device dev;
Documentation/input/gameport-programming.rst-225- struct list_head node;
--
arch/mips/kernel/vpe-mt.c=317=static struct class vpe_class = {
--
arch/mips/kernel/vpe-mt.c-322-
arch/mips/kernel/vpe-mt.c:323:static struct device vpe_device;
arch/mips/kernel/vpe-mt.c-324-
--
arch/mips/txx9/generic/setup.c=772=struct txx9_sramc_dev {
arch/mips/txx9/generic/setup.c:773: struct device dev;
arch/mips/txx9/generic/setup.c-774- struct bin_attribute bindata_attr;
--
arch/powerpc/include/asm/ps3.h=352=struct ps3_system_bus_device {
--
arch/powerpc/include/asm/ps3.h-369-/* struct iommu_table *iommu_table; -- waiting for BenH's cleanups */
arch/powerpc/include/asm/ps3.h:370: struct device core;
arch/powerpc/include/asm/ps3.h-371- void *driver_priv; /* private driver variables */
--
arch/powerpc/include/asm/spu.h=108=struct spu {
--
arch/powerpc/include/asm/spu.h-157-
arch/powerpc/include/asm/spu.h:158: struct device dev;
arch/powerpc/include/asm/spu.h-159-
--
arch/powerpc/include/asm/vio.h=96=struct vio_dev {
--
arch/powerpc/include/asm/vio.h-108- enum vio_dev_family family;
arch/powerpc/include/asm/vio.h:109: struct device dev;
arch/powerpc/include/asm/vio.h-110-};
--
arch/powerpc/platforms/pseries/cmm.c=52=static unsigned long simulate_loan_target_kb;
arch/powerpc/platforms/pseries/cmm.c:53:static struct device cmm_dev;
arch/powerpc/platforms/pseries/cmm.c-54-
--
arch/powerpc/platforms/pseries/suspend.c-18-
arch/powerpc/platforms/pseries/suspend.c:19:static struct device suspend_dev;
arch/powerpc/platforms/pseries/suspend.c-20-
--
arch/s390/include/asm/ccwdev.h=87=struct ccw_device {
--
arch/s390/include/asm/ccwdev.h-94- struct ccw_driver *drv;
arch/s390/include/asm/ccwdev.h:95: struct device dev;
arch/s390/include/asm/ccwdev.h-96- int online;
--
arch/s390/include/asm/eadm.h=85=struct scm_device {
--
arch/s390/include/asm/eadm.h-88- unsigned int nr_max_block;
arch/s390/include/asm/eadm.h:89: struct device dev;
arch/s390/include/asm/eadm.h-90- struct {
--
arch/sh/include/asm/dma.h=61=struct dma_channel {
--
arch/sh/include/asm/dma.h-79-
arch/sh/include/asm/dma.h:80: struct device dev;
arch/sh/include/asm/dma.h-81- void *priv_data;
--
block/bsg.c=24=struct bsg_device {
block/bsg.c-25- struct request_queue *queue;
block/bsg.c:26: struct device device;
block/bsg.c-27- struct cdev cdev;
--
drivers/acpi/platform_profile.c=19=struct platform_profile_handler {
drivers/acpi/platform_profile.c-20- const char *name;
drivers/acpi/platform_profile.c:21: struct device dev;
drivers/acpi/platform_profile.c-22- int minor;
--
drivers/ata/pata_parport/pata_parport.h=14=struct pi_adapter {
drivers/ata/pata_parport/pata_parport.h:15: struct device dev;
drivers/ata/pata_parport/pata_parport.h-16- struct pi_protocol *proto; /* adapter protocol */
--
drivers/auxdisplay/line-display.h=71=struct linedisp {
drivers/auxdisplay/line-display.h:72: struct device dev;
drivers/auxdisplay/line-display.h-73- struct timer_list timer;
--
drivers/base/attribute_container.c=25=struct internal_container {
--
drivers/base/attribute_container.c-27- struct attribute_container *cont;
drivers/base/attribute_container.c:28: struct device classdev;
drivers/base/attribute_container.c-29-};
--
drivers/base/core.c=4322=struct root_device {
drivers/base/core.c:4323: struct device dev;
drivers/base/core.c-4324- struct module *owner;
--
drivers/base/devcoredump.c=21=struct devcd_entry {
drivers/base/devcoredump.c:22: struct device devcd_dev;
drivers/base/devcoredump.c-23- void *data;
--
drivers/base/firmware_loader/sysfs.h=76=struct fw_sysfs {
drivers/base/firmware_loader/sysfs.h-77- bool nowait;
drivers/base/firmware_loader/sysfs.h:78: struct device dev;
drivers/base/firmware_loader/sysfs.h-79- struct fw_priv *fw_priv;
--
drivers/base/isa.c=16=struct isa_dev {
drivers/base/isa.c:17: struct device dev;
drivers/base/isa.c-18- struct device *next;
--
drivers/base/node.c=295=struct node_cache_info {
drivers/base/node.c:296: struct device dev;
drivers/base/node.c-297- struct list_head node;
--
drivers/base/soc.c=25=struct soc_device {
drivers/base/soc.c:26: struct device dev;
drivers/base/soc.c-27- struct soc_device_attribute *attr;
--
drivers/crypto/caam/blob_gen.c=35=struct caam_blob_priv {
drivers/crypto/caam/blob_gen.c:36: struct device jrdev;
drivers/crypto/caam/blob_gen.c-37-};
--
drivers/cxl/cxl.h=296=struct cxl_decoder {
drivers/cxl/cxl.h:297: struct device dev;
drivers/cxl/cxl.h-298- int id;
--
drivers/cxl/cxl.h=482=struct cxl_region {
drivers/cxl/cxl.h:483: struct device dev;
drivers/cxl/cxl.h-484- int id;
--
drivers/cxl/cxl.h=498=struct cxl_nvdimm_bridge {
drivers/cxl/cxl.h-499- int id;
drivers/cxl/cxl.h:500: struct device dev;
drivers/cxl/cxl.h-501- struct cxl_port *port;
--
drivers/cxl/cxl.h=512=struct cxl_nvdimm {
drivers/cxl/cxl.h:513: struct device dev;
drivers/cxl/cxl.h-514- struct cxl_memdev *cxlmd;
--
drivers/cxl/cxl.h=528=struct cxl_pmem_region {
drivers/cxl/cxl.h:529: struct device dev;
drivers/cxl/cxl.h-530- struct cxl_region *cxlr;
--
drivers/cxl/cxl.h=537=struct cxl_dax_region {
drivers/cxl/cxl.h:538: struct device dev;
drivers/cxl/cxl.h-539- struct cxl_region *cxlr;
--
drivers/cxl/cxl.h=568=struct cxl_port {
drivers/cxl/cxl.h:569: struct device dev;
drivers/cxl/cxl.h-570- struct device *uport_dev;
--
drivers/cxl/cxlmem.h=55=struct cxl_memdev {
drivers/cxl/cxlmem.h:56: struct device dev;
drivers/cxl/cxlmem.h-57- struct cdev cdev;
--
drivers/cxl/pmu.h=15=struct cxl_pmu {
drivers/cxl/pmu.h:16: struct device dev;
drivers/cxl/pmu.h-17- void __iomem *base;
--
drivers/dax/dax-private.h=49=struct dax_mapping {
drivers/dax/dax-private.h:50: struct device dev;
drivers/dax/dax-private.h-51- int range_id;
--
drivers/dax/dax-private.h=85=struct dev_dax {
--
drivers/dax/dax-private.h-94- struct ida ida;
drivers/dax/dax-private.h:95: struct device dev;
drivers/dax/dax-private.h-96- struct dev_pagemap *pgmap;
--
drivers/dma/idxd/idxd.h=39=struct idxd_dev {
drivers/dma/idxd/idxd.h:40: struct device conf_dev;
drivers/dma/idxd/idxd.h-41- enum idxd_dev_type type;
--
drivers/dma/ti/k3-udma-glue.c=24=struct k3_udma_glue_common {
drivers/dma/ti/k3-udma-glue.c-25- struct device *dev;
drivers/dma/ti/k3-udma-glue.c:26: struct device chan_dev;
drivers/dma/ti/k3-udma-glue.c-27- struct udma_dev *udmax;
--
drivers/edac/altera_edac.h=374=struct altr_edac_device_dev {
--
drivers/edac/altera_edac.h-383- struct edac_device_ctl_info *edac_dev;
drivers/edac/altera_edac.h:384: struct device ddev;
drivers/edac/altera_edac.h-385- int edac_idx;
--
drivers/edac/ie31200_edac.c=172=struct ie31200_priv {
--
drivers/edac/ie31200_edac.c-178- struct pci_dev *pdev;
drivers/edac/ie31200_edac.c:179: struct device dev;
drivers/edac/ie31200_edac.c-180-};
--
drivers/edac/igen6_edac.c=125=struct igen6_imc {
--
drivers/edac/igen6_edac.c-128- struct pci_dev *pdev;
drivers/edac/igen6_edac.c:129: struct device dev;
drivers/edac/igen6_edac.c-130- void __iomem *window;
--
drivers/extcon/extcon.h=42=struct extcon_dev {
--
drivers/extcon/extcon.h-48- /* Internal data. Please do not set. */
drivers/extcon/extcon.h:49: struct device dev;
drivers/extcon/extcon.h-50- unsigned int id;
--
drivers/firmware/google/coreboot_table.h=19=struct coreboot_device {
drivers/firmware/google/coreboot_table.h:20: struct device dev;
drivers/firmware/google/coreboot_table.h-21- union {
--
drivers/fsi/fsi-scom.c=66=struct scom_device {
--
drivers/fsi/fsi-scom.c-68- struct fsi_device *fsi_dev;
drivers/fsi/fsi-scom.c:69: struct device dev;
drivers/fsi/fsi-scom.c-70- struct cdev cdev;
--
drivers/fsi/i2cr-scom.c=13=struct i2cr_scom {
drivers/fsi/i2cr-scom.c:14: struct device dev;
drivers/fsi/i2cr-scom.c-15- struct cdev cdev;
--
drivers/gpu/drm/sti/sti_dvo.c=86=struct sti_dvo {
drivers/gpu/drm/sti/sti_dvo.c:87: struct device dev;
drivers/gpu/drm/sti/sti_dvo.c-88- struct drm_device *drm_dev;
--
drivers/gpu/drm/sti/sti_hda.c=244=struct sti_hda {
drivers/gpu/drm/sti/sti_hda.c:245: struct device dev;
drivers/gpu/drm/sti/sti_hda.c-246- struct drm_device *drm_dev;
--
drivers/gpu/drm/sti/sti_hdmi.h=66=struct sti_hdmi {
drivers/gpu/drm/sti/sti_hdmi.h:67: struct device dev;
drivers/gpu/drm/sti/sti_hdmi.h-68- struct drm_device *drm_dev;
--
drivers/hwmon/hwmon.c=35=struct hwmon_device {
--
drivers/hwmon/hwmon.c-37- const char *label;
drivers/hwmon/hwmon.c:38: struct device dev;
drivers/hwmon/hwmon.c-39- const struct hwmon_chip_info *chip;
--
drivers/hwtracing/coresight/coresight-syscfg.h=47=struct cscfg_manager {
drivers/hwtracing/coresight/coresight-syscfg.h:48: struct device dev;
drivers/hwtracing/coresight/coresight-syscfg.h-49- struct list_head csdev_desc_list;
--
drivers/i2c/i2c-dev.c=42=struct i2c_dev {
--
drivers/i2c/i2c-dev.c-44- struct i2c_adapter *adap;
drivers/i2c/i2c-dev.c:45: struct device dev;
drivers/i2c/i2c-dev.c-46- struct cdev cdev;
--
drivers/infiniband/core/ucaps.c=20=struct ib_ucap {
drivers/infiniband/core/ucaps.c-21- struct cdev cdev;
drivers/infiniband/core/ucaps.c:22: struct device dev;
drivers/infiniband/core/ucaps.c-23- struct kref ref;
--
drivers/input/evdev.c=28=struct evdev {
--
drivers/input/evdev.c-34- struct mutex mutex;
drivers/input/evdev.c:35: struct device dev;
drivers/input/evdev.c-36- struct cdev cdev;
--
drivers/input/joydev.c=35=struct joydev {
--
drivers/input/joydev.c-41- struct mutex mutex;
drivers/input/joydev.c:42: struct device dev;
drivers/input/joydev.c-43- struct cdev cdev;
--
drivers/input/mousedev.c=58=struct mousedev {
--
drivers/input/mousedev.c-64- struct mutex mutex;
drivers/input/mousedev.c:65: struct device dev;
drivers/input/mousedev.c-66- struct cdev cdev;
--
drivers/input/rmi4/rmi_bus.h=36=struct rmi_function {
--
drivers/input/rmi4/rmi_bus.h-38- struct rmi_device *rmi_dev;
drivers/input/rmi4/rmi_bus.h:39: struct device dev;
drivers/input/rmi4/rmi_bus.h-40- struct list_head node;
--
drivers/iommu/iommufd/selftest.c=176=struct mock_dev {
drivers/iommu/iommufd/selftest.c:177: struct device dev;
drivers/iommu/iommufd/selftest.c-178- struct mock_viommu *viommu;
--
drivers/mailbox/zynqmp-ipi-mailbox.c=110=struct zynqmp_ipi_mbox {
drivers/mailbox/zynqmp-ipi-mailbox.c-111- struct zynqmp_ipi_pdata *pdata;
drivers/mailbox/zynqmp-ipi-mailbox.c:112: struct device dev;
drivers/mailbox/zynqmp-ipi-mailbox.c-113- u32 remote_id;
--
drivers/media/pci/bt8xx/bttv.h=331=struct bttv_sub_device {
drivers/media/pci/bt8xx/bttv.h:332: struct device dev;
drivers/media/pci/bt8xx/bttv.h-333- struct bttv_core *core;
--
drivers/media/pci/ngene/ngene.h=600=struct ngene_channel {
drivers/media/pci/ngene/ngene.h:601: struct device device;
drivers/media/pci/ngene/ngene.h-602- struct i2c_adapter i2c_adapter;
--
drivers/media/pci/ngene/ngene.h=683=struct ngene_ci {
drivers/media/pci/ngene/ngene.h:684: struct device device;
drivers/media/pci/ngene/ngene.h-685- struct i2c_adapter i2c_adapter;
--
drivers/misc/mei/mei_dev.h=582=struct mei_device {
drivers/misc/mei/mei_dev.h-583- struct device *parent;
drivers/misc/mei/mei_dev.h:584: struct device dev;
drivers/misc/mei/mei_dev.h-585- struct cdev *cdev;
--
drivers/misc/ocxl/ocxl_internal.h=16=struct ocxl_fn {
drivers/misc/ocxl/ocxl_internal.h:17: struct device dev;
drivers/misc/ocxl/ocxl_internal.h-18- int bar_used[3];
--
drivers/misc/ocxl/ocxl_internal.h=30=struct ocxl_file_info {
drivers/misc/ocxl/ocxl_internal.h-31- struct ocxl_afu *afu;
drivers/misc/ocxl/ocxl_internal.h:32: struct device dev;
drivers/misc/ocxl/ocxl_internal.h-33- struct cdev cdev;
--
drivers/mmc/core/block.c=168=struct mmc_rpmb_data {
drivers/mmc/core/block.c:169: struct device dev;
drivers/mmc/core/block.c-170- struct cdev chrdev;
--
drivers/most/core.c=38=struct most_channel {
drivers/most/core.c:39: struct device dev;
drivers/most/core.c-40- struct completion cleanup;
--
drivers/most/most_usb.c=65=struct most_dci_obj {
drivers/most/most_usb.c:66: struct device dev;
drivers/most/most_usb.c-67- struct usb_device *usb_device;
--
drivers/most/most_usb.c=102=struct most_dev {
drivers/most/most_usb.c:103: struct device dev;
drivers/most/most_usb.c-104- struct usb_device *usb_device;
--
drivers/mtd/ubi/ubi.h=330=struct ubi_volume {
drivers/mtd/ubi/ubi.h:331: struct device dev;
drivers/mtd/ubi/ubi.h-332- struct cdev cdev;
--
drivers/mtd/ubi/ubi.h=555=struct ubi_device {
drivers/mtd/ubi/ubi.h-556- struct cdev cdev;
drivers/mtd/ubi/ubi.h:557: struct device dev;
drivers/mtd/ubi/ubi.h-558- int ubi_num;
--
drivers/net/ethernet/hisilicon/hns/hnae.h=530=struct hnae_ae_dev {
drivers/net/ethernet/hisilicon/hns/hnae.h:531: struct device cls_dev; /* the class dev */
drivers/net/ethernet/hisilicon/hns/hnae.h-532- struct device *dev; /* the presented dev */
--
drivers/net/ethernet/netronome/nfp/nfpcore/nfp_cppcore.c=59=struct nfp_cpp {
drivers/net/ethernet/netronome/nfp/nfpcore/nfp_cppcore.c:60: struct device dev;
drivers/net/ethernet/netronome/nfp/nfpcore/nfp_cppcore.c-61-
--
drivers/net/netdevsim/netdevsim.h=475=struct nsim_bus_dev {
drivers/net/netdevsim/netdevsim.h:476: struct device dev;
drivers/net/netdevsim/netdevsim.h-477- struct list_head list;
--
drivers/net/wwan/wwan_core.c=58=struct wwan_device {
--
drivers/net/wwan/wwan_core.c-60- int refcount;
drivers/net/wwan/wwan_core.c:61: struct device dev;
drivers/net/wwan/wwan_core.c-62- const struct wwan_ops *ops;
--
drivers/net/wwan/wwan_core.c=85=struct wwan_port {
--
drivers/net/wwan/wwan_core.c-90- struct mutex ops_lock; /* Serialize ops + protect against removal */
drivers/net/wwan/wwan_core.c:91: struct device dev;
drivers/net/wwan/wwan_core.c-92- struct sk_buff_head rxq;
--
drivers/net/wwan/wwan_hwsim.c=42=struct wwan_hwsim_dev {
--
drivers/net/wwan/wwan_hwsim.c-44- unsigned int id;
drivers/net/wwan/wwan_hwsim.c:45: struct device dev;
drivers/net/wwan/wwan_hwsim.c-46- struct work_struct del_work;
--
drivers/ntb/ntb_transport.c=229=struct ntb_transport_client_dev {
--
drivers/ntb/ntb_transport.c-231- struct ntb_transport_ctx *nt;
drivers/ntb/ntb_transport.c:232: struct device dev;
drivers/ntb/ntb_transport.c-233-};
--
drivers/nvdimm/nd-core.h=19=struct nvdimm_bus {
--
drivers/nvdimm/nd-core.h-22- struct list_head list;
drivers/nvdimm/nd-core.h:23: struct device dev;
drivers/nvdimm/nd-core.h-24- int id, probe_active;
--
drivers/nvdimm/nd-core.h=31=struct nvdimm {
--
drivers/nvdimm/nd-core.h-34- unsigned long cmd_mask;
drivers/nvdimm/nd-core.h:35: struct device dev;
drivers/nvdimm/nd-core.h-36- atomic_t busy;
--
drivers/nvdimm/nd.h=402=struct nd_region {
drivers/nvdimm/nd.h:403: struct device dev;
drivers/nvdimm/nd.h-404- struct ida ns_ida;
--
drivers/nvdimm/nd.h=447=struct nd_btt {
drivers/nvdimm/nd.h:448: struct device dev;
drivers/nvdimm/nd.h-449- struct nd_namespace_common *ndns;
--
drivers/nvdimm/nd.h=466=struct nd_pfn {
--
drivers/nvdimm/nd.h-468- uuid_t *uuid;
drivers/nvdimm/nd.h:469: struct device dev;
drivers/nvdimm/nd.h-470- unsigned long align;
--
drivers/nvme/host/nvme.h=334=struct nvme_ctrl {
--
drivers/nvme/host/nvme.h-352- struct srcu_struct srcu;
drivers/nvme/host/nvme.h:353: struct device ctrl_device;
drivers/nvme/host/nvme.h-354- struct device *device; /* char device */
--
drivers/phy/tegra/xusb.h=167=struct tegra_xusb_pad {
--
drivers/phy/tegra/xusb.h-171- struct phy **lanes;
drivers/phy/tegra/xusb.h:172: struct device dev;
drivers/phy/tegra/xusb.h-173-
--
drivers/phy/tegra/xusb.h=276=struct tegra_xusb_port {
--
drivers/phy/tegra/xusb.h-281- struct list_head list;
drivers/phy/tegra/xusb.h:282: struct device dev;
drivers/phy/tegra/xusb.h-283-
--
drivers/platform/chrome/wilco_ec/event.c=190=struct event_device_data {
--
drivers/platform/chrome/wilco_ec/event.c-193- wait_queue_head_t wq;
drivers/platform/chrome/wilco_ec/event.c:194: struct device dev;
drivers/platform/chrome/wilco_ec/event.c-195- struct cdev cdev;
--
drivers/platform/chrome/wilco_ec/telemetry.c=203=struct telem_device_data {
drivers/platform/chrome/wilco_ec/telemetry.c:204: struct device dev;
drivers/platform/chrome/wilco_ec/telemetry.c-205- struct cdev cdev;
--
drivers/platform/x86/intel_scu_ipc.c=58=struct intel_scu_ipc_dev {
drivers/platform/x86/intel_scu_ipc.c:59: struct device dev;
drivers/platform/x86/intel_scu_ipc.c-60- struct module *owner;
--
drivers/power/sequencing/core.c=233=struct pwrseq_device {
drivers/power/sequencing/core.c:234: struct device dev;
drivers/power/sequencing/core.c-235- int id;
--
drivers/ptp/ptp_private.h=44=struct ptp_clock {
drivers/ptp/ptp_private.h-45- struct posix_clock clock;
drivers/ptp/ptp_private.h:46: struct device dev;
drivers/ptp/ptp_private.h-47- struct ptp_clock_info *info;
--
drivers/pwm/core.c=1101=struct pwm_export {
drivers/pwm/core.c:1102: struct device pwm_dev;
drivers/pwm/core.c-1103- struct pwm_device *pwm;
--
drivers/remoteproc/qcom_wcnss_iris.c=20=struct qcom_iris {
drivers/remoteproc/qcom_wcnss_iris.c:21: struct device dev;
drivers/remoteproc/qcom_wcnss_iris.c-22-
--
drivers/rpmsg/qcom_glink_smem.c=38=struct qcom_glink_smem {
drivers/rpmsg/qcom_glink_smem.c:39: struct device dev;
drivers/rpmsg/qcom_glink_smem.c-40-
--
drivers/rpmsg/qcom_smd.c=116=struct qcom_smd_edge {
drivers/rpmsg/qcom_smd.c:117: struct device dev;
drivers/rpmsg/qcom_smd.c-118-
--
drivers/rpmsg/rpmsg_char.c=58=struct rpmsg_eptdev {
drivers/rpmsg/rpmsg_char.c:59: struct device dev;
drivers/rpmsg/rpmsg_char.c-60- struct cdev cdev;
--
drivers/rpmsg/rpmsg_ctrl.c=48=struct rpmsg_ctrldev {
--
drivers/rpmsg/rpmsg_ctrl.c-50- struct cdev cdev;
drivers/rpmsg/rpmsg_ctrl.c:51: struct device dev;
drivers/rpmsg/rpmsg_ctrl.c-52- struct mutex ctrl_lock;
--
drivers/s390/block/dcssblk.c=67=struct dcssblk_dev_info {
drivers/s390/block/dcssblk.c-68- struct list_head lh;
drivers/s390/block/dcssblk.c:69: struct device dev;
drivers/s390/block/dcssblk.c-70- char segment_name[DCSS_BUS_ID_SIZE];
--
drivers/s390/cio/chp.h=43=struct channel_path {
drivers/s390/cio/chp.h:44: struct device dev;
drivers/s390/cio/chp.h-45- struct chp_id chpid;
--
drivers/s390/cio/cio.h=84=struct subchannel {
--
drivers/s390/cio/cio.h-99- struct chsc_ssd_info ssd_info; /* subchannel description */
drivers/s390/cio/cio.h:100: struct device dev; /* entry in device tree */
drivers/s390/cio/cio.h-101- struct css_driver *driver;
--
drivers/s390/cio/css.h=124=struct channel_subsystem {
--
drivers/s390/cio/css.h-128- struct channel_path *chps[__MAX_CHPID + 1];
drivers/s390/cio/css.h:129: struct device device;
drivers/s390/cio/css.h-130- struct pgid global_pgid;
--
drivers/s390/crypto/ap_bus.h=167=struct ap_device {
drivers/s390/crypto/ap_bus.h:168: struct device device;
]
|
| 14/2 |
2026/07/28 01:32 |
llm |
expert |
0m
Model:
Tokens:
input: 25580
output: 36
thoughts: 673
Thoughts: **Debugging a Suspect Pointer Dereference**
Okay, wait a minute. The `struct i2c_adapter` itself contains a `struct device dev;` member. Right, let's just double-check the `i2c_adapter` structure layout again to be certain. Ah, yes, `dev` is at an offset of 2432 bits, which is 304 bytes, or `0x130`. Now, I was assuming the `adapter` was at `ffff88802a2bb000`, so that would put `adapter->dev` at `ffff88802a2bb130`. But the issue is, the `parent` pointer is at `ffff88802a2bb0b0`.
Hold on. What if my assumption about the `adapter` base address is wrong? What if the actual `adapter` is at `ffff88802a2bb0b0`? Okay, let's recalculate. If the `adapter` *is* at `ffff88802a2bb0b0`, then `adapter->dev` would be at `ffff88802a2bb0b0 + 0x130`, which equals `ffff88802a2bb1e0`. But the actual read is at `ffff88802a2bb108`.
Wait, the code is reading `parent->type`. This means `parent` is a pointer. If `parent` *is* `ffff88802a2bb0b0`, then `parent->type` would be at `ffff88802a2bb0b0 + 88`, which is `ffff88802a2bb108`. Perfect, it matches *exactly*! That confirms that `parent` is indeed `ffff88802a2bb0b0`.
But *why* is `parent` `ffff88802a2bb0b0`? Because `adapter->dev.parent` was `ffff88802a2bb0b0`. Right. Where did that come from, though? It was read from `adapter->dev.parent`. Now, if `adapter` is a valid `i2c_adapter`, why is its `dev.parent` pointing to `ffff88802a2bb0b0`?
Aha! Maybe `adapter->dev.parent` points to a `struct device` that's embedded within some other struct, and that other struct was allocated at `ffff88802a2bb000` and subsequently freed? Let's check. The freed object *is* at `ffff88802a2bb000`, and its size is 2048. The `struct device` is located at `ffff88802a2bb0b0`. The offset is `0xb0`, which is 176 bytes. Okay, the question now is, what struct has a `struct device` member at offset 176? Let me check the grep output for `struct device dev;` and see if any structs match offset 176. Wait a minute... Could it be `struct usb_device`? Time to check the `struct usb_device` layout!
|
| 15/2 |
2026/07/28 01:32 |
tool |
codesearch-struct-layout |
0m
Args: map[ContextFile:drivers/usb/core/usb.c Name:usb_device]
Results: map[Fields:[map[Name:devnum OffsetBits:0 SizeBits:32] map[Name:devpath OffsetBits:32 SizeBits:128] map[Name:route OffsetBits:160 SizeBits:32] map[Name:state OffsetBits:192 SizeBits:32] map[Name:speed OffsetBits:224 SizeBits:32] map[Name:rx_lanes OffsetBits:256 SizeBits:32] map[Name:tx_lanes OffsetBits:288 SizeBits:32] map[Name:ssp_rate OffsetBits:320 SizeBits:32] map[Name:tt OffsetBits:384 SizeBits:64] map[Name:ttport OffsetBits:448 SizeBits:32] map[Name:toggle OffsetBits:480 SizeBits:64] map[Name:parent OffsetBits:576 SizeBits:64] map[Name:bus OffsetBits:640 SizeBits:64] map[Name:ep0 OffsetBits:704 SizeBits:704] map[Name:dev OffsetBits:1408 SizeBits:9792] map[Name:descriptor OffsetBits:11200 SizeBits:144] map[Name:bos OffsetBits:11392 SizeBits:64] map[Name:config OffsetBits:11456 SizeBits:64] map[Name:actconfig OffsetBits:11520 SizeBits:64] map[Name:ep_in OffsetBits:11584 SizeBits:1024] map[Name:ep_out OffsetBits:12608 SizeBits:1024] map[Name:rawdescriptors OffsetBits:13632 SizeBits:64] map[Name:bus_mA OffsetBits:13696 SizeBits:16] map[Name:portnum OffsetBits:13712 SizeBits:8] map[Name:level OffsetBits:13720 SizeBits:8] map[Name:devaddr OffsetBits:13728 SizeBits:8] map[Name:can_submit OffsetBits:13736 SizeBits:1] map[Name:persist_enabled OffsetBits:13737 SizeBits:1] map[Name:reset_in_progress OffsetBits:13738 SizeBits:1] map[Name:have_langid OffsetBits:13739 SizeBits:1] map[Name:authorized OffsetBits:13740 SizeBits:1] map[Name:authenticated OffsetBits:13741 SizeBits:1] map[Name:lpm_capable OffsetBits:13742 SizeBits:1] map[Name:lpm_devinit_allow OffsetBits:13743 SizeBits:1] map[Name:usb2_hw_lpm_capable OffsetBits:13744 SizeBits:1] map[Name:usb2_hw_lpm_besl_capable OffsetBits:13745 SizeBits:1] map[Name:usb2_hw_lpm_enabled OffsetBits:13746 SizeBits:1] map[Name:usb2_hw_lpm_allowed OffsetBits:13747 SizeBits:1] map[Name:usb3_lpm_u1_enabled OffsetBits:13748 SizeBits:1] map[Name:usb3_lpm_u2_enabled OffsetBits:13749 SizeBits:1] map[Name:string_langid OffsetBits:13760 SizeBits:32] map[Name:product OffsetBits:13824 SizeBits:64] map[Name:manufacturer OffsetBits:13888 SizeBits:64] map[Name:serial OffsetBits:13952 SizeBits:64] map[Name:filelist OffsetBits:14016 SizeBits:128] map[Name:maxchild OffsetBits:14144 SizeBits:32] map[Name:quirks OffsetBits:14176 SizeBits:32] map[Name:urbnum OffsetBits:14208 SizeBits:32] map[Name:active_duration OffsetBits:14272 SizeBits:64] map[Name:connect_time OffsetBits:14336 SizeBits:64] map[Name:do_remote_wakeup OffsetBits:14400 SizeBits:1] map[Name:reset_resume OffsetBits:14401 SizeBits:1] map[Name:port_is_suspended OffsetBits:14402 SizeBits:1] map[Name:offload_pm_locked OffsetBits:14403 SizeBits:1] map[Name:offload_usage OffsetBits:14432 SizeBits:32] map[Name:offload_lock OffsetBits:14464 SizeBits:512] map[Name:tunnel_mode OffsetBits:14976 SizeBits:32] map[Name:usb4_link OffsetBits:15040 SizeBits:64] map[Name:slot_id OffsetBits:15104 SizeBits:32] map[Name:l1_params OffsetBits:15136 SizeBits:64] map[Name:u1_params OffsetBits:15200 SizeBits:128] map[Name:u2_params OffsetBits:15328 SizeBits:128] map[Name:lpm_disable_count OffsetBits:15456 SizeBits:32] map[Name:hub_delay OffsetBits:15488 SizeBits:16] map[Name:use_generic_driver OffsetBits:15504 SizeBits:1]]]
|
| 16/2 |
2026/07/28 01:32 |
llm |
expert |
0m
Model:
Tokens:
input: 28517
output: 36
thoughts: 864
Thoughts: **Analyzing a Suspected Use-After-Free in USB/I2C Interaction**
Okay, so I'm looking at this memory layout. I see that `struct usb_device` has a `dev` field, and it's quite a ways into the structure, at an offset of 176 bytes. Interesting. It's clear that `dev` is a `struct device` embedded within the larger `struct usb_device`. I've pinned down the address of a particular `struct usb_device` at `ffff88802a2bb000`.
Let's trace its allocation. A quick look at the allocation trace confirms that this page was originally allocated for a `struct usb_device` via `usb_alloc_dev`. It looks like it was later freed, then reallocated as an `sk_buff` data buffer and freed again. Now, the `i2c_adapter` code is attempting to access `parent->type`, where `parent` is that `dev` field within the freed `struct usb_device`. That's a textbook use-after-free scenario.
The really interesting question is *why* an `i2c_adapter` is pointing to a freed `struct usb_device` in the first place. My initial hypothesis is that the `i2c_adapter` was registered as a child of the `usb_device`, or perhaps one of its interfaces. If the `usb_device` was deallocated before the `i2c_adapter` was unregistered, we have our use-after-free.
But wait. Aren't `i2c_adapter`s supposed to manage references to their parents? I remember `i2c_add_adapter` calls `device_register(&adapter->dev)`. Critically, `adapter->dev.parent` is set *before* that call. And `device_register` will call `get_device(adapter->dev.parent)`. That should increment the refcount of the parent `struct device`. If the parent's refcount is incremented, the underlying `struct usb_device` *should* be protected from premature freeing. So the `struct usb_device` should only be released when the `i2c_adapter` is unregistered, and its `dev` is released. But why isn't that happening here?
I need to understand how the `i2c_adapter` is created in the context of a USB driver. Generally, they're created in the driver's `probe` function. The corresponding `disconnect` function should unregister the `i2c_adapter` when the USB device is removed. The problem must be that the driver forgot to unregister the adapter, or there's a race condition.
Hold on, I'm missing something fundamental. `get_device(adapter->dev.parent)` increments the refcount of the *parent* `struct device`. For a `struct usb_device`, the `dev` field *is* the `struct device`. So, calling `get_device` on `&usb_dev->dev` should increment the refcount on `usb_dev->dev`, right? And the `struct usb_device` is freed in the `release` function of its `dev`. Thus, `usb_dev` *cannot* be freed as long as `adapter->dev.parent` holds a reference to it! This is really odd. Unless... the parent isn't actually the `usb_device` itself, but a `usb_interface`? I need to check the layout of the `struct usb_interface` now.
|
| 17/2 |
2026/07/28 01:32 |
tool |
codesearch-struct-layout |
0m
Args: map[ContextFile:drivers/usb/core/usb.c Name:usb_interface]
Results: map[Fields:[map[Name:altsetting OffsetBits:0 SizeBits:64] map[Name:cur_altsetting OffsetBits:64 SizeBits:64] map[Name:num_altsetting OffsetBits:128 SizeBits:32] map[Name:intf_assoc OffsetBits:192 SizeBits:64] map[Name:minor OffsetBits:256 SizeBits:32] map[Name:condition OffsetBits:288 SizeBits:32] map[Name:sysfs_files_created OffsetBits:320 SizeBits:1] map[Name:ep_devs_created OffsetBits:321 SizeBits:1] map[Name:unregistering OffsetBits:322 SizeBits:1] map[Name:needs_remote_wakeup OffsetBits:323 SizeBits:1] map[Name:needs_altsetting0 OffsetBits:324 SizeBits:1] map[Name:needs_binding OffsetBits:325 SizeBits:1] map[Name:resetting_device OffsetBits:326 SizeBits:1] map[Name:authorized OffsetBits:327 SizeBits:1] map[Name:wireless_status OffsetBits:352 SizeBits:32] map[Name:wireless_status_work OffsetBits:384 SizeBits:576] map[Name:dev OffsetBits:960 SizeBits:9792] map[Name:usb_dev OffsetBits:10752 SizeBits:64] map[Name:reset_ws OffsetBits:10816 SizeBits:576]]]
|
| 18/2 |
2026/07/28 01:32 |
llm |
expert |
0m
Model:
Tokens:
input: 30611
output: 59
thoughts: 211
Thoughts: **Debugging a USB Driver Crash**
Okay, so I'm looking at this crash report, and the clues are starting to fall into place. The error log is pinpointing an issue with the `parent` field. The `struct usb_interface` has its `dev` field at an offset of 120 bytes, that's straightforward. But, the crash log is reporting `parent` at 176 bytes. That immediately suggests that `parent` is actually a `struct usb_device`'s `dev` field. Let's see...
Aha! If the crash is saying that `parent` is equivalent to `usb_dev->dev` and that the underlying `usb_dev` has already been freed, that means the reference count on `usb_dev->dev` hit zero. This makes me suspect a reference counting issue somewhere. But there's a problem here. My `adapter->dev.parent` is pointing to *that* memory. That should not be happening. It should never be pointing to freed memory.
This suggests a timeline violation. I need to figure out how `adapter->dev.parent` is pointing to freed memory. Perhaps, `adapter->dev.parent` was set, but the `device_register` function hasn't been called yet? Maybe `device_register` failed, but `adapter->dev.parent` still got set, and then someone called `i2c_adapter_depth`? That would definitely explain the crash. Now, let's see what's calling `i2c_adapter_depth` to investigate this further. Time to trace the execution path.
|
| 19/2 |
2026/07/28 01:32 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-base.c IncludeSnippetLines:0 Name:i2c_adapter_depth SourceTreePrefix:]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:delete_device_store SourceFile:drivers/i2c/i2c-core-base.c SourceLine:1355] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:i2c_adapter_lock_bus SourceFile:drivers/i2c/i2c-core-base.c SourceLine:849] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:i2c_deregister_clients SourceFile:drivers/i2c/i2c-core-base.c SourceLine:1776] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:i2c_mux_lock_bus SourceFile:drivers/i2c/i2c-mux.c SourceLine:135] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:i2c_parent_lock_bus SourceFile:drivers/i2c/i2c-mux.c SourceLine:172] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__UNIQUE_ID_addressable_i2c_adapter_depth_1224 SourceFile:drivers/i2c/i2c-core-base.c SourceLine:1251] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__gendwarfksyms_ptr_i2c_adapter_depth SourceFile:drivers/i2c/i2c-core-base.c SourceLine:1251] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__gendwarfksyms_ptr_i2c_adapter_depth SourceFile:drivers/i2c/i2c-core-base.c SourceLine:1251]] TruncatedOutput:false]
|
| 20/2 |
2026/07/28 01:32 |
llm |
expert |
0m
Model:
Tokens:
input: 31774
output: 34
thoughts: 244
Thoughts: **Debugging a Persistent I2C Driver Crash**
Okay, so I'm tracking this crash down, and it looks like it's happening in `i2c_adapter_lock_bus`. That function's called by `i2c_transfer`, which in turn gets called through `i2cdev_ioctl_rdwr` when someone uses a `/dev/i2c-X` ioctl. Right, so if `/dev/i2c-X` is open, the corresponding `i2c_adapter` must have been registered somewhere in the system. And if the file is open, it should be holding a reference to the `i2c_adapter` itself.
Now, when the USB device is disconnected, the driver correctly calls `i2c_del_adapter`. This removes the adapter from the I2C subsystem. But wait, it looks like the `i2c_adapter` struct itself is *only* freed when its reference count drops to zero. That's the crux of this.
But hang onâŚ`i2c_del_adapter` calls `device_unregister(&adap->dev)`. And *that* calls `device_del` and then `put_device`. I know I've seen some funny things happen with `device_del` and reference counting. Let's see, `device_del` does something important with the parent device in the device tree... I'd better double check that bit of code in `drivers/base/core.c` immediately. Something's not right here...
|
| 21/2 |
2026/07/28 01:32 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/base/core.c Name:device_del]
Results: map[SourceCode:3902: /**
3903: * device_del - delete device from system.
3904: * @dev: device.
3905: *
3906: * This is the first part of the device unregistration
3907: * sequence. This removes the device from the lists we control
3908: * from here, has it removed from the other driver model
3909: * subsystems it was added to in device_add(), and removes it
3910: * from the kobject hierarchy.
3911: *
3912: * NOTE: this should be called manually _iff_ device_add() was
3913: * also called manually.
3914: */
3915: void device_del(struct device *dev)
3916: {
3917: struct subsys_private *sp;
3918: struct device *parent = dev->parent;
3919: struct kobject *glue_dir = NULL;
3920: struct class_interface *class_intf;
3921: unsigned int noio_flag;
3922:
3923: device_lock(dev);
3924: kill_device(dev);
3925: device_unlock(dev);
3926:
3927: if (dev->fwnode && dev->fwnode->dev == dev)
3928: dev->fwnode->dev = NULL;
3929:
3930: /* Notify clients of device removal. This call must come
3931: * before dpm_sysfs_remove().
3932: */
3933: noio_flag = memalloc_noio_save();
3934: bus_notify(dev, BUS_NOTIFY_DEL_DEVICE);
3935:
3936: dpm_sysfs_remove(dev);
3937: if (parent)
3938: klist_del(&dev->p->knode_parent);
3939: if (MAJOR(dev->devt)) {
3940: devtmpfs_delete_node(dev);
3941: device_remove_sys_dev_entry(dev);
3942: device_remove_file(dev, &dev_attr_dev);
3943: }
3944:
3945: sp = class_to_subsys(dev->class);
3946: if (sp) {
3947: device_remove_class_symlinks(dev);
3948:
3949: mutex_lock(&sp->mutex);
3950: /* notify any interfaces that the device is now gone */
3951: list_for_each_entry(class_intf, &sp->interfaces, node)
3952: if (class_intf->remove_dev)
3953: class_intf->remove_dev(dev);
3954: /* remove the device from the class list */
3955: klist_del(&dev->p->knode_class);
3956: mutex_unlock(&sp->mutex);
3957: subsys_put(sp);
3958: }
3959: device_remove_file(dev, &dev_attr_uevent);
3960: device_remove_attrs(dev);
3961: bus_remove_device(dev);
3962: device_pm_remove(dev);
3963: driver_deferred_probe_del(dev);
3964: device_platform_notify_remove(dev);
3965: device_links_purge(dev);
3966:
3967: /*
3968: * If a device does not have a driver attached, we need to clean
3969: * up any managed resources. We do this in device_release(), but
3970: * it's never called (and we leak the device) if a managed
3971: * resource holds a reference to the device. So release all
3972: * managed resources here, like we do in driver_detach(). We
3973: * still need to do so again in device_release() in case someone
3974: * adds a new resource after this point, though.
3975: */
3976: devres_release_all(dev);
3977:
3978: bus_notify(dev, BUS_NOTIFY_REMOVED_DEVICE);
3979: kobject_uevent(&dev->kobj, KOBJ_REMOVE);
3980: glue_dir = get_glue_dir(dev);
3981: kobject_del(&dev->kobj);
3982: cleanup_glue_dir(dev, glue_dir);
3983: memalloc_noio_restore(noio_flag);
3984: put_device(parent);
3985: }
SourceFile:drivers/base/core.c]
|
| 22/2 |
2026/07/28 01:32 |
llm |
expert |
0m
Model:
Tokens:
input: 33719
output: 46
thoughts: 324
Thoughts: **The I2C Adapter: A Post-Deletion Memory Landmine**
Okay, so I've been digging into this I2C driver code, and I think I've found a pretty nasty use-after-free scenario. Line 3984 of `device_del` is where it starts to unravel. It calls `put_device(parent)`. That's a key function!
Now, when `i2c_del_adapter` gets called, it calls `device_unregister(&adap->dev)`. That's reasonable. And then `device_unregister` calls `device_del(&adap->dev)`. Here's the kicker: `device_del` is dropping the reference count on `adap->dev.parent`. If that was the *last* reference to the parent, the `usb_device` it refers to is freed. That's a classic memory management pattern.
The problem? `adap->dev.parent` isn't *set* to NULL after the `usb_device` is freed! It's still pointing to that liberated memory! That's just asking for trouble.
Let's assume that some user space application still has `/dev/i2c-X` open. Now it calls `ioctl(I2C_RDWR)`. That ioctl function is going to lead to `i2c_transfer`, right? And `i2c_transfer` calls `i2c_adapter_lock_bus`. `i2c_adapter_lock_bus` then calls `i2c_adapter_depth(adapter)`, and that's where we hit the jackpot! `i2c_adapter_depth` accesses `adapter->dev.parent->type`. But `adapter->dev.parent` is pointing to that freed `usb_device` memory! It's the classic use-after-free! The dreaded dangling pointer!
Hold on, I need to check something here. I think there might be a flaw here, is it even allowed to call `i2c_transfer` after `i2c_del_adapter`? I need to check what `i2cdev_ioctl_rdwr` does and then I'll be certain.
|
| 23/2 |
2026/07/28 01:32 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-dev.c Name:i2cdev_ioctl_rdwr]
Results: map[SourceCode: 243: static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client,
244: unsigned nmsgs, struct i2c_msg *msgs)
245: {
246: u8 __user **data_ptrs;
247: int i, res;
248:
249: /* Adapter must support I2C transfers */
250: if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
251: return -EOPNOTSUPP;
252:
253: data_ptrs = kmalloc_array(nmsgs, sizeof(u8 __user *), GFP_KERNEL);
254: if (!data_ptrs)
255: return -ENOMEM;
256:
257: res = 0;
258: for (i = 0; i < nmsgs; i++) {
259: /* Limit the size of the message to a sane amount */
260: if (msgs[i].len > 8192) {
261: res = -EINVAL;
262: break;
263: }
264:
265: data_ptrs[i] = (u8 __user *)msgs[i].buf;
266: msgs[i].buf = memdup_user(data_ptrs[i], msgs[i].len);
267: if (IS_ERR(msgs[i].buf)) {
268: res = PTR_ERR(msgs[i].buf);
269: break;
270: }
271: /* memdup_user allocates with GFP_KERNEL, so DMA is ok */
272: msgs[i].flags |= I2C_M_DMA_SAFE;
273:
274: /*
275: * If the message length is received from the slave (similar
276: * to SMBus block read), we must ensure that the buffer will
277: * be large enough to cope with a message length of
278: * I2C_SMBUS_BLOCK_MAX as this is the maximum underlying bus
279: * drivers allow. The first byte in the buffer must be
280: * pre-filled with the number of extra bytes, which must be
281: * at least one to hold the message length, but can be
282: * greater (for example to account for a checksum byte at
283: * the end of the message.)
284: */
285: if (msgs[i].flags & I2C_M_RECV_LEN) {
286: if (!(msgs[i].flags & I2C_M_RD) ||
287: msgs[i].len < 1 || msgs[i].buf[0] < 1 ||
288: msgs[i].len < msgs[i].buf[0] +
289: I2C_SMBUS_BLOCK_MAX) {
290: i++;
291: res = -EINVAL;
292: break;
293: }
294:
295: msgs[i].len = msgs[i].buf[0];
296: }
297: }
298: if (res < 0) {
299: int j;
300: for (j = 0; j < i; ++j)
301: kfree(msgs[j].buf);
302: kfree(data_ptrs);
303: return res;
304: }
305:
306: res = i2c_transfer(client->adapter, msgs, nmsgs);
307: while (i-- > 0) {
308: if (res >= 0 && (msgs[i].flags & I2C_M_RD)) {
309: if (copy_to_user(data_ptrs[i], msgs[i].buf,
310: msgs[i].len))
311: res = -EFAULT;
312: }
313: kfree(msgs[i].buf);
314: }
315: kfree(data_ptrs);
316: return res;
317: }
SourceFile:drivers/i2c/i2c-dev.c]
|
| 24/2 |
2026/07/28 01:32 |
llm |
expert |
0m
Model:
Tokens:
input: 35805
output: 43
thoughts: 38
Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person:
**Potential I2C Adapter Reliability Concerns**
Alright, a red flag just went up. I was digging into the I2C driver and it hit me: `i2cdev_ioctl_rdwr` â that core function for read/write operations â *doesn't* appear to have any built-in checks to ensure the I2C adapter itself is still functioning as expected. That's a potential reliability issue, isn't it?
My immediate follow-up thought is: could this vital check be handled upstream, possibly by `i2cdev_ioctl`? Itâs possible the lower level ioctl function is the keeper of this sanity check, but the implication would be `i2cdev_ioctl_rdwr` should then still be checking that a valid ioctl function is in place. Otherwise, we are setting ourselves up for some potentially very difficult debugging scenarios down the road. It also means that any downstream drivers that depend on `i2cdev_ioctl_rdwr` must also implement some form of adapter health monitoring, which could lead to redundant code. Now I'm curious to dig deeper and see if my hunch about `i2cdev_ioctl` is correct, and if not, how robust the design actually is.
|
| 25/2 |
2026/07/28 01:32 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-dev.c Name:i2cdev_ioctl]
Results: map[SourceCode: 400: static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
401: {
402: struct i2c_client *client = file->private_data;
403: unsigned long funcs;
404:
405: dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",
406: cmd, arg);
407:
408: switch (cmd) {
409: case I2C_SLAVE:
410: case I2C_SLAVE_FORCE:
411: if ((arg > 0x3ff) ||
412: (((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
413: return -EINVAL;
414: if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))
415: return -EBUSY;
416: /* REVISIT: address could become busy later */
417: client->addr = arg;
418: return 0;
419: case I2C_TENBIT:
420: if (arg)
421: client->flags |= I2C_M_TEN;
422: else
423: client->flags &= ~I2C_M_TEN;
424: return 0;
425: case I2C_PEC:
426: /*
427: * Setting the PEC flag here won't affect kernel drivers,
428: * which will be using the i2c_client node registered with
429: * the driver model core. Likewise, when that client has
430: * the PEC flag already set, the i2c-dev driver won't see
431: * (or use) this setting.
432: */
433: if (arg)
434: client->flags |= I2C_CLIENT_PEC;
435: else
436: client->flags &= ~I2C_CLIENT_PEC;
437: return 0;
438: case I2C_FUNCS:
439: funcs = i2c_get_functionality(client->adapter);
440: return put_user(funcs, (unsigned long __user *)arg);
441:
442: case I2C_RDWR: {
443: struct i2c_rdwr_ioctl_data rdwr_arg;
444: struct i2c_msg *rdwr_pa;
445: int res;
446:
447: if (copy_from_user(&rdwr_arg,
448: (struct i2c_rdwr_ioctl_data __user *)arg,
449: sizeof(rdwr_arg)))
450: return -EFAULT;
451:
452: if (!rdwr_arg.msgs || rdwr_arg.nmsgs == 0)
453: return -EINVAL;
454:
455: /*
456: * Put an arbitrary limit on the number of messages that can
457: * be sent at once
458: */
459: if (rdwr_arg.nmsgs > I2C_RDWR_IOCTL_MAX_MSGS)
460: return -EINVAL;
461:
462: rdwr_pa = memdup_array_user(rdwr_arg.msgs,
463: rdwr_arg.nmsgs, sizeof(struct i2c_msg));
464: if (IS_ERR(rdwr_pa))
465: return PTR_ERR(rdwr_pa);
466:
467: res = i2cdev_ioctl_rdwr(client, rdwr_arg.nmsgs, rdwr_pa);
468: kfree(rdwr_pa);
469: return res;
470: }
471:
472: case I2C_SMBUS: {
473: struct i2c_smbus_ioctl_data data_arg;
474: if (copy_from_user(&data_arg,
475: (struct i2c_smbus_ioctl_data __user *) arg,
476: sizeof(struct i2c_smbus_ioctl_data)))
477: return -EFAULT;
478: return i2cdev_ioctl_smbus(client, data_arg.read_write,
479: data_arg.command,
480: data_arg.size,
481: data_arg.data);
482: }
483: case I2C_RETRIES:
484: if (arg > INT_MAX)
485: return -EINVAL;
486:
487: client->adapter->retries = arg;
488: break;
489: case I2C_TIMEOUT:
490: /*
491: * For historical reasons, user-space sets the timeout value in
492: * units of 10 ms.
493: */
494: if (arg > INT_MAX / 10)
495: return -EINVAL;
496:
497: client->adapter->timeout = msecs_to_jiffies(arg * 10);
498: break;
499: default:
500: /* NOTE: returning a fault code here could cause trouble
501: * in buggy userspace code. Some old kernel bugs returned
502: * zero in this case, and userspace code might accidentally
503: * have depended on that bug.
504: */
505: return -ENOTTY;
506: }
507: return 0;
508: }
SourceFile:drivers/i2c/i2c-dev.c]
|
| 26/2 |
2026/07/28 01:32 |
llm |
expert |
0m
Model:
Tokens:
input: 37894
output: 43
thoughts: 70
Thoughts: Okay, let's break this down.
**Investigating Potential I2C Adapter Issues**
Hold on a second... I was just looking at `i2cdev_ioctl` and I realized something. It's not explicitly checking whether the I2C adapter is still valid, is it? That's a potential vulnerability. But, wait... logically, if the adapter has been unregistered, shouldn't the `i2c-dev` module itself be preventing any further `ioctl` calls from succeeding? It should be, right? It should be handling that. Let's dig deeper. To confirm how this should work, I need to understand the connection between `i2cdev_ioctl`, `i2c-dev`, and the adapter unregistering better. I'll need to check the `i2cdev_open` and `i2cdev_release` functions to get a clearer picture of what's happening. They have to be the key to enforcing the adapter's lifecycle, so, they are the next step to review.
|
| 27/2 |
2026/07/28 01:33 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-dev.c Name:i2cdev_open]
Results: map[SourceCode: 598: static int i2cdev_open(struct inode *inode, struct file *file)
599: {
600: unsigned int minor = iminor(inode);
601: struct i2c_client *client;
602: struct i2c_adapter *adap;
603:
604: adap = i2c_get_adapter(minor);
605: if (!adap)
606: return -ENODEV;
607:
608: /* This creates an anonymous i2c_client, which may later be
609: * pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
610: *
611: * This client is ** NEVER REGISTERED ** with the driver model
612: * or I2C core code!! It just holds private copies of addressing
613: * information and maybe a PEC flag.
614: */
615: client = kzalloc_obj(*client);
616: if (!client) {
617: i2c_put_adapter(adap);
618: return -ENOMEM;
619: }
620: snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);
621:
622: client->adapter = adap;
623: file->private_data = client;
624:
625: return 0;
626: }
SourceFile:drivers/i2c/i2c-dev.c]
|
| 28/2 |
2026/07/28 01:33 |
llm |
expert |
0m
Model:
Tokens:
input: 38692
output: 46
thoughts: 60
Thoughts: Okay, let's break this down.
**Understanding the I2C Device Driver Initialization**
Right, so `i2cdev_open` is the entry point, the function that's called when a user-space application attempts to open an I2C device via the `/dev/i2c-*` character device. The first thing it does is call `i2c_get_adapter` with the minor number. Makes perfect sense. The minor number is what maps to a specific I2C bus on the system.
Now, that `i2c_get_adapter` is the crucial part here. My understanding is that this function is responsible for retrieving and, more importantly, *managing* the underlying `i2c_adapter` structure, which is the representation of the I2C bus itself. The interesting part, the part that needs to be clarified, is that `i2c_get_adapter` is incrementing the reference count of the `i2c_adapter` device. This is typical of resource management, but it's important to understand the details to know how this impacts the device life-cycle. I need to make sure I understand the precise implications of this increment; if the refcount is managed properly, then devices will be prevented from being prematurely unmounted. Therefore, I need to dig into `i2c_get_adapter` and see *exactly* how it's incrementing the refcount, and where that refcount is decremented. It's all about resource allocation, locking, and preventing race conditions, so I'll see where this goes.
|
| 29/2 |
2026/07/28 01:33 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-base.c Name:i2c_get_adapter]
Results: map[SourceCode:2628: struct i2c_adapter *i2c_get_adapter(int nr)
2629: {
2630: struct i2c_adapter *adapter;
2631:
2632: mutex_lock(&core_lock);
2633: adapter = idr_find(&i2c_adapter_idr, nr);
2634: if (!adapter)
2635: goto exit;
2636:
2637: if (try_module_get(adapter->owner))
2638: get_device(&adapter->dev);
2639: else
2640: adapter = NULL;
2641:
2642: exit:
2643: mutex_unlock(&core_lock);
2644: return adapter;
2645: }
SourceFile:drivers/i2c/i2c-core-base.c]
|
| 30/2 |
2026/07/28 01:33 |
llm |
expert |
0m
Model:
Tokens:
input: 39399
output: 61
thoughts: 561
Thoughts: **Analyzing a Potential Use-After-Free Vulnerability in I2C Adapter Handling**
Okay, so I'm thinking through this I2C adapter situation. `i2c_get_adapter` grabs a reference to the `i2c_adapter`'s device (`adapter->dev`) â that much I understand. As long as the file descriptor is open, the `i2c_adapter` itself should be safe. But the parent device is the key here. `i2c_add_adapter` registers the device, and `device_register` essentially calls `get_device(dev->parent)` to increment the parent's reference count, which is right. That makes sense.
Then, when we go to remove the adapter with `i2c_del_adapter`, it calls `device_unregister(&adapter->dev)`. That's where things get interesting. `device_unregister` calls `device_del` and *also* `put_device`. Crucially, `device_del` drops the reference to the parent! So, the parent device's refcount is decremented. If that parent device happens to be a `usb_device`, and its refcount hits zero, it could be freed!
But the `i2c_adapter` itself is *still* alive because the file is open. And, crucially, its `dev.parent` pointer STILL points to the freed `usb_device`! Now, when the user does an `ioctl(I2C_RDWR)`, `i2c_adapter_lock_bus` gets called, which in turn calls `i2c_adapter_depth(adapter)`. And *that* function attempts to access `adapter->dev.parent->type`! Boom. That's a classic use-after-free scenario.
Wait⌠is this a known issue? I should check if there are existing commits addressing this. The heart of the problem is that `adapter->dev.parent` is accessed after `device_del` has already released its hold on it. In other words, `adapter->dev.parent` becomes a dangling pointer after `device_del` if the parent device is actually freed.
Ah, but⌠the driver sets `adapter->dev.parent` before calling `i2c_add_adapter`. Does the driver keep a reference to the parent? Generally, yes, the driver will be bound to the parent (like a USB interface). When the USB device disconnects, the driver's `disconnect` function is called. The driver then calls `i2c_del_adapter`, which is the dangerous part. After `disconnect` returns, the USB core *could* free the USB device if its reference count goes to zero. But the `i2c_adapter` is still alive because of the open file descriptor! And its `dev.parent` pointer is left pointing to that freed USB device.
So I think I need to look for a fix in `i2c-core-base.c` â a place where I would expect this kind of handling to be addressed.
|
| 31/2 |
2026/07/28 01:33 |
tool |
git-log |
0m
Args: map[CodeRegexp:adapter->dev\.parent Count:10 SourcePath:drivers/i2c/i2c-core-base.c SymbolName:i2c_adapter_depth]
Results: map[Output:53801d2e762a i2c: core: fix lockdep warning for sparsely nested adapter chain
2771dc34ddaa i2c: add a warning to i2c_adapter_depth()
8dd1fe1594a7 i2c: export i2c_adapter_depth()
390946b17260 i2c-core: Fix for lockdep validator
99cd8e25875a i2c: Add a sysfs interface to instantiate devices
]
|
| 32/2 |
2026/07/28 01:33 |
llm |
expert |
0m
Model:
Tokens:
input: 40812
output: 61
thoughts: 16
Thoughts: Here's a summary of my thought process:
**Reviewing the `i2c_adapter_depth` History**
Okay, so I need to understand the evolution of `i2c_adapter_depth`. I'm starting by thinking back on the potential areas of interest. What were the initial design choices? Why was depth considered a relevant parameter for I2C adapters in the first place? Were there any early limitations or optimizations around it?
I need to understand if the current implementation aligns with those original intentions and see if there is recent activity.
I'm thinking about typical use cases and remembering the potential performance implications. Does this parameter relate to things like queuing, transaction size, or potential bus contention? I need to think about what its contribution to any recent issues might be. Perhaps there were conflicts, or changes to hardware and software dependencies that led to this need for review.
Alright, let's dive into the revision history and see what we can dig up. I'll be looking for key commits, discussions, and any related bug reports that touch upon this specific configuration parameter. I'm hoping to quickly identify the major milestones and the rationale behind significant changes.
|
| 33/2 |
2026/07/28 01:33 |
tool |
git-log |
0m
Args: map[CodeRegexp:parent = parent->parent Count:10 SourcePath:drivers/i2c/i2c-core-base.c SymbolName:i2c_adapter_depth]
Results: map[Output:53801d2e762a i2c: core: fix lockdep warning for sparsely nested adapter chain
]
|
| 34/2 |
2026/07/28 01:33 |
llm |
expert |
0m
Model:
Tokens:
input: 41175
output: 44
thoughts: 250
Thoughts: **UAF Potential in I2C Adapter Handling**
Okay, so I'm digging into this `i2c_adapter_depth` function, and it's hitting me that all it does is walk up the parent pointers. Now, is there anywhere *else* in the code that tries to access `adapter->dev.parent` after a `device_del` call? Wait, does `device_del` *actually* clear the `parent` pointer? No, it doesn't! But, it *does* drop the reference to that `parent` device. That means the `parent` is potentially freed. If the parent is freed, any later access to `parent` is, without a doubt, a classic UAF.
How do we prevent this? My initial thought is that if the driver itself holds an extra reference to the parent device, it would prevent the `parent` from being freed. But, generally, drivers don't do that, do they? But wait! If `i2c_adapter` *needs* access to its `parent` after `device_del`, it *should* explicitly hold a reference to the `parent`! Or, as an alternative, maybe we shouldn't allow `i2c_adapter_depth` to be called if the adapter has been unregistered?
Wait a second. I remember that `i2c_transfer` is called from `i2cdev_ioctl_rdwr`. Does `i2c_transfer` have a safety check in there to ensure that the adapter is still registered? I need to check the `i2c_transfer` function to be sure.
|
| 35/2 |
2026/07/28 01:33 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-base.c Name:i2c_transfer]
Results: map[SourceCode:2307: /**
2308: * i2c_transfer - execute a single or combined I2C message
2309: * @adap: Handle to I2C bus
2310: * @msgs: One or more messages to execute before STOP is issued to
2311: * terminate the operation; each message begins with a START.
2312: * @num: Number of messages to be executed.
2313: *
2314: * Returns negative errno, else the number of messages executed.
2315: *
2316: * Note that there is no requirement that each message be sent to
2317: * the same slave address, although that is the most common model.
2318: */
2319: int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
2320: {
2321: int ret;
2322:
2323: /* REVISIT the fault reporting model here is weak:
2324: *
2325: * - When we get an error after receiving N bytes from a slave,
2326: * there is no way to report "N".
2327: *
2328: * - When we get a NAK after transmitting N bytes to a slave,
2329: * there is no way to report "N" ... or to let the master
2330: * continue executing the rest of this combined message, if
2331: * that's the appropriate response.
2332: *
2333: * - When for example "num" is two and we successfully complete
2334: * the first message but get an error part way through the
2335: * second, it's unclear whether that should be reported as
2336: * one (discarding status on the second message) or errno
2337: * (discarding status on the first one).
2338: */
2339: ret = __i2c_lock_bus_helper(adap);
2340: if (ret)
2341: return ret;
2342:
2343: ret = __i2c_transfer(adap, msgs, num);
2344: i2c_unlock_bus(adap, I2C_LOCK_SEGMENT);
2345:
2346: return ret;
2347: }
SourceFile:drivers/i2c/i2c-core-base.c]
|
| 36/2 |
2026/07/28 01:33 |
llm |
expert |
0m
Model:
Tokens:
input: 42506
output: 47
thoughts: 40
Thoughts: Okay, here's my interpretation of that thought process, from an expert's perspective:
**Diving into the I2C Transfer Lock**
Alright, so `i2c_transfer` is the workhorse here, and it's calling `__i2c_lock_bus_helper(adap)`. Naturally, my first instinct is to understand what that lock helper is doing. It's a critical piece for I2C bus arbitration, making sure we don't have conflicting access. I need to understand the locking mechanism to be absolutely sure the bus is operating correctly, and prevent any deadlocks or access conflicts. I'll need to scrutinize that function's implementation next, understanding how it interacts with the I2C adapter passed in (`adap`), particularly regarding the locking primitives used and any potential race conditions, or if this function is safe for multi-threaded access.
|
| 37/2 |
2026/07/28 01:34 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core.h Name:__i2c_lock_bus_helper]
Results: map[SourceCode: 38: static inline int __i2c_lock_bus_helper(struct i2c_adapter *adap)
39: {
40: int ret = 0;
41:
42: if (i2c_in_atomic_xfer_mode()) {
43: WARN(!adap->algo->master_xfer_atomic && !adap->algo->smbus_xfer_atomic,
44: "No atomic I2C transfer handler for '%s'\n", dev_name(&adap->dev));
45: ret = i2c_trylock_bus(adap, I2C_LOCK_SEGMENT) ? 0 : -EAGAIN;
46: } else {
47: i2c_lock_bus(adap, I2C_LOCK_SEGMENT);
48: }
49:
50: return ret;
51: }
SourceFile:drivers/i2c/i2c-core.h]
|
| 38/2 |
2026/07/28 01:34 |
llm |
expert |
0m
Model:
Tokens:
input: 43052
output: 48
thoughts: 21
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm an expert in this field, rendered in the first person:
**Locking Down That I2C Bus**
Alright, so I'm thinking about the I2C bus locking mechanism. Specifically, my mind keeps coming back to the relationship between `i2c_lock_bus` and `i2c_adapter_lock_bus`. It's pretty straightforward, really. The `i2c_lock_bus` function, which I often use when needing to coordinate access, is just a wrapper, a higher-level call, if you will. It, in turn, *calls* the lower-level function, `i2c_adapter_lock_bus`. It seems pretty clear that`i2c_adapter_lock_bus` is the actual core of locking the bus. The other function does something that ultimately calls that. This suggests the function is the more universal solution that can lock an I2C bus from a variety of sources.
|
| 39/2 |
2026/07/28 01:34 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-base.c Name:i2c_adapter_lock_bus]
Results: map[SourceCode: 840: /**
841: * i2c_adapter_lock_bus - Get exclusive access to an I2C bus segment
842: * @adapter: Target I2C bus segment
843: * @flags: I2C_LOCK_ROOT_ADAPTER locks the root i2c adapter, I2C_LOCK_SEGMENT
844: * locks only this branch in the adapter tree
845: */
846: static void i2c_adapter_lock_bus(struct i2c_adapter *adapter,
847: unsigned int flags)
848: {
849: rt_mutex_lock_nested(&adapter->bus_lock, i2c_adapter_depth(adapter));
850: }
SourceFile:drivers/i2c/i2c-core-base.c]
|
| 40/2 |
2026/07/28 01:34 |
llm |
expert |
0m
Model:
Tokens:
input: 43577
output: 39
thoughts: 98
Thoughts: **Analyzing the I2C Adapter Bus Locking**
Okay, so I'm looking at `i2c_adapter_lock_bus`. I see it calls `i2c_adapter_depth(adapter)`. Now, that's interesting... because I seem to recall that function being primarily, or perhaps *exclusively*, used for lockdep. I need to make sure I'm correct in that assumption. If lockdep is disabled, which is entirely possible and a common configuration in many embedded systems or specific kernels, then `rt_mutex_lock_nested` is simply an alias for `rt_mutex_lock`. Consequently, `i2c_adapter_depth` might not even be invoked, which would affect the performance characteristics. I need to double-check that `rt_mutex_lock_nested` is behaving as I expect in this context and confirm my understanding of the implications of lockdep being enabled or disabled. Let me go and check the implementation of `rt_mutex_lock_nested` to be absolutely certain of the flow.
|
| 41/2 |
2026/07/28 01:34 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:include/linux/rtmutex.h Name:rt_mutex_lock_nested]
Results: map[SourceCode: 63: /**
64: * rt_mutex_lock_nested - lock a rt_mutex
65: *
66: * @lock: the rt_mutex to be locked
67: * @subclass: the lockdep subclass
68: */
69: void __sched rt_mutex_lock_nested(struct rt_mutex *lock, unsigned int subclass)
70: {
71: if (__rt_mutex_lock_common(lock, TASK_UNINTERRUPTIBLE, NULL, subclass) == 0)
72: return;
73: /*
74: * The code below is never reached because __rt_mutex_lock_common() only
75: * returns an error code if interrupted by a signal or upon a timeout.
76: */
77: WARN_ON_ONCE(true);
78: __acquire(lock);
79: }
SourceFile:kernel/locking/rtmutex_api.c]
|
| 42/2 |
2026/07/28 01:34 |
llm |
expert |
0m
Model:
Tokens:
input: 44209
output: 46
thoughts: 101
Thoughts: **Analyzing the Argument to `rt_mutex_lock_nested`**
Okay, so I'm looking at this `rt_mutex_lock_nested` function, and it takes `subclass` as an argument. The key thing I'm focusing on is how `i2c_adapter_depth(adapter)` is being *used* here. I see it's being evaluated as part of the argument list for `rt_mutex_lock_nested`. Even if we've got lockdep disabled, which I know is a common optimization, the kernel is still going to *call* `i2c_adapter_depth` because it's an argument to the function. This is important to remember because it means that function *always* executes. I should check if the definition of `i2c_adapter_depth` itself is conditional. If it's only defined under certain configurations, that's where I need to look closer for potential issues.
|
| 43/2 |
2026/07/28 01:34 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-base.c Name:i2c_adapter_depth]
Results: map[SourceCode:1237: unsigned int i2c_adapter_depth(struct i2c_adapter *adapter)
1238: {
1239: unsigned int depth = 0;
1240: struct device *parent;
1241:
1242: for (parent = adapter->dev.parent; parent; parent = parent->parent)
1243: if (parent->type == &i2c_adapter_type)
1244: depth++;
1245:
1246: WARN_ONCE(depth >= MAX_LOCKDEP_SUBCLASSES,
1247: "adapter depth exceeds lockdep subclass limit\n");
1248:
1249: return depth;
1250: }
SourceFile:drivers/i2c/i2c-core-base.c]
|
| 44/2 |
2026/07/28 01:34 |
llm |
expert |
0m
Model:
Tokens:
input: 44793
output: 45
thoughts: 569
Thoughts: **My Analysis of the I2C UAF Vulnerability**
Okay, so this isn't conditional â the code path is always executed, even without lockdep, which is concerning. The User-After-Free (UAF) will always occur. But what's the actual impact? I'm reading `parent->type` after `parent` has been freed, a `struct device`. If memory has been reallocated, I'm reading garbage. The code then compares this garbage to `&i2c_adapter_type`. If it matches, `depth` gets incremented. If it doesn't, it proceeds to read `parent = parent->parent`.
Hold on... `parent->parent` is *also* read from the freed memory! And it's located at an offset of 64 bytes within the freed chunk! Now things get interesting. If that memory has been reallocated as an `sk_buff` data buffer, the attacker can *control* what's in there! This means the user could control what `parent->parent` points to! This opens the door to arbitrary memory reads.
The code then enters a loop: `parent = parent->parent` (which is now an arbitrary address). Subsequently, `parent->type` gets read from the arbitrary address + 88! It's arbitrary memory read!
If `parent->type` matches `&i2c_adapter_type`, then `depth` is incremented. The loop continues until `parent` is NULL. So the user can chain `parent->parent` pointers to read arbitrary memory addresses.
However, the result of this arbitrary read is just incrementing `depth`. This value is then passed to `rt_mutex_lock_nested`. No, I can't leak memory. The value of `depth` is not returned to the user.
But wait, the memory read of `parent->type` from an arbitrary address could potentially crash the kernel if it's an invalid address. This is a denial-of-service vulnerability at least. I'm wondering if this can be exploited further. What if `parent->parent` points to a crafted `struct device` in user space? If SMAP is disabled, it can be done. But I'm only reading `parent->type` and `parent->parent`. I'm not writing anything!
I need to re-examine how `adapter->dev.parent` is used in other locations. I'm going to look at `i2c_transfer` and trace it to `__i2c_transfer`
|
| 45/2 |
2026/07/28 01:34 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-base.c Name:__i2c_transfer]
Results: map[SourceCode:2235: /**
2236: * __i2c_transfer - unlocked flavor of i2c_transfer
2237: * @adap: Handle to I2C bus
2238: * @msgs: One or more messages to execute before STOP is issued to
2239: * terminate the operation; each message begins with a START.
2240: * @num: Number of messages to be executed.
2241: *
2242: * Returns negative errno, else the number of messages executed.
2243: *
2244: * Adapter lock must be held when calling this function. No debug logging
2245: * takes place.
2246: */
2247: int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
2248: {
2249: unsigned long orig_jiffies;
2250: int ret, try;
2251:
2252: if (!adap->algo->master_xfer) {
2253: dev_dbg(&adap->dev, "I2C level transfers not supported\n");
2254: return -EOPNOTSUPP;
2255: }
2256:
2257: if (WARN_ON(!msgs || num < 1))
2258: return -EINVAL;
2259:
2260: ret = __i2c_check_suspended(adap);
2261: if (ret)
2262: return ret;
2263:
2264: if (adap->quirks && i2c_check_for_quirks(adap, msgs, num))
2265: return -EOPNOTSUPP;
2266:
2267: /*
2268: * i2c_trace_msg_key gets enabled when tracepoint i2c_transfer gets
2269: * enabled. This is an efficient way of keeping the for-loop from
2270: * being executed when not needed.
2271: */
2272: if (static_branch_unlikely(&i2c_trace_msg_key)) {
2273: int i;
2274: for (i = 0; i < num; i++)
2275: if (msgs[i].flags & I2C_M_RD)
2276: trace_i2c_read(adap, &msgs[i], i);
2277: else
2278: trace_i2c_write(adap, &msgs[i], i);
2279: }
2280:
2281: /* Retry automatically on arbitration loss */
2282: orig_jiffies = jiffies;
2283: for (ret = 0, try = 0; try <= adap->retries; try++) {
2284: if (i2c_in_atomic_xfer_mode() && adap->algo->master_xfer_atomic)
2285: ret = adap->algo->master_xfer_atomic(adap, msgs, num);
2286: else
2287: ret = adap->algo->master_xfer(adap, msgs, num);
2288:
2289: if (ret != -EAGAIN)
2290: break;
2291: if (time_after(jiffies, orig_jiffies + adap->timeout))
2292: break;
2293: }
2294:
2295: if (static_branch_unlikely(&i2c_trace_msg_key)) {
2296: int i;
2297: for (i = 0; i < ret; i++)
2298: if (msgs[i].flags & I2C_M_RD)
2299: trace_i2c_reply(adap, &msgs[i], i);
2300: trace_i2c_result(adap, num, ret);
2301: }
2302:
2303: return ret;
2304: }
SourceFile:drivers/i2c/i2c-core-base.c]
|
| 46/2 |
2026/07/28 01:34 |
llm |
expert |
0m
Model:
Tokens:
input: 47094
output: 46
thoughts: 142
Thoughts: **The I2C Transfer Conundrum**
Okay, so `__i2c_transfer` is calling `adap->algo->master_xfer(adap, msgs, num)`. Standard I2C transfer function. But hold on, a potential red flag just popped into my head. What if the I2C adapter, `adap`, has been unregistered? Is `adap->algo` still pointing to valid memory? Yes, the `i2c_adapter` struct itself is still alive, so `adap` is valid. However, the *underlying hardware* - let's say a USB device â has very likely been freed. This means when `adap->algo->master_xfer` is called, it's going to try accessing a device that no longer exists! Disaster! That's a classic use-after-free scenario, and that's not good at all. I need to get to the bottom of this. Time to dig into a real-world example. Let's look at a common USB I2C driver, like the `i2c-tiny-usb` driver in the kernel source. I need to see how they're handling (or not handling) this situation.
|
| 47/2 |
2026/07/28 01:34 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/busses/i2c-tiny-usb.c Name:usb_xfer]
Results: map[SourceCode: 52: static int usb_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, int num)
53: {
54: unsigned char *pstatus;
55: struct i2c_msg *pmsg;
56: int i, ret;
57:
58: pstatus = kmalloc_obj(*pstatus);
59: if (!pstatus)
60: return -ENOMEM;
61:
62: for (i = 0 ; i < num ; i++) {
63: int cmd = CMD_I2C_IO;
64:
65: if (i == 0)
66: cmd |= CMD_I2C_IO_BEGIN;
67:
68: if (i == num-1)
69: cmd |= CMD_I2C_IO_END;
70:
71: pmsg = &msgs[i];
72:
73: dev_dbg(&adapter->dev,
74: " %d: %s (flags %d) %d bytes to 0x%02x\n",
75: i, str_read_write(pmsg->flags & I2C_M_RD),
76: pmsg->flags, pmsg->len, pmsg->addr);
77:
78: /* and directly send the message */
79: if (pmsg->flags & I2C_M_RD) {
80: /* read data */
81: if (usb_read(adapter, cmd,
82: pmsg->flags, pmsg->addr,
83: pmsg->buf, pmsg->len) != pmsg->len) {
84: dev_err(&adapter->dev,
85: "failure reading data\n");
86: ret = -EIO;
87: goto out;
88: }
89: } else {
90: /* write data */
91: if (usb_write(adapter, cmd,
92: pmsg->flags, pmsg->addr,
93: pmsg->buf, pmsg->len) != pmsg->len) {
94: dev_err(&adapter->dev,
95: "failure writing data\n");
96: ret = -EIO;
97: goto out;
98: }
99: }
100:
101: /* read status */
102: if (usb_read(adapter, CMD_GET_STATUS, 0, 0, pstatus, 1) != 1) {
103: dev_err(&adapter->dev, "failure reading status\n");
104: ret = -EIO;
105: goto out;
106: }
107:
108: dev_dbg(&adapter->dev, " status = %d\n", *pstatus);
109: if (*pstatus == STATUS_ADDRESS_NAK) {
110: ret = -ENXIO;
111: goto out;
112: }
113: }
114:
115: ret = i;
116: out:
117: kfree(pstatus);
118: return ret;
119: }
SourceFile:drivers/i2c/busses/i2c-tiny-usb.c]
|
| 48/2 |
2026/07/28 01:34 |
llm |
expert |
0m
Model:
Tokens:
input: 48507
output: 45
thoughts: 18
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**USB Transfer Abstraction: A Refinement of Core I/O**
Alright, so the `usb_xfer` function is clearly the higher-level abstraction. It's essentially orchestrating the data transfer. What I'm seeing is that `usb_xfer` is *using* the fundamental, low-level calls `usb_read` and `usb_write`. That makes perfect sense. We build these higher-level operations on top of the simpler read/write primitives. I'm guessing `usb_xfer` handles things like buffer management, error checking, possibly setting up control transfers, and maybe even splitting up large transfers into smaller packets if necessary. It's likely handling the state transitions of the USB transaction too, tracking completion and error handling within those underlying read and write calls. It's the standard design pattern; keep the core operations lean and mean, then wrap them in something more intelligent for practical use.
|
| 49/2 |
2026/07/28 01:34 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/busses/i2c-tiny-usb.c Name:usb_read]
Results: map[SourceCode: 177: static int usb_read(struct i2c_adapter *adapter, int cmd,
178: int value, int index, void *data, int len)
179: {
180: struct i2c_tiny_usb *dev = (struct i2c_tiny_usb *)adapter->algo_data;
181: void *dmadata = kmalloc(len, GFP_KERNEL);
182: int ret;
183:
184: if (!dmadata)
185: return -ENOMEM;
186:
187: /* do control transfer */
188: ret = usb_control_msg(dev->usb_dev, usb_rcvctrlpipe(dev->usb_dev, 0),
189: cmd, USB_TYPE_VENDOR | USB_RECIP_INTERFACE |
190: USB_DIR_IN, value, index, dmadata, len, 2000);
191:
192: memcpy(data, dmadata, len);
193: kfree(dmadata);
194: return ret;
195: }
SourceFile:drivers/i2c/busses/i2c-tiny-usb.c]
|
| 50/2 |
2026/07/28 01:34 |
llm |
expert |
0m
Model:
Tokens:
input: 49143
output: 38
thoughts: 158
Thoughts: **A Critical Oversight in USB Handling**
Okay, so I'm looking at this `usb_read` function and it's using `dev->usb_dev`. Right, straightforward so far. But then it hits me: `dev->usb_dev` is *pointing* to a `usb_device` structure that has already been freed! That's a huge red flag. And even worse, `usb_control_msg` is going to use this *same* freed `usb_device`!
This is screaming "use-after-free" at me. The implications are significant: the user can essentially trigger this use-after-free vulnerability within `usb_control_msg`. It's a potential path to arbitrary code execution, especially given that we're talking about a core USB structure.
But let's think this through. `usb_control_msg` does indeed take `dev->usb_dev` as a parameter. Since `usb_device` is now freed, and if the user can control the memory's contents â perhaps through some data spraying technique with `sk_buff` â then they could, theoretically, manipulate the fields of the freed `usb_device`. This would be a game-changer. I need to dig deeper and see exactly how `usb_control_msg` interacts with the `usb_device` structure, and exactly what potential for control the user has, now that it's freed. This could be a very serious vulnerability.
|
| 51/2 |
2026/07/28 01:34 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/core/message.c Name:usb_control_msg]
Results: map[SourceCode: 124: /**
125: * usb_control_msg - Builds a control urb, sends it off and waits for completion
126: * @dev: pointer to the usb device to send the message to
127: * @pipe: endpoint "pipe" to send the message to
128: * @request: USB message request value
129: * @requesttype: USB message request type value
130: * @value: USB message value
131: * @index: USB message index value
132: * @data: pointer to the data to send
133: * @size: length in bytes of the data to send
134: * @timeout: time in msecs to wait for the message to complete before timing out
135: *
136: * Context: task context, might sleep.
137: *
138: * This function sends a simple control message to a specified endpoint and
139: * waits for the message to complete, or timeout.
140: *
141: * Don't use this function from within an interrupt context. If you need
142: * an asynchronous message, or need to send a message from within interrupt
143: * context, use usb_submit_urb(). If a thread in your driver uses this call,
144: * make sure your disconnect() method can wait for it to complete. Since you
145: * don't have a handle on the URB used, you can't cancel the request.
146: *
147: * Return: If successful, the number of bytes transferred. Otherwise, a negative
148: * error number.
149: */
150: int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request,
151: __u8 requesttype, __u16 value, __u16 index, void *data,
152: __u16 size, int timeout)
153: {
154: struct usb_ctrlrequest *dr;
155: int ret;
156:
157: dr = kmalloc_obj(struct usb_ctrlrequest, GFP_NOIO);
158: if (!dr)
159: return -ENOMEM;
160:
161: dr->bRequestType = requesttype;
162: dr->bRequest = request;
163: dr->wValue = cpu_to_le16(value);
164: dr->wIndex = cpu_to_le16(index);
165: dr->wLength = cpu_to_le16(size);
166:
167: ret = usb_internal_control_msg(dev, pipe, dr, data, size, timeout);
168:
169: /* Linger a bit, prior to the next control message. */
170: if (dev->quirks & USB_QUIRK_DELAY_CTRL_MSG)
171: msleep(200);
172:
173: kfree(dr);
174:
175: return ret;
176: }
SourceFile:drivers/usb/core/message.c]
|
| 52/2 |
2026/07/28 01:34 |
llm |
expert |
0m
Model:
Tokens:
input: 50547
output: 40
thoughts: 17
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Digging into the USB Control Message Flow**
Alright, so the initial thought that pops into my head is a simple observation: When I'm working with the USB stack, specifically when dealing with control transfers, I see `usb_control_msg` as the entry point. That function, straightforward as it may seem, is actually a wrapper. My mind immediately starts tracing the call stack, because I *know* these things are rarely as simple as they look on the surface. And, sure enough, that wrapper very quickly hands off the real heavy lifting to `usb_internal_control_msg`. This tells me a few things already. `usb_control_msg` is most likely doing some pre-processing, perhaps parameter validation, maybe handling some higher-level abstraction, or even some common error checking. The internal function, `usb_internal_control_msg`, is obviously where the core USB communication logic resides. Itâs what actually builds and sends the USB control request to the device. This mental note is important because now I know the "real" work is happening deeper down. When I'm debugging or optimizing, I need to focus my efforts further down the chain when I'm dealing with the USB device itself.
|
| 53/2 |
2026/07/28 01:34 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/core/message.c Name:usb_internal_control_msg]
Results: map[SourceCode: 99: /*-------------------------------------------------------------------*/
100: /* returns status (negative) or length (positive) */
101: static int usb_internal_control_msg(struct usb_device *usb_dev,
102: unsigned int pipe,
103: struct usb_ctrlrequest *cmd,
104: void *data, int len, int timeout)
105: {
106: struct urb *urb;
107: int retv;
108: int length;
109:
110: urb = usb_alloc_urb(0, GFP_NOIO);
111: if (!urb)
112: return -ENOMEM;
113:
114: usb_fill_control_urb(urb, usb_dev, pipe, (unsigned char *)cmd, data,
115: len, usb_api_blocking_completion, NULL);
116:
117: retv = usb_start_wait_urb(urb, timeout, &length, false);
118: if (retv < 0)
119: return retv;
120: else
121: return length;
122: }
SourceFile:drivers/usb/core/message.c]
|
| 54/2 |
2026/07/28 01:34 |
llm |
expert |
0m
Model:
Tokens:
input: 51259
output: 40
thoughts: 19
Thoughts: Okay, here's my interpretation of that:
**My Internal USB Control Flow**
Alright, let's break this down. When I see `usb_internal_control_msg`, my first thought is, "Ah, we're initiating a control message transfer within the USB stack." I know that function is responsible for the internal mechanics of a control transfer. And, as I remember, it quickly leads me to `usb_start_wait_urb`. This means that I am getting ready to process the USB Request Block - Urb - asynchronously. I use `usb_start_wait_urb` to kick off the underlying transfer. This makes sense. That's the core of how I handle this - prepare and launch the transfer. Now I have to go deal with the response.
|
| 55/2 |
2026/07/28 01:34 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/core/message.c Name:usb_start_wait_urb]
Results: map[SourceCode: 44: /*
45: * Starts urb and waits for completion or timeout.
46: * Whether or not the wait is killable depends on the flag passed in.
47: * For example, compare usb_bulk_msg() and usb_bulk_msg_killable().
48: *
49: * For non-killable waits, we enforce a maximum limit on the timeout value.
50: */
51: static int usb_start_wait_urb(struct urb *urb, int timeout, int *actual_length,
52: bool killable)
53: {
54: struct api_context ctx;
55: unsigned long expire;
56: int retval;
57: long rc;
58:
59: init_completion(&ctx.done);
60: urb->context = &ctx;
61: urb->actual_length = 0;
62: retval = usb_submit_urb(urb, GFP_NOIO);
63: if (unlikely(retval))
64: goto out;
65:
66: if (!killable && (timeout <= 0 || timeout > USB_MAX_SYNCHRONOUS_TIMEOUT))
67: timeout = USB_MAX_SYNCHRONOUS_TIMEOUT;
68: expire = (timeout > 0) ? msecs_to_jiffies(timeout) : MAX_SCHEDULE_TIMEOUT;
69: if (killable)
70: rc = wait_for_completion_killable_timeout(&ctx.done, expire);
71: else
72: rc = wait_for_completion_timeout(&ctx.done, expire);
73: if (rc <= 0) {
74: usb_kill_urb(urb);
75: if (ctx.status != -ENOENT)
76: retval = ctx.status;
77: else if (rc == 0)
78: retval = -ETIMEDOUT;
79: else
80: retval = rc;
81:
82: dev_dbg(&urb->dev->dev,
83: "%s timed out or killed on ep%d%s len=%u/%u\n",
84: current->comm,
85: usb_endpoint_num(&urb->ep->desc),
86: usb_urb_dir_in(urb) ? "in" : "out",
87: urb->actual_length,
88: urb->transfer_buffer_length);
89: } else
90: retval = ctx.status;
91: out:
92: if (actual_length)
93: *actual_length = urb->actual_length;
94:
95: usb_free_urb(urb);
96: return retval;
97: }
SourceFile:drivers/usb/core/message.c]
|
| 56/2 |
2026/07/28 01:34 |
llm |
expert |
0m
Model:
Tokens:
input: 52275
output: 38
thoughts: 60
Thoughts: Here's my analysis of what's going on:
**Dissecting a USB Submission Race Condition**
Okay, so I'm looking at this `usb_start_wait_urb` function, and it seems to be calling `usb_submit_urb` with `GFP_NOIO`. No surprises there. But the interesting part is what happens *inside* `usb_submit_urb`. It looks like it's grabbing `urb->dev`, which, alarm bells, is referencing a `usb_device`. And the code doesn't seem to be protecting this resource in the way I would expect. This raises a red flag immediately. It's screaming a potential use-after-free scenario. This is because the device could potentially be freed asynchronously and at any point. Looks like I need to dive into `usb_submit_urb` to confirm this suspicion, and get a better understanding of the race. I have a feeling this could be nasty. Time to dig deeper.
|
| 57/2 |
2026/07/28 01:34 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/core/urb.c Name:usb_submit_urb]
Results: map[SourceCode: 230: /**
231: * usb_submit_urb - issue an asynchronous transfer request for an endpoint
232: * @urb: pointer to the urb describing the request
233: * @mem_flags: the type of memory to allocate, see kmalloc() for a list
234: * of valid options for this.
235: *
236: * This submits a transfer request, and transfers control of the URB
237: * describing that request to the USB subsystem. Request completion will
238: * be indicated later, asynchronously, by calling the completion handler.
239: * The three types of completion are success, error, and unlink
240: * (a software-induced fault, also called "request cancellation").
241: *
242: * URBs may be submitted in interrupt context.
243: *
244: * The caller must have correctly initialized the URB before submitting
245: * it. Functions such as usb_fill_bulk_urb() and usb_fill_control_urb() are
246: * available to ensure that most fields are correctly initialized, for
247: * the particular kind of transfer, although they will not initialize
248: * any transfer flags.
249: *
250: * If the submission is successful, the complete() callback from the URB
251: * will be called exactly once, when the USB core and Host Controller Driver
252: * (HCD) are finished with the URB. When the completion function is called,
253: * control of the URB is returned to the device driver which issued the
254: * request. The completion handler may then immediately free or reuse that
255: * URB.
256: *
257: * With few exceptions, USB device drivers should never access URB fields
258: * provided by usbcore or the HCD until its complete() is called.
259: * The exceptions relate to periodic transfer scheduling. For both
260: * interrupt and isochronous urbs, as part of successful URB submission
261: * urb->interval is modified to reflect the actual transfer period used
262: * (normally some power of two units). And for isochronous urbs,
263: * urb->start_frame is modified to reflect when the URB's transfers were
264: * scheduled to start.
265: *
266: * Not all isochronous transfer scheduling policies will work, but most
267: * host controller drivers should easily handle ISO queues going from now
268: * until 10-200 msec into the future. Drivers should try to keep at
269: * least one or two msec of data in the queue; many controllers require
270: * that new transfers start at least 1 msec in the future when they are
271: * added. If the driver is unable to keep up and the queue empties out,
272: * the behavior for new submissions is governed by the URB_ISO_ASAP flag.
273: * If the flag is set, or if the queue is idle, then the URB is always
274: * assigned to the first available (and not yet expired) slot in the
275: * endpoint's schedule. If the flag is not set and the queue is active
276: * then the URB is always assigned to the next slot in the schedule
277: * following the end of the endpoint's previous URB, even if that slot is
278: * in the past. When a packet is assigned in this way to a slot that has
279: * already expired, the packet is not transmitted and the corresponding
280: * usb_iso_packet_descriptor's status field will return -EXDEV. If this
281: * would happen to all the packets in the URB, submission fails with a
282: * -EXDEV error code.
283: *
284: * For control endpoints, the synchronous usb_control_msg() call is
285: * often used (in non-interrupt context) instead of this call.
286: * That is often used through convenience wrappers, for the requests
287: * that are standardized in the USB 2.0 specification. For bulk
288: * endpoints, a synchronous usb_bulk_msg() call is available.
289: *
290: * Return:
291: * 0 on successful submissions. A negative error number otherwise.
292: *
293: * Request Queuing:
294: *
295: * URBs may be submitted to endpoints before previous ones complete, to
296: * minimize the impact of interrupt latencies and system overhead on data
297: * throughput. With that queuing policy, an endpoint's queue would never
298: * be empty. This is required for continuous isochronous data streams,
299: * and may also be required for some kinds of interrupt transfers. Such
300: * queuing also maximizes bandwidth utilization by letting USB controllers
301: * start work on later requests before driver software has finished the
302: * completion processing for earlier (successful) requests.
303: *
304: * As of Linux 2.6, all USB endpoint transfer queues support depths greater
305: * than one. This was previously a HCD-specific behavior, except for ISO
306: * transfers. Non-isochronous endpoint queues are inactive during cleanup
307: * after faults (transfer errors or cancellation).
308: *
309: * Reserved Bandwidth Transfers:
310: *
311: * Periodic transfers (interrupt or isochronous) are performed repeatedly,
312: * using the interval specified in the urb. Submitting the first urb to
313: * the endpoint reserves the bandwidth necessary to make those transfers.
314: * If the USB subsystem can't allocate sufficient bandwidth to perform
315: * the periodic request, submitting such a periodic request should fail.
316: *
317: * For devices under xHCI, the bandwidth is reserved at configuration time, or
318: * when the alt setting is selected. If there is not enough bus bandwidth, the
319: * configuration/alt setting request will fail. Therefore, submissions to
320: * periodic endpoints on devices under xHCI should never fail due to bandwidth
321: * constraints.
322: *
323: * Device drivers must explicitly request that repetition, by ensuring that
324: * some URB is always on the endpoint's queue (except possibly for short
325: * periods during completion callbacks). When there is no longer an urb
326: * queued, the endpoint's bandwidth reservation is canceled. This means
327: * drivers can use their completion handlers to ensure they keep bandwidth
328: * they need, by reinitializing and resubmitting the just-completed urb
329: * until the driver longer needs that periodic bandwidth.
330: *
331: * Memory Flags:
332: *
333: * The general rules for how to decide which mem_flags to use
334: * are the same as for kmalloc. There are four
335: * different possible values; GFP_KERNEL, GFP_NOFS, GFP_NOIO and
336: * GFP_ATOMIC.
337: *
338: * GFP_NOFS is not ever used, as it has not been implemented yet.
339: *
340: * GFP_ATOMIC is used when
341: * (a) you are inside a completion handler, an interrupt, bottom half,
342: * tasklet or timer, or
343: * (b) you are holding a spinlock or rwlock (does not apply to
344: * semaphores), or
345: * (c) current->state != TASK_RUNNING, this is the case only after
346: * you've changed it.
347: *
348: * GFP_NOIO is used in the block io path and error handling of storage
349: * devices.
350: *
351: * All other situations use GFP_KERNEL.
352: *
353: * Some more specific rules for mem_flags can be inferred, such as
354: * (1) start_xmit, timeout, and receive methods of network drivers must
355: * use GFP_ATOMIC (they are called with a spinlock held);
356: * (2) queuecommand methods of scsi drivers must use GFP_ATOMIC (also
357: * called with a spinlock held);
358: * (3) If you use a kernel thread with a network driver you must use
359: * GFP_NOIO, unless (b) or (c) apply;
360: * (4) after you have done a down() you can use GFP_KERNEL, unless (b) or (c)
361: * apply or your are in a storage driver's block io path;
362: * (5) USB probe and disconnect can use GFP_KERNEL unless (b) or (c) apply; and
363: * (6) changing firmware on a running storage or net device uses
364: * GFP_NOIO, unless b) or c) apply
365: *
366: */
367: int usb_submit_urb(struct urb *urb, gfp_t mem_flags)
368: {
369: int xfertype, max;
370: struct usb_device *dev;
371: struct usb_host_endpoint *ep;
372: int is_out;
373: unsigned int allowed;
374: bool is_eusb2_isoch_double;
375:
376: if (!urb || !urb->complete)
377: return -EINVAL;
378: if (urb->hcpriv) {
379: WARN_ONCE(1, "URB %p submitted while active\n", urb);
380: return -EBUSY;
381: }
382:
383: dev = urb->dev;
384: if ((!dev) || (dev->state < USB_STATE_UNAUTHENTICATED))
385: return -ENODEV;
386:
387: /* For now, get the endpoint from the pipe. Eventually drivers
388: * will be required to set urb->ep directly and we will eliminate
389: * urb->pipe.
390: */
391: ep = usb_pipe_endpoint(dev, urb->pipe);
392: if (!ep)
393: return -ENOENT;
394:
395: urb->ep = ep;
396: urb->status = -EINPROGRESS;
397: urb->actual_length = 0;
398:
399: /* Lots of sanity checks, so HCDs can rely on clean data
400: * and don't need to duplicate tests
401: */
402: xfertype = usb_endpoint_type(&ep->desc);
403: if (xfertype == USB_ENDPOINT_XFER_CONTROL) {
404: struct usb_ctrlrequest *setup =
405: (struct usb_ctrlrequest *) urb->setup_packet;
406:
407: if (!setup)
408: return -ENOEXEC;
409: is_out = !(setup->bRequestType & USB_DIR_IN) ||
410: !setup->wLength;
411: dev_WARN_ONCE(&dev->dev, (usb_pipeout(urb->pipe) != is_out),
412: "BOGUS control dir, pipe %x doesn't match bRequestType %x\n",
413: urb->pipe, setup->bRequestType);
414: if (le16_to_cpu(setup->wLength) != urb->transfer_buffer_length) {
415: dev_dbg(&dev->dev, "BOGUS control len %d doesn't match transfer length %d\n",
416: le16_to_cpu(setup->wLength),
417: urb->transfer_buffer_length);
418: return -EBADR;
419: }
420: } else {
421: is_out = usb_endpoint_dir_out(&ep->desc);
422: }
423:
424: /* Clear the internal flags and cache the direction for later use */
425: urb->transfer_flags &= ~(URB_DIR_MASK | URB_DMA_MAP_SINGLE |
426: URB_DMA_MAP_PAGE | URB_DMA_MAP_SG | URB_MAP_LOCAL |
427: URB_SETUP_MAP_SINGLE | URB_SETUP_MAP_LOCAL |
428: URB_DMA_SG_COMBINED);
429: urb->transfer_flags |= (is_out ? URB_DIR_OUT : URB_DIR_IN);
430: kmsan_handle_urb(urb, is_out);
431:
432: if (xfertype != USB_ENDPOINT_XFER_CONTROL &&
433: dev->state < USB_STATE_CONFIGURED)
434: return -ENODEV;
435:
436: max = usb_endpoint_maxp(&ep->desc);
437: is_eusb2_isoch_double = usb_endpoint_is_hs_isoc_double(dev, ep);
438: if (!max && !is_eusb2_isoch_double) {
439: dev_dbg(&dev->dev,
440: "bogus endpoint ep%d%s in %s (bad maxpacket %d)\n",
441: usb_endpoint_num(&ep->desc), is_out ? "out" : "in",
442: __func__, max);
443: return -EMSGSIZE;
444: }
445:
446: /* periodic transfers limit size per frame/uframe,
447: * but drivers only control those sizes for ISO.
448: * while we're checking, initialize return status.
449: */
450: if (xfertype == USB_ENDPOINT_XFER_ISOC) {
451: int n, len;
452:
453: /* SuperSpeed isoc endpoints have up to 16 bursts of up to
454: * 3 packets each
455: */
456: if (dev->speed >= USB_SPEED_SUPER) {
457: int burst = 1 + ep->ss_ep_comp.bMaxBurst;
458: int mult = USB_SS_MULT(ep->ss_ep_comp.bmAttributes);
459: max *= burst;
460: max *= mult;
461: }
462:
463: if (dev->speed == USB_SPEED_SUPER_PLUS &&
464: USB_SS_SSP_ISOC_COMP(ep->ss_ep_comp.bmAttributes)) {
465: struct usb_ssp_isoc_ep_comp_descriptor *isoc_ep_comp;
466:
467: isoc_ep_comp = &ep->ssp_isoc_ep_comp;
468: max = le32_to_cpu(isoc_ep_comp->dwBytesPerInterval);
469: }
470:
471: /* High speed, 1-3 packets/uframe, max 6 for eUSB2 double bw */
472: if (dev->speed == USB_SPEED_HIGH) {
473: if (is_eusb2_isoch_double)
474: max = le32_to_cpu(ep->eusb2_isoc_ep_comp.dwBytesPerInterval);
475: else
476: max *= usb_endpoint_maxp_mult(&ep->desc);
477: }
478:
479: if (urb->number_of_packets <= 0)
480: return -EINVAL;
481: for (n = 0; n < urb->number_of_packets; n++) {
482: len = urb->iso_frame_desc[n].length;
483: if (len < 0 || len > max)
484: return -EMSGSIZE;
485: urb->iso_frame_desc[n].status = -EXDEV;
486: urb->iso_frame_desc[n].actual_length = 0;
487: }
488: } else if (urb->num_sgs && !urb->dev->bus->no_sg_constraint) {
489: struct scatterlist *sg;
490: int i;
491:
492: for_each_sg(urb->sg, sg, urb->num_sgs - 1, i)
493: if (sg->length % max)
494: return -EINVAL;
495: }
496:
497: /* the I/O buffer must be mapped/unmapped, except when length=0 */
498: if (urb->transfer_buffer_length > INT_MAX)
499: return -EMSGSIZE;
500:
501: /*
502: * stuff that drivers shouldn't do, but which shouldn't
503: * cause problems in HCDs if they get it wrong.
504: */
505:
506: /* Check that the pipe's type matches the endpoint's type */
507: if (usb_pipe_type_check(urb->dev, urb->pipe))
508: dev_warn_once(&dev->dev, "BOGUS urb xfer, pipe %x != type %x\n",
509: usb_pipetype(urb->pipe), pipetypes[xfertype]);
510:
511: /* Check against a simple/standard policy */
512: allowed = (URB_NO_TRANSFER_DMA_MAP | URB_NO_INTERRUPT | URB_DIR_MASK |
513: URB_FREE_BUFFER);
514: switch (xfertype) {
515: case USB_ENDPOINT_XFER_BULK:
516: case USB_ENDPOINT_XFER_INT:
517: if (is_out)
518: allowed |= URB_ZERO_PACKET;
519: fallthrough;
520: default: /* all non-iso endpoints */
521: if (!is_out)
522: allowed |= URB_SHORT_NOT_OK;
523: break;
524: case USB_ENDPOINT_XFER_ISOC:
525: allowed |= URB_ISO_ASAP;
526: break;
527: }
528: allowed &= urb->transfer_flags;
529:
530: /* warn if submitter gave bogus flags */
531: if (allowed != urb->transfer_flags)
532: dev_WARN(&dev->dev, "BOGUS urb flags, %x --> %x\n",
533: urb->transfer_flags, allowed);
534:
535: /*
536: * Force periodic transfer intervals to be legal values that are
537: * a power of two (so HCDs don't need to).
538: *
539: * FIXME want bus->{intr,iso}_sched_horizon values here. Each HC
540: * supports different values... this uses EHCI/UHCI defaults (and
541: * EHCI can use smaller non-default values).
542: */
543: switch (xfertype) {
544: case USB_ENDPOINT_XFER_ISOC:
545: case USB_ENDPOINT_XFER_INT:
546: /* too small? */
547: if (urb->interval <= 0)
548: return -EINVAL;
549:
550: /* too big? */
551: switch (dev->speed) {
552: case USB_SPEED_SUPER_PLUS:
553: case USB_SPEED_SUPER: /* units are 125us */
554: /* Handle up to 2^(16-1) microframes */
555: if (urb->interval > (1 << 15))
556: return -EINVAL;
557: max = 1 << 15;
558: break;
559: case USB_SPEED_HIGH: /* units are microframes */
560: /* NOTE usb handles 2^15 */
561: if (urb->interval > (1024 * 8))
562: urb->interval = 1024 * 8;
563: max = 1024 * 8;
564: break;
565: case USB_SPEED_FULL: /* units are frames/msec */
566: case USB_SPEED_LOW:
567: if (xfertype == USB_ENDPOINT_XFER_INT) {
568: if (urb->interval > 255)
569: return -EINVAL;
570: /* NOTE ohci only handles up to 32 */
571: max = 128;
572: } else {
573: if (urb->interval > 1024)
574: urb->interval = 1024;
575: /* NOTE usb and ohci handle up to 2^15 */
576: max = 1024;
577: }
578: break;
579: default:
580: return -EINVAL;
581: }
582: /* Round down to a power of 2, no more than max */
583: urb->interval = min(max, 1 << ilog2(urb->interval));
584: }
585:
586: return usb_hcd_submit_urb(urb, mem_flags);
587: }
SourceFile:drivers/usb/core/urb.c]
|
| 58/2 |
2026/07/28 01:34 |
llm |
expert |
0m
Model:
Tokens:
input: 58814
output: 42
thoughts: 110
Thoughts: **Analyzing a Potential Vulnerability in USB Handling**
Okay, so I'm looking at `usb_submit_urb`, and it's calling `usb_hcd_submit_urb` with the URB and some memory flags. Right away, my spider-sense is tingling. I see that `usb_hcd_submit_urb` relies on `urb->dev->bus`. That's where things could get dicey. If, for some reason, `urb->dev` gets freed *and* the user has control over what that memory gets repurposed for, then `urb->dev->bus` could be pointing at absolutely *anything* â a completely arbitrary address!
Essentially, if I can control the content of `urb->dev`, I can make `urb->dev->bus` point to whatever memory I choose. Then, `usb_hcd_submit_urb` is going to happily dereference that memory address. That's a classic recipe for a kernel panic, or worse â arbitrary code execution! I'm going to dig into the `usb_hcd_submit_urb` function more closely and see where things go from here. Need to understand how this is used and where things are initialized.
|
| 59/2 |
2026/07/28 01:35 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/core/hcd.c Name:usb_hcd_submit_urb]
Results: map[SourceCode:1510: /* may be called in any context with a valid urb->dev usecount
1511: * caller surrenders "ownership" of urb
1512: * expects usb_submit_urb() to have sanity checked and conditioned all
1513: * inputs in the urb
1514: */
1515: int usb_hcd_submit_urb (struct urb *urb, gfp_t mem_flags)
1516: {
1517: int status;
1518: struct usb_hcd *hcd = bus_to_hcd(urb->dev->bus);
1519:
1520: /* increment urb's reference count as part of giving it to the HCD
1521: * (which will control it). HCD guarantees that it either returns
1522: * an error or calls giveback(), but not both.
1523: */
1524: usb_get_urb(urb);
1525: atomic_inc(&urb->use_count);
1526: atomic_inc(&urb->dev->urbnum);
1527: usbmon_urb_submit(&hcd->self, urb);
1528:
1529: /* NOTE requirements on root-hub callers (usbfs and the hub
1530: * driver, for now): URBs' urb->transfer_buffer must be
1531: * valid and usb_buffer_{sync,unmap}() not be needed, since
1532: * they could clobber root hub response data. Also, control
1533: * URBs must be submitted in process context with interrupts
1534: * enabled.
1535: */
1536:
1537: if (is_root_hub(urb->dev)) {
1538: status = rh_urb_enqueue(hcd, urb, mem_flags);
1539: } else {
1540: status = map_urb_for_dma(hcd, urb, mem_flags);
1541: if (likely(status == 0)) {
1542: status = hcd->driver->urb_enqueue(hcd, urb, mem_flags);
1543: if (unlikely(status))
1544: unmap_urb_for_dma(hcd, urb);
1545: }
1546: }
1547:
1548: if (unlikely(status)) {
1549: usbmon_urb_submit_error(&hcd->self, urb, status);
1550: urb->hcpriv = NULL;
1551: INIT_LIST_HEAD(&urb->urb_list);
1552: atomic_dec(&urb->use_count);
1553: /*
1554: * Order the write of urb->use_count above before the read
1555: * of urb->reject below. Pairs with the memory barriers in
1556: * usb_kill_urb() and usb_poison_urb().
1557: */
1558: smp_mb__after_atomic();
1559:
1560: atomic_dec(&urb->dev->urbnum);
1561: if (atomic_read(&urb->reject))
1562: wake_up(&usb_kill_urb_queue);
1563: usb_put_urb(urb);
1564: }
1565: return status;
1566: }
SourceFile:drivers/usb/core/hcd.c]
|
| 60/2 |
2026/07/28 01:35 |
llm |
expert |
0m
Model:
Tokens:
input: 60178
output: 45
thoughts: 1045
Thoughts: **Analyzing the USB Host Controller Driver Vulnerability**
Okay, so I'm looking at this bug report, and it seems like a juicy one. The heart of the problem lies within how `usb_hcd_submit_urb` interacts with the USB bus structure. It calls `bus_to_hcd`, which is essentially a `container_of` macro that's used to derive the `usb_hcd` structure from the `usb_bus` structure. The vulnerability stems from how this macro operates with potentially controlled data.
The user can manipulate `urb->dev->bus`, which is passed to the macro. Since the user controls this value, a clever attacker could craft a "fake" `usb_hcd` struct. If SMAP is disabled, this fake struct can even reside in user space, though it's still possible to trigger it in kernel space with spraying.
The following call is `hcd->driver->urb_enqueue(hcd, urb, mem_flags)`. Crucially, `hcd->driver` is a pointer within that fake `usb_hcd` structure. Therefore, by controlling `hcd`, the attacker can make `hcd->driver` point to *anywhere* in memory, effectively making `urb_enqueue` an arbitrary function call, which gives arbitrary code execution.
Now, a critical detail emerges: `urb->dev->bus` is read from a freed `usb_device` structure. This means the attacker must somehow spray data over that freed `usb_device` data to overwrite the controlled `urb->dev->bus` field. This presents a crucial use-after-free (UAF) condition.
The UAF can be triggered by calling `ioctl(I2C_RDWR)` on `/dev/i2c-X`.
The next question: is this accessible from unprivileged processes? The `/dev/i2c-X` device nodes usually have restrictive permissions (root:root, 0600 or 0660 with i2c group). Typically, unprivileged users can't open these devices directly. I initially considered a "race" where the user opens the device when it's accessible and then the device disconnects, but that doesn't seem likely because unprivileged users couldn't open the device in the first place.
I also thought of other potential avenues such as an internal I2C call triggered by a V4L2 device and its driver, which the user *could* have access to. If a V4L2 device's driver, for instance, uses I2C to communicate with its tuner, and has a race condition in the `disconnect` function, it might call `i2c_transfer` *after* the I2C adapter is unregistered. The attacker could exploit this, by issuing ioctls that would translate to `i2c_transfer`, like through `/dev/video0`.
However, the bug report specifically mentions `i2cdev_ioctl_rdwr`. The provided stack trace confirms this: It's being triggered directly via `/dev/i2c-X`. The report's analysis notes that syzkaller likely triggered the bug. Syzkaller is a fuzzer that runs as root. So, the direct attack via `/dev/i2c-X` *is* possible with root-level privileges.
So, the key questions are: can an unprivileged user trigger this vulnerability directly through `/dev/i2c-X`? No, because of the permissions. Can they trigger it through a driver that has a UAF? Yes, but that would be a bug in that specific driver, and not a direct exploitation of `i2c-core` or `i2c-dev`. So the core vulnerability is that `i2c-dev` allows `ioctl(I2C_RDWR)` *after* the adapter has been unregistered. Does `i2c-dev` have any mechanisms to prevent this? I need to check the code.
|
| 61/2 |
2026/07/28 01:35 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-dev.c Name:i2cdev_notifier_call]
Results: map[SourceCode: 722: static int i2cdev_notifier_call(struct notifier_block *nb, unsigned long action,
723: void *data)
724: {
725: struct device *dev = data;
726:
727: switch (action) {
728: case BUS_NOTIFY_ADD_DEVICE:
729: return i2cdev_attach_adapter(dev);
730: case BUS_NOTIFY_DEL_DEVICE:
731: return i2cdev_detach_adapter(dev);
732: }
733:
734: return NOTIFY_DONE;
735: }
SourceFile:drivers/i2c/i2c-dev.c]
|
| 62/2 |
2026/07/28 01:35 |
llm |
expert |
0m
Model:
Tokens:
input: 62349
output: 45
thoughts: 18
Thoughts: Okay, here's my summary, taking on the persona you requested:
**Detaching the I2C Adapter**
Right, so I'm thinking about the `i2cdev_detach_adapter` function. That one's pretty crucial, especially from a resource management perspective. It's the point at which we need to clean up and release everything related to an I2C adapter when it's physically removed, or logically removed for some reason. It's triggered when the kernel detects that the adapter is no longer present or active. That likely involves a few important cleanup operations. Weâre releasing all resources tied to the adapter, and potentially invalidating any associated device handles. Ensuring we do this correctly is fundamental to avoiding resource leaks and potential crashes down the line. We canât just let these things hang around. We need a clean, consistent state when an adapter goes away. Weâve probably got some graceful handling in place, too, maybe error reporting or logging, but the core function is the detachment process itself.
|
| 63/2 |
2026/07/28 01:35 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-dev.c Name:i2cdev_detach_adapter]
Results: map[SourceCode: 703: static int i2cdev_detach_adapter(struct device *dev)
704: {
705: struct i2c_adapter *adap;
706: struct i2c_dev *i2c_dev;
707:
708: if (dev->type != &i2c_adapter_type)
709: return NOTIFY_DONE;
710: adap = to_i2c_adapter(dev);
711:
712: i2c_dev = i2c_dev_get_by_minor(adap->nr);
713: if (!i2c_dev) /* attach_adapter must have failed */
714: return NOTIFY_DONE;
715:
716: put_i2c_dev(i2c_dev, true);
717:
718: pr_debug("adapter [%s] unregistered\n", adap->name);
719: return NOTIFY_OK;
720: }
SourceFile:drivers/i2c/i2c-dev.c]
|
| 64/2 |
2026/07/28 01:35 |
llm |
expert |
0m
Model:
Tokens:
input: 62945
output: 44
thoughts: 12
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm an expert in embedded systems and I2C communication:
**I2C Device Initialization: My Internal Checklist**
Alright, `put_i2c_dev` has been called. Instantly, my mind starts running through the usual checklist for I2C device interaction. First, I need to understand the context. What device is this related to? What's the intended purpose of this call? Is this part of a driver initialization sequence, a read operation, a write operation, or something else entirely? Knowing the overall goal will heavily influence the assumptions I can make and the steps I need to verify.
Next, I'm thinking about the arguments to the function. What does `put_i2c_dev` *expect* as input? I'm rapidly cycling through the typical parameters in my head: the I2C bus number or handle, the device address, any relevant register addresses, the data buffer (if there is one). I'm checking to see if those arguments are valid; are they within expected ranges, is the device address a known, supported one, and if the bus is active? Any obvious errors here are a signal to abort, probably with a specific error code.
Then I am thinking about how the I2C bus is currently configured. Are there any existing bus locks I need to respect? Are there any device-specific timing constraints that must be accounted for before issuing a command? Does this specific `put_i2c_dev` call suggest that I am interacting with the device directly, or is there an abstraction layer? I need to know how my actions fit into the overall device management scheme.
Finally, I'm already anticipating the potential outcomes: Did the call succeed? Did it return a standard error code? If it failed, what could be the possible causes? Bad bus, bad device address, the need for a reset? What monitoring tools are available to help debug this? The information and logic I am considering now will determine what information I will want to capture later.
|
| 65/2 |
2026/07/28 01:35 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-dev.c Name:put_i2c_dev]
Results: map[SourceCode: 88: static void put_i2c_dev(struct i2c_dev *i2c_dev, bool del_cdev)
89: {
90: spin_lock(&i2c_dev_list_lock);
91: list_del(&i2c_dev->list);
92: spin_unlock(&i2c_dev_list_lock);
93: if (del_cdev)
94: cdev_device_del(&i2c_dev->cdev, &i2c_dev->dev);
95: put_device(&i2c_dev->dev);
96: }
SourceFile:drivers/i2c/i2c-dev.c]
|
| 66/2 |
2026/07/28 01:35 |
llm |
expert |
0m
Model:
Tokens:
input: 63639
output: 46
thoughts: 209
Thoughts: **Vulnerability Analysis: I2C Character Device Removal**
Okay, so I'm looking at this `put_i2c_dev` function, and it calls `cdev_device_del`, which is supposed to remove the character device. That's fine, but then it hits me: if a file handle is still open, the `file` struct *still* exists, doesn't it? Which means `ioctl` can still be called on it! That's a huge potential race condition.
My immediate question is whether `i2cdev_ioctl` has any kind of check to see if the underlying device has been removed. Time to dive into the code. Let's see... here's `i2cdev_ioctl`:
```c
static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct i2c_client *client = file->private_data;
...
```
Right, it uses `client->adapter`. Hmm... but there's no check! There's no check to see if `client->adapter` is still registered. That's a pretty big oversight.
Wait a second, `client->adapter` *is* the `i2c_adapter` itself, isn't it? So, the real question is whether the `i2c_adapter` struct has any flag, any indicator, that it's been unregistered. If not, we have a significant vulnerability here. The device might be conceptually removed, but a user-space process with an open file handle could continue to call `ioctl` on a dangling pointer! I need to investigate `i2c_del_adapter` next. This needs a much closer look.
|
| 67/2 |
2026/07/28 01:35 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-base.c Name:i2c_del_adapter]
Results: map[SourceCode:1795: /**
1796: * i2c_del_adapter - unregister I2C adapter
1797: * @adap: the adapter being unregistered
1798: * Context: can sleep
1799: *
1800: * This unregisters an I2C adapter which was previously registered
1801: * by @i2c_add_adapter or @i2c_add_numbered_adapter.
1802: */
1803: void i2c_del_adapter(struct i2c_adapter *adap)
1804: {
1805: struct i2c_adapter *found;
1806:
1807: /* First make sure that this adapter was ever added */
1808: mutex_lock(&core_lock);
1809: found = idr_find(&i2c_adapter_idr, adap->nr);
1810: if (found == adap)
1811: idr_replace(&i2c_adapter_idr, NULL, adap->nr);
1812: mutex_unlock(&core_lock);
1813: if (found != adap) {
1814: pr_debug("attempting to delete unregistered adapter [%s]\n", adap->name);
1815: return;
1816: }
1817:
1818: i2c_acpi_remove_space_handler(adap);
1819:
1820: i2c_deregister_clients(adap);
1821:
1822: /* device name is gone after device_unregister */
1823: dev_dbg(&adap->dev, "adapter [%s] unregistered\n", adap->name);
1824:
1825: pm_runtime_disable(&adap->dev);
1826:
1827: i2c_host_notify_irq_teardown(adap);
1828:
1829: debugfs_remove_recursive(adap->debugfs);
1830:
1831: /* wait until all references to the device are gone
1832: *
1833: * FIXME: This is old code and should ideally be replaced by an
1834: * alternative which results in decoupling the lifetime of the struct
1835: * device from the i2c_adapter, like spi or netdev do. Any solution
1836: * should be thoroughly tested with DEBUG_KOBJECT_RELEASE enabled!
1837: */
1838: init_completion(&adap->dev_released);
1839: device_unregister(&adap->dev);
1840: wait_for_completion(&adap->dev_released);
1841:
1842: /* free bus id */
1843: mutex_lock(&core_lock);
1844: idr_remove(&i2c_adapter_idr, adap->nr);
1845: mutex_unlock(&core_lock);
1846:
1847: /* Clear the device structure in case this adapter is ever going to be
1848: added again */
1849: memset(&adap->dev, 0, sizeof(adap->dev));
1850: }
SourceFile:drivers/i2c/i2c-core-base.c]
|
| 68/2 |
2026/07/28 01:35 |
llm |
expert |
0m
Model:
Tokens:
input: 65163
output: 48
thoughts: 65
Thoughts: **Delving into the I2C Adapter Release Sequence**
Okay, hold on a second! I need to trace this I2C adapter deallocation more carefully. My initial understanding was incomplete. This `i2c_del_adapter` functionâŚit calls `wait_for_completion(&adap->dev_released)`. Right, right. Now, where is `adap->dev_released` actually being signalled as complete? Ah, yes, it's the `release` function that's part of `adap->dev`. That makes sense. The device release is the crucial point for synchronization. Let me make sure, then the next step is to examine `i2c_adapter_dev_release`. It's all about ensuring that the device is truly freed before we proceed with deleting the adapter. We need to be absolutely certain we're not racing.
|
| 69/2 |
2026/07/28 01:35 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-base.c Name:i2c_adapter_dev_release]
Results: map[SourceCode:1231: static void i2c_adapter_dev_release(struct device *dev)
1232: {
1233: struct i2c_adapter *adap = to_i2c_adapter(dev);
1234: complete(&adap->dev_released);
1235: }
SourceFile:drivers/i2c/i2c-core-base.c]
|
| 70/2 |
2026/07/28 01:35 |
llm |
expert |
0m
Model:
Tokens:
input: 65576
output: 36
thoughts: 576
Thoughts: **The I2C Adapter and USB Disconnect Conundrum**
Okay, wait a minute. This is bothering me. If `i2c_del_adapter` is designed to wait for `adap->dev_released`, then it *blocks* until all references to `adap->dev` are dropped, right? And, hold on, `i2cdev_open` calls `i2c_get_adapter`, which in turn calls `get_device(&adap->dev)`. That means, as long as a file associated with the I2C adapter is open, the reference count of `adap->dev` remains greater than zero.
So, am I understanding this correctly? Then `i2c_adapter_dev_release` is *never* called! Consequently, `i2c_del_adapter` blocks... forever! This is a serious problem. If `i2c_del_adapter` is blocking forever, the USB disconnect function, which depends on it, will also block forever! Then the USB device will never be freed!
Now, how does this use-after-free even happen if the USB device is never freed? Let's trace it through. When a USB device disconnects, `usb_disconnect` is called, which then calls `device_del(&udev->dev)`. That, in turn, calls `bus_remove_device`, which eventually calls the driver's `disconnect` function. And it is the driver's disconnect function that calls `i2c_del_adapter`. See the problem now?
If `i2c_del_adapter` blocks, then `usb_disconnect` blocks! Therefore, the USB device *cannot* be freed! This doesn't seem right. But is that actually the case? Time to check the allocation trace, and confirm the USB disconnect flow.
Based on the allocation trace, it shows the following:
```
_kzalloc_noprof include/linux/slab.h:1309 [inline]
usb_alloc_dev+0x5f/0xf10 drivers/usb/core/usb.c:651
usb_add_hcd+0x407/0xf80 drivers/usb/core/hcd.c:2880
dummy_hcd_probe+0x134/0x270 drivers/usb/gadget/udc/dummy_hcd.c:2722
```
Ah, it's the root hub of a dummy HCD! This is allocated in `usb_add_hcd`. Okay, so when the dummy HCD is removed, `usb_remove_hcd` is called, which calls `usb_disconnect` on the root hub.
Now, the question is, does `usb_disconnect` block if a child device is held open? I need to go back and check the `usb_disconnect` function in detail to get this straight. This is a subtle but critical point that I need to be sure about.
|
| 71/2 |
2026/07/28 01:35 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/core/hub.c Name:usb_disconnect]
Results: map[SourceCode:2298: /**
2299: * usb_disconnect - disconnect a device (usbcore-internal)
2300: * @pdev: pointer to device being disconnected
2301: *
2302: * Context: task context, might sleep
2303: *
2304: * Something got disconnected. Get rid of it and all of its children.
2305: *
2306: * If *pdev is a normal device then the parent hub must already be locked.
2307: * If *pdev is a root hub then the caller must hold the usb_bus_idr_lock,
2308: * which protects the set of root hubs as well as the list of buses.
2309: *
2310: * Only hub drivers (including virtual root hub drivers for host
2311: * controllers) should ever call this.
2312: *
2313: * This call is synchronous, and may not be used in an interrupt context.
2314: */
2315: void usb_disconnect(struct usb_device **pdev)
2316: {
2317: struct usb_port *port_dev = NULL;
2318: struct usb_device *udev = *pdev;
2319: struct usb_hub *hub = NULL;
2320: int port1 = 1;
2321:
2322: /* mark the device as inactive, so any further urb submissions for
2323: * this device (and any of its children) will fail immediately.
2324: * this quiesces everything except pending urbs.
2325: */
2326: usb_set_device_state(udev, USB_STATE_NOTATTACHED);
2327: dev_info(&udev->dev, "USB disconnect, device number %d\n",
2328: udev->devnum);
2329:
2330: /*
2331: * Ensure that the pm runtime code knows that the USB device
2332: * is in the process of being disconnected.
2333: */
2334: pm_runtime_barrier(&udev->dev);
2335:
2336: usb_lock_device(udev);
2337:
2338: hub_disconnect_children(udev);
2339:
2340: /* deallocate hcd/hardware state ... nuking all pending urbs and
2341: * cleaning up all state associated with the current configuration
2342: * so that the hardware is now fully quiesced.
2343: */
2344: dev_dbg(&udev->dev, "unregistering device\n");
2345: usb_disable_device(udev, 0);
2346: usb_hcd_synchronize_unlinks(udev);
2347:
2348: if (udev->parent) {
2349: port1 = udev->portnum;
2350: hub = usb_hub_to_struct_hub(udev->parent);
2351: port_dev = hub->ports[port1 - 1];
2352:
2353: sysfs_remove_link(&udev->dev.kobj, "port");
2354: sysfs_remove_link(&port_dev->dev.kobj, "device");
2355:
2356: /*
2357: * As usb_port_runtime_resume() de-references udev, make
2358: * sure no resumes occur during removal
2359: */
2360: if (!test_and_set_bit(port1, hub->child_usage_bits))
2361: pm_runtime_get_sync(&port_dev->dev);
2362:
2363: typec_deattach(port_dev->connector, &udev->dev);
2364: }
2365:
2366: usb_remove_ep_devs(&udev->ep0);
2367: usb_unlock_device(udev);
2368:
2369: if (udev->usb4_link)
2370: device_link_del(udev->usb4_link);
2371:
2372: /* Unregister the device. The device driver is responsible
2373: * for de-configuring the device and invoking the remove-device
2374: * notifier chain (used by usbfs and possibly others).
2375: */
2376: device_del(&udev->dev);
2377:
2378: /* Free the device number and delete the parent's children[]
2379: * (or root_hub) pointer.
2380: */
2381: release_devnum(udev);
2382:
2383: /* Avoid races with recursively_mark_NOTATTACHED() */
2384: spin_lock_irq(&device_state_lock);
2385: *pdev = NULL;
2386: spin_unlock_irq(&device_state_lock);
2387:
2388: if (port_dev && test_and_clear_bit(port1, hub->child_usage_bits))
2389: pm_runtime_put(&port_dev->dev);
2390:
2391: hub_free_dev(udev);
2392:
2393: put_device(&udev->dev);
2394: }
SourceFile:drivers/usb/core/hub.c]
|
| 72/2 |
2026/07/28 01:35 |
llm |
expert |
0m
Model:
Tokens:
input: 68334
output: 40
thoughts: 1416
Thoughts: **The I2C Adapter and the Dangling Parent Pointer: A USB Disconnect Conundrum**
Okay, here's how I'm piecing this together. It starts with the familiar dance of a USB device disconnect: `usb_disconnect` triggers `device_del` on the USB device itself. `device_del` cascades down, calling `bus_remove_device`, unbinding drivers. If we're dealing with an I2C adapter driver, we hit its `disconnect` function. Now, this is where it gets interesting because this disconnect function can block if the driver calls `i2c_del_adapter` and is blocked on `adap->dev_released`.
The typical scenario is that an I2C adapter is a child of a USB interface. `usb_disconnect` calls `hub_disconnect_children` which unregisters the USB interface, thus its driver's `disconnect` method is called. This driver then calls `i2c_del_adapter`. `i2c_del_adapter` unregisters the I2C adapter device by calling `device_unregister`. In this process, the parent reference (the USB interface) is dropped, which is where things can go wrong if `i2cdev_open` is holding a reference to the I2C adapter device. This then blocks the `i2c_del_adapter` call, and the disconnection flow blocks all the way back to `usb_disconnect`, potentially preventing the complete removal of the USB device. This is the first place a problem is possible, where the driver's `disconnect` blocks, and that's it.
However, the question is how this translates to a use-after-free situation? I was confused. We have a USB device, presumably the root hub of the dummy HCD, where `usb_alloc_dev` allocates `struct usb_device`, the root hub. This root hub normally doesn't have an associated I2C adapter, which is what caused my confusion. Normally, it would be the USB device's driver registering an I2C adapter, which would have the USB interface as its parent, not the root hub. I had to go back to the code and remind myself about the concept of `i2c_adapter_depth`. `i2c_adapter_depth` traverses up the parent pointers of the I2C adapter. The parent is the USB interface, the grandparent is the USB device, and the great-grandparent is the root hub.
Here's the problem: The issue must be that the *root hub* is being freed prematurely. Why? Well, if the root hub is being freed, it means its reference count is going to zero. A USB device's `dev.parent` is set to the root hub and `device_add` increments the refcount of the parent (the root hub), which means the root hub should not be freed while the USB device exists. The USB device, and its USB interface, and the I2C adapter all hold references to each other.
The critical insight lies in how `device_del` behaves. `device_del` *drops* the reference to the parent. But it **doesn't clear the `dev->parent` pointer**! This becomes a dangling pointer. If the parent is freed, then this pointer is invalid, and this is where the UAF happens.
The scenario starts with the dummy HCD being removed. `usb_remove_hcd` calls `usb_disconnect` on the *root hub*. This is the critical point. This means that a `device_del` call is made on the parent before all the children are taken care of! That's the problem.
|
| 73/2 |
2026/07/28 01:36 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/core/hcd.c Name:usb_remove_hcd]
Results: map[SourceCode:3019: /**
3020: * usb_remove_hcd - shutdown processing for generic HCDs
3021: * @hcd: the usb_hcd structure to remove
3022: *
3023: * Context: task context, might sleep.
3024: *
3025: * Disconnects the root hub, then reverses the effects of usb_add_hcd(),
3026: * invoking the HCD's stop() method.
3027: */
3028: void usb_remove_hcd(struct usb_hcd *hcd)
3029: {
3030: struct usb_device *rhdev;
3031: bool rh_registered;
3032:
3033: if (!hcd) {
3034: pr_debug("%s: hcd is NULL\n", __func__);
3035: return;
3036: }
3037: rhdev = hcd->self.root_hub;
3038:
3039: dev_info(hcd->self.controller, "remove, state %x\n", hcd->state);
3040:
3041: usb_get_dev(rhdev);
3042: clear_bit(HCD_FLAG_RH_RUNNING, &hcd->flags);
3043: if (HC_IS_RUNNING (hcd->state))
3044: hcd->state = HC_STATE_QUIESCING;
3045:
3046: dev_dbg(hcd->self.controller, "roothub graceful disconnect\n");
3047: spin_lock_irq (&hcd_root_hub_lock);
3048: rh_registered = hcd->rh_registered;
3049: hcd->rh_registered = 0;
3050: spin_unlock_irq (&hcd_root_hub_lock);
3051:
3052: #ifdef CONFIG_PM
3053: cancel_work_sync(&hcd->wakeup_work);
3054: #endif
3055: cancel_work_sync(&hcd->died_work);
3056:
3057: mutex_lock(&usb_bus_idr_lock);
3058: if (rh_registered)
3059: usb_disconnect(&rhdev); /* Sets rhdev to NULL */
3060: mutex_unlock(&usb_bus_idr_lock);
3061:
3062: /*
3063: * flush_work() isn't needed here because:
3064: * - driver's disconnect() called from usb_disconnect() should
3065: * make sure its URBs are completed during the disconnect()
3066: * callback
3067: *
3068: * - it is too late to run complete() here since driver may have
3069: * been removed already now
3070: */
3071:
3072: /* Prevent any more root-hub status calls from the timer.
3073: * The HCD might still restart the timer (if a port status change
3074: * interrupt occurs), but usb_hcd_poll_rh_status() won't invoke
3075: * the hub_status_data() callback.
3076: */
3077: usb_stop_hcd(hcd);
3078:
3079: if (usb_hcd_is_primary_hcd(hcd)) {
3080: if (hcd->irq > 0)
3081: free_irq(hcd->irq, hcd);
3082: }
3083:
3084: usb_deregister_bus(&hcd->self);
3085: hcd_buffer_destroy(hcd);
3086:
3087: usb_phy_roothub_power_off(hcd->phy_roothub);
3088: usb_phy_roothub_exit(hcd->phy_roothub);
3089:
3090: usb_put_invalidate_rhdev(hcd);
3091: hcd->flags = 0;
3092: }
SourceFile:drivers/usb/core/hcd.c]
|
| 74/2 |
2026/07/28 01:36 |
llm |
expert |
0m
Model:
Tokens:
input: 71755
output: 51
thoughts: 661
Thoughts: **Analyzing a Potential USB Root Hub UAF**
Okay, so I'm tracing this USB removal process, trying to understand how a Use-After-Free vulnerability on the root hub could arise. It starts with `usb_remove_hcd`, which, as expected, calls `usb_disconnect(&rhdev)`. `usb_disconnect` then kicks off the device removal sequence. First, it calls `device_del(&udev->dev)` on the root hub itself. This drops a reference to the root hub's parent, which is the HCD controller device. That seems reasonable so far.
But then, and this is where it gets interesting, `usb_disconnect` also calls `hub_disconnect_children(udev)`. This is where all the devices attached to the root hub get disconnected. For *each* child device, it calls `device_del`. Crucially, `device_del` again drops a reference to the root hub. This means the `dev.parent` pointer of the child device becomes a dangling pointer to the root hub, doesn't it? That's a potential problem right there.
The sequence continues: the child's interfaces are then disconnected. For *each* interface, `device_del` is called *again*. So, `device_del` drops the reference to the child device, making the interface's `dev.parent` pointer now point to a dangling address (the child device). Following this, the interface's driver, let's say `i2c-tiny-usb` in this example, is disconnected. The driver then calls `i2c_del_adapter`.
`i2c_del_adapter` calls `device_unregister(&adap->dev)`. This further leads to `device_del` and `put_device`. So, `device_del` drops the reference to the interface, leaving `adap->dev.parent` with a dangling pointer to the interface. And that's where I need to be careful, because `i2c_del_adapter` actually *blocks* waiting for `adap->dev_released`!
This blocking is a key point. If `i2c_del_adapter` waits on `adap->dev_released`, the `disconnect` function of that interface driver also blocks. When this happens, the `device_del` function of the interface will block as well.
Because `device_del` will be called by `bus_remove_device` after this blocking, the disconnection of the interface can then be blocked. Now, since `device_del` of the interface is blocking, `hub_disconnect_children` blocks, because it calls `device_del` for each child. Further up, `usb_disconnect` blocks, and that blocks `usb_remove_hcd`. Thus, the root hub never gets freed!
So, if the root hub never gets freed, how can there be a Use-After-Free condition? This is contradictory! I'm missing something, or the issue is somewhere else. The code shows that the `i2c_del_adapter` *does* indeed block:
```c
init_completion(&adap->dev_released);
device_unregister(&adap->dev);
wait_for_completion(&adap->dev_released);
```
So it blocks! But, hang on... what if the *driver* doesn't call `i2c_del_adapter` in its `disconnect` function? Or what if it calls it asynchronously? That's the crucial question. I need to check the `i2c-tiny-usb` driver implementation to see what the disconnect path looks like.
|
| 75/2 |
2026/07/28 01:36 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/busses/i2c-tiny-usb.c Name:i2c_tiny_usb_disconnect]
Results: map[SourceCode: 278: static void i2c_tiny_usb_disconnect(struct usb_interface *interface)
279: {
280: struct i2c_tiny_usb *dev = usb_get_intfdata(interface);
281:
282: i2c_del_adapter(&dev->adapter);
283: usb_set_intfdata(interface, NULL);
284: kfree(dev);
285:
286: dev_dbg(&interface->dev, "disconnected\n");
287: }
SourceFile:drivers/i2c/busses/i2c-tiny-usb.c]
|
| 76/2 |
2026/07/28 01:36 |
llm |
expert |
0m
Model:
Tokens:
input: 73441
output: 66
thoughts: 6348
Thoughts: **Analyzing a Potential Use-After-Free Vulnerability in I2C Adapter Handling**
Okay, so I'm digging into this crash report, and it looks like a classic Use-After-Free (UAF) situation. The key is how the I2C adapter, which is linked to a USB device, is being handled during disconnection. My immediate thought is that `i2c_del_adapter` is the culprit. It *synchronously* removes the adapter, and if that call blocks (which it normally does while a file is open), the USB device should remain alive, right? If it *doesn't* block, then the root hub (which the USB device is connected to) gets freed *before* `i2c_del_adapter` finishes.
The crash report points to the root hub being the freed memory that's being accessed. So, I need to figure out how the root hub gets freed when an I2C adapter, presumably linked to it through a USB device, is still alive and used. Initial thoughts point to the I2C adapter being incorrectly unregistered. I then checked `i2cdev_open` and realized that after `i2c_del_adapter` removes the adapter from the IDR, `i2c_get_adapter` will return NULL. So the user canât open the file after it's unregistered.
The key then, has to be something else. What if the driver *forgets* to call `i2c_del_adapter`? Then the adapter is leaked, it's never unregistered. My hunch is that this is the real root cause!
When a USB device is disconnected, the USB subsystem calls `device_del` on the USB interface. If the I2C adapter is still linked to it, the USB interface is *also* kept alive. However, the child device's `device_del` would drop the reference to its parent. So the root hub *could* be freed by `device_del` in this scenario, while the I2C adapter still exists. Then, when the user tries to access the I2C adapter, it traverses up the parent chain (USB interface, USB device, root hub), and boom â UAF on the freed root hub.
The `i2c_adapter_depth` function, which is called during I2C transfer operations, is where the dangling parent pointers come into play. It's supposed to prevent a lockdep warning, but it unintentionally makes the code vulnerable.
However, the question is what can trigger the call to `i2c_adapter_depth`. Syzkaller and/or malicious USB devices can trigger this. If a driver registers an I2C adapter and then forgets to unregister it, this is a major problem.
The problem, however, is a *lack* of unregistering. Even if the driver *does* call `i2c_del_adapter`, the adapter is unlinked.
This means it's a buggy USB driver, likely triggered by syzkaller emulating a device, that forgets to call `i2c_del_adapter`. This driver could be any of the many USB drivers that register I2C adapters, or any USB driver which calls some `device_add` on an I2C adapter, and *then* fails to call `i2c_del_adapter`.
|
| 77/2 |
2026/07/28 01:37 |
tool |
git-log |
0m
Args: map[CodeRegexp:adapter->dev\.parent Count:10 MessageRegexps:[i2c use-after-free] SourcePath:drivers/i2c/i2c-core-base.c]
Results:
|
| 78/2 |
2026/07/28 01:37 |
llm |
expert |
0m
Model:
Tokens:
input: 80580
output: 50
thoughts: 19
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Hunting Down That UAF in i2c_adapter_depth Commits**
Right, let's get down to brass tacks. I need to track down those dang User-After-Free vulnerabilities specifically within the `i2c_adapter_depth` context. I'm going to start by getting my hands dirty and doing some targeted commit searching. I'll be sifting through the commit history. The focus is pinpointing those patches that explicitly address UAF issues related to that module. I'm thinking I'll start with keywords like "use-after-free", "UAF", and maybe some more subtle indicators, such as mentions of double-frees or dangling pointer dereferences, all within the code touching that specific section. Hopefully, the commit messages are descriptive enough to give a clear indication of the root cause and the fix implemented. I might need to refine my search terms as I go, depending on what I turn up initially. I'm also going to be very thorough. I don't want to miss anything, even if the commit doesn't scream "UAF" from the get-go. Might be buried in a larger commit that only alludes to the vulnerability. I'll need to examine the code changes themselves, not just the messages, to confirm the relevance.
|
| 79/2 |
2026/07/28 01:37 |
tool |
git-log |
0m
Args: map[Count:10 MessageRegexps:[i2c_adapter_depth] SourcePath:drivers/i2c/i2c-core-base.c]
Results: map[Output:232219b9a464 i2c-cht-wc: Fix lockdep warning
2771dc34ddaa i2c: add a warning to i2c_adapter_depth()
a5ecddfe0bf8 lockdep: make MAX_LOCKDEP_SUBCLASSES unconditionally visible
8dd1fe1594a7 i2c: export i2c_adapter_depth()
]
|
| 80/2 |
2026/07/28 01:37 |
llm |
expert |
0m
Model:
Tokens:
input: 63077
output: 24
thoughts: 0
|
| 81/2 |
2026/07/28 01:37 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 2771dc34ddaa313127d6431f23ae4cea26b869b0
Author: Bartosz Golaszewski <brgl@kernel.org>
Date: Fri Sep 16 18:02:44 2016 +0200
i2c: add a warning to i2c_adapter_depth()
This routine is only used together with lockdep for nested locking.
The number of lock subclasses is limited to 8 as defined in lockdep.h
Emit a warning if the adapter depth exceeds the maximum number of
lockdep subclasses.
Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
Acked-by: Peter Rosin <peda@axentia.se>
Acked-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c
index 981de21ab814..98fffa3a09f7 100644
--- a/drivers/i2c/i2c-core.c
+++ b/drivers/i2c/i2c-core.c
@@ -1433,6 +1433,9 @@ unsigned int i2c_adapter_depth(struct i2c_adapter *adapter)
while ((adapter = i2c_parent_is_i2c_adapter(adapter)))
depth++;
+ WARN_ONCE(depth >= MAX_LOCKDEP_SUBCLASSES,
+ "adapter depth exceeds lockdep subclass limit\n");
+
return depth;
}
EXPORT_SYMBOL_GPL(i2c_adapter_depth);
]
|
| 82/2 |
2026/07/28 01:37 |
llm |
expert |
0m
Model:
Tokens:
input: 63583
output: 29
thoughts: 0
|
| 83/2 |
2026/07/28 01:37 |
tool |
git-log |
0m
Args: map[Count:10 MessageRegexps:[i2c_adapter_depth]]
Results: map[Output:232219b9a464 i2c-cht-wc: Fix lockdep warning
2771dc34ddaa i2c: add a warning to i2c_adapter_depth()
a5ecddfe0bf8 lockdep: make MAX_LOCKDEP_SUBCLASSES unconditionally visible
8dd1fe1594a7 i2c: export i2c_adapter_depth()
]
|
| 84/2 |
2026/07/28 01:38 |
llm |
expert |
0m
Model:
Tokens:
input: 81693
output: 27
thoughts: 50
Thoughts: Here's my take on those thoughts:
**Analyzing a Potential I2C Adapter Depth Issue**
Okay, so the `i2c_adapter_depth` variableâthat's a key one for managing nested I2C adaptersâwas changed in commit `53801d2e762a i2c: core: fix lockdep warning for sparsely nested adapter chain`. That change seems relevant, and if a lockdep warning was addressed, then I need to carefully examine what was done there. Let's pull up that commit and see what it actually did to `i2c_adapter_depth`. I suspect it's dealing with cases where the adapter hierarchy isn't perfectly linear, which can be a tricky area, and is a strong candidate for an area of concern that I need to be aware of. I'll need to assess the context of the change, and the specific impact.
|
| 85/2 |
2026/07/28 01:38 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 53801d2e762a611d972e91d151df56c3412ca5cf
Author: Daniel Mack <daniel@zonque.org>
Date: Wed Oct 18 11:46:13 2023 +0200
i2c: core: fix lockdep warning for sparsely nested adapter chain
When adapters are chained in a sparse manner (with intermediate MFD devices,
for instance) the code currently fails to use the correct subclass for
the adapter's bus_lock which leads to false-positive lockdep warnings.
Fix this by walking the entire pedigree of the device and count all
adapters along the way instead of just checking the immediate parent.
Signed-off-by: Daniel Mack <daniel@zonque.org>
Signed-off-by: Wolfram Sang <wsa@kernel.org>
diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c
index 7f30bcceebae..eac90a3cf61a 100644
--- a/drivers/i2c/i2c-core-base.c
+++ b/drivers/i2c/i2c-core-base.c
@@ -1194,9 +1194,11 @@ static void i2c_adapter_dev_release(struct device *dev)
unsigned int i2c_adapter_depth(struct i2c_adapter *adapter)
{
unsigned int depth = 0;
+ struct device *parent;
- while ((adapter = i2c_parent_is_i2c_adapter(adapter)))
- depth++;
+ for (parent = adapter->dev.parent; parent; parent = parent->parent)
+ if (parent->type == &i2c_adapter_type)
+ depth++;
WARN_ONCE(depth >= MAX_LOCKDEP_SUBCLASSES,
"adapter depth exceeds lockdep subclass limit\n");
]
|
| 86/2 |
2026/07/28 01:38 |
llm |
expert |
0m
Model:
Tokens:
input: 82485
output: 52
thoughts: 81
Thoughts: Okay, let me break this down.
**Diving into the I2C Adapter Traversal Change**
Right, so commit `53801d2e762a` has tweaked the `i2c_adapter_depth` function, specifically how it handles traversing the parent structure. Previously, it relied on `i2c_parent_is_i2c_adapter(adapter)`. Now, it's directly using `parent = parent->parent`. This is interesting, and I immediately want to understand the implications.
The switch from `i2c_parent_is_i2c_adapter` to direct pointer manipulation suggests a potentially significant change in how the I2C adapter hierarchy is navigated. I need to refresh my understanding of what `i2c_parent_is_i2c_adapter` does, and understand the logic behind this change. Specifically, I need to understand what this means for the integrity of the traversal, and if there are any subtle performance or safety trade-offs. The potential implications of simply hopping from `parent` to `parent->parent` are the most critical aspect to analyze.
|
| 87/2 |
2026/07/28 01:38 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-base.c Name:i2c_parent_is_i2c_adapter]
Results: map[SourceCode: 781: static inline struct i2c_adapter *
782: i2c_parent_is_i2c_adapter(const struct i2c_adapter *adapter)
783: {
784: #if IS_ENABLED(CONFIG_I2C_MUX)
785: struct device *parent = adapter->dev.parent;
786:
787: if (parent != NULL && parent->type == &i2c_adapter_type)
788: return to_i2c_adapter(parent);
789: else
790: #endif
791: return NULL;
792: }
SourceFile:include/linux/i2c.h]
|
| 88/2 |
2026/07/28 01:38 |
llm |
expert |
0m
Model:
Tokens:
input: 83070
output: 49
thoughts: 1399
Thoughts: **The UAF Conundrum: Delving into I2C Adapter Unregistration**
Okay, so I've been tracing this bug and I think I've got a handle on the root cause. Initially, I thought the problem was confined to the immediate parent in the `i2c_parent_is_i2c_adapter` check. Turns out, the new code traverses *all* parents up to the root, which is why we're seeing it hit the root hub. And because of this all parent traversal, it's accessing `parent->parent` without proper reference tracking. That's screaming "use-after-free" at me.
Here's the scenario: `device_del` drops the reference to the parent, but crucially, it *doesn't* clear `dev->parent`. So, if `device_del` is called on a device but the device is still alive (refcount > 0), `dev->parent` becomes a dangling pointer pointing to freed memory. And, if `i2c_adapter_depth` is later called on that device, it traverses this `dev->parent` pointer. Any time `i2c_adapter_depth` is called on a device that had its `device_del` called, it's a guaranteed access to a dangling pointer!
So, the critical bit is this: if `device_del` has been called on an `adapter->dev`, then `adapter->dev.parent` is a dangling pointer. `i2c_del_adapter` calls `device_unregister`, which in turn calls `device_del`, leading to this dangerous state. Then, `i2c_del_adapter` blocks in `wait_for_completion(&adap->dev_released)`. Here's the kicker: while it's blocked, the user calls `ioctl(I2C_RDWR)`. That ioctl leads to `i2c_transfer`, which leads to `i2c_adapter_lock_bus`, which calls `i2c_adapter_depth`. And there it is: `i2c_adapter_depth` accesses `adapter->dev.parent`, which is now a UAF! And get this, it doesn't even require a faulty driver! It's a race that's possible with *any* I2C adapter unregistered while a user has `/dev/i2c-X` open.
Let me break down the verification:
1. User opens `/dev/i2c-X`. This increments `adap->dev`'s refcount.
2. The I2C adapter device is removed (e.g., USB unplug).
3. The driver calls `i2c_del_adapter`.
4. `i2c_del_adapter` calls `device_unregister(&adap->dev)`.
5. `device_unregister` calls `device_del(&adap->dev)`.
6. `device_del` drops the reference to `adap->dev.parent`.
7. *If* this was the *last* reference to the parent, the parent is freed.
8. `i2c_del_adapter` calls `wait_for_completion(&adap->dev_released)`. It BLOCKS because the user still has the file open (refcount > 0).
9. The user calls `ioctl(I2C_RDWR)`.
10. `i2cdev_ioctl_rdwr` calls `i2c_transfer`.
11. `i2c_transfer` calls `i2c_adapter_lock_bus`.
12. `i2c_adapter_lock_bus` calls `i2c_adapter_depth`.
13. `i2c_adapter_depth` reads `adapter->dev.parent`.
14. Since the parent was freed in step 7, this is a UAF!
But wait, is the parent *actually* freed in step 7? If the parent is a USB interface, does `device_del` on the I2C adapter drop the last reference to the USB interface? Yes, it could if the USB interface had already been unregistered!
Okay, if the USB device is unplugged, `usb_disconnect` is called. `usb_disconnect` calls `device_del` on the USB interface. That `device_del` on the USB interface calls `bus_remove_device`, which calls the driver's `disconnect`. The driver's `disconnect` calls `i2c_del_adapter`. So that `device_del` on the USB interface is *currently executing*! The USB interface's refcount is *not* zero yet, because `device_del
|
| 89/2 |
2026/07/28 01:38 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-base.c Name:devm_i2c_add_adapter]
Results: map[SourceCode:1858: /**
1859: * devm_i2c_add_adapter - device-managed variant of i2c_add_adapter()
1860: * @dev: managing device for adding this I2C adapter
1861: * @adapter: the adapter to add
1862: * Context: can sleep
1863: *
1864: * Add adapter with dynamic bus number, same with i2c_add_adapter()
1865: * but the adapter will be auto deleted on driver detach.
1866: */
1867: int devm_i2c_add_adapter(struct device *dev, struct i2c_adapter *adapter)
1868: {
1869: int ret;
1870:
1871: ret = i2c_add_adapter(adapter);
1872: if (ret)
1873: return ret;
1874:
1875: return devm_add_action_or_reset(dev, devm_i2c_del_adapter, adapter);
1876: }
SourceFile:drivers/i2c/i2c-core-base.c]
|
| 90/2 |
2026/07/28 01:38 |
llm |
expert |
0m
Model:
Tokens:
input: 85875
output: 39
thoughts: 149
Thoughts: **Analyzing I2C Adapter Removal in Kernel Drivers**
Okay, so if I'm understanding this correctly, using `devm_i2c_add_adapter` means `devm_i2c_del_adapter` gets called automatically when the driver detaches. Makes sense. And that detach happens, as I recall, within `bus_remove_device`. So, in essence, it's just like using `i2c_del_adapter` directly, because it will be called during `bus_remove_device`. And that is a blocking call.
But hang on, what if this isn't a USB device at all? What if this is a platform device and the root hub thing is just some weird coincidence? No, that allocation trace I saw points directly to `dummy_hcd_probe`, so I'm thinking the `dummy_hcd` is the one to blame.
Wait a second... Could `dummy_hcd` *itself* be registering an I2C adapter? That's a possibility I hadn't considered. Let's dig into `dummy_hcd.c` and see if that's the source of this problem. Gotta confirm if this is the cause.
|
| 91/2 |
2026/07/28 01:38 |
tool |
grepper |
0m
Error:
Args: map[Expression:i2c PathPrefix:drivers/usb/gadget/udc/dummy_hcd.c]
Results:
|
| 92/2 |
2026/07/28 01:38 |
llm |
expert |
0m
Model:
Tokens:
input: 86337
output: 30
thoughts: 63
Thoughts: **Investigating I2C Absence in Dummy HCD**
Okay, so I'm looking at `dummy_hcd.c`, and I'm not seeing any I2C implementation there. That's unexpected, but it's not a showstopper. My next thought is, what if the I2C adapter isn't directly within the HCD, but is *registered* by a USB gadget? That's a plausible scenario, and it would explain its absence in the dummy driver.
Syzkaller, being the powerful fuzzing tool that it is, can configure these USB gadgets. The real question then becomes: does *any* USB gadget out there actually register an I2C adapter? If so, I need to know which one.
Therefore, the next logical step is to delve into the `drivers/usb/gadget/` directory. That's where I'll find the implementations of the various USB gadgets. I'll need to go through those and determine if any of them include I2C driver registration. This seems like the most likely path to resolving this.
|
| 93/2 |
2026/07/28 01:38 |
tool |
grepper |
0m
Args: map[Expression:i2c PathPrefix:drivers/usb/gadget/]
Results: map[Output:drivers/usb/gadget/udc/at91_udc.c=1542=static void at91_vbus_timer(struct timer_list *t)
--
drivers/usb/gadget/udc/at91_udc.c-1547- * If we are polling vbus it is likely that the gpio is on an
drivers/usb/gadget/udc/at91_udc.c:1548: * bus such as i2c or spi which may sleep, so schedule some work
drivers/usb/gadget/udc/at91_udc.c-1549- * to read the vbus gpio
--
drivers/usb/gadget/udc/lpc32xx_udc.c-21-#include <linux/dmapool.h>
drivers/usb/gadget/udc/lpc32xx_udc.c:22:#include <linux/i2c.h>
drivers/usb/gadget/udc/lpc32xx_udc.c-23-#include <linux/interrupt.h>
--
drivers/usb/gadget/udc/lpc32xx_udc.c=125=struct lpc32xx_udc {
--
drivers/usb/gadget/udc/lpc32xx_udc.c-130- spinlock_t lock;
drivers/usb/gadget/udc/lpc32xx_udc.c:131: struct i2c_client *isp1301_i2c_client;
drivers/usb/gadget/udc/lpc32xx_udc.c-132-
--
drivers/usb/gadget/udc/lpc32xx_udc.c=544=static void isp1301_udc_configure(struct lpc32xx_udc *udc)
--
drivers/usb/gadget/udc/lpc32xx_udc.c-548-
drivers/usb/gadget/udc/lpc32xx_udc.c:549: vendor = i2c_smbus_read_word_data(udc->isp1301_i2c_client, 0x00);
drivers/usb/gadget/udc/lpc32xx_udc.c:550: product = i2c_smbus_read_word_data(udc->isp1301_i2c_client, 0x02);
drivers/usb/gadget/udc/lpc32xx_udc.c-551-
--
drivers/usb/gadget/udc/lpc32xx_udc.c-558- /* Disable transparent UART mode first */
drivers/usb/gadget/udc/lpc32xx_udc.c:559: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-560- (ISP1301_I2C_MODE_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR),
--
drivers/usb/gadget/udc/lpc32xx_udc.c-563- /* Set full speed and SE0 mode */
drivers/usb/gadget/udc/lpc32xx_udc.c:564: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-565- (ISP1301_I2C_MODE_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR), ~0);
drivers/usb/gadget/udc/lpc32xx_udc.c:566: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-567- ISP1301_I2C_MODE_CONTROL_1, (MC1_SPEED_REG | MC1_DAT_SE0));
--
drivers/usb/gadget/udc/lpc32xx_udc.c-571- */
drivers/usb/gadget/udc/lpc32xx_udc.c:572: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-573- (ISP1301_I2C_MODE_CONTROL_2 | ISP1301_I2C_REG_CLEAR_ADDR), ~0);
--
drivers/usb/gadget/udc/lpc32xx_udc.c-577- value |= MC2_SPD_SUSP_CTRL;
drivers/usb/gadget/udc/lpc32xx_udc.c:578: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-579- ISP1301_I2C_MODE_CONTROL_2, value);
--
drivers/usb/gadget/udc/lpc32xx_udc.c-582- if (udc->board->vbus_drv_pol != 0)
drivers/usb/gadget/udc/lpc32xx_udc.c:583: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-584- ISP1301_I2C_OTG_CONTROL_1, OTG1_VBUS_DRV);
drivers/usb/gadget/udc/lpc32xx_udc.c-585- else
drivers/usb/gadget/udc/lpc32xx_udc.c:586: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-587- ISP1301_I2C_OTG_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR,
--
drivers/usb/gadget/udc/lpc32xx_udc.c-592- * is detected */
drivers/usb/gadget/udc/lpc32xx_udc.c:593: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-594- (ISP1301_I2C_OTG_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR), ~0);
drivers/usb/gadget/udc/lpc32xx_udc.c:595: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-596- ISP1301_I2C_OTG_CONTROL_1,
--
drivers/usb/gadget/udc/lpc32xx_udc.c-599- /* Discharge VBUS (just in case) */
drivers/usb/gadget/udc/lpc32xx_udc.c:600: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-601- ISP1301_I2C_OTG_CONTROL_1, OTG1_VBUS_DISCHRG);
drivers/usb/gadget/udc/lpc32xx_udc.c-602- msleep(1);
drivers/usb/gadget/udc/lpc32xx_udc.c:603: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-604- (ISP1301_I2C_OTG_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR),
--
drivers/usb/gadget/udc/lpc32xx_udc.c-606-
drivers/usb/gadget/udc/lpc32xx_udc.c:607: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-608- ISP1301_I2C_INTERRUPT_LATCH | ISP1301_I2C_REG_CLEAR_ADDR, ~0);
drivers/usb/gadget/udc/lpc32xx_udc.c-609-
drivers/usb/gadget/udc/lpc32xx_udc.c:610: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-611- ISP1301_I2C_INTERRUPT_FALLING | ISP1301_I2C_REG_CLEAR_ADDR, ~0);
drivers/usb/gadget/udc/lpc32xx_udc.c:612: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-613- ISP1301_I2C_INTERRUPT_RISING | ISP1301_I2C_REG_CLEAR_ADDR, ~0);
--
drivers/usb/gadget/udc/lpc32xx_udc.c-617- dev_info(udc->dev, "ISP1301 Version ID : 0x%04x\n",
drivers/usb/gadget/udc/lpc32xx_udc.c:618: i2c_smbus_read_word_data(udc->isp1301_i2c_client, 0x14));
drivers/usb/gadget/udc/lpc32xx_udc.c-619-
--
drivers/usb/gadget/udc/lpc32xx_udc.c=623=static void isp1301_pullup_set(struct lpc32xx_udc *udc)
--
drivers/usb/gadget/udc/lpc32xx_udc.c-626- /* Enable pullup for bus signalling */
drivers/usb/gadget/udc/lpc32xx_udc.c:627: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-628- ISP1301_I2C_OTG_CONTROL_1, OTG1_DP_PULLUP);
--
drivers/usb/gadget/udc/lpc32xx_udc.c-630- /* Enable pullup for bus signalling */
drivers/usb/gadget/udc/lpc32xx_udc.c:631: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-632- ISP1301_I2C_OTG_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR,
--
drivers/usb/gadget/udc/lpc32xx_udc.c=644=static void isp1301_pullup_enable(struct lpc32xx_udc *udc, int en_pullup,
--
drivers/usb/gadget/udc/lpc32xx_udc.c-653- else
drivers/usb/gadget/udc/lpc32xx_udc.c:654: /* defer slow i2c pull up setting */
drivers/usb/gadget/udc/lpc32xx_udc.c-655- schedule_work(&udc->pullup_job);
--
drivers/usb/gadget/udc/lpc32xx_udc.c=660=static void isp1301_set_powerstate(struct lpc32xx_udc *udc, int enable)
--
drivers/usb/gadget/udc/lpc32xx_udc.c-668- when VBUS is detected */
drivers/usb/gadget/udc/lpc32xx_udc.c:669: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-670- ISP1301_I2C_MODE_CONTROL_2 | ISP1301_I2C_REG_CLEAR_ADDR,
--
drivers/usb/gadget/udc/lpc32xx_udc.c-673- /* Power down ISP1301 */
drivers/usb/gadget/udc/lpc32xx_udc.c:674: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-675- ISP1301_I2C_MODE_CONTROL_2, MC2_GLOBAL_PWR_DN);
--
drivers/usb/gadget/udc/lpc32xx_udc.c=2827=static void vbus_work(struct lpc32xx_udc *udc)
--
drivers/usb/gadget/udc/lpc32xx_udc.c-2832- /* Discharge VBUS real quick */
drivers/usb/gadget/udc/lpc32xx_udc.c:2833: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-2834- ISP1301_I2C_OTG_CONTROL_1, OTG1_VBUS_DISCHRG);
--
drivers/usb/gadget/udc/lpc32xx_udc.c-2839- /* Disable VBUS discharge resistor */
drivers/usb/gadget/udc/lpc32xx_udc.c:2840: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-2841- ISP1301_I2C_OTG_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR,
--
drivers/usb/gadget/udc/lpc32xx_udc.c-2844- /* Clear interrupt */
drivers/usb/gadget/udc/lpc32xx_udc.c:2845: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-2846- ISP1301_I2C_INTERRUPT_LATCH |
--
drivers/usb/gadget/udc/lpc32xx_udc.c-2849- /* Get the VBUS status from the transceiver */
drivers/usb/gadget/udc/lpc32xx_udc.c:2850: value = i2c_smbus_read_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-2851- ISP1301_I2C_INTERRUPT_SOURCE);
--
drivers/usb/gadget/udc/lpc32xx_udc.c=2876=static int lpc32xx_start(struct usb_gadget *gadget,
--
drivers/usb/gadget/udc/lpc32xx_udc.c-2901- /* enable interrupts */
drivers/usb/gadget/udc/lpc32xx_udc.c:2902: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-2903- ISP1301_I2C_INTERRUPT_FALLING, INT_SESS_VLD | INT_VBUS_VLD);
drivers/usb/gadget/udc/lpc32xx_udc.c:2904: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-2905- ISP1301_I2C_INTERRUPT_RISING, INT_SESS_VLD | INT_VBUS_VLD);
--
drivers/usb/gadget/udc/lpc32xx_udc.c=2910=static int lpc32xx_stop(struct usb_gadget *gadget)
--
drivers/usb/gadget/udc/lpc32xx_udc.c-2913-
drivers/usb/gadget/udc/lpc32xx_udc.c:2914: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-2915- ISP1301_I2C_INTERRUPT_FALLING | ISP1301_I2C_REG_CLEAR_ADDR, ~0);
drivers/usb/gadget/udc/lpc32xx_udc.c:2916: i2c_smbus_write_byte_data(udc->isp1301_i2c_client,
drivers/usb/gadget/udc/lpc32xx_udc.c-2917- ISP1301_I2C_INTERRUPT_RISING | ISP1301_I2C_REG_CLEAR_ADDR, ~0);
--
drivers/usb/gadget/udc/lpc32xx_udc.c=2982=static int lpc32xx_udc_probe(struct platform_device *pdev)
--
drivers/usb/gadget/udc/lpc32xx_udc.c-3010-
drivers/usb/gadget/udc/lpc32xx_udc.c:3011: udc->isp1301_i2c_client = isp1301_get_client(isp1301_node);
drivers/usb/gadget/udc/lpc32xx_udc.c-3012- of_node_put(isp1301_node);
drivers/usb/gadget/udc/lpc32xx_udc.c:3013: if (!udc->isp1301_i2c_client) {
drivers/usb/gadget/udc/lpc32xx_udc.c-3014- return -EPROBE_DEFER;
--
drivers/usb/gadget/udc/lpc32xx_udc.c-3017- dev_info(udc->dev, "ISP1301 I2C device at address 0x%x\n",
drivers/usb/gadget/udc/lpc32xx_udc.c:3018: udc->isp1301_i2c_client->addr);
drivers/usb/gadget/udc/lpc32xx_udc.c-3019-
--
drivers/usb/gadget/udc/lpc32xx_udc.c-3168-err_put_client:
drivers/usb/gadget/udc/lpc32xx_udc.c:3169: put_device(&udc->isp1301_i2c_client->dev);
drivers/usb/gadget/udc/lpc32xx_udc.c-3170-
--
drivers/usb/gadget/udc/lpc32xx_udc.c=3176=static void lpc32xx_udc_remove(struct platform_device *pdev)
--
drivers/usb/gadget/udc/lpc32xx_udc.c-3199-
drivers/usb/gadget/udc/lpc32xx_udc.c:3200: put_device(&udc->isp1301_i2c_client->dev);
drivers/usb/gadget/udc/lpc32xx_udc.c-3201-}
]
|
| 94/2 |
2026/07/28 01:38 |
llm |
expert |
0m
Model:
Tokens:
input: 92222
output: 66
thoughts: 6234
Thoughts: **Analysis of a Kernel Use-After-Free Vulnerability in I2C Adapter Depth Traversal**
Okay, so I've been digging into this bug report, and it looks like a classic use-after-free vulnerability, specifically related to how the kernel traverses I2C adapter hierarchies. The key is that `i2c_adapter_depth` is accessing freed memory, and this is happening in a very specific scenario, which makes it particularly nasty. My initial thought was that this was I2C clients, but, no, it is I2C Adapters.
Hereâs the breakdown of my thinking: From the bug report, the crash is occurring when the kernel is probing `dummy_hcd`, the "root hub" is freed, and then `i2c_adapter_depth` tries to access it, causing the crash. This strongly suggests that `i2c_adapter_depth` is being called on an I2C adapter, and its parent chain includes this freed root hub.
The crux of the problem seems to be a USB device registering an I2C adapter, and then that USB device being disconnected, but the I2C adapter not being properly unregistered. In other words, its a peripheral trigger. There are two potential scenarios here: either the adapter isn't unregistered at all, or it *is* unregistered, but `i2c_adapter_depth` is called concurrently.
First, I considered if `i2c_del_adapter` was called, and if it blocks, but that would keep the device alive. `i2c_del_adapter` has to complete immediately. The only way is if the user opens the I2C file and then the I2C adapter is deleted. But `i2c_get_adapter` returns `NULL` if the adapter is unregistered. So the user cannot open the file. Then what if, `i2c_del_adapter` is called, but the user closes the file? That would unblock `i2c_del_adapter`, but the user cannot use `ioctl(I2C_RDWR)` if the file is closed. This cannot happen.
However, the more likely scenario seems to involve a USB driver calling `i2c_del_adapter` asynchronouslyâperhaps in a workqueueâafter the USB device has been disconnected. The `disconnect` callback would return, the USB device gets freed, and then the workqueue executes `i2c_del_adapter`. This is exactly how syzkaller likely triggers the bug. The `device_del` is the issue, `i2c_del_adapter` calls `device_unregister(&adap->dev)`. The critical thing is the `adap->dev` memberâthis holds a reference to the USB interface structure. But `device_del` on the USB interface has already happened in this asynchronous case! The device that contains `parent` (USB interface) is dropped, so when `i2c_adapter_depth` tries to trace its way up the device treeâ`adap->dev.parent` to the USB interface to USB device to root hubâitâs hitting freed memory. `i2c_adapter_depth` is traversing the device hierarchy without holding proper references, and it is using dangling pointers.
This vulnerability, I've concluded, originates from commit `53801d2e762a`. The problem is that `i2c_adapter_depth` goes up the device tree without holding any locks or references, making it susceptible to dangling pointers if a parent device is removed asynchronously. This can be triggered by any I2C adapter whose parent is removed *and* is kept alive via an open file descriptor or by asynchronous unregistration. This is a fundamental flaw in the logic!
I verified the issue: When `device_del` is called on a device, it *drops* the reference to `dev->parent` but *doesn't* clear the `dev->parent` pointer. Thus, if the `dev` is kept alive (e.g., by a child), `dev->parent` becomes a dangling pointer. This means `i2c_adapter_depth` will access these dangling pointers and crash.
So the conditions are specific: This requires the USB interface to have gone through `device_del` (which drops the reference to its parent, the USB device), but an I2C adapter using the interface to keep a reference to it. And this leads to a textbook use-after-free, where the USB device and root hub are freed, and then subsequently accessed through the I2C adapterâs parent pointers.
I also considered other possibilities. For instance, what if `i2c_del_adapter` *does* block in `wait_for_completion`? In that case, the USB interface *isn't* freed.
|
| 95/2 |
2026/07/28 01:39 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 96/2 |
2026/07/28 01:39 |
llm |
expert |
0m
Model:
Tokens:
input: 99611
output: 737
thoughts: 428
Thoughts: **Final Reasoning on the I2C Adapter UAF Bug**
Alright, let's break this down. The results are in, and now I need to solidify the final reasoning. This Use-After-Free in `i2c_adapter_depth()` is a nasty one. We're talking about traversing `parent` pointers of an `i2c_adapter` without proper reference counting. Basically, the driver for a device (say, a USB device) unregisters the I2C adapter when the device is disconnected, as it should. The problem arises when the driver is faulty. Either it leaks the I2C adapter, or it unregisters it asynchronously, perhaps in a workqueue. This means that the parent devicesâthe USB interface, the USB device itself, and maybe even the root hubâcan get freed *while* the I2C adapter is still technically registered and, for example, is kept alive due to an open file descriptor.
Now, an unprivileged user, if the driver exposes a device node like `/dev/videoX`, or even a privileged user, can trigger this via an I2C ioctl call, such as `I2C_RDWR`. This calls `i2c_adapter_depth()`, which needs to figure out the lockdep subclass and starts walking up `adapter->dev.parent`. However, since those parent devices are now freed, we are accessing dangling pointers. Bam, we've got a Use-After-Free vulnerability.
And this UAF? It's highly exploitable. We can spray the heap with data, maybe through something like `sk_buff` data, and effectively control the contents of those freed parent devices, like the `usb_device` struct. Once the I2C transfer moves on to `master_xfer`, it'll start using this attacker-controlled `usb_device` to submit URBs. This is where it gets interesting. The USB core will dereference `dev->bus` to find the Host Controller Driver, then calls `hcd->driver->urb_enqueue`. By controlling these pointers, we can hijack the control flow, and achieve arbitrary code execution within the kernel.
The scary part is that it's a Peripheral Trigger. A malicious USB device binding to the buggy driver could be enough to trigger this. At the very least, if the dangling pointers point to unmapped memory, we're looking at a Denial of Service, likely a kernel panic. I've got a lot of work to do.
|
| 97/1 |
2026/07/28 01:39 |
action |
format-explanation |
0m
Results: map[Explanation:**Analysis of the Bug**
**Root Cause:**
The bug is a Use-After-Free (UAF) in `i2c_adapter_depth()` located in
`drivers/i2c/i2c-core-base.c`. This function is used to calculate the lockdep
subclass for the I2C adapter's bus lock by traversing the device tree upwards
via `parent = parent->parent`.
However, it traverses these pointers without holding any references to the
parent devices.
When a physical device (e.g., a USB device) is disconnected, the kernel
unregisters and frees the device structures (USB interface, USB device, root
hub). If a buggy driver fails to unregister its I2C adapter synchronously during
the `disconnect` callback (either by leaking it or unregistering it
asynchronously in a workqueue), the I2C adapter remains alive. Its `dev.parent`
pointer, however, becomes a dangling pointer pointing to the freed parent
device.
If a user holds an open file descriptor to the I2C adapter (or a related device
node exposed by the driver) and issues an I2C transfer (e.g., via
`ioctl(I2C_RDWR)`), `i2c_adapter_depth()` is called. It will traverse the
dangling `parent` pointers, accessing the freed memory of the USB interface, USB
device, and root hub.
**Exploitability:**
**Yes.** This UAF is highly exploitable for privilege escalation.
An attacker can use heap spraying techniques (e.g., via `sk_buff` allocations,
as seen in the syzkaller report) to overwrite the freed `usb_device` structure
with controlled data.
1. The attacker can craft the fake `parent->parent` pointers to safely bypass
the loop in `i2c_adapter_depth()` without crashing.
2. The execution then proceeds to the actual I2C transfer (`master_xfer`), which
will use the attacker-controlled `usb_device` to send USB control messages.
3. The USB core will dereference the fake `dev->bus` to find the Host Controller
Driver (`hcd`), and subsequently call the function pointer at
`hcd->driver->urb_enqueue`.
By controlling these structures, an attacker can hijack the kernel's control
flow and achieve arbitrary code execution (ACE).
**Denial of Service:**
**Yes.** If the attacker does not perfectly control the sprayed memory, the
dangling `parent->parent` pointer will likely point to unmapped or invalid
memory. Dereferencing it in `i2c_adapter_depth()` will cause a page fault and a
kernel panic, leading to a Denial of Service.
**Trigger Mechanisms:**
* **Peripheral Trigger:** **Yes.** The vulnerability requires a buggy USB driver
to bind to a device. An attacker can trigger this by physically plugging in a
malicious USB device (or a specific supported device), waiting for it to bind,
and then unplugging it to force the buggy disconnect path.
* **Unprivileged / User Namespace:** **Yes.** While `/dev/i2c-X` is typically
restricted to root or the `i2c` group, many USB drivers (such as webcams or TV
tuners) expose secondary device nodes (like `/dev/videoX`) that are accessible
to unprivileged users. If the driver translates unprivileged ioctls into I2C
transfers, an unprivileged user can trigger the UAF after the device is
unplugged.]
|