FAILED: patch "[PATCH] io_uring: fix UAF due to missing POLLFREE handling" failed to apply to 5.16-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 5.16-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 791f3465c4afde02d7f16cf7424ca87070b69396 Mon Sep 17 00:00:00 2001
From: Pavel Begunkov <[email protected]>
Date: Fri, 14 Jan 2022 11:59:10 +0000
Subject: [PATCH] io_uring: fix UAF due to missing POLLFREE handling
Fixes a problem described in 50252e4b5e989
("aio: fix use-after-free due to missing POLLFREE handling") and copies the approach used there.
In short, we have to forcibly eject a poll entry when we meet POLLFREE. We can't rely on io_poll_get_ownership() as can't wait for potentially running tw handlers, so we use the fact that wqs are RCU freed. See Eric's patch and comments for more details.
fs/io_uring.c +50 -8
@@ -5462,12 +5462,14 @@ static void io_init_poll_iocb(struct io_poll_iocb *poll, __poll_t events,static inline void io_poll_remove_entry (struct io_poll_iocb *poll) { -struct wait_queue_head *head = poll->head; +struct wait_queue_head *head =smp_load_acquire (&poll->head); -spin_lock_irq (&head->lock); -list_del_init (&poll->wait.entry); - poll->head =NULL ; -spin_unlock_irq (&head->lock); +if (head) { +spin_lock_irq (&head->lock); +list_del_init (&poll->wait.entry); + poll->head =NULL ; +spin_unlock_irq (&head->lock); + } }static void io_poll_remove_entries (struct io_kiocb *req)
@@ -5475,10 +5477,26 @@ static void io_poll_remove_entries(struct io_kiocb *req)struct io_poll_iocb *poll =io_poll_get_single (req);struct io_poll_iocb *poll_double =io_poll_get_double (req); -if (poll->head) -io_poll_remove_entry (poll); -if (poll_double && poll_double->head) +/* +* While we hold the waitqueue lock and the waitqueue is nonempty, +* wake_up_pollfree() will wait for us. However, taking the waitqueue +* lock in the first place can race with the waitqueue being freed. +* +* We solve this as eventpoll does: by taking advantage of the fact that +* all users of wake_up_pollfree() will RCU-delay the actual free. If +* we enter rcu_read_lock() and see that the pointer to the queue is +* non-NULL, we can then lock it without the memory being freed out from +* under us. +* +* Keep holding rcu_read_lock() as long as we hold the queue lock, in +* case the caller deletes the entry from the queue, leaving it empty. +* In that case, only RCU prevents the queue memory from being freed. +*/ +rcu_read_lock (); +io_poll_remove_entry (poll); +if (poll_double)io_poll_remove_entry (poll_double); +rcu_read_unlock (); }/*
@@ -5618,6 +5636,30 @@ static int io_poll_wake(struct wait_queue_entry *wait, unsigned mode, int sync, wait);__poll_t mask =key_to_poll (key); +if (unlikely (mask &POLLFREE )) { +io_poll_mark_cancelled (req); +/* we have to kick tw in case it's not already */ +io_poll_execute (req,0 ); + +/* +* If the waitqueue is being freed early but someone is already +* holds ownership over it, we have to tear down the request as +* best we can. That means immediately removing the request from +* its waitqueue and preventing all further accesses to the +* waitqueue via the request. +*/ +list_del_init (&poll->wait.entry); + +/* +* Careful: this *must* be the last step, since as soon +* as req->head is NULL'ed out, the request can be +* completed and freed, since aio_poll_complete_work() +* will no longer need to take the waitqueue lock. +*/ +smp_store_release (&poll->head,NULL ); +return 1 ; + } +/* for instances that support it check for an event match first */ if (mask && !(mask & poll->events))return 0 ;