|
|
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 |
}
|