diff mbox series

[136/147] nilfs2: use refcount_dec_and_lock() to fix potential UAF

Message ID 20210908030026.2dLZCmkE4%akpm@linux-foundation.org (mailing list archive)
State New
Headers show
Series [001/147] mm, slub: don't call flush_all() from slab_debug_trace_open() | expand

Commit Message

Andrew Morton Sept. 8, 2021, 3 a.m. UTC
From: Zhen Lei <thunder.leizhen@huawei.com>
Subject: nilfs2: use refcount_dec_and_lock() to fix potential UAF

When the refcount is decreased to 0, the resource reclamation branch is
entered.  Before CPU0 reaches the race point (1), CPU1 may obtain the
spinlock and traverse the rbtree to find 'root', see nilfs_lookup_root(). 
Although CPU1 will call refcount_inc() to increase the refcount, it is
obviously too late.  CPU0 will release 'root' directly, CPU1 then accesses
'root' and triggers UAF.

Use refcount_dec_and_lock() to ensure that both the operations of decrease
refcount to 0 and link deletion are lock protected eliminates this risk.

     CPU0                      CPU1
nilfs_put_root():
			    <-------- (1)
spin_lock(&nilfs->ns_cptree_lock);
rb_erase(&root->rb_node, &nilfs->ns_cptree);
spin_unlock(&nilfs->ns_cptree_lock);

kfree(root);
			    <-------- use-after-free

========================================================================
refcount_t: underflow; use-after-free.
WARNING: CPU: 2 PID: 9476 at lib/refcount.c:28 \
refcount_warn_saturate+0x1cf/0x210 lib/refcount.c:28
Modules linked in:
CPU: 2 PID: 9476 Comm: syz-executor.0 Not tainted 5.10.45-rc1+ #3
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), ...
RIP: 0010:refcount_warn_saturate+0x1cf/0x210 lib/refcount.c:28
... ...
Call Trace:
 __refcount_sub_and_test include/linux/refcount.h:283 [inline]
 __refcount_dec_and_test include/linux/refcount.h:315 [inline]
 refcount_dec_and_test include/linux/refcount.h:333 [inline]
 nilfs_put_root+0xc1/0xd0 fs/nilfs2/the_nilfs.c:795
 nilfs_segctor_destroy fs/nilfs2/segment.c:2749 [inline]
 nilfs_detach_log_writer+0x3fa/0x570 fs/nilfs2/segment.c:2812
 nilfs_put_super+0x2f/0xf0 fs/nilfs2/super.c:467
 generic_shutdown_super+0xcd/0x1f0 fs/super.c:464
 kill_block_super+0x4a/0x90 fs/super.c:1446
 deactivate_locked_super+0x6a/0xb0 fs/super.c:335
 deactivate_super+0x85/0x90 fs/super.c:366
 cleanup_mnt+0x277/0x2e0 fs/namespace.c:1118
 __cleanup_mnt+0x15/0x20 fs/namespace.c:1125
 task_work_run+0x8e/0x110 kernel/task_work.c:151
 tracehook_notify_resume include/linux/tracehook.h:188 [inline]
 exit_to_user_mode_loop kernel/entry/common.c:164 [inline]
 exit_to_user_mode_prepare+0x13c/0x170 kernel/entry/common.c:191
 syscall_exit_to_user_mode+0x16/0x30 kernel/entry/common.c:266
 do_syscall_64+0x45/0x80 arch/x86/entry/common.c:56
 entry_SYSCALL_64_after_hwframe+0x44/0xa9

There is no reproduction program, and the above is only theoretical
analysis.

Link: https://lkml.kernel.org/r/1629859428-5906-1-git-send-email-konishi.ryusuke@gmail.com
Fixes: ba65ae4729bf ("nilfs2: add checkpoint tree to nilfs object")
Link: https://lkml.kernel.org/r/20210723012317.4146-1-thunder.leizhen@huawei.com
Signed-off-by: Zhen Lei <thunder.leizhen@huawei.com>
Signed-off-by: Ryusuke Konishi <konishi.ryusuke@gmail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
---

 fs/nilfs2/the_nilfs.c |    9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

Comments

Pavel Machek Sept. 24, 2021, 10:35 a.m. UTC | #1
Hi!

> From: Zhen Lei <thunder.leizhen@huawei.com>
> Subject: nilfs2: use refcount_dec_and_lock() to fix potential UAF
> 
> When the refcount is decreased to 0, the resource reclamation branch is
> entered.  Before CPU0 reaches the race point (1), CPU1 may obtain the
> spinlock and traverse the rbtree to find 'root', see nilfs_lookup_root(). 
> Although CPU1 will call refcount_inc() to increase the refcount, it is
> obviously too late.  CPU0 will release 'root' directly, CPU1 then accesses
> 'root' and triggers UAF.
> 
> Use refcount_dec_and_lock() to ensure that both the operations of decrease
> refcount to 0 and link deletion are lock protected eliminates this risk.
> 
>      CPU0                      CPU1
> nilfs_put_root():
> 			    <-------- (1)
> spin_lock(&nilfs->ns_cptree_lock);
> rb_erase(&root->rb_node, &nilfs->ns_cptree);
> spin_unlock(&nilfs->ns_cptree_lock);
> 
> kfree(root);
> 			    <-------- use-after-free

> There is no reproduction program, and the above is only theoretical
> analysis.

Ok, so we have a theoretical bug, and fix already on its way to
stable. But ... is it correct?

> +++ a/fs/nilfs2/the_nilfs.c
> @@ -792,14 +792,13 @@ nilfs_find_or_create_root(struct the_nil
>  
>  void nilfs_put_root(struct nilfs_root *root)
>  {
> -	if (refcount_dec_and_test(&root->count)) {
> -		struct the_nilfs *nilfs = root->nilfs;
> +	struct the_nilfs *nilfs = root->nilfs;
>  
> -		nilfs_sysfs_delete_snapshot_group(root);
> -
> -		spin_lock(&nilfs->ns_cptree_lock);
> +	if (refcount_dec_and_lock(&root->count, &nilfs->ns_cptree_lock)) {
>  		rb_erase(&root->rb_node, &nilfs->ns_cptree);
>  		spin_unlock(&nilfs->ns_cptree_lock);
> +
> +		nilfs_sysfs_delete_snapshot_group(root);
>  		iput(root->ifile);
>  
>  		kfree(root);

spin_lock() is deleted, but spin_unlock() is not affected. This means
unbalanced locking, right?

Best regards,
								Pavel
--
DENX Software Engineering GmbH,      Managing Director: Wolfgang Denk
HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
Ryusuke Konishi Sept. 24, 2021, 11:09 a.m. UTC | #2
Hi,

On Fri, Sep 24, 2021 at 7:35 PM Pavel Machek <pavel@denx.de> wrote:
>
> Hi!
>
> > From: Zhen Lei <thunder.leizhen@huawei.com>
> > Subject: nilfs2: use refcount_dec_and_lock() to fix potential UAF
> >
> > When the refcount is decreased to 0, the resource reclamation branch is
> > entered.  Before CPU0 reaches the race point (1), CPU1 may obtain the
> > spinlock and traverse the rbtree to find 'root', see nilfs_lookup_root().
> > Although CPU1 will call refcount_inc() to increase the refcount, it is
> > obviously too late.  CPU0 will release 'root' directly, CPU1 then accesses
> > 'root' and triggers UAF.
> >
> > Use refcount_dec_and_lock() to ensure that both the operations of decrease
> > refcount to 0 and link deletion are lock protected eliminates this risk.
> >
> >      CPU0                      CPU1
> > nilfs_put_root():
> >                           <-------- (1)
> > spin_lock(&nilfs->ns_cptree_lock);
> > rb_erase(&root->rb_node, &nilfs->ns_cptree);
> > spin_unlock(&nilfs->ns_cptree_lock);
> >
> > kfree(root);
> >                           <-------- use-after-free
>
> > There is no reproduction program, and the above is only theoretical
> > analysis.
>
> Ok, so we have a theoretical bug, and fix already on its way to
> stable. But ... is it correct?
>
> > +++ a/fs/nilfs2/the_nilfs.c
> > @@ -792,14 +792,13 @@ nilfs_find_or_create_root(struct the_nil
> >
> >  void nilfs_put_root(struct nilfs_root *root)
> >  {
> > -     if (refcount_dec_and_test(&root->count)) {
> > -             struct the_nilfs *nilfs = root->nilfs;
> > +     struct the_nilfs *nilfs = root->nilfs;
> >
> > -             nilfs_sysfs_delete_snapshot_group(root);
> > -
> > -             spin_lock(&nilfs->ns_cptree_lock);
> > +     if (refcount_dec_and_lock(&root->count, &nilfs->ns_cptree_lock)) {
> >               rb_erase(&root->rb_node, &nilfs->ns_cptree);
> >               spin_unlock(&nilfs->ns_cptree_lock);
> > +
> > +             nilfs_sysfs_delete_snapshot_group(root);
> >               iput(root->ifile);
> >
> >               kfree(root);
>
> spin_lock() is deleted, but spin_unlock() is not affected. This means
> unbalanced locking, right?

It's okay.   spin_lock() is integrated into refcount_dec_and_lock(), which was
originally refcount_dec_and_test().

Thanks,
Ryusuke Konishi

>
> Best regards,
>                                                                 Pavel
> --
> DENX Software Engineering GmbH,      Managing Director: Wolfgang Denk
> HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
>
Matthew Wilcox Sept. 24, 2021, 12:12 p.m. UTC | #3
On Tue, Sep 07, 2021 at 08:00:26PM -0700, Andrew Morton wrote:
> From: Zhen Lei <thunder.leizhen@huawei.com>
> Subject: nilfs2: use refcount_dec_and_lock() to fix potential UAF
> 
> When the refcount is decreased to 0, the resource reclamation branch is
> entered.  Before CPU0 reaches the race point (1), CPU1 may obtain the
> spinlock and traverse the rbtree to find 'root', see nilfs_lookup_root(). 
> Although CPU1 will call refcount_inc() to increase the refcount, it is
> obviously too late.  CPU0 will release 'root' directly, CPU1 then accesses
> 'root' and triggers UAF.
> 
> Use refcount_dec_and_lock() to ensure that both the operations of decrease
> refcount to 0 and link deletion are lock protected eliminates this risk.
> 
>      CPU0                      CPU1
> nilfs_put_root():
> 			    <-------- (1)
> spin_lock(&nilfs->ns_cptree_lock);
> rb_erase(&root->rb_node, &nilfs->ns_cptree);
> spin_unlock(&nilfs->ns_cptree_lock);
> 
> kfree(root);
> 			    <-------- use-after-free

I don't know where this happened, but the leading whitespace has been
eaten at some point, making this description of the race completely
unreadable as everything appears to be done by CPU 0.
Ryusuke Konishi Sept. 24, 2021, 3:09 p.m. UTC | #4
Hi,

On Fri, Sep 24, 2021 at 9:13 PM Matthew Wilcox <willy@infradead.org> wrote:
>
> On Tue, Sep 07, 2021 at 08:00:26PM -0700, Andrew Morton wrote:
> > From: Zhen Lei <thunder.leizhen@huawei.com>
> > Subject: nilfs2: use refcount_dec_and_lock() to fix potential UAF
> >
> > When the refcount is decreased to 0, the resource reclamation branch is
> > entered.  Before CPU0 reaches the race point (1), CPU1 may obtain the
> > spinlock and traverse the rbtree to find 'root', see nilfs_lookup_root().
> > Although CPU1 will call refcount_inc() to increase the refcount, it is
> > obviously too late.  CPU0 will release 'root' directly, CPU1 then accesses
> > 'root' and triggers UAF.
> >
> > Use refcount_dec_and_lock() to ensure that both the operations of decrease
> > refcount to 0 and link deletion are lock protected eliminates this risk.
> >
> >      CPU0                      CPU1
> > nilfs_put_root():
> >                           <-------- (1)
> > spin_lock(&nilfs->ns_cptree_lock);
> > rb_erase(&root->rb_node, &nilfs->ns_cptree);
> > spin_unlock(&nilfs->ns_cptree_lock);
> >
> > kfree(root);
> >                           <-------- use-after-free
>
> I don't know where this happened, but the leading whitespace has been
> eaten at some point, making this description of the race completely
> unreadable as everything appears to be done by CPU 0.

The diagram is the same as that in the original patch by author, and
I approved it without any discomfort because these five operations
(nilfs_put_root() ~ spin_lock(); rb_erase(); spin_unlock() ~ kfree() ) are
all done by CPU0.

But, yeah, an example of a function call might have been written on
the CPU1 side as well, for instance, a nilfs_lookup_root() call etc, to
clarify the race issue the message explains..

Regards,
Ryusuke Konishi
diff mbox series

Patch

--- a/fs/nilfs2/the_nilfs.c~nilfs2-use-refcount_dec_and_lock-to-fix-potential-uaf
+++ a/fs/nilfs2/the_nilfs.c
@@ -792,14 +792,13 @@  nilfs_find_or_create_root(struct the_nil
 
 void nilfs_put_root(struct nilfs_root *root)
 {
-	if (refcount_dec_and_test(&root->count)) {
-		struct the_nilfs *nilfs = root->nilfs;
+	struct the_nilfs *nilfs = root->nilfs;
 
-		nilfs_sysfs_delete_snapshot_group(root);
-
-		spin_lock(&nilfs->ns_cptree_lock);
+	if (refcount_dec_and_lock(&root->count, &nilfs->ns_cptree_lock)) {
 		rb_erase(&root->rb_node, &nilfs->ns_cptree);
 		spin_unlock(&nilfs->ns_cptree_lock);
+
+		nilfs_sysfs_delete_snapshot_group(root);
 		iput(root->ifile);
 
 		kfree(root);