CVE-2026-89708: nfsd: RCU-protect cl_cb_session to fix use-after-free on session teardown
In the Linux kernel, the following vulnerability has been resolved:
nfsd: RCU-protect clcbsession to fix use-after-free on session teardown
After a DESTROYSESSION the per-session teardown path can free a session while rpciod still holds an inflight callback rpctask that dereferences clp->clcbsession. nfsd4probecallbacksync() flushes clcallbackwq, but once nfsd4runcbwork() has called rpccallasync() the rpctask lives on rpciod; flushing the workqueue does not wait for it. rpcshutdownclient() does drain rpciod tasks, but uses a 1-second waiteventtimeout — tasks stuck in rpcdelay() (e.g. 2-second NFS4ERRDELAY retries) can outlive the drain.
destroy path rpciod ------------ ------ unhashsession(ses) nfsd4probecallbacksync(clp) flushworkqueue(clcallbackwq) / returns; rpctask still live / nfsd4putsessionlocked(ses) freesession(ses) -> kfree(ses) nfsd4cbsequencedone() reads cbclp->clcbsession / freed slab /
A second window exists in nfsd4processcbupdate(). When nfsd4findbackchannel() returns NULL because unhashsession() has already removed the destroyed session from clsessions, setupcallbackclient() takes the v4.1 early return so clp->clcbsession = ses never fires and the field retains a pointer to the about-to-be-freed session.
Fix both by converting clcbsession to an RCU-protected pointer:
- Move the clcbsession = ses assignment in setupcallbackclient() to after rpccreate() succeeds, so it is only published when a working backchannel exists. Clear clcbsession on the error return in nfsd4processcbupdate(). Both stores use rcuassignpointer().
- Annotate clcbsession with rcu. All rpciod-side readers use rcureadlock()/rcudereference() and check for NULL, bailing to the appropriate error or requeue path: encodecbsequence4args(), decodecbsequence4resok(), nfsd41cbgetslot(), nfsd41cbreleaseslot(), nfsd4cbprepare(), and nfsd4cbsequencedone().
- Switch freesession() from kfree() to kfreercu() so the session slab is not reclaimed until after an RCU grace period, guaranteeing that rpciod readers inside rcureadlock() never dereference freed memory.
- Pass the session pointer to the nfsdcbseqstatus and nfsdcbfreeslot tracepoints instead of having them re-read clcbsession.
- nfsd4cbprepare() calls rpcexit() when the session is NULL, routing through the done/release path to requeue the callback.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
debian/linuxto a version that resolves this vulnerability.Fixed in 6.12.115-2Fixed in 7.2.12-1Fixed in 7.2.15-1 - Upgrade
Upgrade
debian/linux-6.12to a version that resolves this vulnerability.Fixed in 6.12.113-1~deb12u1 - Upgrade
Upgrade
ubuntu/linux-hwe-7.0to a version that resolves this vulnerability.Fixed in 7.0.0-42.42~24.04.1 - Upgrade
Upgrade
ubuntu/linux-riscv-7.0to a version that resolves this vulnerability.Fixed in 7.0.0-42.42.1~24.04.1 - Upgrade
Upgrade
ubuntu/linuxto a version that resolves this vulnerability.Fixed in 7.0.0-42.42 - Upgrade
Upgrade
ubuntu/linux-awsto a version that resolves this vulnerability.Fixed in 7.0.0-1016.17 - Upgrade
Upgrade
ubuntu/linux-gcpto a version that resolves this vulnerability.Fixed in 7.0.0-1016.18 - Upgrade
Upgrade
ubuntu/linux-ibmto a version that resolves this vulnerability.Fixed in 7.0.0-1017.17 - Upgrade
Upgrade
ubuntu/linux-oem-7.0to a version that resolves this vulnerability.Fixed in 7.0.0-1017.17 - Upgrade
Upgrade
ubuntu/linux-oracleto a version that resolves this vulnerability.Fixed in 7.0.0-1015.16 - Upgrade
Upgrade
ubuntu/linux-riscvto a version that resolves this vulnerability.Fixed in 7.0.0-42.42.1 - Compensating control
Convert cl_cb_session to an RCU-protected pointer: annotate it with __rcu, publish both stores with rcu_assign_pointer(), and have encode_cb_sequence4args(), decode_cb_sequence4resok(), nfsd41_cb_get_slot(), nfsd41_cb_release_slot(), nfsd4_cb_prepare(), and nfsd4_cb_sequence_done() use rcu_read_lock()/rcu_dereference(), check for NULL, and take the appropriate error or requeue path.
- Compensating control
Move the cl_cb_session = ses assignment in setup_callback_client() to after rpc_create() succeeds, so the session is published only when a working backchannel exists.
- Compensating control
Pass the session pointer directly to the nfsd_cb_seq_status and nfsd_cb_free_slot tracepoints instead of having them re-read cl_cb_session.
- Compensating control
Change __free_session() from kfree() to kfree_rcu() so the session slab is not reclaimed until after an RCU grace period.
- Compensating control
Make nfsd4_cb_prepare() call rpc_exit() when the session is NULL, routing through the done/release path to requeue the callback.
- Compensating control
Clear cl_cb_session on the error return in nfsd4_process_cb_update().
Event History
Frequently Asked Questions
Which systems are exposed to this race?
Linux systems running the NFS server callback/session teardown path are exposed when a session is destroyed while an asynchronous callback RPC task remains in flight on rpciod. The issue specifically involves access to the callback session pointer after the session has been freed.
What timing conditions are required for the use-after-free to occur?
A DESTROY_SESSION operation must begin teardown after callback work has started an asynchronous RPC task. The task can outlive the callback workqueue flush, particularly when it is delayed in rpc_delay(), such as during NFS4ERR_DELAY retry handling.
Why is flushing the callback workqueue insufficient protection?
Once nfsd4_run_cb_work() has invoked rpc_call_async(), the RPC task is owned by rpciod rather than the callback workqueue. The client shutdown drain uses a one-second timeout, so delayed tasks can remain alive after session memory is released.