CVE-2026-89974: nvme-fc: fix double free of fabrics options when nvme_add_ctrl() fails
In the Linux kernel, the following vulnerability has been resolved:
nvme-fc: fix double free of fabrics options when nvmeaddctrl() fails
nvmfcreatectrl() owns the fabrics options and frees them whenever ->createctrl() returns an error, so a transport must not free them on its own error paths. nvme-fc tracks this by testing ctrl->ctrl.opts in nvmefcctrlfree(), which requires nvmefcinitctrl() to clear that pointer on every error exit.
The coupling is implicit, and commit 1a9e218195a5 ("nvme: split device add from initialization") broke it by adding a second error exit. When nvmeaddctrl() fails, nvmefcinitctrl() jumps to outputctrl:, past the "ctrl->ctrl.opts = NULL" that only sits on the failctrl: path, so nvmefcctrlfree() frees the options and nvmfcreatectrl() frees them a second time:
BUG: KASAN: slab-use-after-free in nvmffreeoptions+0x30/0x190 nvmffreeoptions+0x30/0x190 drivers/nvme/host/fabrics.c:1284 nvmfcreatectrl drivers/nvme/host/fabrics.c:1374 [inline] Freed by task 5534: nvmefcctrlfree drivers/nvme/host/fc.c:2374 [inline] nvmefcinitctrl+0xe17/0x1450 drivers/nvme/host/fc.c:3605
nvmeaddctrl() fails when devsetname() cannot allocate, so this is reachable under memory pressure or fault injection. Without KASAN the options are freed twice.
Rather than clear the pointer on the second exit as well, derive ownership the way nvme-tcp, nvme-rdma and nvme-loop do, from list membership: their freectrl leaves the options alone unless the controller made it onto the transport list.
The list cannot simply be populated on the success path as it is there. nvme-fc runs the initial connect synchronously via flushdelayedwork(), and the controller has to be reachable on rport->ctrllist for the whole of it: nvmefcunregisterremoteport() needs to find it to signal connectivity loss, nvmefcmatchdisconnls() matches an incoming Disconnect Association LS against ctrl->associationid, which is only assigned during that window, nvmefcresumecontroller() needs it on remoteport re-registration, and nvmefcexistingcontroller() uses it to reject a duplicate connect racing the one in flight.
Keep the insertion where it is and add a failunlist: label, falling into failctrl:, for the error paths that run after it. The earlier error paths never reach the insertion and keep using failctrl: directly, so the list is only touched where the controller is actually on it.
nvmefcctrlfree() cannot use the plain "goto freectrl" the other transports use, because it still has to putdevice(), release the rport reference and free the ida entry for resources taken before the insertion. Sample listempty() under rport->lock instead.
ctrl->ctrl.opts also stays valid for the whole teardown now. That is not the bug being fixed, but it removes some fragility around the old idiom: nvmefreectrl() calls nvmeauthfree() before ->freectrl(), and ctrlmaxdhchaps() dereferences ctrl->opts without a NULL check when ctrl->dhchapctxs is set, which nvme-fc permits since NVMFALLOWEDOPTS allows the dhchap options. The nvme sysfs attributes that dereference ctrl->opts, such as hostnqn and address, evaluate their isvisible() test once at deviceadd() time and stay readable until cdevdevicedel().
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Configuration
Apply the described kernel fix pattern: keep the insertion where it is, add a fail_unlist: label for the error paths, and protect list emptiness with rport->lock. Avoid clearing ctrl->ctrl.opts on the second exit so that ctrl->ctrl.opts remains valid through controller teardown and only becomes NULL on the fail_ctrl: path (per the resolved KASAN use-after-free in nvmf_free_options).
Linux kernel NVMe fabrics (nvmf) Control teardown/error-path flow for ctrl->ctrl.opts/dhchap_ctxs and rport->ctrl_list insertion = Use fail_unlist label and ensure list_empty() is evaluated under rport->lock; do not clear the pointer on the second exit - Compensating control
When disconnecting, ensure NVMe fabrics association LS is disconnected against ctrl->association_id only, and avoid any transport teardown that could free options during the lifetime window where the controller must remain reachable on rport->ctrl_list.
Event History
Frequently Asked Questions
When can this failure path be reached?
The double free occurs when NVMe Fibre Channel controller initialization reaches an nvme_add_ctrl() failure. The provided data states that nvme_add_ctrl() can fail when dev_set_name() cannot allocate memory.
What makes the affected error path trigger the memory corruption?
On the nvme_add_ctrl() error exit, nvme_fc_init_ctrl() does not clear ctrl->ctrl.opts before nvme_fc_ctrl_free() runs. That cleanup frees the fabrics options, after which nvmf_create_ctrl() frees the same options again, producing a use-after-free/double-free condition.