| From foo@baz Tue Aug 14 16:14:56 CEST 2018 |
| From: Nicolai Stange <nstange@suse.de> |
| Date: Fri, 27 Jul 2018 13:22:16 +0200 |
| Subject: x86/KVM/VMX: Introduce per-host-cpu analogue of l1tf_flush_l1d |
| |
| From: Nicolai Stange <nstange@suse.de> |
| |
| commit 45b575c00d8e72d69d75dd8c112f044b7b01b069 upstream |
| |
| Part of the L1TF mitigation for vmx includes flushing the L1D cache upon |
| VMENTRY. |
| |
| L1D flushes are costly and two modes of operations are provided to users: |
| "always" and the more selective "conditional" mode. |
| |
| If operating in the latter, the cache would get flushed only if a host side |
| code path considered unconfined had been traversed. "Unconfined" in this |
| context means that it might have pulled in sensitive data like user data |
| or kernel crypto keys. |
| |
| The need for L1D flushes is tracked by means of the per-vcpu flag |
| l1tf_flush_l1d. KVM exit handlers considered unconfined set it. A |
| vmx_l1d_flush() subsequently invoked before the next VMENTER will conduct a |
| L1d flush based on its value and reset that flag again. |
| |
| Currently, interrupts delivered "normally" while in root operation between |
| VMEXIT and VMENTER are not taken into account. Part of the reason is that |
| these don't leave any traces and thus, the vmx code is unable to tell if |
| any such has happened. |
| |
| As proposed by Paolo Bonzini, prepare for tracking all interrupts by |
| introducing a new per-cpu flag, "kvm_cpu_l1tf_flush_l1d". It will be in |
| strong analogy to the per-vcpu ->l1tf_flush_l1d. |
| |
| A later patch will make interrupt handlers set it. |
| |
| For the sake of cache locality, group kvm_cpu_l1tf_flush_l1d into x86' |
| per-cpu irq_cpustat_t as suggested by Peter Zijlstra. |
| |
| Provide the helpers kvm_set_cpu_l1tf_flush_l1d(), |
| kvm_clear_cpu_l1tf_flush_l1d() and kvm_get_cpu_l1tf_flush_l1d(). Make them |
| trivial resp. non-existent for !CONFIG_KVM_INTEL as appropriate. |
| |
| Let vmx_l1d_flush() handle kvm_cpu_l1tf_flush_l1d in the same way as |
| l1tf_flush_l1d. |
| |
| Suggested-by: Paolo Bonzini <pbonzini@redhat.com> |
| Suggested-by: Peter Zijlstra <peterz@infradead.org> |
| Signed-off-by: Nicolai Stange <nstange@suse.de> |
| Signed-off-by: Thomas Gleixner <tglx@linutronix.de> |
| Reviewed-by: Paolo Bonzini <pbonzini@redhat.com> |
| Signed-off-by: David Woodhouse <dwmw@amazon.co.uk> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| arch/x86/include/asm/hardirq.h | 23 +++++++++++++++++++++++ |
| arch/x86/kvm/vmx.c | 17 +++++++++++++---- |
| 2 files changed, 36 insertions(+), 4 deletions(-) |
| |
| --- a/arch/x86/include/asm/hardirq.h |
| +++ b/arch/x86/include/asm/hardirq.h |
| @@ -6,6 +6,9 @@ |
| |
| typedef struct { |
| u16 __softirq_pending; |
| +#if IS_ENABLED(CONFIG_KVM_INTEL) |
| + u8 kvm_cpu_l1tf_flush_l1d; |
| +#endif |
| unsigned int __nmi_count; /* arch dependent */ |
| #ifdef CONFIG_X86_LOCAL_APIC |
| unsigned int apic_timer_irqs; /* arch dependent */ |
| @@ -60,4 +63,24 @@ extern u64 arch_irq_stat_cpu(unsigned in |
| extern u64 arch_irq_stat(void); |
| #define arch_irq_stat arch_irq_stat |
| |
| + |
| +#if IS_ENABLED(CONFIG_KVM_INTEL) |
| +static inline void kvm_set_cpu_l1tf_flush_l1d(void) |
| +{ |
| + __this_cpu_write(irq_stat.kvm_cpu_l1tf_flush_l1d, 1); |
| +} |
| + |
| +static inline void kvm_clear_cpu_l1tf_flush_l1d(void) |
| +{ |
| + __this_cpu_write(irq_stat.kvm_cpu_l1tf_flush_l1d, 0); |
| +} |
| + |
| +static inline bool kvm_get_cpu_l1tf_flush_l1d(void) |
| +{ |
| + return __this_cpu_read(irq_stat.kvm_cpu_l1tf_flush_l1d); |
| +} |
| +#else /* !IS_ENABLED(CONFIG_KVM_INTEL) */ |
| +static inline void kvm_set_cpu_l1tf_flush_l1d(void) { } |
| +#endif /* IS_ENABLED(CONFIG_KVM_INTEL) */ |
| + |
| #endif /* _ASM_X86_HARDIRQ_H */ |
| --- a/arch/x86/kvm/vmx.c |
| +++ b/arch/x86/kvm/vmx.c |
| @@ -8667,14 +8667,23 @@ static void vmx_l1d_flush(struct kvm_vcp |
| * 'always' |
| */ |
| if (static_branch_likely(&vmx_l1d_flush_cond)) { |
| - bool flush_l1d = vcpu->arch.l1tf_flush_l1d; |
| + bool flush_l1d; |
| |
| /* |
| - * Clear the flush bit, it gets set again either from |
| - * vcpu_run() or from one of the unsafe VMEXIT |
| - * handlers. |
| + * Clear the per-vcpu flush bit, it gets set again |
| + * either from vcpu_run() or from one of the unsafe |
| + * VMEXIT handlers. |
| */ |
| + flush_l1d = vcpu->arch.l1tf_flush_l1d; |
| vcpu->arch.l1tf_flush_l1d = false; |
| + |
| + /* |
| + * Clear the per-cpu flush bit, it gets set again from |
| + * the interrupt handlers. |
| + */ |
| + flush_l1d |= kvm_get_cpu_l1tf_flush_l1d(); |
| + kvm_clear_cpu_l1tf_flush_l1d(); |
| + |
| if (!flush_l1d) |
| return; |
| } |