Skip to content

Commit d89a4fa

Browse files
committed
io_uring: fix assuming triggered poll waitqueue is the single poll
syzbot reports a recent regression: BUG: KASAN: use-after-free in __wake_up_common+0x637/0x650 kernel/sched/wait.c:101 Read of size 8 at addr ffff888011e8a130 by task syz-executor413/3618 CPU: 0 PID: 3618 Comm: syz-executor413 Tainted: G W 5.17.0-syzkaller-01402-g8565d64430f8 #0 Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/01/2011 Call Trace: <TASK> __dump_stack lib/dump_stack.c:88 [inline] dump_stack_lvl+0xcd/0x134 lib/dump_stack.c:106 print_address_description.constprop.0.cold+0x8d/0x303 mm/kasan/report.c:255 __kasan_report mm/kasan/report.c:442 [inline] kasan_report.cold+0x83/0xdf mm/kasan/report.c:459 __wake_up_common+0x637/0x650 kernel/sched/wait.c:101 __wake_up_common_lock+0xd0/0x130 kernel/sched/wait.c:138 tty_release+0x657/0x1200 drivers/tty/tty_io.c:1781 __fput+0x286/0x9f0 fs/file_table.c:317 task_work_run+0xdd/0x1a0 kernel/task_work.c:164 exit_task_work include/linux/task_work.h:32 [inline] do_exit+0xaff/0x29d0 kernel/exit.c:806 do_group_exit+0xd2/0x2f0 kernel/exit.c:936 __do_sys_exit_group kernel/exit.c:947 [inline] __se_sys_exit_group kernel/exit.c:945 [inline] __x64_sys_exit_group+0x3a/0x50 kernel/exit.c:945 do_syscall_x64 arch/x86/entry/common.c:50 [inline] do_syscall_64+0x35/0xb0 arch/x86/entry/common.c:80 entry_SYSCALL_64_after_hwframe+0x44/0xae RIP: 0033:0x7f439a1fac69 which is due to leaving the request on the waitqueue mistakenly. The reproducer is using a tty device, which means we end up arming the same poll queue twice (it uses the same poll waitqueue for both), but in io_poll_wake() we always just clear REQ_F_SINGLE_POLL regardless of which entry triggered. This leaves one waitqueue potentially armed after we're done, which then blows up in tty when the waitqueue is attempted removed. We have no room to store this information, so simply encode it in the wait_queue_entry->private where we store the io_kiocb request pointer. Fixes: 91eac1c ("io_uring: cache poll/double-poll state with a request flag") Reported-by: [email protected] Signed-off-by: Jens Axboe <[email protected]>
1 parent e2c0cb7 commit d89a4fa

File tree

1 file changed

+12
-4
lines changed

1 file changed

+12
-4
lines changed

fs/io_uring.c

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6027,10 +6027,13 @@ static void io_poll_cancel_req(struct io_kiocb *req)
60276027
io_poll_execute(req, 0, 0);
60286028
}
60296029

6030+
#define wqe_to_req(wait) ((void *)((unsigned long) (wait)->private & ~1))
6031+
#define wqe_is_double(wait) ((unsigned long) (wait)->private & 1)
6032+
60306033
static int io_poll_wake(struct wait_queue_entry *wait, unsigned mode, int sync,
60316034
void *key)
60326035
{
6033-
struct io_kiocb *req = wait->private;
6036+
struct io_kiocb *req = wqe_to_req(wait);
60346037
struct io_poll_iocb *poll = container_of(wait, struct io_poll_iocb,
60356038
wait);
60366039
__poll_t mask = key_to_poll(key);
@@ -6068,7 +6071,10 @@ static int io_poll_wake(struct wait_queue_entry *wait, unsigned mode, int sync,
60686071
if (mask && poll->events & EPOLLONESHOT) {
60696072
list_del_init(&poll->wait.entry);
60706073
poll->head = NULL;
6071-
req->flags &= ~REQ_F_SINGLE_POLL;
6074+
if (wqe_is_double(wait))
6075+
req->flags &= ~REQ_F_DOUBLE_POLL;
6076+
else
6077+
req->flags &= ~REQ_F_SINGLE_POLL;
60726078
}
60736079
__io_poll_execute(req, mask, poll->events);
60746080
}
@@ -6080,6 +6086,7 @@ static void __io_queue_proc(struct io_poll_iocb *poll, struct io_poll_table *pt,
60806086
struct io_poll_iocb **poll_ptr)
60816087
{
60826088
struct io_kiocb *req = pt->req;
6089+
unsigned long wqe_private = (unsigned long) req;
60836090

60846091
/*
60856092
* The file being polled uses multiple waitqueues for poll handling
@@ -6105,6 +6112,8 @@ static void __io_queue_proc(struct io_poll_iocb *poll, struct io_poll_table *pt,
61056112
pt->error = -ENOMEM;
61066113
return;
61076114
}
6115+
/* mark as double wq entry */
6116+
wqe_private |= 1;
61086117
req->flags |= REQ_F_DOUBLE_POLL;
61096118
io_init_poll_iocb(poll, first->events, first->wait.func);
61106119
*poll_ptr = poll;
@@ -6115,7 +6124,7 @@ static void __io_queue_proc(struct io_poll_iocb *poll, struct io_poll_table *pt,
61156124
req->flags |= REQ_F_SINGLE_POLL;
61166125
pt->nr_entries++;
61176126
poll->head = head;
6118-
poll->wait.private = req;
6127+
poll->wait.private = (void *) wqe_private;
61196128

61206129
if (poll->events & EPOLLEXCLUSIVE)
61216130
add_wait_queue_exclusive(head, &poll->wait);
@@ -6142,7 +6151,6 @@ static int __io_arm_poll_handler(struct io_kiocb *req,
61426151
INIT_HLIST_NODE(&req->hash_node);
61436152
io_init_poll_iocb(poll, mask, io_poll_wake);
61446153
poll->file = req->file;
6145-
poll->wait.private = req;
61466154

61476155
ipt->pt._key = mask;
61486156
ipt->req = req;

0 commit comments

Comments
 (0)