NFSD: Prevent client use-after-free during close_lru reaping

An nfs4_openowner left on nn->close_lru after its final CLOSE keeps
its last closed stateid in oo_last_closed_stid, holding only a raw
pointer to its nfs4_client. The laundromat reaps timed-out entries,
drops nn->client_lock, and calls nfs4_put_stid(), which dereferences
the client through cl_lock. Nothing pins the client across that
window, so a concurrent force_expire_client() can free it and
nfs4_put_stid() reads freed memory. __destroy_client() hits the same
race, walking clp->cl_openowners without cl_lock.

Pin the client with cl_rpc_users before dropping client_lock, and
skip clients already expiring. __destroy_client() then cleans up its
own close_lru entries through release_last_closed_stateid(), so
teardown no longer races the laundromat.

Fixes: 217526e7ec ("nfsd: protect the close_lru list and oo_last_closed_stid with client_lock")
Cc: stable@vger.kernel.org
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Link: https://patch.msgid.link/20260709-cel-v4-8-1d519d9be0cb@kernel.org
Signed-off-by: Chuck Lever <cel@kernel.org>
This commit is contained in:
Chuck Lever 2026-07-09 13:40:31 -04:00
parent 9026932ac8
commit 2330b788d7

View File

@ -7581,11 +7581,16 @@ nfs4_laundromat(struct nfsd_net *nn)
if (!state_expired(&lt, oo->oo_time))
break;
list_del_init(&oo->oo_close_lru);
clp = oo->oo_owner.so_client;
if (is_client_expired(clp))
continue;
stp = oo->oo_last_closed_stid;
oo->oo_last_closed_stid = NULL;
atomic_inc(&clp->cl_rpc_users);
spin_unlock(&nn->client_lock);
nfs4_put_stid(&stp->st_stid);
spin_lock(&nn->client_lock);
put_client_no_renew_locked(clp);
}
spin_unlock(&nn->client_lock);