MSHV_SET_POLL_FILE invokes a file descriptor poll callback with a poll
table backed by a single embedded wait entry. A poll callback may,
however, register the table with more than one waitqueue. pipe_poll()
does this naturally for an O_RDWR FIFO.

The second registration currently warns but then inserts the same list
node into another list. The new insertion overwrites the entry links while
the first waitqueue still points at it, corrupting the first waitqueue and
causing an invalid callback traversal when that queue wakes.

Reject a second distinct waitqueue, remove the first registration through
the common removal helper, and return -EINVAL from the ioctl. Repeated
registration of the same waitqueue is treated as a no-op. A rejected
replacement leaves the CPU slot unregistered, consistent with the existing
remove-before-register ordering. Single-waitqueue users such as the OpenHCL
io_uring poll file keep their existing behavior.

An LLM assisted the source audit, fix development, and validation of this
issue.

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

diff --git a/drivers/hv/mshv_vtl_main.c b/drivers/hv/mshv_vtl_main.c
index c6f1e2157d258..9133646b5b66c 100644
--- a/drivers/hv/mshv_vtl_main.c
+++ b/drivers/hv/mshv_vtl_main.c
@@ -73,6 +73,7 @@ struct mshv_vtl_poll_file {
        wait_queue_entry_t wait;
        wait_queue_head_t *wqh;
        poll_table pt;
+       int error;
        int cpu;
 };
 
@@ -482,7 +483,18 @@ static void mshv_vtl_ptable_queue_proc(struct file *file, 
wait_queue_head_t *wqh
 {
        struct mshv_vtl_poll_file *poll_file = container_of(pt, struct 
mshv_vtl_poll_file, pt);
 
-       WARN_ON(poll_file->wqh);
+       if (poll_file->error)
+               return;
+
+       if (poll_file->wqh) {
+               if (poll_file->wqh == wqh)
+                       return;
+
+               mshv_vtl_poll_file_remove_wait_queue(poll_file);
+               poll_file->error = -EINVAL;
+               return;
+       }
+
        poll_file->wqh = wqh;
        add_wait_queue(wqh, &poll_file->wait);
 }
@@ -492,6 +504,7 @@ static int mshv_vtl_ioctl_set_poll_file(struct 
mshv_vtl_set_poll_file __user *us
        struct file *file, *old_file;
        struct mshv_vtl_poll_file *poll_file;
        struct mshv_vtl_set_poll_file input;
+       int ret;
 
        if (copy_from_user(&input, user_input, sizeof(input)))
                return -EFAULT;
@@ -518,6 +531,7 @@ static int mshv_vtl_ioctl_set_poll_file(struct 
mshv_vtl_set_poll_file __user *us
 
        old_file = poll_file->file;
        poll_file->file = file;
+       poll_file->error = 0;
        poll_file->cpu = input.cpu;
 
        if (file) {
@@ -525,13 +539,18 @@ static int mshv_vtl_ioctl_set_poll_file(struct 
mshv_vtl_set_poll_file __user *us
                init_poll_funcptr(&poll_file->pt, mshv_vtl_ptable_queue_proc);
                vfs_poll(file, &poll_file->pt);
        }
+       ret = poll_file->error;
+       if (ret)
+               poll_file->file = NULL;
 
        mutex_unlock(&mshv_vtl_poll_file_lock);
 
        if (old_file)
                fput(old_file);
+       if (ret)
+               fput(file);
 
-       return 0;
+       return ret;
 }
 
 /* Static table mapping register names to their corresponding actions */
-- 
2.55.0


Reply via email to