| 0/0 |
2026/08/11 01:32 |
flow |
moderation |
33m
Results: map[Actionable:true Explanation:An analysis of the bug report and the Linux kernel source code confirms that
this report is **self-consistent, highly actionable, and describes a real,
reproducible use-after-free (double-free) bug** in the NVMe over Fabrics Fibre
Channel (FC) host driver.
---
### Root Cause Analysis
The bug is triggered by a combination of **failslab fault injection** and an
**incomplete error cleanup path** in `nvme_fc_init_ctrl()`.
#### 1. Allocation of the `nvmf_ctrl_options` Object
When a user requests the creation of a new NVMe-over-Fabrics controller (e.g.,
via `/dev/nvme-fabrics`), `nvmf_create_ctrl()` in `drivers/nvme/host/fabrics.c`
is called:
```c
opts = kzalloc_obj(*opts); // Allocated here (corresponds to the Allocation
Stack)
...
ctrl = ops->create_ctrl(dev, opts); // Calls nvme_fc_create_ctrl() for FC
```
#### 2. Fault Injection and Failure in `nvme_add_ctrl()`
Inside `nvme_fc_create_ctrl()`, the driver allocates a Fibre Channel controller
structure (`struct nvme_fc_ctrl *ctrl`) and associates the options with it:
```c
ctrl->ctrl.opts = opts; // ctrl takes ownership of opts
```
Then, `nvme_fc_init_ctrl()` calls `nvme_add_ctrl(&ctrl->ctrl)` to register the
device:
```c
ret = nvme_add_ctrl(&ctrl->ctrl);
if (ret)
goto out_put_ctrl;
```
In this specific run, **failslab fault injection** forces a failure in
`kvasprintf()` inside `dev_set_name()`, which is called by `nvme_add_ctrl()`. As
a result, `nvme_add_ctrl()` returns an error, and `nvme_fc_init_ctrl()` jumps to
`out_put_ctrl`.
#### 3. First Free of `opts` (via `nvme_put_ctrl`)
In the `out_put_ctrl` error path, the driver releases its reference to the
controller:
```c
out_put_ctrl:
/* Remove core ctrl ref. */
nvme_put_ctrl(&ctrl->ctrl);
```
Since the reference count of the controller's device drops to zero, the kernel
triggers the device release callback `nvme_free_ctrl()`, which calls the
FC-specific `.free_ctrl` callback `nvme_fc_free_ctrl()`. This eventually calls
`nvme_fc_ctrl_free()`, which does:
```c
if (ctrl->ctrl.opts)
nvmf_free_options(ctrl->ctrl.opts); // First Free (corresponds to the Free
Stack)
kfree(ctrl);
```
Because the code jumped directly to `out_put_ctrl` and bypassed the `fail_ctrl`
label, **`ctrl->ctrl.opts` was never set to `NULL`**. Thus,
`nvmf_free_options()` is called, freeing the `opts` structure.
#### 4. Second Free of `opts` (Use-After-Free / Double-Free)
After `nvme_fc_init_ctrl()` returns the error pointer, the error propagates back
to `nvmf_create_ctrl()` in `drivers/nvme/host/fabrics.c`:
```c
ctrl = ops->create_ctrl(dev, opts);
if (IS_ERR(ctrl)) {
ret = PTR_ERR(ctrl);
goto out_module_put;
}
...
out_free_opts:
nvmf_free_options(opts); // Second Free / Use-After-Free (corresponds to the
Bad Access Stack)
return ERR_PTR(ret);
```
Since `opts` was already freed in step 3, calling `nvmf_free_options(opts)` here
results in a **slab-use-after-free (double-free)**.
---
### Comparison with Other Transport Drivers
Other transport drivers (such as TCP, RDMA, and Loop) avoid this issue because
their `.free_ctrl` callbacks check if the controller was successfully added to
their global lists before freeing the options:
```c
static void nvme_tcp_free_ctrl(struct nvme_ctrl *nctrl)
{
struct nvme_tcp_ctrl *ctrl = to_tcp_ctrl(nctrl);
if (list_empty(&ctrl->list))
goto free_ctrl; // Bypasses nvmf_free_options() if not fully
initialized/added
...
nvmf_free_options(nctrl->opts);
free_ctrl:
kfree(ctrl->queues);
kfree(ctrl);
}
```
The Fibre Channel (FC) driver lacks this safeguard and unconditionally frees
`ctrl->ctrl.opts` if it is non-NULL.
---
### Proposed Fix
To fix this bug, `nvme_fc_init_ctrl()` must set `ctrl->ctrl.opts = NULL` if
`nvme_add_ctrl()` fails, ensuring that the subsequent `nvme_put_ctrl()` call
does not free the options (leaving the cleanup responsibility to
`nvmf_create_ctrl()`).
```diff
diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c
index c52446013388..d5a383766b34 100644
--- a/drivers/nvme/host/fc.c
+++ b/drivers/nvme/host/fc.c
@@ -3564,8 +3564,10 @@ nvme_fc_init_ctrl(struct device *dev, struct
nvmf_ctrl_options *opts,
return ERR_CAST(ctrl);
ret = nvme_add_ctrl(&ctrl->ctrl);
- if (ret)
+ if (ret) {
+ ctrl->ctrl.opts = NULL;
goto out_put_ctrl;
+ }
ret = nvme_alloc_admin_tag_set(&ctrl->ctrl, &ctrl->admin_tag_set,
&nvme_fc_admin_mq_ops,
```
---
### Conclusion
This report is **fully actionable and self-consistent**. The three stack traces
align perfectly with the lifecycle of the `struct nvmf_ctrl_options` object, and
the root cause is a clear error-handling bug introduced during the refactoring
of the NVMe controller initialization/addition sequence.]
|
| 1/1 |
2026/08/11 01:32 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/9d4bc8dbb37da7464722132cca9f0756f97b7cb8]
|
| 2/1 |
2026/08/11 01:32 |
action |
kernel-builder |
19m
Results: map[KernelObj:/app/workdir/cache/build/d8187be62dbd499452962aee299f173585f35e49]
|
| 3/1 |
2026/08/11 01:51 |
action |
codesearch-prepare |
11m
Results: map[Index:codesearch-index]
|
| 4/1 |
2026/08/11 02:03 |
agent |
expert |
2m
Model:
Results:
Instruction: You are an experienced Linux kernel developer tasked with determining if the given kernel bug
report is actionable or not. Actionable means that it contains enough info to root cause
the underlying bug, and that the report is self-consistent and makes sense, rather than
e.g. a one-off nonsensical crash induced by a previous memory corruption.
The bug report is about a use-after-free bug generated by KASAN tool.
It should contain 3 stack traces: the bad memory access stack, the heap block allocation stack,
and the heap block free stack. If the report does not contain 3 stacks, it's not actionable.
All 3 stack traces should be related to the same object type,
and usually be in the same kernel subsystem (at least leaf stack frames).
An example of an actionable and consistent report would be: first access stack relates
to an access to a field of struct Foo, allocation/free stacks relate to allocation/free
of the struct Foo.
In inconsistent/nonsensical reports an access may be to a struct Foo, but allocation
stack allocates a different structure in a different subsystem.
Look for other suspicious signals/inconsistencies that can make this report hard to
debug/understand.
In the final reply explain why you think the report is self-consistent and actionable,
or why it's inconsistent and/or not actionable.
Use the provided tools to confirm any assumptions, variables/fields being accessed, etc.
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 bug report is:
FAULT_INJECTION: forcing a failure.
name failslab, interval 1, probability 0, space 0, times 1
CPU: 0 UID: 0 PID: 5326 Comm: syz.0.0 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
fail_dump lib/fault-inject.c:73 [inline]
should_fail_ex+0x40c/0x560 lib/fault-inject.c:174
should_failslab+0xa8/0x100 mm/failslab.c:46
slab_pre_alloc_hook mm/slub.c:4539 [inline]
slab_alloc_node mm/slub.c:4897 [inline]
__do_kmalloc_node mm/slub.c:5333 [inline]
__kmalloc_node_track_caller_noprof+0x100/0x730 mm/slub.c:5471
kvasprintf+0xeb/0x1a0 lib/kasprintf.c:25
kobject_set_name_vargs+0x61/0x110 lib/kobject.c:274
dev_set_name+0xe2/0x140 drivers/base/core.c:3560
nvme_add_ctrl+0x6e/0x270 drivers/nvme/host/core.c:5248
nvme_fc_init_ctrl+0xcb0/0x1450 drivers/nvme/host/fc.c:3554
nvme_fc_create_ctrl+0x4cd/0x520 drivers/nvme/host/fc.c:3723
nvmf_create_ctrl drivers/nvme/host/fabrics.c:1359 [inline]
nvmf_dev_write+0x24ca/0x2d90 drivers/nvme/host/fabrics.c:1406
vfs_write+0x296/0xba0 fs/read_write.c:685
ksys_write+0x150/0x270 fs/read_write.c:739
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:0x7fea9b39e0d9
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:00007fea9c299fe8 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007fea9b625fa0 RCX: 00007fea9b39e0d9
RDX: 000000000000007e RSI: 0000200000000a00 RDI: 0000000000000005
RBP: 00007fea9c29a050 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000002
R13: 00007fea9b626038 R14: 00007fea9b625fa0 R15: 00007ffe156ed1b8
</TASK>
==================================================================
BUG: KASAN: slab-use-after-free in nvmf_free_options+0x30/0x190 drivers/nvme/host/fabrics.c:1284
Read of size 8 at addr ffff88801f4a2550 by task syz.0.0/5326
CPU: 0 UID: 0 PID: 5326 Comm: syz.0.0 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_address_description+0x55/0x1e0 mm/kasan/report.c:378
print_report+0x58/0x70 mm/kasan/report.c:482
kasan_report+0x117/0x150 mm/kasan/report.c:595
nvmf_free_options+0x30/0x190 drivers/nvme/host/fabrics.c:1284
nvmf_create_ctrl drivers/nvme/host/fabrics.c:1374 [inline]
nvmf_dev_write+0x267f/0x2d90 drivers/nvme/host/fabrics.c:1406
vfs_write+0x296/0xba0 fs/read_write.c:685
ksys_write+0x150/0x270 fs/read_write.c:739
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:0x7fea9b39e0d9
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:00007fea9c299fe8 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007fea9b625fa0 RCX: 00007fea9b39e0d9
RDX: 000000000000007e RSI: 0000200000000a00 RDI: 0000000000000005
RBP: 00007fea9c29a050 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000002
R13: 00007fea9b626038 R14: 00007fea9b625fa0 R15: 00007ffe156ed1b8
</TASK>
Allocated by task 5326:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
kasan_kmalloc include/linux/kasan.h:263 [inline]
__kmalloc_cache_noprof+0x32d/0x660 mm/slub.c:5489
_kmalloc_noprof include/linux/slab.h:988 [inline]
_kzalloc_noprof include/linux/slab.h:1309 [inline]
nvmf_create_ctrl drivers/nvme/host/fabrics.c:1315 [inline]
nvmf_dev_write+0x243/0x2d90 drivers/nvme/host/fabrics.c:1406
vfs_write+0x296/0xba0 fs/read_write.c:685
ksys_write+0x150/0x270 fs/read_write.c:739
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
Freed by task 5326:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2677 [inline]
slab_free mm/slub.c:6377 [inline]
kfree+0x1c5/0x640 mm/slub.c:6692
nvme_fc_ctrl_free drivers/nvme/host/fc.c:2374 [inline]
kref_put include/linux/kref.h:65 [inline]
nvme_fc_ctrl_put+0x2cc/0x340 drivers/nvme/host/fc.c:2381
nvme_free_ctrl+0x38c/0x570 drivers/nvme/host/core.c:5146
device_release+0xc4/0x1f0 drivers/base/core.c:-1
kobject_cleanup lib/kobject.c:689 [inline]
kobject_release lib/kobject.c:720 [inline]
kref_put include/linux/kref.h:65 [inline]
kobject_put+0x222/0x550 lib/kobject.c:737
nvme_put_ctrl drivers/nvme/host/nvme.h:833 [inline]
nvme_fc_init_ctrl+0xe17/0x1450 drivers/nvme/host/fc.c:3605
nvme_fc_create_ctrl+0x4cd/0x520 drivers/nvme/host/fc.c:3723
nvmf_create_ctrl drivers/nvme/host/fabrics.c:1359 [inline]
nvmf_dev_write+0x24ca/0x2d90 drivers/nvme/host/fabrics.c:1406
vfs_write+0x296/0xba0 fs/read_write.c:685
ksys_write+0x150/0x270 fs/read_write.c:739
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
The buggy address belongs to the object at ffff88801f4a2500
which belongs to the cache kmalloc-192 of size 192
The buggy address is located 80 bytes inside of
freed 192-byte region [ffff88801f4a2500, ffff88801f4a25c0)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x1f4a2
flags: 0xfff00000000000(node=0|zone=1|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 00fff00000000000 ffff88801b0413c0 dead000000000122 0000000000000000
raw: 0000000000000000 0000000800100010 00000000f5000000 0000000000000000
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0xd2cc0(GFP_KERNEL|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 5289, tgid 5289 (syz-executor), ts 83703979346, free_ts 83087386547
set_page_owner include/linux/page_owner.h:32 [inline]
post_alloc_hook+0x1f9/0x250 mm/page_alloc.c:1859
prep_new_page mm/page_alloc.c:1867 [inline]
get_page_from_freelist+0x21fa/0x2270 mm/page_alloc.c:3946
__alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5304
alloc_slab_page mm/slub.c:3266 [inline]
allocate_slab+0x79/0x5e0 mm/slub.c:3380
new_slab mm/slub.c:3426 [inline]
refill_objects+0x2d5/0x350 mm/slub.c:7310
refill_sheaf mm/slub.c:2804 [inline]
__pcs_replace_empty_main+0x2bf/0x6b0 mm/slub.c:4675
alloc_from_pcs mm/slub.c:4773 [inline]
slab_alloc_node mm/slub.c:4905 [inline]
__do_kmalloc_node mm/slub.c:5333 [inline]
__kmalloc_node_track_caller_noprof+0x557/0x730 mm/slub.c:5471
kmemdup_noprof+0x2b/0x70 mm/util.c:138
kmemdup_noprof include/linux/fortify-string.h:715 [inline]
ebt_register_table+0x18e/0x1190 net/bridge/netfilter/ebtables.c:1197
find_inlist_lock_noload+0x171/0x260 net/bridge/netfilter/ebtables.c:344
find_inlist_lock net/bridge/netfilter/ebtables.c:372 [inline]
find_table_lock net/bridge/netfilter/ebtables.c:380 [inline]
do_ebt_get_ctl+0x367/0x1f30 net/bridge/netfilter/ebtables.c:2512
nf_getsockopt+0x26e/0x290 net/netfilter/nf_sockopt.c:116
ip_getsockopt+0x19e/0x230 net/ipv4/ip_sockglue.c:1777
do_sock_getsockopt+0x569/0xa70 net/socket.c:2474
__sys_getsockopt net/socket.c:2505 [inline]
__do_sys_getsockopt net/socket.c:2512 [inline]
__se_sys_getsockopt net/socket.c:2509 [inline]
__x64_sys_getsockopt+0x1a4/0x240 net/socket.c:2509
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
page last free pid 75 tgid 75 stack trace:
reset_page_owner include/linux/page_owner.h:25 [inline]
__free_pages_prepare mm/page_alloc.c:1406 [inline]
free_unref_folios+0xd84/0x14a0 mm/page_alloc.c:3011
shrink_folio_list+0x4b16/0x5330 mm/vmscan.c:1582
evict_folios+0x3821/0x49e0 mm/vmscan.c:4834
try_to_shrink_lruvec+0xb4f/0xed0 mm/vmscan.c:4983
shrink_one+0x233/0x730 mm/vmscan.c:5024
shrink_many mm/vmscan.c:5087 [inline]
lru_gen_shrink_node mm/vmscan.c:5165 [inline]
shrink_node+0x3303/0x3b60 mm/vmscan.c:6154
kswapd_shrink_node mm/vmscan.c:7008 [inline]
balance_pgdat mm/vmscan.c:7186 [inline]
kswapd+0x17b6/0x31c0 mm/vmscan.c:7462
kthread+0x388/0x470 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
Memory state around the buggy address:
ffff88801f4a2400: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88801f4a2480: fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
>ffff88801f4a2500: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff88801f4a2580: fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
ffff88801f4a2600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
==================================================================
Reply: An analysis of the bug report and the Linux kernel source code confirms that this report is **self-consistent, highly actionable, and describes a real, reproducible use-after-free (double-free) bug** in the NVMe over Fabrics Fibre Channel (FC) host driver.
---
### Root Cause Analysis
The bug is triggered by a combination of **failslab fault injection** and an **incomplete error cleanup path** in `nvme_fc_init_ctrl()`.
#### 1. Allocation of the `nvmf_ctrl_options` Object
When a user requests the creation of a new NVMe-over-Fabrics controller (e.g., via `/dev/nvme-fabrics`), `nvmf_create_ctrl()` in `drivers/nvme/host/fabrics.c` is called:
```c
opts = kzalloc_obj(*opts); // Allocated here (corresponds to the Allocation Stack)
...
ctrl = ops->create_ctrl(dev, opts); // Calls nvme_fc_create_ctrl() for FC
```
#### 2. Fault Injection and Failure in `nvme_add_ctrl()`
Inside `nvme_fc_create_ctrl()`, the driver allocates a Fibre Channel controller structure (`struct nvme_fc_ctrl *ctrl`) and associates the options with it:
```c
ctrl->ctrl.opts = opts; // ctrl takes ownership of opts
```
Then, `nvme_fc_init_ctrl()` calls `nvme_add_ctrl(&ctrl->ctrl)` to register the device:
```c
ret = nvme_add_ctrl(&ctrl->ctrl);
if (ret)
goto out_put_ctrl;
```
In this specific run, **failslab fault injection** forces a failure in `kvasprintf()` inside `dev_set_name()`, which is called by `nvme_add_ctrl()`. As a result, `nvme_add_ctrl()` returns an error, and `nvme_fc_init_ctrl()` jumps to `out_put_ctrl`.
#### 3. First Free of `opts` (via `nvme_put_ctrl`)
In the `out_put_ctrl` error path, the driver releases its reference to the controller:
```c
out_put_ctrl:
/* Remove core ctrl ref. */
nvme_put_ctrl(&ctrl->ctrl);
```
Since the reference count of the controller's device drops to zero, the kernel triggers the device release callback `nvme_free_ctrl()`, which calls the FC-specific `.free_ctrl` callback `nvme_fc_free_ctrl()`. This eventually calls `nvme_fc_ctrl_free()`, which does:
```c
if (ctrl->ctrl.opts)
nvmf_free_options(ctrl->ctrl.opts); // First Free (corresponds to the Free Stack)
kfree(ctrl);
```
Because the code jumped directly to `out_put_ctrl` and bypassed the `fail_ctrl` label, **`ctrl->ctrl.opts` was never set to `NULL`**. Thus, `nvmf_free_options()` is called, freeing the `opts` structure.
#### 4. Second Free of `opts` (Use-After-Free / Double-Free)
After `nvme_fc_init_ctrl()` returns the error pointer, the error propagates back to `nvmf_create_ctrl()` in `drivers/nvme/host/fabrics.c`:
```c
ctrl = ops->create_ctrl(dev, opts);
if (IS_ERR(ctrl)) {
ret = PTR_ERR(ctrl);
goto out_module_put;
}
...
out_free_opts:
nvmf_free_options(opts); // Second Free / Use-After-Free (corresponds to the Bad Access Stack)
return ERR_PTR(ret);
```
Since `opts` was already freed in step 3, calling `nvmf_free_options(opts)` here results in a **slab-use-after-free (double-free)**.
---
### Comparison with Other Transport Drivers
Other transport drivers (such as TCP, RDMA, and Loop) avoid this issue because their `.free_ctrl` callbacks check if the controller was successfully added to their global lists before freeing the options:
```c
static void nvme_tcp_free_ctrl(struct nvme_ctrl *nctrl)
{
struct nvme_tcp_ctrl *ctrl = to_tcp_ctrl(nctrl);
if (list_empty(&ctrl->list))
goto free_ctrl; // Bypasses nvmf_free_options() if not fully initialized/added
...
nvmf_free_options(nctrl->opts);
free_ctrl:
kfree(ctrl->queues);
kfree(ctrl);
}
```
The Fibre Channel (FC) driver lacks this safeguard and unconditionally frees `ctrl->ctrl.opts` if it is non-NULL.
---
### Proposed Fix
To fix this bug, `nvme_fc_init_ctrl()` must set `ctrl->ctrl.opts = NULL` if `nvme_add_ctrl()` fails, ensuring that the subsequent `nvme_put_ctrl()` call does not free the options (leaving the cleanup responsibility to `nvmf_create_ctrl()`).
```diff
diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c
index c52446013388..d5a383766b34 100644
--- a/drivers/nvme/host/fc.c
+++ b/drivers/nvme/host/fc.c
@@ -3564,8 +3564,10 @@ nvme_fc_init_ctrl(struct device *dev, struct nvmf_ctrl_options *opts,
return ERR_CAST(ctrl);
ret = nvme_add_ctrl(&ctrl->ctrl);
- if (ret)
+ if (ret) {
+ ctrl->ctrl.opts = NULL;
goto out_put_ctrl;
+ }
ret = nvme_alloc_admin_tag_set(&ctrl->ctrl, &ctrl->admin_tag_set,
&nvme_fc_admin_mq_ops,
```
---
### Conclusion
This report is **fully actionable and self-consistent**. The three stack traces align perfectly with the lifecycle of the `struct nvmf_ctrl_options` object, and the root cause is a clear error-handling bug introduced during the refactoring of the NVMe controller initialization/addition sequence.
|
| 5/2 |
2026/08/11 02:03 |
llm |
expert |
0m
Model:
Tokens:
input: 9682
output: 41
thoughts: 1960
Thoughts: **KASAN Slab-Use-After-Free Bug Analysis**
Okay, here's what I'm seeing. We've got a KASAN slab-use-after-free bug report, which means a pointer is being used *after* the memory it pointed to has been freed. First, I'm going to look at the three provided stack traces.
The "bad memory access stack" gives us the crash point: `nvmf_free_options` in `drivers/nvme/host/fabrics.c`, specifically at line 1284. The crash occurs during a read of size 8 at address `ffff88801f4a2550`.
Then, we have the "allocation stack", showing the memory that was allocated via `nvmf_create_ctrl` (called from `nvmf_dev_write`) using `kzalloc`. This tells me the memory in question originated here.
Finally, the "free stack" shows the deallocation path: the same memory allocated in `nvmf_create_ctrl` is freed by `nvme_fc_ctrl_free` or, more specifically, `nvme_fc_ctrl_put`. `nvme_free_ctrl` which is also called by `nvmf_create_ctrl`. Crucially, this goes through `kfree`, the general-purpose kernel free. It all makes sense, as the object is freed through the kernel memory management.
Hmm, this `FAULT_INJECTION` message is important. It indicates that a failure was *forced* during the allocation (`__kmalloc_node_track_caller_noprof`). The specific fault injection is "failslab," which is designed to simulate memory allocation failures. Interesting. This means that a memory allocation failure might be involved, which is what's triggered the crash. The fault injection occurred in `nvme_add_ctrl` which is called in `nvme_fc_init_ctrl`.
I need to correlate these. The bug seems to be that memory freed by `nvme_free_ctrl` (or possibly the functions it calls) in the same call stack from the allocation is being used later in `nvmf_free_options`.
Now, I'm diving into the code. I'm going to pinpoint the locations I identified earlier: `drivers/nvme/host/fabrics.c` around lines 1284, 1315, 1359, 1374, and 1406, and also `drivers/nvme/host/fc.c` lines 3554, 3605, and 3723. I'm going to see exactly what's going on in these functions, to understand how the allocation, use, and free operations are related, what's failing, and why this is only happening in the presence of a forced fault. I need to understand the control flow between these calls to see if there's a missed reference count or a double-free, or a simple race condition. Let's start with `read-file` and/or `codesearch-definition-source`.
|
| 6/2 |
2026/08/11 02:03 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/nvme/host/fabrics.c Name:nvmf_create_ctrl]
Results: map[SourceCode:1307: static struct nvme_ctrl *
1308: nvmf_create_ctrl(struct device *dev, const char *buf)
1309: {
1310: struct nvmf_ctrl_options *opts;
1311: struct nvmf_transport_ops *ops;
1312: struct nvme_ctrl *ctrl;
1313: int ret;
1314:
1315: opts = kzalloc_obj(*opts);
1316: if (!opts)
1317: return ERR_PTR(-ENOMEM);
1318:
1319: ret = nvmf_parse_options(opts, buf);
1320: if (ret)
1321: goto out_free_opts;
1322:
1323:
1324: request_module("nvme-%s", opts->transport);
1325:
1326: /*
1327: * Check the generic options first as we need a valid transport for
1328: * the lookup below. Then clear the generic flags so that transport
1329: * drivers don't have to care about them.
1330: */
1331: ret = nvmf_check_required_opts(opts, NVMF_REQUIRED_OPTS);
1332: if (ret)
1333: goto out_free_opts;
1334: opts->mask &= ~NVMF_REQUIRED_OPTS;
1335:
1336: down_read(&nvmf_transports_rwsem);
1337: ops = nvmf_lookup_transport(opts);
1338: if (!ops) {
1339: pr_info("no handler found for transport %s.\n",
1340: opts->transport);
1341: ret = -EINVAL;
1342: goto out_unlock;
1343: }
1344:
1345: if (!try_module_get(ops->module)) {
1346: ret = -EBUSY;
1347: goto out_unlock;
1348: }
1349: up_read(&nvmf_transports_rwsem);
1350:
1351: ret = nvmf_check_required_opts(opts, ops->required_opts);
1352: if (ret)
1353: goto out_module_put;
1354: ret = nvmf_check_allowed_opts(opts, NVMF_ALLOWED_OPTS |
1355: ops->allowed_opts | ops->required_opts);
1356: if (ret)
1357: goto out_module_put;
1358:
1359: ctrl = ops->create_ctrl(dev, opts);
1360: if (IS_ERR(ctrl)) {
1361: ret = PTR_ERR(ctrl);
1362: goto out_module_put;
1363: }
1364:
1365: module_put(ops->module);
1366: return ctrl;
1367:
1368: out_module_put:
1369: module_put(ops->module);
1370: goto out_free_opts;
1371: out_unlock:
1372: up_read(&nvmf_transports_rwsem);
1373: out_free_opts:
1374: nvmf_free_options(opts);
1375: return ERR_PTR(ret);
1376: }
SourceFile:drivers/nvme/host/fabrics.c]
|
| 7/2 |
2026/08/11 02:03 |
llm |
expert |
0m
Model:
Tokens:
input: 13367
output: 42
thoughts: 229
Thoughts: **NVMe Fabric Controller Creation – Memory Management Head-Scratch**
Okay, here we go again, diving into NVMe fabric controller creation. First thing's first, I'm checking out `nvmf_create_ctrl` in `drivers/nvme/host/fabrics.c`. Line 1315: `opts = kzalloc_obj(*opts);` - right, the allocation stack is here. It uses `kzalloc_obj`, so that's a good start for preventing uninitialized data issues. Now, line 1359 calls `ctrl = ops->create_ctrl(dev, opts);` which translates to a call to `nvme_fc_create_ctrl`. Alright, standard procedure.
Now, here's where my attention is piqued. If `ops->create_ctrl` returns an error, we jump to `out_module_put` then `out_free_opts`, leading ultimately to `nvmf_free_options(opts);` on line 1374. But wait a second! The crucial question is: *did* `nvme_fc_create_ctrl` (which is `ops->create_ctrl` in this context) free `opts` itself upon an error return, or did it essentially "take ownership" of `opts` for later freeing in the error path? Is it supposed to free it only if everything goes right, and expects the `nvmf_create_ctrl` function to take care of freeing on failure? That's what I need to verify. I need to understand this memory management boundary. Let's delve into the guts of `nvme_fc_create_ctrl` within `drivers/nvme/host/fc.c`. This is where the answer lies.
|
| 8/2 |
2026/08/11 02:03 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/nvme/host/fc.c Name:nvme_fc_create_ctrl]
Results: map[SourceCode:3684: static struct nvme_ctrl *
3685: nvme_fc_create_ctrl(struct device *dev, struct nvmf_ctrl_options *opts)
3686: {
3687: struct nvme_fc_lport *lport;
3688: struct nvme_fc_rport *rport;
3689: struct nvme_ctrl *ctrl;
3690: struct nvmet_fc_traddr laddr = { 0L, 0L };
3691: struct nvmet_fc_traddr raddr = { 0L, 0L };
3692: unsigned long flags;
3693: int ret;
3694:
3695: ret = nvme_fc_parse_traddr(&raddr, opts->traddr, NVMF_TRADDR_SIZE);
3696: if (ret || !raddr.nn || !raddr.pn)
3697: return ERR_PTR(-EINVAL);
3698:
3699: ret = nvme_fc_parse_traddr(&laddr, opts->host_traddr, NVMF_TRADDR_SIZE);
3700: if (ret || !laddr.nn || !laddr.pn)
3701: return ERR_PTR(-EINVAL);
3702:
3703: /* find the host and remote ports to connect together */
3704: spin_lock_irqsave(&nvme_fc_lock, flags);
3705: list_for_each_entry(lport, &nvme_fc_lport_list, port_list) {
3706: if (lport->localport.node_name != laddr.nn ||
3707: lport->localport.port_name != laddr.pn ||
3708: lport->localport.port_state != FC_OBJSTATE_ONLINE)
3709: continue;
3710:
3711: list_for_each_entry(rport, &lport->endp_list, endp_list) {
3712: if (rport->remoteport.node_name != raddr.nn ||
3713: rport->remoteport.port_name != raddr.pn ||
3714: rport->remoteport.port_state != FC_OBJSTATE_ONLINE)
3715: continue;
3716:
3717: /* if fail to get reference fall through. Will error */
3718: if (!nvme_fc_rport_get(rport))
3719: break;
3720:
3721: spin_unlock_irqrestore(&nvme_fc_lock, flags);
3722:
3723: ctrl = nvme_fc_init_ctrl(dev, opts, lport, rport);
3724: if (IS_ERR(ctrl))
3725: nvme_fc_rport_put(rport);
3726: return ctrl;
3727: }
3728: }
3729: spin_unlock_irqrestore(&nvme_fc_lock, flags);
3730:
3731: pr_warn("%s: %s - %s combination not found\n",
3732: __func__, opts->traddr, opts->host_traddr);
3733: return ERR_PTR(-ENOENT);
3734: }
SourceFile:drivers/nvme/host/fc.c]
|
| 9/2 |
2026/08/11 02:03 |
llm |
expert |
0m
Model:
Tokens:
input: 14960
output: 42
thoughts: 63
Thoughts: Here's my summary, as if I were thinking it:
**Digging into the NVMe/FC Initialization**
Alright, so `nvme_fc_create_ctrl` is the key function here. My initial thought is it's gotta be doing the heavy lifting to create the NVMe/FC controller. Now, I see it immediately calls `nvme_fc_init_ctrl(dev, opts, lport, rport)`. That makes perfect sense; `create_ctrl` probably sets up some initial data and then *passes* the actual initialization to this specific init function.
Time to dive deeper into `nvme_fc_init_ctrl`. I'll want to check its implementation in `drivers/nvme/host/fc.c` to see how it sets up this controller. It will likely deal with the device, configuration options, and the local and remote ports. I want to see how these elements are integrated to make the controller come to life. Gotta get into the weeds of how they do this and if they are following best practices for FC integration.
|
| 10/2 |
2026/08/11 02:03 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/nvme/host/fc.c Name:nvme_fc_init_ctrl]
Results: map[SourceCode:3542: static struct nvme_ctrl *
3543: nvme_fc_init_ctrl(struct device *dev, struct nvmf_ctrl_options *opts,
3544: struct nvme_fc_lport *lport, struct nvme_fc_rport *rport)
3545: {
3546: struct nvme_fc_ctrl *ctrl;
3547: unsigned long flags;
3548: int ret;
3549:
3550: ctrl = nvme_fc_alloc_ctrl(dev, opts, lport, rport);
3551: if (IS_ERR(ctrl))
3552: return ERR_CAST(ctrl);
3553:
3554: ret = nvme_add_ctrl(&ctrl->ctrl);
3555: if (ret)
3556: goto out_put_ctrl;
3557:
3558: ret = nvme_alloc_admin_tag_set(&ctrl->ctrl, &ctrl->admin_tag_set,
3559: &nvme_fc_admin_mq_ops,
3560: struct_size_t(struct nvme_fcp_op_w_sgl, priv,
3561: ctrl->lport->ops->fcprqst_priv_sz));
3562: if (ret)
3563: goto fail_ctrl;
3564:
3565: spin_lock_irqsave(&rport->lock, flags);
3566: list_add_tail(&ctrl->ctrl_list, &rport->ctrl_list);
3567: spin_unlock_irqrestore(&rport->lock, flags);
3568:
3569: if (!nvme_change_ctrl_state(&ctrl->ctrl, NVME_CTRL_CONNECTING)) {
3570: dev_err(ctrl->ctrl.device,
3571: "NVME-FC{%d}: failed to init ctrl state\n", ctrl->cnum);
3572: goto fail_ctrl;
3573: }
3574:
3575: if (!queue_delayed_work(nvme_wq, &ctrl->connect_work, 0)) {
3576: dev_err(ctrl->ctrl.device,
3577: "NVME-FC{%d}: failed to schedule initial connect\n",
3578: ctrl->cnum);
3579: goto fail_ctrl;
3580: }
3581:
3582: flush_delayed_work(&ctrl->connect_work);
3583:
3584: dev_info(ctrl->ctrl.device,
3585: "NVME-FC{%d}: new ctrl: NQN \"%s\", hostnqn: %s\n",
3586: ctrl->cnum, nvmf_ctrl_subsysnqn(&ctrl->ctrl), opts->host->nqn);
3587:
3588: return &ctrl->ctrl;
3589:
3590: fail_ctrl:
3591: nvme_change_ctrl_state(&ctrl->ctrl, NVME_CTRL_DELETING);
3592: cancel_work_sync(&ctrl->ioerr_work);
3593: cancel_work_sync(&ctrl->ctrl.reset_work);
3594: cancel_delayed_work_sync(&ctrl->connect_work);
3595:
3596: ctrl->ctrl.opts = NULL;
3597:
3598: if (ctrl->ctrl.admin_tagset)
3599: nvme_remove_admin_tag_set(&ctrl->ctrl);
3600: /* initiate nvme ctrl ref counting teardown */
3601: nvme_uninit_ctrl(&ctrl->ctrl);
3602:
3603: out_put_ctrl:
3604: /* Remove core ctrl ref. */
3605: nvme_put_ctrl(&ctrl->ctrl);
3606:
3607: /* as we're past the point where we transition to the ref
3608: * counting teardown path, if we return a bad pointer here,
3609: * the calling routine, thinking it's prior to the
3610: * transition, will do an rport put. Since the teardown
3611: * path also does a rport put, we do an extra get here to
3612: * so proper order/teardown happens.
3613: */
3614: nvme_fc_rport_get(rport);
3615:
3616: return ERR_PTR(-EIO);
3617: }
SourceFile:drivers/nvme/host/fc.c]
|
| 11/2 |
2026/08/11 02:03 |
llm |
expert |
0m
Model:
Tokens:
input: 16577
output: 42
thoughts: 114
Thoughts: **Tracing the NVMe-FC Controller Initialization**
Okay, let's break this down meticulously. We're looking at `nvme_fc_init_ctrl`, specifically the allocation of the controller. The code snippet starts with `ctrl = nvme_fc_alloc_ctrl(dev, opts, lport, rport);` and immediately checks if `ctrl` is an error using `IS_ERR(ctrl)`.
My immediate question is, what happens inside `nvme_fc_alloc_ctrl` with the `opts` parameter? Does it actually store those options within the allocated `ctrl` structure, potentially as `ctrl->ctrl.opts`? That's key for how this controller is configured later, and if `opts` isn't saved, this will be a problem. Time to dive into the definition of `nvme_fc_alloc_ctrl`. Let's see what's really going on in that function.
|
| 12/2 |
2026/08/11 02:03 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/nvme/host/fc.c Name:nvme_fc_alloc_ctrl]
Results: map[SourceCode:3434: static struct nvme_fc_ctrl *
3435: nvme_fc_alloc_ctrl(struct device *dev, struct nvmf_ctrl_options *opts,
3436: struct nvme_fc_lport *lport, struct nvme_fc_rport *rport)
3437: {
3438: struct nvme_fc_ctrl *ctrl;
3439: int ret, idx, ctrl_loss_tmo;
3440:
3441: if (!(rport->remoteport.port_role &
3442: (FC_PORT_ROLE_NVME_DISCOVERY | FC_PORT_ROLE_NVME_TARGET))) {
3443: ret = -EBADR;
3444: goto out_fail;
3445: }
3446:
3447: if (!opts->duplicate_connect &&
3448: nvme_fc_existing_controller(rport, opts)) {
3449: ret = -EALREADY;
3450: goto out_fail;
3451: }
3452:
3453: ctrl = kzalloc_obj(*ctrl);
3454: if (!ctrl) {
3455: ret = -ENOMEM;
3456: goto out_fail;
3457: }
3458:
3459: idx = ida_alloc(&nvme_fc_ctrl_cnt, GFP_KERNEL);
3460: if (idx < 0) {
3461: ret = -ENOSPC;
3462: goto out_free_ctrl;
3463: }
3464:
3465: /*
3466: * if ctrl_loss_tmo is being enforced and the default reconnect delay
3467: * is being used, change to a shorter reconnect delay for FC.
3468: */
3469: if (opts->max_reconnects != -1 &&
3470: opts->reconnect_delay == NVMF_DEF_RECONNECT_DELAY &&
3471: opts->reconnect_delay > NVME_FC_DEFAULT_RECONNECT_TMO) {
3472: ctrl_loss_tmo = opts->max_reconnects * opts->reconnect_delay;
3473: opts->reconnect_delay = NVME_FC_DEFAULT_RECONNECT_TMO;
3474: opts->max_reconnects = DIV_ROUND_UP(ctrl_loss_tmo,
3475: opts->reconnect_delay);
3476: }
3477:
3478: ctrl->ctrl.opts = opts;
3479: ctrl->ctrl.nr_reconnects = 0;
3480: atomic_long_set(&ctrl->ctrl.acc_reconnects, 0);
3481: INIT_LIST_HEAD(&ctrl->ctrl_list);
3482: ctrl->lport = lport;
3483: ctrl->rport = rport;
3484: ctrl->dev = lport->dev;
3485: ctrl->cnum = idx;
3486: ctrl->ioq_live = false;
3487: init_waitqueue_head(&ctrl->ioabort_wait);
3488:
3489: get_device(ctrl->dev);
3490: kref_init(&ctrl->ref);
3491:
3492: INIT_WORK(&ctrl->ctrl.reset_work, nvme_fc_reset_ctrl_work);
3493: INIT_DELAYED_WORK(&ctrl->connect_work, nvme_fc_connect_ctrl_work);
3494: INIT_WORK(&ctrl->ioerr_work, nvme_fc_ctrl_ioerr_work);
3495: spin_lock_init(&ctrl->lock);
3496:
3497: /* io queue count */
3498: ctrl->ctrl.queue_count = min_t(unsigned int,
3499: opts->nr_io_queues,
3500: lport->ops->max_hw_queues);
3501: ctrl->ctrl.queue_count++; /* +1 for admin queue */
3502:
3503: ctrl->ctrl.sqsize = opts->queue_size - 1;
3504: ctrl->ctrl.kato = opts->kato;
3505: ctrl->ctrl.cntlid = 0xffff;
3506:
3507: ret = -ENOMEM;
3508: ctrl->queues = kzalloc_objs(struct nvme_fc_queue,
3509: ctrl->ctrl.queue_count);
3510: if (!ctrl->queues)
3511: goto out_free_ida;
3512:
3513: nvme_fc_init_queue(ctrl, 0);
3514:
3515: /*
3516: * Would have been nice to init io queues tag set as well.
3517: * However, we require interaction from the controller
3518: * for max io queue count before we can do so.
3519: * Defer this to the connect path.
3520: */
3521:
3522: ret = nvme_init_ctrl(&ctrl->ctrl, dev, &nvme_fc_ctrl_ops, 0);
3523: if (ret)
3524: goto out_free_queues;
3525: if (lport->dev)
3526: ctrl->ctrl.numa_node = dev_to_node(lport->dev);
3527:
3528: return ctrl;
3529:
3530: out_free_queues:
3531: kfree(ctrl->queues);
3532: out_free_ida:
3533: put_device(ctrl->dev);
3534: ida_free(&nvme_fc_ctrl_cnt, ctrl->cnum);
3535: out_free_ctrl:
3536: kfree(ctrl);
3537: out_fail:
3538: /* exit via here doesn't follow ctlr ref points */
3539: return ERR_PTR(ret);
3540: }
SourceFile:drivers/nvme/host/fc.c]
|
| 13/2 |
2026/08/11 02:03 |
llm |
expert |
0m
Model:
Tokens:
input: 18670
output: 40
thoughts: 467
Thoughts: **Dissecting the NVMe Controller Initialization Failure**
Okay, so I'm looking at this NVMe driver code, and I see this assignment: `ctrl->ctrl.opts = opts;` (line 3478). Right away, it clicks – `ctrl` is taking ownership of `opts`. I need to understand the full lifecycle. I'm going to jump into the `nvme_init_ctrl` function in `drivers/nvme/host/core.c` and trace the error handling flow, just to be sure.
Aha! Now I see a failure path in `nvme_fc_init_ctrl`:
```c
ret = nvme_add_ctrl(&ctrl->ctrl);
if (ret)
goto out_put_ctrl;
```
Interesting, `nvme_add_ctrl` is called and upon failure, it jumps to `out_put_ctrl`. Let me dive into `nvme_add_ctrl` now.
And hold on... I think I'm picking up a problem here. Checking the FAULT_INJECTION stack trace in this situation.
```
kvasprintf+0xeb/0x1a0 lib/kasprintf.c:25
kobject_set_name_vargs+0x61/0x110 lib/kobject.c:274
dev_set_name+0xe2/0x140 drivers/base/core.c:3560
nvme_add_ctrl+0x6e/0x270 drivers/nvme/host/core.c:5248
nvme_fc_init_ctrl+0xcb0/0x1450 drivers/nvme/host/fc.c:3554
```
It looks like the `nvme_add_ctrl` is failing because `dev_set_name` is failing, likely due to a failslab fault injection in `kvasprintf`. That means `nvme_add_ctrl` is returning an error – looks like `-ENOMEM` is likely.
Consequently, `nvme_fc_init_ctrl` hits the `out_put_ctrl` label:
```c
out_put_ctrl:
/* Remove core ctrl ref. */
nvme_put_ctrl(&ctrl->ctrl);
```
Okay, so it calls `nvme_put_ctrl` on the controller. I need to understand what this means to correctly diagnose the problem. Time to track down `nvme_put_ctrl`.
|
| 14/2 |
2026/08/11 02:04 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/nvme/host/fc.c Name:nvme_put_ctrl]
Results: map[SourceCode: 831: static inline void nvme_put_ctrl(struct nvme_ctrl *ctrl)
832: {
833: put_device(ctrl->device);
834: }
SourceFile:drivers/nvme/host/nvme.h]
|
| 15/2 |
2026/08/11 02:04 |
llm |
expert |
0m
Model:
Tokens:
input: 19833
output: 40
thoughts: 116
Thoughts: **Deconstructing the NVMe Controller Release Path**
Okay, so I'm tracing this `nvme_put_ctrl` function... right, it calls `put_device(ctrl->device)`. Makes sense. Now, `ctrl->device` is *that* `struct device` we're using to represent the controller. When the reference count on that device object hits zero, `device_release` gets triggered. That's the key. `device_release` in turn calls the *release callback* associated with the device. I need to figure out what that release callback does for an NVMe controller.
Where did that get set? Let's go back to where the `struct device` is initialized for the controller… Ah, got it. In `nvme_init_ctrl`, the device is initialized. Let's dig into `nvme_init_ctrl` in `drivers/nvme/host/core.c` and find that release function. That should tell me how the controller is actually cleaned up when it's no longer needed.
|
| 16/2 |
2026/08/11 02:04 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/nvme/host/core.c Name:nvme_init_ctrl]
Results: map[SourceCode:5152: /*
5153: * Initialize a NVMe controller structures. This needs to be called during
5154: * earliest initialization so that we have the initialized structured around
5155: * during probing.
5156: *
5157: * On success, the caller must use the nvme_put_ctrl() to release this when
5158: * needed, which also invokes the ops->free_ctrl() callback.
5159: */
5160: int nvme_init_ctrl(struct nvme_ctrl *ctrl, struct device *dev,
5161: const struct nvme_ctrl_ops *ops, unsigned long quirks)
5162: {
5163: int ret;
5164:
5165: WRITE_ONCE(ctrl->state, NVME_CTRL_NEW);
5166: ctrl->passthru_err_log_enabled = false;
5167: clear_bit(NVME_CTRL_FAILFAST_EXPIRED, &ctrl->flags);
5168: spin_lock_init(&ctrl->lock);
5169: mutex_init(&ctrl->namespaces_lock);
5170:
5171: ret = init_srcu_struct(&ctrl->srcu);
5172: if (ret)
5173: return ret;
5174:
5175: mutex_init(&ctrl->scan_lock);
5176: INIT_LIST_HEAD(&ctrl->namespaces);
5177: xa_init(&ctrl->cels);
5178: ctrl->dev = dev;
5179: ctrl->ops = ops;
5180: ctrl->quirks = quirks;
5181: ctrl->numa_node = NUMA_NO_NODE;
5182: INIT_WORK(&ctrl->scan_work, nvme_scan_work);
5183: INIT_WORK(&ctrl->async_event_work, nvme_async_event_work);
5184: INIT_WORK(&ctrl->fw_act_work, nvme_fw_act_work);
5185: INIT_WORK(&ctrl->delete_work, nvme_delete_ctrl_work);
5186: init_waitqueue_head(&ctrl->state_wq);
5187:
5188: INIT_DELAYED_WORK(&ctrl->ka_work, nvme_keep_alive_work);
5189: INIT_DELAYED_WORK(&ctrl->failfast_work, nvme_failfast_work);
5190: memset(&ctrl->ka_cmd, 0, sizeof(ctrl->ka_cmd));
5191: ctrl->ka_cmd.common.opcode = nvme_admin_keep_alive;
5192: ctrl->ka_last_check_time = jiffies;
5193: ctrl->admin_timeout = NVME_ADMIN_TIMEOUT;
5194: ctrl->io_timeout = NVME_IO_TIMEOUT;
5195:
5196: BUILD_BUG_ON(NVME_DSM_MAX_RANGES * sizeof(struct nvme_dsm_range) >
5197: PAGE_SIZE);
5198: ctrl->discard_page = alloc_page(GFP_KERNEL);
5199: if (!ctrl->discard_page) {
5200: ret = -ENOMEM;
5201: goto out;
5202: }
5203:
5204: ret = ida_alloc(&nvme_instance_ida, GFP_KERNEL);
5205: if (ret < 0)
5206: goto out;
5207: ctrl->instance = ret;
5208:
5209: ret = nvme_auth_init_ctrl(ctrl);
5210: if (ret)
5211: goto out_release_instance;
5212:
5213: nvme_mpath_init_ctrl(ctrl);
5214:
5215: device_initialize(&ctrl->ctrl_device);
5216: ctrl->device = &ctrl->ctrl_device;
5217: ctrl->device->devt = MKDEV(MAJOR(nvme_ctrl_base_chr_devt),
5218: ctrl->instance);
5219: ctrl->device->class = &nvme_class;
5220: ctrl->device->parent = ctrl->dev;
5221: if (ops->dev_attr_groups)
5222: ctrl->device->groups = ops->dev_attr_groups;
5223: else
5224: ctrl->device->groups = nvme_dev_attr_groups;
5225: ctrl->device->release = nvme_free_ctrl;
5226: dev_set_drvdata(ctrl->device, ctrl);
5227:
5228: return ret;
5229:
5230: out_release_instance:
5231: ida_free(&nvme_instance_ida, ctrl->instance);
5232: out:
5233: if (ctrl->discard_page)
5234: __free_page(ctrl->discard_page);
5235: cleanup_srcu_struct(&ctrl->srcu);
5236: return ret;
5237: }
SourceFile:drivers/nvme/host/core.c]
|
| 17/2 |
2026/08/11 02:04 |
llm |
expert |
0m
Model:
Tokens:
input: 21657
output: 40
thoughts: 77
Thoughts: Okay, let me break this down.
**Diving into the NVMe Controller Release Process**
Right, so I'm tracing the NVMe controller's life cycle, and I've hit a key point: `ctrl->device->release = nvme_free_ctrl;` on line 5225. Aha! That little assignment is crucial. It means that when the device is finally released, specifically when `put_device(ctrl->device)` gets called, it's *`nvme_free_ctrl`* that's going to be the cleanup function. Makes sense, that's where the controller-specific freeing happens. Now, I need to understand what `nvme_free_ctrl` actually *does*. Time to jump over to `drivers/nvme/host/core.c` and take a closer look at the implementation of `nvme_free_ctrl`. Let's see how it handles the graceful exit.
|
| 18/2 |
2026/08/11 02:04 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/nvme/host/core.c Name:nvme_free_ctrl]
Results: map[SourceCode:5119: static void nvme_free_ctrl(struct device *dev)
5120: {
5121: struct nvme_ctrl *ctrl =
5122: container_of(dev, struct nvme_ctrl, ctrl_device);
5123: struct nvme_subsystem *subsys = ctrl->subsys;
5124:
5125: if (ctrl->admin_q)
5126: blk_put_queue(ctrl->admin_q);
5127: if (ctrl->fabrics_q)
5128: blk_put_queue(ctrl->fabrics_q);
5129: if (!subsys || ctrl->instance != subsys->instance)
5130: ida_free(&nvme_instance_ida, ctrl->instance);
5131: nvme_free_cels(ctrl);
5132: nvme_mpath_uninit(ctrl);
5133: cleanup_srcu_struct(&ctrl->srcu);
5134: nvme_auth_stop(ctrl);
5135: nvme_auth_free(ctrl);
5136: __free_page(ctrl->discard_page);
5137: free_opal_dev(ctrl->opal_dev);
5138:
5139: if (subsys) {
5140: mutex_lock(&nvme_subsystems_lock);
5141: list_del(&ctrl->subsys_entry);
5142: sysfs_remove_link(&subsys->dev.kobj, dev_name(ctrl->device));
5143: mutex_unlock(&nvme_subsystems_lock);
5144: }
5145:
5146: ctrl->ops->free_ctrl(ctrl);
5147:
5148: if (subsys)
5149: nvme_put_subsystem(subsys);
5150: }
SourceFile:drivers/nvme/host/core.c]
|
| 19/2 |
2026/08/11 02:04 |
llm |
expert |
0m
Model:
Tokens:
input: 22525
output: 66
thoughts: 79
Thoughts: **Deconstructing the NVMe Controller Freeing Process**
Okay, so I'm looking at this `nvme_free_ctrl` function. It's calling `ctrl->ops->free_ctrl(ctrl)` right on line 5146. Seems straightforward enough. The key here is figuring out what `ctrl->ops` actually *is* in this specific context. Aha, for Fibre Channel (FC) devices, that `ctrl->ops` pointer should be pointing to `nvme_fc_ctrl_ops`. Now I need to quickly locate the implementation of `nvme_fc_ctrl_ops`. Looks like that should be defined somewhere in `drivers/nvme/host/fc.c`. Time to dive into that file and see how the FC-specific controller freeing is handled.
|
| 20/2 |
2026/08/11 02:04 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile:drivers/nvme/host/fc.c IncludeSnippetLines:10 Name:nvme_fc_ctrl_ops SourceTreePrefix:drivers/nvme/host/fc.c]
Results: map[References:<nil> TruncatedOutput:false]
|
| 21/2 |
2026/08/11 02:04 |
llm |
expert |
0m
Model:
Tokens:
input: 22856
output: 42
thoughts: 71
Thoughts: **Debugging the Missing References**
Okay, so I ran a search and got nothing back, which means the initial approach using `SourceTreePrefix` probably wasn't correct; it's likely expecting a directory prefix and I'm not giving it that. Hmmm, what are my options? I could try a direct `grep` for the function name `nvme_fc_ctrl_ops`, that's a quick and dirty way to find instances. Alternatively, and this seems like the more robust approach given the tools available, I'll leverage `codesearch-definition-source` and see if that gives me a clear path. Let's start with that on `nvme_fc_ctrl_ops`. I'm confident that this will give me something to work with.
|
| 22/2 |
2026/08/11 02:04 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/nvme/host/fc.c Name:nvme_fc_ctrl_ops]
Results: map[SourceCode:3363: static const struct nvme_ctrl_ops nvme_fc_ctrl_ops = {
3364: .name = "fc",
3365: .module = THIS_MODULE,
3366: .flags = NVME_F_FABRICS,
3367: .reg_read32 = nvmf_reg_read32,
3368: .reg_read64 = nvmf_reg_read64,
3369: .reg_write32 = nvmf_reg_write32,
3370: .subsystem_reset = nvmf_subsystem_reset,
3371: .free_ctrl = nvme_fc_free_ctrl,
3372: .submit_async_event = nvme_fc_submit_async_event,
3373: .delete_ctrl = nvme_fc_delete_ctrl,
3374: .get_address = nvmf_get_address,
3375: .get_virt_boundary = nvmf_get_virt_boundary,
3376: };
SourceFile:drivers/nvme/host/fc.c]
|
| 23/2 |
2026/08/11 02:04 |
llm |
expert |
0m
Model:
Tokens:
input: 23456
output: 42
thoughts: 44
Thoughts: Okay, here's my interpretation of that thought process, as if I were having it:
**Deconstructing the NVMe FC Control Path**
Alright, let's see... so `.free_ctrl` is assigned to `nvme_fc_free_ctrl`. That's the function pointer for freeing the controller structure in the Fibre Channel implementation, interesting. Now, I need to understand what that actually *does*. I'll jump over to the `drivers/nvme/host/fc.c` file and dive into `nvme_fc_free_ctrl` to get the details. Let's see how they clean up the Fibre Channel resources when a controller is being released. It's probably going to involve de-registering from the fabric, freeing associated memory, and potentially handling any outstanding I/O. Time to dig in!
|
| 24/2 |
2026/08/11 02:04 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/nvme/host/fc.c Name:nvme_fc_free_ctrl]
Results: map[SourceCode:2390: /*
2391: * All accesses from nvme core layer done - can now free the
2392: * controller. Called after last nvme_put_ctrl() call
2393: */
2394: static void
2395: nvme_fc_free_ctrl(struct nvme_ctrl *nctrl)
2396: {
2397: struct nvme_fc_ctrl *ctrl = to_fc_ctrl(nctrl);
2398:
2399: WARN_ON(nctrl != &ctrl->ctrl);
2400:
2401: nvme_fc_ctrl_put(ctrl);
2402: }
SourceFile:drivers/nvme/host/fc.c]
|
| 25/2 |
2026/08/11 02:04 |
llm |
expert |
0m
Model:
Tokens:
input: 23939
output: 42
thoughts: 126
Thoughts: Okay, let's break this down.
**Diving into the NVMe-FC Control Flow**
So, `nvme_fc_ctrl_put`... right, that's where the reference count on the NVMe-FC controller is being dropped. Following the call stack, I see it's calling `nvme_fc_ctrl_free`. And immediately, I can see that `nvme_fc_ctrl_free` is handling the core freeing of the resource, but first, a `kref_put` call. Got it. That suggests this controller object is likely using a kernel reference count (`kref`) to manage its lifecycle, and `kref_put` is how that reference is released when the controller is no longer in use. This implies a careful balancing act of resource allocation and deallocation to prevent memory leaks and dangling pointers. Let's delve deeper into `nvme_fc_ctrl_free` to understand the specific cleanup actions.
|
| 26/2 |
2026/08/11 02:04 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/nvme/host/fc.c Name:nvme_fc_ctrl_put]
Results: map[SourceCode:2378: static void
2379: nvme_fc_ctrl_put(struct nvme_fc_ctrl *ctrl)
2380: {
2381: kref_put(&ctrl->ref, nvme_fc_ctrl_free);
2382: }
SourceFile:drivers/nvme/host/fc.c]
|
| 27/2 |
2026/08/11 02:04 |
llm |
expert |
0m
Model:
Tokens:
input: 24422
output: 42
thoughts: 15
Thoughts: Okay, let's dive into this. Here's my take on `nvme_fc_ctrl_free`:
**Deconstructing `nvme_fc_ctrl_free`**
Alright, so we're looking at `nvme_fc_ctrl_free`. Right off the bat, I'm thinking, this *has* to be the function responsible for releasing resources associated with a Fibre Channel NVMe controller, right? The naming convention strongly suggests that: `nvme_` points to the NVMe domain, `fc_` clearly specifies Fibre Channel, and `ctrl_free`… well, that's almost self-explanatory – it means "free the controller."
Now, the interesting questions start bubbling up. What *exactly* does this function do? What resources are being freed? Is it just memory? Is it things like registered memory regions (MRs) with the HBA? Are there any associated locks that need to be carefully released to avoid deadlocks? What about outstanding I/O? This function must handle graceful shutdown. It probably has to deal with flushing any cached data and ensuring the host sees consistency. How robust is the error handling? Does it check for any race conditions? A good `free` function needs to take into account many circumstances.
I expect a thorough cleanup. It's not just a simple `kfree`. I'm anticipating some intricate interactions with the Fibre Channel HBA driver, probably involving deregistering the controller and releasing associated queues and structures. There'll likely be an attempt to cancel and complete any outstanding requests safely. I'd need to consider what happens if the HBA driver is also in the process of shutting down or being unloaded. This function is likely to be a critical point of failure or resource leak if not implemented correctly. It's a key spot to focus on for stability.
|
| 28/2 |
2026/08/11 02:04 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/nvme/host/fc.c Name:nvme_fc_ctrl_free]
Results: map[SourceCode:2355: static void
2356: nvme_fc_ctrl_free(struct kref *ref)
2357: {
2358: struct nvme_fc_ctrl *ctrl =
2359: container_of(ref, struct nvme_fc_ctrl, ref);
2360: unsigned long flags;
2361:
2362: /* remove from rport list */
2363: spin_lock_irqsave(&ctrl->rport->lock, flags);
2364: list_del(&ctrl->ctrl_list);
2365: spin_unlock_irqrestore(&ctrl->rport->lock, flags);
2366:
2367: kfree(ctrl->queues);
2368:
2369: put_device(ctrl->dev);
2370: nvme_fc_rport_put(ctrl->rport);
2371:
2372: ida_free(&nvme_fc_ctrl_cnt, ctrl->cnum);
2373: if (ctrl->ctrl.opts)
2374: nvmf_free_options(ctrl->ctrl.opts);
2375: kfree(ctrl);
2376: }
SourceFile:drivers/nvme/host/fc.c]
|
| 29/2 |
2026/08/11 02:04 |
llm |
expert |
0m
Model:
Tokens:
input: 25227
output: 31
thoughts: 1003
Thoughts: **Double-Free Bug in NVMe-FC Initialization**
Okay, here's what's happening. I've been tracing the `nvmf_create_ctrl` function and the control flow within the NVMe-FC driver. I've zeroed in on lines 2373-2374 where `nvmf_free_options(ctrl->ctrl.opts)` is called within the `nvme_fc_ctrl_free` function. That's a key observation.
Let's recap the sequence. I see that `nvme_fc_ctrl_free` *does* free `ctrl->ctrl.opts`. Now, I need to see *when* this happens relative to other `opts` frees.
Going back to `nvmf_create_ctrl`, the `ops->create_ctrl` function pointer is called. This turns out to be `nvme_fc_create_ctrl`. Inside that, `nvme_fc_init_ctrl` is called and inside there is a call to `nvme_fc_alloc_ctrl`. This function allocates the controller and critically, sets `ctrl->ctrl.opts = opts;`.
Next, a fault injection in `dev_set_name` causes `nvme_add_ctrl` to fail. Because of this failure, we go into a cleanup path. `nvme_fc_init_ctrl` jumps to `out_put_ctrl`. Here, `nvme_put_ctrl` is called, which in turn calls `put_device(ctrl->device)`. This reduces the refcount and, as part of the refcount cleanup, triggers `nvme_free_ctrl`. That calls `ctrl->ops->free_ctrl(ctrl)`, which is `nvme_fc_free_ctrl`. And *that* eventually calls `nvme_fc_ctrl_free` through a `kref_put`.
And *that* frees `opts` using `nvmf_free_options`!
So, `nvme_fc_init_ctrl` then returns an error. `nvme_fc_create_ctrl` receives this error, cleans up the remote port and also returns an error. Finally, `nvmf_create_ctrl` sees this error and jumps to `out_module_put` and eventually calls `nvmf_free_options(opts)`.
And here's the problem: *`opts` has already been freed by `nvme_fc_ctrl_free`*. Boom! Double-free/use-after-free situation! A classic concurrency bug that must be addressed immediately.
Now, let me check other transports (TCP, RDMA). I need to make sure this isn't a pattern and that these other drivers handle the `opts` lifetime differently. Let's start by searching for their create and free implementations such as `nvme_rdma_create_ctrl` or `nvme_tcp_create_ctrl` and see how they are implemented.
|
| 30/2 |
2026/08/11 02:04 |
tool |
grepper |
0m
Args: map[Expression:\.create_ctrl PathPrefix:drivers/nvme/host/]
Results: map[Output:drivers/nvme/host/fc.c=3737=static struct nvmf_transport_ops nvme_fc_transport = {
--
drivers/nvme/host/fc.c-3741- .allowed_opts = NVMF_OPT_RECONNECT_DELAY | NVMF_OPT_CTRL_LOSS_TMO,
drivers/nvme/host/fc.c:3742: .create_ctrl = nvme_fc_create_ctrl,
drivers/nvme/host/fc.c-3743-};
--
drivers/nvme/host/rdma.c=2361=static struct nvmf_transport_ops nvme_rdma_transport = {
--
drivers/nvme/host/rdma.c-2368- NVMF_OPT_TOS,
drivers/nvme/host/rdma.c:2369: .create_ctrl = nvme_rdma_create_ctrl,
drivers/nvme/host/rdma.c-2370-};
--
drivers/nvme/host/tcp.c=3036=static struct nvmf_transport_ops nvme_tcp_transport = {
--
drivers/nvme/host/tcp.c-3045- NVMF_OPT_KEYRING | NVMF_OPT_TLS_KEY | NVMF_OPT_CONCAT,
drivers/nvme/host/tcp.c:3046: .create_ctrl = nvme_tcp_create_ctrl,
drivers/nvme/host/tcp.c-3047-};
]
|
| 31/2 |
2026/08/11 02:04 |
llm |
expert |
0m
Model:
Tokens:
input: 27296
output: 42
thoughts: 28
Thoughts: Here's my take on the thought process, as requested:
**Diving into `nvme_tcp_create_ctrl`**
Okay, time to dissect `nvme_tcp_create_ctrl`. Specifically, I'm going to look at that function definition in `drivers/nvme/host/tcp.c`. It's where the magic begins, isn't it? This is where the NVMe-oF TCP controller is actually brought to life within the kernel. I need to understand its initialization sequence, how it handles the fundamental resource allocations, and how it sets up the connection-specific data structures. I'm especially keen to see how it interacts with the underlying TCP stack and how it manages the various contexts required for NVMe operations over TCP. This is where the rubber meets the road, so to speak, in establishing that persistent and efficient link. My expertise demands I fully grasp the intricacies here.
|
| 32/2 |
2026/08/11 02:04 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/nvme/host/tcp.c Name:nvme_tcp_create_ctrl]
Results: map[SourceCode:2994: static struct nvme_ctrl *nvme_tcp_create_ctrl(struct device *dev,
2995: struct nvmf_ctrl_options *opts)
2996: {
2997: struct nvme_tcp_ctrl *ctrl;
2998: int ret;
2999:
3000: ctrl = nvme_tcp_alloc_ctrl(dev, opts);
3001: if (IS_ERR(ctrl))
3002: return ERR_CAST(ctrl);
3003:
3004: ret = nvme_add_ctrl(&ctrl->ctrl);
3005: if (ret)
3006: goto out_put_ctrl;
3007:
3008: if (!nvme_change_ctrl_state(&ctrl->ctrl, NVME_CTRL_CONNECTING)) {
3009: WARN_ON_ONCE(1);
3010: ret = -EINTR;
3011: goto out_uninit_ctrl;
3012: }
3013:
3014: ret = nvme_tcp_setup_ctrl(&ctrl->ctrl, true);
3015: if (ret)
3016: goto out_uninit_ctrl;
3017:
3018: dev_info(ctrl->ctrl.device, "new ctrl: NQN \"%s\", addr %pISp, hostnqn: %s\n",
3019: nvmf_ctrl_subsysnqn(&ctrl->ctrl), &ctrl->addr, opts->host->nqn);
3020:
3021: mutex_lock(&nvme_tcp_ctrl_mutex);
3022: list_add_tail(&ctrl->list, &nvme_tcp_ctrl_list);
3023: mutex_unlock(&nvme_tcp_ctrl_mutex);
3024:
3025: return &ctrl->ctrl;
3026:
3027: out_uninit_ctrl:
3028: nvme_uninit_ctrl(&ctrl->ctrl);
3029: out_put_ctrl:
3030: nvme_put_ctrl(&ctrl->ctrl);
3031: if (ret > 0)
3032: ret = -EIO;
3033: return ERR_PTR(ret);
3034: }
SourceFile:drivers/nvme/host/tcp.c]
|
| 33/2 |
2026/08/11 02:04 |
llm |
expert |
0m
Model:
Tokens:
input: 28224
output: 42
thoughts: 58
Thoughts: **Analyzing Controller Creation in NVMe-TCP**
Okay, so I'm looking at `nvme_tcp_create_ctrl` and just realized something important. If `nvme_add_ctrl` fails within that function, `nvme_tcp_create_ctrl` *also* calls `nvme_put_ctrl(&ctrl->ctrl)`. That's critical because it means we're handling the controller lifecycle correctly, even in error scenarios. It seems like we're not leaking resources here, which is exactly what I'd expect to see.
Now, I need to check how `nvme_tcp_alloc_ctrl` handles those passed `opts`. I need to ensure that whatever configurations are passed in are handled correctly by the allocation function. I need to make sure this happens, because this is where my system can be customized. I need to investigate the implications of `opts` further.
|
| 34/2 |
2026/08/11 02:04 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/nvme/host/tcp.c Name:nvme_tcp_alloc_ctrl]
Results: map[SourceCode:2912: static struct nvme_tcp_ctrl *nvme_tcp_alloc_ctrl(struct device *dev,
2913: struct nvmf_ctrl_options *opts)
2914: {
2915: struct nvme_tcp_ctrl *ctrl;
2916: int ret;
2917:
2918: ctrl = kzalloc_obj(*ctrl);
2919: if (!ctrl)
2920: return ERR_PTR(-ENOMEM);
2921:
2922: INIT_LIST_HEAD(&ctrl->list);
2923: ctrl->ctrl.opts = opts;
2924: ctrl->ctrl.queue_count = opts->nr_io_queues + opts->nr_write_queues +
2925: opts->nr_poll_queues + 1;
2926: ctrl->ctrl.sqsize = opts->queue_size - 1;
2927: ctrl->ctrl.kato = opts->kato;
2928:
2929: INIT_DELAYED_WORK(&ctrl->connect_work,
2930: nvme_tcp_reconnect_ctrl_work);
2931: INIT_WORK(&ctrl->err_work, nvme_tcp_error_recovery_work);
2932: INIT_WORK(&ctrl->ctrl.reset_work, nvme_reset_ctrl_work);
2933:
2934: if (!(opts->mask & NVMF_OPT_TRSVCID)) {
2935: opts->trsvcid =
2936: kstrdup(__stringify(NVME_TCP_DISC_PORT), GFP_KERNEL);
2937: if (!opts->trsvcid) {
2938: ret = -ENOMEM;
2939: goto out_free_ctrl;
2940: }
2941: opts->mask |= NVMF_OPT_TRSVCID;
2942: }
2943:
2944: ret = inet_pton_with_scope(&init_net, AF_UNSPEC,
2945: opts->traddr, opts->trsvcid, &ctrl->addr);
2946: if (ret) {
2947: pr_err("malformed address passed: %s:%s\n",
2948: opts->traddr, opts->trsvcid);
2949: goto out_free_ctrl;
2950: }
2951:
2952: if (opts->mask & NVMF_OPT_HOST_TRADDR) {
2953: ret = inet_pton_with_scope(&init_net, AF_UNSPEC,
2954: opts->host_traddr, NULL, &ctrl->src_addr);
2955: if (ret) {
2956: pr_err("malformed src address passed: %s\n",
2957: opts->host_traddr);
2958: goto out_free_ctrl;
2959: }
2960: }
2961:
2962: if (opts->mask & NVMF_OPT_HOST_IFACE) {
2963: if (!__dev_get_by_name(&init_net, opts->host_iface)) {
2964: pr_err("invalid interface passed: %s\n",
2965: opts->host_iface);
2966: ret = -ENODEV;
2967: goto out_free_ctrl;
2968: }
2969: }
2970:
2971: if (!opts->duplicate_connect && nvme_tcp_existing_controller(opts)) {
2972: ret = -EALREADY;
2973: goto out_free_ctrl;
2974: }
2975:
2976: ctrl->queues = kzalloc_objs(*ctrl->queues, ctrl->ctrl.queue_count);
2977: if (!ctrl->queues) {
2978: ret = -ENOMEM;
2979: goto out_free_ctrl;
2980: }
2981:
2982: ret = nvme_init_ctrl(&ctrl->ctrl, dev, &nvme_tcp_ctrl_ops, 0);
2983: if (ret)
2984: goto out_kfree_queues;
2985:
2986: return ctrl;
2987: out_kfree_queues:
2988: kfree(ctrl->queues);
2989: out_free_ctrl:
2990: kfree(ctrl);
2991: return ERR_PTR(ret);
2992: }
SourceFile:drivers/nvme/host/tcp.c]
|
| 35/2 |
2026/08/11 02:04 |
llm |
expert |
0m
Model:
Tokens:
input: 29816
output: 42
thoughts: 32
Thoughts: Here's my thought process, as if I were considering this code:
**Deep Dive on `nvme_tcp_free_ctrl`**
Alright, hold on a second. I need to be absolutely sure about something here. Does `nvme_tcp_free_ctrl` handle the deallocation of `opts`? It's a critical detail, and I'd rather not introduce a memory leak or double-free situation. My gut feeling is it might, but I need to confirm this. The details are fuzzy in my mind, so it's time to dig into the `nvme_tcp_free_ctrl` function itself to be certain of its behavior. I can't leave this to chance. Let's see what's going on in that code.
|
| 36/2 |
2026/08/11 02:04 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/nvme/host/tcp.c Name:nvme_tcp_free_ctrl]
Results: map[SourceCode:2576: static void nvme_tcp_free_ctrl(struct nvme_ctrl *nctrl)
2577: {
2578: struct nvme_tcp_ctrl *ctrl = to_tcp_ctrl(nctrl);
2579:
2580: if (list_empty(&ctrl->list))
2581: goto free_ctrl;
2582:
2583: mutex_lock(&nvme_tcp_ctrl_mutex);
2584: list_del(&ctrl->list);
2585: mutex_unlock(&nvme_tcp_ctrl_mutex);
2586:
2587: nvmf_free_options(nctrl->opts);
2588: free_ctrl:
2589: kfree(ctrl->queues);
2590: kfree(ctrl);
2591: }
SourceFile:drivers/nvme/host/tcp.c]
|
| 37/2 |
2026/08/11 02:04 |
llm |
expert |
0m
Model:
Tokens:
input: 30319
output: 44
thoughts: 316
Thoughts: **NVMe-TCP Controller Cleanup Logic Analysis**
Okay, here's what's running through my mind as I look at this `nvme_tcp_free_ctrl` function. The first thing that jumps out is this conditional: `if (list_empty(&ctrl->list)) goto free_ctrl;`. That means if the list is empty, we skip the call to `nvmf_free_options(nctrl->opts)`. Hmm, that's interesting. So, when *is* `ctrl->list` populated?
Let me look back at the `nvme_tcp_create_ctrl` function. Ah-ha! The list insertion, `list_add_tail(&ctrl->list, &nvme_tcp_ctrl_list)`, only happens at the *very end* of that function, and *only* after a successful controller creation.
This means, if `nvme_tcp_create_ctrl` fails for any reason, the `ctrl->list` will remain empty. Consequently, in `nvme_tcp_free_ctrl`, we *will not* free the `opts` because we'll hit that `goto free_ctrl`. This is key! This implies that `nvmf_create_ctrl` in `drivers/nvme/host/fabrics.c` is *safe* to free the `opts` on an error return path.
Just to be thorough, I should also quickly check `nvme_rdma_free_ctrl` in `drivers/nvme/host/rdma.c` to see if a similar pattern exists. Good to be certain, given the different fabrics involved.
|
| 38/2 |
2026/08/11 02:04 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/nvme/host/rdma.c Name:nvme_rdma_free_ctrl]
Results: map[SourceCode: 968: static void nvme_rdma_free_ctrl(struct nvme_ctrl *nctrl)
969: {
970: struct nvme_rdma_ctrl *ctrl = to_rdma_ctrl(nctrl);
971:
972: if (list_empty(&ctrl->list))
973: goto free_ctrl;
974:
975: mutex_lock(&nvme_rdma_ctrl_mutex);
976: list_del(&ctrl->list);
977: mutex_unlock(&nvme_rdma_ctrl_mutex);
978:
979: nvmf_free_options(nctrl->opts);
980: free_ctrl:
981: kfree(ctrl->queues);
982: kfree(ctrl);
983: }
SourceFile:drivers/nvme/host/rdma.c]
|
| 39/2 |
2026/08/11 02:04 |
llm |
expert |
0m
Model:
Tokens:
input: 31303
output: 45
thoughts: 1183
Thoughts: **Vulnerability Identified: Double Free in NVMe over Fibre Channel Controller Handling**
Okay, this is interesting. I've been tracing the controller free path and found a potential double-free issue in the NVMe over Fibre Channel (FC) code. Specifically, while TCP and RDMA have checks to prevent freeing options if the controller isn't fully initialized, FC's `nvme_fc_ctrl_free` lacks this guard.
Let me break down my thought process:
* First, I confirmed that both TCP and RDMA have checks, skipping `opts` freeing if `ctrl` wasn't fully created. FC, however, doesn't seem to have the same protection.
* I examined `nvme_fc_ctrl_free`. I noticed it calls `nvmf_free_options(ctrl->ctrl.opts)` unconditionally if `ctrl->ctrl.opts` is not NULL.
* Then I looked at where `ctrl->ctrl.opts` gets set. It's assigned `opts` in `nvme_fc_alloc_ctrl`.
* My next step was to find where `ctrl->ctrl.opts` is set to NULL, and I found it in `nvme_fc_init_ctrl`. However, there's a problem: If `nvme_add_ctrl` fails, the code jumps to `out_put_ctrl`. Crucially, `ctrl->ctrl.opts = NULL;` is *bypassed* in this scenario.
* Thus, if `nvme_add_ctrl` fails, `ctrl->ctrl.opts` remains non-NULL. When `nvme_fc_ctrl_free` is called, it unconditionally frees `ctrl->ctrl.opts`. But, if `nvme_add_ctrl` succeeds, `nvmf_create_ctrl` is called, and that might also free the opts.
* I now see the double-free condition! `nvme_fc_ctrl_free` frees `opts` when it shouldn't, causing a double free.
My next steps are to double-check there aren't other places handling `ctrl->ctrl.opts`, or other means that prevent this, and review the git history for `drivers/nvme/host/fc.c` and `drivers/nvme/host/fabrics.c`, with search terms such as `nvme_fc_init_ctrl` or `nvme_fc_ctrl_free`. The details here are clear and precise.
|
| 40/2 |
2026/08/11 02:04 |
tool |
git-log |
0m
Error:
Args: map[Count:10 PathPrefix:drivers/nvme/host/fc.c SymbolName:nvme_fc_init_ctrl]
Results:
|
| 41/2 |
2026/08/11 02:04 |
llm |
expert |
0m
Model:
Tokens:
input: 33088
output: 45
thoughts: 29
Thoughts: Okay, here's my summary, tailored as if I, an expert, am processing these thoughts:
**Navigating Configuration Options**
Alright, so I'm looking at how to configure this particular component. Immediately, I'm noting a conflict. It seems that `SymbolName` and `PathPrefix` are mutually exclusive. Using them together just isn't going to work – probably leads to ambiguous or conflicting behavior, depending on the implementation. So, I have to make a choice. Thinking through the requirements here, and considering the overall architecture... Yes, *that's* the cleaner solution. I'll stick with `SymbolName` and couple it with `SourcePath` instead. That combination offers a more direct and less confusing approach, providing the information needed without the overlap and potential for ambiguity. That feels right.
|
| 42/2 |
2026/08/11 02:04 |
tool |
git-log |
0m
Args: map[Count:10 SourcePath:drivers/nvme/host/fc.c SymbolName:nvme_fc_init_ctrl]
Results: map[Output:d1877cc72703 nvme-fc: release admin tagset if init fails
d3d380eded7e nvme-fc: go straight to connecting state when initializing
1a9e218195a5 nvme: split device add from initialization
72cded7573c8 nvme: fc: split controller bringup handling
c3a846fe4bf3 nvme-fc: show hostnqn when connecting to fc target
d67790ddf021 overflow: Add struct_size_t() helper
98e3528012cd nvme-fc: fix initialization order
6dfba1c09c10 nvme-fc: use the tagset alloc/free helpers
6f8191fdf41d block: simplify disk shutdown
3dd83f4013f0 nvme-fc: replace ida_simple[get|remove] with the simler ida_[alloc|free]
]
|
| 43/2 |
2026/08/11 02:05 |
llm |
expert |
0m
Model:
Tokens:
input: 33579
output: 27
thoughts: 86
Thoughts: Here's my analysis of the situation:
**Investigating a Potential Regression in NVMe Driver Initialization**
Okay, so we've got a problem, and my gut feeling is pointing me towards the commit `1a9e218195a5 nvme: split device add from initialization`. That feels like the right place to start digging. This is likely the commit that refactored the NVMe controller initialization, separating `nvme_init_ctrl` and `nvme_add_ctrl`. It's a prime suspect for introducing a subtle race condition or some initialization sequence issue that's causing this bug.
Time to confirm my suspicions. I'll fire up `git show` and take a look at the code changes in that specific commit. I need to refresh my memory on the modifications made there and see if anything jumps out as a potential cause for this. Gotta be methodical and see if anything stands out.
|
| 44/2 |
2026/08/11 02:05 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 1a9e218195a55d0a31e8cbe263aa2f618580ef1d
Author: Keith Busch <kbusch@kernel.org>
Date: Tue Jun 4 11:59:08 2024 -0700
nvme: split device add from initialization
Combining both creates an ambiguous cleanup scenario for the caller if
an error is returned: does the device reference need to be dropped or
did the error occur before the device was initialized? If an error
occurs after the device is added, then the existing cleanup routines
will leak memory.
Furthermore, the nvme core is taking it upon itself to free the device's
kobj name under certain conditions rather than go through the core
device API. We shouldn't be peaking into these implementation details.
Split the device initialization from the addition to make it easier to
know the error handling actions, fix the existing memory leaks, and stop
the device layering violations.
Link: https://lore.kernel.org/linux-nvme/c4050a37-ecc9-462c-9772-65e25166f439@grimberg.me/
Tested-by: Yi Zhang <yi.zhang@redhat.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Chaitanya Kulkarni <kch@nvidia.com>
Signed-off-by: Keith Busch <kbusch@kernel.org>
diff --git a/drivers/nvme/host/apple.c b/drivers/nvme/host/apple.c
index c43ada920c3b..42ba7fbc749f 100644
--- a/drivers/nvme/host/apple.c
+++ b/drivers/nvme/host/apple.c
@@ -1531,6 +1531,10 @@ static int apple_nvme_probe(struct platform_device *pdev)
if (IS_ERR(anv))
return PTR_ERR(anv);
+ ret = nvme_add_ctrl(&anv->ctrl);
+ if (ret)
+ goto out_put_ctrl;
+
anv->ctrl.admin_q = blk_mq_alloc_queue(&anv->admin_tagset, NULL, NULL);
if (IS_ERR(anv->ctrl.admin_q)) {
ret = -ENOMEM;
@@ -1545,6 +1549,7 @@ static int apple_nvme_probe(struct platform_device *pdev)
out_uninit_ctrl:
nvme_uninit_ctrl(&anv->ctrl);
+out_put_ctrl:
nvme_put_ctrl(&anv->ctrl);
return ret;
}
diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index 89ebfa89613e..80d8460bb92f 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -4667,6 +4667,9 @@ static void nvme_free_ctrl(struct device *dev)
* Initialize a NVMe controller structures. This needs to be called during
* earliest initialization so that we have the initialized structured around
* during probing.
+ *
+ * On success, the caller must use the nvme_put_ctrl() to release this when
+ * needed, which also invokes the ops->free_ctrl() callback.
*/
int nvme_init_ctrl(struct nvme_ctrl *ctrl, struct device *dev,
const struct nvme_ctrl_ops *ops, unsigned long quirks)
@@ -4715,6 +4718,12 @@ int nvme_init_ctrl(struct nvme_ctrl *ctrl, struct device *dev,
goto out;
ctrl->instance = ret;
+ ret = nvme_auth_init_ctrl(ctrl);
+ if (ret)
+ goto out_release_instance;
+
+ nvme_mpath_init_ctrl(ctrl);
+
device_initialize(&ctrl->ctrl_device);
ctrl->device = &ctrl->ctrl_device;
ctrl->device->devt = MKDEV(MAJOR(nvme_ctrl_base_chr_devt),
@@ -4727,16 +4736,36 @@ int nvme_init_ctrl(struct nvme_ctrl *ctrl, struct device *dev,
ctrl->device->groups = nvme_dev_attr_groups;
ctrl->device->release = nvme_free_ctrl;
dev_set_drvdata(ctrl->device, ctrl);
+
+ return ret;
+
+out_release_instance:
+ ida_free(&nvme_instance_ida, ctrl->instance);
+out:
+ if (ctrl->discard_page)
+ __free_page(ctrl->discard_page);
+ cleanup_srcu_struct(&ctrl->srcu);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(nvme_init_ctrl);
+
+/*
+ * On success, returns with an elevated controller reference and caller must
+ * use nvme_uninit_ctrl() to properly free resources associated with the ctrl.
+ */
+int nvme_add_ctrl(struct nvme_ctrl *ctrl)
+{
+ int ret;
+
ret = dev_set_name(ctrl->device, "nvme%d", ctrl->instance);
if (ret)
- goto out_release_instance;
+ return ret;
- nvme_get_ctrl(ctrl);
cdev_init(&ctrl->cdev, &nvme_dev_fops);
- ctrl->cdev.owner = ops->module;
+ ctrl->cdev.owner = ctrl->ops->module;
ret = cdev_device_add(&ctrl->cdev, ctrl->device);
if (ret)
- goto out_free_name;
+ return ret;
/*
* Initialize latency tolerance controls. The sysfs files won't
@@ -4747,28 +4776,11 @@ int nvme_init_ctrl(struct nvme_ctrl *ctrl, struct device *dev,
min(default_ps_max_latency_us, (unsigned long)S32_MAX));
nvme_fault_inject_init(&ctrl->fault_inject, dev_name(ctrl->device));
- nvme_mpath_init_ctrl(ctrl);
- ret = nvme_auth_init_ctrl(ctrl);
- if (ret)
- goto out_free_cdev;
+ nvme_get_ctrl(ctrl);
return 0;
-out_free_cdev:
- nvme_fault_inject_fini(&ctrl->fault_inject);
- dev_pm_qos_hide_latency_tolerance(ctrl->device);
- cdev_device_del(&ctrl->cdev, ctrl->device);
-out_free_name:
- nvme_put_ctrl(ctrl);
- kfree_const(ctrl->device->kobj.name);
-out_release_instance:
- ida_free(&nvme_instance_ida, ctrl->instance);
-out:
- if (ctrl->discard_page)
- __free_page(ctrl->discard_page);
- cleanup_srcu_struct(&ctrl->srcu);
- return ret;
}
-EXPORT_SYMBOL_GPL(nvme_init_ctrl);
+EXPORT_SYMBOL_GPL(nvme_add_ctrl);
/* let I/O to all namespaces fail in preparation for surprise removal */
void nvme_mark_namespaces_dead(struct nvme_ctrl *ctrl)
diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c
index c52446013388..d5a383766b34 100644
--- a/drivers/nvme/host/fc.c
+++ b/drivers/nvme/host/fc.c
@@ -3563,6 +3563,10 @@ nvme_fc_init_ctrl(struct device *dev, struct nvmf_ctrl_options *opts,
if (IS_ERR(ctrl))
return ERR_CAST(ctrl);
+ ret = nvme_add_ctrl(&ctrl->ctrl);
+ if (ret)
+ goto out_put_ctrl;
+
ret = nvme_alloc_admin_tag_set(&ctrl->ctrl, &ctrl->admin_tag_set,
&nvme_fc_admin_mq_ops,
struct_size_t(struct nvme_fcp_op_w_sgl, priv,
@@ -3607,6 +3611,7 @@ nvme_fc_init_ctrl(struct device *dev, struct nvmf_ctrl_options *opts,
/* initiate nvme ctrl ref counting teardown */
nvme_uninit_ctrl(&ctrl->ctrl);
+out_put_ctrl:
/* Remove core ctrl ref. */
nvme_put_ctrl(&ctrl->ctrl);
diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h
index f3a41133ac3f..969349068086 100644
--- a/drivers/nvme/host/nvme.h
+++ b/drivers/nvme/host/nvme.h
@@ -792,6 +792,7 @@ int nvme_disable_ctrl(struct nvme_ctrl *ctrl, bool shutdown);
int nvme_enable_ctrl(struct nvme_ctrl *ctrl);
int nvme_init_ctrl(struct nvme_ctrl *ctrl, struct device *dev,
const struct nvme_ctrl_ops *ops, unsigned long quirks);
+int nvme_add_ctrl(struct nvme_ctrl *ctrl);
void nvme_uninit_ctrl(struct nvme_ctrl *ctrl);
void nvme_start_ctrl(struct nvme_ctrl *ctrl);
void nvme_stop_ctrl(struct nvme_ctrl *ctrl);
diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c
index 102a9fb0c65f..c92125b0238d 100644
--- a/drivers/nvme/host/pci.c
+++ b/drivers/nvme/host/pci.c
@@ -3015,6 +3015,10 @@ static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id)
if (IS_ERR(dev))
return PTR_ERR(dev);
+ result = nvme_add_ctrl(&dev->ctrl);
+ if (result)
+ goto out_put_ctrl;
+
result = nvme_dev_map(dev);
if (result)
goto out_uninit_ctrl;
@@ -3101,6 +3105,7 @@ static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id)
nvme_dev_unmap(dev);
out_uninit_ctrl:
nvme_uninit_ctrl(&dev->ctrl);
+out_put_ctrl:
nvme_put_ctrl(&dev->ctrl);
return result;
}
diff --git a/drivers/nvme/host/rdma.c b/drivers/nvme/host/rdma.c
index 94d4f3dbac6b..5c44c7c5c688 100644
--- a/drivers/nvme/host/rdma.c
+++ b/drivers/nvme/host/rdma.c
@@ -2323,6 +2323,10 @@ static struct nvme_ctrl *nvme_rdma_create_ctrl(struct device *dev,
if (IS_ERR(ctrl))
return ERR_CAST(ctrl);
+ ret = nvme_add_ctrl(&ctrl->ctrl);
+ if (ret)
+ goto out_put_ctrl;
+
changed = nvme_change_ctrl_state(&ctrl->ctrl, NVME_CTRL_CONNECTING);
WARN_ON_ONCE(!changed);
@@ -2341,6 +2345,7 @@ static struct nvme_ctrl *nvme_rdma_create_ctrl(struct device *dev,
out_uninit_ctrl:
nvme_uninit_ctrl(&ctrl->ctrl);
+out_put_ctrl:
nvme_put_ctrl(&ctrl->ctrl);
if (ret > 0)
ret = -EIO;
diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c
index 5ee3bbc67f41..3be67c98c906 100644
--- a/drivers/nvme/host/tcp.c
+++ b/drivers/nvme/host/tcp.c
@@ -2779,6 +2779,10 @@ static struct nvme_ctrl *nvme_tcp_create_ctrl(struct device *dev,
if (IS_ERR(ctrl))
return ERR_CAST(ctrl);
+ ret = nvme_add_ctrl(&ctrl->ctrl);
+ if (ret)
+ goto out_put_ctrl;
+
if (!nvme_change_ctrl_state(&ctrl->ctrl, NVME_CTRL_CONNECTING)) {
WARN_ON_ONCE(1);
ret = -EINTR;
@@ -2800,6 +2804,7 @@ static struct nvme_ctrl *nvme_tcp_create_ctrl(struct device *dev,
out_uninit_ctrl:
nvme_uninit_ctrl(&ctrl->ctrl);
+out_put_ctrl:
nvme_put_ctrl(&ctrl->ctrl);
if (ret > 0)
ret = -EIO;
diff --git a/drivers/nvme/target/loop.c b/drivers/nvme/target/loop.c
index e589915ddef8..e32790d8fc26 100644
--- a/drivers/nvme/target/loop.c
+++ b/drivers/nvme/target/loop.c
@@ -555,6 +555,10 @@ static struct nvme_ctrl *nvme_loop_create_ctrl(struct device *dev,
goto out;
}
+ ret = nvme_add_ctrl(&ctrl->ctrl);
+ if (ret)
+ goto out_put_ctrl;
+
if (!nvme_change_ctrl_state(&ctrl->ctrl, NVME_CTRL_CONNECTING))
WARN_ON_ONCE(1);
@@ -611,6 +615,7 @@ static struct nvme_ctrl *nvme_loop_create_ctrl(struct device *dev,
kfree(ctrl->queues);
out_uninit_ctrl:
nvme_uninit_ctrl(&ctrl->ctrl);
+out_put_ctrl:
nvme_put_ctrl(&ctrl->ctrl);
out:
if (ret > 0)
]
|
| 45/2 |
2026/08/11 02:05 |
llm |
expert |
0m
Model:
Tokens:
input: 37821
output: 42
thoughts: 1343
Thoughts: **Deep Dive into a NVMe Controller Initialization Bug**
Wow, this commit `1a9e218195a55d0a31e8cbe263aa2f618580ef1d` is fascinating. It's a clean separation of responsibilities for NVMe controller initialization and addition, but I think I've spotted a crucial bug.
Before this change, `nvme_init_ctrl` handled initialization *and* adding the controller. Now, it's split. `nvme_init_ctrl` initializes and sets up the release function, while `nvme_add_ctrl` handles the addition. The critical part is what happens on failure. If `nvme_add_ctrl` fails, we're supposed to call `nvme_put_ctrl` to clean up.
Looking at `nvme_fc_init_ctrl` specifically, if `nvme_add_ctrl` fails, the code jumps to `out_put_ctrl`, which correctly calls `nvme_put_ctrl`. This triggers the release path: `nvme_free_ctrl` -> `nvme_fc_free_ctrl` -> `nvme_fc_ctrl_put` -> `nvme_fc_ctrl_free`. Here's the potential issue: `nvme_fc_ctrl_free` *frees* `opts`, because `ctrl->ctrl.opts` is non-NULL.
But here's the kicker: `nvmf_create_ctrl` in `drivers/nvme/host/fabrics.c` *also* frees `opts` if the underlying transport's `create_ctrl` function returns an error! The rationale is that `nvmf_create_ctrl` allocated `opts` in the first place, assuming the transport driver either doesn't handle the allocation or should *not* have taken ownership of it on error.
Now, let's consider how other transport drivers handle the error path. `nvme_tcp_free_ctrl` and `nvme_rdma_free_ctrl` include checks to *skip* freeing `opts` if the controller was not successfully added (i.e. if it's not in the global list). But `nvme_fc_free_ctrl` *lacks* this crucial check!
Does `nvme_fc_ctrl_free` have any way to know if the controller was successfully created? `ctrl->ctrl.opts` is set during `nvme_fc_alloc_ctrl`. Ideally, if `nvme_fc_init_ctrl` fails, it should either avoid freeing `opts` in `nvme_fc_ctrl_free` *or* explicitly set `ctrl->ctrl.opts = NULL` before calling `nvme_put_ctrl` if `nvme_add_ctrl` fails!
Wait, let's revisit `nvme_fc_init_ctrl`. If `nvme_add_ctrl` fails, the code *doesn't* go to the `fail_ctrl` label. That means `ctrl->ctrl.opts` still points to the allocated `opts`! Then, when `nvme_put_ctrl` is called, `nvme_fc_ctrl_free` happily frees `opts` because it's non-NULL. Meanwhile, if `ops->create_ctrl` returns an error, `nvmf_create_ctrl` also *always* frees `opts`!
This explains it perfectly! `nvme_fc_init_ctrl` fails after the initialization part, but before the addition. `nvme_fc_ctrl_free` frees the `opts` during the teardown and the `nvmf_create_ctrl` does too. A double free! It's an elegant demonstration of how subtle resource management issues can arise from split initialization and failure paths.
It seems the fix is to make sure `ctrl->ctrl.opts` is set to NULL before calling `nvme_put_ctrl` if the controller addition failed. We also need to check for this pattern in other transport drivers. Let's look at `nvme_loop_create_ctrl` right away.
|
| 46/2 |
2026/08/11 02:05 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/nvme/target/loop.c Name:nvme_loop_create_ctrl]
Results: map[SourceCode: 552: static struct nvme_ctrl *nvme_loop_create_ctrl(struct device *dev,
553: struct nvmf_ctrl_options *opts)
554: {
555: struct nvme_loop_ctrl *ctrl;
556: int ret;
557:
558: ctrl = kzalloc_obj(*ctrl);
559: if (!ctrl)
560: return ERR_PTR(-ENOMEM);
561: ctrl->ctrl.opts = opts;
562: INIT_LIST_HEAD(&ctrl->list);
563:
564: INIT_WORK(&ctrl->ctrl.reset_work, nvme_loop_reset_ctrl_work);
565:
566: ret = nvme_init_ctrl(&ctrl->ctrl, dev, &nvme_loop_ctrl_ops,
567: 0 /* no quirks, we're perfect! */);
568: if (ret) {
569: kfree(ctrl);
570: goto out;
571: }
572:
573: ret = nvme_add_ctrl(&ctrl->ctrl);
574: if (ret)
575: goto out_put_ctrl;
576:
577: if (!nvme_change_ctrl_state(&ctrl->ctrl, NVME_CTRL_CONNECTING))
578: WARN_ON_ONCE(1);
579:
580: ret = -ENOMEM;
581:
582: ctrl->ctrl.kato = opts->kato;
583: ctrl->port = nvme_loop_find_port(&ctrl->ctrl);
584:
585: ctrl->queues = kzalloc_objs(*ctrl->queues, opts->nr_io_queues + 1);
586: if (!ctrl->queues)
587: goto out_uninit_ctrl;
588:
589: ret = nvme_alloc_admin_tag_set(&ctrl->ctrl, &ctrl->admin_tag_set,
590: &nvme_loop_admin_mq_ops,
591: sizeof(struct nvme_loop_iod) +
592: NVME_INLINE_SG_CNT * sizeof(struct scatterlist));
593: if (ret)
594: goto out_free_queues;
595:
596: ret = nvme_loop_configure_admin_queue(ctrl);
597: if (ret)
598: goto out_remove_admin_tagset;
599:
600: if (opts->queue_size > ctrl->ctrl.maxcmd) {
601: /* warn if maxcmd is lower than queue_size */
602: dev_warn(ctrl->ctrl.device,
603: "queue_size %zu > ctrl maxcmd %u, clamping down\n",
604: opts->queue_size, ctrl->ctrl.maxcmd);
605: opts->queue_size = ctrl->ctrl.maxcmd;
606: }
607: ctrl->ctrl.sqsize = opts->queue_size - 1;
608:
609: if (opts->nr_io_queues) {
610: ret = nvme_loop_create_io_queues(ctrl);
611: if (ret)
612: goto out_remove_admin_queue;
613: }
614:
615: nvme_loop_init_iod(ctrl, &ctrl->async_event_iod, 0);
616:
617: dev_info(ctrl->ctrl.device,
618: "new ctrl: \"%s\"\n", ctrl->ctrl.opts->subsysnqn);
619:
620: if (!nvme_change_ctrl_state(&ctrl->ctrl, NVME_CTRL_LIVE))
621: WARN_ON_ONCE(1);
622:
623: mutex_lock(&nvme_loop_ctrl_mutex);
624: list_add_tail(&ctrl->list, &nvme_loop_ctrl_list);
625: mutex_unlock(&nvme_loop_ctrl_mutex);
626:
627: nvme_start_ctrl(&ctrl->ctrl);
628:
629: return &ctrl->ctrl;
630:
631: out_remove_admin_queue:
632: nvme_quiesce_admin_queue(&ctrl->ctrl);
633: nvme_cancel_admin_tagset(&ctrl->ctrl);
634: nvme_loop_destroy_admin_queue(ctrl);
635: out_remove_admin_tagset:
636: nvme_remove_admin_tag_set(&ctrl->ctrl);
637: out_free_queues:
638: kfree(ctrl->queues);
639: out_uninit_ctrl:
640: nvme_uninit_ctrl(&ctrl->ctrl);
641: out_put_ctrl:
642: nvme_put_ctrl(&ctrl->ctrl);
643: out:
644: if (ret > 0)
645: ret = -EIO;
646: return ERR_PTR(ret);
647: }
SourceFile:drivers/nvme/target/loop.c]
|
| 47/2 |
2026/08/11 02:05 |
llm |
expert |
0m
Model:
Tokens:
input: 41647
output: 42
thoughts: 27
Thoughts: Here's a summary of my thought process:
**Diving into the NVMe Loop Control Free Function**
Alright, time to dig into the `nvme_loop_free_ctrl` function. I need to refresh my memory on how the NVMe loop target driver handles controller deallocation. The specific file I'm targeting is `drivers/nvme/target/loop.c`. I'm hoping to get a clearer picture of how resources are released when a loop controller is no longer needed, especially considering the potential implications for resource leaks or unexpected behavior during shutdown or device removal. I'll need to pay close attention to the sequencing of calls and the data structures being manipulated to ensure proper cleanup. This is a crucial area for stability, and I want to confirm that the existing implementation is robust.
|
| 48/2 |
2026/08/11 02:05 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/nvme/target/loop.c Name:nvme_loop_free_ctrl]
Results: map[SourceCode: 279: static void nvme_loop_free_ctrl(struct nvme_ctrl *nctrl)
280: {
281: struct nvme_loop_ctrl *ctrl = to_loop_ctrl(nctrl);
282:
283: if (list_empty(&ctrl->list))
284: goto free_ctrl;
285:
286: mutex_lock(&nvme_loop_ctrl_mutex);
287: list_del(&ctrl->list);
288: mutex_unlock(&nvme_loop_ctrl_mutex);
289:
290: if (nctrl->tagset)
291: nvme_remove_io_tag_set(nctrl);
292: kfree(ctrl->queues);
293: nvmf_free_options(nctrl->opts);
294: free_ctrl:
295: kfree(ctrl);
296: }
SourceFile:drivers/nvme/target/loop.c]
|
| 49/2 |
2026/08/11 02:05 |
llm |
expert |
0m
Model:
Tokens:
input: 42183
output: 15
thoughts: 3278
Thoughts: **Actionable Double-Free Bug in NVMe/FC Controller Initialization**
Okay, here's the deal. I've been digging into this NVMe/FC controller initialization and I've found a critical double-free vulnerability. It centers around the `nvmf_ctrl_options` structure, the allocation of which is triggered during controller creation. I've pinpointed that the problem arises when `nvme_add_ctrl` fails within the `nvme_fc_init_ctrl` function.
I see that the code flow diverges. When `nvme_add_ctrl` fails, we bypass the error path (`fail_ctrl`) where `ctrl->ctrl.opts` is explicitly set to `NULL`. Instead, we go directly to `out_put_ctrl`, which calls `nvme_put_ctrl`. This in turn leads to the `nvme_fc_ctrl_free` function being executed.
Now, here's where it goes south: in `nvme_fc_ctrl_free`, the code *doesn't* check if `ctrl->ctrl.opts` is `NULL` before calling `nvmf_free_options`. Because `nvme_add_ctrl` failed, the function `nvmf_create_ctrl` has already attempted to free the same `opts` structure during the error handling. This is a clear double-free scenario, or use-after-free, since the memory may have been reallocated.
The analysis is complete. Setting `ctrl->ctrl.opts = NULL;` before calling `nvme_put_ctrl` in `out_put_ctrl` is a straightforward fix, ensuring the correct cleanup of `ctrl->ctrl.opts`. The current implementation is missing that crucial safety check or cleanup, resulting in a use-after-free. I also confirm that there aren't any other leaks in the code path. So, I have marked this as actionable!
|
| 50/2 |
2026/08/11 02:05 |
tool |
set-results |
0m
Args:
Results:
|
| 51/2 |
2026/08/11 02:05 |
llm |
expert |
0m
Model:
Tokens:
input: 45900
output: 1504
thoughts: 236
Thoughts: **Analyzing a Subtle NVMe-FC Controller Bug**
Okay, so the tool tells me this is actionable, good. My brain is already churning through the problem. Time to map this out in detail. First, I need to thoroughly dissect the bug. The core of this lies in how the `struct nvmf_ctrl_options` object is handled, and I need to walk through *why* the bug is occurring and *how* it's occurring.
I'll start with the stack traces. There are three, all linked to that `struct nvmf_ctrl_options` object, which is a great starting point. I need to understand the relationship between those traces and what the error signatures look like.
Next, it's about the sequence of events. The bug originates from that fault injection in `nvme_add_ctrl`, specifically the path through `dev_set_name` and then `kvasprintf`. I'll need to clearly establish the exact control flow, tracing it step-by-step.
The tricky part, and where the solution likely sits, is the buggy error path in `nvme_fc_init_ctrl`. I have to grasp *why* this path is failing. Specifically, why it's bypassing `fail_ctrl` which is where `ctrl->ctrl.opts = NULL` *should* be set. It seems that this oversight is the root cause.
Then, I'm going to look at the differences between this and other transports. It's time to compare and contrast how other drivers, like TCP, RDMA, and Loop, manage similar situations. They apparently avoid this problem by checking `list_empty(&ctrl->list)` in their `free_ctrl` callbacks. This comparison will give the added context to find the right fix.
Finally, the proposed solution: Setting `ctrl->ctrl.opts = NULL` when `nvme_add_ctrl` fails within `nvme_fc_init_ctrl`. That seems right, but I want to be certain about the *why* of this fix. How does it address the problem, and why is it the best approach? I need to think this through and verify it will catch future issues.
|
| 52/1 |
2026/08/11 02:05 |
action |
format-explanation |
0m
Results: map[Explanation:An analysis of the bug report and the Linux kernel source code confirms that
this report is **self-consistent, highly actionable, and describes a real,
reproducible use-after-free (double-free) bug** in the NVMe over Fabrics Fibre
Channel (FC) host driver.
---
### Root Cause Analysis
The bug is triggered by a combination of **failslab fault injection** and an
**incomplete error cleanup path** in `nvme_fc_init_ctrl()`.
#### 1. Allocation of the `nvmf_ctrl_options` Object
When a user requests the creation of a new NVMe-over-Fabrics controller (e.g.,
via `/dev/nvme-fabrics`), `nvmf_create_ctrl()` in `drivers/nvme/host/fabrics.c`
is called:
```c
opts = kzalloc_obj(*opts); // Allocated here (corresponds to the Allocation
Stack)
...
ctrl = ops->create_ctrl(dev, opts); // Calls nvme_fc_create_ctrl() for FC
```
#### 2. Fault Injection and Failure in `nvme_add_ctrl()`
Inside `nvme_fc_create_ctrl()`, the driver allocates a Fibre Channel controller
structure (`struct nvme_fc_ctrl *ctrl`) and associates the options with it:
```c
ctrl->ctrl.opts = opts; // ctrl takes ownership of opts
```
Then, `nvme_fc_init_ctrl()` calls `nvme_add_ctrl(&ctrl->ctrl)` to register the
device:
```c
ret = nvme_add_ctrl(&ctrl->ctrl);
if (ret)
goto out_put_ctrl;
```
In this specific run, **failslab fault injection** forces a failure in
`kvasprintf()` inside `dev_set_name()`, which is called by `nvme_add_ctrl()`. As
a result, `nvme_add_ctrl()` returns an error, and `nvme_fc_init_ctrl()` jumps to
`out_put_ctrl`.
#### 3. First Free of `opts` (via `nvme_put_ctrl`)
In the `out_put_ctrl` error path, the driver releases its reference to the
controller:
```c
out_put_ctrl:
/* Remove core ctrl ref. */
nvme_put_ctrl(&ctrl->ctrl);
```
Since the reference count of the controller's device drops to zero, the kernel
triggers the device release callback `nvme_free_ctrl()`, which calls the
FC-specific `.free_ctrl` callback `nvme_fc_free_ctrl()`. This eventually calls
`nvme_fc_ctrl_free()`, which does:
```c
if (ctrl->ctrl.opts)
nvmf_free_options(ctrl->ctrl.opts); // First Free (corresponds to the Free
Stack)
kfree(ctrl);
```
Because the code jumped directly to `out_put_ctrl` and bypassed the `fail_ctrl`
label, **`ctrl->ctrl.opts` was never set to `NULL`**. Thus,
`nvmf_free_options()` is called, freeing the `opts` structure.
#### 4. Second Free of `opts` (Use-After-Free / Double-Free)
After `nvme_fc_init_ctrl()` returns the error pointer, the error propagates back
to `nvmf_create_ctrl()` in `drivers/nvme/host/fabrics.c`:
```c
ctrl = ops->create_ctrl(dev, opts);
if (IS_ERR(ctrl)) {
ret = PTR_ERR(ctrl);
goto out_module_put;
}
...
out_free_opts:
nvmf_free_options(opts); // Second Free / Use-After-Free (corresponds to the
Bad Access Stack)
return ERR_PTR(ret);
```
Since `opts` was already freed in step 3, calling `nvmf_free_options(opts)` here
results in a **slab-use-after-free (double-free)**.
---
### Comparison with Other Transport Drivers
Other transport drivers (such as TCP, RDMA, and Loop) avoid this issue because
their `.free_ctrl` callbacks check if the controller was successfully added to
their global lists before freeing the options:
```c
static void nvme_tcp_free_ctrl(struct nvme_ctrl *nctrl)
{
struct nvme_tcp_ctrl *ctrl = to_tcp_ctrl(nctrl);
if (list_empty(&ctrl->list))
goto free_ctrl; // Bypasses nvmf_free_options() if not fully
initialized/added
...
nvmf_free_options(nctrl->opts);
free_ctrl:
kfree(ctrl->queues);
kfree(ctrl);
}
```
The Fibre Channel (FC) driver lacks this safeguard and unconditionally frees
`ctrl->ctrl.opts` if it is non-NULL.
---
### Proposed Fix
To fix this bug, `nvme_fc_init_ctrl()` must set `ctrl->ctrl.opts = NULL` if
`nvme_add_ctrl()` fails, ensuring that the subsequent `nvme_put_ctrl()` call
does not free the options (leaving the cleanup responsibility to
`nvmf_create_ctrl()`).
```diff
diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c
index c52446013388..d5a383766b34 100644
--- a/drivers/nvme/host/fc.c
+++ b/drivers/nvme/host/fc.c
@@ -3564,8 +3564,10 @@ nvme_fc_init_ctrl(struct device *dev, struct
nvmf_ctrl_options *opts,
return ERR_CAST(ctrl);
ret = nvme_add_ctrl(&ctrl->ctrl);
- if (ret)
+ if (ret) {
+ ctrl->ctrl.opts = NULL;
goto out_put_ctrl;
+ }
ret = nvme_alloc_admin_tag_set(&ctrl->ctrl, &ctrl->admin_tag_set,
&nvme_fc_admin_mq_ops,
```
---
### Conclusion
This report is **fully actionable and self-consistent**. The three stack traces
align perfectly with the lifecycle of the `struct nvmf_ctrl_options` object, and
the root cause is a clear error-handling bug introduced during the refactoring
of the NVMe controller initialization/addition sequence.]
|