# CVE-2026-89974: nvme-fc: fix double free of fabrics options when nvme_add_ctrl() fails

> A double free vulnerability in the Linux kernel NVMe FC driver allows attackers to cause a denial of service or potentially execute code via memory corruption.

- **Severity:** High 7.5 (`CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H`)
- **Exploited in the wild:** Not known
- **Login needed:** Not needed
- **Attackable:** Local or same network only
- **Victim action needed:** Not needed
- **Fix available:** Yes
- **Published:** 16 Sep 2026
- **Updated:** 19 Oct 2026
- **Also known as:** BELL-CVE-2026-89974, DEBIAN-CVE-2026-89974, GHSA-6hqx-63gq-9x2p, UBUNTU-CVE-2026-89974

## What is CVE-2026-89974?

CVE-2026-89974 is a high-severity use after free vulnerability in Linux, published on 16 Sep 2026. It can only be exploited from the same machine or local network, with no action from the victim.

### Official description

In the Linux kernel, the following vulnerability has been resolved:

nvme-fc: fix double free of fabrics options when nvme_add_ctrl() fails

nvmf_create_ctrl() owns the fabrics options and frees them whenever
->create_ctrl() 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
nvme_fc_ctrl_free(), which requires nvme_fc_init_ctrl() 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
nvme_add_ctrl() fails, nvme_fc_init_ctrl() jumps to out_put_ctrl:, past
the "ctrl->ctrl.opts = NULL" that only sits on the fail_ctrl: path, so
nvme_fc_ctrl_free() frees the options and nvmf_create_ctrl() frees them
a second time:

  BUG: KASAN: slab-use-after-free in nvmf_free_options+0x30/0x190
   nvmf_free_options+0x30/0x190 drivers/nvme/host/fabrics.c:1284
   nvmf_create_ctrl drivers/nvme/host/fabrics.c:1374 [inline]
  Freed by task 5534:
   nvme_fc_ctrl_free drivers/nvme/host/fc.c:2374 [inline]
   nvme_fc_init_ctrl+0xe17/0x1450 drivers/nvme/host/fc.c:3605

nvme_add_ctrl() fails when dev_set_name() 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 free_ctrl 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 flush_delayed_work(),
and the controller has to be reachable on rport->ctrl_list for the whole
of it: nvme_fc_unregister_remoteport() needs to find it to signal
connectivity loss, nvme_fc_match_disconn_ls() matches an incoming
Disconnect Association LS against ctrl->association_id, which is only
assigned during that window, nvme_fc_resume_controller() needs it on
remoteport re-registration, and nvme_fc_existing_controller() uses it to
reject a duplicate connect racing the one in flight.

Keep the insertion where it is and add a fail_unlist: label, falling
into fail_ctrl:, for the error paths that run after it.  The earlier
error paths never reach the insertion and keep using fail_ctrl:
directly, so the list is only touched where the controller is actually
on it.

nvme_fc_ctrl_free() cannot use the plain "goto free_ctrl" the other
transports use, because it still has to put_device(), release the rport
reference and free the ida entry for resources taken before the
insertion.  Sample list_empty() 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: nvme_free_ctrl() calls nvme_auth_free() before ->free_ctrl(), and
ctrl_max_dhchaps() dereferences ctrl->opts without a NULL check when
ctrl->dhchap_ctxs is set, which nvme-fc permits since NVMF_ALLOWED_OPTS
allows the dhchap options.  The nvme sysfs attributes that dereference
ctrl->opts, such as hostnqn and address, evaluate their is_visible()
test once at device_add() time and stay readable until
cdev_device_del().

## Which versions are affected by CVE-2026-89974?


## How do I fix CVE-2026-89974?

Apply the vendor's patch.

## Is CVE-2026-89974 being exploited?

There is no known exploitation of CVE-2026-89974 in the wild and no public exploit code.

## Fix prompt for AI coding agents

```text
Check this codebase for CVE-2026-89974 and fix it if it is affected.

What it is: CVE-2026-89974 is a high-severity use after free vulnerability in Linux, published on 16 Sep 2026. It can only be exploited from the same machine or local network, with no action from the victim.
Exploitation: There is no known exploitation of CVE-2026-89974 in the wild and no public exploit code.

Affected product: Linux. No package-level version data is available, so check the vendor advisory linked from the reference page.

Task:
1. Search the dependency manifests and lockfiles (for example package.json, package-lock.json, requirements.txt, poetry.lock, uv.lock, pom.xml, build.gradle, go.mod, Cargo.lock, Gemfile.lock) and any Dockerfiles for the affected packages, including transitive dependencies.
2. If an affected version is in use, upgrade it to the fixed version for its release line with the project's package manager, so the lockfile is regenerated. Prefer the smallest upgrade that fixes it.
3. If there is no fix, or the upgrade would break the build, do not guess: explain the risk and the safest workaround instead.
4. Run the tests and summarise what changed and why.
5. If nothing in the project is affected, say so and change nothing.

Reference: https://insights.sbomify.com/CVE-2026-89974/ (Markdown: https://insights.sbomify.com/CVE-2026-89974/index.md)
```

## Sources

- CVE record: https://www.cve.org/CVERecord?id=CVE-2026-89974
- NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-89974
- OSV: https://osv.dev/vulnerability/CVE-2026-89974

---
From sbomify Insights: https://insights.sbomify.com/CVE-2026-89974/
Summaries are generated and may contain mistakes; the official description above is the record.
