| 0/0 |
2026/07/18 00:01 |
flow |
assessment-security |
6m
Results: map[DenialOfService:true Explanation:Based on the analysis of the provided bug report and kernel source code, here is
the assessment of the vulnerability:
### Lock Dependency Chain
Lockdep has detected a complex circular locking dependency that can lead to a
deadlock. The chain is formed by the following sequence of lock acquisitions:
1. **`fs_reclaim` -> `q->q_usage_counter(io)`**: Explicitly registered in
`blk_alloc_queue()` to teach lockdep that memory reclaim can wait for a block
queue to unfreeze.
2. **`q->q_usage_counter(io)` -> `q->elevator_lock`**: In `elevator_change()`,
the block queue is frozen (which acquires the lockdep map for
`q->q_usage_counter(io)` for write) before acquiring `q->elevator_lock`.
3. **`q->elevator_lock` -> `set->srcu`**: In `elevator_switch()`,
`q->elevator_lock` is held while calling `blk_mq_quiesce_queue()`, which waits
for SRCU readers via `synchronize_srcu(set->srcu)`.
4. **`set->srcu` -> `cmd->lock`**: During IO dispatch, `blk_mq_run_work_fn()`
holds `srcu_read_lock(set->srcu)` and calls `nbd_queue_rq()`, which acquires
`cmd->lock`.
5. **`cmd->lock` -> `nsock->tx_lock`**: `nbd_queue_rq()` holds `cmd->lock` and
calls `nbd_handle_cmd()`, which acquires `nsock->tx_lock`.
6. **`nsock->tx_lock` -> `cpu_hotplug_lock`**: `nbd_reconnect_socket()` holds
`nsock->tx_lock` and calls `sk_set_memalloc()`, which increments a static key
and acquires `cpus_read_lock()`.
7. **`cpu_hotplug_lock` -> `wq_pool_mutex`**: `padata_alloc()` holds
`cpus_read_lock()` and calls `alloc_workqueue()`, which acquires
`wq_pool_mutex`. (Additionally, CPU hotplug callbacks like
`workqueue_online_cpu()` hold `cpus_write_lock()` and acquire `wq_pool_mutex`).
8. **`wq_pool_mutex` -> `fs_reclaim`**: `__alloc_workqueue()` holds
`wq_pool_mutex` and allocates memory with `GFP_KERNEL`, which can enter the
memory reclaim path (`fs_reclaim`).
### Security Impact
- **Denial of Service (DoS):** Yes. If the deadlock occurs, it will hang the
involved tasks and potentially the entire system (especially if a CPU hotplug
operation hangs, which blocks many other subsystems).
- **Exploitable:** No. This is a pure locking issue (deadlock) and does not lead
to memory corruption, use-after-free, or information leaks.
### Accessibility
- **Accessible From Unprivileged Processes:** No. Triggering the NBD and RDMA
netlink paths requires `CAP_SYS_ADMIN` and `CAP_NET_ADMIN` respectively.
- **Accessible From User Namespaces:** Yes. The netlink handlers for NBD
(`nbd_genl_connect`, `nbd_genl_reconfigure`) and RDMA (`nldev_newlink`) use
`netlink_capable()`, which checks for capabilities in the user namespace that
owns the network namespace of the socket. An unprivileged user can create a new
user namespace and network namespace to gain these capabilities and trigger the
vulnerable code paths.
### Exploitability Caveat
While an unprivileged user in a user namespace can set up most of the locks in
this chain (by configuring NBD devices and creating RDMA links), the cycle
relies on `cpu_hotplug_lock`. Since both `nbd_reconnect_socket()` and
`padata_alloc()` acquire this lock for *read*, they do not block each other. For
the deadlock to actually manifest, a concurrent *writer* to `cpu_hotplug_lock`
(i.e., a CPU hotplug operation like offlining a CPU) must occur at the exact
right time to block the readers. Because unprivileged users cannot trigger CPU
hotplug operations, they cannot reliably force the deadlock to happen on demand.
However, it remains a valid DoS vector if the system administrator or automated
power-management scripts perform CPU hotplugging while the user is holding the
other locks. Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/07/18 00:01 |
action |
syz-repro-to-c-repro |
0m
Results:
|
| 2/1 |
2026/07/18 00:01 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/c29781298d2bd891141914c908cacce3f6b834ea]
|
| 3/1 |
2026/07/18 00:01 |
action |
kernel-builder |
0m
Results: map[KernelObj:/app/workdir/cache/build/40f3db31fe643191bb91b7a80d77ed4608477ed5]
|
| 4/1 |
2026/07/18 00:01 |
action |
codesearch-prepare |
0m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/07/18 00:02 |
agent |
expert |
6m
Model:
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false 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:
wlan1 speed is unknown, defaulting to 1000
wlan1 speed is unknown, defaulting to 1000
wlan1 speed is unknown, defaulting to 1000
infiniband syz1: set down
infiniband syz1: added wlan1
======================================================
WARNING: possible circular locking dependency detected
syzkaller #0 Not tainted
------------------------------------------------------
syz.1.835/9438 is trying to acquire lock:
ffffffff8ec848a0 (fs_reclaim){+.+.}-{0:0}, at: might_alloc include/linux/sched/mm.h:317 [inline]
ffffffff8ec848a0 (fs_reclaim){+.+.}-{0:0}, at: slab_pre_alloc_hook mm/slub.c:4565 [inline]
ffffffff8ec848a0 (fs_reclaim){+.+.}-{0:0}, at: slab_alloc_node mm/slub.c:4925 [inline]
ffffffff8ec848a0 (fs_reclaim){+.+.}-{0:0}, at: __do_kmalloc_node mm/slub.c:5361 [inline]
ffffffff8ec848a0 (fs_reclaim){+.+.}-{0:0}, at: __kmalloc_noprof+0xbc/0x720 mm/slub.c:5387
but task is already holding lock:
ffffffff8e9f6d00 (wq_pool_mutex){+.+.}-{4:4}, at: __alloc_workqueue+0xa9c/0x2060 kernel/workqueue.c:5895
which lock already depends on the new lock.
the existing dependency chain (in reverse order) is:
-> #7 (wq_pool_mutex){+.+.}-{4:4}:
__mutex_lock_common kernel/locking/mutex.c:646 [inline]
__mutex_lock+0x19d/0x1550 kernel/locking/mutex.c:821
__alloc_workqueue+0xa9c/0x2060 kernel/workqueue.c:5895
alloc_workqueue_va kernel/workqueue.c:5946 [inline]
alloc_workqueue_noprof+0xe3/0x210 kernel/workqueue.c:5962
padata_alloc+0xbe/0x360 kernel/padata.c:964
pcrypt_init_padata+0x27/0x100 crypto/pcrypt.c:335
pcrypt_init+0x60/0xc0 crypto/pcrypt.c:360
do_one_initcall+0x250/0x870 init/main.c:1347
do_initcall_level+0x10a/0x1a0 init/main.c:1409
do_initcalls+0x59/0xa0 init/main.c:1425
kernel_init_freeable+0x29d/0x3e0 init/main.c:1658
kernel_init+0x1d/0x1d0 init/main.c:1548
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
-> #6 (cpu_hotplug_lock){++++}-{0:0}:
percpu_down_read_internal include/linux/percpu-rwsem.h:53 [inline]
percpu_down_read include/linux/percpu-rwsem.h:77 [inline]
cpus_read_lock+0x42/0x160 kernel/cpu.c:490
static_key_slow_inc+0x12/0x30 kernel/jump_label.c:190
nbd_reconnect_socket drivers/block/nbd.c:1379 [inline]
nbd_genl_reconfigure+0x1301/0x1e80 drivers/block/nbd.c:2468
genl_family_rcv_msg_doit+0x233/0x340 net/netlink/genetlink.c:1114
genl_family_rcv_msg net/netlink/genetlink.c:1194 [inline]
genl_rcv_msg+0x614/0x7a0 net/netlink/genetlink.c:1209
netlink_rcv_skb+0x226/0x4a0 net/netlink/af_netlink.c:2556
genl_rcv+0x28/0x40 net/netlink/genetlink.c:1218
netlink_unicast_kernel net/netlink/af_netlink.c:1319 [inline]
netlink_unicast+0x7bb/0x940 net/netlink/af_netlink.c:1345
netlink_sendmsg+0x813/0xb40 net/netlink/af_netlink.c:1900
sock_sendmsg_nosec+0x13a/0x180 net/socket.c:775
__sock_sendmsg net/socket.c:790 [inline]
____sys_sendmsg+0x54e/0x850 net/socket.c:2684
___sys_sendmsg+0x2a5/0x360 net/socket.c:2738
__sys_sendmsg net/socket.c:2770 [inline]
__do_sys_sendmsg net/socket.c:2775 [inline]
__se_sys_sendmsg net/socket.c:2773 [inline]
__x64_sys_sendmsg+0x1b1/0x290 net/socket.c:2773
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
-> #5 (&nsock->tx_lock){+.+.}-{4:4}:
__mutex_lock_common kernel/locking/mutex.c:646 [inline]
__mutex_lock+0x19d/0x1550 kernel/locking/mutex.c:821
nbd_handle_cmd drivers/block/nbd.c:1143 [inline]
nbd_queue_rq+0x373/0x1150 drivers/block/nbd.c:1207
blk_mq_dispatch_rq_list+0x499/0x1990 block/blk-mq.c:2117
__blk_mq_do_dispatch_sched block/blk-mq-sched.c:168 [inline]
blk_mq_do_dispatch_sched block/blk-mq-sched.c:182 [inline]
__blk_mq_sched_dispatch_requests+0xd36/0x1580 block/blk-mq-sched.c:307
blk_mq_sched_dispatch_requests+0xd7/0x190 block/blk-mq-sched.c:329
blk_mq_run_work_fn+0x16c/0x300 block/blk-mq.c:2532
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
-> #4 (&cmd->lock){+.+.}-{4:4}:
__mutex_lock_common kernel/locking/mutex.c:646 [inline]
__mutex_lock+0x19d/0x1550 kernel/locking/mutex.c:821
nbd_queue_rq+0xc1/0x1150 drivers/block/nbd.c:1199
blk_mq_dispatch_rq_list+0x499/0x1990 block/blk-mq.c:2117
__blk_mq_do_dispatch_sched block/blk-mq-sched.c:168 [inline]
blk_mq_do_dispatch_sched block/blk-mq-sched.c:182 [inline]
__blk_mq_sched_dispatch_requests+0xd36/0x1580 block/blk-mq-sched.c:307
blk_mq_sched_dispatch_requests+0xd7/0x190 block/blk-mq-sched.c:329
blk_mq_run_work_fn+0x16c/0x300 block/blk-mq.c:2532
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
-> #3 (set->srcu){.+.+}-{0:0}:
srcu_lock_sync include/linux/srcu.h:199 [inline]
__synchronize_srcu+0xc9/0x2f0 kernel/rcu/srcutree.c:1481
elevator_switch+0x1e8/0x7b0 block/elevator.c:576
elevator_change+0x2fa/0x480 block/elevator.c:681
elevator_set_default+0x375/0x440 block/elevator.c:754
blk_register_queue+0x3f3/0x4e0 block/blk-sysfs.c:992
__add_disk+0x6cb/0xe30 block/genhd.c:528
add_disk_fwnode+0xfb/0x4b0 block/genhd.c:597
add_disk include/linux/blkdev.h:800 [inline]
nbd_dev_add+0x733/0xb60 drivers/block/nbd.c:2021
nbd_init+0x15f/0x1e0 drivers/block/nbd.c:2729
do_one_initcall+0x250/0x870 init/main.c:1347
do_initcall_level+0x10a/0x1a0 init/main.c:1409
do_initcalls+0x59/0xa0 init/main.c:1425
kernel_init_freeable+0x29d/0x3e0 init/main.c:1658
kernel_init+0x1d/0x1d0 init/main.c:1548
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
-> #2 (&q->elevator_lock){+.+.}-{4:4}:
__mutex_lock_common kernel/locking/mutex.c:646 [inline]
__mutex_lock+0x19d/0x1550 kernel/locking/mutex.c:821
elevator_change+0x1af/0x480 block/elevator.c:679
elevator_set_none+0xb5/0x140 block/elevator.c:769
blk_mq_elv_switch_none block/blk-mq.c:5101 [inline]
__blk_mq_update_nr_hw_queues block/blk-mq.c:5146 [inline]
blk_mq_update_nr_hw_queues+0x5ef/0x19f0 block/blk-mq.c:5211
nbd_start_device+0x189/0xb30 drivers/block/nbd.c:1526
nbd_genl_connect+0x1597/0x1c10 drivers/block/nbd.c:2276
genl_family_rcv_msg_doit+0x233/0x340 net/netlink/genetlink.c:1114
genl_family_rcv_msg net/netlink/genetlink.c:1194 [inline]
genl_rcv_msg+0x614/0x7a0 net/netlink/genetlink.c:1209
netlink_rcv_skb+0x226/0x4a0 net/netlink/af_netlink.c:2556
genl_rcv+0x28/0x40 net/netlink/genetlink.c:1218
netlink_unicast_kernel net/netlink/af_netlink.c:1319 [inline]
netlink_unicast+0x7bb/0x940 net/netlink/af_netlink.c:1345
netlink_sendmsg+0x813/0xb40 net/netlink/af_netlink.c:1900
sock_sendmsg_nosec+0x13a/0x180 net/socket.c:775
__sock_sendmsg net/socket.c:790 [inline]
____sys_sendmsg+0x54e/0x850 net/socket.c:2684
___sys_sendmsg+0x2a5/0x360 net/socket.c:2738
__sys_sendmsg net/socket.c:2770 [inline]
__do_sys_sendmsg net/socket.c:2775 [inline]
__se_sys_sendmsg net/socket.c:2773 [inline]
__x64_sys_sendmsg+0x1b1/0x290 net/socket.c:2773
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
-> #1 (&q->q_usage_counter(io)#50){++++}-{0:0}:
blk_alloc_queue+0x544/0x690 block/blk-core.c:504
blk_mq_alloc_queue block/blk-mq.c:4420 [inline]
__blk_mq_alloc_disk+0x194/0x390 block/blk-mq.c:4467
nbd_dev_add+0x494/0xb60 drivers/block/nbd.c:1991
nbd_init+0x15f/0x1e0 drivers/block/nbd.c:2729
do_one_initcall+0x250/0x870 init/main.c:1347
do_initcall_level+0x10a/0x1a0 init/main.c:1409
do_initcalls+0x59/0xa0 init/main.c:1425
kernel_init_freeable+0x29d/0x3e0 init/main.c:1658
kernel_init+0x1d/0x1d0 init/main.c:1548
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
-> #0 (fs_reclaim){+.+.}-{0:0}:
check_prev_add kernel/locking/lockdep.c:3165 [inline]
check_prevs_add kernel/locking/lockdep.c:3284 [inline]
validate_chain kernel/locking/lockdep.c:3908 [inline]
__lock_acquire+0x1520/0x2cf0 kernel/locking/lockdep.c:5237
lock_acquire+0x106/0x350 kernel/locking/lockdep.c:5868
__fs_reclaim_acquire mm/page_alloc.c:4329 [inline]
fs_reclaim_acquire+0x71/0x100 mm/page_alloc.c:4343
might_alloc include/linux/sched/mm.h:317 [inline]
slab_pre_alloc_hook mm/slub.c:4565 [inline]
slab_alloc_node mm/slub.c:4925 [inline]
__do_kmalloc_node mm/slub.c:5361 [inline]
__kmalloc_noprof+0xbc/0x720 mm/slub.c:5387
_kmalloc_noprof include/linux/slab.h:973 [inline]
_kzalloc_noprof include/linux/slab.h:1290 [inline]
apply_wqattrs_prepare+0xee/0xdc0 kernel/workqueue.c:5410
apply_workqueue_attrs_locked kernel/workqueue.c:5495 [inline]
alloc_and_link_pwqs kernel/workqueue.c:5645 [inline]
__alloc_workqueue+0x117c/0x2060 kernel/workqueue.c:5897
alloc_workqueue_va kernel/workqueue.c:5946 [inline]
alloc_workqueue_noprof+0xe3/0x210 kernel/workqueue.c:5962
ib_mad_port_open drivers/infiniband/core/mad.c:3252 [inline]
ib_mad_init_device+0x993/0x2150 drivers/infiniband/core/mad.c:3339
add_client_context+0x37c/0x7b0 drivers/infiniband/core/device.c:732
enable_device_and_get+0x19c/0x3e0 drivers/infiniband/core/device.c:1341
ib_register_device+0x10af/0x1380 drivers/infiniband/core/device.c:1468
rxe_register_device+0x1e3/0x350 drivers/infiniband/sw/rxe/rxe_verbs.c:1543
rxe_net_add+0x81/0x110 drivers/infiniband/sw/rxe/rxe_net.c:625
rxe_newlink+0xf4/0x1c0 drivers/infiniband/sw/rxe/rxe.c:243
nldev_newlink+0x5bc/0x650 drivers/infiniband/core/nldev.c:1816
rdma_nl_rcv_msg drivers/infiniband/core/netlink.c:-1 [inline]
rdma_nl_rcv_skb drivers/infiniband/core/netlink.c:239 [inline]
rdma_nl_rcv+0x6ef/0xa40 drivers/infiniband/core/netlink.c:259
netlink_unicast_kernel net/netlink/af_netlink.c:1319 [inline]
netlink_unicast+0x7bb/0x940 net/netlink/af_netlink.c:1345
netlink_sendmsg+0x813/0xb40 net/netlink/af_netlink.c:1900
sock_sendmsg_nosec+0x13a/0x180 net/socket.c:775
__sock_sendmsg net/socket.c:790 [inline]
____sys_sendmsg+0x54e/0x850 net/socket.c:2684
___sys_sendmsg+0x2a5/0x360 net/socket.c:2738
__sys_sendmsg net/socket.c:2770 [inline]
__do_sys_sendmsg net/socket.c:2775 [inline]
__se_sys_sendmsg net/socket.c:2773 [inline]
__x64_sys_sendmsg+0x1b1/0x290 net/socket.c:2773
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
other info that might help us debug this:
Chain exists of:
fs_reclaim --> cpu_hotplug_lock --> wq_pool_mutex
Possible unsafe locking scenario:
CPU0 CPU1
---- ----
lock(wq_pool_mutex);
lock(cpu_hotplug_lock);
lock(wq_pool_mutex);
lock(fs_reclaim);
*** DEADLOCK ***
6 locks held by syz.1.835/9438:
#0: ffffffff9ab1b1a8 (&rdma_nl_types[idx].sem){.+.+}-{4:4}, at: rdma_nl_rcv_msg drivers/infiniband/core/netlink.c:164 [inline]
#0: ffffffff9ab1b1a8 (&rdma_nl_types[idx].sem){.+.+}-{4:4}, at: rdma_nl_rcv_skb drivers/infiniband/core/netlink.c:239 [inline]
#0: ffffffff9ab1b1a8 (&rdma_nl_types[idx].sem){.+.+}-{4:4}, at: rdma_nl_rcv+0x33d/0xa40 drivers/infiniband/core/netlink.c:259
#1: ffffffff8fd8ae28 (link_ops_rwsem){++++}-{4:4}, at: nldev_newlink+0x429/0x650 drivers/infiniband/core/nldev.c:1806
#2: ffffffff8fd7c308 (devices_rwsem){++++}-{4:4}, at: enable_device_and_get+0xff/0x3e0 drivers/infiniband/core/device.c:1331
#3: ffffffff8fd7c608 (clients_rwsem){++++}-{4:4}, at: enable_device_and_get+0x165/0x3e0 drivers/infiniband/core/device.c:1339
#4: ffff888020ea0620 (&device->client_data_rwsem){++++}-{4:4}, at: add_client_context+0x33e/0x7b0 drivers/infiniband/core/device.c:730
#5: ffffffff8e9f6d00 (wq_pool_mutex){+.+.}-{4:4}, at: __alloc_workqueue+0xa9c/0x2060 kernel/workqueue.c:5895
stack backtrace:
CPU: 1 UID: 0 PID: 9438 Comm: syz.1.835 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 06/25/2026
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_circular_bug+0x2e1/0x300 kernel/locking/lockdep.c:2043
check_noncircular+0x12e/0x150 kernel/locking/lockdep.c:2175
check_prev_add kernel/locking/lockdep.c:3165 [inline]
check_prevs_add kernel/locking/lockdep.c:3284 [inline]
validate_chain kernel/locking/lockdep.c:3908 [inline]
__lock_acquire+0x1520/0x2cf0 kernel/locking/lockdep.c:5237
lock_acquire+0x106/0x350 kernel/locking/lockdep.c:5868
__fs_reclaim_acquire mm/page_alloc.c:4329 [inline]
fs_reclaim_acquire+0x71/0x100 mm/page_alloc.c:4343
might_alloc include/linux/sched/mm.h:317 [inline]
slab_pre_alloc_hook mm/slub.c:4565 [inline]
slab_alloc_node mm/slub.c:4925 [inline]
__do_kmalloc_node mm/slub.c:5361 [inline]
__kmalloc_noprof+0xbc/0x720 mm/slub.c:5387
_kmalloc_noprof include/linux/slab.h:973 [inline]
_kzalloc_noprof include/linux/slab.h:1290 [inline]
apply_wqattrs_prepare+0xee/0xdc0 kernel/workqueue.c:5410
apply_workqueue_attrs_locked kernel/workqueue.c:5495 [inline]
alloc_and_link_pwqs kernel/workqueue.c:5645 [inline]
__alloc_workqueue+0x117c/0x2060 kernel/workqueue.c:5897
alloc_workqueue_va kernel/workqueue.c:5946 [inline]
alloc_workqueue_noprof+0xe3/0x210 kernel/workqueue.c:5962
ib_mad_port_open drivers/infiniband/core/mad.c:3252 [inline]
ib_mad_init_device+0x993/0x2150 drivers/infiniband/core/mad.c:3339
add_client_context+0x37c/0x7b0 drivers/infiniband/core/device.c:732
enable_device_and_get+0x19c/0x3e0 drivers/infiniband/core/device.c:1341
ib_register_device+0x10af/0x1380 drivers/infiniband/core/device.c:1468
rxe_register_device+0x1e3/0x350 drivers/infiniband/sw/rxe/rxe_verbs.c:1543
rxe_net_add+0x81/0x110 drivers/infiniband/sw/rxe/rxe_net.c:625
rxe_newlink+0xf4/0x1c0 drivers/infiniband/sw/rxe/rxe.c:243
nldev_newlink+0x5bc/0x650 drivers/infiniband/core/nldev.c:1816
rdma_nl_rcv_msg drivers/infiniband/core/netlink.c:-1 [inline]
rdma_nl_rcv_skb drivers/infiniband/core/netlink.c:239 [inline]
rdma_nl_rcv+0x6ef/0xa40 drivers/infiniband/core/netlink.c:259
netlink_unicast_kernel net/netlink/af_netlink.c:1319 [inline]
netlink_unicast+0x7bb/0x940 net/netlink/af_netlink.c:1345
netlink_sendmsg+0x813/0xb40 net/netlink/af_netlink.c:1900
sock_sendmsg_nosec+0x13a/0x180 net/socket.c:775
__sock_sendmsg net/socket.c:790 [inline]
____sys_sendmsg+0x54e/0x850 net/socket.c:2684
___sys_sendmsg+0x2a5/0x360 net/socket.c:2738
__sys_sendmsg net/socket.c:2770 [inline]
__do_sys_sendmsg net/socket.c:2775 [inline]
__se_sys_sendmsg net/socket.c:2773 [inline]
__x64_sys_sendmsg+0x1b1/0x290 net/socket.c:2773
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:0x7f8013f9de59
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:00007f8014d82028 EFLAGS: 00000246 ORIG_RAX: 000000000000002e
RAX: ffffffffffffffda RBX: 00007f8014225fa0 RCX: 00007f8013f9de59
RDX: 0000000004000000 RSI: 00002000000002c0 RDI: 000000000000000d
RBP: 00007f8014033e6f R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f8014226038 R14: 00007f8014225fa0 R15: 00007ffc03c3f938
</TASK>
smbdirect: ib_dev[syz1]: added: IB_CA max_fast_reg_page_list_len=512 device_cap_flags=0x1c001223c76 kernel_cap_flags=0x14 page_size_cap=0xfffff000
smbdirect: ib_dev[syz1]: num_ports=1 max_qp_rd_atom=128 max_qp_init_rd_atom=128 max_sgl_rd=0 max_sge_rd=32 max_cqe=32767 max_qp_wr=1048576 max_send_sge=32 max_recv_sge=32
smbdirect: ib_dev[syz1]PORT[1]: iwarp=0 ib=0 roce=1 v1=0 v2=1 core_cap_flags=0x803005
RDS/IB: syz1: added
smc: adding ib device syz1 with port count 1
smc: ib device syz1 port 1 has no pnetid
wlan1 speed is unknown, defaulting to 1000
wlan1 speed is unknown, defaulting to 1000
wlan1 speed is unknown, defaulting to 1000
wlan1 speed is unknown, defaulting to 1000
wlan1 speed is unknown, defaulting to 1000
Reply: Based on the analysis of the provided bug report and kernel source code, here is the assessment of the vulnerability:
### Lock Dependency Chain
Lockdep has detected a complex circular locking dependency that can lead to a deadlock. The chain is formed by the following sequence of lock acquisitions:
1. **`fs_reclaim` -> `q->q_usage_counter(io)`**: Explicitly registered in `blk_alloc_queue()` to teach lockdep that memory reclaim can wait for a block queue to unfreeze.
2. **`q->q_usage_counter(io)` -> `q->elevator_lock`**: In `elevator_change()`, the block queue is frozen (which acquires the lockdep map for `q->q_usage_counter(io)` for write) before acquiring `q->elevator_lock`.
3. **`q->elevator_lock` -> `set->srcu`**: In `elevator_switch()`, `q->elevator_lock` is held while calling `blk_mq_quiesce_queue()`, which waits for SRCU readers via `synchronize_srcu(set->srcu)`.
4. **`set->srcu` -> `cmd->lock`**: During IO dispatch, `blk_mq_run_work_fn()` holds `srcu_read_lock(set->srcu)` and calls `nbd_queue_rq()`, which acquires `cmd->lock`.
5. **`cmd->lock` -> `nsock->tx_lock`**: `nbd_queue_rq()` holds `cmd->lock` and calls `nbd_handle_cmd()`, which acquires `nsock->tx_lock`.
6. **`nsock->tx_lock` -> `cpu_hotplug_lock`**: `nbd_reconnect_socket()` holds `nsock->tx_lock` and calls `sk_set_memalloc()`, which increments a static key and acquires `cpus_read_lock()`.
7. **`cpu_hotplug_lock` -> `wq_pool_mutex`**: `padata_alloc()` holds `cpus_read_lock()` and calls `alloc_workqueue()`, which acquires `wq_pool_mutex`. (Additionally, CPU hotplug callbacks like `workqueue_online_cpu()` hold `cpus_write_lock()` and acquire `wq_pool_mutex`).
8. **`wq_pool_mutex` -> `fs_reclaim`**: `__alloc_workqueue()` holds `wq_pool_mutex` and allocates memory with `GFP_KERNEL`, which can enter the memory reclaim path (`fs_reclaim`).
### Security Impact
- **Denial of Service (DoS):** Yes. If the deadlock occurs, it will hang the involved tasks and potentially the entire system (especially if a CPU hotplug operation hangs, which blocks many other subsystems).
- **Exploitable:** No. This is a pure locking issue (deadlock) and does not lead to memory corruption, use-after-free, or information leaks.
### Accessibility
- **Accessible From Unprivileged Processes:** No. Triggering the NBD and RDMA netlink paths requires `CAP_SYS_ADMIN` and `CAP_NET_ADMIN` respectively.
- **Accessible From User Namespaces:** Yes. The netlink handlers for NBD (`nbd_genl_connect`, `nbd_genl_reconfigure`) and RDMA (`nldev_newlink`) use `netlink_capable()`, which checks for capabilities in the user namespace that owns the network namespace of the socket. An unprivileged user can create a new user namespace and network namespace to gain these capabilities and trigger the vulnerable code paths.
### Exploitability Caveat
While an unprivileged user in a user namespace can set up most of the locks in this chain (by configuring NBD devices and creating RDMA links), the cycle relies on `cpu_hotplug_lock`. Since both `nbd_reconnect_socket()` and `padata_alloc()` acquire this lock for *read*, they do not block each other. For the deadlock to actually manifest, a concurrent *writer* to `cpu_hotplug_lock` (i.e., a CPU hotplug operation like offlining a CPU) must occur at the exact right time to block the readers. Because unprivileged users cannot trigger CPU hotplug operations, they cannot reliably force the deadlock to happen on demand. However, it remains a valid DoS vector if the system administrator or automated power-management scripts perform CPU hotplugging while the user is holding the other locks.
|
| 6/2 |
2026/07/18 00:02 |
llm |
expert |
0m
Model:
Tokens:
input: 14443
output: 35
thoughts: 1215
Thoughts: **Analyzing a Potential Circular Lock Dependency**
Okay, I've got a lockdep warning, which always makes me pay attention. It looks like we have a possible circular dependency. The system has detected a lock dependency chain: `fs_reclaim -> cpu_hotplug_lock -> wq_pool_mutex`. I need to figure out the context. The crucial point is that a task is trying to acquire `fs_reclaim` *while* holding `wq_pool_mutex`. Let me make sure I'm following the chain: the task *currently holds* `wq_pool_mutex` and is trying to acquire `fs_reclaim`.
Now, looking at the existing dependency chain, I see the following:
* The system has listed a chain of dependencies. It's using `wq_pool_mutex` and attempting to grab `fs_reclaim`
Based on what I can gather from the existing dependency, the `cpu_hotplug_lock` is in the dependency chain, and I know it's being held in a related task. That might be relevant.
Wait, let's make sure I'm still clear on this. The chain is: `fs_reclaim -> cpu_hotplug_lock -> wq_pool_mutex`. The task is currently holding `wq_pool_mutex` and is trying to acquire `fs_reclaim`. This definitely *smells* like a potential deadlock.
The lockdep report highlights the issue with a clear scenario:
* CPU0 holds `wq_pool_mutex` and *then* tries to get `fs_reclaim`.
* CPU1 holds `cpu_hotplug_lock` and attempts to acquire `wq_pool_mutex`.
And then, **BAM** - a ***DEADLOCK***.
Looking at the current locks held by this task:
1. `rdma_nl_types[idx].sem`
2. `link_ops_rwsem`
3. `devices_rwsem`
4. `clients_rwsem`
5. `device->client_data_rwsem`
6. `wq_pool_mutex`
And it's trying to acquire `fs_reclaim` because it's doing memory allocation with `GFP_KERNEL` (or something similar) *while* holding `wq_pool_mutex`. That is concerning because it's saying it's trying to allocate memory while holding the pool mutex.
Okay, this requires a deeper dive. The crucial piece is `__alloc_workqueue`. I know this function is involved in workqueue creation, which is often a source of contention. I need to confirm if it allocates memory with `GFP_KERNEL` *while* holding `wq_pool_mutex`, because that is the issue. I'll need to check the code in `kernel/workqueue.c` to understand the exact allocation behavior.
|
| 7/2 |
2026/07/18 00:02 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/workqueue.c Name:__alloc_workqueue]
Results: map[SourceCode:5801: __printf(1, 0)
5802: static struct workqueue_struct *__alloc_workqueue(const char *fmt,
5803: unsigned int flags,
5804: int max_active, va_list args)
5805: {
5806: struct workqueue_struct *wq;
5807: size_t wq_size;
5808: int name_len;
5809:
5810: if (flags & WQ_BH) {
5811: if (WARN_ON_ONCE(flags & ~__WQ_BH_ALLOWS))
5812: return NULL;
5813: if (WARN_ON_ONCE(max_active))
5814: return NULL;
5815: }
5816:
5817: /* see the comment above the definition of WQ_POWER_EFFICIENT */
5818: if ((flags & WQ_POWER_EFFICIENT) && wq_power_efficient)
5819: flags = (flags & ~WQ_PERCPU) | WQ_UNBOUND;
5820:
5821: /* allocate wq and format name */
5822: if (flags & WQ_UNBOUND)
5823: wq_size = struct_size(wq, node_nr_active, nr_node_ids + 1);
5824: else
5825: wq_size = sizeof(*wq);
5826:
5827: wq = kzalloc_noprof(wq_size, GFP_KERNEL);
5828: if (!wq)
5829: return NULL;
5830:
5831: if (flags & WQ_UNBOUND) {
5832: wq->unbound_attrs = alloc_workqueue_attrs_noprof();
5833: if (!wq->unbound_attrs)
5834: goto err_free_wq;
5835: }
5836:
5837: name_len = vsnprintf(wq->name, sizeof(wq->name), fmt, args);
5838:
5839: if (name_len >= WQ_NAME_LEN)
5840: pr_warn_once("workqueue: name exceeds WQ_NAME_LEN. Truncating to: %s\n",
5841: wq->name);
5842:
5843: /*
5844: * One among WQ_PERCPU and WQ_UNBOUND must be set, but not both.
5845: * - If neither is set, default to WQ_PERCPU
5846: * - If both are set, default to WQ_UNBOUND
5847: *
5848: * This code can be removed after workqueue are unbound by default
5849: */
5850: if (unlikely(!(flags & (WQ_UNBOUND | WQ_PERCPU)))) {
5851: WARN_ONCE(1, "workqueue: %s is using neither WQ_PERCPU or WQ_UNBOUND. "
5852: "Setting WQ_PERCPU.\n", wq->name);
5853: flags |= WQ_PERCPU;
5854: } else if (unlikely((flags & WQ_PERCPU) && (flags & WQ_UNBOUND))) {
5855: WARN_ONCE(1, "workqueue: %s uses both WQ_PERCPU and WQ_UNBOUND. "
5856: "Dropped WQ_PERCPU, keeping WQ_UNBOUND.\n", wq->name);
5857: flags &= ~WQ_PERCPU;
5858: }
5859:
5860: if (flags & WQ_BH) {
5861: /*
5862: * BH workqueues always share a single execution context per CPU
5863: * and don't impose any max_active limit.
5864: */
5865: max_active = INT_MAX;
5866: } else {
5867: max_active = max_active ?: WQ_DFL_ACTIVE;
5868: max_active = wq_clamp_max_active(max_active, flags, wq->name);
5869: }
5870:
5871: /* init wq */
5872: wq->flags = flags;
5873: wq->max_active = max_active;
5874: wq->min_active = min(max_active, WQ_DFL_MIN_ACTIVE);
5875: wq->saved_max_active = wq->max_active;
5876: wq->saved_min_active = wq->min_active;
5877: mutex_init(&wq->mutex);
5878: atomic_set(&wq->nr_pwqs_to_flush, 0);
5879: INIT_LIST_HEAD(&wq->pwqs);
5880: INIT_LIST_HEAD(&wq->flusher_queue);
5881: INIT_LIST_HEAD(&wq->flusher_overflow);
5882: INIT_LIST_HEAD(&wq->maydays);
5883:
5884: INIT_LIST_HEAD(&wq->list);
5885:
5886: if (flags & WQ_UNBOUND) {
5887: if (alloc_node_nr_active(wq->node_nr_active) < 0)
5888: goto err_free_wq;
5889: }
5890:
5891: /*
5892: * wq_pool_mutex protects the workqueues list, allocations of PWQs,
5893: * and the global freeze state.
5894: */
5895: mutex_lock(&wq_pool_mutex);
5896:
5897: if (alloc_and_link_pwqs(wq) < 0)
5898: goto err_unlock_free_node_nr_active;
5899:
5900: mutex_lock(&wq->mutex);
5901: wq_adjust_max_active(wq);
5902: mutex_unlock(&wq->mutex);
5903:
5904: list_add_tail_rcu(&wq->list, &workqueues);
5905:
5906: if (wq_online && init_rescuer(wq) < 0)
5907: goto err_unlock_destroy;
5908:
5909: mutex_unlock(&wq_pool_mutex);
5910:
5911: if ((wq->flags & WQ_SYSFS) && workqueue_sysfs_register(wq))
5912: goto err_destroy;
5913:
5914: return wq;
5915:
5916: err_unlock_free_node_nr_active:
5917: mutex_unlock(&wq_pool_mutex);
5918: /*
5919: * Failed alloc_and_link_pwqs() may leave pending pwq->release_work,
5920: * flushing the pwq_release_worker ensures that the pwq_release_workfn()
5921: * completes before calling kfree(wq).
5922: */
5923: if (wq->flags & WQ_UNBOUND) {
5924: kthread_flush_worker(pwq_release_worker);
5925: free_node_nr_active(wq->node_nr_active);
5926: }
5927: err_free_wq:
5928: free_workqueue_attrs(wq->unbound_attrs);
5929: kfree(wq);
5930: return NULL;
5931: err_unlock_destroy:
5932: mutex_unlock(&wq_pool_mutex);
5933: err_destroy:
5934: destroy_workqueue(wq);
5935: return NULL;
5936: }
SourceFile:kernel/workqueue.c]
|
| 8/2 |
2026/07/18 00:02 |
llm |
expert |
0m
Model:
Tokens:
input: 18527
output: 39
thoughts: 360
Thoughts: **Analyzing a Potential Deadlock in the Workqueue Allocation Path**
Okay, so I'm digging into this workqueue allocation flow, and it looks like `__alloc_workqueue` holds the `wq_pool_mutex` at line 5895. Then, it calls `alloc_and_link_pwqs` on line 5897. The `alloc_and_link_pwqs` function calls `apply_workqueue_attrs_locked`, which then calls `apply_wqattrs_prepare`. That last function allocates memory using `GFP_KERNEL`, which I know can trigger `fs_reclaim`. This whole thing screams lockdep warning or known issue, I suspect, and I need to figure out why.
The potential problem is that `fs_reclaim` can lead to `cpu_hotplug_lock`, and ultimately `wq_pool_mutex`. The question is, is there a legitimate dependency issue in there?
Looking further, in call stack `#7`, I see `__alloc_workqueue` acquiring `wq_pool_mutex` is actually called from `padata_alloc` which is called from `pcrypt_init_padata` -> `pcrypt_init` -> `do_one_initcall`. That makes me think... `pcrypt_init` is an initcall, right? So we are dealing with initialization code. That adds a layer of complexity.
Now, in call stack `#6`, I see `cpus_read_lock` which *does* acquire `cpu_hotplug_lock`, and this is all called from `nbd_reconnect_socket` -> `nbd_genl_reconfigure` -> `genl_family_rcv_msg_doit` and then some more calls that ultimately get to `sys_sendmsg`. Wait... does `nbd_reconnect_socket` acquire `cpu_hotplug_lock` directly? No, it looks like it is calling `cpus_read_lock`. I need to be sure about this, let me check the `nbd_reconnect_socket` function to confirm. I need to make sure the lock acquisition order isn't reversed and causing a deadlock.
|
| 9/2 |
2026/07/18 00:02 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/block/nbd.c Name:nbd_reconnect_socket]
Results: map[SourceCode:1350: static int nbd_reconnect_socket(struct nbd_device *nbd, unsigned long arg)
1351: {
1352: struct nbd_config *config = nbd->config;
1353: struct socket *sock, *old;
1354: struct recv_thread_args *args;
1355: int i;
1356: int err;
1357:
1358: sock = nbd_get_socket(nbd, arg, &err);
1359: if (!sock)
1360: return err;
1361:
1362: args = kzalloc_obj(*args);
1363: if (!args) {
1364: sockfd_put(sock);
1365: return -ENOMEM;
1366: }
1367:
1368: for (i = 0; i < config->num_connections; i++) {
1369: struct nbd_sock *nsock = config->socks[i];
1370:
1371: if (!nsock->dead)
1372: continue;
1373:
1374: mutex_lock(&nsock->tx_lock);
1375: if (!nsock->dead) {
1376: mutex_unlock(&nsock->tx_lock);
1377: continue;
1378: }
1379: sk_set_memalloc(sock->sk);
1380: if (nbd->tag_set.timeout)
1381: sock->sk->sk_sndtimeo = nbd->tag_set.timeout;
1382: atomic_inc(&config->recv_threads);
1383: refcount_inc(&nbd->config_refs);
1384: old = nsock->sock;
1385: nsock->fallback_index = -1;
1386: nsock->sock = sock;
1387: nsock->dead = false;
1388: INIT_WORK(&args->work, recv_work);
1389: args->index = i;
1390: args->nbd = nbd;
1391: args->nsock = nsock;
1392: nsock->cookie++;
1393: mutex_unlock(&nsock->tx_lock);
1394: sockfd_put(old);
1395:
1396: clear_bit(NBD_RT_DISCONNECTED, &config->runtime_flags);
1397:
1398: /* We take the tx_mutex in an error path in the recv_work, so we
1399: * need to queue_work outside of the tx_mutex.
1400: */
1401: queue_work(nbd->recv_workq, &args->work);
1402:
1403: atomic_inc(&config->live_connections);
1404: wake_up(&config->conn_wait);
1405: return 0;
1406: }
1407: sockfd_put(sock);
1408: kfree(args);
1409: return -ENOSPC;
1410: }
SourceFile:drivers/block/nbd.c]
|
| 10/2 |
2026/07/18 00:02 |
llm |
expert |
0m
Model:
Tokens:
input: 20303
output: 37
thoughts: 95
Thoughts: Here's my thought process, as it unfolded:
**Unraveling the Connection - Memory Allocation and CPU Locking**
Okay, so I'm digging into this `nbd_reconnect_socket` function, and something's not quite right. My initial thought was: hold on, doesn't `nbd_reconnect_socket` call `sk_set_memalloc(sock->sk)`? But then I thought, wait, could it be that `cpus_read_lock` is called *from* within `nbd_reconnect_socket` instead? It's important to keep track of these control flow paths.
Alright, let's nail this down. Let me just double-check that line reference I have, line 1379 in the code. Aha! I see `sk_set_memalloc(sock->sk);` there. That seems to confirm the direct call. Now, this is where it gets interesting... I need to be absolutely sure because something doesn't feel right. Let me just pause here to consider. Does `sk_set_memalloc` *itself* possibly call `cpus_read_lock`? If so, that would totally change my interpretation.
|
| 11/2 |
2026/07/18 00:02 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/core/sock.c Name:sk_set_memalloc]
Results: map[SourceCode: 294: /**
295: * sk_set_memalloc - sets %SOCK_MEMALLOC
296: * @sk: socket to set it on
297: *
298: * Set %SOCK_MEMALLOC on a socket for access to emergency reserves.
299: * It's the responsibility of the admin to adjust min_free_kbytes
300: * to meet the requirements
301: */
302: void sk_set_memalloc(struct sock *sk)
303: {
304: sock_set_flag(sk, SOCK_MEMALLOC);
305: sk->sk_allocation |= __GFP_MEMALLOC;
306: static_branch_inc(&memalloc_socks_key);
307: }
SourceFile:net/core/sock.c]
|
| 12/2 |
2026/07/18 00:02 |
llm |
expert |
0m
Model:
Tokens:
input: 20942
output: 34
thoughts: 1006
Thoughts: **Analyzing a Potential Locking Dependency**
Okay, let's break this down. My initial thought process starts with `static_branch_inc` calling `static_key_slow_inc`, which acquires `cpu_hotplug_lock` through `cpus_read_lock`. Then I see that `nbd_reconnect_socket` acquires `nsock->tx_lock` and subsequently calls `sk_set_memalloc`, which also acquires `cpu_hotplug_lock`. This suggests a potential locking order issue where `nsock->tx_lock` might be acquired before `cpu_hotplug_lock`.
I then look more closely at dependency chain `#6`. It clearly shows `cpu_hotplug_lock` being acquired while holding `nsock->tx_lock`. Specifically, the path is `nbd_genl_reconfigure` calling `nbd_reconnect_socket`, which calls `static_key_slow_inc` and acquires the `cpu_hotplug_lock`. My understanding is that `static_key_slow_inc` (where the lock is acquired) is at line 1379 in the nbd.c file.
Then I look at the acquisition of `nsock->tx_lock`. I see that it is acquired in `nbd_queue_rq`. So I see a chain: `cpu_hotplug_lock` -> `nsock->tx_lock`. And looking at chain `#5`, I see the lock is acquired by the `nbd_handle_cmd` function, which calls `nbd_queue_rq`.
Okay, so I see a longer chain. It seems `fs_reclaim` -> `q->q_usage_counter(io)` -> `q->elevator_lock` -> `set->srcu` -> `cmd->lock` -> `nsock->tx_lock` -> `cpu_hotplug_lock` -> `wq_pool_mutex`. I'm starting to get a better handle on the dependency, but am still working through the finer details.
Now I am trying to determine if there is a circular dependency. I see a chain `cpu_hotplug_lock` -> `wq_pool_mutex`, and want to check if the acquisition order is correct. I have to verify that the locks are indeed acquired in the correct order. I'm focusing on dependency `#7` here. It seems `wq_pool_mutex` is acquired in `do_one_initcall` when initializing the workqueue. More precisely, it appears that `padata_alloc`, called from `pcrypt_init_padata`, calls `cpus_read_lock`. Therefore it acquires `cpu_hotplug_lock`.
I'll double-check this in `padata_alloc` to confirm, but this is the likely source of the contention.
|
| 13/2 |
2026/07/18 00:02 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/padata.c Name:padata_alloc]
Results: map[SourceCode: 943: /**
944: * padata_alloc - allocate and initialize a padata instance
945: * @name: used to identify the instance
946: *
947: * Return: new instance on success, NULL on error
948: */
949: struct padata_instance *padata_alloc(const char *name)
950: {
951: struct padata_instance *pinst;
952:
953: pinst = kzalloc_obj(struct padata_instance);
954: if (!pinst)
955: goto err;
956:
957: pinst->parallel_wq = alloc_workqueue("%s_parallel", WQ_UNBOUND, 0,
958: name);
959: if (!pinst->parallel_wq)
960: goto err_free_inst;
961:
962: cpus_read_lock();
963:
964: pinst->serial_wq = alloc_workqueue("%s_serial",
965: WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE | WQ_PERCPU,
966: 1, name);
967: if (!pinst->serial_wq)
968: goto err_put_cpus;
969:
970: if (!alloc_cpumask_var(&pinst->cpumask.pcpu, GFP_KERNEL))
971: goto err_free_serial_wq;
972: if (!alloc_cpumask_var(&pinst->cpumask.cbcpu, GFP_KERNEL))
973: goto err_free_p_mask;
974: if (!alloc_cpumask_var(&pinst->validate_cpumask, GFP_KERNEL))
975: goto err_free_cb_mask;
976:
977: INIT_LIST_HEAD(&pinst->pslist);
978:
979: cpumask_copy(pinst->cpumask.pcpu, cpu_possible_mask);
980: cpumask_copy(pinst->cpumask.cbcpu, cpu_possible_mask);
981:
982: if (padata_setup_cpumasks(pinst))
983: goto err_free_v_mask;
984:
985: __padata_start(pinst);
986:
987: kobject_init(&pinst->kobj, &padata_attr_type);
988: mutex_init(&pinst->lock);
989:
990: #ifdef CONFIG_HOTPLUG_CPU
991: cpuhp_state_add_instance_nocalls_cpuslocked(hp_online,
992: &pinst->cpuhp_node);
993: #endif
994:
995: cpus_read_unlock();
996:
997: return pinst;
998:
999: err_free_v_mask:
1000: free_cpumask_var(pinst->validate_cpumask);
1001: err_free_cb_mask:
1002: free_cpumask_var(pinst->cpumask.cbcpu);
1003: err_free_p_mask:
1004: free_cpumask_var(pinst->cpumask.pcpu);
1005: err_free_serial_wq:
1006: destroy_workqueue(pinst->serial_wq);
1007: err_put_cpus:
1008: cpus_read_unlock();
1009: destroy_workqueue(pinst->parallel_wq);
1010: err_free_inst:
1011: kfree(pinst);
1012: err:
1013: return NULL;
1014: }
SourceFile:kernel/padata.c]
|
| 14/2 |
2026/07/18 00:02 |
llm |
expert |
0m
Model:
Tokens:
input: 23726
output: 38
thoughts: 225
Thoughts: **Analyzing the Lock Acquisition Order in the Kernel Module**
Okay, so I'm tracing through this code, trying to nail down this locking issue. I see that `padata_alloc` calls `cpus_read_lock()` on line 962, and then almost immediately, on line 964, it calls `alloc_workqueue`. Now, `alloc_workqueue` acquires `wq_pool_mutex`. So, right there, we have a clear ordering dependency: `cpu_hotplug_lock` *before* `wq_pool_mutex`. That's the baseline.
Now, let's look at the other side of this. I'm focusing on the condition labelled as `#6`. This involves `cpu_hotplug_lock` being acquired *while* holding `nsock->tx_lock`. Hmm, that's interesting. I need to refresh my memory on the call chain. Let's go back to `nbd_reconnect_socket`. Line 1374, it grabs `nsock->tx_lock`. And then, on line 1379, it calls `sk_set_memalloc(sock->sk)`. And *that* calls `cpu_hotplug_lock` (through `static_branch_inc`). So, in this path, we have the opposite ordering: `nsock->tx_lock` *before* `cpu_hotplug_lock`. Good, confirming that.
The last thing to look at is condition `#5`. This is `nsock->tx_lock` being acquired while holding `cmd->lock`. Let's confirm that via inspection of the `nbd_queue_rq` function.
|
| 15/2 |
2026/07/18 00:03 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/block/nbd.c Name:nbd_queue_rq]
Results: map[SourceCode:1184: static blk_status_t nbd_queue_rq(struct blk_mq_hw_ctx *hctx,
1185: const struct blk_mq_queue_data *bd)
1186: {
1187: struct nbd_cmd *cmd = blk_mq_rq_to_pdu(bd->rq);
1188: blk_status_t ret;
1189:
1190: /*
1191: * Since we look at the bio's to send the request over the network we
1192: * need to make sure the completion work doesn't mark this request done
1193: * before we are done doing our send. This keeps us from dereferencing
1194: * freed data if we have particularly fast completions (ie we get the
1195: * completion before we exit sock_xmit on the last bvec) or in the case
1196: * that the server is misbehaving (or there was an error) before we're
1197: * done sending everything over the wire.
1198: */
1199: mutex_lock(&cmd->lock);
1200: clear_bit(NBD_CMD_REQUEUED, &cmd->flags);
1201:
1202: /* We can be called directly from the user space process, which means we
1203: * could possibly have signals pending so our sendmsg will fail. In
1204: * this case we need to return that we are busy, otherwise error out as
1205: * appropriate.
1206: */
1207: ret = nbd_handle_cmd(cmd, hctx->queue_num);
1208: mutex_unlock(&cmd->lock);
1209:
1210: return ret;
1211: }
SourceFile:drivers/block/nbd.c]
|
| 16/2 |
2026/07/18 00:03 |
llm |
expert |
0m
Model:
Tokens:
input: 24871
output: 38
thoughts: 54
Thoughts: Here's my summary, as the expert:
**Tracing the NBD Command Handling Path**
Alright, let's break this down. The `nbd_queue_rq` function, on line 1199, grabs the `cmd->lock`. Okay, that's standard for protecting the command structure itself during processing. Now, the interesting part is what happens *after* that lock is held. Line 1207 calls `nbd_handle_cmd`. So, the core of the command handling must be within that function. My focus is going to shift there immediately; I need to understand exactly what `nbd_handle_cmd` does. What are the critical paths within `nbd_handle_cmd`? What potential concurrency issues or race conditions might be introduced? Time to dive into the code for that function and look for the interesting things.
|
| 17/2 |
2026/07/18 00:03 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/block/nbd.c Name:nbd_handle_cmd]
Results: map[SourceCode:1117: static blk_status_t nbd_handle_cmd(struct nbd_cmd *cmd, int index)
1118: {
1119: struct request *req = blk_mq_rq_from_pdu(cmd);
1120: struct nbd_device *nbd = cmd->nbd;
1121: struct nbd_config *config;
1122: struct nbd_sock *nsock;
1123: blk_status_t ret;
1124:
1125: lockdep_assert_held(&cmd->lock);
1126:
1127: config = nbd_get_config_unlocked(nbd);
1128: if (!config) {
1129: dev_err_ratelimited(disk_to_dev(nbd->disk),
1130: "Socks array is empty\n");
1131: return BLK_STS_IOERR;
1132: }
1133:
1134: if (index >= config->num_connections) {
1135: dev_err_ratelimited(disk_to_dev(nbd->disk),
1136: "Attempted send on invalid socket\n");
1137: nbd_config_put(nbd);
1138: return BLK_STS_IOERR;
1139: }
1140: cmd->status = BLK_STS_OK;
1141: again:
1142: nsock = config->socks[index];
1143: mutex_lock(&nsock->tx_lock);
1144: if (nsock->dead) {
1145: int old_index = index;
1146: index = find_fallback(nbd, index);
1147: mutex_unlock(&nsock->tx_lock);
1148: if (index < 0) {
1149: if (wait_for_reconnect(nbd)) {
1150: index = old_index;
1151: goto again;
1152: }
1153: /* All the sockets should already be down at this point,
1154: * we just want to make sure that DISCONNECTED is set so
1155: * any requests that come in that were queue'ed waiting
1156: * for the reconnect timer don't trigger the timer again
1157: * and instead just error out.
1158: */
1159: sock_shutdown(nbd);
1160: nbd_config_put(nbd);
1161: return BLK_STS_IOERR;
1162: }
1163: goto again;
1164: }
1165:
1166: /* Handle the case that we have a pending request that was partially
1167: * transmitted that _has_ to be serviced first. We need to call requeue
1168: * here so that it gets put _after_ the request that is already on the
1169: * dispatch list.
1170: */
1171: blk_mq_start_request(req);
1172: if (unlikely(nsock->pending && nsock->pending != req)) {
1173: nbd_requeue_cmd(cmd);
1174: ret = BLK_STS_OK;
1175: goto out;
1176: }
1177: ret = nbd_send_cmd(nbd, cmd, index);
1178: out:
1179: mutex_unlock(&nsock->tx_lock);
1180: nbd_config_put(nbd);
1181: return ret;
1182: }
SourceFile:drivers/block/nbd.c]
|
| 18/2 |
2026/07/18 00:03 |
llm |
expert |
0m
Model:
Tokens:
input: 26191
output: 32
thoughts: 130
Thoughts: **Analyzing the Locking Order in NBD Command Handling**
Okay, so I'm tracing through this NBD code and need to sort out the locking order. Right, `nbd_handle_cmd` *does* acquire `nsock->tx_lock` first, as confirmed at line 1143. That makes `cmd->lock` essentially the same as `nsock->tx_lock` - good, that's the base of the command's lock.
Now, I'm trying to follow the locking sequence that's annotated with `#4`, which indicates `cmd->lock` is acquired while holding `set->srcu`. Wait a second… isn't `#4` about acquiring `cmd->lock` within `nbd_queue_rq`? That's right, it is. But, I thought `#3` involved `set->srcu`, but that seemed to occur in `elevator_switch`.
Alright, I need to verify that `elevator_switch` *actually* acquires `set->srcu`. Let's dig into that function and make sure that's how this order actually pans out.
|
| 19/2 |
2026/07/18 00:03 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:block/elevator.c Name:elevator_switch]
Results: map[SourceCode: 556: /*
557: * Switch to new_e io scheduler.
558: *
559: * If switching fails, we are most likely running out of memory and not able
560: * to restore the old io scheduler, so leaving the io scheduler being none.
561: */
562: static int elevator_switch(struct request_queue *q, struct elv_change_ctx *ctx)
563: {
564: struct elevator_type *new_e = NULL;
565: int ret = 0;
566:
567: WARN_ON_ONCE(q->mq_freeze_depth == 0);
568: lockdep_assert_held(&q->elevator_lock);
569:
570: if (strncmp(ctx->name, "none", 4)) {
571: new_e = elevator_find_get(ctx->name);
572: if (!new_e)
573: return -EINVAL;
574: }
575:
576: blk_mq_quiesce_queue(q);
577:
578: if (q->elevator) {
579: ctx->old = q->elevator;
580: elevator_exit(q);
581: }
582:
583: if (new_e) {
584: ret = blk_mq_init_sched(q, new_e, &ctx->res);
585: if (ret)
586: goto out_unfreeze;
587: ctx->new = q->elevator;
588: } else {
589: blk_queue_flag_clear(QUEUE_FLAG_SQ_SCHED, q);
590: q->elevator = NULL;
591: q->nr_requests = q->tag_set->queue_depth;
592: q->async_depth = q->tag_set->queue_depth;
593: }
594: blk_add_trace_msg(q, "elv switch: %s", ctx->name);
595:
596: out_unfreeze:
597: blk_mq_unquiesce_queue(q);
598:
599: if (ret) {
600: pr_warn("elv: switch to \"%s\" failed, falling back to \"none\"\n",
601: new_e->elevator_name);
602: }
603:
604: if (new_e)
605: elevator_put(new_e);
606: return ret;
607: }
SourceFile:block/elevator.c]
|
| 20/2 |
2026/07/18 00:03 |
llm |
expert |
0m
Model:
Tokens:
input: 27379
output: 40
thoughts: 194
Thoughts: **Investigating the SRCU Acquisition in `elevator_switch`**
Okay, I'm trying to trace how the `set->srcu` variable is being handled within the `elevator_switch` function. My initial thought is, where exactly is the SRCU lock being acquired? Looking at the trace, specifically at `#3`, it looks like `elevator_switch` calls `__synchronize_srcu`, which in turn involves `srcu_lock_sync` – that seems to be the critical point.
Wait a second... doesn't `blk_mq_quiesce_queue(q)` call `synchronize_srcu(set->srcu)`? It seems I have conflated a couple of methods. It looks like `blk_mq_quiesce_queue` calls `synchronize_srcu` on `set->srcu` so I should look there.
I should really check the implementation of `blk_mq_quiesce_queue` to confirm this connection.
|
| 21/2 |
2026/07/18 00:03 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:block/blk-mq.c Name:blk_mq_quiesce_queue]
Results: map[SourceCode: 290: /**
291: * blk_mq_quiesce_queue() - wait until all ongoing dispatches have finished
292: * @q: request queue.
293: *
294: * Note: this function does not prevent that the struct request end_io()
295: * callback function is invoked. Once this function is returned, we make
296: * sure no dispatch can happen until the queue is unquiesced via
297: * blk_mq_unquiesce_queue().
298: */
299: void blk_mq_quiesce_queue(struct request_queue *q)
300: {
301: blk_mq_quiesce_queue_nowait(q);
302: /* nothing to wait for non-mq queues */
303: if (queue_is_mq(q))
304: blk_mq_wait_quiesce_done(q->tag_set);
305: }
SourceFile:block/blk-mq.c]
|
| 22/2 |
2026/07/18 00:03 |
llm |
expert |
0m
Model:
Tokens:
input: 28131
output: 42
thoughts: 241
Thoughts: **Analyzing the blk-mq Quiesce Process**
Okay, let's break this down. `blk_mq_wait_quiesce_done` uses `synchronize_srcu(set->srcu)`. That means `elevator_switch` is waiting on `set->srcu`, which is an SRCU read lock. So, there's a dependency on that SRCU read lock. My initial thought is: does `set->srcu` relate to `cmd->lock`?
Now I need to clarify `#4`. Ah, `#4` acquires `cmd->lock` in `nbd_queue_rq`. But, wait... I was thinking this was acquired while holding `set->srcu`, but that's incorrect. `nbd_queue_rq` is called from `blk_mq_dispatch_rq_list`, which traces back to `blk_mq_do_dispatch_sched`, and finally to `blk_mq_run_work_fn`.
The key question is, where is `set->srcu` read lock acquired if not at `#4`? Right, `blk_mq_run_work_fn` calls `blk_mq_sched_dispatch_requests`. It's *possible* `blk_mq_sched_dispatch_requests` acquires the `set->srcu` read lock. I need to examine the implementation of `blk_mq_sched_dispatch_requests` to confirm.
|
| 23/2 |
2026/07/18 00:03 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:block/blk-mq-sched.c Name:blk_mq_sched_dispatch_requests]
Results: map[SourceCode: 317: void blk_mq_sched_dispatch_requests(struct blk_mq_hw_ctx *hctx)
318: {
319: struct request_queue *q = hctx->queue;
320:
321: /* RCU or SRCU read lock is needed before checking quiesced flag */
322: if (unlikely(blk_mq_hctx_stopped(hctx) || blk_queue_quiesced(q)))
323: return;
324:
325: /*
326: * A return of -EAGAIN is an indication that hctx->dispatch is not
327: * empty and we must run again in order to avoid starving flushes.
328: */
329: if (__blk_mq_sched_dispatch_requests(hctx) == -EAGAIN) {
330: if (__blk_mq_sched_dispatch_requests(hctx) == -EAGAIN)
331: blk_mq_run_hw_queue(hctx, true);
332: }
333: }
SourceFile:block/blk-mq-sched.c]
|
| 24/2 |
2026/07/18 00:03 |
llm |
expert |
0m
Model:
Tokens:
input: 29055
output: 40
thoughts: 23
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience and written in the first person:
**SRCU Lock Deep Dive**
Okay, let's see... *blk_mq_run_work_fn*... right, I'm tracing through this code path again. And there it is, buried in the function, it looks like *blk_mq_run_work_fn* *acquires* the *set->srcu* read lock. Hmm, interesting. I need to keep that in mind as I analyze the implications for concurrency and potential contention in this part of the block layer. That read lock acquisition is going to be crucial for understanding how data is protected and how this function plays with other parts of the system. I will need to consider whether it is acquired at the right time in the process and is causing any bottlenecks, especially given the context of concurrent requests that I'm working with. Time to see what other functions are interacting with this SRCU lock as well.
|
| 25/2 |
2026/07/18 00:03 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:block/blk-mq.c Name:blk_mq_run_work_fn]
Results: map[SourceCode:2526: static void blk_mq_run_work_fn(struct work_struct *work)
2527: {
2528: struct blk_mq_hw_ctx *hctx =
2529: container_of(work, struct blk_mq_hw_ctx, run_work.work);
2530:
2531: blk_mq_run_dispatch_ops(hctx->queue,
2532: blk_mq_sched_dispatch_requests(hctx));
2533: }
SourceFile:block/blk-mq.c]
|
| 26/2 |
2026/07/18 00:03 |
llm |
expert |
0m
Model:
Tokens:
input: 29499
output: 40
thoughts: 15
Thoughts: Okay, let's dive into this. Here's my thought process summarized, assuming I'm already pretty familiar with block I/O internals and am likely debugging or analyzing performance:
**Deep Dive into `blk_mq_run_dispatch_ops`**
Alright, time to zero in on `blk_mq_run_dispatch_ops`. That's where the rubber meets the road, where the actual dispatch logic for a block I/O queue kicks in. I need to refresh my understanding of *precisely* what operations it's responsible for orchestrating, right at the point where the requests get their heads. I want to be certain I understand how it interacts with the software queues and hardware queues, or what is being configured and dispatched. I should review how it chooses the specific `hctx` (host context), how it handles the different types of requests and, most importantly, what the expected outcome of its execution should be.
I'll be looking for potential bottlenecks, race conditions, or performance anomalies here. Anything that could cause a stall or excessive latency will be flagged as suspect. Let's see how that looks now, in this codebase.
|
| 27/2 |
2026/07/18 00:03 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:block/blk-mq.h Name:blk_mq_run_dispatch_ops]
Results: map[SourceCode: SourceFile:]
|
| 28/2 |
2026/07/18 00:03 |
llm |
expert |
0m
Model:
Tokens:
input: 29818
output: 25
thoughts: 7
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Initial Assessment: Probable Macro Suspect**
Alright, that's what I'm thinking, based on this initial clue. It’s highly probable, given my experience, that we're dealing with a macro. The specific context is missing, of course, but that's my instinctual starting point. We need to follow this line of inquiry, likely considering how it operates, and assessing the specific macro functions in question.
|
| 29/2 |
2026/07/18 00:03 |
tool |
grepper |
0m
Args: map[Expression:define blk_mq_run_dispatch_ops]
Results: map[Output:block/blk-mq.h=440=do { \
--
block/blk-mq.h-455-
block/blk-mq.h:456:#define blk_mq_run_dispatch_ops(q, dispatch_ops) \
block/blk-mq.h-457- __blk_mq_run_dispatch_ops(q, true, dispatch_ops) \
]
|
| 30/2 |
2026/07/18 00:03 |
llm |
expert |
0m
Model:
Tokens:
input: 30079
output: 41
thoughts: 16
Thoughts: Okay, here's my interpretation of what's going on, formatted as you requested:
**Deconstructing `__blk_mq_run_dispatch_ops`**
Alright, let's dive into this `__blk_mq_run_dispatch_ops` function. It looks like we're about to run the dispatch operations within the block multi-queue (blk-mq) subsystem, which means we're dealing with the core of how I/O requests are scheduled and processed. I need to understand exactly what "dispatch ops" encompass here. Are we talking about actually submitting requests to the hardware, or is this a higher-level scheduling phase, perhaps dealing with request merging, sorting, or prioritization? The naming suggests that the core execution is happening in here.
I'll be looking closely to see how the code handles concurrency and locking. With blk-mq, we're talking about a multi-queue environment which is a complex interaction of hardware queues and software. I’m expecting to see robust mechanisms to prevent data races and ensure that requests are handled correctly in parallel.
I'll also pay attention to any optimization strategies. Are they using batching, or work-stealing, to balance the load across the different queues? Does it interact with request merge or are the merging algorithms happening upstream? The efficiency of this function directly impacts overall I/O performance, so every detail matters. I will assess if this handles interrupt coalescing, and if so, how.
|
| 31/2 |
2026/07/18 00:03 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:block/blk-mq.h Name:__blk_mq_run_dispatch_ops]
Results: map[SourceCode: SourceFile:]
|
| 32/2 |
2026/07/18 00:03 |
llm |
expert |
0m
Model:
Tokens:
input: 30466
output: 25
thoughts: 7
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Initial Assessment: It's a Macro**
Alright, that's what I'm seeing. It seems we're dealing with a macro. Specifically, my initial reaction is that whatever this is, it's operating at that level. I'm taking a quick, high-level view and it seems the pattern matches one of those. Let's see if this warrants a deeper dive into the specific macro family.
|
| 33/2 |
2026/07/18 00:03 |
tool |
grepper |
0m
Args: map[Expression:define __blk_mq_run_dispatch_ops]
Results: map[Output:block/blk-mq.h=403=static inline bool hctx_may_queue(struct blk_mq_hw_ctx *hctx,
--
block/blk-mq.h-438-/* run the code block in @dispatch_ops with rcu/srcu read lock held */
block/blk-mq.h:439:#define __blk_mq_run_dispatch_ops(q, check_sleep, dispatch_ops) \
block/blk-mq.h-440-do { \
]
|
| 34/2 |
2026/07/18 00:03 |
llm |
expert |
0m
Model:
Tokens:
input: 30750
output: 38
thoughts: 505
Thoughts: **Analyzing Potential Lock Ordering Issues in the Block Layer**
Okay, so I'm digging into this code path. It looks like `__blk_mq_run_dispatch_ops` is running with the `rcu/srcu` read lock held. That means the `set->srcu` read lock is definitely held during the `blk_mq_sched_dispatch_requests` call. This function, in turn, calls `nbd_queue_rq`, and that's where things get interesting because it acquires `cmd->lock`. So we have `set->srcu` nesting into `cmd->lock`. That seems straightforward enough.
Now, I'm spotting a potential issue marked as `#3`. This involves acquiring `set->srcu` while *also* holding `q->elevator_lock`. Hmm, that's something to investigate further. It looks like `elevator_switch` is called while holding `q->elevator_lock`, and that in turn calls `blk_mq_quiesce_queue`, which ultimately calls `synchronize_srcu(set->srcu)`. Alright, so we're looking at `q->elevator_lock` potentially nesting into `set->srcu`.
Then, I'm seeing this `#2` which seems to involve acquiring `q->elevator_lock` while *already* holding `q->q_usage_counter(io)`. Let's clarify what's going on with this path: It looks like `#2` is:
-> #2 (&q->elevator_lock){+.+.}-{4:4}:
__mutex_lock_common kernel/locking/mutex.c:646 [inline]
__mutex_lock+0x19d/0x1550 kernel/locking/mutex.c:821
elevator_change+0x1af/0x480 block/elevator.c:679
elevator_set_none+0xb5/0x140 block/elevator.c:769
blk_mq_elv_switch_none block/blk-mq.c:5101 [inline]
__blk_mq_update_nr_hw_queues block/blk-mq.c:5146 [inline]
blk_mq_update_nr_hw_queues+0x5ef/0x19f0 block/blk-mq.c:5211
nbd_start_device+0x189/0xb30 drivers/block/nbd.c:1526
nbd_genl_connect+0x1597/0x1c10 drivers/block/nbd.c:2276
Okay, so the trace points to `nbd_start_device`. I think the next step is to examine that function in more detail and trace the lock acquisition from there. I need to understand the full context of how these locks are being acquired and released to determine if there's a risk of deadlocks or priority inversion.
|
| 35/2 |
2026/07/18 00:03 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/block/nbd.c Name:nbd_start_device]
Results: map[SourceCode:1508: static int nbd_start_device(struct nbd_device *nbd)
1509: {
1510: struct nbd_config *config = nbd->config;
1511: int num_connections = config->num_connections;
1512: int error = 0, i;
1513:
1514: if (nbd->pid)
1515: return -EBUSY;
1516: if (!config->socks)
1517: return -EINVAL;
1518: if (num_connections > 1 &&
1519: !(config->flags & NBD_FLAG_CAN_MULTI_CONN)) {
1520: dev_err(disk_to_dev(nbd->disk), "server does not support multiple connections per device.\n");
1521: return -EINVAL;
1522: }
1523:
1524: retry:
1525: mutex_unlock(&nbd->config_lock);
1526: blk_mq_update_nr_hw_queues(&nbd->tag_set, num_connections);
1527: mutex_lock(&nbd->config_lock);
1528:
1529: /* if another code path updated nr_hw_queues, retry until succeed */
1530: if (num_connections != config->num_connections) {
1531: num_connections = config->num_connections;
1532: goto retry;
1533: }
1534:
1535: nbd->pid = task_pid_nr(current);
1536:
1537: nbd_parse_flags(nbd);
1538:
1539: error = device_create_file(disk_to_dev(nbd->disk), &pid_attr);
1540: if (error) {
1541: dev_err(disk_to_dev(nbd->disk), "device_create_file failed for pid!\n");
1542: return error;
1543: }
1544: set_bit(NBD_RT_HAS_PID_FILE, &config->runtime_flags);
1545:
1546: nbd_dev_dbg_init(nbd);
1547: for (i = 0; i < num_connections; i++) {
1548: struct recv_thread_args *args;
1549:
1550: args = kzalloc_obj(*args);
1551: if (!args) {
1552: sock_shutdown(nbd);
1553: /*
1554: * If num_connections is m (2 < m),
1555: * and NO.1 ~ NO.n(1 < n < m) kzallocs are successful.
1556: * But NO.(n + 1) failed. We still have n recv threads.
1557: * So, add flush_workqueue here to prevent recv threads
1558: * dropping the last config_refs and trying to destroy
1559: * the workqueue from inside the workqueue.
1560: */
1561: if (i)
1562: flush_workqueue(nbd->recv_workq);
1563: return -ENOMEM;
1564: }
1565: sk_set_memalloc(config->socks[i]->sock->sk);
1566: if (nbd->tag_set.timeout)
1567: config->socks[i]->sock->sk->sk_sndtimeo =
1568: nbd->tag_set.timeout;
1569: atomic_inc(&config->recv_threads);
1570: refcount_inc(&nbd->config_refs);
1571: INIT_WORK(&args->work, recv_work);
1572: args->nbd = nbd;
1573: args->nsock = config->socks[i];
1574: args->index = i;
1575: queue_work(nbd->recv_workq, &args->work);
1576: }
1577: return nbd_set_size(nbd, config->bytesize, nbd_blksize(config));
1578: }
SourceFile:drivers/block/nbd.c]
|
| 36/2 |
2026/07/18 00:03 |
llm |
expert |
0m
Model:
Tokens:
input: 33147
output: 36
thoughts: 316
Thoughts: **Analyzing the Usage Counter Acquisition**
Okay, hold on a second. I need to trace the usage of `q->q_usage_counter(io)` within this code. The initial question is where is it acquired in `#2`? I need to know where and when this crucial resource is taken before I can understand the context.
In `#2`, the code calls `blk_mq_update_nr_hw_queues`. Alright. But what about `#1`? Is the `q->q_usage_counter(io)` acquisition happening while holding the `fs_reclaim` lock? No, that can't be right. Let me double-check. Okay, `#1` is pointing to `blk_alloc_queue`, which I know from experience.
Let me refresh my memory of the call stack for `#1`: `blk_alloc_queue`. Looking at the backtrace, it's pretty clear: `blk_alloc_queue`, within that context, is where it's being acquired.
So, let's confirm this by examining `blk_alloc_queue`. I need to verify that `blk_alloc_queue` is indeed the place where `q->q_usage_counter(io)` is acquired, and not a later call. Let me check the code there.
|
| 37/2 |
2026/07/18 00:03 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:block/blk-core.c Name:blk_alloc_queue]
Results: map[SourceCode: 436: struct request_queue *blk_alloc_queue(struct queue_limits *lim, int node_id)
437: {
438: struct request_queue *q;
439: int error;
440:
441: q = kmem_cache_alloc_node(blk_requestq_cachep, GFP_KERNEL | __GFP_ZERO,
442: node_id);
443: if (!q)
444: return ERR_PTR(-ENOMEM);
445:
446: q->last_merge = NULL;
447:
448: q->id = ida_alloc(&blk_queue_ida, GFP_KERNEL);
449: if (q->id < 0) {
450: error = q->id;
451: goto fail_q;
452: }
453:
454: q->stats = blk_alloc_queue_stats();
455: if (!q->stats) {
456: error = -ENOMEM;
457: goto fail_id;
458: }
459:
460: error = blk_set_default_limits(lim);
461: if (error)
462: goto fail_stats;
463: q->limits = *lim;
464:
465: q->node = node_id;
466:
467: atomic_set(&q->nr_active_requests_shared_tags, 0);
468:
469: timer_setup(&q->timeout, blk_rq_timed_out_timer, 0);
470: INIT_WORK(&q->timeout_work, blk_timeout_work);
471: INIT_LIST_HEAD(&q->icq_list);
472:
473: refcount_set(&q->refs, 1);
474: mutex_init(&q->debugfs_mutex);
475: mutex_init(&q->elevator_lock);
476: mutex_init(&q->sysfs_lock);
477: mutex_init(&q->limits_lock);
478: mutex_init(&q->rq_qos_mutex);
479: spin_lock_init(&q->queue_lock);
480:
481: init_waitqueue_head(&q->mq_freeze_wq);
482: mutex_init(&q->mq_freeze_lock);
483:
484: blkg_init_queue(q);
485:
486: /*
487: * Init percpu_ref in atomic mode so that it's faster to shutdown.
488: * See blk_register_queue() for details.
489: */
490: error = percpu_ref_init(&q->q_usage_counter,
491: blk_queue_usage_counter_release,
492: PERCPU_REF_INIT_ATOMIC, GFP_KERNEL);
493: if (error)
494: goto fail_stats;
495: lockdep_register_key(&q->io_lock_cls_key);
496: lockdep_register_key(&q->q_lock_cls_key);
497: lockdep_init_map(&q->io_lockdep_map, "&q->q_usage_counter(io)",
498: &q->io_lock_cls_key, 0);
499: lockdep_init_map(&q->q_lockdep_map, "&q->q_usage_counter(queue)",
500: &q->q_lock_cls_key, 0);
501:
502: /* Teach lockdep about lock ordering (reclaim WRT queue freeze lock). */
503: fs_reclaim_acquire(GFP_KERNEL);
504: rwsem_acquire_read(&q->io_lockdep_map, 0, 0, _RET_IP_);
505: rwsem_release(&q->io_lockdep_map, _RET_IP_);
506: fs_reclaim_release(GFP_KERNEL);
507:
508: q->nr_requests = BLKDEV_DEFAULT_RQ;
509: q->async_depth = BLKDEV_DEFAULT_RQ;
510:
511: return q;
512:
513: fail_stats:
514: blk_free_queue_stats(q->stats);
515: fail_id:
516: ida_free(&blk_queue_ida, q->id);
517: fail_q:
518: kmem_cache_free(blk_requestq_cachep, q);
519: return ERR_PTR(error);
520: }
SourceFile:block/blk-core.c]
|
| 38/2 |
2026/07/18 00:03 |
llm |
expert |
0m
Model:
Tokens:
input: 35160
output: 43
thoughts: 432
Thoughts: **Analyzing Lock Ordering in `blk_alloc_queue`**
Okay, so looking at `blk_alloc_queue`, it's explicitly teaching lockdep about the lock ordering. I see the explicit setup: `fs_reclaim` acquires, then `q->io_lockdep_map` acquires, and finally, releases both. This clearly states the relationship: `fs_reclaim` comes before `q->q_usage_counter(io)`. Got it.
Now, considering the second point, `#2`, it shows `q->q_usage_counter(io)` followed by `q->elevator_lock`. But I need to confirm where `q->q_usage_counter(io)` is actually acquired *before* `q->elevator_lock`. Hmm, I see it in `#2`: specifically, in the call chain. It’s showing `elevator_change` calls `q->elevator_lock` through `__mutex_lock_common`. This looks like the critical place.
Let's dig into `blk_mq_update_nr_hw_queues` now to see what's really happening.
|
| 39/2 |
2026/07/18 00:04 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:block/blk-mq.c Name:blk_mq_update_nr_hw_queues]
Results: map[SourceCode:5207: void blk_mq_update_nr_hw_queues(struct blk_mq_tag_set *set, int nr_hw_queues)
5208: {
5209: down_write(&set->update_nr_hwq_lock);
5210: mutex_lock(&set->tag_list_lock);
5211: __blk_mq_update_nr_hw_queues(set, nr_hw_queues);
5212: mutex_unlock(&set->tag_list_lock);
5213: up_write(&set->update_nr_hwq_lock);
5214: }
SourceFile:block/blk-mq.c]
|
| 40/2 |
2026/07/18 00:04 |
llm |
expert |
0m
Model:
Tokens:
input: 36077
output: 44
thoughts: 29
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm already deep in the weeds of block I/O internals:
**Diving into Block MQ Queue Updates**
Okay, so I'm tracing through this `__blk_mq_update_nr_hw_queues` function. It seems to be the core of dynamically adjusting the number of hardware queues, which is key for I/O performance under varying loads. But hang on... I see it's calling `blk_mq_elv_switch_none`. That's unexpected. `blk_mq_elv_switch_none`? That implies a no-op, that there is no need to switch any kind of queue policy, or elevator. Why is this function even being called in this context? I'd expect something more... active, at least a queue switch. I need to understand why they've chosen this specific route. Is this some kind of optimization, or a very specific edge case that's handled with a no-op? Or maybe this is the default and only when hardware queues change, do we even need to use it? This connection seems strange and needs further scrutiny. Let me investigate those call paths and see why this is so.
|
| 41/2 |
2026/07/18 00:04 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:block/blk-mq.c Name:__blk_mq_update_nr_hw_queues]
Results: map[SourceCode:5106: static void __blk_mq_update_nr_hw_queues(struct blk_mq_tag_set *set,
5107: int nr_hw_queues)
5108: {
5109: struct request_queue *q;
5110: int prev_nr_hw_queues = set->nr_hw_queues;
5111: unsigned int memflags;
5112: int i;
5113: struct xarray elv_tbl;
5114: struct blk_mq_tags **new_tags;
5115: bool queues_frozen = false;
5116:
5117: lockdep_assert_held(&set->tag_list_lock);
5118:
5119: if (set->nr_maps == 1 && nr_hw_queues > nr_cpu_ids)
5120: nr_hw_queues = nr_cpu_ids;
5121: if (nr_hw_queues < 1)
5122: return;
5123: if (set->nr_maps == 1 && nr_hw_queues == set->nr_hw_queues)
5124: return;
5125:
5126: memflags = memalloc_noio_save();
5127:
5128: xa_init(&elv_tbl);
5129: if (blk_mq_alloc_sched_ctx_batch(&elv_tbl, set) < 0)
5130: goto out_free_ctx;
5131:
5132: if (blk_mq_alloc_sched_res_batch(&elv_tbl, set, nr_hw_queues) < 0)
5133: goto out_free_ctx;
5134:
5135: list_for_each_entry(q, &set->tag_list, tag_set_list) {
5136: blk_mq_debugfs_unregister_hctxs(q);
5137: blk_mq_sysfs_unregister_hctxs(q);
5138: }
5139:
5140: /*
5141: * Switch IO scheduler to 'none', cleaning up the data associated
5142: * with the previous scheduler. We will switch back once we are done
5143: * updating the new sw to hw queue mappings.
5144: */
5145: list_for_each_entry(q, &set->tag_list, tag_set_list)
5146: if (blk_mq_elv_switch_none(q, &elv_tbl))
5147: goto switch_back;
5148:
5149: new_tags = blk_mq_prealloc_tag_set_tags(set, nr_hw_queues);
5150: if (IS_ERR(new_tags))
5151: goto switch_back;
5152:
5153: list_for_each_entry(q, &set->tag_list, tag_set_list)
5154: blk_mq_freeze_queue_nomemsave(q);
5155: queues_frozen = true;
5156: if (new_tags) {
5157: kfree(set->tags);
5158: set->tags = new_tags;
5159: }
5160: set->nr_hw_queues = nr_hw_queues;
5161:
5162: fallback:
5163: blk_mq_update_queue_map(set);
5164: list_for_each_entry(q, &set->tag_list, tag_set_list) {
5165: __blk_mq_realloc_hw_ctxs(set, q);
5166:
5167: if (q->nr_hw_queues != set->nr_hw_queues) {
5168: int i = prev_nr_hw_queues;
5169:
5170: pr_warn("Increasing nr_hw_queues to %d fails, fallback to %d\n",
5171: nr_hw_queues, prev_nr_hw_queues);
5172: for (; i < set->nr_hw_queues; i++)
5173: __blk_mq_free_map_and_rqs(set, i);
5174:
5175: set->nr_hw_queues = prev_nr_hw_queues;
5176: goto fallback;
5177: }
5178: blk_mq_map_swqueue(q);
5179: }
5180: switch_back:
5181: /* The blk_mq_elv_switch_back unfreezes queue for us. */
5182: list_for_each_entry(q, &set->tag_list, tag_set_list) {
5183: /* switch_back expects queue to be frozen */
5184: if (!queues_frozen)
5185: blk_mq_freeze_queue_nomemsave(q);
5186: blk_mq_elv_switch_back(q, &elv_tbl);
5187: }
5188:
5189: list_for_each_entry(q, &set->tag_list, tag_set_list) {
5190: blk_mq_sysfs_register_hctxs(q);
5191: blk_mq_debugfs_register_hctxs(q);
5192:
5193: blk_mq_remove_hw_queues_cpuhp(q);
5194: blk_mq_add_hw_queues_cpuhp(q);
5195: }
5196:
5197: out_free_ctx:
5198: blk_mq_free_sched_ctx_batch(&elv_tbl);
5199: xa_destroy(&elv_tbl);
5200: memalloc_noio_restore(memflags);
5201:
5202: /* Free the excess tags when nr_hw_queues shrink. */
5203: for (i = set->nr_hw_queues; i < prev_nr_hw_queues; i++)
5204: __blk_mq_free_map_and_rqs(set, i);
5205: }
SourceFile:block/blk-mq.c]
|
| 42/2 |
2026/07/18 00:04 |
llm |
expert |
0m
Model:
Tokens:
input: 38167
output: 40
thoughts: 138
Thoughts: Okay, let me think this through.
**Analyzing the blk-mq Elevator Switch**
Right, so `blk_mq_elv_switch_none` grabs `q->elevator_lock`. That's the first thing I noticed. But where does the counter increment, `q->q_usage_counter(io)`, come into play? Ah, wait, it's not directly in `blk_mq_elv_switch_none`. It's buried a bit deeper. `blk_mq_elv_switch_none` *calls* `elevator_set_none`. Then `elevator_set_none` calls `elevator_change`, which I *know* takes `q->elevator_lock`. Perfect, it all lines up.
Just a quick mental check... does `blk_mq_elv_switch_none` also call `blk_mq_freeze_queue`? No, no it doesn't. No, `blk_mq_elv_switch_none` only calls `elevator_set_none`. Just to be absolutely sure, let's run through it again. Yep. I will double check `blk_mq_elv_switch_none`.
|
| 43/2 |
2026/07/18 00:04 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:block/blk-mq.c Name:blk_mq_elv_switch_none]
Results: map[SourceCode:5063: /*
5064: * Stores elevator name and type in ctx and set current elevator to none.
5065: */
5066: static int blk_mq_elv_switch_none(struct request_queue *q,
5067: struct xarray *elv_tbl)
5068: {
5069: struct elv_change_ctx *ctx;
5070:
5071: lockdep_assert_held_write(&q->tag_set->update_nr_hwq_lock);
5072:
5073: /*
5074: * Accessing q->elevator without holding q->elevator_lock is safe here
5075: * because we're called from nr_hw_queue update which is protected by
5076: * set->update_nr_hwq_lock in the writer context. So, scheduler update/
5077: * switch code (which acquires the same lock in the reader context)
5078: * can't run concurrently.
5079: */
5080: if (q->elevator) {
5081: ctx = xa_load(elv_tbl, q->id);
5082: if (WARN_ON_ONCE(!ctx))
5083: return -ENOENT;
5084:
5085: ctx->name = q->elevator->type->elevator_name;
5086:
5087: /*
5088: * Before we switch elevator to 'none', take a reference to
5089: * the elevator module so that while nr_hw_queue update is
5090: * running, no one can remove elevator module. We'd put the
5091: * reference to elevator module later when we switch back
5092: * elevator.
5093: */
5094: __elevator_get(q->elevator->type);
5095:
5096: /*
5097: * Store elevator type so that we can release the reference
5098: * taken above later.
5099: */
5100: ctx->type = q->elevator->type;
5101: elevator_set_none(q);
5102: }
5103: return 0;
5104: }
SourceFile:block/blk-mq.c]
|
| 44/2 |
2026/07/18 00:04 |
llm |
expert |
0m
Model:
Tokens:
input: 39237
output: 34
thoughts: 15
Thoughts: Here's my take on those thoughts, tailored for an expert audience:
**Elevator State Transitions - A Quick Look**
Okay, so I'm processing this. The function `elevator_set_none` – and I'm assuming we're talking about a typical elevator control system here – *calls* `elevator_change`. That's the key. I need to understand the implications of this direct call chain. `elevator_set_none` is likely a higher-level state-setting function, perhaps intended to indicate a 'no action' or idle state. It's an interesting design choice because that means the responsibility for *actually* implementing the state transition logic is handled down the line by `elevator_change`.
My immediate questions are: What's the state parameter being passed to `elevator_change` from `elevator_set_none`? Is it explicitly a "NONE" state ID, or is it something more subtle, like a specific configuration that results in an implied "NONE" state? Are there any potential timing issues due to this call chain – does the system need to guarantee the transition happens atomically, or can other interrupts/events occur in between? And how does this `elevator_change` function manage all the underlying complexities of the motor control, door open/close, and safety checks? We're going to need to dig deeper into the details of that second function.
|
| 45/2 |
2026/07/18 00:04 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:block/elevator.c Name:elevator_set_none]
Results: map[SourceCode: 762: void elevator_set_none(struct request_queue *q)
763: {
764: struct elv_change_ctx ctx = {
765: .name = "none",
766: };
767: int err;
768:
769: err = elevator_change(q, &ctx);
770: if (err < 0)
771: pr_warn("%s: set none elevator failed %d\n", __func__, err);
772: }
SourceFile:block/elevator.c]
|
| 46/2 |
2026/07/18 00:04 |
llm |
expert |
0m
Model:
Tokens:
input: 39758
output: 32
thoughts: 16
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**My Line of Reasoning with `elevator_change` and Queue Freezing**
Right, so I'm thinking about `elevator_change`. The crucial thing here, the bit that immediately jumps out at me, is that this function *calls* `blk_mq_freeze_queue`. Now, knowing how this all works, that immediately triggers certain assumptions in my head. `elevator_change` is obviously involved in how we manage the block I/O queue, likely in response to some significant change – perhaps a device configuration alteration, a re-evaluation of I/O priorities, or even an elevator algorithm switch. The fact that it then proceeds to call `blk_mq_freeze_queue` implies that whatever the change *is*, it requires us to temporarily halt processing on a specific queue or queues. It's a safeguarding mechanism, a way to ensure data integrity and prevent any race conditions or inconsistent states during the transition. I'm inferring a critical path for I/O operations and the need for a safe pause to enable a critical configuration change.
|
| 47/2 |
2026/07/18 00:04 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:block/elevator.c Name:elevator_change]
Results: map[SourceCode: 650: /*
651: * Switch this queue to the given IO scheduler.
652: */
653: static int elevator_change(struct request_queue *q, struct elv_change_ctx *ctx)
654: {
655: unsigned int memflags;
656: struct blk_mq_tag_set *set = q->tag_set;
657: int ret = 0;
658:
659: lockdep_assert_held(&set->update_nr_hwq_lock);
660:
661: if (strncmp(ctx->name, "none", 4)) {
662: ret = blk_mq_alloc_sched_res(q, ctx->type, &ctx->res,
663: set->nr_hw_queues);
664: if (ret)
665: return ret;
666: }
667:
668: memflags = blk_mq_freeze_queue(q);
669: /*
670: * May be called before adding disk, when there isn't any FS I/O,
671: * so freezing queue plus canceling dispatch work is enough to
672: * drain any dispatch activities originated from passthrough
673: * requests, then no need to quiesce queue which may add long boot
674: * latency, especially when lots of disks are involved.
675: *
676: * Disk isn't added yet, so verifying queue lock only manually.
677: */
678: blk_mq_cancel_work_sync(q);
679: mutex_lock(&q->elevator_lock);
680: if (!(q->elevator && elevator_match(q->elevator->type, ctx->name)))
681: ret = elevator_switch(q, ctx);
682: mutex_unlock(&q->elevator_lock);
683: blk_mq_unfreeze_queue(q, memflags);
684: if (!ret)
685: ret = elevator_change_done(q, ctx);
686:
687: /*
688: * Free sched resource if it's allocated but we couldn't switch elevator.
689: */
690: if (!ctx->new)
691: blk_mq_free_sched_res(&ctx->res, ctx->type, set);
692:
693: return ret;
694: }
SourceFile:block/elevator.c]
|
| 48/2 |
2026/07/18 00:04 |
llm |
expert |
0m
Model:
Tokens:
input: 40772
output: 38
thoughts: 64
Thoughts: Okay, let's break this down.
**Understanding blk_mq_freeze_queue**
So, `blk_mq_freeze_queue(q)`... right, it's getting a handle on `q->q_usage_counter(io)`. My initial thought was that it *acquires* that counter, like a direct grab. But no, it's actually waiting for it to reach zero. That's a crucial distinction. It needs the counter to be zero so no I/O is actively using the queue, so it has a safe state. Ah, but that's not the only thing. It also grabs the lockdep map associated with it. This suggests a form of synchronization or prevention of lock inversions which is really helpful. Okay, let's take a closer look at what `blk_mq_freeze_queue` is actually doing, and what other actions it has. I should refresh my memory on the interactions of those components.
|
| 49/2 |
2026/07/18 00:04 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:block/blk-mq.c Name:blk_mq_freeze_queue]
Results: map[SourceCode: 951: static inline unsigned int __must_check
952: blk_mq_freeze_queue(struct request_queue *q)
953: {
954: unsigned int memflags = memalloc_noio_save();
955:
956: blk_mq_freeze_queue_nomemsave(q);
957: return memflags;
958: }
SourceFile:include/linux/blk-mq.h]
|
| 50/2 |
2026/07/18 00:04 |
llm |
expert |
0m
Model:
Tokens:
input: 41222
output: 42
thoughts: 23
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Unpacking `blk_mq_freeze_queue_nomemsave`**
Alright, let's break this down. My initial thought process is triggered by that function name: `blk_mq_freeze_queue_nomemsave`. Okay, so it’s calling `blk_freeze_queue`. Naturally, my first instinct is to immediately follow that call, understanding that `blk_mq_freeze_queue_nomemsave` must be doing some additional pre or post-processing or configuration around the standard `blk_freeze_queue` function that is the core of this operation. It's almost certain this is an MQ-specific wrapper, but the *why* of that distinction and its interactions with the core freeze function is immediately top of mind. Time to start digging!
|
| 51/2 |
2026/07/18 00:04 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:block/blk-mq.c Name:blk_mq_freeze_queue_nomemsave]
Results: map[SourceCode: 204: void blk_mq_freeze_queue_nomemsave(struct request_queue *q)
205: {
206: blk_freeze_queue_start(q);
207: blk_mq_freeze_queue_wait(q);
208: }
SourceFile:block/blk-mq.c]
|
| 52/2 |
2026/07/18 00:04 |
llm |
expert |
0m
Model:
Tokens:
input: 41579
output: 38
thoughts: 16
Thoughts: Here's the summary, framed as your thoughts:
**Lockdep Conundrums**
Okay, so I'm looking at `blk_freeze_queue_start`. Right off the bat, I see it's dealing with a lockdep map. That immediately flags a red flag in my mind. Lockdep, especially around block I/O, is notoriously tricky. It's a critical mechanism for detecting lock ordering issues, but it's also prone to false positives or, worse, missing real deadlocks. The fact that this function *immediately* grabs a lockdep map makes me wonder what it's trying to protect and how it interacts with other locking hierarchies within the block layer. What’s the scope of the protection here? How deep does this extend? I need to dig deeper into the code that calls this function and the context it operates in to understand the full implications of this lock acquisition. I need to understand if the map is just for internal consistency or something more critical. I need to figure out which structures are covered. This deserves a closer look to be sure there isn't a lurking, latent deadlock scenario waiting to explode.
|
| 53/2 |
2026/07/18 00:04 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:block/blk-mq.c Name:blk_freeze_queue_start]
Results: map[SourceCode: 182: void blk_freeze_queue_start(struct request_queue *q)
183: {
184: if (__blk_freeze_queue_start(q, current))
185: blk_freeze_acquire_lock(q);
186: }
SourceFile:block/blk-mq.c]
|
| 54/2 |
2026/07/18 00:04 |
llm |
expert |
0m
Model:
Tokens:
input: 41979
output: 38
thoughts: 43
Thoughts: Okay, here's my interpretation of those thoughts, summarized from my perspective as someone well-versed in kernel internals:
**Analyzing Block Layer Locking in `blk_freeze_acquire_lock`**
Alright, so `blk_freeze_acquire_lock(q)`... that's where things get interesting. My initial thought is that it's going after the `q->q_usage_counter(io)` lockdep map, which is crucial for handling I/O operations and ensuring proper ordering within the block layer. It immediately flags to me the importance of understanding the acquisition process. I need to dive deeper into how `blk_freeze_acquire_lock` functions. Understanding the specifics of this acquisition is key to figuring out the impact on concurrency and preventing deadlocks within the block device subsystem, especially when dealing with freezes and thaws.
|
| 55/2 |
2026/07/18 00:04 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:block/blk-mq.c Name:blk_freeze_acquire_lock]
Results: map[SourceCode: 754: static inline void blk_freeze_acquire_lock(struct request_queue *q)
755: {
756: if (!q->mq_freeze_disk_dead)
757: rwsem_acquire(&q->io_lockdep_map, 0, 1, _RET_IP_);
758: if (!q->mq_freeze_queue_dying)
759: rwsem_acquire(&q->q_lockdep_map, 0, 1, _RET_IP_);
760: }
SourceFile:block/blk.h]
|
| 56/2 |
2026/07/18 00:04 |
llm |
expert |
0m
Model:
Tokens:
input: 42408
output: 38
thoughts: 1735
Thoughts: **Analyzing a Potential Deadlock in the Block Layer**
Okay, so I've been tracing the lock acquisition sequence and there's a potential cycle I need to investigate. It starts with the acquisition of `q->io_lockdep_map`, which is done during a queue freeze operation with `blk_mq_freeze_queue`. I see that `elevator_change` then acquires `q->elevator_lock`, establishing the first link in the potential chain: `q->q_usage_counter(io)` -> `q->elevator_lock`.
I'm noting that `#1` is `fs_reclaim` -> `q->q_usage_counter(io)`. This is explicitly registered in `blk_alloc_queue`. Specifically: memory reclaim acquires `fs_reclaim`, then `rwsem_acquire_read(&q->io_lockdep_map)`. This helps the lockdep subsystem reason about the locking order.
The complete lock chain I've identified is:
1. `fs_reclaim` -> `q->q_usage_counter(io)` (explicitly registered)
2. `q->q_usage_counter(io)` -> `q->elevator_lock` (in `elevator_change`)
3. `q->elevator_lock` -> `set->srcu` (in `elevator_switch`)
4. `set->srcu` -> `cmd->lock` (in `blk_mq_sched_dispatch_requests`)
5. `cmd->lock` -> `nsock->tx_lock` (in `nbd_queue_rq`)
6. `nsock->tx_lock` -> `cpu_hotplug_lock` (in `nbd_reconnect_socket`)
7. `cpu_hotplug_lock` -> `wq_pool_mutex` (in `padata_alloc`)
8. `wq_pool_mutex` -> `fs_reclaim` (in `__alloc_workqueue`)
My main question is, is this a *real* deadlock? I need to determine if all these locks can actually be held simultaneously in a cyclical manner by different threads.
Let's break it down by simulating scenarios:
* **Scenario 1:** A thread is in `__alloc_workqueue`, holding `wq_pool_mutex`. If memory allocation is needed, `GFP_KERNEL` might trigger memory reclaim, which will attempt to acquire `fs_reclaim`.
* **Scenario 2:** Memory reclaim (holding `fs_reclaim`) tries to freeze a block queue... Okay, the explicit registration is important here. `fs_reclaim` *happens before* the `q->q_usage_counter(io)` in the lockdep map. This seems to suggest a memory reclaim path *could* acquire `q->q_usage_counter(io)` by submitting IO.
* Yes, if memory reclaim submits IO, it *will* acquire `q->q_usage_counter(io)` by calling `blk_queue_enter`.
* **Scenario 3:** `elevator_change` holds `q->q_usage_counter(io)` (via `blk_mq_freeze_queue`), and *then* acquires `q->elevator_lock`. But `blk_mq_freeze_queue` *waits* for `q->q_usage_counter(io)` to drop to zero! It is a percpu_ref. Freezing the queue prevents new IO and waits for existing IO to finish. Then it acquires the lockdep map for write. I need to clarify here...
* `blk_mq_freeze_queue` does not hold `q->q_usage_counter(io)` *while* acquiring `q->elevator_lock`. It freezes the queue, *then* acquires the lock. Importantly, the lockdep map for `q->q_usage_counter(io)` *is held* from `blk_freeze_queue_start` to `blk_mq_unfreeze_queue`.
So: Thread 3, freezing the queue, blocks Thread 2 (memory reclaim). It prevents *new* IO, and then acquires `q->elevator_lock`.
* **Scenario 4:** `elevator_switch` holds `q->elevator_lock`, and calls `synchronize_srcu(set->srcu)`. It waits for all SRCU readers.
* **Scenario 5:** `blk_mq_sched_dispatch_requests` holds `set->srcu` read lock and acquires `cmd->lock`.
* **Scenario 6:** `nbd_queue_rq` holds `
|
| 57/2 |
2026/07/18 00:04 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/jump_label.c Name:static_key_slow_inc]
Results: map[SourceCode: 186: bool static_key_slow_inc(struct static_key *key)
187: {
188: bool ret;
189:
190: cpus_read_lock();
191: ret = static_key_slow_inc_cpuslocked(key);
192: cpus_read_unlock();
193: return ret;
194: }
SourceFile:kernel/jump_label.c]
|
| 58/2 |
2026/07/18 00:04 |
llm |
expert |
0m
Model:
Tokens:
input: 45353
output: 38
thoughts: 978
Thoughts: **Analyzing Potential Deadlock in the NBD Driver**
Okay, so I'm thinking through this locking scenario, and the `cpus_read_lock()` confirms it's a read lock, right? If Thread 7 and Thread 8 both acquire `cpu_hotplug_lock` for reading, they won't block each other directly, that's fine. But wait, lockdep is tracking both read and write locks, so the overall picture is more complex.
Here's the chain I'm sketching out: Thread 7 holds `nsock->tx_lock` and then acquires `cpu_hotplug_lock` in read mode. Thread 8 holds `cpu_hotplug_lock` (read) and then acquires `wq_pool_mutex`. The key question is whether this creates a dangerous dependency: `nsock->tx_lock` becoming, effectively, a dependency for `wq_pool_mutex`.
Lockdep's rule for valid chains involving read locks means that `A (read) -> B` and `C -> A (read)` is okay *unless* there's a potential read-read deadlock, which means contention against a writer. So if a writer, like a CPU hotplug event, tries to write to `cpu_hotplug_lock`, it will block on any readers and then subsequent readers will block on the writer. So the chain `nsock->tx_lock` -> `cpu_hotplug_lock` (read) -> `cpu_hotplug_lock` (write) -> `cpu_hotplug_lock` (read) -> `wq_pool_mutex` is a valid deadlock chain. This is a potential deadlock condition, but the good news is that Lockdep should flag this.
Now, let's concretely check if these locks are actually held simultaneously. Can `nbd_reconnect_socket` be called while holding `nsock->tx_lock`? Let's check the code:
```c
mutex_lock(&nsock->tx_lock);
if (!nsock->dead) {
mutex_unlock(&nsock->tx_lock);
continue;
}
sk_set_memalloc(sock->sk);
```
Yes, it acquires `nsock->tx_lock` and then *does* call `sk_set_memalloc`! That's a valid scenario.
Next, is `nbd_handle_cmd` ever called while holding `cmd->lock`?
```c
mutex_lock(&cmd->lock);
clear_bit(NBD_CMD_REQUEUED, &cmd->flags);
ret = nbd_handle_cmd(cmd, hctx->queue_num);
mutex_unlock(&cmd->lock);
```
Yes, `nbd_queue_rq` holds `cmd->lock` and then calls `nbd_handle_cmd`.
And in `nbd_handle_cmd`:
```c
nsock = config->socks[index];
mutex_lock(&nsock->tx_lock);
```
This confirms `nbd_handle_cmd` acquires `nsock->tx_lock` while holding `cmd->lock`. So `cmd->lock` can potentially be held before `nsock->tx_lock` is acquired.
Now, the final piece: Does `blk_mq_sched_dispatch_requests` hold a lock that could interact with the other two? Specifically, the `set->srcu`? It's called from `blk_mq_run_work_fn`, which wraps it in `__blk_mq_run_dispatch_ops`.
```c
#define __blk_mq_run_dispatch_ops(q, check_sleep, dispatch_ops) \
do { \
if ((q)->tag_set->flags & BLK_MQ_F_BLOCKING) { \
struct blk_mq_tag_set *__tag_set = (q)->tag_set; \
int srcu_idx; \
\
might_sleep_if(check_sleep); \
srcu_idx = srcu_read_lock(__tag_set->srcu); \
(dispatch_ops); \
srcu_read_unlock(__tag_set->srcu, srcu_idx); \
} else { \
rcu_read_lock(); \
(dispatch_ops); \
rcu_read_unlock(); \
} \
} while (0)
```
And yes, if `BLK_MQ_F_BLOCKING`
|
| 59/2 |
2026/07/18 00:05 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/block/nbd.c Name:nbd_dev_add]
Results: map[SourceCode:1944: static struct nbd_device *nbd_dev_add(int index, unsigned int refs)
1945: {
1946: struct queue_limits lim = {
1947: .max_hw_sectors = 65536,
1948: .io_opt = 256 << SECTOR_SHIFT,
1949: .max_segments = USHRT_MAX,
1950: .max_segment_size = UINT_MAX,
1951: };
1952: struct nbd_device *nbd;
1953: struct gendisk *disk;
1954: int err = -ENOMEM;
1955:
1956: nbd = kzalloc_obj(struct nbd_device);
1957: if (!nbd)
1958: goto out;
1959:
1960: nbd->tag_set.ops = &nbd_mq_ops;
1961: nbd->tag_set.nr_hw_queues = 1;
1962: nbd->tag_set.queue_depth = 128;
1963: nbd->tag_set.numa_node = NUMA_NO_NODE;
1964: nbd->tag_set.cmd_size = sizeof(struct nbd_cmd);
1965: nbd->tag_set.flags = BLK_MQ_F_BLOCKING;
1966: nbd->tag_set.driver_data = nbd;
1967: INIT_WORK(&nbd->remove_work, nbd_dev_remove_work);
1968: nbd->backend = NULL;
1969:
1970: err = blk_mq_alloc_tag_set(&nbd->tag_set);
1971: if (err)
1972: goto out_free_nbd;
1973:
1974: mutex_lock(&nbd_index_mutex);
1975: if (index >= 0) {
1976: err = idr_alloc(&nbd_index_idr, nbd, index, index + 1,
1977: GFP_KERNEL);
1978: if (err == -ENOSPC)
1979: err = -EEXIST;
1980: } else {
1981: err = idr_alloc(&nbd_index_idr, nbd, 0,
1982: (MINORMASK >> part_shift) + 1, GFP_KERNEL);
1983: if (err >= 0)
1984: index = err;
1985: }
1986: nbd->index = index;
1987: mutex_unlock(&nbd_index_mutex);
1988: if (err < 0)
1989: goto out_free_tags;
1990:
1991: disk = blk_mq_alloc_disk(&nbd->tag_set, &lim, NULL);
1992: if (IS_ERR(disk)) {
1993: err = PTR_ERR(disk);
1994: goto out_free_idr;
1995: }
1996: nbd->disk = disk;
1997:
1998: nbd->recv_workq = alloc_workqueue("nbd%d-recv",
1999: WQ_MEM_RECLAIM | WQ_HIGHPRI |
2000: WQ_UNBOUND, 0, nbd->index);
2001: if (!nbd->recv_workq) {
2002: dev_err(disk_to_dev(nbd->disk), "Could not allocate knbd recv work queue.\n");
2003: err = -ENOMEM;
2004: goto out_err_disk;
2005: }
2006:
2007: mutex_init(&nbd->config_lock);
2008: refcount_set(&nbd->config_refs, 0);
2009: /*
2010: * Start out with a zero references to keep other threads from using
2011: * this device until it is fully initialized.
2012: */
2013: refcount_set(&nbd->refs, 0);
2014: INIT_LIST_HEAD(&nbd->list);
2015: disk->major = NBD_MAJOR;
2016: disk->first_minor = index << part_shift;
2017: disk->minors = 1 << part_shift;
2018: disk->fops = &nbd_fops;
2019: disk->private_data = nbd;
2020: sprintf(disk->disk_name, "nbd%d", index);
2021: err = add_disk(disk);
2022: if (err)
2023: goto out_free_work;
2024:
2025: /*
2026: * Now publish the device.
2027: */
2028: refcount_set(&nbd->refs, refs);
2029: nbd_total_devices++;
2030: return nbd;
2031:
2032: out_free_work:
2033: destroy_workqueue(nbd->recv_workq);
2034: out_err_disk:
2035: put_disk(disk);
2036: out_free_idr:
2037: mutex_lock(&nbd_index_mutex);
2038: idr_remove(&nbd_index_idr, index);
2039: mutex_unlock(&nbd_index_mutex);
2040: out_free_tags:
2041: blk_mq_free_tag_set(&nbd->tag_set);
2042: out_free_nbd:
2043: kfree(nbd);
2044: out:
2045: return ERR_PTR(err);
2046: }
SourceFile:drivers/block/nbd.c]
|
| 60/2 |
2026/07/18 00:05 |
llm |
expert |
0m
Model:
Tokens:
input: 49077
output: 22
thoughts: 2087
Thoughts: **Deadlock Analysis in NBD Driver**
Okay, let's break this down. First, the `nbd->tag_set.flags = BLK_MQ_F_BLOCKING;` is set as expected, indicating blocking behavior. So the `srcu` read lock through `blk_mq_run_work_fn` is correctly synchronized. Now, a crucial part of the code is that `elevator_switch` calls `blk_mq_quiesce_queue` which then calls `synchronize_srcu(set->srcu)`, which waits for all SRCU readers to finish. This seems fine, `set->srcu` to `cmd->lock` looks okay. Then `q->elevator_lock` wait for `set->srcu` which seems fine.
Here is where the fun starts. I'm seeing a possible deadlock scenario. I've got to follow the threads of execution carefully here. Thread 4 might be holding `q->elevator_lock` and waiting for `set->srcu` readers to finish. Concurrently, Thread 5 has `set->srcu` (read) and is waiting for `cmd->lock`. Then Thread 6 holds `cmd->lock` and is blocked on `nsock->tx_lock`. And Thread 7 has `nsock->tx_lock` and is blocked on `cpu_hotplug_lock` (read). Then Thread 8 is holding `cpu_hotplug_lock` (read) and blocked on `wq_pool_mutex`, and Thread 1 has `wq_pool_mutex` and is waiting for `fs_reclaim`. Finally, Thread 2 holds `fs_reclaim` and is blocked on `q->q_usage_counter(io)`. Thread 3 waits for `q->q_usage_counter(io)` to drop to zero and then attempts `q->elevator_lock`.
Initially, it seemed Thread 3 wouldn't block Thread 4 because it waits for the counter to go down before acquiring the lock. However, looking at the lockdep messages, it seems `q->q_usage_counter(io)` is acquired during `mutex_lock(&q->elevator_lock)`. That's because `blk_mq_freeze_queue` is acquiring lockdep maps and it doesn't release it until `blk_mq_unfreeze_queue`. Therefore, `q->q_usage_counter(io)` is held when `q->elevator_lock` is acquired from a lockdep point of view.
It looks like the deadlock is very real. If Thread 2 (holding `fs_reclaim`) tries to acquire `q->q_usage_counter(io)` by calling `blk_queue_enter`, it will block if `blk_mq_freeze_queue` (Thread 3) is currently executing, which does set `q->mq_freeze_depth > 0`. So Thread 3 *does* block Thread 2.
Let's retrace our steps. Thread 3 freezes the queue. Existing IO (Thread 5) holds `q->q_usage_counter(io)` and is blocked on `cmd->lock`, because Thread 6 holds `cmd->lock` and is blocked on `nsock->tx_lock`. Now, let's pause... Thread 7 is acquiring `cpu_hotplug_lock` for *read*. It will *only* block if there is a writer. So if Thread 9 is a writer waiting on `cpu_hotplug_lock` then this *is* a blocker. Thread 9 is waiting for existing readers to finish, namely Thread 8. And Thread 8 is holding `cpu_hotplug_lock` (read) and waiting on `wq_pool_mutex`. Thread 1 has `wq_pool_mutex` and blocked on `fs_reclaim`. And Thread 2 is holding `fs_reclaim` and is now blocked on `q->q_usage_counter(io)`.
Okay, there's a cyclical dependency. It is a genuine deadlock.
Let's verify: Thread 1 holds `wq_pool_mutex`, calls `kzalloc`, enters memory reclaim, holds `fs_reclaim`, and *then* blocks on `q->q_usage_counter(io)` because Thread 2 is already there. Wait, I think I'm seeing it. Thread 1 has `wq_pool_mutex` and `fs_reclaim`, and enters `blk_queue_enter`, but blocks on `q->q_usage_counter(io)` because the queue is frozen. Thread 3, `elevator_change` freezes the queue. Thread 5 currently holds `q->q_usage_counter(io)` and executes `blk_mq_sched_
|
| 61/2 |
2026/07/18 00:05 |
tool |
codesearch-find-references |
0m
Args:
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:pcrypt_init_padata SourceFile:crypto/pcrypt.c SourceLine:335] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__UNIQUE_ID_addressable_padata_alloc_777 SourceFile:kernel/padata.c SourceLine:1015] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__gendwarfksyms_ptr_padata_alloc SourceFile:kernel/padata.c SourceLine:1015] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__gendwarfksyms_ptr_padata_alloc SourceFile:kernel/padata.c SourceLine:1015]] TruncatedOutput:false]
|
| 62/2 |
2026/07/18 00:05 |
llm |
expert |
0m
Model:
Tokens:
input: 52473
output: 44
thoughts: 1171
Thoughts: **Deadlock Analysis: A Path to Denial of Service**
Okay, let's break this down. My initial thought process focused on the origins of `padata_alloc`. It's only called from `pcrypt_init_padata`, which itself is only called from `pcrypt_init`. And `pcrypt_init` is an initcall, meaning it happens at boot. That would mean Thread 8 would be only relevant to boot. However, the use of `wq_pool_mutex` in `alloc_workqueue` makes me think there might be more to it.
I then started to think about cross-dependencies between `cpu_hotplug_lock` and `wq_pool_mutex`. The lockdep information recorded at boot is probably relevant. I was looking for a thread holding `cpu_hotplug_lock` and blocking on `wq_pool_mutex`, or vice-versa, or some indirect dependency. Then the thought struck: can `pcrypt_init` happen at runtime? It's a module init function! If `pcrypt` is compiled as a module, loading it at runtime *will* call `pcrypt_init`, potentially triggering the problematic lock acquisition during runtime. This could be a significant vulnerability. If it's built-in, it only happens at boot.
Next, I looked at a possible alternative route through `wq_pool_mutex` -> `fs_reclaim`. `__alloc_workqueue` acquires `wq_pool_mutex`, then calls `alloc_and_link_pwqs`, which uses `GFP_KERNEL` memory allocation. That means `__alloc_workqueue` is called every time a workqueue is created.
I figured out that the user *can* create workqueues, e.g. when creating a loop device, opening some devices, etc. Looking at the syzbot report, I found `__alloc_workqueue` is called from `ib_mad_port_open` -> `ib_mad_init_device` -> `add_client_context` -> `enable_device_and_get` -> `ib_register_device` -> `rxe_register_device` -> `rxe_net_add` -> `rxe_newlink` -> `nldev_newlink` -> `rdma_nl_rcv_msg`. So the user *can* trigger `__alloc_workqueue` by creating an RDMA link via netlink! Thread 1 is now fully user-controllable.
Let's review the deadlock cycle. Thread 1, a user-controlled thread, creates an RDMA link. This causes it to acquire `wq_pool_mutex` and enter memory reclaim (`fs_reclaim`). Then Thread 1 blocks on `q->q_usage_counter(io)` (e.g., waiting for writes to a swap file or a loop device backed by NBD). The key is the interaction of memory reclaim with NBD. If the system is under memory pressure, reclaim might try to write dirty pages to a block device, and if that block device is NBD, it may call `blk_queue_enter`, which will block the NBD queue if it is frozen.
Thread 3 (also user-controlled) changes the elevator for NBD. This calls `blk_mq_freeze_queue`, which sets `q->mq_freeze_depth > 0` and waits for `q->q_usage_counter(io)` to drop to zero, blocking Thread 1.
Thread 5 (NBD worker or user) submits IO to NBD, thus holds `q->q_usage_counter(io)`, then calls `nbd_queue_rq` which will block on `nsock->tx_lock`.
Thread 7 (User) reconfigures NBD via netlink. This causes a call to `nbd_reconnect_socket`, which acquires `nsock->tx_lock`. Then it calls `sk_set_memalloc`, which blocks on `cpu_hotplug_lock` (read) because a writer is waiting.
Thread 9 (User/System) performs a CPU hotplug operation and blocks on `cpu_hotplug_lock` (write), because Thread 8 is holding it for read.
Thread 8 (User) loads the `pcrypt` module and holds `cpu_hotplug_lock` (read) and blocks on `wq_pool_mutex` (held by Thread 1).
This is a valid deadlock! A classic textbook scenario. The interesting question becomes exploitability. This represents a Denial of Service (DoS) vulnerability. Now I need to determine if an unprivileged user can trigger this. What capabilities are required for the operations? I need to focus on `nldev_newlink`, the create RD
|
| 63/2 |
2026/07/18 00:05 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/infiniband/core/nldev.c Name:nldev_newlink]
Results: map[SourceCode:1776: static int nldev_newlink(struct sk_buff *skb, struct nlmsghdr *nlh,
1777: struct netlink_ext_ack *extack)
1778: {
1779: struct nlattr *tb[RDMA_NLDEV_ATTR_MAX];
1780: char ibdev_name[IB_DEVICE_NAME_MAX];
1781: const struct rdma_link_ops *ops;
1782: char ndev_name[IFNAMSIZ];
1783: struct net_device *ndev;
1784: char type[IFNAMSIZ];
1785: int err;
1786:
1787: err = nlmsg_parse(nlh, 0, tb, RDMA_NLDEV_ATTR_MAX - 1,
1788: nldev_policy, extack);
1789: if (err || !tb[RDMA_NLDEV_ATTR_DEV_NAME] ||
1790: !tb[RDMA_NLDEV_ATTR_LINK_TYPE] || !tb[RDMA_NLDEV_ATTR_NDEV_NAME])
1791: return -EINVAL;
1792:
1793: nla_strscpy(ibdev_name, tb[RDMA_NLDEV_ATTR_DEV_NAME],
1794: sizeof(ibdev_name));
1795: if (strchr(ibdev_name, '%') || strlen(ibdev_name) == 0)
1796: return -EINVAL;
1797:
1798: nla_strscpy(type, tb[RDMA_NLDEV_ATTR_LINK_TYPE], sizeof(type));
1799: nla_strscpy(ndev_name, tb[RDMA_NLDEV_ATTR_NDEV_NAME],
1800: sizeof(ndev_name));
1801:
1802: ndev = dev_get_by_name(sock_net(skb->sk), ndev_name);
1803: if (!ndev)
1804: return -ENODEV;
1805:
1806: down_read(&link_ops_rwsem);
1807: ops = link_ops_get(type);
1808: #ifdef CONFIG_MODULES
1809: if (!ops) {
1810: up_read(&link_ops_rwsem);
1811: request_module("rdma-link-%s", type);
1812: down_read(&link_ops_rwsem);
1813: ops = link_ops_get(type);
1814: }
1815: #endif
1816: err = ops ? ops->newlink(ibdev_name, ndev) : -EINVAL;
1817: up_read(&link_ops_rwsem);
1818: dev_put(ndev);
1819:
1820: return err;
1821: }
SourceFile:drivers/infiniband/core/nldev.c]
|
| 64/2 |
2026/07/18 00:05 |
llm |
expert |
0m
Model:
Tokens:
input: 55561
output: 24
thoughts: 38
Thoughts: Okay, let me think this through.
**Analyzing the Netlink Handler: nldev_newlink**
Right, `nldev_newlink`. That name strongly suggests it's a netlink handler, and specifically one dealing with new links. Now, my immediate thought is: what are the security implications? More specifically, does this handler require `CAP_NET_ADMIN` to execute? That's the critical question I need to answer. I need to understand the potential attack surface. Okay, the next logical step is to dive into the kernel code and see how `nldev_newlink` is actually registered. I'll trace its registration path. I'll need to confirm any necessary permissions. This information is key to understanding whether this can be abused to perform privilege escalation.
|
| 65/2 |
2026/07/18 00:05 |
tool |
codesearch-find-references |
0m
Args:
Results: map[References:[map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:nldev_cb_table SourceFile:drivers/infiniband/core/nldev.c SourceLine:2950]] TruncatedOutput:false]
|
| 66/2 |
2026/07/18 00:05 |
llm |
expert |
0m
Model:
Tokens:
input: 55875
output: 45
thoughts: 13
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Analyzing the `nldev_cb_table`**
Alright, let's dive in. My immediate thought is to examine the `nldev_cb_table`. Given my experience, that table is likely a critical data structure, probably a callback table associated with network device management. I need to understand its structure, contents, and how it's used within the broader netlink framework. I'm assuming it holds function pointers or descriptors that handle events related to network devices, like link state changes, address configuration, or even interface registration/deregistration.
My brain's already firing off questions: Is it a simple array, a hash table, or something more complex? What are the key fields within each entry? Are there any locking mechanisms used to protect concurrent access? I'll probably start by looking at its definition (in the code, of course) to confirm my assumptions and get a handle on the data types involved. From there, I'll trace how this table gets populated, how the callbacks are invoked, and under what conditions. Understanding the interaction between this table and the rest of the netlink stack is key. Any potential race conditions or performance bottlenecks? I'll be keeping a sharp eye out for those. Let's dig in and see what we can find.
|
| 67/2 |
2026/07/18 00:05 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/infiniband/core/nldev.c Name:nldev_cb_table]
Results: map[SourceCode:2937: static const struct rdma_nl_cbs nldev_cb_table[RDMA_NLDEV_NUM_OPS] = {
2938: [RDMA_NLDEV_CMD_GET] = {
2939: .doit = nldev_get_doit,
2940: .dump = nldev_get_dumpit,
2941: },
2942: [RDMA_NLDEV_CMD_GET_CHARDEV] = {
2943: .doit = nldev_get_chardev,
2944: },
2945: [RDMA_NLDEV_CMD_SET] = {
2946: .doit = nldev_set_doit,
2947: .flags = RDMA_NL_ADMIN_PERM,
2948: },
2949: [RDMA_NLDEV_CMD_NEWLINK] = {
2950: .doit = nldev_newlink,
2951: .flags = RDMA_NL_ADMIN_PERM,
2952: },
2953: [RDMA_NLDEV_CMD_DELLINK] = {
2954: .doit = nldev_dellink,
2955: .flags = RDMA_NL_ADMIN_PERM,
2956: },
2957: [RDMA_NLDEV_CMD_PORT_GET] = {
2958: .doit = nldev_port_get_doit,
2959: .dump = nldev_port_get_dumpit,
2960: },
2961: [RDMA_NLDEV_CMD_RES_GET] = {
2962: .doit = nldev_res_get_doit,
2963: .dump = nldev_res_get_dumpit,
2964: },
2965: [RDMA_NLDEV_CMD_RES_QP_GET] = {
2966: .doit = nldev_res_get_qp_doit,
2967: .dump = nldev_res_get_qp_dumpit,
2968: },
2969: [RDMA_NLDEV_CMD_RES_CM_ID_GET] = {
2970: .doit = nldev_res_get_cm_id_doit,
2971: .dump = nldev_res_get_cm_id_dumpit,
2972: },
2973: [RDMA_NLDEV_CMD_RES_CQ_GET] = {
2974: .doit = nldev_res_get_cq_doit,
2975: .dump = nldev_res_get_cq_dumpit,
2976: },
2977: [RDMA_NLDEV_CMD_RES_MR_GET] = {
2978: .doit = nldev_res_get_mr_doit,
2979: .dump = nldev_res_get_mr_dumpit,
2980: },
2981: [RDMA_NLDEV_CMD_RES_PD_GET] = {
2982: .doit = nldev_res_get_pd_doit,
2983: .dump = nldev_res_get_pd_dumpit,
2984: },
2985: [RDMA_NLDEV_CMD_RES_CTX_GET] = {
2986: .doit = nldev_res_get_ctx_doit,
2987: .dump = nldev_res_get_ctx_dumpit,
2988: },
2989: [RDMA_NLDEV_CMD_RES_SRQ_GET] = {
2990: .doit = nldev_res_get_srq_doit,
2991: .dump = nldev_res_get_srq_dumpit,
2992: },
2993: [RDMA_NLDEV_CMD_SYS_GET] = {
2994: .doit = nldev_sys_get_doit,
2995: },
2996: [RDMA_NLDEV_CMD_SYS_SET] = {
2997: .doit = nldev_set_sys_set_doit,
2998: .flags = RDMA_NL_ADMIN_PERM,
2999: },
3000: [RDMA_NLDEV_CMD_STAT_SET] = {
3001: .doit = nldev_stat_set_doit,
3002: .flags = RDMA_NL_ADMIN_PERM,
3003: },
3004: [RDMA_NLDEV_CMD_STAT_GET] = {
3005: .doit = nldev_stat_get_doit,
3006: .dump = nldev_stat_get_dumpit,
3007: },
3008: [RDMA_NLDEV_CMD_STAT_DEL] = {
3009: .doit = nldev_stat_del_doit,
3010: .flags = RDMA_NL_ADMIN_PERM,
3011: },
3012: [RDMA_NLDEV_CMD_RES_QP_GET_RAW] = {
3013: .doit = nldev_res_get_qp_raw_doit,
3014: .dump = nldev_res_get_qp_raw_dumpit,
3015: .flags = RDMA_NL_ADMIN_PERM,
3016: },
3017: [RDMA_NLDEV_CMD_RES_CQ_GET_RAW] = {
3018: .doit = nldev_res_get_cq_raw_doit,
3019: .dump = nldev_res_get_cq_raw_dumpit,
3020: .flags = RDMA_NL_ADMIN_PERM,
3021: },
3022: [RDMA_NLDEV_CMD_RES_MR_GET_RAW] = {
3023: .doit = nldev_res_get_mr_raw_doit,
3024: .dump = nldev_res_get_mr_raw_dumpit,
3025: .flags = RDMA_NL_ADMIN_PERM,
3026: },
3027: [RDMA_NLDEV_CMD_RES_SRQ_GET_RAW] = {
3028: .doit = nldev_res_get_srq_raw_doit,
3029: .dump = nldev_res_get_srq_raw_dumpit,
3030: .flags = RDMA_NL_ADMIN_PERM,
3031: },
3032: [RDMA_NLDEV_CMD_STAT_GET_STATUS] = {
3033: .doit = nldev_stat_get_counter_status_doit,
3034: },
3035: [RDMA_NLDEV_CMD_NEWDEV] = {
3036: .doit = nldev_newdev,
3037: .flags = RDMA_NL_ADMIN_PERM,
3038: },
3039: [RDMA_NLDEV_CMD_DELDEV] = {
3040: .doit = nldev_deldev,
3041: .flags = RDMA_NL_ADMIN_PERM,
3042: },
3043: [RDMA_NLDEV_CMD_FRMR_POOLS_GET] = {
3044: .dump = nldev_frmr_pools_get_dumpit,
3045: },
3046: [RDMA_NLDEV_CMD_FRMR_POOLS_SET] = {
3047: .doit = nldev_frmr_pools_set_doit,
3048: .flags = RDMA_NL_ADMIN_PERM,
3049: },
3050: };
SourceFile:drivers/infiniband/core/nldev.c]
|
| 68/2 |
2026/07/18 00:05 |
llm |
expert |
0m
Model:
Tokens:
input: 58426
output: 46
thoughts: 44
Thoughts: Okay, here's my interpretation of those thoughts, summarized from my perspective as an expert in the field:
**Internal Review of RDMA Permission Checks**
Alright, so the system indicates that `RDMA_NL_ADMIN_PERM` is set when processing `RDMA_NLDEV_CMD_NEWLINK`. That’s good, it suggests the intended permissions are being enforced. But... *why* is it set? What specific checks are being done when `RDMA_NL_ADMIN_PERM` is evaluated? I need to understand the underlying logic. I'm going to have to dig into the code and determine exactly what criteria trigger that permission check to be successful. Is it verifying the user's capabilities, their group membership, or something more nuanced like the context of the requested operation? Knowing the specifics of what that permission check entails is crucial for understanding the security posture of the RDMA device management. I need to ensure there are no unexpected loopholes or permission escalation paths. Understanding the nuances of this permission is crucial.
|
| 69/2 |
2026/07/18 00:05 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/infiniband/core/netlink.c Name:rdma_nl_rcv_msg]
Results: map[SourceCode: 152: static int rdma_nl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh,
153: struct netlink_ext_ack *extack)
154: {
155: int type = nlh->nlmsg_type;
156: unsigned int index = RDMA_NL_GET_CLIENT(type);
157: unsigned int op = RDMA_NL_GET_OP(type);
158: const struct rdma_nl_cbs *cb_table;
159: int err = -EINVAL;
160:
161: if (!is_nl_msg_valid(index, op))
162: return -EINVAL;
163:
164: down_read(&rdma_nl_types[index].sem);
165: cb_table = get_cb_table(skb, index, op);
166: if (!cb_table)
167: goto done;
168:
169: if ((cb_table[op].flags & RDMA_NL_ADMIN_PERM) &&
170: !netlink_capable(skb, CAP_NET_ADMIN)) {
171: err = -EPERM;
172: goto done;
173: }
174:
175: /*
176: * LS responses overload the 0x100 (NLM_F_ROOT) flag. Don't
177: * mistakenly call the .dump() function.
178: */
179: if (index == RDMA_NL_LS) {
180: if (cb_table[op].doit)
181: err = cb_table[op].doit(skb, nlh, extack);
182: goto done;
183: }
184: /* FIXME: Convert IWCM to properly handle doit callbacks */
185: if ((nlh->nlmsg_flags & NLM_F_DUMP) || index == RDMA_NL_IWCM) {
186: struct netlink_dump_control c = {
187: .dump = cb_table[op].dump,
188: };
189: if (c.dump)
190: err = netlink_dump_start(skb->sk, skb, nlh, &c);
191: goto done;
192: }
193:
194: if (cb_table[op].doit)
195: err = cb_table[op].doit(skb, nlh, extack);
196: done:
197: up_read(&rdma_nl_types[index].sem);
198: return err;
199: }
SourceFile:drivers/infiniband/core/netlink.c]
|
| 70/2 |
2026/07/18 00:05 |
llm |
expert |
0m
Model:
Tokens:
input: 59548
output: 40
thoughts: 182
Thoughts: **Analyzing Network Namespace Capabilities**
Okay, so I'm looking at this code and it's using `netlink_capable(skb, CAP_NET_ADMIN)`. Right, that's the check, so you *need* `CAP_NET_ADMIN` to trigger the `nldev_newlink` function. But here's the crucial question: where is this capability *checked*? Is it the *initial* user namespace, or the *current* one?
Let's dig deeper... Ah, `netlink_capable` calls `netlink_ns_capable(skb, sock_net(skb->sk)->user_ns, cap)`. That's the key! It's checking `CAP_NET_ADMIN` *within* the network namespace's *user namespace*. This has interesting implications for privilege escalation.
What this *means* is that an unprivileged user can create a new user namespace. Then, they can go ahead and create a new network namespace within *that* user namespace. And, crucially, in that newly minted network namespace, they *will* have `CAP_NET_ADMIN`. So, it's totally accessible from user namespaces! This is a potential vulnerability.
Now, let me just check another function quickly. What about `nbd_genl_reconfigure`? Does *it* require any capabilities? I should see if there are any other similar checks to assess.
|
| 71/2 |
2026/07/18 00:05 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/block/nbd.c Name:nbd_genl_reconfigure]
Results: map[SourceCode:2347: static int nbd_genl_reconfigure(struct sk_buff *skb, struct genl_info *info)
2348: {
2349: struct nbd_device *nbd = NULL;
2350: struct nbd_config *config;
2351: int index;
2352: int ret = 0;
2353: bool put_dev = false;
2354:
2355: if (!netlink_capable(skb, CAP_SYS_ADMIN))
2356: return -EPERM;
2357:
2358: if (GENL_REQ_ATTR_CHECK(info, NBD_ATTR_INDEX)) {
2359: pr_err("must specify a device to reconfigure\n");
2360: return -EINVAL;
2361: }
2362: index = nla_get_u32(info->attrs[NBD_ATTR_INDEX]);
2363: mutex_lock(&nbd_index_mutex);
2364: nbd = idr_find(&nbd_index_idr, index);
2365: if (!nbd) {
2366: mutex_unlock(&nbd_index_mutex);
2367: pr_err("couldn't find a device at index %d\n", index);
2368: return -EINVAL;
2369: }
2370: if (nbd->backend) {
2371: if (info->attrs[NBD_ATTR_BACKEND_IDENTIFIER]) {
2372: if (nla_strcmp(info->attrs[NBD_ATTR_BACKEND_IDENTIFIER],
2373: nbd->backend)) {
2374: mutex_unlock(&nbd_index_mutex);
2375: dev_err(nbd_to_dev(nbd),
2376: "backend image doesn't match with %s\n",
2377: nbd->backend);
2378: return -EINVAL;
2379: }
2380: } else {
2381: mutex_unlock(&nbd_index_mutex);
2382: dev_err(nbd_to_dev(nbd), "must specify backend\n");
2383: return -EINVAL;
2384: }
2385: }
2386: if (!refcount_inc_not_zero(&nbd->refs)) {
2387: mutex_unlock(&nbd_index_mutex);
2388: pr_err("device at index %d is going down\n", index);
2389: return -EINVAL;
2390: }
2391: mutex_unlock(&nbd_index_mutex);
2392:
2393: config = nbd_get_config_unlocked(nbd);
2394: if (!config) {
2395: dev_err(nbd_to_dev(nbd),
2396: "not configured, cannot reconfigure\n");
2397: nbd_put(nbd);
2398: return -EINVAL;
2399: }
2400:
2401: mutex_lock(&nbd->config_lock);
2402: if (!test_bit(NBD_RT_BOUND, &config->runtime_flags) ||
2403: !nbd->pid) {
2404: dev_err(nbd_to_dev(nbd),
2405: "not configured, cannot reconfigure\n");
2406: ret = -EINVAL;
2407: goto out;
2408: }
2409:
2410: ret = nbd_genl_size_set(info, nbd);
2411: if (ret)
2412: goto out;
2413:
2414: if (info->attrs[NBD_ATTR_TIMEOUT])
2415: nbd_set_cmd_timeout(nbd,
2416: nla_get_u64(info->attrs[NBD_ATTR_TIMEOUT]));
2417: if (info->attrs[NBD_ATTR_DEAD_CONN_TIMEOUT]) {
2418: config->dead_conn_timeout =
2419: nla_get_u64(info->attrs[NBD_ATTR_DEAD_CONN_TIMEOUT]);
2420: config->dead_conn_timeout *= HZ;
2421: }
2422: if (info->attrs[NBD_ATTR_CLIENT_FLAGS]) {
2423: u64 flags = nla_get_u64(info->attrs[NBD_ATTR_CLIENT_FLAGS]);
2424: if (flags & NBD_CFLAG_DESTROY_ON_DISCONNECT) {
2425: if (!test_and_set_bit(NBD_DESTROY_ON_DISCONNECT,
2426: &nbd->flags))
2427: put_dev = true;
2428: } else {
2429: if (test_and_clear_bit(NBD_DESTROY_ON_DISCONNECT,
2430: &nbd->flags))
2431: refcount_inc(&nbd->refs);
2432: }
2433:
2434: if (flags & NBD_CFLAG_DISCONNECT_ON_CLOSE) {
2435: set_bit(NBD_RT_DISCONNECT_ON_CLOSE,
2436: &config->runtime_flags);
2437: } else {
2438: clear_bit(NBD_RT_DISCONNECT_ON_CLOSE,
2439: &config->runtime_flags);
2440: }
2441: }
2442:
2443: if (info->attrs[NBD_ATTR_SOCKETS]) {
2444: struct nlattr *attr;
2445: int rem, fd;
2446:
2447: nla_for_each_nested(attr, info->attrs[NBD_ATTR_SOCKETS],
2448: rem) {
2449: struct nlattr *socks[NBD_SOCK_MAX+1];
2450:
2451: if (nla_type(attr) != NBD_SOCK_ITEM) {
2452: pr_err("socks must be embedded in a SOCK_ITEM attr\n");
2453: ret = -EINVAL;
2454: goto out;
2455: }
2456: ret = nla_parse_nested_deprecated(socks, NBD_SOCK_MAX,
2457: attr,
2458: nbd_sock_policy,
2459: info->extack);
2460: if (ret != 0) {
2461: pr_err("error processing sock list\n");
2462: ret = -EINVAL;
2463: goto out;
2464: }
2465: if (!socks[NBD_SOCK_FD])
2466: continue;
2467: fd = (int)nla_get_u32(socks[NBD_SOCK_FD]);
2468: ret = nbd_reconnect_socket(nbd, fd);
2469: if (ret) {
2470: if (ret == -ENOSPC)
2471: ret = 0;
2472: goto out;
2473: }
2474: dev_info(nbd_to_dev(nbd), "reconnected socket\n");
2475: }
2476: }
2477: out:
2478: mutex_unlock(&nbd->config_lock);
2479: nbd_config_put(nbd);
2480: nbd_put(nbd);
2481: if (put_dev)
2482: nbd_put(nbd);
2483: return ret;
2484: }
SourceFile:drivers/block/nbd.c]
|
| 72/2 |
2026/07/18 00:05 |
llm |
expert |
0m
Model:
Tokens:
input: 62295
output: 21
thoughts: 467
Thoughts: **Analyzing the Security Implications of NBD Reconfiguration**
Okay, so I'm digging into this `nbd_genl_reconfigure` function and trying to understand its security context. My initial thought was, "It's checking `netlink_capable(skb, CAP_SYS_ADMIN)`, so it should require `CAP_SYS_ADMIN`." That would mean the network namespace's user namespace needs that capability. This is where it gets interesting because an unprivileged user can create a user namespace and a network namespace, and boom, they'd have `CAP_SYS_ADMIN` within that namespace. My first assessment was that it would be user namespace accessible.
But then I had to step back and clarify, does `nbd_genl_reconfigure` need `CAP_SYS_ADMIN` in the *initial* user namespace, not just the network namespace? I realized that the `netlink_capable` function actually checks `netlink_ns_capable(skb, sock_net(skb->sk)->user_ns, cap)`. So, it's checking `CAP_SYS_ADMIN` in the user namespace that *owns* the network namespace of the netlink socket. Bingo, that user can create the network namespace. That user then has the capability in its user namespace. Therefore, they can trigger `nbd_genl_reconfigure`.
Now I got side-tracked by `elevator_change`. The question, of course, becomes how can a user trigger this? My first instinct was `/sys/block/nbd0/queue/scheduler`. Writing to sysfs... is it root? No, not necessarily. But does it require `CAP_SYS_ADMIN`? I know that's not always true. What if this file is owned by root? Can an unprivileged user in a user namespace write to `/sys/block/nbd0/queue/scheduler`? I know that sysfs files are usually `root:root` in the initial user namespace. When a user namespace is created, they are mapped to root within the namespace. But those sysfs files are still owned by the *global* root, aren't they? Unless the file is explicitly chowned, or the user namespace mounts a new sysfs, which isn't allowed for block devices because they are not namespaced. Therefore, an unprivileged user CANNOT write to that sysfs file, even in a user namespace.
So, is there another way to trigger `elevator_change`? I need to check the callers of `elevator_change`.
|
| 73/2 |
2026/07/18 00:06 |
tool |
codesearch-find-references |
0m
Args: map[Name:elevator_change]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:elevator_set_default SourceFile:block/elevator.c SourceLine:754] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:elevator_set_none SourceFile:block/elevator.c SourceLine:769] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:elv_iosched_store SourceFile:block/elevator.c SourceLine:821]] TruncatedOutput:false]
|
| 74/2 |
2026/07/18 00:06 |
llm |
expert |
0m
Model:
Tokens:
input: 63487
output: 30
thoughts: 92
Thoughts: Here's my thought process, distilled:
**Tracing the Queue Management Call Chain**
Okay, so I'm digging into how the block layer handles hardware queue updates. I've got `elevator_set_none` as the starting point. It's clearly being called from `blk_mq_elv_switch_none`. Alright, makes sense; some sort of elevator switching. But this `blk_mq_elv_switch_none` function is itself called by `__blk_mq_update_nr_hw_queues`. This is becoming interesting - it sounds like the root of the hardware queue change logic. And, `__blk_mq_update_nr_hw_queues` is being called from `blk_mq_update_nr_hw_queues`.
Now, the crucial question: Who calls `blk_mq_update_nr_hw_queues`? That's the key to understanding the *trigger* for this whole hardware queue update cascade. I need to trace back from that function to see what initiates this process. That's the piece of the puzzle I'm after.
|
| 75/2 |
2026/07/18 00:06 |
tool |
codesearch-find-references |
0m
Args: map[Name:blk_mq_update_nr_hw_queues]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:nbd_start_device SourceFile:drivers/block/nbd.c SourceLine:1526] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:nullb_update_nr_hw_queues SourceFile:drivers/block/null_blk/main.c SourceLine:409] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:nvme_fc_recreate_io_queues SourceFile:drivers/nvme/host/fc.c SourceLine:2950] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:nvme_loop_reset_ctrl_work SourceFile:drivers/nvme/target/loop.c SourceLine:471] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:nvme_pci_update_nr_queues SourceFile:drivers/nvme/host/pci.c SourceLine:3186] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:nvme_rdma_configure_io_queues SourceFile:drivers/nvme/host/rdma.c SourceLine:901] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:nvme_tcp_configure_io_queues SourceFile:drivers/nvme/host/tcp.c SourceLine:2228] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__UNIQUE_ID_addressable_blk_mq_update_nr_hw_queues_1452 SourceFile:block/blk-mq.c SourceLine:5215] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__gendwarfksyms_ptr_blk_mq_update_nr_hw_queues SourceFile:block/blk-mq.c SourceLine:5215] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:__gendwarfksyms_ptr_blk_mq_update_nr_hw_queues SourceFile:block/blk-mq.c SourceLine:5215]] TruncatedOutput:false]
|
| 76/2 |
2026/07/18 00:06 |
llm |
expert |
0m
Model:
Tokens:
input: 64495
output: 39
thoughts: 62
Thoughts: **Tracing the NBD Device Initialization**
Okay, so it looks like `nbd_start_device` is the key function here, and that it's the one responsible for kicking off the process of setting up the hardware queues. Interestingly, it's immediately calling `blk_mq_update_nr_hw_queues`, which makes perfect sense given what I know about block layer multi-queue setup.
Now, where is `nbd_start_device` being called from? The code flow quickly points me to `nbd_genl_connect`. That's the entry point to investigate. I'll need to dig into `nbd_genl_connect` and understand how the connection and device startup are linked, and how the hardware queue configuration fits into that picture. This could be where the interesting interaction with the generic netlink interface happens.
|
| 77/2 |
2026/07/18 00:06 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/block/nbd.c Name:nbd_genl_connect]
Results: map[SourceCode:2110: static int nbd_genl_connect(struct sk_buff *skb, struct genl_info *info)
2111: {
2112: struct nbd_device *nbd;
2113: struct nbd_config *config;
2114: int index = -1;
2115: int ret;
2116: bool put_dev = false;
2117:
2118: if (!netlink_capable(skb, CAP_SYS_ADMIN))
2119: return -EPERM;
2120:
2121: if (info->attrs[NBD_ATTR_INDEX]) {
2122: index = nla_get_u32(info->attrs[NBD_ATTR_INDEX]);
2123:
2124: /*
2125: * Too big first_minor can cause duplicate creation of
2126: * sysfs files/links, since index << part_shift might overflow, or
2127: * MKDEV() expect that the max bits of first_minor is 20.
2128: */
2129: if (index < 0 || index > MINORMASK >> part_shift) {
2130: pr_err("illegal input index %d\n", index);
2131: return -EINVAL;
2132: }
2133: }
2134: if (GENL_REQ_ATTR_CHECK(info, NBD_ATTR_SOCKETS)) {
2135: pr_err("must specify at least one socket\n");
2136: return -EINVAL;
2137: }
2138: if (GENL_REQ_ATTR_CHECK(info, NBD_ATTR_SIZE_BYTES)) {
2139: pr_err("must specify a size in bytes for the device\n");
2140: return -EINVAL;
2141: }
2142: again:
2143: mutex_lock(&nbd_index_mutex);
2144: if (index == -1) {
2145: nbd = nbd_find_get_unused();
2146: } else {
2147: nbd = idr_find(&nbd_index_idr, index);
2148: if (nbd) {
2149: if ((test_bit(NBD_DESTROY_ON_DISCONNECT, &nbd->flags) &&
2150: test_bit(NBD_DISCONNECT_REQUESTED, &nbd->flags)) ||
2151: !refcount_inc_not_zero(&nbd->refs)) {
2152: mutex_unlock(&nbd_index_mutex);
2153: pr_err("device at index %d is going down\n",
2154: index);
2155: return -EINVAL;
2156: }
2157: }
2158: }
2159: mutex_unlock(&nbd_index_mutex);
2160:
2161: if (!nbd) {
2162: nbd = nbd_dev_add(index, 2);
2163: if (IS_ERR(nbd)) {
2164: pr_err("failed to add new device\n");
2165: return PTR_ERR(nbd);
2166: }
2167: }
2168:
2169: mutex_lock(&nbd->config_lock);
2170: if (refcount_read(&nbd->config_refs)) {
2171: mutex_unlock(&nbd->config_lock);
2172: nbd_put(nbd);
2173: if (index == -1)
2174: goto again;
2175: pr_err("nbd%d already in use\n", index);
2176: return -EBUSY;
2177: }
2178:
2179: ret = nbd_alloc_and_init_config(nbd);
2180: if (ret) {
2181: mutex_unlock(&nbd->config_lock);
2182: nbd_put(nbd);
2183: pr_err("couldn't allocate config\n");
2184: return ret;
2185: }
2186:
2187: config = nbd->config;
2188: set_bit(NBD_RT_BOUND, &config->runtime_flags);
2189: ret = nbd_genl_size_set(info, nbd);
2190: if (ret)
2191: goto out;
2192:
2193: if (info->attrs[NBD_ATTR_TIMEOUT])
2194: nbd_set_cmd_timeout(nbd,
2195: nla_get_u64(info->attrs[NBD_ATTR_TIMEOUT]));
2196: if (info->attrs[NBD_ATTR_DEAD_CONN_TIMEOUT]) {
2197: config->dead_conn_timeout =
2198: nla_get_u64(info->attrs[NBD_ATTR_DEAD_CONN_TIMEOUT]);
2199: config->dead_conn_timeout *= HZ;
2200: }
2201: if (info->attrs[NBD_ATTR_SERVER_FLAGS])
2202: config->flags =
2203: nla_get_u64(info->attrs[NBD_ATTR_SERVER_FLAGS]);
2204: if (info->attrs[NBD_ATTR_CLIENT_FLAGS]) {
2205: u64 flags = nla_get_u64(info->attrs[NBD_ATTR_CLIENT_FLAGS]);
2206: if (flags & NBD_CFLAG_DESTROY_ON_DISCONNECT) {
2207: /*
2208: * We have 1 ref to keep the device around, and then 1
2209: * ref for our current operation here, which will be
2210: * inherited by the config. If we already have
2211: * DESTROY_ON_DISCONNECT set then we know we don't have
2212: * that extra ref already held so we don't need the
2213: * put_dev.
2214: */
2215: if (!test_and_set_bit(NBD_DESTROY_ON_DISCONNECT,
2216: &nbd->flags))
2217: put_dev = true;
2218: } else {
2219: if (test_and_clear_bit(NBD_DESTROY_ON_DISCONNECT,
2220: &nbd->flags))
2221: refcount_inc(&nbd->refs);
2222: }
2223: if (flags & NBD_CFLAG_DISCONNECT_ON_CLOSE) {
2224: set_bit(NBD_RT_DISCONNECT_ON_CLOSE,
2225: &config->runtime_flags);
2226: }
2227: }
2228:
2229: if (info->attrs[NBD_ATTR_SOCKETS]) {
2230: struct nlattr *attr;
2231: int rem, fd;
2232:
2233: nla_for_each_nested(attr, info->attrs[NBD_ATTR_SOCKETS],
2234: rem) {
2235: struct nlattr *socks[NBD_SOCK_MAX+1];
2236:
2237: if (nla_type(attr) != NBD_SOCK_ITEM) {
2238: pr_err("socks must be embedded in a SOCK_ITEM attr\n");
2239: ret = -EINVAL;
2240: goto out;
2241: }
2242: ret = nla_parse_nested_deprecated(socks, NBD_SOCK_MAX,
2243: attr,
2244: nbd_sock_policy,
2245: info->extack);
2246: if (ret != 0) {
2247: pr_err("error processing sock list\n");
2248: ret = -EINVAL;
2249: goto out;
2250: }
2251: if (!socks[NBD_SOCK_FD])
2252: continue;
2253: fd = (int)nla_get_u32(socks[NBD_SOCK_FD]);
2254: ret = nbd_add_socket(nbd, fd, true);
2255: if (ret)
2256: goto out;
2257: }
2258: }
2259:
2260: if (info->attrs[NBD_ATTR_BACKEND_IDENTIFIER]) {
2261: nbd->backend = nla_strdup(info->attrs[NBD_ATTR_BACKEND_IDENTIFIER],
2262: GFP_KERNEL);
2263: if (!nbd->backend) {
2264: ret = -ENOMEM;
2265: goto out;
2266: }
2267: }
2268: ret = device_create_file(disk_to_dev(nbd->disk), &backend_attr);
2269: if (ret) {
2270: dev_err(disk_to_dev(nbd->disk),
2271: "device_create_file failed for backend!\n");
2272: goto out;
2273: }
2274: set_bit(NBD_RT_HAS_BACKEND_FILE, &config->runtime_flags);
2275:
2276: ret = nbd_start_device(nbd);
2277: out:
2278: if (!ret) {
2279: set_bit(NBD_RT_HAS_CONFIG_REF, &config->runtime_flags);
2280: refcount_inc(&nbd->config_refs);
2281: nbd_connect_reply(info, nbd->index);
2282: }
2283: mutex_unlock(&nbd->config_lock);
2284:
2285: nbd_config_put(nbd);
2286: if (put_dev)
2287: nbd_put(nbd);
2288: return ret;
2289: }
SourceFile:drivers/block/nbd.c]
|
| 78/2 |
2026/07/18 00:06 |
llm |
expert |
0m
Model:
Tokens:
input: 67665
output: 20
thoughts: 2082
Thoughts: **Vulnerability Analysis: NBD Kernel Deadlock**
Alright, let's break this down. My initial assessment is that `nbd_genl_connect` requires `CAP_SYS_ADMIN` within a network namespace's user namespace. That's a key point. This means an unprivileged user can trigger this functionality by simply creating a new user and network namespace.
I've identified several threads and their associated capabilities:
* **Thread 1:** Creates an RDMA link (`nldev_newlink`). Requires `CAP_NET_ADMIN` in the user namespace. User-controllable.
* **Thread 3:** Connects NBD (`nbd_genl_connect`). Requires `CAP_SYS_ADMIN` in the user namespace, which the unprivileged user *can* trigger. This calls `nbd_start_device`, leading to a potentially user-controlled path involving `blk_mq_update_nr_hw_queues`, `blk_mq_elv_switch_none`, queue freezing, and acquisition of `q->elevator_lock`. This thread is fully within the user's control.
* **Thread 5:** Submits IO to NBD. Initially, I thought this required direct block device access. But the critical realization is that `nbd_genl_connect` configures the device, which, as a side effect of `nbd_start_device` calling `nbd_set_size`, will trigger the kernel's own partition scanning mechanism through `set_capacity_and_notify`, which then calls `bdev_disk_changed`, which uses `blkdev_get_whole` to read the partition table. This is what's important: the kernel *automatically* submits IO to the NBD device as a result of the connection setup.
* **Thread 7:** Reconfigures NBD (`nbd_genl_reconfigure`). Requires `CAP_SYS_ADMIN` in the user namespace. User-controllable.
* **Thread 8:** Loads the `pcrypt` module. My initial thought was that unprivileged users can't do this. I've confirmed that if they request an AEAD algorithm using an `AF_ALG` socket, it might auto-load the relevant crypto module, specifically through `crypto_register_aead` and the resulting `request_module("crypto-pcrypt")` call. This thread is also user-controllable.
* **Thread 9:** This is CPU hotplug, and I correctly assessed it requires root or other privileges. Writing to `/sys/devices/system/cpu/cpuX/online` is root-only.
Now, for the deadlock analysis: The potential deadlock involves `cpu_hotplug_lock` along with other locks. The crucial point is that Threads 7 and 8 both acquire `cpus_read_lock()`. *Without* a CPU hotplug operation (a writer), acquiring read locks concurrently doesn't cause a deadlock. Lockdep flags it as a potential deadlock because it *assumes* the possibility of a write lock (CPU hotplug) being introduced.
Lockdep reports this:
```
-> #6 (cpu_hotplug_lock){++++}-{0:0}:
percpu_down_read_internal include/linux/percpu-rwsem.h:53 [inline]
percpu_down_read include/linux/percpu-rwsem.h:77 [inline]
cpus_read_lock+0x42/0x160 kernel/cpu.c:490
```
However, the deadlock *requires* a write lock on `cpu_hotplug_lock`. Without a writer, there is no deadlock between two readers. Thus, an unprivileged user cannot reliably trigger the deadlock.
The deadlock *only* becomes a problem if a CPU hotplug event happens concurrently.
CPU hotplug requires root access (or equivalent) in most cases.
So, absent a privileged process triggering CPU hotplug, the deadlock is not exploitable. It will be triggered only on systems with auto-CPU-hotplug, or if other `cpus_write_lock` acquires are possible. I will need to determine if other write-lock acquisitions are possible in this context.
|
| 79/2 |
2026/07/18 00:06 |
tool |
grepper |
0m
Args: map[Expression:cpus_write_lock]
Results: map[Output:drivers/acpi/acpi_processor.c=227=static int acpi_processor_hotadd_init(struct acpi_processor *pr,
--
drivers/acpi/acpi_processor.c-235- cpu_maps_update_begin();
drivers/acpi/acpi_processor.c:236: cpus_write_lock();
drivers/acpi/acpi_processor.c-237-
--
drivers/acpi/acpi_processor.c=476=static void acpi_processor_post_eject(struct acpi_device *device)
--
drivers/acpi/acpi_processor.c-498- cpu_maps_update_begin();
drivers/acpi/acpi_processor.c:499: cpus_write_lock();
drivers/acpi/acpi_processor.c-500-
--
include/linux/cpuhplock.h=13=struct device;
--
include/linux/cpuhplock.h-15-#ifdef CONFIG_HOTPLUG_CPU
include/linux/cpuhplock.h:16:void cpus_write_lock(void);
include/linux/cpuhplock.h-17-void cpus_write_unlock(void);
--
include/linux/cpuhplock.h=30=void smp_shutdown_nonboot_cpus(unsigned int primary_cpu);
--
include/linux/cpuhplock.h-33-
include/linux/cpuhplock.h:34:static inline void cpus_write_lock(void) { }
include/linux/cpuhplock.h-35-static inline void cpus_write_unlock(void) { }
--
kernel/cgroup/cpuset.c=51=static const char * const perr_strings[] = {
--
kernel/cgroup/cpuset.c-70- * - cpuset_top_mutex
kernel/cgroup/cpuset.c:71: * - cpu_hotplug_lock (cpus_read_lock/cpus_write_lock)
kernel/cgroup/cpuset.c-72- * - cpuset_mutex
--
kernel/cgroup/cpuset.c-80- * The first cpuset_top_mutex will be held except when calling into
kernel/cgroup/cpuset.c:81: * cpuset_handle_hotplug() from the CPU hotplug code where cpus_write_lock
kernel/cgroup/cpuset.c-82- * and cpuset_mutex will be held instead. The main purpose of this mutex
--
kernel/cgroup/cpuset.c=118=static DEFINE_MUTEX(cpuset_mutex);
--
kernel/cgroup/cpuset.c-123- *
kernel/cgroup/cpuset.c:124: * RWCS: Read/write-able by holding either cpus_write_lock (and optionally
kernel/cgroup/cpuset.c-125- * cpuset_mutex) or both cpus_read_lock and cpuset_mutex.
--
kernel/cgroup/cpuset.c=157=static cpumask_var_t isolated_hk_cpus; /* T */
--
kernel/cgroup/cpuset.c-167- *
kernel/cgroup/cpuset.c:168: * Protected by cpuset_mutex (with cpus_read_lock held) or cpus_write_lock.
kernel/cgroup/cpuset.c-169- *
--
kernel/cpu.c=504=EXPORT_SYMBOL_GPL(cpus_read_unlock);
kernel/cpu.c-505-
kernel/cpu.c:506:void cpus_write_lock(void)
kernel/cpu.c-507-{
--
kernel/cpu.c=1403=static int __ref _cpu_down(unsigned int cpu, int tasks_frozen,
--
kernel/cpu.c-1414-
kernel/cpu.c:1415: cpus_write_lock();
kernel/cpu.c-1416-
--
kernel/cpu.c=1616=static int _cpu_up(unsigned int cpu, int tasks_frozen, enum cpuhp_state target)
--
kernel/cpu.c-1621-
kernel/cpu.c:1622: cpus_write_lock();
kernel/cpu.c-1623-
--
kernel/time/timer_migration.c=425=static DEFINE_PER_CPU(struct tmigr_cpu, tmigr_cpu);
--
kernel/time/timer_migration.c-428- * CPUs available for timer migration.
kernel/time/timer_migration.c:429: * Protected by cpuset_mutex (with cpus_read_lock held) or cpus_write_lock.
kernel/time/timer_migration.c-430- * Additionally tmigr_available_mutex serializes set/clear operations with each other.
]
|
| 80/2 |
2026/07/18 00:06 |
llm |
expert |
0m
Model:
Tokens:
input: 71931
output: 20
thoughts: 2573
Thoughts: **Analysis of a Potential Deadlock in the Linux Kernel**
Okay, let's break this down. My initial thought was that a deadlock involving `cpus_write_lock` could be exploitable, since I know these locks are used for CPU hotplug, which, while normally privileged, could be triggered concurrently. The problem is that an unprivileged user can't *directly* trigger hotplug operations. However, I considered whether the user could set up a state that *guarantees* a deadlock *when* an admin does a hotplug operation.
I'm focusing on user-controllable threads. If a user holds `nsock->tx_lock` and waits on `cpus_read_lock`, that creates a potential blockage. Crucially, thread 7 does *acquire* `cpus_read_lock()` only if the hotplug writer is already waiting. The administrator's CPU hotplug process, thread 9, would then block on thread 8, `padata_alloc` inside `pcrypt_init`, which is a module init or initcall. This is important: if it's a module init, it finishes and releases `cpus_read_lock()` soon after. If it blocks on the mutex `wq_pool_mutex`, the user can potentially block it indefinitely.
I see that `pcrypt_init` blocks on `wq_pool_mutex`, which is also acquired by `__alloc_workqueue` (thread 1), which then blocks on `q->q_usage_counter(io)` which is held by thread 3. Thread 3, freezing the queue (and its dependencies), blocks on thread 5, which blocks on `nsock->tx_lock` held by thread 7, which calls `cpus_read_lock()` and therefore could possibly wait on the hotplug lock.
So, if thread 8 (pcrypt_init) gets blocked on the mutex of thread 1, which holds the mutex because it waits for the memory reclaim to finish, which gets blocked by the io freezing by thread 3, then it blocks indefinitely! That causes the CPU hotplug to hang, which causes the nbd reconnect to hang.
The key question is: can the user force thread 1 to hold the mutex `wq_pool_mutex` indefinitely? `__alloc_workqueue` calls `kzalloc`, which can trigger memory reclaim. Memory reclaim can enter `blk_queue_enter`, which would block if the queue is frozen. Thread 3 is freezing the queue waiting for existing IO, and thread 5, the IO, blocks on `nsock->tx_lock` held by thread 7, which tries to call `cpus_read_lock()` and may block. If the write lock is *not* waiting, the threads would not block and would complete!
I'm seeing that, *without* a writer (CPU hotplug), the entire chain resolves itself! The threads will complete. The lockdep tool is reporting a circular dependency, but it *assumes* the arrival of a writer (CPU hotplug).
Now the focus is on whether a writer can arrive, and the answer is NO, because the user cannot directly trigger `suspend`, `hibernate`, or `acpi_processor_hotadd_init`.
It seems there is no other path for `cpus_write_lock()`.
However, looking deeper, the "writer" is actually `blk_mq_freeze_queue`, or `q->q_usage_counter(io)`. This is a *percpu_ref* mechanism. The "writer" does try to acquire a lockdep map for a write but the queue is frozen, and can only block new readers and writers.
Here is the kicker: If thread 8 (pcrypt_init) and thread 7 both call `cpus_read_lock()`, they are both READERS! There is NO writer, so thread 7 does *not* block on thread 8! Lockdep shows the issue because it assumes a writer *could* arrive. It thinks this is a deadlock but it is not.
It's reporting a possible deadlock *that cannot actually happen*, because the user cannot trigger a real writer (hotplug, suspend, etc.). There are only readers, and readers don't block each other. It's a limitation of lockdep that it flags this.
But, and here's the catch: what if the *admin* just happens to do a CPU hotplug (e.g., using a script) *while* the user has set up the state? *Then* the deadlock WILL happen. The user can create the preconditions (threads 1, 3, 5, 7, 8) and *wait* for the hotplug. But, as noted, Thread 8 will finish very quickly
|
| 81/2 |
2026/07/18 00:07 |
tool |
grepper |
0m
Args: map[Expression:cpus_write_lock]
Results: map[Output:drivers/acpi/acpi_processor.c=227=static int acpi_processor_hotadd_init(struct acpi_processor *pr,
--
drivers/acpi/acpi_processor.c-235- cpu_maps_update_begin();
drivers/acpi/acpi_processor.c:236: cpus_write_lock();
drivers/acpi/acpi_processor.c-237-
--
drivers/acpi/acpi_processor.c=476=static void acpi_processor_post_eject(struct acpi_device *device)
--
drivers/acpi/acpi_processor.c-498- cpu_maps_update_begin();
drivers/acpi/acpi_processor.c:499: cpus_write_lock();
drivers/acpi/acpi_processor.c-500-
--
include/linux/cpuhplock.h=13=struct device;
--
include/linux/cpuhplock.h-15-#ifdef CONFIG_HOTPLUG_CPU
include/linux/cpuhplock.h:16:void cpus_write_lock(void);
include/linux/cpuhplock.h-17-void cpus_write_unlock(void);
--
include/linux/cpuhplock.h=30=void smp_shutdown_nonboot_cpus(unsigned int primary_cpu);
--
include/linux/cpuhplock.h-33-
include/linux/cpuhplock.h:34:static inline void cpus_write_lock(void) { }
include/linux/cpuhplock.h-35-static inline void cpus_write_unlock(void) { }
--
kernel/cgroup/cpuset.c=51=static const char * const perr_strings[] = {
--
kernel/cgroup/cpuset.c-70- * - cpuset_top_mutex
kernel/cgroup/cpuset.c:71: * - cpu_hotplug_lock (cpus_read_lock/cpus_write_lock)
kernel/cgroup/cpuset.c-72- * - cpuset_mutex
--
kernel/cgroup/cpuset.c-80- * The first cpuset_top_mutex will be held except when calling into
kernel/cgroup/cpuset.c:81: * cpuset_handle_hotplug() from the CPU hotplug code where cpus_write_lock
kernel/cgroup/cpuset.c-82- * and cpuset_mutex will be held instead. The main purpose of this mutex
--
kernel/cgroup/cpuset.c=118=static DEFINE_MUTEX(cpuset_mutex);
--
kernel/cgroup/cpuset.c-123- *
kernel/cgroup/cpuset.c:124: * RWCS: Read/write-able by holding either cpus_write_lock (and optionally
kernel/cgroup/cpuset.c-125- * cpuset_mutex) or both cpus_read_lock and cpuset_mutex.
--
kernel/cgroup/cpuset.c=157=static cpumask_var_t isolated_hk_cpus; /* T */
--
kernel/cgroup/cpuset.c-167- *
kernel/cgroup/cpuset.c:168: * Protected by cpuset_mutex (with cpus_read_lock held) or cpus_write_lock.
kernel/cgroup/cpuset.c-169- *
--
kernel/cpu.c=504=EXPORT_SYMBOL_GPL(cpus_read_unlock);
kernel/cpu.c-505-
kernel/cpu.c:506:void cpus_write_lock(void)
kernel/cpu.c-507-{
--
kernel/cpu.c=1403=static int __ref _cpu_down(unsigned int cpu, int tasks_frozen,
--
kernel/cpu.c-1414-
kernel/cpu.c:1415: cpus_write_lock();
kernel/cpu.c-1416-
--
kernel/cpu.c=1616=static int _cpu_up(unsigned int cpu, int tasks_frozen, enum cpuhp_state target)
--
kernel/cpu.c-1621-
kernel/cpu.c:1622: cpus_write_lock();
kernel/cpu.c-1623-
--
kernel/time/timer_migration.c=425=static DEFINE_PER_CPU(struct tmigr_cpu, tmigr_cpu);
--
kernel/time/timer_migration.c-428- * CPUs available for timer migration.
kernel/time/timer_migration.c:429: * Protected by cpuset_mutex (with cpus_read_lock held) or cpus_write_lock.
kernel/time/timer_migration.c-430- * Additionally tmigr_available_mutex serializes set/clear operations with each other.
]
|
| 82/2 |
2026/07/18 00:07 |
llm |
expert |
0m
Model:
Tokens:
input: 76791
output: 38
thoughts: 587
Thoughts: **Analyzing a Potential Deadlock**
Okay, so I'm trying to figure out if there's a deadlock scenario here. The initial thought was around CPU hotplug and ACPI hotplug, but the key is unprivileged users don't have access to trigger `cpus_write_lock()`. Alright, I've got that.
The next thing I need to investigate is whether there is a writer lock, other than `cpus_write_lock()`, that can create a blockage. I'm thinking about `set->srcu` now. Thread 4 is doing a `synchronize_srcu(set->srcu)`, which waits on the SRCU readers to be finished. Thread 5 is holding `srcu_read_lock(set->srcu)`, so Thread 4 *is* blocked by Thread 5. That seems like a genuine block.
Now, Thread 5 is blocked on `nsock->tx_lock`, which is currently held by Thread 7. Then, Thread 7 is blocked on `cpus_read_lock()`. Wait. But Thread 7 doesn't block on Thread 8 here, as they are both readers. So, the deadlock is *impossible* without a writer of `cpu_hotplug_lock`.
But is that *all* of the threads involved? Let me consider if Thread 8 isn't actually `padata_alloc`... The lockdep report shows a path: `cpu_hotplug_lock` -> `wq_pool_mutex`. I have to remember that's only ONE path! Are there others?
My immediate goal here is to determine whether acquiring `wq_pool_mutex` while holding a WRITE lock on `cpu_hotplug_lock` is possible. If a CPU hotplug operation acquires `wq_pool_mutex`, the writer (let's say Thread 9) would block on whatever thread that holds `wq_pool_mutex` (let's say Thread 1). I have to check if a CPU hotplug acquires `wq_pool_mutex`. CPU hotplug is using hotplug callbacks. Do any of those callbacks use `wq_pool_mutex`? I need to check something like `workqueue_cpu_up_callback` or similar.
|
| 83/2 |
2026/07/18 00:07 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:kernel/workqueue.c Name:workqueue_cpu_up_callback]
Results: map[SourceCode: SourceFile:]
|
| 84/2 |
2026/07/18 00:07 |
llm |
expert |
0m
Model:
Tokens:
input: 77918
output: 36
thoughts: 12
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Analyzing `workqueue_online_cpu`**
Okay, let's dive into this. I'm thinking, "workqueue_online_cpu"... What's the context here? Is this related to core online/offline operations? Am I looking at the function that manages moving workqueues to different CPUs? Or maybe it's the routine that updates CPU affinity within the workqueue? Depending on the architecture I'm in, this could be critical for load balancing and preventing contention. I need to quickly recall the typical call sites and what potential issues could trigger an investigation into this function. I'm anticipating that I might see performance bottlenecks, race conditions, or even lock contention if it's not well-designed. I'm going to have to make sure that I understand how this interacts with the scheduling subsystem, especially with preemptive kernels. Got it. Let's start the investigation.
|
| 85/2 |
2026/07/18 00:07 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/workqueue.c Name:workqueue_online_cpu]
Results: map[SourceCode:6847: int workqueue_online_cpu(unsigned int cpu)
6848: {
6849: struct worker_pool *pool;
6850: struct workqueue_struct *wq;
6851: int pi;
6852:
6853: mutex_lock(&wq_pool_mutex);
6854:
6855: cpumask_set_cpu(cpu, wq_online_cpumask);
6856:
6857: for_each_pool(pool, pi) {
6858: /* BH pools aren't affected by hotplug */
6859: if (pool->flags & POOL_BH)
6860: continue;
6861:
6862: mutex_lock(&wq_pool_attach_mutex);
6863: if (pool->cpu == cpu)
6864: rebind_workers(pool);
6865: else if (pool->cpu < 0)
6866: restore_unbound_workers_cpumask(pool, cpu);
6867: mutex_unlock(&wq_pool_attach_mutex);
6868: }
6869:
6870: /* update pod affinity of unbound workqueues */
6871: list_for_each_entry(wq, &workqueues, list) {
6872: struct workqueue_attrs *attrs = wq->unbound_attrs;
6873:
6874: if (attrs) {
6875: const struct wq_pod_type *pt = wqattrs_pod_type(attrs);
6876: int tcpu;
6877:
6878: for_each_cpu(tcpu, pt->pod_cpus[pt->cpu_pod[cpu]])
6879: unbound_wq_update_pwq(wq, tcpu);
6880:
6881: mutex_lock(&wq->mutex);
6882: wq_update_node_max_active(wq, -1);
6883: mutex_unlock(&wq->mutex);
6884: }
6885: }
6886:
6887: mutex_unlock(&wq_pool_mutex);
6888: return 0;
6889: }
SourceFile:kernel/workqueue.c]
|
| 86/2 |
2026/07/18 00:07 |
llm |
expert |
0m
Model:
Tokens:
input: 78823
output: 36
thoughts: 2459
Thoughts: **My Analysis of a Potential Kernel Deadlock**
Alright, let's break this down. My initial thought is that `workqueue_online_cpu` *does* grab `wq_pool_mutex`, and, crucially, it's a CPU hotplug callback. CPU hotplug callbacks are called with `cpu_hotplug_lock` held in WRITE mode. I need to confirm this...
Okay, yes, `_cpu_up` acquires `cpus_write_lock()`, then calls `cpuhp_up_callbacks`, and there we have `workqueue_online_cpu`. So, we have Thread 9 (CPU hotplug) holding `cpu_hotplug_lock` (WRITE) and *blocking* on `wq_pool_mutex`! This confirms it: the dependency of `cpu_hotplug_lock` (WRITE) -> `wq_pool_mutex` is a real thing, and it's happening during CPU hotplug.
Therefore, we have a potential deadlock cycle:
* **Thread 1:** Holds `wq_pool_mutex`, is blocked on something.
* **Thread 3:** Blocks on `q->q_usage_counter(io)`.
* **Thread 5:** Blocks on `nsock->tx_lock`.
* **Thread 7:** Holds `nsock->tx_lock`, and is blocked on `cpu_hotplug_lock` (read) because Thread 9 is waiting.
* **Thread 9:** Holds `cpu_hotplug_lock` (write), and is blocked on `wq_pool_mutex` (held by Thread 1).
Yes, that is a classic, perfect deadlock! And it doesn't even need `padata_alloc` to trigger.
Ah, Thread 9 is the key, the one that blocks on the other. It is the one that holds `cpu_hotplug_lock` (write) and is blocked on `wq_pool_mutex`. Thread 7 attempts to acquire `cpu_hotplug_lock` (read), but it is blocked by Thread 9. Thus:
* Thread 7 -> Thread 9
* Thread 9 -> Thread 1
* Thread 1 -> Thread 3
* Thread 3 -> Thread 5
* Thread 5 -> Thread 7
*CYCLE!*
Is this exploitable?
An unprivileged user *could* set up Threads 1, 3, 5, and 7, but they *cannot* trigger Thread 9 (CPU hotplug). That's either the system administrator, or automatic system events. If CPU hotplug *never* happens, this deadlock will never occur. If it *does* happen, a user *could* intentionally cause a deadlock by getting the locking order right.
Can the user hold the locks indefinitely? No, since the memory reclaim operation of Thread 1 has to finish. The whole flow would have to be synchronous for the exploit to work. Thread 7 will eventually release its lock. Thus the timing has to be very specific and the deadlock only happens if Thread 9 is started *exactly* when Thread 7 is about to call `cpus_read_lock()` or *already* is holding it.
Even if Thread 9 starts, the deadlock *might not* happen if Thread 7 finishes before Thread 9 acquires `cpus_write_lock()`. Now, what if Thread 9 acquires `cpus_write_lock()` *before* Thread 7 calls `cpus_read_lock()`? If Thread 9 has the write lock, it will block on `wq_pool_mutex` (held by Thread 1). And Thread 7's attempt to acquire a read lock will be blocked by Thread 9's write lock! Deadlock!
So, the deadlock is dependent on a precise race condition, where:
* Thread 9 acquires `cpus_write_lock()` *after* Thread 1 acquires `wq_pool_mutex`, but *before* Thread 7 calls `cpus_read_lock()`.
This race condition is the only means to make it happen. Can the user widen this race window? Thread 1 is entering memory reclaim, which *can* take a while. I am now more convinced that the user *cannot* reliably trigger this, because the user cannot trigger the concurrent part, CPU hotplug.
It's a Denial of Service, but it requires a privileged operation happening concurrently. Is it accessible from unprivileged processes? No. You need `CAP_NET_ADMIN` and `CAP_SYS_ADMIN` in a user namespace. Wait! Unprivileged users *can* create user namespaces and network namespaces! Thus, it IS accessible from
|
| 87/2 |
2026/07/18 00:07 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/block/nbd.c Name:nbd_ioctl]
Results: map[SourceCode:1677: static int nbd_ioctl(struct block_device *bdev, blk_mode_t mode,
1678: unsigned int cmd, unsigned long arg)
1679: {
1680: struct nbd_device *nbd = bdev->bd_disk->private_data;
1681: struct nbd_config *config = nbd->config;
1682: int error = -EINVAL;
1683:
1684: if (!capable(CAP_SYS_ADMIN))
1685: return -EPERM;
1686:
1687: /* The block layer will pass back some non-nbd ioctls in case we have
1688: * special handling for them, but we don't so just return an error.
1689: */
1690: if (_IOC_TYPE(cmd) != 0xab)
1691: return -EINVAL;
1692:
1693: mutex_lock(&nbd->config_lock);
1694:
1695: /* Don't allow ioctl operations on a nbd device that was created with
1696: * netlink, unless it's DISCONNECT or CLEAR_SOCK, which are fine.
1697: */
1698: if (!test_bit(NBD_RT_BOUND, &config->runtime_flags) ||
1699: (cmd == NBD_DISCONNECT || cmd == NBD_CLEAR_SOCK))
1700: error = __nbd_ioctl(bdev, nbd, cmd, arg);
1701: else
1702: dev_err(nbd_to_dev(nbd), "Cannot use ioctl interface on a netlink controlled device.\n");
1703: mutex_unlock(&nbd->config_lock);
1704: return error;
1705: }
SourceFile:drivers/block/nbd.c]
|
| 88/2 |
2026/07/18 00:07 |
llm |
expert |
0m
Model:
Tokens:
input: 82873
output: 25
thoughts: 585
Thoughts: **My Assessment of the Potential Denial-of-Service Vulnerability**
Okay, let's break this down. The core issue is this: the exploit leverages a lockdep warning, specifically a potential deadlock. Access to the ioctl interface is blocked from user namespaces, which makes it useless. However, the exploit can be triggered via the netlink interface, which is accessible from user namespaces.
Now, the crucial point: to *actually* deadlock the system, a concurrent CPU hotplug operation is required. The user can't directly trigger CPU hotplug, that's privileged, so reliably forcing the deadlock is out of the question. Lockdep warnings themselves *could* be a DoS if `panic_on_warn` were enabled, but that's unlikely in production builds. In a typical production scenario, with lockdep disabled, the deadlock only occurs if a highly improbable race condition happens – an administrator is running CPU hotplug at precisely the same time.
The prompt, though, asks us to think about DoS implications, especially for "WARNING bugs" that *won't* cause crashes directly in a production build. The prompt also notes that if the unexpected condition is properly handled, there is no DOS. In our case, the core issue is the potential deadlock. While extremely difficult to trigger, even in production, this theoretical deadlock *is* a system-hang bug.
Therefore, although the practical DoS impact is very low due to the race condition, *strictly speaking* this can result in denial of service. I will classify it as such.
Now, let's see if there is another way to trigger `cpus_write_lock()`. No. It is not possible without elevated privileges. And let me check if there is any other way to trigger `wq_pool_mutex` while holding `cpu_hotplug_lock` (read). `padata_alloc` is called from `pcrypt_init` with Thread 8. Let's make sure we have evaluated all the callers of `alloc_workqueue` that also hold `cpus_read_lock()` for any other potential issue.
|
| 89/2 |
2026/07/18 00:07 |
tool |
grepper |
0m
Error:
Args: map[Expression:cpus_read_lock.*alloc_workqueue]
Results:
|
| 90/2 |
2026/07/18 00:07 |
llm |
expert |
0m
Model:
Tokens:
input: 83940
output: 37
thoughts: 25
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**Investigating Potential Synchronization Issues**
Alright, let's dive in. I need to pinpoint some code that's potentially causing a problem, and the best way to start is to look for specific patterns. I suspect there might be a synchronization issue, so I'm going to focus on two key elements: `cpus_read_lock` and `alloc_workqueue`. Finding both of these in the same function is a strong indicator of an area that needs a closer look. `cpus_read_lock` implies some kind of shared resource protection at the CPU level, and `alloc_workqueue` suggests deferred work or background processing, which introduces complexity for concurrency. This combination could be a recipe for subtle bugs if not handled carefully. My goal is to use this search as a starting point, and I expect it to quickly highlight the lines of code that require my expert attention. It's a targeted approach; hopefully this search will give me a clear lead.
|
| 91/2 |
2026/07/18 00:07 |
tool |
grepper |
0m
Error:
Args: map[Expression:cpus_read_lock.*alloc_workqueue|alloc_workqueue.*cpus_read_lock]
Results:
|
| 92/2 |
2026/07/18 00:07 |
llm |
expert |
0m
Model:
Tokens:
input: 84238
output: 66
thoughts: 1275
Thoughts: **Analysis Complete: Deadlock in NBD Driver**
Okay, so looking at this code, my initial thought was to quickly check for the `cpus_read_lock` and `alloc_workqueue` pair, but they're on separate lines in `padata_alloc`. I trust that `padata_alloc` is indeed the function of interest. Let's stick with that.
Based on the code analysis, the key takeaways are:
* **Exploitable: False**. It's not memory corruption, it's a deadlock scenario.
* **DenialOfService: True**. This deadlock can potentially halt the system, so DoS is a valid classification.
* **Unprivileged: False**. This requires `CAP_NET_ADMIN` and `CAP_SYS_ADMIN`, but only in the context of user namespaces.
* **UserNamespace: True**. The netlink interfaces use `netlink_capable`, meaning it's reachable via user namespaces.
* **VMGuestTrigger, VMHostTrigger, NetworkTrigger, RemoteTrigger, PeripheralTrigger, FilesystemTrigger: False**. No specific triggers other than local netlink usage.
Now, let's revisit the user namespace requirements. I needed to ensure there was no other trigger for `nbd_genl_connect`. It looks like the `netlink_capable(skb, CAP_SYS_ADMIN)` call requires that capability within the user namespace. This confirms an unprivileged user needs a user namespace to trigger the deadlock.
I then did a quick check, is `blk_mq_update_nr_hw_queues` triggered only via `nbd_genl_connect`? No, other block devices could trigger it. `nullb_update_nr_hw_queues` is specific to the test module `null_blk` so that's not it. Then there are NVMe modules, but NVMe over Fabrics (TCP, RDMA, Loop) can be configured via configfs. Configfs is usually root-owned in the initial user namespace. This means that, for an unprivileged user, NBD via netlink is indeed the only viable path to trigger `blk_mq_update_nr_hw_queues`.
Another check, can we trigger `elevator_change` another way, like writing to `/sys/block/*/queue/scheduler`? That also requires root in the initial user namespace, so no, not an option.
So the path is NBD via netlink, which means a user namespace is required.
To be absolutely sure, let's trace the deadlock chain:
1. `fs_reclaim` -> `q->q_usage_counter(io)` - `blk_alloc_queue` explicitly set this.
2. `q->q_usage_counter(io)` -> `q->elevator_lock` - `elevator_change` calls `blk_mq_freeze_queue` which acquires these in that order.
3. `q->elevator_lock` -> `set->srcu` - `elevator_switch` holds `q->elevator_lock` and calls `blk_mq_quiesce_queue`, then calls `synchronize_srcu(set->srcu)`.
4. `set->srcu` -> `cmd->lock` - `blk_mq_run_work_fn` holds `srcu_read_lock(set->srcu)` and calls `blk_mq_sched_dispatch_requests`, and this calls `nbd_queue_rq` which acquires `cmd->lock`.
5. `cmd->lock` -> `nsock->tx_lock` - `nbd_queue_rq` holds `cmd->lock` and calls `nbd_handle_cmd`, which acquires `nsock->tx_lock`.
6. `nsock->tx_lock` -> `cpu_hotplug_lock` - `nbd_reconnect_socket` calls `sk_set_memalloc`, which calls `static_branch_inc`, which calls `cpus_read_lock()`.
7. `cpu_hotplug_lock` -> `wq_pool_mutex` - `padata_alloc` calls `cpus_read_lock()` and then `alloc_workqueue`, which acquires `wq_pool_mutex`. Or, `workqueue_online_cpu` can be called from hotplug, holding `cpus_write_lock()` acquiring `wq_pool_mutex`.
8. `wq_pool_mutex` -> `fs_reclaim` - `__alloc_workqueue` holds `wq_pool_mutex` and calls `kzalloc(GFP_KERNEL)` which might enter memory reclaim (`fs_reclaim`).
The chain is
|
| 93/2 |
2026/07/18 00:08 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 94/2 |
2026/07/18 00:08 |
llm |
expert |
0m
Model:
Tokens:
input: 86668
output: 985
thoughts: 709
Thoughts: **My Analysis of the Lock Dependency Deadlock**
Alright, the results are in, and now it's time to craft the final response. My focus needs to be on explaining this tricky lock dependency chain, establishing its validity as a deadlock (specifically one that involves a concurrent CPU hotplug operation), and demonstrating how an unprivileged user can trigger it within a user namespace via the NBD netlink interface.
Here's how I see it:
The key lies in understanding the lock dependency chain:
1. It all begins with `fs_reclaim` which is registered with `q->q_usage_counter(io)` in `blk_alloc_queue`, making lockdep aware that memory reclaim can wait for queue freeze.
2. `q->q_usage_counter(io)` then leads to `q->elevator_lock` through `elevator_change`. The queue gets frozen (grabbing the lockdep map) before `q->elevator_lock` is grabbed.
3. `q->elevator_lock` then leads to `set->srcu` through `elevator_switch`. While `q->elevator_lock` is held, `blk_mq_quiesce_queue` is called and waits for `synchronize_srcu(set->srcu)`.
4. `set->srcu` then leads to `cmd->lock`. Through `blk_mq_run_work_fn`, `srcu_read_lock(set->srcu)` is held, and `nbd_queue_rq` is called, which acquires `cmd->lock`.
5. `cmd->lock` then leads to `nsock->tx_lock`. `nbd_queue_rq` is holding `cmd->lock` and calls `nbd_handle_cmd`, which grabs `nsock->tx_lock`.
6. `nsock->tx_lock` then leads to `cpu_hotplug_lock`. `nbd_reconnect_socket` holds `nsock->tx_lock` and calls `sk_set_memalloc`, which in turn, acquires `cpus_read_lock()`.
7. `cpu_hotplug_lock` then leads to `wq_pool_mutex` via `padata_alloc`. `padata_alloc` calls `alloc_workqueue`, which grabs `wq_pool_mutex`. CPU hotplug callbacks like `workqueue_online_cpu` also hold `cpus_write_lock()` and then grab `wq_pool_mutex`.
8. Finally, `wq_pool_mutex` leads back to `fs_reclaim`. `__alloc_workqueue` allocates memory with `GFP_KERNEL` and enters memory reclaim.
Now, about the triggerability:
`nbd_genl_connect` and `nbd_genl_reconfigure` are available through netlink and require `CAP_SYS_ADMIN` in the network namespace's user namespace. `nldev_newlink` needs `CAP_NET_ADMIN` in the network namespace's user namespace. So, an unprivileged user could create a new user namespace and network namespace to get there.
This deadlock depends on a concurrent writer to `cpu_hotplug_lock` (a CPU hotplug operation) blocking the readers, to allow the cycle to complete. Although unprivileged users can't directly trigger CPU hotplug, the deadlock remains a valid Denial of Service vulnerability, even if exploitation is unreliable.
|
| 95/1 |
2026/07/18 00:08 |
action |
format-explanation |
0m
Results: map[Explanation:Based on the analysis of the provided bug report and kernel source code, here is
the assessment of the vulnerability:
### Lock Dependency Chain
Lockdep has detected a complex circular locking dependency that can lead to a
deadlock. The chain is formed by the following sequence of lock acquisitions:
1. **`fs_reclaim` -> `q->q_usage_counter(io)`**: Explicitly registered in
`blk_alloc_queue()` to teach lockdep that memory reclaim can wait for a block
queue to unfreeze.
2. **`q->q_usage_counter(io)` -> `q->elevator_lock`**: In `elevator_change()`,
the block queue is frozen (which acquires the lockdep map for
`q->q_usage_counter(io)` for write) before acquiring `q->elevator_lock`.
3. **`q->elevator_lock` -> `set->srcu`**: In `elevator_switch()`,
`q->elevator_lock` is held while calling `blk_mq_quiesce_queue()`, which waits
for SRCU readers via `synchronize_srcu(set->srcu)`.
4. **`set->srcu` -> `cmd->lock`**: During IO dispatch, `blk_mq_run_work_fn()`
holds `srcu_read_lock(set->srcu)` and calls `nbd_queue_rq()`, which acquires
`cmd->lock`.
5. **`cmd->lock` -> `nsock->tx_lock`**: `nbd_queue_rq()` holds `cmd->lock` and
calls `nbd_handle_cmd()`, which acquires `nsock->tx_lock`.
6. **`nsock->tx_lock` -> `cpu_hotplug_lock`**: `nbd_reconnect_socket()` holds
`nsock->tx_lock` and calls `sk_set_memalloc()`, which increments a static key
and acquires `cpus_read_lock()`.
7. **`cpu_hotplug_lock` -> `wq_pool_mutex`**: `padata_alloc()` holds
`cpus_read_lock()` and calls `alloc_workqueue()`, which acquires
`wq_pool_mutex`. (Additionally, CPU hotplug callbacks like
`workqueue_online_cpu()` hold `cpus_write_lock()` and acquire `wq_pool_mutex`).
8. **`wq_pool_mutex` -> `fs_reclaim`**: `__alloc_workqueue()` holds
`wq_pool_mutex` and allocates memory with `GFP_KERNEL`, which can enter the
memory reclaim path (`fs_reclaim`).
### Security Impact
- **Denial of Service (DoS):** Yes. If the deadlock occurs, it will hang the
involved tasks and potentially the entire system (especially if a CPU hotplug
operation hangs, which blocks many other subsystems).
- **Exploitable:** No. This is a pure locking issue (deadlock) and does not lead
to memory corruption, use-after-free, or information leaks.
### Accessibility
- **Accessible From Unprivileged Processes:** No. Triggering the NBD and RDMA
netlink paths requires `CAP_SYS_ADMIN` and `CAP_NET_ADMIN` respectively.
- **Accessible From User Namespaces:** Yes. The netlink handlers for NBD
(`nbd_genl_connect`, `nbd_genl_reconfigure`) and RDMA (`nldev_newlink`) use
`netlink_capable()`, which checks for capabilities in the user namespace that
owns the network namespace of the socket. An unprivileged user can create a new
user namespace and network namespace to gain these capabilities and trigger the
vulnerable code paths.
### Exploitability Caveat
While an unprivileged user in a user namespace can set up most of the locks in
this chain (by configuring NBD devices and creating RDMA links), the cycle
relies on `cpu_hotplug_lock`. Since both `nbd_reconnect_socket()` and
`padata_alloc()` acquire this lock for *read*, they do not block each other. For
the deadlock to actually manifest, a concurrent *writer* to `cpu_hotplug_lock`
(i.e., a CPU hotplug operation like offlining a CPU) must occur at the exact
right time to block the readers. Because unprivileged users cannot trigger CPU
hotplug operations, they cannot reliably force the deadlock to happen on demand.
However, it remains a valid DoS vector if the system administrator or automated
power-management scripts perform CPU hotplugging while the user is holding the
other locks.]
|