[Xen-devel,for-4.13,v4,19/19] xen/arm: entry: Ensure the guest state is synced when receiving a vSError

Message ID 20191031150922.22938-20-julien.grall@arm.com
State New
Headers show
Series
  • xen/arm: XSA-201 and XSA-263 fixes
Related show

Commit Message

Julien Grall Oct. 31, 2019, 3:09 p.m.
When a SError/Asynchronous Abort generated by the guest has been
consumed, we will skip the handling of the initial exception.

This includes the calls to enter_hypervisor_from_guest{, _noirq} that
is used to synchronize part of the guest state with the internal
representation and re-enable workarounds (e.g. SSBD). However, we still
call leave_hypervisor_to_guest() which is used for preempting the guest
and synchronizing back part of the guest state.

enter_hypervisor_from_guest{, _noirq} works in pair with
leave_hypervisor_to_guest(), so skipping the first two may result
in a loss of some part of guest state.

An example is the new vGIC which will save the state of the LRs on exit
from the guest and rewrite all of them on entry to the guest.

A more worrying example is SSBD workaround may not be re-enabled. If
leave_hypervisor_to_guest() is rescheduling the vCPU, then we may end to
run a lot of code with SSBD workaroud disabled.

For now, calling leave_hypervisor_to_guest() is not necessary when
injecting a vSError to the guest. But it would still be good to give an
opportunity to reschedule. So both enter_hypervisor_from_guest() and
leave_hypervisor_to_guest() are called.

Note that on arm64, the return value for check_pending_vserror is now
stored in x19 instead of x0. This is because we want to keep the value
across call to C-functions (x0, unlike x19, will not be saved by the
callee).

Take the opportunity to rename check_pending_vserror() to
check_pending_guest_serror() as the function is dealing with host SError
and *not* virtual SError. The documentation is also updated accross
Arm32 and Arm64 to clarify how Xen is dealing with SError generated by
the guest.

Signed-off-by: Julien Grall <julien.grall@arm.com>

---

    Changes in v4:
        - Rewording + typo

    Changes in v3:
        - Update comments in the code.
        - Update commit message
        - Add arm32 support

There are two known issues without this patch applied:
    * The state of the vGIC when using the new version may be lost.
    * SSBD workaround may be kept disabled while rescheduling the guest.
---
 xen/arch/arm/arm32/entry.S | 57 ++++++++++++++++++++++++++++++++++++++--------
 xen/arch/arm/arm64/entry.S | 54 ++++++++++++++++++++++++++++++++-----------
 2 files changed, 88 insertions(+), 23 deletions(-)

Comments

Stefano Stabellini Oct. 31, 2019, 6:18 p.m. | #1
On Thu, 31 Oct 2019, Julien Grall wrote:
> When a SError/Asynchronous Abort generated by the guest has been
> consumed, we will skip the handling of the initial exception.
> 
> This includes the calls to enter_hypervisor_from_guest{, _noirq} that
> is used to synchronize part of the guest state with the internal
> representation and re-enable workarounds (e.g. SSBD). However, we still
> call leave_hypervisor_to_guest() which is used for preempting the guest
> and synchronizing back part of the guest state.
> 
> enter_hypervisor_from_guest{, _noirq} works in pair with
> leave_hypervisor_to_guest(), so skipping the first two may result
> in a loss of some part of guest state.
> 
> An example is the new vGIC which will save the state of the LRs on exit
> from the guest and rewrite all of them on entry to the guest.
> 
> A more worrying example is SSBD workaround may not be re-enabled. If
> leave_hypervisor_to_guest() is rescheduling the vCPU, then we may end to
> run a lot of code with SSBD workaroud disabled.
> 
> For now, calling leave_hypervisor_to_guest() is not necessary when
> injecting a vSError to the guest. But it would still be good to give an
> opportunity to reschedule. So both enter_hypervisor_from_guest() and
> leave_hypervisor_to_guest() are called.
> 
> Note that on arm64, the return value for check_pending_vserror is now
> stored in x19 instead of x0. This is because we want to keep the value
> across call to C-functions (x0, unlike x19, will not be saved by the
> callee).
> 
> Take the opportunity to rename check_pending_vserror() to
> check_pending_guest_serror() as the function is dealing with host SError
> and *not* virtual SError. The documentation is also updated accross
> Arm32 and Arm64 to clarify how Xen is dealing with SError generated by
> the guest.
> 
> Signed-off-by: Julien Grall <julien.grall@arm.com>

Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>


> ---
> 
>     Changes in v4:
>         - Rewording + typo
> 
>     Changes in v3:
>         - Update comments in the code.
>         - Update commit message
>         - Add arm32 support
> 
> There are two known issues without this patch applied:
>     * The state of the vGIC when using the new version may be lost.
>     * SSBD workaround may be kept disabled while rescheduling the guest.
> ---
>  xen/arch/arm/arm32/entry.S | 57 ++++++++++++++++++++++++++++++++++++++--------
>  xen/arch/arm/arm64/entry.S | 54 ++++++++++++++++++++++++++++++++-----------
>  2 files changed, 88 insertions(+), 23 deletions(-)
> 
> diff --git a/xen/arch/arm/arm32/entry.S b/xen/arch/arm/arm32/entry.S
> index 34156c4404..b31056a616 100644
> --- a/xen/arch/arm/arm32/entry.S
> +++ b/xen/arch/arm/arm32/entry.S
> @@ -27,6 +27,10 @@
>  /*
>   * Actions that needs to be done after entering the hypervisor from the
>   * guest and before the interrupts are unmasked.
> + *
> + * @return:
> + *  r4: Set to a non-zero value if a pending Abort exception took place.
> + *      Otherwise, it will be set to zero.
>   */
>  arch_enter_hypervisor_from_guest_preirq:
>  #ifdef CONFIG_ARM32_HARDEN_BRANCH_PREDICTOR
> @@ -56,18 +60,35 @@ arch_enter_hypervisor_from_guest_preirq:
>          SAVE_ONE_BANKED(R11_fiq); SAVE_ONE_BANKED(R12_fiq);
>  
>          /*
> -         * If the SKIP_SYNCHRONIZE_SERROR_ENTRY_EXIT has been set in the cpu
> -         * feature, the checking of pending SErrors will be skipped.
> +         * We may have entered the hypervisor with pending asynchronous Abort
> +         * generated by the guest. If we need to categorize them, then
> +         * we need to consume any outstanding asynchronous Abort.
> +         * Otherwise, they can be consumed later on.
>           */
>          alternative_if SKIP_SYNCHRONIZE_SERROR_ENTRY_EXIT
> +        mov r4, #0              /* r4 := No Abort was consumed */
>          b   skip_check
>          alternative_else_nop_endif
>  
>          /*
> -         * Start to check pending virtual abort in the gap of Guest -> HYP
> -         * world switch.
> +         * Consume pending asynchronous Abort generated by the guest if any.
> +         *
> +         * The only way to consume an Abort interrupt is to unmask it. So
> +         * Abort exception will be unmaked for a small window and then masked
> +         * it again.
> +         *
> +         * It is fine to unmask asynchronous Abort exception as we fully
> +         * control the state of the processor and only limited code will
> +         * be executed if the exception returns (see do_trap_data_abort()).
>           *
> -         * Save ELR_hyp to check whether the pending virtual abort exception
> +         * TODO: The asynchronous abort path should be reworked to
> +         * inject the virtual asynchronous Abort in enter_hypervisor_*
> +         * rather than do_trap_data_abort(). This should make easier to
> +         * understand the path.
> +         */
> +
> +        /*
> +         * save elr_hyp to check whether the pending virtual abort exception
>           * takes place while we are doing this trap exception.
>           */
>          mrs r1, ELR_hyp
> @@ -112,11 +133,11 @@ abort_guest_exit_end:
>          cmp r1, r2
>  
>          /*
> -         * Not equal, the pending virtual abort exception took place, the
> -         * initial exception does not have any significance to be handled.
> -         * Exit ASAP.
> +         * Set r4 depending on whether an asynchronous abort were
> +         * consumed.
>           */
> -        bne return_from_trap
> +        movne r4, #1
> +        moveq r4, #0
>  
>  skip_check:
>          b   enter_hypervisor_from_guest_preirq
> @@ -179,12 +200,28 @@ ENDPROC(arch_enter_hypervisor_from_guest_preirq)
>  
>  1:
>          /* Trap from the guest */
> +        /*
> +         * arch_enter_hypervisor_from_guest_preirq will return with r4 set to
> +         * a non-zero value if an asynchronous Abort was consumed.
> +         *
> +         * When an asynchronous Abort has been consumed (r4 != 0), we may have
> +         * injected a virtual asynchronous Abort to the guest.
> +         *
> +         * In this case, the initial exception will be discarded (PC has
> +         * been adjusted by inject_vabt_exception()). However, we still
> +         * want to give an opportunity to reschedule the vCPU. So we
> +         * only want to skip the handling of the initial exception (i.e.
> +         * do_trap_*()).
> +         */
>          bl      arch_enter_hypervisor_from_guest_preirq
>          .if     \guest_iflags != n
>          cpsie   \guest_iflags
>          .endif
>  
> -        bl      enter_hypervisor_from_guest
> +        adr     lr, 2f
> +        cmp     r4, #0
> +        adrne   lr, return_from_trap
> +        b       enter_hypervisor_from_guest
>  
>  2:
>          /* We are ready to handle the trap, setup the registers and jump. */
> diff --git a/xen/arch/arm/arm64/entry.S b/xen/arch/arm/arm64/entry.S
> index a8ba7ab961..d35855af96 100644
> --- a/xen/arch/arm/arm64/entry.S
> +++ b/xen/arch/arm/arm64/entry.S
> @@ -184,18 +184,41 @@
>          .macro  guest_vector compat, iflags, trap, save_x0_x1=1
>          entry   hyp=0, compat=\compat, save_x0_x1=\save_x0_x1
>          /*
> -         * The vSError will be checked while SKIP_SYNCHRONIZE_SERROR_ENTRY_EXIT
> -         * is not set. If a vSError took place, the initial exception will be
> -         * skipped. Exit ASAP
> +         * We may have entered the hypervisor with pending SErrors
> +         * generated by the guest. If we need to categorize them, then
> +         * we need to check any outstanding SErrors will be consumed.
> +         *
> +         * The function check_pending_guest_serror() will unmask SError
> +         * exception temporarily. This is fine to do before enter_*
> +         * helpers are called because we fully control the state of the
> +         * processor and only limited code willl be executed (see
> +         * do_trap_hyp_serror()).
> +         *
> +         * When a SError has been consumed (x19 != 0), we may have injected a
> +         * virtual SError to the guest.
> +         *
> +         * In this case, the initial exception will be discarded (PC has
> +         * been adjusted by inject_vabt_exception()). However, we still
> +         * want to give an opportunity to reschedule the vCPU. So we
> +         * only want to skip the handling of the initial exception (i.e.
> +         * do_trap_*()).
> +         *
> +         * TODO: The SErrors path should be reworked to inject the vSError in
> +         * enter_hypervisor_* rather than do_trap_hyp_serror. This should make
> +         * easier to understand the path.
>           */
>          alternative_if_not SKIP_SYNCHRONIZE_SERROR_ENTRY_EXIT
> -        bl      check_pending_vserror
> -        cbnz    x0, 1f
> +        bl      check_pending_guest_serror
>          alternative_else_nop_endif
>  
>          bl      enter_hypervisor_from_guest_preirq
>          msr     daifclr, \iflags
>          bl      enter_hypervisor_from_guest
> +
> +        alternative_if SKIP_SYNCHRONIZE_SERROR_ENTRY_EXIT
> +        cbnz    x19, 1f
> +        alternative_else_nop_endif
> +
>          mov     x0, sp
>          bl      do_trap_\trap
>  1:
> @@ -436,13 +459,17 @@ return_from_trap:
>          eret
>  
>  /*
> - * This function is used to check pending virtual SError in the gap of
> - * EL1 -> EL2 world switch.
> - * The x0 register will be used to indicate the results of detection.
> - * x0 -- Non-zero indicates a pending virtual SError took place.
> - * x0 -- Zero indicates no pending virtual SError took place.
> + * Consume pending SError generated by the guest if any.
> + *
> + * @return:
> + *  x19: Set to a non-zero value if a pending Abort exception took place.
> + *       Otherwise, it will be set to zero.
> + *
> + * Without RAS extension, the only way to consume a SError is to unmask
> + * it. So the function will unmask SError exception for a small window and
> + * then mask it again.
>   */
> -check_pending_vserror:
> +check_pending_guest_serror:
>          /*
>           * Save elr_el2 to check whether the pending SError exception takes
>           * place while we are doing this sync exception.
> @@ -487,11 +514,12 @@ abort_guest_exit_end:
>  
>          /*
>           * Not equal, the pending SError exception took place, set
> -         * x0 to non-zero.
> +         * x19 to non-zero.
>           */
> -        cset    x0, ne
> +        cset    x19, ne
>  
>          ret
> +ENDPROC(check_pending_guest_serror)
>  
>  /*
>   * Exception vectors.
> -- 
> 2.11.0
>

Patch

diff --git a/xen/arch/arm/arm32/entry.S b/xen/arch/arm/arm32/entry.S
index 34156c4404..b31056a616 100644
--- a/xen/arch/arm/arm32/entry.S
+++ b/xen/arch/arm/arm32/entry.S
@@ -27,6 +27,10 @@ 
 /*
  * Actions that needs to be done after entering the hypervisor from the
  * guest and before the interrupts are unmasked.
+ *
+ * @return:
+ *  r4: Set to a non-zero value if a pending Abort exception took place.
+ *      Otherwise, it will be set to zero.
  */
 arch_enter_hypervisor_from_guest_preirq:
 #ifdef CONFIG_ARM32_HARDEN_BRANCH_PREDICTOR
@@ -56,18 +60,35 @@  arch_enter_hypervisor_from_guest_preirq:
         SAVE_ONE_BANKED(R11_fiq); SAVE_ONE_BANKED(R12_fiq);
 
         /*
-         * If the SKIP_SYNCHRONIZE_SERROR_ENTRY_EXIT has been set in the cpu
-         * feature, the checking of pending SErrors will be skipped.
+         * We may have entered the hypervisor with pending asynchronous Abort
+         * generated by the guest. If we need to categorize them, then
+         * we need to consume any outstanding asynchronous Abort.
+         * Otherwise, they can be consumed later on.
          */
         alternative_if SKIP_SYNCHRONIZE_SERROR_ENTRY_EXIT
+        mov r4, #0              /* r4 := No Abort was consumed */
         b   skip_check
         alternative_else_nop_endif
 
         /*
-         * Start to check pending virtual abort in the gap of Guest -> HYP
-         * world switch.
+         * Consume pending asynchronous Abort generated by the guest if any.
+         *
+         * The only way to consume an Abort interrupt is to unmask it. So
+         * Abort exception will be unmaked for a small window and then masked
+         * it again.
+         *
+         * It is fine to unmask asynchronous Abort exception as we fully
+         * control the state of the processor and only limited code will
+         * be executed if the exception returns (see do_trap_data_abort()).
          *
-         * Save ELR_hyp to check whether the pending virtual abort exception
+         * TODO: The asynchronous abort path should be reworked to
+         * inject the virtual asynchronous Abort in enter_hypervisor_*
+         * rather than do_trap_data_abort(). This should make easier to
+         * understand the path.
+         */
+
+        /*
+         * save elr_hyp to check whether the pending virtual abort exception
          * takes place while we are doing this trap exception.
          */
         mrs r1, ELR_hyp
@@ -112,11 +133,11 @@  abort_guest_exit_end:
         cmp r1, r2
 
         /*
-         * Not equal, the pending virtual abort exception took place, the
-         * initial exception does not have any significance to be handled.
-         * Exit ASAP.
+         * Set r4 depending on whether an asynchronous abort were
+         * consumed.
          */
-        bne return_from_trap
+        movne r4, #1
+        moveq r4, #0
 
 skip_check:
         b   enter_hypervisor_from_guest_preirq
@@ -179,12 +200,28 @@  ENDPROC(arch_enter_hypervisor_from_guest_preirq)
 
 1:
         /* Trap from the guest */
+        /*
+         * arch_enter_hypervisor_from_guest_preirq will return with r4 set to
+         * a non-zero value if an asynchronous Abort was consumed.
+         *
+         * When an asynchronous Abort has been consumed (r4 != 0), we may have
+         * injected a virtual asynchronous Abort to the guest.
+         *
+         * In this case, the initial exception will be discarded (PC has
+         * been adjusted by inject_vabt_exception()). However, we still
+         * want to give an opportunity to reschedule the vCPU. So we
+         * only want to skip the handling of the initial exception (i.e.
+         * do_trap_*()).
+         */
         bl      arch_enter_hypervisor_from_guest_preirq
         .if     \guest_iflags != n
         cpsie   \guest_iflags
         .endif
 
-        bl      enter_hypervisor_from_guest
+        adr     lr, 2f
+        cmp     r4, #0
+        adrne   lr, return_from_trap
+        b       enter_hypervisor_from_guest
 
 2:
         /* We are ready to handle the trap, setup the registers and jump. */
diff --git a/xen/arch/arm/arm64/entry.S b/xen/arch/arm/arm64/entry.S
index a8ba7ab961..d35855af96 100644
--- a/xen/arch/arm/arm64/entry.S
+++ b/xen/arch/arm/arm64/entry.S
@@ -184,18 +184,41 @@ 
         .macro  guest_vector compat, iflags, trap, save_x0_x1=1
         entry   hyp=0, compat=\compat, save_x0_x1=\save_x0_x1
         /*
-         * The vSError will be checked while SKIP_SYNCHRONIZE_SERROR_ENTRY_EXIT
-         * is not set. If a vSError took place, the initial exception will be
-         * skipped. Exit ASAP
+         * We may have entered the hypervisor with pending SErrors
+         * generated by the guest. If we need to categorize them, then
+         * we need to check any outstanding SErrors will be consumed.
+         *
+         * The function check_pending_guest_serror() will unmask SError
+         * exception temporarily. This is fine to do before enter_*
+         * helpers are called because we fully control the state of the
+         * processor and only limited code willl be executed (see
+         * do_trap_hyp_serror()).
+         *
+         * When a SError has been consumed (x19 != 0), we may have injected a
+         * virtual SError to the guest.
+         *
+         * In this case, the initial exception will be discarded (PC has
+         * been adjusted by inject_vabt_exception()). However, we still
+         * want to give an opportunity to reschedule the vCPU. So we
+         * only want to skip the handling of the initial exception (i.e.
+         * do_trap_*()).
+         *
+         * TODO: The SErrors path should be reworked to inject the vSError in
+         * enter_hypervisor_* rather than do_trap_hyp_serror. This should make
+         * easier to understand the path.
          */
         alternative_if_not SKIP_SYNCHRONIZE_SERROR_ENTRY_EXIT
-        bl      check_pending_vserror
-        cbnz    x0, 1f
+        bl      check_pending_guest_serror
         alternative_else_nop_endif
 
         bl      enter_hypervisor_from_guest_preirq
         msr     daifclr, \iflags
         bl      enter_hypervisor_from_guest
+
+        alternative_if SKIP_SYNCHRONIZE_SERROR_ENTRY_EXIT
+        cbnz    x19, 1f
+        alternative_else_nop_endif
+
         mov     x0, sp
         bl      do_trap_\trap
 1:
@@ -436,13 +459,17 @@  return_from_trap:
         eret
 
 /*
- * This function is used to check pending virtual SError in the gap of
- * EL1 -> EL2 world switch.
- * The x0 register will be used to indicate the results of detection.
- * x0 -- Non-zero indicates a pending virtual SError took place.
- * x0 -- Zero indicates no pending virtual SError took place.
+ * Consume pending SError generated by the guest if any.
+ *
+ * @return:
+ *  x19: Set to a non-zero value if a pending Abort exception took place.
+ *       Otherwise, it will be set to zero.
+ *
+ * Without RAS extension, the only way to consume a SError is to unmask
+ * it. So the function will unmask SError exception for a small window and
+ * then mask it again.
  */
-check_pending_vserror:
+check_pending_guest_serror:
         /*
          * Save elr_el2 to check whether the pending SError exception takes
          * place while we are doing this sync exception.
@@ -487,11 +514,12 @@  abort_guest_exit_end:
 
         /*
          * Not equal, the pending SError exception took place, set
-         * x0 to non-zero.
+         * x19 to non-zero.
          */
-        cset    x0, ne
+        cset    x19, ne
 
         ret
+ENDPROC(check_pending_guest_serror)
 
 /*
  * Exception vectors.