Skip to content

Commit f647053

Browse files
committed
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
2 parents b423f5a + d5ff2fb commit f647053

File tree

2 files changed

+43
-9
lines changed

2 files changed

+43
-9
lines changed

fs/nfsd/nfs4state.c

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,21 +1359,47 @@ static void destroy_delegation(struct nfs4_delegation *dp)
13591359
destroy_unhashed_deleg(dp);
13601360
}
13611361

1362+
/**
1363+
* revoke_delegation - perform nfs4 delegation structure cleanup
1364+
* @dp: pointer to the delegation
1365+
*
1366+
* This function assumes that it's called either from the administrative
1367+
* interface (nfsd4_revoke_states()) that's revoking a specific delegation
1368+
* stateid or it's called from a laundromat thread (nfsd4_landromat()) that
1369+
* determined that this specific state has expired and needs to be revoked
1370+
* (both mark state with the appropriate stid sc_status mode). It is also
1371+
* assumed that a reference was taken on the @dp state.
1372+
*
1373+
* If this function finds that the @dp state is SC_STATUS_FREED it means
1374+
* that a FREE_STATEID operation for this stateid has been processed and
1375+
* we can proceed to removing it from recalled list. However, if @dp state
1376+
* isn't marked SC_STATUS_FREED, it means we need place it on the cl_revoked
1377+
* list and wait for the FREE_STATEID to arrive from the client. At the same
1378+
* time, we need to mark it as SC_STATUS_FREEABLE to indicate to the
1379+
* nfsd4_free_stateid() function that this stateid has already been added
1380+
* to the cl_revoked list and that nfsd4_free_stateid() is now responsible
1381+
* for removing it from the list. Inspection of where the delegation state
1382+
* in the revocation process is protected by the clp->cl_lock.
1383+
*/
13621384
static void revoke_delegation(struct nfs4_delegation *dp)
13631385
{
13641386
struct nfs4_client *clp = dp->dl_stid.sc_client;
13651387

13661388
WARN_ON(!list_empty(&dp->dl_recall_lru));
1389+
WARN_ON_ONCE(!(dp->dl_stid.sc_status &
1390+
(SC_STATUS_REVOKED | SC_STATUS_ADMIN_REVOKED)));
13671391

13681392
trace_nfsd_stid_revoke(&dp->dl_stid);
13691393

1370-
if (dp->dl_stid.sc_status &
1371-
(SC_STATUS_REVOKED | SC_STATUS_ADMIN_REVOKED)) {
1372-
spin_lock(&clp->cl_lock);
1373-
refcount_inc(&dp->dl_stid.sc_count);
1374-
list_add(&dp->dl_recall_lru, &clp->cl_revoked);
1375-
spin_unlock(&clp->cl_lock);
1394+
spin_lock(&clp->cl_lock);
1395+
if (dp->dl_stid.sc_status & SC_STATUS_FREED) {
1396+
list_del_init(&dp->dl_recall_lru);
1397+
goto out;
13761398
}
1399+
list_add(&dp->dl_recall_lru, &clp->cl_revoked);
1400+
dp->dl_stid.sc_status |= SC_STATUS_FREEABLE;
1401+
out:
1402+
spin_unlock(&clp->cl_lock);
13771403
destroy_unhashed_deleg(dp);
13781404
}
13791405

@@ -1780,6 +1806,7 @@ void nfsd4_revoke_states(struct net *net, struct super_block *sb)
17801806
mutex_unlock(&stp->st_mutex);
17811807
break;
17821808
case SC_TYPE_DELEG:
1809+
refcount_inc(&stid->sc_count);
17831810
dp = delegstateid(stid);
17841811
spin_lock(&state_lock);
17851812
if (!unhash_delegation_locked(
@@ -6545,6 +6572,7 @@ nfs4_laundromat(struct nfsd_net *nn)
65456572
dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru);
65466573
if (!state_expired(&lt, dp->dl_time))
65476574
break;
6575+
refcount_inc(&dp->dl_stid.sc_count);
65486576
unhash_delegation_locked(dp, SC_STATUS_REVOKED);
65496577
list_add(&dp->dl_recall_lru, &reaplist);
65506578
}
@@ -7157,7 +7185,9 @@ nfsd4_free_stateid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
71577185
s->sc_status |= SC_STATUS_CLOSED;
71587186
spin_unlock(&s->sc_lock);
71597187
dp = delegstateid(s);
7160-
list_del_init(&dp->dl_recall_lru);
7188+
if (s->sc_status & SC_STATUS_FREEABLE)
7189+
list_del_init(&dp->dl_recall_lru);
7190+
s->sc_status |= SC_STATUS_FREED;
71617191
spin_unlock(&cl->cl_lock);
71627192
nfs4_put_stid(s);
71637193
ret = nfs_ok;
@@ -7487,7 +7517,9 @@ nfsd4_delegreturn(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
74877517
if ((status = fh_verify(rqstp, &cstate->current_fh, S_IFREG, 0)))
74887518
return status;
74897519

7490-
status = nfsd4_lookup_stateid(cstate, stateid, SC_TYPE_DELEG, 0, &s, nn);
7520+
status = nfsd4_lookup_stateid(cstate, stateid, SC_TYPE_DELEG,
7521+
SC_STATUS_REVOKED | SC_STATUS_FREEABLE,
7522+
&s, nn);
74917523
if (status)
74927524
goto out;
74937525
dp = delegstateid(s);
@@ -8684,7 +8716,7 @@ nfs4_state_shutdown_net(struct net *net)
86848716
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
86858717

86868718
shrinker_free(nn->nfsd_client_shrinker);
8687-
cancel_work(&nn->nfsd_shrinker_work);
8719+
cancel_work_sync(&nn->nfsd_shrinker_work);
86888720
cancel_delayed_work_sync(&nn->laundromat_work);
86898721
locks_end_grace(&nn->nfsd4_manager);
86908722

fs/nfsd/state.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ struct nfs4_stid {
114114
/* For a deleg stateid kept around only to process free_stateid's: */
115115
#define SC_STATUS_REVOKED BIT(1)
116116
#define SC_STATUS_ADMIN_REVOKED BIT(2)
117+
#define SC_STATUS_FREEABLE BIT(3)
118+
#define SC_STATUS_FREED BIT(4)
117119
unsigned short sc_status;
118120

119121
struct list_head sc_cp_list;

0 commit comments

Comments
 (0)