Skip to content

Commit 959cadf

Browse files
Peter Zijlstragregkh
authored andcommitted
x86/its: Use dynamic thunks for indirect branches
commit 872df34d7c51a79523820ea6a14860398c639b87 upstream. 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. [ pawan: CONFIG_EXECMEM and CONFIG_EXECMEM_ROX are not supported on backport kernel, made changes to use module_alloc() and set_memory_*() for dynamic thunks. ] 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]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 9502e83 commit 959cadf

File tree

4 files changed

+149
-3
lines changed

4 files changed

+149
-3
lines changed

arch/x86/include/asm/alternative.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,16 @@ extern void apply_ibt_endbr(s32 *start, s32 *end);
8181

8282
struct module;
8383

84+
#ifdef CONFIG_MITIGATION_ITS
85+
extern void its_init_mod(struct module *mod);
86+
extern void its_fini_mod(struct module *mod);
87+
extern void its_free_mod(struct module *mod);
88+
#else /* CONFIG_MITIGATION_ITS */
89+
static inline void its_init_mod(struct module *mod) { }
90+
static inline void its_fini_mod(struct module *mod) { }
91+
static inline void its_free_mod(struct module *mod) { }
92+
#endif
93+
8494
#if defined(CONFIG_RETHUNK) && defined(CONFIG_OBJTOOL)
8595
extern bool cpu_wants_rethunk(void);
8696
extern bool cpu_wants_rethunk_at(void *addr);

arch/x86/kernel/alternative.c

Lines changed: 127 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#include <linux/mmu_context.h>
1919
#include <linux/bsearch.h>
2020
#include <linux/sync_core.h>
21+
#include <linux/moduleloader.h>
22+
#include <linux/cleanup.h>
2123
#include <asm/text-patching.h>
2224
#include <asm/alternative.h>
2325
#include <asm/sections.h>
@@ -31,6 +33,7 @@
3133
#include <asm/paravirt.h>
3234
#include <asm/asm-prototypes.h>
3335
#include <asm/cfi.h>
36+
#include <asm/set_memory.h>
3437

3538
int __read_mostly alternatives_patched;
3639

@@ -399,6 +402,123 @@ static int emit_indirect(int op, int reg, u8 *bytes)
399402

400403
#ifdef CONFIG_MITIGATION_ITS
401404

405+
static struct module *its_mod;
406+
static void *its_page;
407+
static unsigned int its_offset;
408+
409+
/* Initialize a thunk with the "jmp *reg; int3" instructions. */
410+
static void *its_init_thunk(void *thunk, int reg)
411+
{
412+
u8 *bytes = thunk;
413+
int i = 0;
414+
415+
if (reg >= 8) {
416+
bytes[i++] = 0x41; /* REX.B prefix */
417+
reg -= 8;
418+
}
419+
bytes[i++] = 0xff;
420+
bytes[i++] = 0xe0 + reg; /* jmp *reg */
421+
bytes[i++] = 0xcc;
422+
423+
return thunk;
424+
}
425+
426+
void its_init_mod(struct module *mod)
427+
{
428+
if (!cpu_feature_enabled(X86_FEATURE_INDIRECT_THUNK_ITS))
429+
return;
430+
431+
mutex_lock(&text_mutex);
432+
its_mod = mod;
433+
its_page = NULL;
434+
}
435+
436+
void its_fini_mod(struct module *mod)
437+
{
438+
if (!cpu_feature_enabled(X86_FEATURE_INDIRECT_THUNK_ITS))
439+
return;
440+
441+
WARN_ON_ONCE(its_mod != mod);
442+
443+
its_mod = NULL;
444+
its_page = NULL;
445+
mutex_unlock(&text_mutex);
446+
447+
for (int i = 0; i < mod->its_num_pages; i++) {
448+
void *page = mod->its_page_array[i];
449+
set_memory_ro((unsigned long)page, 1);
450+
set_memory_x((unsigned long)page, 1);
451+
}
452+
}
453+
454+
void its_free_mod(struct module *mod)
455+
{
456+
if (!cpu_feature_enabled(X86_FEATURE_INDIRECT_THUNK_ITS))
457+
return;
458+
459+
for (int i = 0; i < mod->its_num_pages; i++) {
460+
void *page = mod->its_page_array[i];
461+
module_memfree(page);
462+
}
463+
kfree(mod->its_page_array);
464+
}
465+
466+
DEFINE_FREE(its_execmem, void *, if (_T) module_memfree(_T));
467+
468+
static void *its_alloc(void)
469+
{
470+
void *page __free(its_execmem) = module_alloc(PAGE_SIZE);
471+
472+
if (!page)
473+
return NULL;
474+
475+
if (its_mod) {
476+
void *tmp = krealloc(its_mod->its_page_array,
477+
(its_mod->its_num_pages+1) * sizeof(void *),
478+
GFP_KERNEL);
479+
if (!tmp)
480+
return NULL;
481+
482+
its_mod->its_page_array = tmp;
483+
its_mod->its_page_array[its_mod->its_num_pages++] = page;
484+
}
485+
486+
return no_free_ptr(page);
487+
}
488+
489+
static void *its_allocate_thunk(int reg)
490+
{
491+
int size = 3 + (reg / 8);
492+
void *thunk;
493+
494+
if (!its_page || (its_offset + size - 1) >= PAGE_SIZE) {
495+
its_page = its_alloc();
496+
if (!its_page) {
497+
pr_err("ITS page allocation failed\n");
498+
return NULL;
499+
}
500+
memset(its_page, INT3_INSN_OPCODE, PAGE_SIZE);
501+
its_offset = 32;
502+
}
503+
504+
/*
505+
* If the indirect branch instruction will be in the lower half
506+
* of a cacheline, then update the offset to reach the upper half.
507+
*/
508+
if ((its_offset + size - 1) % 64 < 32)
509+
its_offset = ((its_offset - 1) | 0x3F) + 33;
510+
511+
thunk = its_page + its_offset;
512+
its_offset += size;
513+
514+
set_memory_rw((unsigned long)its_page, 1);
515+
thunk = its_init_thunk(thunk, reg);
516+
set_memory_ro((unsigned long)its_page, 1);
517+
set_memory_x((unsigned long)its_page, 1);
518+
519+
return thunk;
520+
}
521+
402522
static int __emit_trampoline(void *addr, struct insn *insn, u8 *bytes,
403523
void *call_dest, void *jmp_dest)
404524
{
@@ -446,9 +566,13 @@ static int __emit_trampoline(void *addr, struct insn *insn, u8 *bytes,
446566

447567
static int emit_its_trampoline(void *addr, struct insn *insn, int reg, u8 *bytes)
448568
{
449-
return __emit_trampoline(addr, insn, bytes,
450-
__x86_indirect_its_thunk_array[reg],
451-
__x86_indirect_its_thunk_array[reg]);
569+
u8 *thunk = __x86_indirect_its_thunk_array[reg];
570+
u8 *tmp = its_allocate_thunk(reg);
571+
572+
if (tmp)
573+
thunk = tmp;
574+
575+
return __emit_trampoline(addr, insn, bytes, thunk, thunk);
452576
}
453577

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

arch/x86/kernel/module.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,10 +285,16 @@ int module_finalize(const Elf_Ehdr *hdr,
285285
void *pseg = (void *)para->sh_addr;
286286
apply_paravirt(pseg, pseg + para->sh_size);
287287
}
288+
289+
its_init_mod(me);
290+
288291
if (retpolines) {
289292
void *rseg = (void *)retpolines->sh_addr;
290293
apply_retpolines(rseg, rseg + retpolines->sh_size);
291294
}
295+
296+
its_fini_mod(me);
297+
292298
if (returns) {
293299
void *rseg = (void *)returns->sh_addr;
294300
apply_returns(rseg, rseg + returns->sh_size);
@@ -320,4 +326,5 @@ int module_finalize(const Elf_Ehdr *hdr,
320326
void module_arch_cleanup(struct module *mod)
321327
{
322328
alternatives_smp_module_del(mod);
329+
its_free_mod(mod);
323330
}

include/linux/module.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,11 @@ struct module {
536536
atomic_t refcnt;
537537
#endif
538538

539+
#ifdef CONFIG_MITIGATION_ITS
540+
int its_num_pages;
541+
void **its_page_array;
542+
#endif
543+
539544
#ifdef CONFIG_CONSTRUCTORS
540545
/* Constructor functions. */
541546
ctor_fn_t *ctors;

0 commit comments

Comments
 (0)