* [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
2026-05-15 8:32 ` kernel test robot
0 siblings, 2 replies; 3+ 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 related [flat|nested] 3+ messages in thread
* Re: [PATCH v1] virt: acrn: Fix irqfd use-after-free during async shutdown
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
1 sibling, 0 replies; 3+ messages in thread
From: Fei Li @ 2026-05-15 7:06 UTC (permalink / raw)
To: Sicong Huang; +Cc: acrn-dev, gregkh, linux-kernel, fei1.li
On 2026-05-11 at 21:57:37 +0800, Sicong Huang wrote:
> 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>
Hi Sicong,
Thanks for fixing this. The race looks real to me: `EPOLLHUP` can queue
`irqfd->shutdown`, while the deassign path can remove the waitqueue entry and
free the same irqfd before the work item runs.
The direction is good, but I think the lifetime rules can be made simpler. In
this version the final `kfree()` can still come from several places, with
`list_empty()` and `cancel_work_sync()` deciding who wins. That may work, but
it is harder to audit than necessary.
Could this follow the KVM irqfd pattern more closely? Deassign/deinit would
only deactivate the irqfd, i.e. remove it from the active list and queue cleanup
once. The cleanup work would then be the only place that removes the eventfd
waitqueue entry, drops the eventfd ref, and frees `struct hsm_irqfd`. The
synchronous paths can still flush the workqueue before returning or before
destroying `vm->irqfd_wq`.
Two smaller comments:
- `next` is left unused in `acrn_irqfd_deinit()` after this change.
- It may be worth tightening the assign path too, so an irqfd is not visible on
`vm->irqfds` before its eventfd waitqueue entry has been installed.
Thanks,
Fei
> ---
> 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] 3+ messages in thread
* Re: [PATCH v1] virt: acrn: Fix irqfd use-after-free during async shutdown
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
1 sibling, 0 replies; 3+ messages in thread
From: kernel test robot @ 2026-05-15 8:32 UTC (permalink / raw)
To: Sicong Huang, fei1.li
Cc: llvm, oe-kbuild-all, acrn-dev, gregkh, linux-kernel, Sicong Huang
Hi Sicong,
kernel test robot noticed the following build warnings:
[auto build test WARNING on linus/master]
[also build test WARNING on v7.1-rc3 next-20260508]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Sicong-Huang/virt-acrn-Fix-irqfd-use-after-free-during-async-shutdown/20260515-063914
base: linus/master
patch link: https://lore.kernel.org/r/20260511135737.2285411-1-congei42%40163.com
patch subject: [PATCH v1] virt: acrn: Fix irqfd use-after-free during async shutdown
config: x86_64-allyesconfig (https://download.01.org/0day-ci/archive/20260515/202605151636.lR13SqEN-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260515/202605151636.lR13SqEN-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202605151636.lR13SqEN-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> drivers/virt/acrn/irqfd.c:232:28: warning: unused variable 'next' [-Wunused-variable]
232 | struct hsm_irqfd *irqfd, *next;
| ^~~~
1 warning generated.
vim +/next +232 drivers/virt/acrn/irqfd.c
aa3b483ff1d71c5 Shuo Liu 2021-02-07 229
aa3b483ff1d71c5 Shuo Liu 2021-02-07 230 void acrn_irqfd_deinit(struct acrn_vm *vm)
aa3b483ff1d71c5 Shuo Liu 2021-02-07 231 {
aa3b483ff1d71c5 Shuo Liu 2021-02-07 @232 struct hsm_irqfd *irqfd, *next;
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-05-15 8:40 UTC | newest]
Thread overview: 3+ 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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox