6.18-stable review patch. If anyone has any objections, please let me know. ------------------ From: Jeff Layton [ Upstream commit 8be12e0cf21110f1e0b7fd21711ff13fb75bee72 ] Replace the global state_lock spinlock with a per-nfsd_net deleg_lock. The state_lock was only used to protect delegation lifecycle operations (the del_recall_lru list and delegation hash/unhash), all of which are scoped to a single network namespace. Making the lock per-net removes a source of unnecessary contention between containers. Signed-off-by: Jeff Layton Signed-off-by: Chuck Lever Stable-dep-of: 4683ca76b3b7 ("NFSD: Prevent client use-after-free during delegation revoke") Signed-off-by: Sasha Levin Signed-off-by: Greg Kroah-Hartman --- fs/nfsd/netns.h | 3 ++ fs/nfsd/nfs4state.c | 53 ++++++++++++++++++++++++++-------------------------- fs/nfsd/state.h | 2 - 3 files changed, 31 insertions(+), 27 deletions(-) --- a/fs/nfsd/netns.h +++ b/fs/nfsd/netns.h @@ -101,6 +101,9 @@ struct nfsd_net { */ struct list_head client_lru; struct list_head close_lru; + + /* protects del_recall_lru and delegation hash/unhash */ + spinlock_t deleg_lock ____cacheline_aligned; struct list_head del_recall_lru; /* protected by blocked_locks_lock */ --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -91,13 +91,6 @@ static void deleg_reaper(struct nfsd_net /* Locking: */ -/* - * Currently used for the del_recall_lru and file hash table. In an - * effort to decrease the scope of the client_mutex, this spinlock may - * eventually cover more: - */ -static DEFINE_SPINLOCK(state_lock); - enum nfsd4_st_mutex_lock_subclass { OPEN_STATEID_MUTEX = 0, LOCK_STATEID_MUTEX = 1, @@ -1350,8 +1343,9 @@ nfs4_delegation_exists(struct nfs4_clien { struct nfs4_delegation *searchdp = NULL; struct nfs4_client *searchclp = NULL; + struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id); - lockdep_assert_held(&state_lock); + lockdep_assert_held(&nn->deleg_lock); lockdep_assert_held(&fp->fi_lock); list_for_each_entry(searchdp, &fp->fi_delegations, dl_perfile) { @@ -1380,8 +1374,9 @@ static int hash_delegation_locked(struct nfs4_delegation *dp, struct nfs4_file *fp) { struct nfs4_client *clp = dp->dl_stid.sc_client; + struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id); - lockdep_assert_held(&state_lock); + lockdep_assert_held(&nn->deleg_lock); lockdep_assert_held(&fp->fi_lock); lockdep_assert_held(&clp->cl_lock); @@ -1403,8 +1398,10 @@ static bool unhash_delegation_locked(struct nfs4_delegation *dp, unsigned short statusmask) { struct nfs4_file *fp = dp->dl_stid.sc_file; + struct nfsd_net *nn = net_generic(dp->dl_stid.sc_client->net, + nfsd_net_id); - lockdep_assert_held(&state_lock); + lockdep_assert_held(&nn->deleg_lock); if (!delegation_hashed(dp)) return false; @@ -1429,10 +1426,12 @@ unhash_delegation_locked(struct nfs4_del static void destroy_delegation(struct nfs4_delegation *dp) { bool unhashed; + struct nfsd_net *nn = net_generic(dp->dl_stid.sc_client->net, + nfsd_net_id); - spin_lock(&state_lock); + spin_lock(&nn->deleg_lock); unhashed = unhash_delegation_locked(dp, SC_STATUS_CLOSED); - spin_unlock(&state_lock); + spin_unlock(&nn->deleg_lock); if (unhashed) destroy_unhashed_deleg(dp); } @@ -1900,11 +1899,11 @@ void nfsd4_revoke_states(struct nfsd_net */ refcount_inc(&stid->sc_count); dp = delegstateid(stid); - spin_lock(&state_lock); + spin_lock(&nn->deleg_lock); if (!unhash_delegation_locked( dp, SC_STATUS_ADMIN_REVOKED)) dp = NULL; - spin_unlock(&state_lock); + spin_unlock(&nn->deleg_lock); if (dp) revoke_delegation(dp); else @@ -2570,13 +2569,13 @@ __destroy_client(struct nfs4_client *clp struct nfs4_delegation *dp; LIST_HEAD(reaplist); - spin_lock(&state_lock); + spin_lock(&nn->deleg_lock); while (!list_empty(&clp->cl_delegations)) { dp = list_entry(clp->cl_delegations.next, struct nfs4_delegation, dl_perclnt); unhash_delegation_locked(dp, SC_STATUS_CLOSED); list_add(&dp->dl_recall_lru, &reaplist); } - spin_unlock(&state_lock); + spin_unlock(&nn->deleg_lock); while (!list_empty(&reaplist)) { dp = list_entry(reaplist.next, struct nfs4_delegation, dl_recall_lru); list_del_init(&dp->dl_recall_lru); @@ -5540,12 +5539,12 @@ static void nfsd4_cb_recall_prepare(stru * If the dl_time != 0, then we know that it has already been * queued for a lease break. Don't queue it again. */ - spin_lock(&state_lock); + spin_lock(&nn->deleg_lock); if (delegation_hashed(dp) && dp->dl_time == 0) { dp->dl_time = ktime_get_boottime_seconds(); list_add_tail(&dp->dl_recall_lru, &nn->del_recall_lru); } - spin_unlock(&state_lock); + spin_unlock(&nn->deleg_lock); } static int nfsd4_cb_recall_done(struct nfsd4_callback *cb, @@ -6159,6 +6158,7 @@ nfs4_set_delegation(struct nfsd4_open *o { bool deleg_ts = nfsd4_want_deleg_timestamps(open); struct nfs4_client *clp = stp->st_stid.sc_client; + struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id); struct nfs4_file *fp = stp->st_stid.sc_file; struct nfs4_clnt_odstate *odstate = stp->st_clnt_odstate; struct nfs4_delegation *dp; @@ -6218,7 +6218,7 @@ nfs4_set_delegation(struct nfsd4_open *o return ERR_PTR(-EOPNOTSUPP); } - spin_lock(&state_lock); + spin_lock(&nn->deleg_lock); spin_lock(&fp->fi_lock); if (nfs4_delegation_exists(clp, fp)) status = -EAGAIN; @@ -6233,7 +6233,7 @@ nfs4_set_delegation(struct nfsd4_open *o } else fp->fi_delegees++; spin_unlock(&fp->fi_lock); - spin_unlock(&state_lock); + spin_unlock(&nn->deleg_lock); if (nf) nfsd_file_put(nf); if (status) @@ -6277,13 +6277,13 @@ nfs4_set_delegation(struct nfsd4_open *o if (fp->fi_had_conflict) goto out_unlock; - spin_lock(&state_lock); + spin_lock(&nn->deleg_lock); spin_lock(&clp->cl_lock); spin_lock(&fp->fi_lock); status = hash_delegation_locked(dp, fp); spin_unlock(&fp->fi_lock); spin_unlock(&clp->cl_lock); - spin_unlock(&state_lock); + spin_unlock(&nn->deleg_lock); if (status) goto out_unlock; @@ -7075,7 +7075,7 @@ nfs4_laundromat(struct nfsd_net *nn) nfs40_clean_admin_revoked(nn, <); - spin_lock(&state_lock); + spin_lock(&nn->deleg_lock); list_for_each_safe(pos, next, &nn->del_recall_lru) { dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru); if (!state_expired(<, dp->dl_time)) @@ -7084,7 +7084,7 @@ nfs4_laundromat(struct nfsd_net *nn) unhash_delegation_locked(dp, SC_STATUS_REVOKED); list_add(&dp->dl_recall_lru, &reaplist); } - spin_unlock(&state_lock); + spin_unlock(&nn->deleg_lock); while (!list_empty(&reaplist)) { dp = list_first_entry(&reaplist, struct nfs4_delegation, dl_recall_lru); @@ -9129,6 +9129,7 @@ static int nfs4_state_create_net(struct INIT_LIST_HEAD(&nn->client_lru); INIT_LIST_HEAD(&nn->close_lru); INIT_LIST_HEAD(&nn->del_recall_lru); + spin_lock_init(&nn->deleg_lock); spin_lock_init(&nn->client_lock); spin_lock_init(&nn->s2s_cp_lock); idr_init(&nn->s2s_cp_stateids); @@ -9263,13 +9264,13 @@ nfs4_state_shutdown_net(struct net *net) locks_end_grace(&nn->nfsd4_manager); INIT_LIST_HEAD(&reaplist); - spin_lock(&state_lock); + spin_lock(&nn->deleg_lock); list_for_each_safe(pos, next, &nn->del_recall_lru) { dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru); unhash_delegation_locked(dp, SC_STATUS_CLOSED); list_add(&dp->dl_recall_lru, &reaplist); } - spin_unlock(&state_lock); + spin_unlock(&nn->deleg_lock); list_for_each_safe(pos, next, &reaplist) { dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru); list_del_init(&dp->dl_recall_lru); --- a/fs/nfsd/state.h +++ b/fs/nfsd/state.h @@ -123,7 +123,7 @@ struct nfs4_stid { #define SC_TYPE_LAYOUT BIT(3) unsigned short sc_type; -/* state_lock protects sc_status for delegation stateids. +/* nn->deleg_lock protects sc_status for delegation stateids. * ->cl_lock protects sc_status for open and lock stateids. * ->st_mutex also protect sc_status for open stateids. * ->ls_lock protects sc_status for layout stateids.