From 70cd4623f3e5068a72c72791b96f2dcb4c991131 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Mon, 19 Oct 2026 17:43:49 +0900 Subject: [PATCH] mshv_vtl: reject poll files with multiple waitqueues 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: stable@vger.kernel.org Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Naman Jain Signed-off-by: Wei Liu --- 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 1ffa904837b3..2d4fe24cecbf 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 */