]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
btrfs: fix invalid pointer dereference in __btrfs_run_delayed_refs()
authorFilipe Manana <fdmanana@suse.com>
Thu, 21 May 2026 14:19:37 +0000 (15:19 +0100)
committerFilipe Manana <fdmanana@suse.com>
Tue, 9 Jun 2026 10:49:25 +0000 (11:49 +0100)
In the beginning of the loop, we try to obtain a locked delayed ref head,
if 'locked_ref' is currently NULL, by calling btrfs_select_ref_head(),
which can return an error pointer. If the error pointer is -EAGAIN we do
a continue and go back to the beginning of the loop, which will not try
again to call btrfs_select_ref_head() since 'locked_ref' is no longer
NULL but it's ERR_PTR(-EAGAIN), and then we do:

   spin_lock(&locked_ref->lock);

against a ERR_PTR(-EAGAIN) value, generating an invalid pointer
dereference.

Fix this by ensuring that 'locked_ref' is set to NULL when
btrfs_select_ref_head() returns ERR_PTR(-EAGAIN) and incrementing 'count'
as well, to prevent infinite looping. We do this by doing a goto to the
bottom of the loop that already sets 'locked_ref' to NULL and does a
cond_resched(), with an increment to 'count' right before the goto.
These measures were in place before the refactoring in commit 0110a4c43451
("btrfs: refactor __btrfs_run_delayed_refs loop") but were unintentionally
lost afterwards.

Reported-by: Dan Carpenter <error27@gmail.com>
Link: https://lore.kernel.org/linux-btrfs/ag8ARRwykv8bpJ87@stanley.mountain/
Fixes: 0110a4c43451 ("btrfs: refactor __btrfs_run_delayed_refs loop")
Reviewed-by: Boris Burkov <boris@bur.io>
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/extent-tree.c

index ecc1acb1e3408d30560f1df70a3f40e1e01f6788..6030cdbdb742104330e9691eae0317705dd45487 100644 (file)
@@ -2108,7 +2108,8 @@ static noinline int __btrfs_run_delayed_refs(struct btrfs_trans_handle *trans,
                        locked_ref = btrfs_select_ref_head(fs_info, delayed_refs);
                        if (IS_ERR_OR_NULL(locked_ref)) {
                                if (PTR_ERR(locked_ref) == -EAGAIN) {
-                                       continue;
+                                       count++;
+                                       goto again;
                                } else {
                                        break;
                                }
@@ -2156,7 +2157,7 @@ static noinline int __btrfs_run_delayed_refs(struct btrfs_trans_handle *trans,
                 * Either success case or btrfs_run_delayed_refs_for_head
                 * returned -EAGAIN, meaning we need to select another head
                 */
-
+again:
                locked_ref = NULL;
                cond_resched();
        } while ((min_bytes != U64_MAX && bytes_processed < min_bytes) ||