[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [PATCH] x86/svm: require VMSAVEvirt for nested virt


  • To: Stephen Cheng (程小枫) <stephen.cheng@xxxxxxxxxx>, "xen-devel@xxxxxxxxxxxxxxxxxxxx" <xen-devel@xxxxxxxxxxxxxxxxxxxx>
  • From: Ross Lagerwall <ross.lagerwall@xxxxxxxxxx>
  • Date: Mon, 27 Jul 2026 10:50:51 +0000
  • Accept-language: en-US
  • Arc-authentication-results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=citrix.com; dmarc=pass action=none header.from=citrix.com; dkim=pass header.d=citrix.com; arc=none
  • Arc-message-signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=fZ1TcmxJY9XvlSh2dftL5q91WRz5MPt+xysdhkR34cU=; b=aL4Zm96NycO64IgFAYxAfNA0PqK012UuzBQGb4Kn5gSrLRCFM2EYrjJd/XvUD5OvNGuohWI1KK0ci6FrOpuERz6yvmouvL74C4LxnfrAlZW+yBoiFXfOv/DnInyiUaWIAAXcmvwyvkUrNRW9iW0wRj3i5i77BkVWudjT2LuEkiQ8EDPaHSMEzNq7qwszWFUuUuEtcw3n57+gQcvAdcMQbT+bVu36DSACVRFFj0Bob2UmvVFfnrmoOt7/DeKAsDtv/q4T9f474Rb7MhHlTvMZTTcEfqXBSfCB1jAh1alCn8n5kJMShneXj5LzUCxNPS0vZV1mB0rUMXFsATsLbTARmg==
  • Arc-seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=ZpKAkcllVGp307+3OHk2TYlG6M0VETCdugxic2425Ax/yMEaTIM2/RgkMxxMf3KLXHD393AuoOwnPPDnDRZfPx7KXN4BIS9m/UowCx01RKknj6UDMthzpzmJcxZBgapLv23Hu1sgiZX/89nbvyNVQRUnzMg0CSzV/bBsGJQo/kNcz8n8lgRSBxULzS/xbrEuY3bp6mjZ0Dk6Fy6rYkksP8cS/3EkuYhqJXJgK8N+QZDcdlRdfmQ9Pf2B65NADdfz6BrnFpRXzBi3SIWtUScWJN3Lo1/W3ZmK/4mrH6VAP0Ol1Z60+mOPGbOrUtciFgc7pBjbI+EIYIhWAyItdzuh6A==
  • Authentication-results: eu.smtp.expurgate.cloud; dkim=pass header.s=selector1 header.d=citrix.com header.i="@citrix.com" header.h="From:Date:Subject:Message-ID:Content-Type:MIME-Version:x-ms-exchange-senderadcheck"
  • Authentication-results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=citrix.com;
  • Cc: Andrew Cooper <andrew.cooper3@xxxxxxxxxx>, Anthony PERARD <anthony.perard@xxxxxxxxxx>, Michal Orzel <michal.orzel@xxxxxxx>, Jan Beulich <jbeulich@xxxxxxxx>, Julien Grall <julien@xxxxxxx>, Roger Pau Monné <roger@xxxxxxxxxxxxxx>, Stefano Stabellini <sstabellini@xxxxxxxxxx>, Teddy Astie <teddy.astie@xxxxxxxxxx>, Jason Andryuk <jason.andryuk@xxxxxxx>
  • Delivery-date: Mon, 27 Jul 2026 10:51:04 +0000
  • List-id: Xen developer discussion <xen-devel.lists.xenproject.org>
  • Msip_labels:
  • Thread-index: AQHdHZgDgUepMUDe+06Vza+F9KISebaBKYbP
  • Thread-topic: [PATCH] x86/svm: require VMSAVEvirt for nested virt

> From: Stephen Cheng (程小枫) <stephen.cheng@xxxxxxxxxx>
> Sent: Monday, July 27, 2026 8:17 AM
> To: xen-devel@xxxxxxxxxxxxxxxxxxxx
> Cc: Ross Lagerwall; Stephen Cheng (程小枫); Andrew Cooper; Anthony PERARD; 
> Michal Orzel; Jan Beulich; Julien Grall; Roger Pau Monné; Stefano Stabellini; 
> Teddy Astie; Jason Andryuk
> Subject: [PATCH] x86/svm: require VMSAVEvirt for nested virt
> 
> Virtual VMLOAD/VMSAVE lets an L1 guest execute VMLOAD and VMSAVE
> without intercepts.  Without it, Xen has to map the L1-provided VMCB
> and re-execute each instruction in L0, complicating security-sensitive
> state handling.
> 
> Make VMSAVEvirt a hard requirement for nested SVM.  Advertise it to
> L1, enable it when L1 enables SVM, and remove the nested VMLOAD/VMSAVE
> emulation path.  Any remaining intercept therefore comes from a guest
> which has not enabled SVM and receives #UD.
> 
> Assisted-by: Claude:claude-opus-4-8
> Signed-off-by: Stephen Cheng <stephen.cheng@xxxxxxxxxx>
> ---
>  docs/designs/nested-svm-cpu-features.md |  11 +++
>  xen/arch/x86/cpu-policy.c               |   3 +-
>  xen/arch/x86/hvm/svm/nestedsvm.c        |  24 ++++--
>  xen/arch/x86/hvm/svm/svm.c              | 100 +++---------------------
>  xen/arch/x86/hvm/svm/svm.h              |   2 -
>  5 files changed, 41 insertions(+), 99 deletions(-)
> 
> diff --git a/docs/designs/nested-svm-cpu-features.md 
> b/docs/designs/nested-svm-cpu-features.md
> index ce168e68e1..798480b259 100644
> --- a/docs/designs/nested-svm-cpu-features.md
> +++ b/docs/designs/nested-svm-cpu-features.md
> @@ -109,3 +109,14 @@ leaf 8000000A:edx
>    Using it in L0 reduces the chance that we'll make some sort of error
>    in the decode path.  And if hardware supports it, it's easy enough
>    to provide to the L1.
> +
> +- 15 `VLoadSave` *Virtual VMLOAD/VMSAVE*: Require for L0, provide to L1
> +
> +  Without this feature Xen has to intercept the L1 hypervisor's VMLOAD
> +  and VMSAVE instructions and emulate them by re-executing the real
> +  instruction on a mapped copy of the L1-supplied VMCB.  That path
> +  handles a complex, security-sensitive subset of state (the hidden
> +  segment descriptors for FS/GS/TR/LDTR plus the SYSCALL/SYSENTER
> +  MSRs), so on faithfulness grounds we'd much rather let the hardware
> +  do it.  When present, the instructions execute natively in the guest
> +  without a #VMEXIT, which is both simpler and faster.
> diff --git a/xen/arch/x86/cpu-policy.c b/xen/arch/x86/cpu-policy.c
> index 5273fe0ae4..f151623f5e 100644
> --- a/xen/arch/x86/cpu-policy.c
> +++ b/xen/arch/x86/cpu-policy.c
> @@ -837,7 +837,8 @@ static void __init calculate_hvm_max_policy(void)
>                                 (1u << SVM_FEATURE_LBRV) |
>                                 (1u << SVM_FEATURE_NRIPS) |
>                                 (1u << SVM_FEATURE_PAUSEFILTER) |
> -                               (1u << SVM_FEATURE_DECODEASSISTS));
> +                               (1u << SVM_FEATURE_DECODEASSISTS) |
> +                               (1u << SVM_FEATURE_VLOADSAVE));

Exposing this feature to L1 is separate from requiring it in L0 and IMO should
be a separate commit. If it's exposed to L1, there is a lot more to think
about. E.g. whether L2 should follow L1's setting or L0's for vloadsave_enable
and if it is not enabled what happens if L1 doesn't intercept VMLOAD/VMSAVE.

Also note we discovered previously that contrary to what the APM documents:

If the VMSAVE/VMLOAD intercept bits are clear, and virtual
VMLOAD/VMSAVE is not enabled, then a VMSAVE instruction executed in
guest mode executes as if it were in non-root mode, and operates on host
physical addresses.

I think there is also a separate bug that what Xen advertises doesn't
necessarily control what the guest can use. E.g. previously we didn't advertise
SVM_FEATURE_VLOADSAVE but Xen would always merge in tthe virt_ext settings from
VMCB(L1-L2).

>          /* Enable features which are always emulated. */
>          p->extd.raw[0xa].d |= (1u << SVM_FEATURE_VMCBCLEAN);
>      }
> diff --git a/xen/arch/x86/hvm/svm/nestedsvm.c 
> b/xen/arch/x86/hvm/svm/nestedsvm.c
> index b06124c2c9..794fb6e589 100644
> --- a/xen/arch/x86/hvm/svm/nestedsvm.c
> +++ b/xen/arch/x86/hvm/svm/nestedsvm.c
> @@ -562,7 +562,10 @@ static int nsvm_vmcb_prepare4vmrun(struct vcpu *v, 
> struct cpu_user_regs *regs)
> 
>      /* Keep the host values of the fs, gs, ldtr, tr, kerngsbase,
>       * star, lstar, cstar, sfmask, sysenter_cs, sysenter_esp,
> -     * sysenter_eip. These are handled via VMSAVE/VMLOAD emulation.
> +     * sysenter_eip. These are not transferred by VMRUN/#VMEXIT; they
> +     * are moved directly to/from the L1-provided VMCB by the guest's
> +     * own VMSAVE/VMLOAD, which run natively (VMSAVEvirt is required
> +     * for nested virt).
>       */
> 
>      /* PAT */
> @@ -1097,7 +1100,10 @@ nsvm_vmcb_prepare4vmexit(struct vcpu *v, struct 
> cpu_user_regs *regs)
> 
>      /* Keep the l2 guest values of the fs, gs, ldtr, tr, kerngsbase,
>       * star, lstar, cstar, sfmask, sysenter_cs, sysenter_esp,
> -     * sysenter_eip. These are handled via VMSAVE/VMLOAD emulation.
> +     * sysenter_eip. These are not transferred by VMRUN/#VMEXIT; they
> +     * are moved directly to/from the L1-provided VMCB by the guest's
> +     * own VMSAVE/VMLOAD, which run natively (VMSAVEvirt is required
> +     * for nested virt).
>       */
> 
>      /* CR2 */
> @@ -1547,9 +1553,14 @@ void svm_nested_features_on_efer_update(struct vcpu *v)
>       */
>      if ( nsvm_efer_svm_enabled(v) )
>      {
> +        /*
> +         * VMSAVEvirt is a hard requirement for nested virt (see
> +         * start_nested_svm()), so it is guaranteed present here.
> +         */
> +        ASSERT(cpu_has_svm_vloadsave);
> +

Maybe just personal taste but I think this assert is unnecessary.
cpu_has_svm_vloadsave is constant since boot and also checked at boot so
further checks are unnecessary.

>          if ( !vmcb->virt_ext.fields.vloadsave_enable &&
> -             paging_mode_hap(v->domain) &&
> -             cpu_has_svm_vloadsave )
> +             paging_mode_hap(v->domain) )
>          {
>              vmcb->virt_ext.fields.vloadsave_enable = 1;
>              general2_intercepts  = vmcb_get_general2_intercepts(vmcb);
> @@ -1603,9 +1614,10 @@ void __init start_nested_svm(struct hvm_function_table 
> *hvm_function_table)
>       * docs/designs/nested-svm-cpu-features.md for rationale.
>       */
>      hvm_function_table->caps.nested_virt =
> -        hvm_function_table->caps.hap &&
> +        hvm_function_table->caps.hap &&
>          cpu_has_svm_lbrv &&
>          cpu_has_svm_nrips &&
>          cpu_has_svm_flushbyasid &&
> -        cpu_has_svm_decode;
> +        cpu_has_svm_decode &&
> +        cpu_has_svm_vloadsave;
>  }
> diff --git a/xen/arch/x86/hvm/svm/svm.c b/xen/arch/x86/hvm/svm/svm.c
> index 38c61db1d7..637c29daf7 100644
> --- a/xen/arch/x86/hvm/svm/svm.c
> +++ b/xen/arch/x86/hvm/svm/svm.c
> @@ -2134,95 +2134,17 @@ svm_vmexit_do_vmrun(struct cpu_user_regs *regs,
>      return;
>  }
> 
> -static struct page_info *
> -nsvm_get_nvmcb_page(struct vcpu *v, uint64_t vmcbaddr)
> -{
> -    p2m_type_t p2mt;
> -    struct page_info *page;
> -    struct nestedvcpu *nv = &vcpu_nestedhvm(v);
> -
> -    if ( !nestedsvm_vmcb_map(v, vmcbaddr) )
> -        return NULL;
> -
> -    /* Need to translate L1-GPA to MPA */
> -    page = get_page_from_gfn(v->domain, nv->nv_vvmcxaddr >> PAGE_SHIFT,
> -                             &p2mt, P2M_ALLOC | P2M_UNSHARE);
> -    if ( !page )
> -        return NULL;
> -
> -    if ( !p2m_is_ram(p2mt) || p2m_is_readonly(p2mt) )
> -    {
> -        put_page(page);
> -        return NULL;
> -    }
> -
> -    return  page;
> -}
> -
> -static void
> -svm_vmexit_do_vmload(struct vmcb_struct *vmcb,
> -                     struct cpu_user_regs *regs,
> -                     struct vcpu *v, uint64_t vmcbaddr)
> -{
> -    unsigned int inst_len;
> -    struct page_info *page;
> -
> -    if ( (inst_len = svm_get_insn_len(v, INSTR_VMLOAD)) == 0 )
> -        return;
> -
> -    if ( !nsvm_efer_svm_enabled(v) )
> -    {
> -        hvm_inject_hw_exception(X86_EXC_UD, X86_EVENT_NO_EC);
> -        return;
> -    }
> -
> -    page = nsvm_get_nvmcb_page(v, vmcbaddr);
> -    if ( !page )
> -    {
> -        gdprintk(XENLOG_ERR,
> -            "VMLOAD: mapping failed, injecting #GP\n");
> -        hvm_inject_hw_exception(X86_EXC_GP, 0);
> -        return;
> -    }
> -
> -    svm_vmload_pa(page_to_maddr(page));
> -    put_page(page);
> -
> -    /* State in L1 VMCB is stale now */
> -    v->arch.hvm.svm.vmcb_sync_state = vmcb_needs_vmsave;
> -
> -    __update_guest_eip(regs, inst_len);
> -}
> -
>  static void
> -svm_vmexit_do_vmsave(struct vmcb_struct *vmcb,
> -                     struct cpu_user_regs *regs,
> -                     struct vcpu *v, uint64_t vmcbaddr)
> +svm_vmexit_do_vmload_vmsave(struct vcpu *v)
>  {
> -    unsigned int inst_len;
> -    struct page_info *page;
> -
> -    if ( (inst_len = svm_get_insn_len(v, INSTR_VMSAVE)) == 0 )
> -        return;
> -
> -    if ( !nsvm_efer_svm_enabled(v) )
> -    {
> -        hvm_inject_hw_exception(X86_EXC_UD, X86_EVENT_NO_EC);
> -        return;
> -    }
> -
> -    page = nsvm_get_nvmcb_page(v, vmcbaddr);
> -    if ( !page )
> -    {
> -        gdprintk(XENLOG_ERR,
> -            "VMSAVE: mapping vmcb failed, injecting #GP\n");
> -        hvm_inject_hw_exception(X86_EXC_GP, 0);
> -        return;
> -    }
> -
> -    svm_vmsave_pa(page_to_maddr(page));
> -    put_page(page);
> -    __update_guest_eip(regs, inst_len);
> +    /*
> +     * VMSAVEvirt is required for nested virt, so once a guest enables SVM 
> its
> +     * VMLOAD/VMSAVE execute natively and are no longer intercepted.  An
> +     * intercepted VMLOAD/VMSAVE therefore means the guest has not enabled 
> SVM:
> +     * inject #UD.
> +     */
> +    ASSERT(!nsvm_efer_svm_enabled(v));
> +    hvm_inject_hw_exception(X86_EXC_UD, X86_EVENT_NO_EC);
>  }
> 
>  static int svm_is_erratum_383(struct cpu_user_regs *regs)
> @@ -2950,10 +2872,8 @@ void asmlinkage svm_vmexit_handler(void)
>          svm_vmexit_do_vmrun(regs, v, regs->rax);
>          break;
>      case VMEXIT_VMLOAD:
> -        svm_vmexit_do_vmload(vmcb, regs, v, regs->rax);
> -        break;
>      case VMEXIT_VMSAVE:
> -        svm_vmexit_do_vmsave(vmcb, regs, v, regs->rax);
> +        svm_vmexit_do_vmload_vmsave(v);
>          break;
>      case VMEXIT_STGI:
>          svm_vmexit_do_stgi(regs, v);

I think it would be simpler to just move these case statements up to the
VMEXIT_RDPRU section.

> diff --git a/xen/arch/x86/hvm/svm/svm.h b/xen/arch/x86/hvm/svm/svm.h
> index cfa411ad5a..56a1a4840b 100644
> --- a/xen/arch/x86/hvm/svm/svm.h
> +++ b/xen/arch/x86/hvm/svm/svm.h
> @@ -52,8 +52,6 @@ static inline void svm_invlpga(unsigned long linear, 
> uint32_t asid)
>  #define INSTR_XSETBV     INSTR_ENC(X86EMUL_OPC(0x0f, 0x01), 0321) /* 
> octal-ok */
>  #define INSTR_VMRUN      INSTR_ENC(X86EMUL_OPC(0x0f, 0x01), 0330) /* 
> octal-ok */
>  #define INSTR_VMCALL     INSTR_ENC(X86EMUL_OPC(0x0f, 0x01), 0331) /* 
> octal-ok */
> -#define INSTR_VMLOAD     INSTR_ENC(X86EMUL_OPC(0x0f, 0x01), 0332) /* 
> octal-ok */
> -#define INSTR_VMSAVE     INSTR_ENC(X86EMUL_OPC(0x0f, 0x01), 0333) /* 
> octal-ok */
>  #define INSTR_STGI       INSTR_ENC(X86EMUL_OPC(0x0f, 0x01), 0334) /* 
> octal-ok */
>  #define INSTR_CLGI       INSTR_ENC(X86EMUL_OPC(0x0f, 0x01), 0335) /* 
> octal-ok */
>  #define INSTR_INVLPGA    INSTR_ENC(X86EMUL_OPC(0x0f, 0x01), 0337) /* 
> octal-ok */
> 
> base-commit: 644ea6f84510af27ee1847e981df945a94068479
> --
> 2.49.0

Thanks,
Ross

 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.