[PATCH] btrfs: fix use-after-free of reloc_control in btrfs_reloc_cow_block

Yun Zhou posted 1 patch 21 hours ago
fs/btrfs/relocation.c | 8 ++++++++
1 file changed, 8 insertions(+)
[PATCH] btrfs: fix use-after-free of reloc_control in btrfs_reloc_cow_block
Posted by Yun Zhou 21 hours ago
btrfs_reloc_cow_block() reads fs_info->reloc_ctl without holding any
lock and then uses the obtained rc pointer throughout the function
(including replace_file_extents which may sleep). Meanwhile, the
balance error path can concurrently call unset_reloc_control() followed
by free_reloc_control(), freeing rc while btrfs_reloc_cow_block() is
still using it.

The race window exists because btrfs_commit_current_transaction() in
relocate_block_group() may return immediately when the filesystem is in
error state (no running transaction to wait for), allowing
free_reloc_control() to execute while other threads still hold
references to rc obtained before unset_reloc_control().

Fix this by adding a btrfs_commit_current_transaction() call in
btrfs_relocate_block_group() right before free_reloc_control(). This
ensures that any concurrent thread that obtained rc via reloc_ctl
(which requires being in a transaction context) has completed its
transaction before rc is freed.

Reported-by: syzbot+0eea49bba18051dea35e@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=0eea49bba18051dea35e
Signed-off-by: Yun Zhou <yun.zhou@windriver.com>
---
 fs/btrfs/relocation.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c
index 3ebaf5880125..a01262d0b2ab 100644
--- a/fs/btrfs/relocation.c
+++ b/fs/btrfs/relocation.c
@@ -5443,6 +5443,14 @@ int btrfs_relocate_block_group(struct btrfs_fs_info *fs_info, u64 group_start,
 	reloc_chunk_end(fs_info);
 out_put_bg:
 	btrfs_put_block_group(bg);
+	/*
+	 * Ensure no concurrent btrfs_reloc_cow_block() is still using rc.
+	 * After unset_reloc_control() new callers will see reloc_ctl == NULL
+	 * and return immediately. But callers that read reloc_ctl before unset
+	 * are still in a transaction. Wait for the current transaction to
+	 * complete so all such callers have finished using rc.
+	 */
+	btrfs_commit_current_transaction(fs_info->tree_root);
 	free_reloc_control(rc);
 	return ret;
 }
-- 
2.43.0
Re: [PATCH] btrfs: fix use-after-free of reloc_control in btrfs_reloc_cow_block
Posted by Filipe Manana 18 hours ago
On Sun, Jun 7, 2026 at 6:36 AM Yun Zhou <yun.zhou@windriver.com> wrote:
>
> btrfs_reloc_cow_block() reads fs_info->reloc_ctl without holding any
> lock and then uses the obtained rc pointer throughout the function
> (including replace_file_extents which may sleep). Meanwhile, the
> balance error path can concurrently call unset_reloc_control() followed
> by free_reloc_control(), freeing rc while btrfs_reloc_cow_block() is
> still using it.
>
> The race window exists because btrfs_commit_current_transaction() in
> relocate_block_group() may return immediately when the filesystem is in
> error state (no running transaction to wait for), allowing
> free_reloc_control() to execute while other threads still hold
> references to rc obtained before unset_reloc_control().
>
> Fix this by adding a btrfs_commit_current_transaction() call in
> btrfs_relocate_block_group() right before free_reloc_control(). This
> ensures that any concurrent thread that obtained rc via reloc_ctl
> (which requires being in a transaction context) has completed its
> transaction before rc is freed.
>
> Reported-by: syzbot+0eea49bba18051dea35e@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=0eea49bba18051dea35e
> Signed-off-by: Yun Zhou <yun.zhou@windriver.com>
> ---
>  fs/btrfs/relocation.c | 8 ++++++++
>  1 file changed, 8 insertions(+)
>
> diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c
> index 3ebaf5880125..a01262d0b2ab 100644
> --- a/fs/btrfs/relocation.c
> +++ b/fs/btrfs/relocation.c
> @@ -5443,6 +5443,14 @@ int btrfs_relocate_block_group(struct btrfs_fs_info *fs_info, u64 group_start,
>         reloc_chunk_end(fs_info);
>  out_put_bg:
>         btrfs_put_block_group(bg);
> +       /*
> +        * Ensure no concurrent btrfs_reloc_cow_block() is still using rc.
> +        * After unset_reloc_control() new callers will see reloc_ctl == NULL
> +        * and return immediately. But callers that read reloc_ctl before unset
> +        * are still in a transaction. Wait for the current transaction to
> +        * complete so all such callers have finished using rc.
> +        */
> +       btrfs_commit_current_transaction(fs_info->tree_root);

This does not work at all:

btrfs_commit_current_transaction() can not join the current
transaction if the fs is in error state.
start_transaction() will return immediately with -EROFS if a
transaction abort happened before.

Second, the rc structure points to the block group for which the put
was done just above, so there's a potential for another use-after-free
here.

Finally, always calling btrfs_commit_current_transaction() creates
unnecessary overhead when no errors occur (the expected case).
This is due not only to the IO involved, but also because it results
in an unnecessary rotation of the backup roots in the super block.

Finally there's a similar race in btrfs_init_reloc_root() too.

And there's already a proper way to fix this in progress:

https://lore.kernel.org/linux-btrfs/3d57517e6e8ace7f1294277b0f03baa1150be55c.1780677297.git.fdmanana@suse.com/

Thanks.


>         free_reloc_control(rc);
>         return ret;
>  }
> --
> 2.43.0
>
>