Skip to content

Commit 872df34

Browse files
Peter Zijlstrahansendc
authored andcommitted
x86/its: Use dynamic thunks for indirect branches
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) <[email protected]> Signed-off-by: Pawan Gupta <[email protected]> Signed-off-by: Dave Hansen <[email protected]> Reviewed-by: Alexandre Chartre <[email protected]>
1 parent ebebe30 commit 872df34

File tree

6 files changed

+149
-3
lines changed

6 files changed

+149
-3
lines changed

arch/x86/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2714,6 +2714,7 @@ config MITIGATION_ITS
27142714
bool "Enable Indirect Target Selection mitigation"
27152715
depends on CPU_SUP_INTEL && X86_64
27162716
depends on MITIGATION_RETPOLINE && MITIGATION_RETHUNK
2717+
select EXECMEM
27172718
default y
27182719
help
27192720
Enable Indirect Target Selection (ITS) mitigation. ITS is a bug in

arch/x86/include/asm/alternative.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,16 @@ static __always_inline int x86_call_depth_emit_accounting(u8 **pprog,
124124
}
125125
#endif
126126

127+
#ifdef CONFIG_MITIGATION_ITS
128+
extern void its_init_mod(struct module *mod);
129+
extern void its_fini_mod(struct module *mod);
130+
extern void its_free_mod(struct module *mod);
131+
#else /* CONFIG_MITIGATION_ITS */
132+
static inline void its_init_mod(struct module *mod) { }
133+
static inline void its_fini_mod(struct module *mod) { }
134+
static inline void its_free_mod(struct module *mod) { }
135+
#endif
136+
127137
#if defined(CONFIG_MITIGATION_RETHUNK) && defined(CONFIG_OBJTOOL)
128138
extern bool cpu_wants_rethunk(void);
129139
extern bool cpu_wants_rethunk_at(void *addr);

arch/x86/kernel/alternative.c

Lines changed: 124 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <linux/mmu_context.h>
1919
#include <linux/bsearch.h>
2020
#include <linux/sync_core.h>
21+
#include <linux/execmem.h>
2122
#include <asm/text-patching.h>
2223
#include <asm/alternative.h>
2324
#include <asm/sections.h>
@@ -32,6 +33,7 @@
3233
#include <asm/asm-prototypes.h>
3334
#include <asm/cfi.h>
3435
#include <asm/ibt.h>
36+
#include <asm/set_memory.h>
3537

3638
int __read_mostly alternatives_patched;
3739

@@ -125,6 +127,121 @@ const unsigned char * const x86_nops[ASM_NOP_MAX+1] =
125127
#endif
126128
};
127129

130+
#ifdef CONFIG_MITIGATION_ITS
131+
132+
static struct module *its_mod;
133+
static void *its_page;
134+
static unsigned int its_offset;
135+
136+
/* Initialize a thunk with the "jmp *reg; int3" instructions. */
137+
static void *its_init_thunk(void *thunk, int reg)
138+
{
139+
u8 *bytes = thunk;
140+
int i = 0;
141+
142+
if (reg >= 8) {
143+
bytes[i++] = 0x41; /* REX.B prefix */
144+
reg -= 8;
145+
}
146+
bytes[i++] = 0xff;
147+
bytes[i++] = 0xe0 + reg; /* jmp *reg */
148+
bytes[i++] = 0xcc;
149+
150+
return thunk;
151+
}
152+
153+
void its_init_mod(struct module *mod)
154+
{
155+
if (!cpu_feature_enabled(X86_FEATURE_INDIRECT_THUNK_ITS))
156+
return;
157+
158+
mutex_lock(&text_mutex);
159+
its_mod = mod;
160+
its_page = NULL;
161+
}
162+
163+
void its_fini_mod(struct module *mod)
164+
{
165+
if (!cpu_feature_enabled(X86_FEATURE_INDIRECT_THUNK_ITS))
166+
return;
167+
168+
WARN_ON_ONCE(its_mod != mod);
169+
170+
its_mod = NULL;
171+
its_page = NULL;
172+
mutex_unlock(&text_mutex);
173+
174+
for (int i = 0; i < mod->its_num_pages; i++) {
175+
void *page = mod->its_page_array[i];
176+
execmem_restore_rox(page, PAGE_SIZE);
177+
}
178+
}
179+
180+
void its_free_mod(struct module *mod)
181+
{
182+
if (!cpu_feature_enabled(X86_FEATURE_INDIRECT_THUNK_ITS))
183+
return;
184+
185+
for (int i = 0; i < mod->its_num_pages; i++) {
186+
void *page = mod->its_page_array[i];
187+
execmem_free(page);
188+
}
189+
kfree(mod->its_page_array);
190+
}
191+
192+
static void *its_alloc(void)
193+
{
194+
void *page __free(execmem) = execmem_alloc(EXECMEM_MODULE_TEXT, PAGE_SIZE);
195+
196+
if (!page)
197+
return NULL;
198+
199+
if (its_mod) {
200+
void *tmp = krealloc(its_mod->its_page_array,
201+
(its_mod->its_num_pages+1) * sizeof(void *),
202+
GFP_KERNEL);
203+
if (!tmp)
204+
return NULL;
205+
206+
its_mod->its_page_array = tmp;
207+
its_mod->its_page_array[its_mod->its_num_pages++] = page;
208+
209+
execmem_make_temp_rw(page, PAGE_SIZE);
210+
}
211+
212+
return no_free_ptr(page);
213+
}
214+
215+
static void *its_allocate_thunk(int reg)
216+
{
217+
int size = 3 + (reg / 8);
218+
void *thunk;
219+
220+
if (!its_page || (its_offset + size - 1) >= PAGE_SIZE) {
221+
its_page = its_alloc();
222+
if (!its_page) {
223+
pr_err("ITS page allocation failed\n");
224+
return NULL;
225+
}
226+
memset(its_page, INT3_INSN_OPCODE, PAGE_SIZE);
227+
its_offset = 32;
228+
}
229+
230+
/*
231+
* If the indirect branch instruction will be in the lower half
232+
* of a cacheline, then update the offset to reach the upper half.
233+
*/
234+
if ((its_offset + size - 1) % 64 < 32)
235+
its_offset = ((its_offset - 1) | 0x3F) + 33;
236+
237+
thunk = its_page + its_offset;
238+
its_offset += size;
239+
240+
return its_init_thunk(thunk, reg);
241+
}
242+
243+
#endif
244+
128245
/*
129246
* Nomenclature for variable names to simplify and clarify this code and ease
130247
* any potential staring at it:
@@ -637,9 +754,13 @@ static int emit_call_track_retpoline(void *addr, struct insn *insn, int reg, u8
637754
#ifdef CONFIG_MITIGATION_ITS
638755
static int emit_its_trampoline(void *addr, struct insn *insn, int reg, u8 *bytes)
639756
{
640-
return __emit_trampoline(addr, insn, bytes,
641-
__x86_indirect_its_thunk_array[reg],
642-
__x86_indirect_its_thunk_array[reg]);
757+
u8 *thunk = __x86_indirect_its_thunk_array[reg];
758+
u8 *tmp = its_allocate_thunk(reg);
759+
760+
if (tmp)
761+
thunk = tmp;
762+
763+
return __emit_trampoline(addr, insn, bytes, thunk, thunk);
643764
}
644765

645766
/* Check if an indirect branch is at ITS-unsafe address */

arch/x86/kernel/module.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,8 @@ int module_finalize(const Elf_Ehdr *hdr,
266266
ibt_endbr = s;
267267
}
268268

269+
its_init_mod(me);
270+
269271
if (retpolines || cfi) {
270272
void *rseg = NULL, *cseg = NULL;
271273
unsigned int rsize = 0, csize = 0;
@@ -286,6 +288,9 @@ int module_finalize(const Elf_Ehdr *hdr,
286288
void *rseg = (void *)retpolines->sh_addr;
287289
apply_retpolines(rseg, rseg + retpolines->sh_size);
288290
}
291+
292+
its_fini_mod(me);
293+
289294
if (returns) {
290295
void *rseg = (void *)returns->sh_addr;
291296
apply_returns(rseg, rseg + returns->sh_size);
@@ -326,4 +331,5 @@ int module_finalize(const Elf_Ehdr *hdr,
326331
void module_arch_cleanup(struct module *mod)
327332
{
328333
alternatives_smp_module_del(mod);
334+
its_free_mod(mod);
329335
}

include/linux/execmem.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include <linux/types.h>
66
#include <linux/moduleloader.h>
7+
#include <linux/cleanup.h>
78

89
#if (defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)) && \
910
!defined(CONFIG_KASAN_VMALLOC)
@@ -176,6 +177,8 @@ void *execmem_alloc(enum execmem_type type, size_t size);
176177
*/
177178
void execmem_free(void *ptr);
178179

180+
DEFINE_FREE(execmem, void *, if (_T) execmem_free(_T));
181+
179182
#ifdef CONFIG_MMU
180183
/**
181184
* execmem_vmap - create virtual mapping for EXECMEM_MODULE_DATA memory

include/linux/module.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,11 @@ struct module {
586586
atomic_t refcnt;
587587
#endif
588588

589+
#ifdef CONFIG_MITIGATION_ITS
590+
int its_num_pages;
591+
void **its_page_array;
592+
#endif
593+
589594
#ifdef CONFIG_CONSTRUCTORS
590595
/* Constructor functions. */
591596
ctor_fn_t *ctors;

0 commit comments

Comments
 (0)