Clear CR4.FRED before CR0.PG when leaving long mode on RSM, and restore
it only after CR0.PG has been set, the same way CR4.PCIDE is already
handled in both directions.

SMI delivery saves all 64 bits of CR4 and RSM restores them so CR4.FRED
survives SMM. But KVM emulates RSM as a series of individual register
writes, and so drives the vCPU through an intermediate state with
CR4.FRED set and EFER.LMA clear. kvm_set_cr4() rejects that combination,
so RSM of a 64-bit FRED guest fails with X86EMUL_UNHANDLEABLE.

Note, SMI delivery needs no such handling, as enter_smm() clears CR4 in
full via the vendor callback rather than kvm_set_cr4().

Signed-off-by: Sohil Mehta <[email protected]>
---
v10:
 - New patch
---
 arch/x86/kvm/smm.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

diff --git a/arch/x86/kvm/smm.c b/arch/x86/kvm/smm.c
index 656a38dad7e7..a504e93fc5f5 100644
--- a/arch/x86/kvm/smm.c
+++ b/arch/x86/kvm/smm.c
@@ -435,10 +435,10 @@ static int rsm_enter_protected_mode(struct kvm_vcpu *vcpu,
 
        /*
         * First enable PAE, long mode needs it before CR0.PG = 1 is set.
-        * Then enable protected mode.  However, PCID cannot be enabled
-        * if EFER.LMA=0, so set it separately.
+        * Then enable protected mode. However, PCID and FRED cannot be
+        * enabled if EFER.LMA=0, so set them separately.
         */
-       bad = kvm_set_cr4(vcpu, cr4 & ~X86_CR4_PCIDE);
+       bad = kvm_set_cr4(vcpu, cr4 & ~(X86_CR4_PCIDE | X86_CR4_FRED));
        if (bad)
                return X86EMUL_UNHANDLEABLE;
 
@@ -446,7 +446,7 @@ static int rsm_enter_protected_mode(struct kvm_vcpu *vcpu,
        if (bad)
                return X86EMUL_UNHANDLEABLE;
 
-       if (cr4 & X86_CR4_PCIDE) {
+       if (cr4 & (X86_CR4_PCIDE | X86_CR4_FRED)) {
                bad = kvm_set_cr4(vcpu, cr4);
                if (bad)
                        return X86EMUL_UNHANDLEABLE;
@@ -599,10 +599,14 @@ int emulator_leave_smm(struct x86_emulate_ctxt *ctxt)
                struct kvm_segment cs_desc;
                unsigned long cr4;
 
-               /* Zero CR4.PCIDE before CR0.PG.  */
+               /*
+                * Zero CR4.PCIDE and CR4.FRED before CR0.PG, as neither can
+                * be set while EFER.LMA is 0, and clearing CR0.PG clears
+                * EFER.LMA.
+                */
                cr4 = kvm_read_cr4(vcpu);
-               if (cr4 & X86_CR4_PCIDE)
-                       kvm_set_cr4(vcpu, cr4 & ~X86_CR4_PCIDE);
+               if (cr4 & (X86_CR4_PCIDE | X86_CR4_FRED))
+                       kvm_set_cr4(vcpu, cr4 & ~(X86_CR4_PCIDE | 
X86_CR4_FRED));
 
                /* A 32-bit code segment is required to clear EFER.LMA.  */
                memset(&cs_desc, 0, sizeof(cs_desc));
-- 
2.43.0


Reply via email to