NFSD: Fix delegation reference leak in nfsd4_revoke_states

When revoking delegation state, nfsd4_revoke_states() takes an extra
reference on the stid before calling unhash_delegation_locked(). If
unhash_delegation_locked() returns false (the delegation was already
unhashed by a concurrent path), dp is set to NULL and
revoke_delegation() is skipped, but the extra reference is never
released. Each occurrence permanently pins the stid in memory. The
leaked reference also prevents nfs4_put_stid() from decrementing
cl_admin_revoked, leaving the counter permanently inflated.

Drop the extra reference in the failure path.

Fixes: 8dd91e8d31 ("nfsd: fix race between laundromat and free_stateid")
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
This commit is contained in:
Chuck Lever 2026-03-24 11:18:12 -04:00
parent e7f558158e
commit 625981c8f3

View File

@ -1376,7 +1376,8 @@ static void destroy_delegation(struct nfs4_delegation *dp)
* stateid or it's called from a laundromat thread (nfsd4_landromat()) that
* determined that this specific state has expired and needs to be revoked
* (both mark state with the appropriate stid sc_status mode). It is also
* assumed that a reference was taken on the @dp state.
* assumed that a reference was taken on the @dp state. This function
* consumes that reference.
*
* If this function finds that the @dp state is SC_STATUS_FREED it means
* that a FREE_STATEID operation for this stateid has been processed and
@ -1839,6 +1840,10 @@ void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb)
mutex_unlock(&stp->st_mutex);
break;
case SC_TYPE_DELEG:
/* Extra reference guards against concurrent
* FREE_STATEID; revoke_delegation() consumes
* it, otherwise release it directly.
*/
refcount_inc(&stid->sc_count);
dp = delegstateid(stid);
spin_lock(&nn->deleg_lock);
@ -1848,6 +1853,8 @@ void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb)
spin_unlock(&nn->deleg_lock);
if (dp)
revoke_delegation(dp);
else
nfs4_put_stid(stid);
break;
case SC_TYPE_LAYOUT:
ls = layoutstateid(stid);