[PATCH v3 0/3] x86/crash: Fix double NMI shootdown bug

Sean Christopherson posted 3 patches 3 years, 4 months ago
There is a newer version of this series
arch/x86/include/asm/reboot.h  |  1 +
arch/x86/include/asm/virtext.h | 14 +-----
arch/x86/kernel/crash.c        | 16 +-----
arch/x86/kernel/reboot.c       | 89 +++++++++++++++++++++++++---------
4 files changed, 69 insertions(+), 51 deletions(-)
[PATCH v3 0/3] x86/crash: Fix double NMI shootdown bug
Posted by Sean Christopherson 3 years, 4 months ago
Tom,

I Cc'd you this time around because the APM doesn't explicitly state that
GIF is set when EFER.SVME is disabled.  KVM's nSVM emulation does set GIF
in this case, but it's not clear whether or not KVM is making up behavior.
If clearing EFER.SVME doesn't set GIF, then patch 1 needs to be modified
to try STGI before clearing EFER.SVME, e.g. if a crash is initiated from
KVM between CLGI and STGI.  Responding CPUs are "safe" because GIF=0 also
blocks NMIs, but the initiating CPU might leave GIF=0 when jumping into
the new kernel.

Fix a double NMI shootdown bug found and debugged by Guilherme, who did all
the hard work.  NMI shootdown is a one-time thing; the handler leaves NMIs
blocked and enters halt.  At best, a second (or third...) shootdown is an
expensive nop, at worst it can hang the kernel and prevent kexec'ing into
a new kernel, e.g. prior to the hardening of register_nmi_handler(), a
double shootdown resulted in a double list_add(), which is fatal when running
with CONFIG_BUG_ON_DATA_CORRUPTION=y.

With the "right" kexec/kdump configuration, emergency_vmx_disable_all() can
be reached after kdump_nmi_shootdown_cpus() (currently the only two users
of nmi_shootdown_cpus()).

To fix, move the disabling of virtualization into crash_nmi_callback(),
remove emergency_vmx_disable_all()'s callback, and do a shootdown for
emergency_vmx_disable_all() if and only if a shootdown hasn't yet occurred.
The only thing emergency_vmx_disable_all() cares about is disabling VMX/SVM
(obviously), and since I can't envision a use case for an NMI shootdown that
doesn't want to disable virtualization, doing that in the core handler means
emergency_vmx_disable_all() only needs to ensure _a_ shootdown occurs, it
doesn't care when that shootdown happened or what callback may have run.

Patch 2 is a related bug fix found while exploring ideas for patch 1.
Patch 3 is a cleanup to try to prevent future "fixed VMX but not SVM"
style bugs.

v3:
  - Re-collect Guilherme's Tested-by.
  - Tweak comment in patch 1 to reference STGI instead of CLGI.
  - Celebrate this series' half-birthday.

v2:
  - Use a NULL handler and crash_ipi_issued instead of a magic nop
    handler. [tglx]
  - Add comments to call out that modifying the existing handler
    once the NMI is sent may cause explosions.
  - Add a patch to cleanup cpu_emergency_vmxoff().
  - https://lore.kernel.org/all/20220518001647.1291448-1-seanjc@google.com

v1: https://lore.kernel.org/all/20220511234332.3654455-1-seanjc@google.com

Sean Christopherson (3):
  x86/crash: Disable virt in core NMI crash handler to avoid double
    shootdown
  x86/reboot: Disable virtualization in an emergency if SVM is supported
  x86/virt: Fold __cpu_emergency_vmxoff() into its sole caller

 arch/x86/include/asm/reboot.h  |  1 +
 arch/x86/include/asm/virtext.h | 14 +-----
 arch/x86/kernel/crash.c        | 16 +-----
 arch/x86/kernel/reboot.c       | 89 +++++++++++++++++++++++++---------
 4 files changed, 69 insertions(+), 51 deletions(-)


base-commit: dacca1e5e75d7c1297f1334cdc10491dcdd1b2b8
-- 
2.38.1.431.g37b22c650d-goog
Re: [PATCH v3 0/3] x86/crash: Fix double NMI shootdown bug
Posted by Guilherme G. Piccoli 3 years, 4 months ago
On 14/11/2022 20:34, Sean Christopherson wrote:
> [...]
> v3:
>   - Re-collect Guilherme's Tested-by.
>   - Tweak comment in patch 1 to reference STGI instead of CLGI.
>   - Celebrate this series' half-birthday.

Heheh

Thanks a lot for persisting with this Sean, much appreciated! I'm
surprised on how long is taking to get these _fixes_ merged in the
kernel, hence your effort is very valuable =)

Cheers,


Guilherme
Re: [PATCH v3 0/3] x86/crash: Fix double NMI shootdown bug
Posted by Sean Christopherson 3 years, 4 months ago
On Tue, Nov 15, 2022, Guilherme G. Piccoli wrote:
> On 14/11/2022 20:34, Sean Christopherson wrote:
> > [...]
> > v3:
> >   - Re-collect Guilherme's Tested-by.
> >   - Tweak comment in patch 1 to reference STGI instead of CLGI.
> >   - Celebrate this series' half-birthday.
> 
> Heheh
> 
> Thanks a lot for persisting with this Sean, much appreciated! I'm
> surprised on how long is taking to get these _fixes_ merged in the
> kernel, hence your effort is very valuable =)

Well, to be fair, the fixes aren't perfect.  Aside from the GIF thing, patch 2
breaks CONFIG_SMP=n.

I think there's another bug lurking too.  The emergency reboot path doesn't
VMCLEAR VMCSes.  AFAIK, Intel doesn't guarantee the VMCS caches are purged on
INIT, so if the reboot doesn't actually RESET CPUs, the new kernel could observe
memory corruption due to an old VMCS getting written back.

Argh, and I missed sysvec_reboot() + smp_stop_nmi_callback() for SVM support.

And slightly longer term, this entire mess can be cleaned up.  Once KVM's handling
of VMX/SVM initialization sucks less[*], all of the disabling logic can be moved
into KVM callbacks and the kernel can stop speculatively trying to disable VMX/SVM.

I'll send a v4 to fix all of the suspected bugs, and then work on another series to
clean up the callbacks, which will have dependencies on both the kvm_init() rework
and this series.

[*] https://lore.kernel.org/all/20221102231911.3107438-1-seanjc@google.com
Re: [PATCH v3 0/3] x86/crash: Fix double NMI shootdown bug
Posted by Andrew Cooper 3 years, 4 months ago
On 14/11/2022 23:34, Sean Christopherson wrote:
> Tom,
>
> I Cc'd you this time around because the APM doesn't explicitly state that
> GIF is set when EFER.SVME is disabled.  KVM's nSVM emulation does set GIF
> in this case, but it's not clear whether or not KVM is making up behavior.
> If clearing EFER.SVME doesn't set GIF, then patch 1 needs to be modified
> to try STGI before clearing EFER.SVME, e.g. if a crash is initiated from
> KVM between CLGI and STGI.  Responding CPUs are "safe" because GIF=0 also
> blocks NMIs, but the initiating CPU might leave GIF=0 when jumping into
> the new kernel.

GIF exists independently of EFER.SVME.

It is also gets set by the SKINIT instruction, which is why there is an
asymmetry on the #UD conditions of STGI and CLGI.

STGI is also intended to be used by the DLME once critical
initialisation is done, hence why it's dependent on EFER.SVME || SKINIT.

~Andrew
Re: [PATCH v3 0/3] x86/crash: Fix double NMI shootdown bug
Posted by Sean Christopherson 3 years, 4 months ago
On Tue, Nov 15, 2022, Andrew Cooper wrote:
> On 14/11/2022 23:34, Sean Christopherson wrote:
> > Tom,
> >
> > I Cc'd you this time around because the APM doesn't explicitly state that
> > GIF is set when EFER.SVME is disabled.  KVM's nSVM emulation does set GIF
> > in this case, but it's not clear whether or not KVM is making up behavior.
> > If clearing EFER.SVME doesn't set GIF, then patch 1 needs to be modified
> > to try STGI before clearing EFER.SVME, e.g. if a crash is initiated from
> > KVM between CLGI and STGI.  Responding CPUs are "safe" because GIF=0 also
> > blocks NMIs, but the initiating CPU might leave GIF=0 when jumping into
> > the new kernel.
> 
> GIF exists independently of EFER.SVME.
> 
> It is also gets set by the SKINIT instruction, which is why there is an
> asymmetry on the #UD conditions of STGI and CLGI.
> 
> STGI is also intended to be used by the DLME once critical
> initialisation is done, hence why it's dependent on EFER.SVME || SKINIT.

Gah, stupid APM.  The pseudocode states "EFER.SVME || CPUID.SKINIT", but the
description and the comment both say that SVM must be enabled to execute SKINIT,
which made me hope that disabling SVM would reset everything.

  This instruction generates a #UD exception if SVM is not enabled. See “Enabling
  SVM” in AMD64 Architecture Programmer’s Manual Volume 2: System Instructions,
  order# 24593.

...

  IF ((EFER.SVME == 0) && !(CPUID 8000_0001.ECX[SKINIT]) || (!PROTECTED_MODE))
          EXCEPTION [#UD]  // This instruction can only be executed
                           // in protected mode with SVM enabled
                                                     ^^^^^^^^^^^
Re: [PATCH v3 0/3] x86/crash: Fix double NMI shootdown bug
Posted by Andrew Cooper 3 years, 4 months ago
On 15/11/2022 16:56, Sean Christopherson wrote:
> On Tue, Nov 15, 2022, Andrew Cooper wrote:
>> On 14/11/2022 23:34, Sean Christopherson wrote:
>>> Tom,
>>>
>>> I Cc'd you this time around because the APM doesn't explicitly state that
>>> GIF is set when EFER.SVME is disabled.  KVM's nSVM emulation does set GIF
>>> in this case, but it's not clear whether or not KVM is making up behavior.
>>> If clearing EFER.SVME doesn't set GIF, then patch 1 needs to be modified
>>> to try STGI before clearing EFER.SVME, e.g. if a crash is initiated from
>>> KVM between CLGI and STGI.  Responding CPUs are "safe" because GIF=0 also
>>> blocks NMIs, but the initiating CPU might leave GIF=0 when jumping into
>>> the new kernel.
>> GIF exists independently of EFER.SVME.
>>
>> It is also gets set by the SKINIT instruction, which is why there is an
>> asymmetry on the #UD conditions of STGI and CLGI.
>>
>> STGI is also intended to be used by the DLME once critical
>> initialisation is done, hence why it's dependent on EFER.SVME || SKINIT.
> Gah, stupid APM.  The pseudocode states "EFER.SVME || CPUID.SKINIT", but the
> description and the comment both say that SVM must be enabled to execute SKINIT,
> which made me hope that disabling SVM would reset everything.
>
>   This instruction generates a #UD exception if SVM is not enabled. See “Enabling
>   SVM” in AMD64 Architecture Programmer’s Manual Volume 2: System Instructions,
>   order# 24593.
>
> ...
>
>   IF ((EFER.SVME == 0) && !(CPUID 8000_0001.ECX[SKINIT]) || (!PROTECTED_MODE))
>           EXCEPTION [#UD]  // This instruction can only be executed
>                            // in protected mode with SVM enabled
>                                                      ^^^^^^^^^^^

/sigh - and there needs to be one extra set of brackets to remove the
ambiguity in that pseudocode.


I think it's tied up with the complexity of the VMM LOCK feature.  Just
as with Intel and the SMX inside/outside VMX bits in
MSR_FEATURE_CONTROL, there's a requirement stated in the PPR/BKDG that
firmware offers the option to enable or disable SVM, where disable is
actually set the lock field.

VM_CR.SVM_LOCK can only be configured when EFER.SVME is clear, after
which SKINIT (and STGI) for DTRM is still usable, while general SVM isn't.

In this case, the CPU is operating with EFER.SVME=0 and a working(ish) GIF.


I find it curious that both Intel and AMD have VMM+DTRM technology that
overlap (feature wise), appeared at the same time in silicon, *and* have
slightly weird ways of force disabling VMM while keeping DTRM
operational.  Funny how these two features have played out.

~Andrew
Re: [PATCH v3 0/3] x86/crash: Fix double NMI shootdown bug
Posted by Tom Lendacky 3 years, 4 months ago
On 11/15/22 10:56, Sean Christopherson wrote:
> On Tue, Nov 15, 2022, Andrew Cooper wrote:
>> On 14/11/2022 23:34, Sean Christopherson wrote:
>>> Tom,
>>>
>>> I Cc'd you this time around because the APM doesn't explicitly state that
>>> GIF is set when EFER.SVME is disabled.  KVM's nSVM emulation does set GIF
>>> in this case, but it's not clear whether or not KVM is making up behavior.
>>> If clearing EFER.SVME doesn't set GIF, then patch 1 needs to be modified
>>> to try STGI before clearing EFER.SVME, e.g. if a crash is initiated from
>>> KVM between CLGI and STGI.  Responding CPUs are "safe" because GIF=0 also
>>> blocks NMIs, but the initiating CPU might leave GIF=0 when jumping into
>>> the new kernel.
>>
>> GIF exists independently of EFER.SVME.
>>
>> It is also gets set by the SKINIT instruction, which is why there is an
>> asymmetry on the #UD conditions of STGI and CLGI.
>>
>> STGI is also intended to be used by the DLME once critical
>> initialisation is done, hence why it's dependent on EFER.SVME || SKINIT.
> 
> Gah, stupid APM.  The pseudocode states "EFER.SVME || CPUID.SKINIT", but the
> description and the comment both say that SVM must be enabled to execute SKINIT,
> which made me hope that disabling SVM would reset everything.
> 
>    This instruction generates a #UD exception if SVM is not enabled. See “Enabling
>    SVM” in AMD64 Architecture Programmer’s Manual Volume 2: System Instructions,
>    order# 24593.
> 
> ...
> 
>    IF ((EFER.SVME == 0) && !(CPUID 8000_0001.ECX[SKINIT]) || (!PROTECTED_MODE))
>            EXCEPTION [#UD]  // This instruction can only be executed
>                             // in protected mode with SVM enabled
>                                                       ^^^^^^^^^^^

I'll see if I can't get someone to update the psuedo-code comment here.

But, to your original question, as far as I understand, GIF is not 
automatically set if SVME is cleared from EFER.

Thanks,
Tom