Skip to content

Commit 417c662

Browse files
Jeff LaytonJ. Bruce Fields
authored andcommitted
nfsd: fix race that grants unrecallable delegation
If nfs4_setlease succesfully acquires a new delegation, then another task breaks the delegation before we reach hash_delegation_locked, then the breaking task will see an empty fi_delegations list and do nothing. The client will receive an open reply incorrectly granting a delegation and will never receive a recall. Move more of the delegation fields to be protected by the fi_lock. It's more granular than the state_lock and in later patches we'll want to be able to rely on it in addition to the state_lock. Attempt to acquire a delegation. If that succeeds, take the spinlocks and then check to see if the file has had a conflict show up since then. If it has, then we assume that the lease is no longer valid and that we shouldn't hand out a delegation. There's also one more potential (but very unlikely) problem. If the lease is broken before the delegation is hashed, then it could leak. In the event that the fi_delegations list is empty, reset the fl_break_time to jiffies so that it's cleaned up ASAP by the normal lease handling code. Signed-off-by: Trond Myklebust <[email protected]> Signed-off-by: Jeff Layton <[email protected]> Reviewed-by: Christoph Hellwig <[email protected]> Signed-off-by: J. Bruce Fields <[email protected]>
1 parent 57a3714 commit 417c662

File tree

1 file changed

+66
-24
lines changed

1 file changed

+66
-24
lines changed

fs/nfsd/nfs4state.c

Lines changed: 66 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,8 @@ nfs4_put_delegation(struct nfs4_delegation *dp)
624624

625625
static void nfs4_put_deleg_lease(struct nfs4_file *fp)
626626
{
627+
lockdep_assert_held(&state_lock);
628+
627629
if (!fp->fi_lease)
628630
return;
629631
if (atomic_dec_and_test(&fp->fi_delegees)) {
@@ -643,11 +645,10 @@ static void
643645
hash_delegation_locked(struct nfs4_delegation *dp, struct nfs4_file *fp)
644646
{
645647
lockdep_assert_held(&state_lock);
648+
lockdep_assert_held(&fp->fi_lock);
646649

647650
dp->dl_stid.sc_type = NFS4_DELEG_STID;
648-
spin_lock(&fp->fi_lock);
649651
list_add(&dp->dl_perfile, &fp->fi_delegations);
650-
spin_unlock(&fp->fi_lock);
651652
list_add(&dp->dl_perclnt, &dp->dl_stid.sc_client->cl_delegations);
652653
}
653654

@@ -659,17 +660,18 @@ unhash_delegation(struct nfs4_delegation *dp)
659660

660661
spin_lock(&state_lock);
661662
dp->dl_stid.sc_type = NFS4_CLOSED_DELEG_STID;
663+
spin_lock(&fp->fi_lock);
662664
list_del_init(&dp->dl_perclnt);
663665
list_del_init(&dp->dl_recall_lru);
664-
spin_lock(&fp->fi_lock);
665666
list_del_init(&dp->dl_perfile);
666667
spin_unlock(&fp->fi_lock);
667-
spin_unlock(&state_lock);
668668
if (fp) {
669669
nfs4_put_deleg_lease(fp);
670-
put_nfs4_file(fp);
671670
dp->dl_file = NULL;
672671
}
672+
spin_unlock(&state_lock);
673+
if (fp)
674+
put_nfs4_file(fp);
673675
}
674676

675677
static void destroy_revoked_delegation(struct nfs4_delegation *dp)
@@ -3141,10 +3143,19 @@ static void nfsd_break_deleg_cb(struct file_lock *fl)
31413143
*/
31423144
fl->fl_break_time = 0;
31433145

3144-
fp->fi_had_conflict = true;
31453146
spin_lock(&fp->fi_lock);
3146-
list_for_each_entry(dp, &fp->fi_delegations, dl_perfile)
3147-
nfsd_break_one_deleg(dp);
3147+
fp->fi_had_conflict = true;
3148+
/*
3149+
* If there are no delegations on the list, then we can't count on this
3150+
* lease ever being cleaned up. Set the fl_break_time to jiffies so that
3151+
* time_out_leases will do it ASAP. The fact that fi_had_conflict is now
3152+
* true should keep any new delegations from being hashed.
3153+
*/
3154+
if (list_empty(&fp->fi_delegations))
3155+
fl->fl_break_time = jiffies;
3156+
else
3157+
list_for_each_entry(dp, &fp->fi_delegations, dl_perfile)
3158+
nfsd_break_one_deleg(dp);
31483159
spin_unlock(&fp->fi_lock);
31493160
}
31503161

@@ -3491,46 +3502,77 @@ static int nfs4_setlease(struct nfs4_delegation *dp)
34913502
{
34923503
struct nfs4_file *fp = dp->dl_file;
34933504
struct file_lock *fl;
3494-
int status;
3505+
struct file *filp;
3506+
int status = 0;
34953507

34963508
fl = nfs4_alloc_init_lease(fp, NFS4_OPEN_DELEGATE_READ);
34973509
if (!fl)
34983510
return -ENOMEM;
3499-
fl->fl_file = find_readable_file(fp);
3500-
status = vfs_setlease(fl->fl_file, fl->fl_type, &fl);
3501-
if (status)
3502-
goto out_free;
3511+
filp = find_readable_file(fp);
3512+
if (!filp) {
3513+
/* We should always have a readable file here */
3514+
WARN_ON_ONCE(1);
3515+
return -EBADF;
3516+
}
3517+
fl->fl_file = filp;
3518+
status = vfs_setlease(filp, fl->fl_type, &fl);
3519+
if (status) {
3520+
locks_free_lock(fl);
3521+
goto out_fput;
3522+
}
3523+
spin_lock(&state_lock);
3524+
spin_lock(&fp->fi_lock);
3525+
/* Did the lease get broken before we took the lock? */
3526+
status = -EAGAIN;
3527+
if (fp->fi_had_conflict)
3528+
goto out_unlock;
3529+
/* Race breaker */
3530+
if (fp->fi_lease) {
3531+
status = 0;
3532+
atomic_inc(&fp->fi_delegees);
3533+
hash_delegation_locked(dp, fp);
3534+
goto out_unlock;
3535+
}
35033536
fp->fi_lease = fl;
3504-
fp->fi_deleg_file = fl->fl_file;
3537+
fp->fi_deleg_file = filp;
35053538
atomic_set(&fp->fi_delegees, 1);
3506-
spin_lock(&state_lock);
35073539
hash_delegation_locked(dp, fp);
3540+
spin_unlock(&fp->fi_lock);
35083541
spin_unlock(&state_lock);
35093542
return 0;
3510-
out_free:
3511-
if (fl->fl_file)
3512-
fput(fl->fl_file);
3513-
locks_free_lock(fl);
3543+
out_unlock:
3544+
spin_unlock(&fp->fi_lock);
3545+
spin_unlock(&state_lock);
3546+
out_fput:
3547+
fput(filp);
35143548
return status;
35153549
}
35163550

35173551
static int nfs4_set_delegation(struct nfs4_delegation *dp, struct nfs4_file *fp)
35183552
{
3553+
int status = 0;
3554+
35193555
if (fp->fi_had_conflict)
35203556
return -EAGAIN;
35213557
get_nfs4_file(fp);
3558+
spin_lock(&state_lock);
3559+
spin_lock(&fp->fi_lock);
35223560
dp->dl_file = fp;
3523-
if (!fp->fi_lease)
3561+
if (!fp->fi_lease) {
3562+
spin_unlock(&fp->fi_lock);
3563+
spin_unlock(&state_lock);
35243564
return nfs4_setlease(dp);
3525-
spin_lock(&state_lock);
3565+
}
35263566
atomic_inc(&fp->fi_delegees);
35273567
if (fp->fi_had_conflict) {
3528-
spin_unlock(&state_lock);
3529-
return -EAGAIN;
3568+
status = -EAGAIN;
3569+
goto out_unlock;
35303570
}
35313571
hash_delegation_locked(dp, fp);
3572+
out_unlock:
3573+
spin_unlock(&fp->fi_lock);
35323574
spin_unlock(&state_lock);
3533-
return 0;
3575+
return status;
35343576
}
35353577

35363578
static void nfsd4_open_deleg_none_ext(struct nfsd4_open *open, int status)

0 commit comments

Comments
 (0)