Hi,
The attached patch fixes a NULL pointer dereference crash in the QEMU driver
that was observed .
Issue:
libvirtd crashes with SIGSEGV in virEventThreadStop(evt=0x0) during VM
shutdown. The crash occurs in qemuProcessStop() when it is invoked from the
QEMU monitor EOF event worker thread.
Root Cause:
There is a race condition when two threads enter qemuProcessStop() concurrently
for the same domain:
- Thread A (shutdown path) frees priv->eventThread via g_steal_pointer().
- Thread B (EOF event worker) passes the "if (priv->eventThread)" NULL check
while the pointer is still valid, but the VM object is then unlocked before
virEventThreadStop() is called. In that window, Thread A clears
priv->eventThread, so by the time Thread B calls
virEventThreadStop(priv->eventThread), the pointer is already NULL, causing the
SIGSEGV.
The existing "if (priv->eventThread)" guard is insufficient because the VM
object lock is released between the check and the actual call to
virEventThreadStop().
Crash backtrace:
#0 virEventThreadStop (evt=0x0)
#1 qemuProcessStop (reason=VIR_DOMAIN_SHUTOFF_SHUTDOWN,
asyncJob=VIR_ASYNC_JOB_NONE)
#2 processMonitorEOFEvent (driver=..., vm=...)
#3 qemuProcessEventHandler (data=..., opaque=...)
#4 virThreadPoolWorker (opaque=...)
Fix:
Take a GObject reference on priv->eventThread before unlocking the VM object,
use the local reference for virEventThreadStop(), then release it with
g_object_unref() afterwards. This ensures the pointer remains valid for
virEventThreadStop() even if another thread clears priv->eventThread
concurrently.
The affected code is present in current master (introduced in commit
0888784f387, 2024-07-25):
https://gitlab.com/libvirt/libvirt/-/commit/0888784f387
(https://gitlab.com/libvirt/libvirt/-/commit/0888784f387)
The crash is not reproducible on demand due to the narrow timing window of the
race, but it is deterministic once the window is hit —
virEventThreadStop(evt=0x0) will always SIGSEGV.
The fix has been build-tested.
Thanks,
Pritam Srichandan Sahoo
From 43e203f8fe19702cc9813d109499e4a7165f6c6f Mon Sep 17 00:00:00 2001
From: Pritam Srichandan Sahoo <[email protected]>
Date: Mon, 17 Aug 2026 08:22:22 +0000
Subject: [PATCH 1/1] qemu: Fix crash in qemuProcessStop due to NULL
eventThread
When a VM shuts down, the monitor EOF event is queued to the worker
thread pool. If another thread enters qemuProcessStop() first and
frees priv->eventThread via g_steal_pointer(), the second call from
the EOF event worker thread hits a NULL dereference when calling
virEventThreadStop(priv->eventThread).
The existing 'if (priv->eventThread)' guard is insufficient because
the VM object is unlocked between the check and the call to
virEventThreadStop(), allowing a concurrent thread to clear the
pointer in the meantime.
Fix this by taking a GObject reference to eventThread before
unlocking the VM. This ensures the pointer remains valid for
virEventThreadStop() even if another thread clears
priv->eventThread concurrently.
Crash backtrace:
#0 virEventThreadStop (evt=0x0)
#1 qemuProcessStop (reason=VIR_DOMAIN_SHUTOFF_SHUTDOWN, asyncJob=VIR_ASYNC_JOB_NONE)
#2 processMonitorEOFEvent (driver=..., vm=...)
#3 qemuProcessEventHandler (data=..., opaque=...)
#4 virThreadPoolWorker (opaque=...)
Signed-off-by: Pritam Srichandan Sahoo <[email protected]>
---
src/qemu/qemu_process.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c
index 0d9b8bcb93..642e18eeb1 100644
--- a/src/qemu/qemu_process.c
+++ b/src/qemu/qemu_process.c
@@ -8850,13 +8850,15 @@ void qemuProcessStop(virQEMUDriver *driver,
* the global domain object list code depends on it (and it can't actually
* check 'priv->beingDestroyed as that's private). */
if (priv->eventThread) {
+ virEventThread *eventThread = g_object_ref(priv->eventThread);
/* Explicitly set priv->beingDestroyed. While it's done in
* qemuProcessBeginStopJob(), qemuProcessStop() is called from places
* where stop job is not acquired. */
priv->beingDestroyed = true;
virObjectUnlock(vm);
- virEventThreadStop(priv->eventThread);
+ virEventThreadStop(eventThread);
virObjectLock(vm);
+ g_object_unref(eventThread);
}
if (priv->agent) {
--
2.53.0