MSHV_SET_POLL_FILE keeps a poll wait entry after the ioctl returns. A
file reference does not always pin the registered waitqueue:
signalfd_poll() uses current->sighand->signalfd_wqh, whose lifetime is
tied to the task.

signalfd_cleanup() sends POLLFREE before the sighand is freed. The MSHV
callback currently leaves its entry linked, so __wake_up_pollfree()
warns and a later replacement calls remove_wait_queue() through a stale
wqh pointer. A source-equivalent KASAN run reproduced:

  BUG: KASAN: slab-use-after-free in _raw_spin_lock_irqsave
  _raw_spin_lock_irqsave
  remove_wait_queue
  harness_ioctl [mshv_pollfree_harness]

The object belonged to sighand_cache and had been released through
slab_free_after_rcu_debug() after __cleanup_sighand().

On POLLFREE, unlink the entry under the provider waitqueue lock. Preserve
the existing CPU cancellation before publishing a NULL wqh, since a NULL
load lets a racing replacement reuse the persistent poll state. Use a
matching acquire load under RCU when removing a normal registration.

The callback takes no mutex and does not sleep, so the existing
mutex-to-waitqueue-lock order is unchanged.

Fixes: 7bfe3b8ea6e3 ("Drivers: hv: Introduce mshv_vtl driver")
Cc: [email protected]
Assisted-by: LLM
Signed-off-by: Daehyeon Ko <[email protected]>
---
 drivers/hv/mshv_vtl_main.c | 28 +++++++++++++++++++++++++---
 1 file changed, 25 insertions(+), 3 deletions(-)

diff --git a/drivers/hv/mshv_vtl_main.c b/drivers/hv/mshv_vtl_main.c
index d8225242c73f8..c6f1e2157d258 100644
--- a/drivers/hv/mshv_vtl_main.c
+++ b/drivers/hv/mshv_vtl_main.c
@@ -17,6 +17,7 @@
 #include <linux/entry-virt.h>
 #include <linux/eventfd.h>
 #include <linux/poll.h>
+#include <linux/rcupdate.h>
 #include <linux/file.h>
 #include <linux/vmalloc.h>
 #include <asm/debugreg.h>
@@ -448,12 +449,35 @@ static void mshv_vtl_cancel(int cpu)
 static int mshv_vtl_poll_file_wake(wait_queue_entry_t *wait, unsigned int 
mode, int sync, void *key)
 {
        struct mshv_vtl_poll_file *poll_file = container_of(wait, struct 
mshv_vtl_poll_file, wait);
+       __poll_t pollflags = key_to_poll(key);
+
+       if (pollflags & POLLFREE)
+               list_del_init(&wait->entry);
 
        mshv_vtl_cancel(poll_file->cpu);
 
+       if (pollflags & POLLFREE) {
+               /* Publish teardown last; pair with acquire in the removal 
path. */
+               smp_store_release(&poll_file->wqh, NULL);
+       }
+
        return 0;
 }
 
+static void mshv_vtl_poll_file_remove_wait_queue(struct mshv_vtl_poll_file 
*poll_file)
+{
+       wait_queue_head_t *wqh;
+
+       /* POLLFREE providers RCU-delay the waitqueue free. */
+       rcu_read_lock();
+       /* Pair with the release store in the POLLFREE callback. */
+       wqh = smp_load_acquire(&poll_file->wqh);
+       if (wqh)
+               remove_wait_queue(wqh, &poll_file->wait);
+       rcu_read_unlock();
+       WRITE_ONCE(poll_file->wqh, NULL);
+}
+
 static void mshv_vtl_ptable_queue_proc(struct file *file, wait_queue_head_t 
*wqh, poll_table *pt)
 {
        struct mshv_vtl_poll_file *poll_file = container_of(pt, struct 
mshv_vtl_poll_file, pt);
@@ -490,9 +514,7 @@ static int mshv_vtl_ioctl_set_poll_file(struct 
mshv_vtl_set_poll_file __user *us
 
        mutex_lock(&mshv_vtl_poll_file_lock);
 
-       if (poll_file->wqh)
-               remove_wait_queue(poll_file->wqh, &poll_file->wait);
-       poll_file->wqh = NULL;
+       mshv_vtl_poll_file_remove_wait_queue(poll_file);
 
        old_file = poll_file->file;
        poll_file->file = file;

Reply via email to