Skip to content

Commit 2857bd5

Browse files
neilbrownchucklever
authored andcommitted
nfsd: provide locking for v4_end_grace
Writing to v4_end_grace can race with server shutdown and result in memory being accessed after it was freed - reclaim_str_hashtbl in particularly. We cannot hold nfsd_mutex across the nfsd4_end_grace() call as that is held while client_tracking_op->init() is called and that can wait for an upcall to nfsdcltrack which can write to v4_end_grace, resulting in a deadlock. nfsd4_end_grace() is also called by the landromat work queue and this doesn't require locking as server shutdown will stop the work and wait for it before freeing anything that nfsd4_end_grace() might access. However, we must be sure that writing to v4_end_grace doesn't restart the work item after shutdown has already waited for it. For this we add a new flag protected with nn->client_lock. It is set only while it is safe to make client tracking calls, and v4_end_grace only schedules work while the flag is set with the spinlock held. So this patch adds a nfsd_net field "client_tracking_active" which is set as described. Another field "grace_end_forced", is set when v4_end_grace is written. After this is set, and providing client_tracking_active is set, the laundromat is scheduled. This "grace_end_forced" field bypasses other checks for whether the grace period has finished. This resolves a race which can result in use-after-free. Reported-by: Li Lingfeng <[email protected]> Closes: https://lore.kernel.org/linux-nfs/[email protected]/T/#t Fixes: 7f5ef2e ("nfsd: add a v4_end_grace file to /proc/fs/nfsd") Cc: [email protected] Signed-off-by: NeilBrown <[email protected]> Tested-by: Li Lingfeng <[email protected]> Reviewed-by: Jeff Layton <[email protected]> Signed-off-by: Chuck Lever <[email protected]>
1 parent e901c7f commit 2857bd5

4 files changed

Lines changed: 44 additions & 5 deletions

File tree

fs/nfsd/netns.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ struct nfsd_net {
6666

6767
struct lock_manager nfsd4_manager;
6868
bool grace_ended;
69+
bool grace_end_forced;
70+
bool client_tracking_active;
6971
time64_t boot_time;
7072

7173
struct dentry *nfsd_client_dir;

fs/nfsd/nfs4state.c

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ static u64 current_sessionid = 1;
8484
/* forward declarations */
8585
static bool check_for_locks(struct nfs4_file *fp, struct nfs4_lockowner *lowner);
8686
static void nfs4_free_ol_stateid(struct nfs4_stid *stid);
87-
void nfsd4_end_grace(struct nfsd_net *nn);
87+
static void nfsd4_end_grace(struct nfsd_net *nn);
8888
static void _free_cpntf_state_locked(struct nfsd_net *nn, struct nfs4_cpntf_state *cps);
8989
static void nfsd4_file_hash_remove(struct nfs4_file *fi);
9090
static void deleg_reaper(struct nfsd_net *nn);
@@ -6570,7 +6570,7 @@ nfsd4_renew(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
65706570
return nfs_ok;
65716571
}
65726572

6573-
void
6573+
static void
65746574
nfsd4_end_grace(struct nfsd_net *nn)
65756575
{
65766576
/* do nothing if grace period already ended */
@@ -6603,6 +6603,33 @@ nfsd4_end_grace(struct nfsd_net *nn)
66036603
*/
66046604
}
66056605

6606+
/**
6607+
* nfsd4_force_end_grace - forcibly end the NFSv4 grace period
6608+
* @nn: network namespace for the server instance to be updated
6609+
*
6610+
* Forces bypass of normal grace period completion, then schedules
6611+
* the laundromat to end the grace period immediately. Does not wait
6612+
* for the grace period to fully terminate before returning.
6613+
*
6614+
* Return values:
6615+
* %true: Grace termination schedule
6616+
* %false: No action was taken
6617+
*/
6618+
bool nfsd4_force_end_grace(struct nfsd_net *nn)
6619+
{
6620+
if (!nn->client_tracking_ops)
6621+
return false;
6622+
spin_lock(&nn->client_lock);
6623+
if (nn->grace_ended || !nn->client_tracking_active) {
6624+
spin_unlock(&nn->client_lock);
6625+
return false;
6626+
}
6627+
WRITE_ONCE(nn->grace_end_forced, true);
6628+
mod_delayed_work(laundry_wq, &nn->laundromat_work, 0);
6629+
spin_unlock(&nn->client_lock);
6630+
return true;
6631+
}
6632+
66066633
/*
66076634
* If we've waited a lease period but there are still clients trying to
66086635
* reclaim, wait a little longer to give them a chance to finish.
@@ -6612,6 +6639,8 @@ static bool clients_still_reclaiming(struct nfsd_net *nn)
66126639
time64_t double_grace_period_end = nn->boot_time +
66136640
2 * nn->nfsd4_lease;
66146641

6642+
if (READ_ONCE(nn->grace_end_forced))
6643+
return false;
66156644
if (nn->track_reclaim_completes &&
66166645
atomic_read(&nn->nr_reclaim_complete) ==
66176646
nn->reclaim_str_hashtbl_size)
@@ -8931,6 +8960,8 @@ static int nfs4_state_create_net(struct net *net)
89318960
nn->unconf_name_tree = RB_ROOT;
89328961
nn->boot_time = ktime_get_real_seconds();
89338962
nn->grace_ended = false;
8963+
nn->grace_end_forced = false;
8964+
nn->client_tracking_active = false;
89348965
nn->nfsd4_manager.block_opens = true;
89358966
INIT_LIST_HEAD(&nn->nfsd4_manager.list);
89368967
INIT_LIST_HEAD(&nn->client_lru);
@@ -9011,6 +9042,10 @@ nfs4_state_start_net(struct net *net)
90119042
return ret;
90129043
locks_start_grace(net, &nn->nfsd4_manager);
90139044
nfsd4_client_tracking_init(net);
9045+
/* safe for laundromat to run now */
9046+
spin_lock(&nn->client_lock);
9047+
nn->client_tracking_active = true;
9048+
spin_unlock(&nn->client_lock);
90149049
if (nn->track_reclaim_completes && nn->reclaim_str_hashtbl_size == 0)
90159050
goto skip_grace;
90169051
printk(KERN_INFO "NFSD: starting %lld-second grace period (net %x)\n",
@@ -9059,6 +9094,9 @@ nfs4_state_shutdown_net(struct net *net)
90599094

90609095
shrinker_free(nn->nfsd_client_shrinker);
90619096
cancel_work_sync(&nn->nfsd_shrinker_work);
9097+
spin_lock(&nn->client_lock);
9098+
nn->client_tracking_active = false;
9099+
spin_unlock(&nn->client_lock);
90629100
cancel_delayed_work_sync(&nn->laundromat_work);
90639101
locks_end_grace(&nn->nfsd4_manager);
90649102

fs/nfsd/nfsctl.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,10 +1082,9 @@ static ssize_t write_v4_end_grace(struct file *file, char *buf, size_t size)
10821082
case 'Y':
10831083
case 'y':
10841084
case '1':
1085-
if (!nn->nfsd_serv)
1085+
if (!nfsd4_force_end_grace(nn))
10861086
return -EBUSY;
10871087
trace_nfsd_end_grace(netns(file));
1088-
nfsd4_end_grace(nn);
10891088
break;
10901089
default:
10911090
return -EINVAL;

fs/nfsd/state.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -849,7 +849,7 @@ static inline void nfsd4_revoke_states(struct net *net, struct super_block *sb)
849849
#endif
850850

851851
/* grace period management */
852-
void nfsd4_end_grace(struct nfsd_net *nn);
852+
bool nfsd4_force_end_grace(struct nfsd_net *nn);
853853

854854
/* nfs4recover operations */
855855
extern int nfsd4_client_tracking_init(struct net *net);

0 commit comments

Comments
 (0)