]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
KVM: SVM: Virtualize and advertise support for ERAPS
authorAmit Shah <amit.shah@amd.com>
Fri, 7 Nov 2025 09:32:39 +0000 (10:32 +0100)
committerSean Christopherson <seanjc@google.com>
Thu, 8 Jan 2026 20:12:12 +0000 (12:12 -0800)
AMD CPUs with the Enhanced Return Address Predictor Security (ERAPS)
feature (available on Zen5+) obviate the need for FILL_RETURN_BUFFER
sequences right after VMEXITs.  ERAPS adds guest/host tags to entries in
the RSB (a.k.a. RAP).  This helps with speculation protection across the
VM boundary, and it also preserves host and guest entries in the RSB that
can improve software performance (which would otherwise be flushed due to
the FILL_RETURN_BUFFER sequences).

Importantly, ERAPS also improves cross-domain security by clearing the RAP
in certain situations.  Specifically, the RAP is cleared in response to
actions that are typically tied to software context switching between
tasks.  Per the APM:

  The ERAPS feature eliminates the need to execute CALL instructions to
  clear the return address predictor in most cases. On processors that
  support ERAPS, return addresses from CALL instructions executed in host
  mode are not used in guest mode, and vice versa. Additionally, the
  return address predictor is cleared in all cases when the TLB is
  implicitly invalidated and in the following cases:

  • MOV CR3 instruction
  • INVPCID other than single address invalidation (operation type 0)

ERAPS also allows CPUs to extends the size of the RSB/RAP from the older
standard (of 32 entries) to a new size, enumerated in CPUID leaf
0x80000021:EBX bits 23:16 (64 entries in Zen5 CPUs).

In hardware, ERAPS is always-on, when running in host context, the CPU
uses the full RSB/RAP size without any software changes necessary.
However, when running in guest context, the CPU utilizes the full size of
the RSB/RAP if and only if the new ALLOW_LARGER_RAP flag is set in the
VMCB; if the flag is not set, the CPU limits itself to the historical size
of 32 entires.

Requiring software to opt-in for guest usage of RAPs larger than 32 entries
allows hypervisors, i.e. KVM, to emulate the aforementioned conditions in
which the RAP is cleared as well as the guest/host split.  E.g. if the CPU
unconditionally used the full RAP for guests, failure to clear the RAP on
transitions between L1 or L2, or on emulated guest TLB flushes, would
expose the guest to RAP-based attacks as a guest without support for ERAPS
wouldn't know that its FILL_RETURN_BUFFER sequence is insufficient.

Address the ~two broad categories of ERAPS emulation, and advertise
ERAPS support to userspace, along with the RAP size enumerated in CPUID.

1. Architectural RAP clearing: as above, CPUs with ERAPS clear RAP entries
   on several conditions, including CR3 updates.  To handle scenarios
   where a relevant operation is handled in common code (emulation of
   INVPCID and to a lesser extent MOV CR3), piggyback VCPU_EXREG_CR3 and
   create an alias, VCPU_EXREG_ERAPS.  SVM doesn't utilize CR3 dirty
   tracking, and so for all intents and purposes VCPU_EXREG_CR3 is unused.
   Aliasing VCPU_EXREG_ERAPS ensures that any flow that writes CR3 will
   also clear the guest's RAP, and allows common x86 to mark ERAPS vCPUs
   as needing a RAP clear without having to add a new request (or other
   mechanism).

2. Nested guests: the ERAPS feature adds host/guest tagging to entries
   in the RSB, but does not distinguish between the guest ASIDs.  To
   prevent the case of an L2 guest poisoning the RSB to attack the L1
   guest, the CPU exposes a new VMCB bit (CLEAR_RAP).  The next
   VMRUN with a VMCB that has this bit set causes the CPU to flush the
   RSB before entering the guest context.  Set the bit in VMCB01 after a
   nested #VMEXIT to ensure the next time the L1 guest runs, its RSB
   contents aren't polluted by the L2's contents.  Similarly, before
   entry into a nested guest, set the bit for VMCB02, so that the L1
   guest's RSB contents are not leaked/used in the L2 context.

Enable ALLOW_LARGER_RAP (and emulate RAP clears) if and only if ERAPS is
exposed to the guest.  Enabling ALLOW_LARGER_RAP unconditionally wouldn't
cause any functional issues, but ignoring userspace's (and L1's) desires
would put KVM into a grey area, which is especially undesirable due to the
potential security implications.  E.g. if a use case wants to have L1 do
manual RAP clearing even when ERAPS is present in hardware, enabling
ALLOW_LARGER_RAP could result in L1 leaving stale entries in the RAP.

ERAPS is documented in AMD APM Vol 2 (Pub 24593), in revisions 3.43 and
later.

Signed-off-by: Amit Shah <amit.shah@amd.com>
Co-developed-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
Reviewed-by: Amit Shah <amit.shah@amd.com>
Link: https://patch.msgid.link/aR913X8EqO6meCqa@google.com
arch/x86/include/asm/cpufeatures.h
arch/x86/include/asm/kvm_host.h
arch/x86/include/asm/svm.h
arch/x86/kvm/cpuid.c
arch/x86/kvm/svm/nested.c
arch/x86/kvm/svm/svm.c
arch/x86/kvm/svm/svm.h
arch/x86/kvm/x86.c

index c3b53beb130078cabecd4d43b98dac8af921f087..81f7b3b919867780773df1f814001d50b3515b15 100644 (file)
 #define X86_FEATURE_GP_ON_USER_CPUID   (20*32+17) /* User CPUID faulting */
 
 #define X86_FEATURE_PREFETCHI          (20*32+20) /* Prefetch Data/Instruction to Cache Level */
+#define X86_FEATURE_ERAPS              (20*32+24) /* Enhanced Return Address Predictor Security */
 #define X86_FEATURE_SBPB               (20*32+27) /* Selective Branch Prediction Barrier */
 #define X86_FEATURE_IBPB_BRTYPE                (20*32+28) /* MSR_PRED_CMD[IBPB] flushes all branch type predictions */
 #define X86_FEATURE_SRSO_NO            (20*32+29) /* CPU is not affected by SRSO */
index 5a3bfa293e8b1acfa30ee44cc5e42a11486424be..0353d8b6988c83dedad1a361c5e7a15baa0b6ff7 100644 (file)
@@ -195,7 +195,15 @@ enum kvm_reg {
 
        VCPU_EXREG_PDPTR = NR_VCPU_REGS,
        VCPU_EXREG_CR0,
+       /*
+        * Alias AMD's ERAPS (not a real register) to CR3 so that common code
+        * can trigger emulation of the RAP (Return Address Predictor) with
+        * minimal support required in common code.  Piggyback CR3 as the RAP
+        * is cleared on writes to CR3, i.e. marking CR3 dirty will naturally
+        * mark ERAPS dirty as well.
+        */
        VCPU_EXREG_CR3,
+       VCPU_EXREG_ERAPS = VCPU_EXREG_CR3,
        VCPU_EXREG_CR4,
        VCPU_EXREG_RFLAGS,
        VCPU_EXREG_SEGMENTS,
index 56aa99503dc4fd9b3fd183b06e80c372aa7d91b2..50ece197c98a40a05bb57398312ce29fd5cc66f4 100644 (file)
@@ -131,7 +131,8 @@ struct __attribute__ ((__packed__)) vmcb_control_area {
        u64 tsc_offset;
        u32 asid;
        u8 tlb_ctl;
-       u8 reserved_2[3];
+       u8 erap_ctl;
+       u8 reserved_2[2];
        u32 int_ctl;
        u32 int_vector;
        u32 int_state;
@@ -182,6 +183,9 @@ struct __attribute__ ((__packed__)) vmcb_control_area {
 #define TLB_CONTROL_FLUSH_ASID 3
 #define TLB_CONTROL_FLUSH_ASID_LOCAL 7
 
+#define ERAP_CONTROL_ALLOW_LARGER_RAP BIT(0)
+#define ERAP_CONTROL_CLEAR_RAP BIT(1)
+
 #define V_TPR_MASK 0x0f
 
 #define V_IRQ_SHIFT 8
index 88a5426674a10bd689c1788b0829f3587e3ca1c5..c590a5bd3196bda35b851eed75549c08c055ad2b 100644 (file)
@@ -1223,6 +1223,7 @@ void kvm_set_cpu_caps(void)
                /* PrefetchCtlMsr */
                /* GpOnUserCpuid */
                /* EPSF */
+               F(ERAPS),
                SYNTHESIZED_F(SBPB),
                SYNTHESIZED_F(IBPB_BRTYPE),
                SYNTHESIZED_F(SRSO_NO),
@@ -1803,8 +1804,14 @@ static inline int __do_cpuid_func(struct kvm_cpuid_array *array, u32 function)
                entry->eax = entry->ebx = entry->ecx = entry->edx = 0;
                break;
        case 0x80000021:
-               entry->ebx = entry->edx = 0;
+               entry->edx = 0;
                cpuid_entry_override(entry, CPUID_8000_0021_EAX);
+
+               if (kvm_cpu_cap_has(X86_FEATURE_ERAPS))
+                       entry->ebx &= GENMASK(23, 16);
+               else
+                       entry->ebx = 0;
+
                cpuid_entry_override(entry, CPUID_8000_0021_ECX);
                break;
        /* AMD Extended Performance Monitoring and Debug */
index aa1bea134aced589551ac53cc87fc0dd0c666858..5a1e1164c197be1631df2bb1e652bf850c2e6d9d 100644 (file)
@@ -436,6 +436,7 @@ void __nested_copy_vmcb_control_to_cache(struct kvm_vcpu *vcpu,
        to->msrpm_base_pa       = from->msrpm_base_pa;
        to->tsc_offset          = from->tsc_offset;
        to->tlb_ctl             = from->tlb_ctl;
+       to->erap_ctl            = from->erap_ctl;
        to->int_ctl             = from->int_ctl;
        to->int_vector          = from->int_vector;
        to->int_state           = from->int_state;
@@ -885,6 +886,19 @@ static void nested_vmcb02_prepare_control(struct vcpu_svm *svm,
                }
        }
 
+       /*
+        * Take ALLOW_LARGER_RAP from vmcb12 even though it should be safe to
+        * let L2 use a larger RAP since KVM will emulate the necessary clears,
+        * as it's possible L1 deliberately wants to restrict L2 to the legacy
+        * RAP size.  Unconditionally clear the RAP on nested VMRUN, as KVM is
+        * responsible for emulating the host vs. guest tags (L1 is the "host",
+        * L2 is the "guest").
+        */
+       if (guest_cpu_cap_has(vcpu, X86_FEATURE_ERAPS))
+               vmcb02->control.erap_ctl = (svm->nested.ctl.erap_ctl &
+                                           ERAP_CONTROL_ALLOW_LARGER_RAP) |
+                                          ERAP_CONTROL_CLEAR_RAP;
+
        /*
         * Merge guest and host intercepts - must be called with vcpu in
         * guest-mode to take effect.
@@ -1180,6 +1194,9 @@ int nested_svm_vmexit(struct vcpu_svm *svm)
 
        kvm_nested_vmexit_handle_ibrs(vcpu);
 
+       if (guest_cpu_cap_has(vcpu, X86_FEATURE_ERAPS))
+               vmcb01->control.erap_ctl |= ERAP_CONTROL_CLEAR_RAP;
+
        svm_switch_vmcb(svm, &svm->vmcb01);
 
        /*
@@ -1686,6 +1703,7 @@ static void nested_copy_vmcb_cache_to_control(struct vmcb_control_area *dst,
        dst->tsc_offset           = from->tsc_offset;
        dst->asid                 = from->asid;
        dst->tlb_ctl              = from->tlb_ctl;
+       dst->erap_ctl             = from->erap_ctl;
        dst->int_ctl              = from->int_ctl;
        dst->int_vector           = from->int_vector;
        dst->int_state            = from->int_state;
index d1ff23e02ecd90d3de9a0eaae61fd45306946751..34c8a94b1b8133412a199266aa4869b798202246 100644 (file)
@@ -1141,6 +1141,9 @@ static void init_vmcb(struct kvm_vcpu *vcpu, bool init_event)
                svm_clr_intercept(svm, INTERCEPT_PAUSE);
        }
 
+       if (guest_cpu_cap_has(vcpu, X86_FEATURE_ERAPS))
+               svm->vmcb->control.erap_ctl |= ERAP_CONTROL_ALLOW_LARGER_RAP;
+
        if (kvm_vcpu_apicv_active(vcpu))
                avic_init_vmcb(svm, vmcb);
 
@@ -3293,6 +3296,7 @@ static void dump_vmcb(struct kvm_vcpu *vcpu)
        pr_err("%-20s%016llx\n", "tsc_offset:", control->tsc_offset);
        pr_err("%-20s%d\n", "asid:", control->asid);
        pr_err("%-20s%d\n", "tlb_ctl:", control->tlb_ctl);
+       pr_err("%-20s%d\n", "erap_ctl:", control->erap_ctl);
        pr_err("%-20s%08x\n", "int_ctl:", control->int_ctl);
        pr_err("%-20s%08x\n", "int_vector:", control->int_vector);
        pr_err("%-20s%08x\n", "int_state:", control->int_state);
@@ -4004,6 +4008,13 @@ static void svm_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t gva)
        invlpga(gva, svm->vmcb->control.asid);
 }
 
+static void svm_flush_tlb_guest(struct kvm_vcpu *vcpu)
+{
+       kvm_register_mark_dirty(vcpu, VCPU_EXREG_ERAPS);
+
+       svm_flush_tlb_asid(vcpu);
+}
+
 static inline void sync_cr8_to_lapic(struct kvm_vcpu *vcpu)
 {
        struct vcpu_svm *svm = to_svm(vcpu);
@@ -4262,6 +4273,10 @@ static __no_kcsan fastpath_t svm_vcpu_run(struct kvm_vcpu *vcpu, u64 run_flags)
        }
        svm->vmcb->save.cr2 = vcpu->arch.cr2;
 
+       if (guest_cpu_cap_has(vcpu, X86_FEATURE_ERAPS) &&
+           kvm_register_is_dirty(vcpu, VCPU_EXREG_ERAPS))
+               svm->vmcb->control.erap_ctl |= ERAP_CONTROL_CLEAR_RAP;
+
        svm_hv_update_vp_id(svm->vmcb, vcpu);
 
        /*
@@ -4339,6 +4354,14 @@ static __no_kcsan fastpath_t svm_vcpu_run(struct kvm_vcpu *vcpu, u64 run_flags)
        }
 
        svm->vmcb->control.tlb_ctl = TLB_CONTROL_DO_NOTHING;
+
+       /*
+        * Unconditionally mask off the CLEAR_RAP bit, the AND is just as cheap
+        * as the TEST+Jcc to avoid it.
+        */
+       if (cpu_feature_enabled(X86_FEATURE_ERAPS))
+               svm->vmcb->control.erap_ctl &= ~ERAP_CONTROL_CLEAR_RAP;
+
        vmcb_mark_all_clean(svm->vmcb);
 
        /* if exit due to PF check for async PF */
@@ -5094,7 +5117,7 @@ struct kvm_x86_ops svm_x86_ops __initdata = {
        .flush_tlb_all = svm_flush_tlb_all,
        .flush_tlb_current = svm_flush_tlb_current,
        .flush_tlb_gva = svm_flush_tlb_gva,
-       .flush_tlb_guest = svm_flush_tlb_asid,
+       .flush_tlb_guest = svm_flush_tlb_guest,
 
        .vcpu_pre_run = svm_vcpu_pre_run,
        .vcpu_run = svm_vcpu_run,
index 806e68ba821b4f3eb7904f2437a9d750c3df7ec5..7d28a739865fc8b980f553b7b0eea7b185911b9d 100644 (file)
@@ -156,6 +156,7 @@ struct vmcb_ctrl_area_cached {
        u64 tsc_offset;
        u32 asid;
        u8 tlb_ctl;
+       u8 erap_ctl;
        u32 int_ctl;
        u32 int_vector;
        u32 int_state;
index ff8812f3a1293e6e4f0d39a82ee32bfe086b55e6..e013392fe20cd9c2f9857b18bd7fef16b8b9aede 100644 (file)
@@ -14130,6 +14130,13 @@ int kvm_handle_invpcid(struct kvm_vcpu *vcpu, unsigned long type, gva_t gva)
                        return 1;
                }
 
+               /*
+                * When ERAPS is supported, invalidating a specific PCID clears
+                * the RAP (Return Address Predicator).
+                */
+               if (guest_cpu_cap_has(vcpu, X86_FEATURE_ERAPS))
+                       kvm_register_is_dirty(vcpu, VCPU_EXREG_ERAPS);
+
                kvm_invalidate_pcid(vcpu, operand.pcid);
                return kvm_skip_emulated_instruction(vcpu);
 
@@ -14143,6 +14150,11 @@ int kvm_handle_invpcid(struct kvm_vcpu *vcpu, unsigned long type, gva_t gva)
 
                fallthrough;
        case INVPCID_TYPE_ALL_INCL_GLOBAL:
+               /*
+                * Don't bother marking VCPU_EXREG_ERAPS dirty, SVM will take
+                * care of doing so when emulating the full guest TLB flush
+                * (the RAP is cleared on all implicit TLB flushes).
+                */
                kvm_make_request(KVM_REQ_TLB_FLUSH_GUEST, vcpu);
                return kvm_skip_emulated_instruction(vcpu);