* [PATCH] fuse: fix use-after-free issue in fuse_read_interrupt()
@ 2021-07-01 6:58 lijiazi
2021-07-08 14:34 ` Miklos Szeredi
0 siblings, 1 reply; 2+ messages in thread
From: lijiazi @ 2021-07-01 6:58 UTC (permalink / raw)
To: Miklos Szeredi; +Cc: lijiazi, linux-fsdevel
There is a potential race between fuse_read_interrupt and
fuse_dev_do_write contexts as shown below:
TASK1:
fuse_dev_do_write:
TASK2:
fuse_dev_do_read:
spin_lock(&fiq->lock);
fuse_read_interrupt();
list_del_init(&req->intr_entry);
fuse_request_end()//now req->intr_entry
//is empty so put this req
TASK3:
fuse_flash:
fuse_simple_request();
fuse_put_request();
kmem_cache_free();//free req
After TASK3 free req, TASK2 access this req in fuse_read_interrupt
and gets below crash:
==================================================================
[ 63.712079] BUG: KASAN: use-after-free in
fuse_read_interrupt+0x18c/0x948
[ 63.712084] Read of size 8 at addr ffffff8049797d50 by task
Thread-5/5508
[ 63.712093] CPU: 4 PID: 5508 Comm: Thread-5 Tainted: G S O
5.4.86-qgki-debug-ge1de60c2024c #1
[ 63.712096] Call trace:
[ 63.712100] dump_backtrace+0x0/0x2cc
[ 63.712102] show_stack+0x18/0x24
[ 63.712106] dump_stack+0x134/0x1c4
[ 63.712109] print_address_description+0x88/0x578
[ 63.712111] __kasan_report+0x1c4/0x1e0
[ 63.712113] kasan_report+0x14/0x20
[ 63.712115] __asan_report_load8_noabort+0x1c/0x28
[ 63.712117] fuse_read_interrupt+0x18c/0x948
[ 63.712119] fuse_dev_do_read+0x678/0x11a0
[ 63.712121] fuse_dev_read+0x108/0x17c
[ 63.712124] __vfs_read+0x408/0x544
[ 63.712126] vfs_read+0x114/0x2a8
[ 63.712127] ksys_read+0xd8/0x18c
[ 63.712129] __arm64_sys_read+0x78/0x8c
[ 63.712132] el0_svc_common+0x134/0x2cc
[ 63.712134] el0_svc_handler+0xd0/0xe0
[ 63.712136] el0_svc+0x8/0xc
[ 63.712137]
[ 63.712141] Allocated by task 5535:
[ 63.712146] __kasan_kmalloc+0x100/0x1c0
[ 63.712148] kasan_slab_alloc+0x18/0x24
[ 63.712150] kmem_cache_alloc+0x320/0x3c4
[ 63.712152] fuse_get_req+0x1f0/0x6a4
[ 63.712154] fuse_simple_request+0x5c/0xb90
[ 63.712156] fuse_flush_times+0x340/0x474
[ 63.712158] fuse_write_inode+0x90/0xc4
[ 63.712160] write_inode+0x1ec/0x478
[ 63.712162] __writeback_single_inode+0x420/0x838
[ 63.712165] writeback_single_inode+0x128/0x708
[ 63.712167] write_inode_now+0x2b0/0x370
[ 63.712169] fuse_release+0x9c/0x100
[ 63.712171] __fput+0x18c/0x4a8
[ 63.712173] ____fput+0x10/0x1c
[ 63.712176] task_work_run+0x118/0x1ec
[ 63.712178] do_exit+0x500/0x1810
[ 63.712179] do_group_exit+0x1c8/0x200
[ 63.712182] get_signal+0xc44/0x1194
[ 63.712183] do_signal+0x134/0x464
[ 63.712185] do_notify_resume+0x110/0x1c4
[ 63.712187] work_pending+0x8/0x14
[ 63.712188]
[ 63.712191] Freed by task 5535:
[ 63.712194] __kasan_slab_free+0x168/0x238
[ 63.712196] kasan_slab_free+0x14/0x24
[ 63.712198] slab_free_freelist_hook+0xe0/0x164
[ 63.712201] kmem_cache_free+0xfc/0x358
[ 63.712202] fuse_put_request+0x148/0x1a4
[ 63.712204] fuse_simple_request+0x810/0xb90
[ 63.712206] fuse_flush_times+0x340/0x474
[ 63.712208] fuse_write_inode+0x90/0xc4
[ 63.712210] write_inode+0x1ec/0x478
[ 63.712212] __writeback_single_inode+0x420/0x838
[ 63.712214] writeback_single_inode+0x128/0x708
[ 63.712217] write_inode_now+0x2b0/0x370
[ 63.712219] fuse_release+0x9c/0x100
[ 63.712221] __fput+0x18c/0x4a8
[ 63.712222] ____fput+0x10/0x1c
[ 63.712224] task_work_run+0x118/0x1ec
[ 63.712226] do_exit+0x500/0x1810
[ 63.712228] do_group_exit+0x1c8/0x200
[ 63.712229] get_signal+0xc44/0x1194
[ 63.712231] do_signal+0x134/0x464
[ 63.712233] do_notify_resume+0x110/0x1c4
[ 63.712235] work_pending+0x8/0x14
Put list_del_init after the code access req, if intr_entry not
empty, fuse_request_end will wait for fiq->lock, req will not
free before TASK2 unlock fiq->lock.
Signed-off-by: lijiazi <lijiazi@xiaomi.com>
---
fs/fuse/dev.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 817a0b1..bef21b2 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -1045,13 +1045,13 @@ __releases(fiq->lock)
unsigned reqsize = sizeof(ih) + sizeof(arg);
int err;
- list_del_init(&req->intr_entry);
memset(&ih, 0, sizeof(ih));
memset(&arg, 0, sizeof(arg));
ih.len = reqsize;
ih.opcode = FUSE_INTERRUPT;
ih.unique = (req->in.h.unique | FUSE_INT_REQ_BIT);
arg.unique = req->in.h.unique;
+ list_del_init(&req->intr_entry);
spin_unlock(&fiq->lock);
if (nbytes < reqsize)
--
2.7.4
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH] fuse: fix use-after-free issue in fuse_read_interrupt()
2021-07-01 6:58 [PATCH] fuse: fix use-after-free issue in fuse_read_interrupt() lijiazi
@ 2021-07-08 14:34 ` Miklos Szeredi
0 siblings, 0 replies; 2+ messages in thread
From: Miklos Szeredi @ 2021-07-08 14:34 UTC (permalink / raw)
To: lijiazi; +Cc: lijiazi, linux-fsdevel
On Thu, Jul 01, 2021 at 02:58:07PM +0800, lijiazi wrote:
> There is a potential race between fuse_read_interrupt and
> fuse_dev_do_write contexts as shown below:
>
> TASK1:
> fuse_dev_do_write:
> TASK2:
> fuse_dev_do_read:
> spin_lock(&fiq->lock);
> fuse_read_interrupt();
> list_del_init(&req->intr_entry);
> fuse_request_end()//now req->intr_entry
> //is empty so put this req
> TASK3:
> fuse_flash:
> fuse_simple_request();
> fuse_put_request();
> kmem_cache_free();//free req
>
> After TASK3 free req, TASK2 access this req in fuse_read_interrupt
> and gets below crash:
Great report, thanks.
> Put list_del_init after the code access req, if intr_entry not
> empty, fuse_request_end will wait for fiq->lock, req will not
> free before TASK2 unlock fiq->lock.
Unfortunately this doesn't reliably work since there's no memory barrier to
prevent the compiler or the CPU to rearrange those stores back into a
use-after-free configuration.
One way to fix that would be to use the list_del_init_careful()/
list_empty_careful() pair, that guarantees ordering.
However the below patch is simpler and also works since FR_INTERRUPTED is never
cleared, and it's safe to use list_del_init() unconditionally after having
locked fiq->lock.
Thanks,
Miklos
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 1c8f79b3dd06..dde341a6388a 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -288,10 +288,10 @@ void fuse_request_end(struct fuse_req *req)
/*
* test_and_set_bit() implies smp_mb() between bit
- * changing and below intr_entry check. Pairs with
+ * changing and below FR_INTERRUPTED check. Pairs with
* smp_mb() from queue_interrupt().
*/
- if (!list_empty(&req->intr_entry)) {
+ if (test_bit(FR_INTERRUPTED, &req->flags)) {
spin_lock(&fiq->lock);
list_del_init(&req->intr_entry);
spin_unlock(&fiq->lock);
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2021-07-08 14:34 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-07-01 6:58 [PATCH] fuse: fix use-after-free issue in fuse_read_interrupt() lijiazi
2021-07-08 14:34 ` Miklos Szeredi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).