[PATCH] accel/mshv: mitigate early boot vcpu exit race

Magnus Kulke posted 1 patch 1 week, 2 days ago
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/qemu tags/patchew/20260521133433.48463-1-magnuskulke@linux.microsoft.com
Maintainers: Magnus Kulke <magnuskulke@linux.microsoft.com>, Wei Liu <wei.liu@kernel.org>, "Doru Blânzeanu" <dblanzeanu@linux.microsoft.com>
accel/mshv/mshv-all.c | 42 ++++++++++++++++++++++++++++++++++--------
1 file changed, 34 insertions(+), 8 deletions(-)
[PATCH] accel/mshv: mitigate early boot vcpu exit race
Posted by Magnus Kulke 1 week, 2 days ago
When using the mshv accelerator, there is a high likelihood in early
boot for a QMP execute::quit command to return w/o the QEMU process
terminating.

The race is between SIG_IPI delivery and the vcpu thread's exec loop.
qemu_cpu_kick() relies on SIG_IPI to make MSHV_RUN_VP return -EINTR so
the vcpu thread can observe cpu->stop. During early boot, there are many
PIO intercept exit produce MshvVmExitIgnore. That means the inner loop
in mshv_cpu_exec() spins through RUN_VP ioctl -> handler -> RUN_VP
ioctl, without being able to consult cpu->exit_request of cpu->stop.

The qemu_cpu_kick_self() in sa_ipi_handler() was practically a no-op,
since it calls cpus_kick_thread() which itsself issues a SIG_IPI, in the
handler for that signal. Hence it has been removed a made a practial
noop. Once we have a facility in MSHV to signal immediate_exit to the
kernel it should be used in this handler.

To mitigate we register a custom kick_vcpu_thread() impl for mshv, that
will set cpu->exit_request before calling the generic kick_vcpu_thread()
implement that will raise SIG_IPI.

We then observe cpu->exit request in the mshv_cpu_exec() inner loop and
break with EXCP_INTERRUPT when set.

Signed-off-by: Magnus Kulke <magnuskulke@linux.microsoft.com>
---
 accel/mshv/mshv-all.c | 42 ++++++++++++++++++++++++++++++++++--------
 1 file changed, 34 insertions(+), 8 deletions(-)

diff --git a/accel/mshv/mshv-all.c b/accel/mshv/mshv-all.c
index 58af674bd9..a3593c67db 100644
--- a/accel/mshv/mshv-all.c
+++ b/accel/mshv/mshv-all.c
@@ -502,6 +502,18 @@ static int mshv_cpu_exec(CPUState *cpu)
     cpu_exec_start(cpu);
 
     do {
+        /*
+         * We consider a pending exit_request before re-entering the guest.
+         * This condition is set by mshv_kick_vcpu_thread(), so we unset
+         * thread_kicked to allow future kicks to interrupt the guest.
+         */
+        if (qatomic_load_acquire(&cpu->exit_request)) {
+            qatomic_set(&cpu->exit_request, false);
+            qatomic_set_mb(&cpu->thread_kicked, false);
+            ret = EXCP_INTERRUPT;
+            break;
+        }
+
         if (cpu->accel->dirty) {
             ret = mshv_arch_put_registers(cpu);
             if (ret) {
@@ -540,17 +552,17 @@ static int mshv_cpu_exec(CPUState *cpu)
 }
 
 /*
- * The signal handler is triggered when QEMU's main thread receives a SIG_IPI
- * (SIGUSR1). This signal causes the current CPU thread to be kicked, forcing a
- * VM exit on the CPU. The VM exit generates an exit reason that breaks the loop
- * (see mshv_cpu_exec). If the exit is due to a Ctrl+A+x command, the system
- * will shut down. For other cases, the system will continue running.
+ * SIG_IPI handler for the vCPU thread. It's a noop currently. A decision to
+ * leave the guest is communicated via cpu->exit_request, which is set by
+ * mshv_kick_vcpu_thread() before it raises a SIG_IPI.
+ *
+ * Note: MSHV currently lacks an immediate_exit equivalent to KVM, there
+ * remains a theoretical race window between userspace check and ioctl entry
+ * in the vcpu loop. Once MSHV supports immediate_exit semantics, we should
+ * invoke it here.
  */
 static void sa_ipi_handler(int sig)
 {
-    /* TODO: call IOCTL to set_immediate_exit, once implemented. */
-
-    qemu_cpu_kick_self();
 }
 
 static void init_signal(CPUState *cpu)
@@ -605,6 +617,19 @@ cleanup:
     return NULL;
 }
 
+/*
+ * mshv-custom kick implementation:
+ *
+ * We set cpu->exit_request before the SIG_IPI is delivered to the vcpu
+ * thread (as part of cpus_kick_thread()). It is consumed in the
+ * mshv_cpu_exec loop.
+ */
+static void mshv_kick_vcpu_thread(CPUState *cpu)
+{
+    qatomic_set_mb(&cpu->exit_request, true);
+    cpus_kick_thread(cpu);
+}
+
 static void mshv_start_vcpu_thread(CPUState *cpu)
 {
     char thread_name[VCPU_THREAD_NAME_SIZE];
@@ -719,6 +744,7 @@ static void mshv_accel_ops_class_init(ObjectClass *oc, const void *data)
     AccelOpsClass *ops = ACCEL_OPS_CLASS(oc);
 
     ops->create_vcpu_thread = mshv_start_vcpu_thread;
+    ops->kick_vcpu_thread = mshv_kick_vcpu_thread;
     ops->synchronize_post_init = mshv_cpu_synchronize_post_init;
     ops->synchronize_post_reset = mshv_cpu_synchronize_post_reset;
     ops->synchronize_state = mshv_cpu_synchronize;
-- 
2.34.1
Re: [PATCH] accel/mshv: mitigate early boot vcpu exit race
Posted by Doru Blânzeanu 1 week, 1 day ago
On Thu, May 21, 2026 at 03:34:33PM +0200, Magnus Kulke wrote:
> When using the mshv accelerator, there is a high likelihood in early
> boot for a QMP execute::quit command to return w/o the QEMU process
> terminating.
> 
> The race is between SIG_IPI delivery and the vcpu thread's exec loop.
> qemu_cpu_kick() relies on SIG_IPI to make MSHV_RUN_VP return -EINTR so
> the vcpu thread can observe cpu->stop. During early boot, there are many
> PIO intercept exit produce MshvVmExitIgnore. That means the inner loop
> in mshv_cpu_exec() spins through RUN_VP ioctl -> handler -> RUN_VP
> ioctl, without being able to consult cpu->exit_request of cpu->stop.
> 
> The qemu_cpu_kick_self() in sa_ipi_handler() was practically a no-op,
> since it calls cpus_kick_thread() which itsself issues a SIG_IPI, in the
> handler for that signal. Hence it has been removed a made a practial
> noop. Once we have a facility in MSHV to signal immediate_exit to the
> kernel it should be used in this handler.
> 
> To mitigate we register a custom kick_vcpu_thread() impl for mshv, that
> will set cpu->exit_request before calling the generic kick_vcpu_thread()
> implement that will raise SIG_IPI.
> 
> We then observe cpu->exit request in the mshv_cpu_exec() inner loop and
> break with EXCP_INTERRUPT when set.
> 
> Signed-off-by: Magnus Kulke <magnuskulke@linux.microsoft.com>
> ---
>  accel/mshv/mshv-all.c | 42 ++++++++++++++++++++++++++++++++++--------
>  1 file changed, 34 insertions(+), 8 deletions(-)
> 
> diff --git a/accel/mshv/mshv-all.c b/accel/mshv/mshv-all.c
> index 58af674bd9..a3593c67db 100644
> --- a/accel/mshv/mshv-all.c
> +++ b/accel/mshv/mshv-all.c
> @@ -502,6 +502,18 @@ static int mshv_cpu_exec(CPUState *cpu)
>      cpu_exec_start(cpu);
>  
>      do {
> +        /*
> +         * We consider a pending exit_request before re-entering the guest.
> +         * This condition is set by mshv_kick_vcpu_thread(), so we unset
> +         * thread_kicked to allow future kicks to interrupt the guest.
> +         */
> +        if (qatomic_load_acquire(&cpu->exit_request)) {
> +            qatomic_set(&cpu->exit_request, false);
> +            qatomic_set_mb(&cpu->thread_kicked, false);
> +            ret = EXCP_INTERRUPT;
> +            break;
> +        }
> +
>          if (cpu->accel->dirty) {
>              ret = mshv_arch_put_registers(cpu);
>              if (ret) {
> @@ -540,17 +552,17 @@ static int mshv_cpu_exec(CPUState *cpu)
>  }
>  
>  /*
> - * The signal handler is triggered when QEMU's main thread receives a SIG_IPI
> - * (SIGUSR1). This signal causes the current CPU thread to be kicked, forcing a
> - * VM exit on the CPU. The VM exit generates an exit reason that breaks the loop
> - * (see mshv_cpu_exec). If the exit is due to a Ctrl+A+x command, the system
> - * will shut down. For other cases, the system will continue running.
> + * SIG_IPI handler for the vCPU thread. It's a noop currently. A decision to
> + * leave the guest is communicated via cpu->exit_request, which is set by
> + * mshv_kick_vcpu_thread() before it raises a SIG_IPI.
> + *
> + * Note: MSHV currently lacks an immediate_exit equivalent to KVM, there
> + * remains a theoretical race window between userspace check and ioctl entry
> + * in the vcpu loop. Once MSHV supports immediate_exit semantics, we should
> + * invoke it here.
>   */
>  static void sa_ipi_handler(int sig)
>  {
> -    /* TODO: call IOCTL to set_immediate_exit, once implemented. */
> -
> -    qemu_cpu_kick_self();
>  }
>  
>  static void init_signal(CPUState *cpu)
> @@ -605,6 +617,19 @@ cleanup:
>      return NULL;
>  }
>  
> +/*
> + * mshv-custom kick implementation:
> + *
> + * We set cpu->exit_request before the SIG_IPI is delivered to the vcpu
> + * thread (as part of cpus_kick_thread()). It is consumed in the
> + * mshv_cpu_exec loop.
> + */
> +static void mshv_kick_vcpu_thread(CPUState *cpu)
> +{
> +    qatomic_set_mb(&cpu->exit_request, true);
> +    cpus_kick_thread(cpu);
> +}
> +
>  static void mshv_start_vcpu_thread(CPUState *cpu)
>  {
>      char thread_name[VCPU_THREAD_NAME_SIZE];
> @@ -719,6 +744,7 @@ static void mshv_accel_ops_class_init(ObjectClass *oc, const void *data)
>      AccelOpsClass *ops = ACCEL_OPS_CLASS(oc);
>  
>      ops->create_vcpu_thread = mshv_start_vcpu_thread;
> +    ops->kick_vcpu_thread = mshv_kick_vcpu_thread;
>      ops->synchronize_post_init = mshv_cpu_synchronize_post_init;
>      ops->synchronize_post_reset = mshv_cpu_synchronize_post_reset;
>      ops->synchronize_state = mshv_cpu_synchronize;
> -- 
> 2.34.1

Reviewed-by: Doru Blânzeanu <dblanzeanu@linux.microsoft.com>