nfsd-6.12 fixes:

- Fix a couple of use-after-free bugs
 -----BEGIN PGP SIGNATURE-----
 
 iQIyBAABCAAdFiEEKLLlsBKG3yQ88j7+M2qzM29mf5cFAmcbp9YACgkQM2qzM29m
 f5dzAg/4lCFRbsebia7qktW88ZDBbAoZyKolk2DHfjZXCuq5DHb/5I/Hk1rGyTYs
 VaJmCU59ZdpyBFSdQhOYKf2xvgNPvJG02U8il5KWtMAY5cStXFjeU0FSDoC5O4Dl
 9IoaVbtAWGMCjxWJ1WEGpU82JoM7moSVB4G718LlxF+4cUS7idq5se0uK31WQvft
 DmJsOfmnch1Y/7+RRcDwbBu0HwP2ZQHS8zMYMQ2JGXPDZJFenTibezVb36YyzyZn
 WQfvaW6MmdiVL9omZxvURL9WuBKA2L+Ly/92PyHaflcAXSngcpfu28IIQbzp/m7K
 JT/3ad32lB7F3LrDP5I4gwVh8oGLYiI5r7RBWo0e98LPvAR/89gBVdZHhjasstCh
 nAL7Kk6P/jQdbM/KR9T+yS7xTVScI5Wp4Xcitz2mlHgU4br67GO9gpo1e/tqXenm
 Gasapkg5qCduz+ksj2vwpeFXKQi+qwJgfVGKMxELoV8qTazyr09Dfgouqe045ztl
 /0khkOrLkw0bYLDNJKhj/XG0ZEV5V10c/0PEnivC2BHVQioBIDRQAaH2S2G/8vQH
 MdWayGhNlTV0g4DdtMVPxf5uN+uQmfMsj5BIe17NIUYxiJnw0CSM/bzHUcJ15+DH
 nTblaek6sa5BFpZ2fed8by4il4/tme3NOJEeHnSqe/3QKyZgOQ==
 =1YOr
 -----END PGP SIGNATURE-----

Merge tag 'nfsd-6.12-2' of git://git.kernel.org/pub/scm/linux/kernel/git/cel/linux

Pull nfsd fixes from Chuck Lever:

 - Fix a couple of use-after-free bugs

* tag 'nfsd-6.12-2' of git://git.kernel.org/pub/scm/linux/kernel/git/cel/linux:
  nfsd: cancel nfsd_shrinker_work using sync mode in nfs4_state_shutdown_net
  nfsd: fix race between laundromat and free_stateid
This commit is contained in:
Linus Torvalds 2024-10-25 11:38:15 -07:00
commit f647053312
2 changed files with 43 additions and 9 deletions

View file

@ -1359,21 +1359,47 @@ static void destroy_delegation(struct nfs4_delegation *dp)
destroy_unhashed_deleg(dp);
}
/**
* revoke_delegation - perform nfs4 delegation structure cleanup
* @dp: pointer to the delegation
*
* This function assumes that it's called either from the administrative
* interface (nfsd4_revoke_states()) that's revoking a specific delegation
* 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.
*
* 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
* we can proceed to removing it from recalled list. However, if @dp state
* isn't marked SC_STATUS_FREED, it means we need place it on the cl_revoked
* list and wait for the FREE_STATEID to arrive from the client. At the same
* time, we need to mark it as SC_STATUS_FREEABLE to indicate to the
* nfsd4_free_stateid() function that this stateid has already been added
* to the cl_revoked list and that nfsd4_free_stateid() is now responsible
* for removing it from the list. Inspection of where the delegation state
* in the revocation process is protected by the clp->cl_lock.
*/
static void revoke_delegation(struct nfs4_delegation *dp)
{
struct nfs4_client *clp = dp->dl_stid.sc_client;
WARN_ON(!list_empty(&dp->dl_recall_lru));
WARN_ON_ONCE(!(dp->dl_stid.sc_status &
(SC_STATUS_REVOKED | SC_STATUS_ADMIN_REVOKED)));
trace_nfsd_stid_revoke(&dp->dl_stid);
if (dp->dl_stid.sc_status &
(SC_STATUS_REVOKED | SC_STATUS_ADMIN_REVOKED)) {
spin_lock(&clp->cl_lock);
refcount_inc(&dp->dl_stid.sc_count);
list_add(&dp->dl_recall_lru, &clp->cl_revoked);
spin_unlock(&clp->cl_lock);
spin_lock(&clp->cl_lock);
if (dp->dl_stid.sc_status & SC_STATUS_FREED) {
list_del_init(&dp->dl_recall_lru);
goto out;
}
list_add(&dp->dl_recall_lru, &clp->cl_revoked);
dp->dl_stid.sc_status |= SC_STATUS_FREEABLE;
out:
spin_unlock(&clp->cl_lock);
destroy_unhashed_deleg(dp);
}
@ -1780,6 +1806,7 @@ void nfsd4_revoke_states(struct net *net, struct super_block *sb)
mutex_unlock(&stp->st_mutex);
break;
case SC_TYPE_DELEG:
refcount_inc(&stid->sc_count);
dp = delegstateid(stid);
spin_lock(&state_lock);
if (!unhash_delegation_locked(
@ -6545,6 +6572,7 @@ nfs4_laundromat(struct nfsd_net *nn)
dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru);
if (!state_expired(&lt, dp->dl_time))
break;
refcount_inc(&dp->dl_stid.sc_count);
unhash_delegation_locked(dp, SC_STATUS_REVOKED);
list_add(&dp->dl_recall_lru, &reaplist);
}
@ -7157,7 +7185,9 @@ nfsd4_free_stateid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
s->sc_status |= SC_STATUS_CLOSED;
spin_unlock(&s->sc_lock);
dp = delegstateid(s);
list_del_init(&dp->dl_recall_lru);
if (s->sc_status & SC_STATUS_FREEABLE)
list_del_init(&dp->dl_recall_lru);
s->sc_status |= SC_STATUS_FREED;
spin_unlock(&cl->cl_lock);
nfs4_put_stid(s);
ret = nfs_ok;
@ -7487,7 +7517,9 @@ nfsd4_delegreturn(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
if ((status = fh_verify(rqstp, &cstate->current_fh, S_IFREG, 0)))
return status;
status = nfsd4_lookup_stateid(cstate, stateid, SC_TYPE_DELEG, 0, &s, nn);
status = nfsd4_lookup_stateid(cstate, stateid, SC_TYPE_DELEG,
SC_STATUS_REVOKED | SC_STATUS_FREEABLE,
&s, nn);
if (status)
goto out;
dp = delegstateid(s);
@ -8684,7 +8716,7 @@ nfs4_state_shutdown_net(struct net *net)
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
shrinker_free(nn->nfsd_client_shrinker);
cancel_work(&nn->nfsd_shrinker_work);
cancel_work_sync(&nn->nfsd_shrinker_work);
cancel_delayed_work_sync(&nn->laundromat_work);
locks_end_grace(&nn->nfsd4_manager);

View file

@ -114,6 +114,8 @@ struct nfs4_stid {
/* For a deleg stateid kept around only to process free_stateid's: */
#define SC_STATUS_REVOKED BIT(1)
#define SC_STATUS_ADMIN_REVOKED BIT(2)
#define SC_STATUS_FREEABLE BIT(3)
#define SC_STATUS_FREED BIT(4)
unsigned short sc_status;
struct list_head sc_cp_list;