]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
NFS: use refcount_inc_not_zero nfs_start_delegation_return
authorChristoph Hellwig <hch@lst.de>
Wed, 7 Jan 2026 07:27:10 +0000 (08:27 +0100)
committerAnna Schumaker <anna.schumaker@oracle.com>
Tue, 20 Jan 2026 19:49:47 +0000 (14:49 -0500)
Using the unconditional reference increment means we can take a
reference to a delegation already in the RCU grace period, which could
cause a use after free under very unlikely conditions.  Switch to use
refcount_inc_not_zero instead.

Signed-off-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Anna Schumaker <anna.schumaker@oracle.com>
fs/nfs/delegation.c

index 684ec87dd4ebe638f5774000b8038df8750f0d1d..a3996bd702364289351e61f3a9673d51e4643337 100644 (file)
@@ -60,12 +60,6 @@ static void nfs_mark_delegation_revoked(struct nfs_server *server,
        }
 }
 
-static struct nfs_delegation *nfs_get_delegation(struct nfs_delegation *delegation)
-{
-       refcount_inc(&delegation->refcount);
-       return delegation;
-}
-
 void nfs_put_delegation(struct nfs_delegation *delegation)
 {
        if (refcount_dec_and_test(&delegation->refcount))
@@ -312,25 +306,29 @@ static struct inode *nfs_delegation_grab_inode(struct nfs_delegation *delegation
 static struct nfs_delegation *
 nfs_start_delegation_return(struct nfs_inode *nfsi)
 {
-       struct nfs_delegation *ret = NULL;
        struct nfs_delegation *delegation;
+       bool return_now = false;
 
        lockdep_assert_in_rcu_read_lock();
 
        delegation = rcu_dereference(nfsi->delegation);
-       if (!delegation)
+       if (!delegation || !refcount_inc_not_zero(&delegation->refcount))
                return NULL;
 
        spin_lock(&delegation->lock);
        if (delegation->inode &&
            !test_and_set_bit(NFS_DELEGATION_RETURNING, &delegation->flags)) {
                clear_bit(NFS_DELEGATION_RETURN_DELAYED, &delegation->flags);
-               ret = nfs_get_delegation(delegation);
+               return_now = true;
        }
        spin_unlock(&delegation->lock);
-       if (ret)
-               nfs_clear_verifier_delegated(&nfsi->vfs_inode);
-       return ret;
+
+       if (!return_now) {
+               nfs_put_delegation(delegation);
+               return NULL;
+       }
+       nfs_clear_verifier_delegated(&nfsi->vfs_inode);
+       return delegation;
 }
 
 static void nfs_abort_delegation_return(struct nfs_delegation *delegation,