[PATCH RFC v2 27/29] mm: asi: Add some mitigations on address space transitions

Brendan Jackman
2025-01-10T18:47:22Z

Here we ASI actually starts becoming a real exploit mitigation,

On CPUs with L1TF, flush L1D when the ASI data taints say so.

On all CPUs, do some general branch predictor clearing
whenever the control taints say so.

This policy is very much just a starting point for discussion.
Primarily it's a vague gesture at the fact that there is leeway
in how ASI is used: it can be used to target CPU-specific issues (as
is the case for L1TF here), or it can be used as a fairly broad
mitigation (asi_maybe_flush_control() mitigates several known
Spectre-style attacks and very likely also some unknown ones).

Signed-off-by: Brendan Jackman <jackmanb@google.com>
---
arch/x86/include/asm/nospec-branch.h | 2 ++
arch/x86/kvm/vmx/vmx.c | 1 +
arch/x86/lib/l1tf.c | 2 ++
arch/x86/lib/retpoline.S | 10 ++++++++++
arch/x86/mm/asi.c | 29 +++++++++++++++++++++--------
5 files changed, 36 insertions(+), 8 deletions(-)

diff --git a/arch/x86/include/asm/nospec-branch.h b/arch/x86/include/asm/nospec-branch.h
index 96b410b1d4e841eb02f53a4691ee794ceee4ad2c..4582fb1fb42f6fd226534012d969ed13085e943a 100644
--- a/arch/x86/include/asm/nospec-branch.h
+++ b/arch/x86/include/asm/nospec-branch.h
@@ -614,6 +614,8 @@ static __always_inline void mds_idle_clear_cpu_buffers(void)
mds_clear_cpu_buffers();
}

+extern void fill_return_buffer(void);
+
#endif /* __ASSEMBLY__ */

#endif /* _ASM_X86_NOSPEC_BRANCH_H_ */
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index b1a02f27b3abce0ef6ac448b66bef2c653a52eef..a532783caaea97291cd92a2e2cac617f74f76c7e 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -6635,6 +6635,7 @@ int vmx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t exit_fastpath)
return ret;
}

+/* Must be reentrant, for use by vmx_post_asi_enter. */
static noinstr void vmx_l1d_flush(struct kvm_vcpu *vcpu)
{
/*
diff --git a/arch/x86/lib/l1tf.c b/arch/x86/lib/l1tf.c
index c474f18ae331c8dfa7a029c457dd3cf75bebf808..ffe1c3d0ef43ff8f1781f2e446aed041f4ce3179 100644
--- a/arch/x86/lib/l1tf.c
+++ b/arch/x86/lib/l1tf.c
@@ -46,6 +46,8 @@ EXPORT_SYMBOL(l1tf_flush_setup);
* - may or may not work on other CPUs.
*
* Don't call unless l1tf_flush_setup() has returned successfully.
+ *
+ * Must be reentrant, for use by ASI.
*/
noinstr void l1tf_flush(void)
{
diff --git a/arch/x86/lib/retpoline.S b/arch/x86/lib/retpoline.S
index 391059b2c6fbc4a571f0582c7c4654147a930cef..6d126fff6bf839889086fe21464d8af07316d7e5 100644
--- a/arch/x86/lib/retpoline.S
+++ b/arch/x86/lib/retpoline.S
@@ -396,3 +396,13 @@ SYM_CODE_END(__x86_return_thunk)
EXPORT_SYMBOL(__x86_return_thunk)

#endif /* CONFIG_MITIGATION_RETHUNK */
+
+.pushsection .noinstr.text, "ax"
+SYM_CODE_START(fill_return_buffer)
+ UNWIND_HINT_FUNC
+ ENDBR
+ __FILL_RETURN_BUFFER(%_ASM_AX,RSB_CLEAR_LOOPS)
+ RET
+SYM_CODE_END(fill_return_buffer)
+__EXPORT_THUNK(fill_return_buffer)
+.popsection
diff --git a/arch/x86/mm/asi.c b/arch/x86/mm/asi.c
index 1e9dc568e79e8686a4dbf47f765f2c2535d025ec..f10f6614b26148e5ba423d8a44f640674573ee40 100644
--- a/arch/x86/mm/asi.c
+++ b/arch/x86/mm/asi.c
@@ -10,6 +10,7 @@

#include <asm/cmdline.h>
#include <asm/cpufeature.h>
+#include <asm/l1tf.h>
#include <asm/page.h>
#include <asm/pgalloc.h>
#include <asm/mmu_context.h>
@@ -38,6 +39,8 @@ struct asi __asi_global_nonsensitive = {
.mm = &init_mm,
};

+static bool do_l1tf_flush __ro_after_init;
+
static inline bool asi_class_id_valid(enum asi_class_id class_id)
{
return class_id >= 0 && class_id < ASI_MAX_NUM_CLASSES;
@@ -361,6 +364,15 @@ static int __init asi_global_init(void)
asi_clone_pgd(asi_global_nonsensitive_pgd, init_mm.pgd,
VMEMMAP_START + (1UL << PGDIR_SHIFT));

+ if (boot_cpu_has_bug(X86_BUG_L1TF)) {
+ int err = l1tf_flush_setup();
+
+ if (err)
+ pr_warn("Failed to setup L1TF flushing for ASI (%pe)", ERR_PTR(err));
+ else
+ do_l1tf_flush = true;
+ }
+
#ifdef CONFIG_PM_SLEEP
register_syscore_ops(&asi_syscore_ops);
#endif
@@ -512,10 +524,12 @@ static __always_inline void maybe_flush_control(struct asi *next_asi)
if (!taints)
return;

- /*
- * This is where we'll do the actual dirty work of clearing uarch state.
- * For now we just pretend, clear the taints.
- */
+ /* Clear normal indirect branch predictions, if we haven't */
+ if (cpu_feature_enabled(X86_FEATURE_IBPB))
+ __wrmsr(MSR_IA32_PRED_CMD, PRED_CMD_IBPB, 0);
+
+ fill_return_buffer();
+
this_cpu_and(asi_taints, ~ASI_TAINTS_CONTROL_MASK);
}

@@ -536,10 +550,9 @@ static __always_inline void maybe_flush_data(struct asi *next_asi)
if (!taints)
return;

- /*
- * This is where we'll do the actual dirty work of clearing uarch state.
- * For now we just pretend, clear the taints.
- */
+ if (do_l1tf_flush)
+ l1tf_flush();
+
this_cpu_and(asi_taints, ~ASI_TAINTS_DATA_MASK);
}

--
2.47.1.613.gc27f4b7a9f-goog