HIGH Introduced in 5.6
nfsd CopyNotify UAF
CVE-2026-89663
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
KernelScan AI7.5HIGH
01Description
In the Linux kernel, the following vulnerability has been resolved: nfsd: revoke copy-notify stateids before dropping their reference Copy-notify stateids live in the s2s_cp_stateids IDR and on their parent stid's sc_cp_list, pinned by a single membership reference. _free_cpntf_state_locked() only unlinks an entry once its refcount reaches zero, so any revoke path that runs while a concurrent find_cpntf_state()/manage_cpntf_state() holder has elevated cs_count drops the reference without unlinking, leaving the entry discoverable with its membership reference already consumed. A second revoke or a laundromat tick then frees it while the reader still holds the pointer -- a KASAN-detectable use-after-free at the reader's nfs4_put_cpntf_state(). This affected all three revoke paths: - The parent-stid drain (nfs4_free_cpntf_statelist()) repeatedly called _free_cpntf_state_locked() on the first list entry; a holder that had bumped cs_count made it return early, so the next iteration re-decremented and burned the holder's reference. - OFFLOAD_CANCEL (manage_cpntf_state()) and laundromat expiry likewise used _free_cpntf_state_locked() and could drop 2->1 without unlinking. Add revoke_cpntf_state_locked(), which unhashes the entry from the IDR and sc_cp_list first (deferring the final free to any holder), and use it from all three revoke paths. The drain now walks with list_for_each_entry_safe() and revokes each entry unconditionally, so it terminates in one pass per entry regardless of cs_count. The unhash is gated on !list_empty(&cps->cp_list); the idr_remove() gate matters because idr_alloc_cyclic() may have recycled the so_id by then. Keep _free_cpntf_state_locked() for the reference-holder put path only, where a concurrent revoke may already have unlinked the entry (its list_del_init() then a no-op).
02KernelScan AI Analysis
Risk summary
A remote NFSv4.2 client can trigger a use-after-free in the kernel NFS server's copy-notify stateid handling by racing a revoke path (OFFLOAD_CANCEL, laundromat expiry, or parent-stateid drain) against a concurrent reader. The freed object remains discoverable in an IDR and list, allowing a second revoke to free it while the reader still holds a pointer. This can corrupt kernel heap memory and is exploitable for code execution or a kernel panic on the NFS server.
Vulnerability analysis
The NFS server's cleanup of copy-notify tracking objects was unsafe: it only removed an object from internal lookup tables once all users had released it. If another thread was still using the object, cleanup dropped its tracking reference without removing it from the tables, leaving the object still discoverable. A later cleanup pass then freed the object while the other thread still held a pointer, causing a use-after-free. The fix separates removal from freeing: cleanup now unlinks the object from all lookup tables first so no new user can find it, and only frees the memory once the last reference is released. The drain loop now processes every entry safely in one pass. Any remote NFSv4.2 client can trigger this by sending copy-notify and related operations; the client needs valid open state on the server but no local account or elevated privilege.
Lifecycle
03Fix Versions
| Branch | Introduced | Fixed in | Patch commit |
|---|---|---|---|
| 5.10 | 5.6 | 5.10.270 | 5f00e0ab4de4 |
| 5.15 | 5.6 | 5.15.221 | 06a62f6dbc60 |
| 6.1 | 5.6 | 6.1.188 | a1fca0f7bbb7 |
| 6.12 | 5.6 | 6.12.109 | 6b8149448cdc |
| 6.18 | 5.6 | 6.18.50 | b56d2c5f01cd |
| 6.6 | 5.6 | 6.6.157 | caddbeffab1e |
| 7.2 | 5.6 | 7.2.4 | ff8a3cff02b9 |
| mainline | 5.6 | 7.3 | 3b0c3595db99 |
| cip/5.10 | 5.6 | 5.10.271-cip78 | 5f00e0ab4de4 |
| cip/6.1 | 5.6 | 6.1.189-cip61 | a1fca0f7bbb7 |
| cip/6.1-rt | 5.6 | 6.1.189-cip61-rt32 | a1fca0f7bbb7 |
| cip/6.12 | 5.6 | 6.12.111-cip32 | 6b8149448cdc |
04CIP Kernels
State of the newest release of each Civil Infrastructure Platform SLTS tree, from CIP's own fix records.
| CIP tree | State |
|---|---|
| cip/6.12 | Fixed in 6.12.111-cip32 |
| cip/6.1 | Fixed in 6.1.189-cip61 |
| cip/6.1-rt | Fixed in 6.1.189-cip61-rt32 |
| cip/5.10 | Fixed in 5.10.271-cip78 |
| cip/5.10-rt | Affected — no CIP fix yet (latest 5.10.268-cip77-rt34) |