x86/its: Use dynamic thunks for indirect branches
authorPeter Zijlstra <peterz@infradead.org>
Mon, 14 Oct 2024 17:05:48 +0000 (10:05 -0700)
committerDave Hansen <dave.hansen@linux.intel.com>
Fri, 9 May 2025 20:36:58 +0000 (13:36 -0700)
ITS mitigation moves the unsafe indirect branches to a safe thunk. This
could degrade the prediction accuracy as the source address of indirect
branches becomes same for different execution paths.

To improve the predictions, and hence the performance, assign a separate
thunk for each indirect callsite. This is also a defense-in-depth measure
to avoid indirect branches aliasing with each other.

As an example, 5000 dynamic thunks would utilize around 16 bits of the
address space, thereby gaining entropy. For a BTB that uses
32 bits for indexing, dynamic thunks could provide better prediction
accuracy over fixed thunks.

Have ITS thunks be variable sized and use EXECMEM_MODULE_TEXT such that
they are both more flexible (got to extend them later) and live in 2M TLBs,
just like kernel code, avoiding undue TLB pressure.

Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Signed-off-by: Pawan Gupta <pawan.kumar.gupta@linux.intel.com>
Signed-off-by: Dave Hansen <dave.hansen@linux.intel.com>
Reviewed-by: Alexandre Chartre <alexandre.chartre@oracle.com>
arch/x86/Kconfig
arch/x86/include/asm/alternative.h
arch/x86/kernel/alternative.c
arch/x86/kernel/module.c
include/linux/execmem.h
include/linux/module.h

index a6bd0590c4371b785035d69ba81630abcc5f891d..a1156a554f6d5afe5112fa9026277b96717110d2 100644 (file)
@@ -2714,6 +2714,7 @@ config MITIGATION_ITS
        bool "Enable Indirect Target Selection mitigation"
        depends on CPU_SUP_INTEL && X86_64
        depends on MITIGATION_RETPOLINE && MITIGATION_RETHUNK
+       select EXECMEM
        default y
        help
          Enable Indirect Target Selection (ITS) mitigation. ITS is a bug in
index 4037c611537c85a1f97f1b6d5baa91fd942c29cf..47948ebbb40969a447c5b04b7f60216153eede5f 100644 (file)
@@ -124,6 +124,16 @@ static __always_inline int x86_call_depth_emit_accounting(u8 **pprog,
 }
 #endif
 
+#ifdef CONFIG_MITIGATION_ITS
+extern void its_init_mod(struct module *mod);
+extern void its_fini_mod(struct module *mod);
+extern void its_free_mod(struct module *mod);
+#else /* CONFIG_MITIGATION_ITS */
+static inline void its_init_mod(struct module *mod) { }
+static inline void its_fini_mod(struct module *mod) { }
+static inline void its_free_mod(struct module *mod) { }
+#endif
+
 #if defined(CONFIG_MITIGATION_RETHUNK) && defined(CONFIG_OBJTOOL)
 extern bool cpu_wants_rethunk(void);
 extern bool cpu_wants_rethunk_at(void *addr);
index 6e0b09b2c97201403bea409e7f2618d4bbe122a8..7a10e3ed5d0b5b986777acf8f5559387affab94d 100644 (file)
@@ -18,6 +18,7 @@
 #include <linux/mmu_context.h>
 #include <linux/bsearch.h>
 #include <linux/sync_core.h>
+#include <linux/execmem.h>
 #include <asm/text-patching.h>
 #include <asm/alternative.h>
 #include <asm/sections.h>
@@ -32,6 +33,7 @@
 #include <asm/asm-prototypes.h>
 #include <asm/cfi.h>
 #include <asm/ibt.h>
+#include <asm/set_memory.h>
 
 int __read_mostly alternatives_patched;
 
@@ -125,6 +127,121 @@ const unsigned char * const x86_nops[ASM_NOP_MAX+1] =
 #endif
 };
 
+#ifdef CONFIG_MITIGATION_ITS
+
+static struct module *its_mod;
+static void *its_page;
+static unsigned int its_offset;
+
+/* Initialize a thunk with the "jmp *reg; int3" instructions. */
+static void *its_init_thunk(void *thunk, int reg)
+{
+       u8 *bytes = thunk;
+       int i = 0;
+
+       if (reg >= 8) {
+               bytes[i++] = 0x41; /* REX.B prefix */
+               reg -= 8;
+       }
+       bytes[i++] = 0xff;
+       bytes[i++] = 0xe0 + reg; /* jmp *reg */
+       bytes[i++] = 0xcc;
+
+       return thunk;
+}
+
+void its_init_mod(struct module *mod)
+{
+       if (!cpu_feature_enabled(X86_FEATURE_INDIRECT_THUNK_ITS))
+               return;
+
+       mutex_lock(&text_mutex);
+       its_mod = mod;
+       its_page = NULL;
+}
+
+void its_fini_mod(struct module *mod)
+{
+       if (!cpu_feature_enabled(X86_FEATURE_INDIRECT_THUNK_ITS))
+               return;
+
+       WARN_ON_ONCE(its_mod != mod);
+
+       its_mod = NULL;
+       its_page = NULL;
+       mutex_unlock(&text_mutex);
+
+       for (int i = 0; i < mod->its_num_pages; i++) {
+               void *page = mod->its_page_array[i];
+               execmem_restore_rox(page, PAGE_SIZE);
+       }
+}
+
+void its_free_mod(struct module *mod)
+{
+       if (!cpu_feature_enabled(X86_FEATURE_INDIRECT_THUNK_ITS))
+               return;
+
+       for (int i = 0; i < mod->its_num_pages; i++) {
+               void *page = mod->its_page_array[i];
+               execmem_free(page);
+       }
+       kfree(mod->its_page_array);
+}
+
+static void *its_alloc(void)
+{
+       void *page __free(execmem) = execmem_alloc(EXECMEM_MODULE_TEXT, PAGE_SIZE);
+
+       if (!page)
+               return NULL;
+
+       if (its_mod) {
+               void *tmp = krealloc(its_mod->its_page_array,
+                                    (its_mod->its_num_pages+1) * sizeof(void *),
+                                    GFP_KERNEL);
+               if (!tmp)
+                       return NULL;
+
+               its_mod->its_page_array = tmp;
+               its_mod->its_page_array[its_mod->its_num_pages++] = page;
+
+               execmem_make_temp_rw(page, PAGE_SIZE);
+       }
+
+       return no_free_ptr(page);
+}
+
+static void *its_allocate_thunk(int reg)
+{
+       int size = 3 + (reg / 8);
+       void *thunk;
+
+       if (!its_page || (its_offset + size - 1) >= PAGE_SIZE) {
+               its_page = its_alloc();
+               if (!its_page) {
+                       pr_err("ITS page allocation failed\n");
+                       return NULL;
+               }
+               memset(its_page, INT3_INSN_OPCODE, PAGE_SIZE);
+               its_offset = 32;
+       }
+
+       /*
+        * If the indirect branch instruction will be in the lower half
+        * of a cacheline, then update the offset to reach the upper half.
+        */
+       if ((its_offset + size - 1) % 64 < 32)
+               its_offset = ((its_offset - 1) | 0x3F) + 33;
+
+       thunk = its_page + its_offset;
+       its_offset += size;
+
+       return its_init_thunk(thunk, reg);
+}
+
+#endif
+
 /*
  * Nomenclature for variable names to simplify and clarify this code and ease
  * any potential staring at it:
@@ -637,9 +754,13 @@ static int emit_call_track_retpoline(void *addr, struct insn *insn, int reg, u8
 #ifdef CONFIG_MITIGATION_ITS
 static int emit_its_trampoline(void *addr, struct insn *insn, int reg, u8 *bytes)
 {
-       return __emit_trampoline(addr, insn, bytes,
-                                __x86_indirect_its_thunk_array[reg],
-                                __x86_indirect_its_thunk_array[reg]);
+       u8 *thunk = __x86_indirect_its_thunk_array[reg];
+       u8 *tmp = its_allocate_thunk(reg);
+
+       if (tmp)
+               thunk = tmp;
+
+       return __emit_trampoline(addr, insn, bytes, thunk, thunk);
 }
 
 /* Check if an indirect branch is at ITS-unsafe address */
index a7998f35170175cd75d17a1f3a5fc9d3761e56ba..ff07558b7ebc651abe7fa27c88345441ac0c9733 100644 (file)
@@ -266,6 +266,8 @@ int module_finalize(const Elf_Ehdr *hdr,
                        ibt_endbr = s;
        }
 
+       its_init_mod(me);
+
        if (retpolines || cfi) {
                void *rseg = NULL, *cseg = NULL;
                unsigned int rsize = 0, csize = 0;
@@ -286,6 +288,9 @@ int module_finalize(const Elf_Ehdr *hdr,
                void *rseg = (void *)retpolines->sh_addr;
                apply_retpolines(rseg, rseg + retpolines->sh_size);
        }
+
+       its_fini_mod(me);
+
        if (returns) {
                void *rseg = (void *)returns->sh_addr;
                apply_returns(rseg, rseg + returns->sh_size);
@@ -326,4 +331,5 @@ int module_finalize(const Elf_Ehdr *hdr,
 void module_arch_cleanup(struct module *mod)
 {
        alternatives_smp_module_del(mod);
+       its_free_mod(mod);
 }
index 089c71cc8ddf178ae0e91646311a4fe5f46d86cc..ca42d5e46ccc6b7dd912d5a6da004b8b24420bd3 100644 (file)
@@ -4,6 +4,7 @@
 
 #include <linux/types.h>
 #include <linux/moduleloader.h>
+#include <linux/cleanup.h>
 
 #if (defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)) && \
                !defined(CONFIG_KASAN_VMALLOC)
@@ -176,6 +177,8 @@ void *execmem_alloc(enum execmem_type type, size_t size);
  */
 void execmem_free(void *ptr);
 
+DEFINE_FREE(execmem, void *, if (_T) execmem_free(_T));
+
 #ifdef CONFIG_MMU
 /**
  * execmem_vmap - create virtual mapping for EXECMEM_MODULE_DATA memory
index b3329110d6686f315ae35335dced45e7eae6a130..8050f77c3b64f8512afcb7ccde889a922ae89ab7 100644 (file)
@@ -586,6 +586,11 @@ struct module {
        atomic_t refcnt;
 #endif
 
+#ifdef CONFIG_MITIGATION_ITS
+       int its_num_pages;
+       void **its_page_array;
+#endif
+
 #ifdef CONFIG_CONSTRUCTORS
        /* Constructor functions. */
        ctor_fn_t *ctors;