Skip to content

Commit ed53a0d

Browse files
author
Peter Zijlstra
committed
x86/alternative: Use .ibt_endbr_seal to seal indirect calls
Objtool's --ibt option generates .ibt_endbr_seal which lists superfluous ENDBR instructions. That is those instructions for which the function is never indirectly called. Overwrite these ENDBR instructions with a NOP4 such that these function can never be indirect called, reducing the number of viable ENDBR targets in the kernel. Signed-off-by: Peter Zijlstra (Intel) <[email protected]> Acked-by: Josh Poimboeuf <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent 89bc853 commit ed53a0d

File tree

8 files changed

+117
-13
lines changed

8 files changed

+117
-13
lines changed

arch/um/kernel/um_arch.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,10 @@ void __init check_bugs(void)
424424
os_check_bugs();
425425
}
426426

427+
void apply_ibt_endbr(s32 *start, s32 *end)
428+
{
429+
}
430+
427431
void apply_retpolines(s32 *start, s32 *end)
428432
{
429433
}

arch/x86/Kconfig

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1873,14 +1873,21 @@ config CC_HAS_IBT
18731873
config X86_KERNEL_IBT
18741874
prompt "Indirect Branch Tracking"
18751875
bool
1876-
depends on X86_64 && CC_HAS_IBT
1876+
depends on X86_64 && CC_HAS_IBT && STACK_VALIDATION
18771877
help
18781878
Build the kernel with support for Indirect Branch Tracking, a
18791879
hardware support course-grain forward-edge Control Flow Integrity
18801880
protection. It enforces that all indirect calls must land on
18811881
an ENDBR instruction, as such, the compiler will instrument the
18821882
code with them to make this happen.
18831883

1884+
In addition to building the kernel with IBT, seal all functions that
1885+
are not indirect call targets, avoiding them ever becomming one.
1886+
1887+
This requires LTO like objtool runs and will slow down the build. It
1888+
does significantly reduce the number of ENDBR instructions in the
1889+
kernel image.
1890+
18841891
config X86_INTEL_MEMORY_PROTECTION_KEYS
18851892
prompt "Memory Protection Keys"
18861893
def_bool y

arch/x86/include/asm/alternative.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ extern int alternatives_patched;
7676
extern void alternative_instructions(void);
7777
extern void apply_alternatives(struct alt_instr *start, struct alt_instr *end);
7878
extern void apply_retpolines(s32 *start, s32 *end);
79+
extern void apply_ibt_endbr(s32 *start, s32 *end);
7980

8081
struct module;
8182

arch/x86/include/asm/ibt.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,20 @@ static inline __attribute_const__ u32 gen_endbr(void)
4646
return endbr;
4747
}
4848

49+
static inline __attribute_const__ u32 gen_endbr_poison(void)
50+
{
51+
/*
52+
* 4 byte NOP that isn't NOP4 (in fact it is OSP NOP3), such that it
53+
* will be unique to (former) ENDBR sites.
54+
*/
55+
return 0x001f0f66; /* osp nopl (%rax) */
56+
}
57+
4958
static inline bool is_endbr(u32 val)
5059
{
60+
if (val == gen_endbr_poison())
61+
return true;
62+
5163
val &= ~0x01000000U; /* ENDBR32 -> ENDBR64 */
5264
return val == gen_endbr();
5365
}

arch/x86/kernel/alternative.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ static void __init_or_module add_nops(void *insns, unsigned int len)
115115
}
116116

117117
extern s32 __retpoline_sites[], __retpoline_sites_end[];
118+
extern s32 __ibt_endbr_seal[], __ibt_endbr_seal_end[];
118119
extern struct alt_instr __alt_instructions[], __alt_instructions_end[];
119120
extern s32 __smp_locks[], __smp_locks_end[];
120121
void text_poke_early(void *addr, const void *opcode, size_t len);
@@ -512,6 +513,42 @@ void __init_or_module noinline apply_retpolines(s32 *start, s32 *end) { }
512513

513514
#endif /* CONFIG_RETPOLINE && CONFIG_STACK_VALIDATION */
514515

516+
#ifdef CONFIG_X86_KERNEL_IBT
517+
518+
/*
519+
* Generated by: objtool --ibt
520+
*/
521+
void __init_or_module noinline apply_ibt_endbr(s32 *start, s32 *end)
522+
{
523+
s32 *s;
524+
525+
for (s = start; s < end; s++) {
526+
u32 endbr, poison = gen_endbr_poison();
527+
void *addr = (void *)s + *s;
528+
529+
if (WARN_ON_ONCE(get_kernel_nofault(endbr, addr)))
530+
continue;
531+
532+
if (WARN_ON_ONCE(!is_endbr(endbr)))
533+
continue;
534+
535+
DPRINTK("ENDBR at: %pS (%px)", addr, addr);
536+
537+
/*
538+
* When we have IBT, the lack of ENDBR will trigger #CP
539+
*/
540+
DUMP_BYTES(((u8*)addr), 4, "%px: orig: ", addr);
541+
DUMP_BYTES(((u8*)&poison), 4, "%px: repl: ", addr);
542+
text_poke_early(addr, &poison, 4);
543+
}
544+
}
545+
546+
#else
547+
548+
void __init_or_module noinline apply_ibt_endbr(s32 *start, s32 *end) { }
549+
550+
#endif /* CONFIG_X86_KERNEL_IBT */
551+
515552
#ifdef CONFIG_SMP
516553
static void alternatives_smp_lock(const s32 *start, const s32 *end,
517554
u8 *text, u8 *text_end)
@@ -830,6 +867,8 @@ void __init alternative_instructions(void)
830867
*/
831868
apply_alternatives(__alt_instructions, __alt_instructions_end);
832869

870+
apply_ibt_endbr(__ibt_endbr_seal, __ibt_endbr_seal_end);
871+
833872
#ifdef CONFIG_SMP
834873
/* Patch to UP if other cpus not imminent. */
835874
if (!noreplace_smp && (num_present_cpus() == 1 || setup_max_cpus <= 1)) {

arch/x86/kernel/module.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ int module_finalize(const Elf_Ehdr *hdr,
253253
{
254254
const Elf_Shdr *s, *text = NULL, *alt = NULL, *locks = NULL,
255255
*para = NULL, *orc = NULL, *orc_ip = NULL,
256-
*retpolines = NULL;
256+
*retpolines = NULL, *ibt_endbr = NULL;
257257
char *secstrings = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;
258258

259259
for (s = sechdrs; s < sechdrs + hdr->e_shnum; s++) {
@@ -271,6 +271,8 @@ int module_finalize(const Elf_Ehdr *hdr,
271271
orc_ip = s;
272272
if (!strcmp(".retpoline_sites", secstrings + s->sh_name))
273273
retpolines = s;
274+
if (!strcmp(".ibt_endbr_seal", secstrings + s->sh_name))
275+
ibt_endbr = s;
274276
}
275277

276278
/*
@@ -290,6 +292,10 @@ int module_finalize(const Elf_Ehdr *hdr,
290292
void *aseg = (void *)alt->sh_addr;
291293
apply_alternatives(aseg, aseg + alt->sh_size);
292294
}
295+
if (ibt_endbr) {
296+
void *iseg = (void *)ibt_endbr->sh_addr;
297+
apply_ibt_endbr(iseg, iseg + ibt_endbr->sh_size);
298+
}
293299
if (locks && text) {
294300
void *lseg = (void *)locks->sh_addr;
295301
void *tseg = (void *)text->sh_addr;

scripts/Makefile.build

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,18 @@ ifdef need-builtin
8686
targets-for-builtin += $(obj)/built-in.a
8787
endif
8888

89-
targets-for-modules := $(patsubst %.o, %.mod, $(filter %.o, $(obj-m)))
89+
targets-for-modules :=
9090

9191
ifdef CONFIG_LTO_CLANG
9292
targets-for-modules += $(patsubst %.o, %.lto.o, $(filter %.o, $(obj-m)))
9393
endif
9494

95+
ifdef CONFIG_X86_KERNEL_IBT
96+
targets-for-modules += $(patsubst %.o, %.objtool, $(filter %.o, $(obj-m)))
97+
endif
98+
99+
targets-for-modules += $(patsubst %.o, %.mod, $(filter %.o, $(obj-m)))
100+
95101
ifdef need-modorder
96102
targets-for-modules += $(obj)/modules.order
97103
endif
@@ -230,15 +236,16 @@ objtool := $(objtree)/tools/objtool/objtool
230236
objtool_args = \
231237
$(if $(CONFIG_UNWINDER_ORC),orc generate,check) \
232238
$(if $(part-of-module), --module) \
239+
$(if $(CONFIG_X86_KERNEL_IBT), --lto --ibt) \
233240
$(if $(CONFIG_FRAME_POINTER),, --no-fp) \
234241
$(if $(CONFIG_GCOV_KERNEL)$(CONFIG_LTO_CLANG), --no-unreachable)\
235242
$(if $(CONFIG_RETPOLINE), --retpoline) \
236243
$(if $(CONFIG_X86_SMAP), --uaccess) \
237244
$(if $(CONFIG_FTRACE_MCOUNT_USE_OBJTOOL), --mcount) \
238245
$(if $(CONFIG_SLS), --sls)
239246

240-
cmd_objtool = $(if $(objtool-enabled), ; $(objtool) $(objtool_args) $@)
241-
cmd_gen_objtooldep = $(if $(objtool-enabled), { echo ; echo '$@: $$(wildcard $(objtool))' ; } >> $(dot-target).cmd)
247+
cmd_objtool = $(if $(objtool-enabled), ; $(objtool) $(objtool_args) $(@:.objtool=.o))
248+
cmd_gen_objtooldep = $(if $(objtool-enabled), { echo ; echo '$(@:.objtool=.o): $$(wildcard $(objtool))' ; } >> $(dot-target).cmd)
242249

243250
endif # CONFIG_STACK_VALIDATION
244251

@@ -247,6 +254,21 @@ ifdef CONFIG_LTO_CLANG
247254
# Skip objtool for LLVM bitcode
248255
$(obj)/%.o: objtool-enabled :=
249256

257+
# objtool was skipped for LLVM bitcode, run it now that we have compiled
258+
# modules into native code
259+
$(obj)/%.lto.o: objtool-enabled = y
260+
$(obj)/%.lto.o: part-of-module := y
261+
262+
else ifdef CONFIG_X86_KERNEL_IBT
263+
264+
# Skip objtool on individual files
265+
$(obj)/%.o: objtool-enabled :=
266+
267+
# instead run objtool on the module as a whole, right before
268+
# the final link pass with the linker script.
269+
$(obj)/%.objtool: objtool-enabled = y
270+
$(obj)/%.objtool: part-of-module := y
271+
250272
else
251273

252274
# 'OBJECT_FILES_NON_STANDARD := y': skip objtool checking for a directory
@@ -292,18 +314,13 @@ ifdef CONFIG_LTO_CLANG
292314
# Module .o files may contain LLVM bitcode, compile them into native code
293315
# before ELF processing
294316
quiet_cmd_cc_lto_link_modules = LTO [M] $@
295-
cmd_cc_lto_link_modules = \
317+
cmd_cc_lto_link_modules = \
296318
$(LD) $(ld_flags) -r -o $@ \
297319
$(shell [ -s $(@:.lto.o=.o.symversions) ] && \
298320
echo -T $(@:.lto.o=.o.symversions)) \
299321
--whole-archive $(filter-out FORCE,$^) \
300322
$(cmd_objtool)
301323

302-
# objtool was skipped for LLVM bitcode, run it now that we have compiled
303-
# modules into native code
304-
$(obj)/%.lto.o: objtool-enabled = y
305-
$(obj)/%.lto.o: part-of-module := y
306-
307324
$(obj)/%.lto.o: $(obj)/%.o FORCE
308325
$(call if_changed,cc_lto_link_modules)
309326
endif
@@ -316,6 +333,18 @@ cmd_mod = { \
316333
$(obj)/%.mod: $(obj)/%$(mod-prelink-ext).o FORCE
317334
$(call if_changed,mod)
318335

336+
#
337+
# Since objtool will re-write the file it will change the timestamps, therefore
338+
# it is critical that the %.objtool file gets a timestamp *after* objtool runs.
339+
#
340+
# Additionally, care must be had with ordering this rule against the other rules
341+
# that take %.o as a dependency.
342+
#
343+
cmd_objtool_mod = true $(cmd_objtool) ; touch $@
344+
345+
$(obj)/%.objtool: $(obj)/%$(mod-prelink-ext).o FORCE
346+
$(call if_changed,objtool_mod)
347+
319348
quiet_cmd_cc_lst_c = MKLST $@
320349
cmd_cc_lst_c = $(CC) $(c_flags) -g -c -o $*.o $< && \
321350
$(CONFIG_SHELL) $(srctree)/scripts/makelst $*.o \

scripts/link-vmlinux.sh

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,9 @@ objtool_link()
108108
local objtoolcmd;
109109
local objtoolopt;
110110

111-
if is_enabled CONFIG_LTO_CLANG && is_enabled CONFIG_STACK_VALIDATION; then
111+
if is_enabled CONFIG_STACK_VALIDATION && \
112+
( is_enabled CONFIG_LTO_CLANG || is_enabled CONFIG_X86_KERNEL_IBT ); then
113+
112114
# Don't perform vmlinux validation unless explicitly requested,
113115
# but run objtool on vmlinux.o now that we have an object file.
114116
if is_enabled CONFIG_UNWINDER_ORC; then
@@ -117,6 +119,10 @@ objtool_link()
117119

118120
objtoolopt="${objtoolopt} --lto"
119121

122+
if is_enabled CONFIG_X86_KERNEL_IBT; then
123+
objtoolopt="${objtoolopt} --ibt"
124+
fi
125+
120126
if is_enabled CONFIG_FTRACE_MCOUNT_USE_OBJTOOL; then
121127
objtoolopt="${objtoolopt} --mcount"
122128
fi
@@ -168,7 +174,7 @@ vmlinux_link()
168174
# skip output file argument
169175
shift
170176

171-
if is_enabled CONFIG_LTO_CLANG; then
177+
if is_enabled CONFIG_LTO_CLANG || is_enabled CONFIG_X86_KERNEL_IBT; then
172178
# Use vmlinux.o instead of performing the slow LTO link again.
173179
objs=vmlinux.o
174180
libs=

0 commit comments

Comments
 (0)