FAILED: patch "[PATCH] NFSD: Prevent client use-after-free during blocked-lock" failed to apply to 7.2-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 7.2-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-7.2.y
git checkout FETCH_HEAD
git cherry-pick -x 9026932ac8be4d0ae01db47f23619a98cc57b671
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026090331-frostbite-credibly-4ca0@gregkh' --subject-prefix 'PATCH 7.2.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 9026932ac8be4d0ae01db47f23619a98cc57b671 Mon Sep 17 00:00:00 2001
From: Chuck Lever <[email protected]>
Date: Thu, 9 Jul 2026 13:40:30 -0400
Subject: [PATCH] NFSD: Prevent client use-after-free during blocked-lock
reaping
A bare lock owner -- its only remaining reference a blocked lock on nn->blocked_locks_lru -- holds a raw pointer to its nfs4_client but no reference keeping the client alive. When the per-net laundromat reaps such a lock, freeing the nbl drops the owner reference held through flc_owner, and the final nfs4_put_stateowner() takes the client's cl_lock. Because the laundromat detaches the nbl first, __destroy_client() no longer finds it, so a concurrent force_expire_client() can free the client before nfs4_put_stateowner() runs, dereferencing cl_lock in freed memory.
Pin the client with cl_rpc_users before dropping nn->blocked_locks_lock, and skip clients already expiring, whose blocked locks __destroy_client() frees while holding an owner reference. Take nn->client_lock outside nn->blocked_locks_lock. Every other site holds nn->blocked_locks_lock as a leaf, acquiring no further lock, so placing nn->client_lock outside it cannot form a lock-order cycle.
fs/nfsd/nfs4state.c +20 -3
@@ -357,6 +357,16 @@ free_blocked_lock(struct nfsd4_blocked_lock *nbl)kref_put (&nbl->nbl_kref, free_nbl); } +/* A blocked lock's flc_owner is its nfs4_lockowner. */ +static struct nfs4_client * +nbl_client (struct nfsd4_blocked_lock *nbl) +{ +struct nfs4_lockowner *lo; + + lo = (struct nfs4_lockowner *)nbl->nbl_lock.c.flc_owner; +return lo->lo_owner.so_client; +} +static void remove_blocked_locks (struct nfs4_lockowner *lo) {
@@ -7591,22 +7601,29 @@ nfs4_laundromat(struct nfsd_net *nn)* indefinitely once the lock does become free. */ BUG_ON (!list_empty (&reaplist)); +spin_lock (&nn->client_lock);spin_lock (&nn->blocked_locks_lock); -while (!list_empty (&nn->blocked_locks_lru)) { - nbl =list_first_entry (&nn->blocked_locks_lru, - struct nfsd4_blocked_lock, nbl_lru); +list_for_each_safe (pos, next, &nn->blocked_locks_lru) { + nbl = list_entry(pos, struct nfsd4_blocked_lock, nbl_lru);if (!state_expired (<, nbl->nbl_time))break ; + clp =nbl_client (nbl); +if (is_client_expired (clp)) +continue ; +atomic_inc (&clp->cl_rpc_users);list_move (&nbl->nbl_lru, &reaplist);list_del_init (&nbl->nbl_list); }spin_unlock (&nn->blocked_locks_lock); +spin_unlock (&nn->client_lock);while (!list_empty (&reaplist)) { nbl =list_first_entry (&reaplist, struct nfsd4_blocked_lock, nbl_lru); + clp =nbl_client (nbl);list_del_init (&nbl->nbl_lru);free_blocked_lock (nbl); +put_client_no_renew (clp); }#ifdef CONFIG_NFSD_V4_2_INTER_SSC /* service the server-to-server copy delayed unmount list */
Sasha Levin
maintainerPATCH 1/6 NFSD: Prevent client use-after-free during delegation revoke
From: Chuck Lever <[email protected]>
[ Upstream commit 4683ca76b3b7e5808338491c6eb3c20e6b4894d5 ]
A delegation stateid holds only a bare pointer to its owning nfs4_client and does not keep it alive. The client survives its stateids only because __destroy_client() drains cl_delegations and cl_revoked before free_client() runs.
nfs4_laundromat() breaks that invariant: it unhashes an expired delegation from cl_delegations, drops deleg_lock, then revoke_delegation() relinks it onto cl_revoked under cl_lock. In that window the delegation is on neither list, so client_has_state() can report no remaining state.
Every teardown path first requires cl_rpc_users to be zero, but the laundromat holds no such reference. A client whose recalled delegation has just timed out can therefore reach free_client() while revoke_delegation() is still about to dereference cl_lock, a use-after-free.
Pin the client with cl_rpc_users across the revoke so teardown blocks until it completes, then reap the delegation from cl_revoked. A client already expiring reaps its own, so skip it and leave the delegation on del_recall_lru.
Stable-dep-of: 9026932ac8be ("NFSD: Prevent client use-after-free during blocked-lock reaping")
--- fs/nfsd/netns.h | 6 ++++-- fs/nfsd/nfs4state.c | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-)
fs/nfsd/netns.h +4 -2
@@ -101,7 +101,8 @@ struct nfsd_net {struct list_head client_lru;struct list_head close_lru; -/* protects del_recall_lru and delegation hash/unhash */ +/* protects del_recall_lru and delegation hash/unhash; +* nests outside client_lock */ spinlock_t deleg_lock____cacheline_aligned ;struct list_head del_recall_lru;
@@ -110,7 +111,8 @@ struct nfsd_net {struct delayed_work laundromat_work; -/* client_lock protects the client lru list and session hash table */ +/* client_lock protects the client lru list and session hash +* table; nests inside deleg_lock */ spinlock_t client_lock;/* protects blocked_locks_lru */
fs/nfsd/nfs4state.c +23 -0
@@ -7062,6 +7062,7 @@ nfs4_laundromat(struct nfsd_net *nn) .new_timeo = nn->nfsd4_lease };struct nfs4_cpntf_state *cps; +struct nfs4_client *clp;copy_stateid_t *cps_t;int i;
@@ -7090,6 +7091,18 @@ nfs4_laundromat(struct nfsd_net *nn) dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru);if (!state_expired (<, dp->dl_time))break ; + clp = dp->dl_stid.sc_client; +spin_lock (&nn->client_lock); +if (is_client_expired (clp)) { +spin_unlock (&nn->client_lock); +continue ; + } +/* +* Pin without reviving: get_client_locked() would +* flip a courtesy client back to NFSD4_ACTIVE. +*/ +atomic_inc (&clp->cl_rpc_users); +spin_unlock (&nn->client_lock);refcount_inc (&dp->dl_stid.sc_count);unhash_delegation_locked (dp,SC_STATUS_REVOKED );list_add (&dp->dl_recall_lru, &reaplist);
@@ -7098,8 +7111,18 @@ nfs4_laundromat(struct nfsd_net *nn)while (!list_empty (&reaplist)) { dp = list_first_entry(&reaplist, struct nfs4_delegation, dl_recall_lru); + clp = dp->dl_stid.sc_client;list_del_init (&dp->dl_recall_lru);revoke_delegation (dp); +/* +* Unpin without renewing: put_client_renew() would +* renew the reaped client's lease. +*/ +if (atomic_dec_and_lock (&clp->cl_rpc_users, &nn->client_lock)) { +if (is_client_expired (clp)) +wake_up_all (&expiry_wq); +spin_unlock (&nn->client_lock); + } }spin_lock (&nn->client_lock);
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/6 NFSD: Prevent client use-after-free during admin state revocation
From: Chuck Lever <[email protected]>
[ Upstream commit e270e5a0778e5bff852c8862ce9576ce70359393 ]
A stateid holds only a bare pointer to its nfs4_client; a stateid reference does not pin it. The client survives only because __destroy_client() drains its stateids before free_client() runs.
nfsd4_revoke_states() drops nn->client_lock across revoke_one_stid(), which dereferences the client to revoke a stateid and read clp->cl_minorversion. A teardown racing the dropped lock can free the client first.
Pinning cl_rpc_users under client_lock blocks the DESTROY_CLIENTID and EXCHANGE_ID teardown, which refuses while cl_rpc_users is non-zero. force_expire_client() ignores it: once its wait for cl_rpc_users to reach zero has passed, a later pin goes unnoticed.
Under client_lock, skip a client whose cl_time is already zero -- force_expire_client() clears it there before waiting -- otherwise pin cl_rpc_users before dropping the lock. The walk then either sees the expiry and skips, or pins in time for that wait to cover the revoke.
Stable-dep-of: 9026932ac8be ("NFSD: Prevent client use-after-free during blocked-lock reaping")
--- fs/nfsd/nfs4state.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-)
fs/nfsd/nfs4state.c +20 -2
@@ -1889,9 +1889,19 @@ void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb)struct nfs4_client *clp; retry:list_for_each_entry (clp, head, cl_idhash) { -struct nfs4_stid *stid =find_one_sb_stid (clp, sb, - sc_types); +struct nfs4_stid *stid; + +/* +* force_expire_client() ignores cl_rpc_users once +* its wait_event() has passed, so pinning cannot +* keep an already-expiring client alive; the +* expiry path revokes its states instead. +*/ +if (is_client_expired (clp)) +continue ; + stid =find_one_sb_stid (clp, sb, sc_types);if (stid) { +atomic_inc (&clp->cl_rpc_users);spin_unlock (&nn->client_lock);revoke_one_stid (nn, clp, stid);nfs4_put_stid (stid);
@@ -1904,6 +1914,9 @@ void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb)*/ nn->nfs40_last_revoke =ktime_get_boottime_seconds (); +if (atomic_dec_and_test (&clp->cl_rpc_users) && +is_client_expired (clp)) +wake_up_all (&expiry_wq); goto retry; } }
@@ -3259,6 +3272,11 @@ static void force_expire_client(struct nfs4_client *clp)trace_nfsd_clid_admin_expired (&clp->cl_clientid); +/* +* cl_time is cleared under client_lock before the wait so a +* revocation walk pinning cl_rpc_users under it either skips +* this client or is seen by this wait_event(). +*/ spin_lock (&nn->client_lock); clp->cl_time =0 ;spin_unlock (&nn->client_lock);
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 3/6 NFSD: Prevent client use-after-free during export state revocation
In reply to Sasha LevinFrom: Chuck Lever <[email protected]>
[ Upstream commit 2108de53568a64936a0da3e04d85c35df98d3fb6 ]
nfsd4_revoke_export_states() has the same use-after-free as nfsd4_revoke_states(): it drops nn->client_lock across revoke_one_stid() and the following read of clp->cl_minorversion, but the stateid reference it holds does not pin the client. A teardown racing the dropped lock can free the client while revoke_one_stid() still dereferences it.
exportfs -u drives this path through NFSD_CMD_UNLOCK_EXPORT, so an administrator removing an export can race a client expiry.
Skip a client that is already expiring and otherwise pin it with cl_rpc_users under client_lock before dropping the lock, matching nfsd4_revoke_states().
Stable-dep-of: 9026932ac8be ("NFSD: Prevent client use-after-free during blocked-lock reaping")
--- fs/nfsd/nfs4state.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-)
fs/nfsd/nfs4state.c +10 -3
@@ -1973,10 +1973,14 @@ void nfsd4_revoke_export_states(struct nfsd_net *nn, const struct path *path)struct nfs4_client *clp; retry:list_for_each_entry (clp, head, cl_idhash) { -struct nfs4_stid *stid =find_one_export_stid ( - clp, path, - sc_types); +struct nfs4_stid *stid; + +/* Skip or pin clp as in nfsd4_revoke_states(). */ +if (is_client_expired (clp)) +continue ; + stid =find_one_export_stid (clp, path, sc_types);if (stid) { +atomic_inc (&clp->cl_rpc_users);spin_unlock (&nn->client_lock);revoke_one_stid (nn, clp, stid);nfs4_put_stid (stid);
@@ -1984,6 +1988,9 @@ void nfsd4_revoke_export_states(struct nfsd_net *nn, const struct path *path)if (clp->cl_minorversion ==0 ) nn->nfs40_last_revoke =ktime_get_boottime_seconds (); +if (atomic_dec_and_test (&clp->cl_rpc_users) && +is_client_expired (clp)) +wake_up_all (&expiry_wq); goto retry; } }
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 4/6 NFSD: Prevent client use-after-free during NFSv4.0 revoked-state cleanup
In reply to Sasha LevinFrom: Chuck Lever <[email protected]>
[ Upstream commit 7b4f8a1586c42d3afc3c0ac779af2db7ab1a5c55 ]
nfs40_clean_admin_revoked() takes a stateid reference under clp->cl_lock, drops nn->client_lock, and calls nfsd4_drop_revoked_stid(), which dereferences the stateid's client through s->sc_client->cl_lock. The stateid reference does not pin the client, so a teardown racing the dropped lock can free the client while nfsd4_drop_revoked_stid() is still using it.
This cleanup runs from the laundromat, so a periodic sweep can race force_expire_client() driven by a write to the clients/<id>/ctl file.
Skip a client that is already expiring and otherwise pin it with cl_rpc_users under client_lock before dropping the lock, matching nfsd4_revoke_states().
Stable-dep-of: 9026932ac8be ("NFSD: Prevent client use-after-free during blocked-lock reaping")
--- fs/nfsd/nfs4state.c | 6 ++++++ 1 file changed, 6 insertions(+)
fs/nfsd/nfs4state.c +6 -0
@@ -7057,16 +7057,22 @@ static void nfs40_clean_admin_revoked(struct nfsd_net *nn,if (atomic_read (&clp->cl_admin_revoked) ==0 )continue ; +if (is_client_expired (clp)) +continue ;spin_lock (&clp->cl_lock);idr_for_each_entry_ul (&clp->cl_stateids, stid, tmp, id)if (stid->sc_status &SC_STATUS_ADMIN_REVOKED ) {refcount_inc (&stid->sc_count); +atomic_inc (&clp->cl_rpc_users);spin_unlock (&nn->client_lock);/* this function drops ->cl_lock */ nfsd4_drop_revoked_stid (stid);nfs4_put_stid (stid);spin_lock (&nn->client_lock); +if (atomic_dec_and_test (&clp->cl_rpc_users) && +is_client_expired (clp)) +wake_up_all (&expiry_wq); goto retry; }spin_unlock (&clp->cl_lock);
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 5/6 NFSD: Consolidate the revocation-path client unpin
In reply to Sasha LevinFrom: Chuck Lever <[email protected]>
[ Upstream commit 3308cf3f11ed23c79f9f3f90b34bbbad3e3a6ea9 ]
The client use-after-free fixes in the state-revocation paths left four open-coded copies of one idiom: drop a cl_rpc_users pin without renewing the client's lease, waking force_expire_client() when the last pin drops on a client it is tearing down. The accompanying "do not renew" rationale was documented at only one of the four sites.
put_client_renew_locked() and put_client_renew() already carry the same pin-drop logic, but they renew a non-expired client's lease and so would resurrect the client whose state is being revoked. Factor the common pin-drop into __put_client_locked(), parameterized by whether to renew. The renew helpers pass true; the new put_client_no_renew_locked() and put_client_no_renew() pass false and carry the revocation paths, which must not revive the client they are tearing down. No change in behavior.
Stable-dep-of: 9026932ac8be ("NFSD: Prevent client use-after-free during blocked-lock reaping")
--- fs/nfsd/nfs4state.c | 69 +++++++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 28 deletions(-)
fs/nfsd/nfs4state.c +41 -28
@@ -202,18 +202,28 @@ renew_client_locked(struct nfs4_client *clp) clp->cl_state =NFSD4_ACTIVE ; } +/* +* Finish a cl_rpc_users unpin with the client_lock held. A +* revocation walk clears @renew so the client whose state it is +* revoking is not revived; every other caller renews the lease of +* a still-active client. +*/ +static void __put_client_locked (struct nfs4_client *clp,bool renew) +{ +if (is_client_expired (clp)) +wake_up_all (&expiry_wq); +else if (renew) +renew_client_locked (clp); +} +static void put_client_renew_locked (struct nfs4_client *clp) {struct nfsd_net *nn =net_generic (clp->net, nfsd_net_id);lockdep_assert_held (&nn->client_lock); -if (!atomic_dec_and_test (&clp->cl_rpc_users)) -return ; -if (!is_client_expired (clp)) -renew_client_locked (clp); -else -wake_up_all (&expiry_wq); +if (atomic_dec_and_test (&clp->cl_rpc_users)) +__put_client_locked (clp, true); }static void put_client_renew (struct nfs4_client *clp)
@@ -222,10 +232,27 @@ static void put_client_renew(struct nfs4_client *clp)if (!atomic_dec_and_lock (&clp->cl_rpc_users, &nn->client_lock))return ; -if (!is_client_expired (clp)) -renew_client_locked (clp); -else -wake_up_all (&expiry_wq); +__put_client_locked (clp, true); +spin_unlock (&nn->client_lock); +} + +static void put_client_no_renew_locked (struct nfs4_client *clp) +{ +struct nfsd_net *nn =net_generic (clp->net, nfsd_net_id); + +lockdep_assert_held (&nn->client_lock); + +if (atomic_dec_and_test (&clp->cl_rpc_users)) +__put_client_locked (clp, false); +} + +static void put_client_no_renew (struct nfs4_client *clp) +{ +struct nfsd_net *nn =net_generic (clp->net, nfsd_net_id); + +if (!atomic_dec_and_lock (&clp->cl_rpc_users, &nn->client_lock)) +return ; +__put_client_locked (clp, false);spin_unlock (&nn->client_lock); }
@@ -1914,9 +1941,7 @@ void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb)*/ nn->nfs40_last_revoke =ktime_get_boottime_seconds (); -if (atomic_dec_and_test (&clp->cl_rpc_users) && -is_client_expired (clp)) -wake_up_all (&expiry_wq); +put_client_no_renew_locked (clp); goto retry; } }
@@ -1988,9 +2013,7 @@ void nfsd4_revoke_export_states(struct nfsd_net *nn, const struct path *path)if (clp->cl_minorversion ==0 ) nn->nfs40_last_revoke =ktime_get_boottime_seconds (); -if (atomic_dec_and_test (&clp->cl_rpc_users) && -is_client_expired (clp)) -wake_up_all (&expiry_wq); +put_client_no_renew_locked (clp); goto retry; } }
@@ -7070,9 +7093,7 @@ static void nfs40_clean_admin_revoked(struct nfsd_net *nn,nfsd4_drop_revoked_stid (stid);nfs4_put_stid (stid);spin_lock (&nn->client_lock); -if (atomic_dec_and_test (&clp->cl_rpc_users) && -is_client_expired (clp)) -wake_up_all (&expiry_wq); +put_client_no_renew_locked (clp); goto retry; }spin_unlock (&clp->cl_lock);
@@ -7145,15 +7166,7 @@ nfs4_laundromat(struct nfsd_net *nn) clp = dp->dl_stid.sc_client;list_del_init (&dp->dl_recall_lru);revoke_delegation (dp); -/* -* Unpin without renewing: put_client_renew() would -* renew the reaped client's lease. -*/ -if (atomic_dec_and_lock (&clp->cl_rpc_users, &nn->client_lock)) { -if (is_client_expired (clp)) -wake_up_all (&expiry_wq); -spin_unlock (&nn->client_lock); - } +put_client_no_renew (clp); }spin_lock (&nn->client_lock);
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 6/6 NFSD: Prevent client use-after-free during blocked-lock reaping
In reply to Sasha LevinFrom: Chuck Lever <[email protected]>
[ Upstream commit 9026932ac8be4d0ae01db47f23619a98cc57b671 ]
A bare lock owner -- its only remaining reference a blocked lock on nn->blocked_locks_lru -- holds a raw pointer to its nfs4_client but no reference keeping the client alive. When the per-net laundromat reaps such a lock, freeing the nbl drops the owner reference held through flc_owner, and the final nfs4_put_stateowner() takes the client's cl_lock. Because the laundromat detaches the nbl first, __destroy_client() no longer finds it, so a concurrent force_expire_client() can free the client before nfs4_put_stateowner() runs, dereferencing cl_lock in freed memory.
Pin the client with cl_rpc_users before dropping nn->blocked_locks_lock, and skip clients already expiring, whose blocked locks __destroy_client() frees while holding an owner reference. Take nn->client_lock outside nn->blocked_locks_lock. Every other site holds nn->blocked_locks_lock as a leaf, acquiring no further lock, so placing nn->client_lock outside it cannot form a lock-order cycle.
--- fs/nfsd/nfs4state.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-)
fs/nfsd/nfs4state.c +20 -3
@@ -353,6 +353,16 @@ free_blocked_lock(struct nfsd4_blocked_lock *nbl)kref_put (&nbl->nbl_kref, free_nbl); } +/* A blocked lock's flc_owner is its nfs4_lockowner. */ +static struct nfs4_client * +nbl_client (struct nfsd4_blocked_lock *nbl) +{ +struct nfs4_lockowner *lo; + + lo = (struct nfs4_lockowner *)nbl->nbl_lock.c.flc_owner; +return lo->lo_owner.so_client; +} +static void remove_blocked_locks (struct nfs4_lockowner *lo) {
@@ -7196,22 +7206,29 @@ nfs4_laundromat(struct nfsd_net *nn)* indefinitely once the lock does become free. */ BUG_ON (!list_empty (&reaplist)); +spin_lock (&nn->client_lock);spin_lock (&nn->blocked_locks_lock); -while (!list_empty (&nn->blocked_locks_lru)) { - nbl =list_first_entry (&nn->blocked_locks_lru, - struct nfsd4_blocked_lock, nbl_lru); +list_for_each_safe (pos, next, &nn->blocked_locks_lru) { + nbl = list_entry(pos, struct nfsd4_blocked_lock, nbl_lru);if (!state_expired (<, nbl->nbl_time))break ; + clp =nbl_client (nbl); +if (is_client_expired (clp)) +continue ; +atomic_inc (&clp->cl_rpc_users);list_move (&nbl->nbl_lru, &reaplist);list_del_init (&nbl->nbl_list); }spin_unlock (&nn->blocked_locks_lock); +spin_unlock (&nn->client_lock);while (!list_empty (&reaplist)) { nbl =list_first_entry (&reaplist, struct nfsd4_blocked_lock, nbl_lru); + clp =nbl_client (nbl);list_del_init (&nbl->nbl_lru);free_blocked_lock (nbl); +put_client_no_renew (clp); }#ifdef CONFIG_NFSD_V4_2_INTER_SSC /* service the server-to-server copy delayed unmount list */
Signature · 1 line
2.53.0