mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v1] virt: acrn: Fix irqfd use-after-free during async shutdown
@ 2026-05-11 13:57 Sicong Huang
  2026-05-15  7:06 ` Fei Li
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: Sicong Huang @ 2026-05-11 13:57 UTC (permalink / raw)
  To: fei1.li; +Cc: acrn-dev, gregkh, linux-kernel, Sicong Huang

ACRN irqfd registers a custom waitqueue callback on the eventfd. When the
eventfd is released, eventfd_release() wakes the waitqueue with EPOLLHUP,
and hsm_irqfd_wakeup() queues irqfd->shutdown on vm->irqfd_wq.

The irqfd object can also be removed by ACRN_IOCTL_IRQFD with
ACRN_IRQFD_FLAG_DEASSIGN. In that path, acrn_irqfd_deassign() removes the
waitqueue entry and frees the hsm_irqfd object.

These two paths can race. If EPOLLHUP queues the shutdown work before
deassign frees the object, the work item may run after kfree() and recover
the freed hsm_irqfd via container_of(). It then dereferences irqfd->vm while
taking irqfds_lock.

A possible race is:
CPU0                             CPU1
eventfd_release()
wake_up_poll(EPOLLHUP)
hsm_irqfd_wakeup()
queue_work(&irqfd->shutdown)
                                 acrn_irqfd_deassign()
                                 hsm_irqfd_shutdown()
                                 list_del_init()
                                 eventfd_ctx_remove_wait_queue()
                                 kfree(irqfd)   //free here!
hsm_irqfd_shutdown_work()
irqfd = container_of(work, ...)
vm = irqfd->vm  //UAF!

Fix this by separating logical shutdown from object release. First remove
the irqfd from the VM list and eventfd waitqueue, then synchronously
cancel any pending/running shutdown work before freeing the object.
Also tear down irqfds before destroying the irqfd workqueue, so
eventfd wakeups cannot queue work after the workqueue has been destroyed.

Signed-off-by: Sicong Huang <congei42@163.com>
---
 drivers/virt/acrn/irqfd.c | 47 ++++++++++++++++++++++++++++++---------
 1 file changed, 37 insertions(+), 10 deletions(-)

diff --git a/drivers/virt/acrn/irqfd.c b/drivers/virt/acrn/irqfd.c
index acf8cd5f8f8c..659fd40d9aa5 100644
--- a/drivers/virt/acrn/irqfd.c
+++ b/drivers/virt/acrn/irqfd.c
@@ -44,30 +44,37 @@ static void acrn_irqfd_inject(struct hsm_irqfd *irqfd)
 			irqfd->msi.msi_data);
 }
 
-static void hsm_irqfd_shutdown(struct hsm_irqfd *irqfd)
+static bool hsm_irqfd_shutdown(struct hsm_irqfd *irqfd)
 {
 	u64 cnt;
 
 	lockdep_assert_held(&irqfd->vm->irqfds_lock);
 
+	if (list_empty(&irqfd->list))
+		return false;
+
 	/* remove from wait queue */
 	list_del_init(&irqfd->list);
 	eventfd_ctx_remove_wait_queue(irqfd->eventfd, &irqfd->wait, &cnt);
 	eventfd_ctx_put(irqfd->eventfd);
-	kfree(irqfd);
+
+	return true;
 }
 
 static void hsm_irqfd_shutdown_work(struct work_struct *work)
 {
 	struct hsm_irqfd *irqfd;
 	struct acrn_vm *vm;
+	bool free;
 
 	irqfd = container_of(work, struct hsm_irqfd, shutdown);
 	vm = irqfd->vm;
 	mutex_lock(&vm->irqfds_lock);
-	if (!list_empty(&irqfd->list))
-		hsm_irqfd_shutdown(irqfd);
+	free = hsm_irqfd_shutdown(irqfd);
 	mutex_unlock(&vm->irqfds_lock);
+
+	if (free)
+		kfree(irqfd);
 }
 
 /* Called with wqh->lock held and interrupts disabled */
@@ -170,7 +177,7 @@ static int acrn_irqfd_assign(struct acrn_vm *vm, struct acrn_irqfd *args)
 static int acrn_irqfd_deassign(struct acrn_vm *vm,
 			       struct acrn_irqfd *args)
 {
-	struct hsm_irqfd *irqfd, *tmp;
+	struct hsm_irqfd *irqfd, *tmp, *to_free = NULL;
 	struct eventfd_ctx *eventfd;
 
 	eventfd = eventfd_ctx_fdget(args->fd);
@@ -180,13 +187,19 @@ static int acrn_irqfd_deassign(struct acrn_vm *vm,
 	mutex_lock(&vm->irqfds_lock);
 	list_for_each_entry_safe(irqfd, tmp, &vm->irqfds, list) {
 		if (irqfd->eventfd == eventfd) {
-			hsm_irqfd_shutdown(irqfd);
+			if (hsm_irqfd_shutdown(irqfd))
+				to_free = irqfd;
 			break;
 		}
 	}
 	mutex_unlock(&vm->irqfds_lock);
 	eventfd_ctx_put(eventfd);
 
+	if (to_free) {
+		cancel_work_sync(&to_free->shutdown);
+		kfree(to_free);
+	}
+
 	return 0;
 }
 
@@ -219,9 +232,23 @@ void acrn_irqfd_deinit(struct acrn_vm *vm)
 	struct hsm_irqfd *irqfd, *next;
 
 	dev_dbg(acrn_dev.this_device, "VM %u irqfd deinit.\n", vm->vmid);
+
+	for (;;) {
+		irqfd = NULL;
+
+		mutex_lock(&vm->irqfds_lock);
+		if (!list_empty(&vm->irqfds)) {
+			irqfd = list_first_entry(&vm->irqfds, struct hsm_irqfd, list);
+			hsm_irqfd_shutdown(irqfd);
+		}
+		mutex_unlock(&vm->irqfds_lock);
+
+		if (!irqfd)
+			break;
+
+		cancel_work_sync(&irqfd->shutdown);
+		kfree(irqfd);
+	}
+
 	destroy_workqueue(vm->irqfd_wq);
-	mutex_lock(&vm->irqfds_lock);
-	list_for_each_entry_safe(irqfd, next, &vm->irqfds, list)
-		hsm_irqfd_shutdown(irqfd);
-	mutex_unlock(&vm->irqfds_lock);
 }
-- 
2.34.1


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-05-20  1:21 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-05-11 13:57 [PATCH v1] virt: acrn: Fix irqfd use-after-free during async shutdown Sicong Huang
2026-05-15  7:06 ` Fei Li
2026-05-15  8:32 ` kernel test robot
2026-05-19 11:20 ` [PATCH v2 0/1] virt: acrn: Fix irqfd UAF during eventfd shutdown Sicong Huang
2026-05-19 11:20   ` [PATCH v2 1/1] virt: acrn: Fix irqfd use-after-free " Sicong Huang
2026-05-20  1:19     ` Fei Li

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®