fbfa8c
From: "J. Bruce Fields" <bfields@redhat.com>
fbfa8c
Date: Mon, 29 Nov 2021 15:08:00 -0500
fbfa8c
Subject: [PATCH] nfsd: fix use-after-free due to delegation race
fbfa8c
Git-commit: 548ec0805c399c65ed66c6641be467f717833ab5
fbfa8c
Patch-mainline: v5.16
fbfa8c
References: bsc#1208813
fbfa8c
fbfa8c
A delegation break could arrive as soon as we've called vfs_setlease.  A
fbfa8c
delegation break runs a callback which immediately (in
fbfa8c
nfsd4_cb_recall_prepare) adds the delegation to del_recall_lru.  If we
fbfa8c
then exit nfs4_set_delegation without hashing the delegation, it will be
fbfa8c
freed as soon as the callback is done with it, without ever being
fbfa8c
removed from del_recall_lru.
fbfa8c
fbfa8c
Symptoms show up later as use-after-free or list corruption warnings,
fbfa8c
usually in the laundromat thread.
fbfa8c
fbfa8c
I suspect aba2072f4523 "nfsd: grant read delegations to clients holding
fbfa8c
writes" made this bug easier to hit, but I looked as far back as v3.0
fbfa8c
and it looks to me it already had the same problem.  So I'm not sure
fbfa8c
where the bug was introduced; it may have been there from the beginning.
fbfa8c
fbfa8c
Cc: stable@vger.kernel.org
fbfa8c
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
fbfa8c
Acked-by: NeilBrown <neilb@suse.com>
fbfa8c
fbfa8c
---
fbfa8c
 fs/nfsd/nfs4state.c |    9 +++++++--
fbfa8c
 1 file changed, 7 insertions(+), 2 deletions(-)
fbfa8c
fbfa8c
--- a/fs/nfsd/nfs4state.c
fbfa8c
+++ b/fs/nfsd/nfs4state.c
fbfa8c
@@ -1047,6 +1047,11 @@ hash_delegation_locked(struct nfs4_deleg
fbfa8c
 	return 0;
fbfa8c
 }
fbfa8c
 
fbfa8c
+static bool delegation_hashed(struct nfs4_delegation *dp)
fbfa8c
+{
fbfa8c
+	return !(list_empty(&dp->dl_perfile));
fbfa8c
+}
fbfa8c
+
fbfa8c
 static bool
fbfa8c
 unhash_delegation_locked(struct nfs4_delegation *dp)
fbfa8c
 {
fbfa8c
@@ -1054,7 +1059,7 @@ unhash_delegation_locked(struct nfs4_del
fbfa8c
 
fbfa8c
 	lockdep_assert_held(&state_lock);
fbfa8c
 
fbfa8c
-	if (list_empty(&dp->dl_perfile))
fbfa8c
+	if (!delegation_hashed(dp))
fbfa8c
 		return false;
fbfa8c
 
fbfa8c
 	dp->dl_stid.sc_type = NFS4_CLOSED_DELEG_STID;
fbfa8c
@@ -4505,7 +4510,7 @@ static void nfsd4_cb_recall_prepare(stru
fbfa8c
 	 * queued for a lease break. Don't queue it again.
fbfa8c
 	 */
fbfa8c
 	spin_lock(&state_lock);
fbfa8c
-	if (dp->dl_time == 0) {
fbfa8c
+	if (delegation_hashed(dp) && dp->dl_time == 0) {
fbfa8c
 		dp->dl_time = get_seconds();
fbfa8c
 		list_add_tail(&dp->dl_recall_lru, &nn->del_recall_lru);
fbfa8c
 	}