[PATCH] qemu: fix race in signal interrupt during QEMU startup

Daniel P. Berrangé posted 1 patch 1 week ago
Test syntax-check failed
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/libvirt tags/patchew/20200729180332.2798261-1-berrange@redhat.com
src/qemu/qemu_shim.c | 77 +++++++++++++++++++++++++++++++++++++++++---
1 file changed, 72 insertions(+), 5 deletions(-)

[PATCH] qemu: fix race in signal interrupt during QEMU startup

Posted by Daniel P. Berrangé 1 week ago
If a Ctrl-C arrives while we are in the middle of executing the
virDomainCreateXML call, we will have no "virDomainPtr" object
available, but QEMU may none the less be running.

This means we'll never try to stop the QEMU process before we
honour the Ctrl-C and exit.

To deal with this race we need to postpone quit of the event
loop if it is requested while in the middle of domain startup.

Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 src/qemu/qemu_shim.c | 77 +++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 72 insertions(+), 5 deletions(-)

diff --git a/src/qemu/qemu_shim.c b/src/qemu/qemu_shim.c
index 7e87b8fb96..bbcf9dc886 100644
--- a/src/qemu/qemu_shim.c
+++ b/src/qemu/qemu_shim.c
@@ -30,19 +30,61 @@
 
 #define VIR_FROM_THIS VIR_FROM_QEMU
 
+static GMutex eventLock;
+static bool eventNoQuitFlag;
 static bool eventQuitFlag;
 static int eventQuitFD = -1;
 static virDomainPtr dom;
 
+/* Runs in event loop thread context */
 static void *
 qemuShimEventLoop(void *opaque G_GNUC_UNUSED)
 {
-    while (!eventQuitFlag)
+    bool quit = false;
+    while (!quit) {
+        g_mutex_lock(&eventLock);
+        if (eventQuitFlag && !eventNoQuitFlag) {
+            if (dom) {
+                virDomainDestroy(dom);
+                quit = true;
+            }
+        }
+        g_mutex_unlock(&eventLock);
         virEventRunDefaultImpl();
+    }
 
     return NULL;
 }
 
+/* Runs in any thread context */
+static bool
+qemuShimEventLoopPreventQuit(void)
+{
+    bool quitting;
+    g_mutex_lock(&eventLock);
+    quitting = eventQuitFlag;
+    if (!quitting)
+        eventNoQuitFlag = true;
+    g_mutex_unlock(&eventLock);
+    return quitting;
+}
+
+/* Runs in any thread context */
+static bool
+qemuShimEventLoopAllowQuit(void)
+{
+    bool quitting;
+    g_mutex_lock(&eventLock);
+    eventNoQuitFlag = false;
+    /* kick the event loop thread again immediately */
+    quitting = eventQuitFlag;
+    if (quitting)
+        ignore_value(safewrite(eventQuitFD, "c", 1));
+    g_mutex_unlock(&eventLock);
+    return quitting;
+}
+
+
 /* Runs in event loop thread context */
 static void
 qemuShimEventLoopStop(int watch G_GNUC_UNUSED,
@@ -52,7 +94,9 @@ qemuShimEventLoopStop(int watch G_GNUC_UNUSED,
 {
     char c;
     ignore_value(read(fd, &c, 1));
+    g_mutex_lock(&eventLock);
     eventQuitFlag = true;
+    g_mutex_unlock(&eventLock);
 }
 
 /* Runs in event loop thread context */
@@ -63,8 +107,11 @@ qemuShimDomShutdown(virConnectPtr econn G_GNUC_UNUSED,
                     int detail G_GNUC_UNUSED,
                     void *opaque G_GNUC_UNUSED)
 {
-    if (event == VIR_DOMAIN_EVENT_STOPPED)
+    if (event == VIR_DOMAIN_EVENT_STOPPED) {
+        g_mutex_lock(&eventLock);
         eventQuitFlag = true;
+        g_mutex_unlock(&eventLock);
+    }
 
     return 0;
 }
@@ -109,6 +156,7 @@ int main(int argc, char **argv)
         { 0 }
     };
     int quitfd[2] = {-1, -1};
+    bool quitting;
     long long start = g_get_monotonic_time();
 
 #define deltams() ((long long)g_get_monotonic_time() - start)
@@ -128,8 +176,8 @@ int main(int argc, char **argv)
     }
 
     if (verbose)
-        g_printerr("%s: %lld: initializing libvirt\n",
-                   argv[0], deltams());
+        g_printerr("%s: %lld: initializing libvirt %d\n",
+                   argv[0], deltams(), gettid());
 
     if (virInitialize() < 0) {
         g_printerr("%s: cannot initialize libvirt\n", argv[0]);
@@ -277,7 +325,7 @@ int main(int argc, char **argv)
     }
 
     if (verbose)
-        g_printerr("%s: %lld: starting guest %s\n",
+        g_printerr("%s: %lld: fetching guest config %s\n",
                    argv[0], deltams(), argv[1]);
 
     if (!g_file_get_contents(argv[1], &xml, NULL, &error)) {
@@ -286,7 +334,24 @@ int main(int argc, char **argv)
         goto cleanup;
     }
 
+    if (verbose)
+        g_printerr("%s: %lld: starting guest %s\n",
+                   argv[0], deltams(), argv[1]);
+
+    /*
+     * If the user issues a ctrl-C at this time, we need to
+     * let the virDomainCreateXML call complete, so that we
+     * can then clean up the guest correctly. We must also
+     * ensure that the event loop doesn't quit yet, because
+     * it might be needed to complete VM startup & shutdown
+     * during the cleanup.
+     */
+    quitting = qemuShimEventLoopPreventQuit();
+    if (quitting)
+        goto cleanup;
     dom = virDomainCreateXML(conn, xml, 0);
+    quitting = qemuShimEventLoopAllowQuit();
+
     if (!dom) {
         g_printerr("%s: cannot start VM: %s\n",
                    argv[0], virGetLastErrorMessage());
@@ -295,6 +360,8 @@ int main(int argc, char **argv)
     if (verbose)
         g_printerr("%s: %lld: guest running, Ctrl-C to stop now\n",
                    argv[0], deltams());
+    if (quitting)
+        goto cleanup;
 
     if (debug) {
         g_autofree char *newxml = NULL;
-- 
2.24.1

Re: [PATCH] qemu: fix race in signal interrupt during QEMU startup

Posted by Michal Privoznik 1 week ago
On 7/29/20 8:03 PM, Daniel P. Berrangé wrote:
> If a Ctrl-C arrives while we are in the middle of executing the
> virDomainCreateXML call, we will have no "virDomainPtr" object
> available, but QEMU may none the less be running.
> 
> This means we'll never try to stop the QEMU process before we
> honour the Ctrl-C and exit.
> 
> To deal with this race we need to postpone quit of the event
> loop if it is requested while in the middle of domain startup.
> 
> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
> ---
>   src/qemu/qemu_shim.c | 77 +++++++++++++++++++++++++++++++++++++++++---
>   1 file changed, 72 insertions(+), 5 deletions(-)
> 
> diff --git a/src/qemu/qemu_shim.c b/src/qemu/qemu_shim.c
> index 7e87b8fb96..bbcf9dc886 100644
> --- a/src/qemu/qemu_shim.c
> +++ b/src/qemu/qemu_shim.c
> @@ -30,19 +30,61 @@
>   
>   #define VIR_FROM_THIS VIR_FROM_QEMU
>   
> +static GMutex eventLock;
> +static bool eventNoQuitFlag;

This is not very mnemonic name. How about "eventPreventQuitFlag"?

Reviewed-by: Michal Privoznik <mprivozn@redhat.com>

Michal