diff mbox series

btrfs: fix use-after-free when COWing tree bock and tracing is enabled

Message ID 8e502fc69ea68d1647707d947e0e4625f0dd1af0.1733934886.git.fdmanana@suse.com (mailing list archive)
State New
Headers show
Series btrfs: fix use-after-free when COWing tree bock and tracing is enabled | expand

Commit Message

Filipe Manana Dec. 11, 2024, 4:41 p.m. UTC
From: Filipe Manana <fdmanana@suse.com>

When a COWing a tree block, at btrfs_cow_block(), and we have the
tracepoint trace_btrfs_cow_block() enabled and preemption is also enabled
(CONFIG_PREEMPT=y), we can trigger a use-after-free in the COWed extent
buffer while inside the tracepoint code. This is because in some paths
that call btrfs_cow_block(), such as btrfs_search_slot(), we are holding
the last reference on the extent buffer @buf so btrfs_force_cow_block()
drops the last reference on the @buf extent buffer when it calls
free_extent_buffer_stale(buf), which schedules the release of the extent
buffer with RCU. This means that if we are on a kernel with preemption,
the current task may be preempted before calling trace_btrfs_cow_block()
and the extent buffer already released by the time trace_btrfs_cow_block()
is called, resulting in a use-after-free.

Fix this by grabbing an extra reference on the extent buffer before
calling btrfs_force_cow_block() at btrfs_cow_block(), and then dropping
it after calling the tracepoint.

Reported-by: syzbot+8517da8635307182c8a5@syzkaller.appspotmail.com
Link: https://lore.kernel.org/linux-btrfs/6759a9b9.050a0220.1ac542.000d.GAE@google.com/
Signed-off-by: Filipe Manana <fdmanana@suse.com>
---
 fs/btrfs/ctree.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

Comments

Qu Wenruo Dec. 11, 2024, 8:17 p.m. UTC | #1
在 2024/12/12 03:11, fdmanana@kernel.org 写道:
> From: Filipe Manana <fdmanana@suse.com>
> 
> When a COWing a tree block, at btrfs_cow_block(), and we have the
> tracepoint trace_btrfs_cow_block() enabled and preemption is also enabled
> (CONFIG_PREEMPT=y), we can trigger a use-after-free in the COWed extent
> buffer while inside the tracepoint code. This is because in some paths
> that call btrfs_cow_block(), such as btrfs_search_slot(), we are holding
> the last reference on the extent buffer @buf so btrfs_force_cow_block()
> drops the last reference on the @buf extent buffer when it calls
> free_extent_buffer_stale(buf), which schedules the release of the extent
> buffer with RCU. This means that if we are on a kernel with preemption,
> the current task may be preempted before calling trace_btrfs_cow_block()
> and the extent buffer already released by the time trace_btrfs_cow_block()
> is called, resulting in a use-after-free.
> 
> Fix this by grabbing an extra reference on the extent buffer before
> calling btrfs_force_cow_block() at btrfs_cow_block(), and then dropping
> it after calling the tracepoint.
> 
> Reported-by: syzbot+8517da8635307182c8a5@syzkaller.appspotmail.com
> Link: https://lore.kernel.org/linux-btrfs/6759a9b9.050a0220.1ac542.000d.GAE@google.com/
> Signed-off-by: Filipe Manana <fdmanana@suse.com>

Thanks for pinning down the error.

Although considering there is really only one caller of 
trace_btrfs_cow_block(), can we just move this only caller into 
btrfs_force_cow_block() before freeing @buf?

Thanks,
Qu

> ---
>   fs/btrfs/ctree.c | 11 +++++++++++
>   1 file changed, 11 insertions(+)
> 
> diff --git a/fs/btrfs/ctree.c b/fs/btrfs/ctree.c
> index 693dc27ffb89..3a28e77b6d72 100644
> --- a/fs/btrfs/ctree.c
> +++ b/fs/btrfs/ctree.c
> @@ -751,10 +751,21 @@ int btrfs_cow_block(struct btrfs_trans_handle *trans,
>   	 * Also We don't care about the error, as it's handled internally.
>   	 */
>   	btrfs_qgroup_trace_subtree_after_cow(trans, root, buf);
> +	/*
> +	 * When we are called from btrfs_search_slot() for example, we are
> +	 * not holding an extra reference on @buf so btrfs_force_cow_block()
> +	 * does a free_extent_buffer_stale() on the last reference and schedules
> +	 * the extent buffer release with RCU, so we can trigger a
> +	 * use-after-free in the trace_btrfs_cow_block() call below in case
> +	 * preemption is enabled (CONFIG_PREEMPT=y). So grab an extra reference
> +	 * to prevent that.
> +	 */
> +	atomic_inc(&buf->refs);
>   	ret = btrfs_force_cow_block(trans, root, buf, parent, parent_slot,
>   				    cow_ret, search_start, 0, nest);
>   
>   	trace_btrfs_cow_block(root, buf, *cow_ret);
> +	free_extent_buffer_stale(buf);
>   
>   	return ret;
>   }
Filipe Manana Dec. 11, 2024, 9:53 p.m. UTC | #2
On Wed, Dec 11, 2024 at 8:18 PM Qu Wenruo <wqu@suse.com> wrote:
>
>
>
> 在 2024/12/12 03:11, fdmanana@kernel.org 写道:
> > From: Filipe Manana <fdmanana@suse.com>
> >
> > When a COWing a tree block, at btrfs_cow_block(), and we have the
> > tracepoint trace_btrfs_cow_block() enabled and preemption is also enabled
> > (CONFIG_PREEMPT=y), we can trigger a use-after-free in the COWed extent
> > buffer while inside the tracepoint code. This is because in some paths
> > that call btrfs_cow_block(), such as btrfs_search_slot(), we are holding
> > the last reference on the extent buffer @buf so btrfs_force_cow_block()
> > drops the last reference on the @buf extent buffer when it calls
> > free_extent_buffer_stale(buf), which schedules the release of the extent
> > buffer with RCU. This means that if we are on a kernel with preemption,
> > the current task may be preempted before calling trace_btrfs_cow_block()
> > and the extent buffer already released by the time trace_btrfs_cow_block()
> > is called, resulting in a use-after-free.
> >
> > Fix this by grabbing an extra reference on the extent buffer before
> > calling btrfs_force_cow_block() at btrfs_cow_block(), and then dropping
> > it after calling the tracepoint.
> >
> > Reported-by: syzbot+8517da8635307182c8a5@syzkaller.appspotmail.com
> > Link: https://lore.kernel.org/linux-btrfs/6759a9b9.050a0220.1ac542.000d.GAE@google.com/
> > Signed-off-by: Filipe Manana <fdmanana@suse.com>
>
> Thanks for pinning down the error.
>
> Although considering there is really only one caller of
> trace_btrfs_cow_block(), can we just move this only caller into
> btrfs_force_cow_block() before freeing @buf?

That's actually simpler and more efficient, yes.
Done in v2.

Thanks.

>
> Thanks,
> Qu
>
> > ---
> >   fs/btrfs/ctree.c | 11 +++++++++++
> >   1 file changed, 11 insertions(+)
> >
> > diff --git a/fs/btrfs/ctree.c b/fs/btrfs/ctree.c
> > index 693dc27ffb89..3a28e77b6d72 100644
> > --- a/fs/btrfs/ctree.c
> > +++ b/fs/btrfs/ctree.c
> > @@ -751,10 +751,21 @@ int btrfs_cow_block(struct btrfs_trans_handle *trans,
> >        * Also We don't care about the error, as it's handled internally.
> >        */
> >       btrfs_qgroup_trace_subtree_after_cow(trans, root, buf);
> > +     /*
> > +      * When we are called from btrfs_search_slot() for example, we are
> > +      * not holding an extra reference on @buf so btrfs_force_cow_block()
> > +      * does a free_extent_buffer_stale() on the last reference and schedules
> > +      * the extent buffer release with RCU, so we can trigger a
> > +      * use-after-free in the trace_btrfs_cow_block() call below in case
> > +      * preemption is enabled (CONFIG_PREEMPT=y). So grab an extra reference
> > +      * to prevent that.
> > +      */
> > +     atomic_inc(&buf->refs);
> >       ret = btrfs_force_cow_block(trans, root, buf, parent, parent_slot,
> >                                   cow_ret, search_start, 0, nest);
> >
> >       trace_btrfs_cow_block(root, buf, *cow_ret);
> > +     free_extent_buffer_stale(buf);
> >
> >       return ret;
> >   }
>
diff mbox series

Patch

diff --git a/fs/btrfs/ctree.c b/fs/btrfs/ctree.c
index 693dc27ffb89..3a28e77b6d72 100644
--- a/fs/btrfs/ctree.c
+++ b/fs/btrfs/ctree.c
@@ -751,10 +751,21 @@  int btrfs_cow_block(struct btrfs_trans_handle *trans,
 	 * Also We don't care about the error, as it's handled internally.
 	 */
 	btrfs_qgroup_trace_subtree_after_cow(trans, root, buf);
+	/*
+	 * When we are called from btrfs_search_slot() for example, we are
+	 * not holding an extra reference on @buf so btrfs_force_cow_block()
+	 * does a free_extent_buffer_stale() on the last reference and schedules
+	 * the extent buffer release with RCU, so we can trigger a
+	 * use-after-free in the trace_btrfs_cow_block() call below in case
+	 * preemption is enabled (CONFIG_PREEMPT=y). So grab an extra reference
+	 * to prevent that.
+	 */
+	atomic_inc(&buf->refs);
 	ret = btrfs_force_cow_block(trans, root, buf, parent, parent_slot,
 				    cow_ret, search_start, 0, nest);
 
 	trace_btrfs_cow_block(root, buf, *cow_ret);
+	free_extent_buffer_stale(buf);
 
 	return ret;
 }