From 5c8166419acf468b5bc3e48f928a040485d3e0c2 Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Fri, 11 Feb 2022 14:14:11 +0900 Subject: kbuild: replace $(if A,A,B) with $(or A,B) $(or ...) is available since GNU Make 3.81, and useful to shorten the code in some places. Covert as follows: $(if A,A,B) --> $(or A,B) This patch also converts: $(if A, A, B) --> $(or A, B) Strictly speaking, the latter is not an equivalent conversion because GNU Make keeps spaces after commas; if A is not empty, $(if A, A, B) expands to " A", while $(or A, B) expands to "A". Anyway, preceding spaces are not significant in the code hunks I touched. Signed-off-by: Masahiro Yamada Reviewed-by: Nicolas Schier --- tools/objtool/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tools/objtool') diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index 92ce4fce7bc7..0dbd397f319d 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -13,7 +13,7 @@ srctree := $(patsubst %/,%,$(dir $(srctree))) endif SUBCMD_SRCDIR = $(srctree)/tools/lib/subcmd/ -LIBSUBCMD_OUTPUT = $(if $(OUTPUT),$(OUTPUT),$(CURDIR)/) +LIBSUBCMD_OUTPUT = $(or $(OUTPUT),$(CURDIR)/) LIBSUBCMD = $(LIBSUBCMD_OUTPUT)libsubcmd.a OBJTOOL := $(OUTPUT)objtool -- cgit From 227a06553fe6c785f23d76eece3bb10e2db5059c Mon Sep 17 00:00:00 2001 From: Fenghua Yu Date: Mon, 7 Feb 2022 15:02:53 -0800 Subject: tools/objtool: Check for use of the ENQCMD instruction in the kernel The ENQCMD instruction implicitly accesses the PASID_MSR to fill in the pasid field of the descriptor being submitted to an accelerator. But there is no precise (and stable across kernel changes) point at which the PASID_MSR is updated from the value for one task to the next. Kernel code that uses accelerators must always use the ENQCMDS instruction which does not access the PASID_MSR. Check for use of the ENQCMD instruction in the kernel and warn on its usage. Signed-off-by: Fenghua Yu Signed-off-by: Borislav Petkov Reviewed-by: Tony Luck Acked-by: Josh Poimboeuf Link: https://lore.kernel.org/r/20220207230254.3342514-11-fenghua.yu@intel.com Signed-off-by: Peter Zijlstra --- tools/objtool/arch/x86/decode.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'tools/objtool') diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c index c10ef78df050..479e769ca324 100644 --- a/tools/objtool/arch/x86/decode.c +++ b/tools/objtool/arch/x86/decode.c @@ -112,7 +112,7 @@ int arch_decode_instruction(struct objtool_file *file, const struct section *sec const struct elf *elf = file->elf; struct insn insn; int x86_64, ret; - unsigned char op1, op2, + unsigned char op1, op2, op3, rex = 0, rex_b = 0, rex_r = 0, rex_w = 0, rex_x = 0, modrm = 0, modrm_mod = 0, modrm_rm = 0, modrm_reg = 0, sib = 0, /* sib_scale = 0, */ sib_index = 0, sib_base = 0; @@ -139,6 +139,7 @@ int arch_decode_instruction(struct objtool_file *file, const struct section *sec op1 = insn.opcode.bytes[0]; op2 = insn.opcode.bytes[1]; + op3 = insn.opcode.bytes[2]; if (insn.rex_prefix.nbytes) { rex = insn.rex_prefix.bytes[0]; @@ -491,6 +492,14 @@ int arch_decode_instruction(struct objtool_file *file, const struct section *sec /* nopl/nopw */ *type = INSN_NOP; + } else if (op2 == 0x38 && op3 == 0xf8) { + if (insn.prefixes.nbytes == 1 && + insn.prefixes.bytes[0] == 0xf2) { + /* ENQCMD cannot be used in the kernel. */ + WARN("ENQCMD instruction at %s:%lx", sec->name, + offset); + } + } else if (op2 == 0xa0 || op2 == 0xa8) { /* push fs/gs */ -- cgit From f2d3a250897133cc36c13a641bd6a9b4dd5ad234 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Tue, 8 Mar 2022 16:30:13 +0100 Subject: objtool: Add --dry-run Add a --dry-run argument to skip writing the modifications. This is convenient for debugging. Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Kees Cook Reviewed-by: Miroslav Benes Acked-by: Josh Poimboeuf Link: https://lore.kernel.org/r/20220308154317.282720146@infradead.org --- tools/objtool/builtin-check.c | 3 ++- tools/objtool/elf.c | 3 +++ tools/objtool/include/objtool/builtin.h | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index 38070f26105b..853af934c9fd 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -20,7 +20,7 @@ #include bool no_fp, no_unreachable, retpoline, module, backtrace, uaccess, stats, - validate_dup, vmlinux, mcount, noinstr, backup, sls; + validate_dup, vmlinux, mcount, noinstr, backup, sls, dryrun; static const char * const check_usage[] = { "objtool check [] file.o", @@ -46,6 +46,7 @@ const struct option check_options[] = { OPT_BOOLEAN('M', "mcount", &mcount, "generate __mcount_loc"), OPT_BOOLEAN('B', "backup", &backup, "create .orig files before modification"), OPT_BOOLEAN('S', "sls", &sls, "validate straight-line-speculation"), + OPT_BOOLEAN(0, "dry-run", &dryrun, "don't write the modifications"), OPT_END(), }; diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index 4b384c907027..456ac2206404 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -1019,6 +1019,9 @@ int elf_write(struct elf *elf) struct section *sec; Elf_Scn *s; + if (dryrun) + return 0; + /* Update changed relocation sections and section headers: */ list_for_each_entry(sec, &elf->sections, list) { if (sec->changed) { diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h index 89ba869ed08f..7b4b124b9032 100644 --- a/tools/objtool/include/objtool/builtin.h +++ b/tools/objtool/include/objtool/builtin.h @@ -9,7 +9,7 @@ extern const struct option check_options[]; extern bool no_fp, no_unreachable, retpoline, module, backtrace, uaccess, stats, - validate_dup, vmlinux, mcount, noinstr, backup, sls; + validate_dup, vmlinux, mcount, noinstr, backup, sls, dryrun; extern int cmd_parse_options(int argc, const char **argv, const char * const usage[]); -- cgit From 1ffbe4e935f9b7308615c75be990aec07464d1e7 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Tue, 8 Mar 2022 16:30:14 +0100 Subject: objtool: Default ignore INT3 for unreachable Ignore all INT3 instructions for unreachable code warnings, similar to NOP. This allows using INT3 for various paddings instead of NOPs. Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://lore.kernel.org/r/20220308154317.343312938@infradead.org --- tools/objtool/check.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 7c33ec67c4a9..311bfc6922c1 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -3115,9 +3115,8 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, switch (insn->type) { case INSN_RETURN: - if (next_insn && next_insn->type == INSN_TRAP) { - next_insn->ignore = true; - } else if (sls && !insn->retpoline_safe) { + if (sls && !insn->retpoline_safe && + next_insn && next_insn->type != INSN_TRAP) { WARN_FUNC("missing int3 after ret", insn->sec, insn->offset); } @@ -3164,9 +3163,8 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, break; case INSN_JUMP_DYNAMIC: - if (next_insn && next_insn->type == INSN_TRAP) { - next_insn->ignore = true; - } else if (sls && !insn->retpoline_safe) { + if (sls && !insn->retpoline_safe && + next_insn && next_insn->type != INSN_TRAP) { WARN_FUNC("missing int3 after indirect jump", insn->sec, insn->offset); } @@ -3337,7 +3335,7 @@ static bool ignore_unreachable_insn(struct objtool_file *file, struct instructio int i; struct instruction *prev_insn; - if (insn->ignore || insn->type == INSN_NOP) + if (insn->ignore || insn->type == INSN_NOP || insn->type == INSN_TRAP) return true; /* -- cgit From 5cff2086b01526b8c7deacc86473ffbab0cddfa9 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Tue, 8 Mar 2022 16:30:16 +0100 Subject: objtool: Have WARN_FUNC fall back to sym+off Currently WARN_FUNC() either prints func+off and failing that prints sec+off, add an intermediate sym+off. This is useful when playing around with entry code. Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://lore.kernel.org/r/20220308154317.461283840@infradead.org --- tools/objtool/include/objtool/warn.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'tools/objtool') diff --git a/tools/objtool/include/objtool/warn.h b/tools/objtool/include/objtool/warn.h index d99c4675e4a5..802cfda0a6f6 100644 --- a/tools/objtool/include/objtool/warn.h +++ b/tools/objtool/include/objtool/warn.h @@ -22,6 +22,8 @@ static inline char *offstr(struct section *sec, unsigned long offset) unsigned long name_off; func = find_func_containing(sec, offset); + if (!func) + func = find_symbol_containing(sec, offset); if (func) { name = func->name; name_off = offset - func->offset; -- cgit From 53f7109ef957315ab53205ba3a3f4f48874c0428 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Tue, 8 Mar 2022 16:30:45 +0100 Subject: objtool: Rename --duplicate to --lto In order to prepare for LTO like objtool runs for modules, rename the duplicate argument to lto. Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://lore.kernel.org/r/20220308154319.172584233@infradead.org --- scripts/link-vmlinux.sh | 2 +- tools/objtool/builtin-check.c | 4 ++-- tools/objtool/check.c | 7 ++++++- tools/objtool/include/objtool/builtin.h | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) (limited to 'tools/objtool') diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh index 666f7bbc13eb..9b08dca26f99 100755 --- a/scripts/link-vmlinux.sh +++ b/scripts/link-vmlinux.sh @@ -115,7 +115,7 @@ objtool_link() objtoolcmd="orc generate" fi - objtoolopt="${objtoolopt} --duplicate" + objtoolopt="${objtoolopt} --lto" if is_enabled CONFIG_FTRACE_MCOUNT_USE_OBJTOOL; then objtoolopt="${objtoolopt} --mcount" diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index 853af934c9fd..5c2fcaa2c260 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -20,7 +20,7 @@ #include bool no_fp, no_unreachable, retpoline, module, backtrace, uaccess, stats, - validate_dup, vmlinux, mcount, noinstr, backup, sls, dryrun; + lto, vmlinux, mcount, noinstr, backup, sls, dryrun; static const char * const check_usage[] = { "objtool check [] file.o", @@ -40,7 +40,7 @@ const struct option check_options[] = { OPT_BOOLEAN('b', "backtrace", &backtrace, "unwind on error"), OPT_BOOLEAN('a', "uaccess", &uaccess, "enable uaccess checking"), OPT_BOOLEAN('s', "stats", &stats, "print statistics"), - OPT_BOOLEAN('d', "duplicate", &validate_dup, "duplicate validation for vmlinux.o"), + OPT_BOOLEAN(0, "lto", <o, "whole-archive like runs"), OPT_BOOLEAN('n', "noinstr", &noinstr, "noinstr validation for vmlinux.o"), OPT_BOOLEAN('l', "vmlinux", &vmlinux, "vmlinux.o validation"), OPT_BOOLEAN('M', "mcount", &mcount, "generate __mcount_loc"), diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 311bfc6922c1..ae1d4f996803 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -3499,6 +3499,11 @@ int check(struct objtool_file *file) { int ret, warnings = 0; + if (lto && !(vmlinux || module)) { + fprintf(stderr, "--lto requires: --vmlinux or --module\n"); + return 1; + } + arch_initial_func_cfi_state(&initial_func_cfi); init_cfi_state(&init_cfi); init_cfi_state(&func_cfi); @@ -3519,7 +3524,7 @@ int check(struct objtool_file *file) if (list_empty(&file->insn_list)) goto out; - if (vmlinux && !validate_dup) { + if (vmlinux && !lto) { ret = validate_vmlinux_functions(file); if (ret < 0) goto out; diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h index 7b4b124b9032..0cbe739ab0c8 100644 --- a/tools/objtool/include/objtool/builtin.h +++ b/tools/objtool/include/objtool/builtin.h @@ -9,7 +9,7 @@ extern const struct option check_options[]; extern bool no_fp, no_unreachable, retpoline, module, backtrace, uaccess, stats, - validate_dup, vmlinux, mcount, noinstr, backup, sls, dryrun; + lto, vmlinux, mcount, noinstr, backup, sls, dryrun; extern int cmd_parse_options(int argc, const char **argv, const char * const usage[]); -- cgit From 4adb23686795e9c88e3217b5d7b4524c0da9d04f Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Tue, 8 Mar 2022 16:30:46 +0100 Subject: objtool: Ignore extra-symbol code There's a fun implementation detail on linking STB_WEAK symbols. When the linker combines two translation units, where one contains a weak function and the other an override for it. It simply strips the STB_WEAK symbol from the symbol table, but doesn't actually remove the code. The result is that when objtool is ran in a whole-archive kind of way, it will encounter *heaps* of unused (and unreferenced) code. All rudiments of weak functions. Additionally, when a weak implementation is split into a .cold subfunction that .cold symbol is left in place, even though completely unused. Teach objtool to ignore such rudiments by searching for symbol holes; that is, code ranges that fall outside the given symbol bounds. Specifically, ignore a sequence of unreachable instruction iff they occupy a single hole, additionally ignore any .cold subfunctions referenced. Both ld.bfd and ld.lld behave like this. LTO builds otoh can (and do) properly DCE weak functions. Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://lore.kernel.org/r/20220308154319.232019347@infradead.org --- tools/objtool/check.c | 43 ++++++++++++++++++++++++++ tools/objtool/elf.c | 60 +++++++++++++++++++++++++++++++++++++ tools/objtool/include/objtool/elf.h | 1 + 3 files changed, 104 insertions(+) (limited to 'tools/objtool') diff --git a/tools/objtool/check.c b/tools/objtool/check.c index ae1d4f996803..0e0e5b5a72c8 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -3346,6 +3346,49 @@ static bool ignore_unreachable_insn(struct objtool_file *file, struct instructio !strcmp(insn->sec->name, ".altinstr_aux")) return true; + /* + * Whole archive runs might encounder dead code from weak symbols. + * This is where the linker will have dropped the weak symbol in + * favour of a regular symbol, but leaves the code in place. + * + * In this case we'll find a piece of code (whole function) that is not + * covered by a !section symbol. Ignore them. + */ + if (!insn->func && lto) { + int size = find_symbol_hole_containing(insn->sec, insn->offset); + unsigned long end = insn->offset + size; + + if (!size) /* not a hole */ + return false; + + if (size < 0) /* hole until the end */ + return true; + + sec_for_each_insn_continue(file, insn) { + /* + * If we reach a visited instruction at or before the + * end of the hole, ignore the unreachable. + */ + if (insn->visited) + return true; + + if (insn->offset >= end) + break; + + /* + * If this hole jumps to a .cold function, mark it ignore too. + */ + if (insn->jump_dest && insn->jump_dest->func && + strstr(insn->jump_dest->func->name, ".cold")) { + struct instruction *dest = insn->jump_dest; + func_for_each_insn(file, dest->func, dest) + dest->ignore = true; + } + } + + return false; + } + if (!insn->func) return false; diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index 456ac2206404..d7b99a737496 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -83,6 +83,31 @@ static int symbol_by_offset(const void *key, const struct rb_node *node) return 0; } +struct symbol_hole { + unsigned long key; + const struct symbol *sym; +}; + +/* + * Find !section symbol where @offset is after it. + */ +static int symbol_hole_by_offset(const void *key, const struct rb_node *node) +{ + const struct symbol *s = rb_entry(node, struct symbol, node); + struct symbol_hole *sh = (void *)key; + + if (sh->key < s->offset) + return -1; + + if (sh->key >= s->offset + s->len) { + if (s->type != STT_SECTION) + sh->sym = s; + return 1; + } + + return 0; +} + struct section *find_section_by_name(const struct elf *elf, const char *name) { struct section *sec; @@ -162,6 +187,41 @@ struct symbol *find_symbol_containing(const struct section *sec, unsigned long o return NULL; } +/* + * Returns size of hole starting at @offset. + */ +int find_symbol_hole_containing(const struct section *sec, unsigned long offset) +{ + struct symbol_hole hole = { + .key = offset, + .sym = NULL, + }; + struct rb_node *n; + struct symbol *s; + + /* + * Find the rightmost symbol for which @offset is after it. + */ + n = rb_find(&hole, &sec->symbol_tree, symbol_hole_by_offset); + + /* found a symbol that contains @offset */ + if (n) + return 0; /* not a hole */ + + /* didn't find a symbol for which @offset is after it */ + if (!hole.sym) + return 0; /* not a hole */ + + /* @offset >= sym->offset + sym->len, find symbol after it */ + n = rb_next(&hole.sym->node); + if (!n) + return -1; /* until end of address space */ + + /* hole until start of next symbol */ + s = rb_entry(n, struct symbol, node); + return s->offset - offset; +} + struct symbol *find_func_containing(struct section *sec, unsigned long offset) { struct rb_node *node; diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h index d22336781401..22ba7e2b816e 100644 --- a/tools/objtool/include/objtool/elf.h +++ b/tools/objtool/include/objtool/elf.h @@ -152,6 +152,7 @@ struct symbol *find_func_by_offset(struct section *sec, unsigned long offset); struct symbol *find_symbol_by_offset(struct section *sec, unsigned long offset); struct symbol *find_symbol_by_name(const struct elf *elf, const char *name); struct symbol *find_symbol_containing(const struct section *sec, unsigned long offset); +int find_symbol_hole_containing(const struct section *sec, unsigned long offset); struct reloc *find_reloc_by_dest(const struct elf *elf, struct section *sec, unsigned long offset); struct reloc *find_reloc_by_dest_range(const struct elf *elf, struct section *sec, unsigned long offset, unsigned int len); -- cgit From f9cdf7ca57cada055f61ef6d0eb4db21c3f200db Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Tue, 8 Mar 2022 16:30:47 +0100 Subject: x86: Mark stop_this_cpu() __noreturn vmlinux.o: warning: objtool: smp_stop_nmi_callback()+0x2b: unreachable instruction 0000 0000000000047cf0 : ... 0026 47d16: e8 00 00 00 00 call 47d1b 47d17: R_X86_64_PLT32 stop_this_cpu-0x4 002b 47d1b: b8 01 00 00 00 mov $0x1,%eax Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://lore.kernel.org/r/20220308154319.290905453@infradead.org --- arch/x86/include/asm/processor.h | 2 +- arch/x86/kernel/process.c | 2 +- tools/objtool/check.c | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) (limited to 'tools/objtool') diff --git a/arch/x86/include/asm/processor.h b/arch/x86/include/asm/processor.h index 2c5f12ae7d04..dd34100455d2 100644 --- a/arch/x86/include/asm/processor.h +++ b/arch/x86/include/asm/processor.h @@ -835,7 +835,7 @@ bool xen_set_default_idle(void); #define xen_set_default_idle 0 #endif -void stop_this_cpu(void *dummy); +void __noreturn stop_this_cpu(void *dummy); void microcode_check(void); enum l1tf_mitigations { diff --git a/arch/x86/kernel/process.c b/arch/x86/kernel/process.c index 81d8ef036637..a057a5c08618 100644 --- a/arch/x86/kernel/process.c +++ b/arch/x86/kernel/process.c @@ -747,7 +747,7 @@ bool xen_set_default_idle(void) } #endif -void stop_this_cpu(void *dummy) +void __noreturn stop_this_cpu(void *dummy) { local_irq_disable(); /* diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 0e0e5b5a72c8..c3ddcecdab57 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -181,6 +181,7 @@ static bool __dead_end_function(struct objtool_file *file, struct symbol *func, "kunit_try_catch_throw", "xen_start_kernel", "cpu_bringup_and_idle", + "stop_this_cpu", }; if (!func) -- cgit From eae654f1c21216daa9fbb92591c0d9f5ae46cfc5 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Tue, 8 Mar 2022 16:30:48 +0100 Subject: exit: Mark do_group_exit() __noreturn vmlinux.o: warning: objtool: get_signal()+0x108: unreachable instruction 0000 000000000007f930 : ... 0103 7fa33: e8 00 00 00 00 call 7fa38 7fa34: R_X86_64_PLT32 do_group_exit-0x4 0108 7fa38: 41 8b 45 74 mov 0x74(%r13),%eax Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://lore.kernel.org/r/20220308154319.351270711@infradead.org --- include/linux/sched/task.h | 2 +- kernel/exit.c | 2 +- tools/objtool/check.c | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) (limited to 'tools/objtool') diff --git a/include/linux/sched/task.h b/include/linux/sched/task.h index e84e54d1b490..719c9a6cac8d 100644 --- a/include/linux/sched/task.h +++ b/include/linux/sched/task.h @@ -79,7 +79,7 @@ static inline void exit_thread(struct task_struct *tsk) { } #endif -extern void do_group_exit(int); +extern __noreturn void do_group_exit(int); extern void exit_files(struct task_struct *); extern void exit_itimers(struct signal_struct *); diff --git a/kernel/exit.c b/kernel/exit.c index b00a25bb4ab9..b71f9df9074e 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -906,7 +906,7 @@ SYSCALL_DEFINE1(exit, int, error_code) * Take down every thread in the group. This is called by fatal signals * as well as by sys_exit_group (below). */ -void +void __noreturn do_group_exit(int exit_code) { struct signal_struct *sig = current->signal; diff --git a/tools/objtool/check.c b/tools/objtool/check.c index c3ddcecdab57..9896562350a8 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -181,6 +181,7 @@ static bool __dead_end_function(struct objtool_file *file, struct symbol *func, "kunit_try_catch_throw", "xen_start_kernel", "cpu_bringup_and_idle", + "do_group_exit", "stop_this_cpu", }; -- cgit From 105cd68596392cfe15056a891b0723609dcad247 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Mon, 14 Mar 2022 17:58:35 +0100 Subject: x86: Mark __invalid_creds() __noreturn vmlinux.o: warning: objtool: ksys_unshare()+0x36c: unreachable instruction 0000 0000000000067040 : ... 0364 673a4: 4c 89 ef mov %r13,%rdi 0367 673a7: e8 00 00 00 00 call 673ac 673a8: R_X86_64_PLT32 __invalid_creds-0x4 036c 673ac: e9 28 ff ff ff jmp 672d9 0371 673b1: 41 bc f4 ff ff ff mov $0xfffffff4,%r12d 0377 673b7: e9 80 fd ff ff jmp 6713c Signed-off-by: Peter Zijlstra (Intel) Link: https://lkml.kernel.org/r/Yi9gOW9f1GGwwUD6@hirez.programming.kicks-ass.net --- include/linux/cred.h | 2 +- kernel/cred.c | 2 +- tools/objtool/check.c | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) (limited to 'tools/objtool') diff --git a/include/linux/cred.h b/include/linux/cred.h index fcbc6885cc09..9ed9232af934 100644 --- a/include/linux/cred.h +++ b/include/linux/cred.h @@ -176,7 +176,7 @@ extern int set_cred_ucounts(struct cred *); * check for validity of credentials */ #ifdef CONFIG_DEBUG_CREDENTIALS -extern void __invalid_creds(const struct cred *, const char *, unsigned); +extern void __noreturn __invalid_creds(const struct cred *, const char *, unsigned); extern void __validate_process_creds(struct task_struct *, const char *, unsigned); diff --git a/kernel/cred.c b/kernel/cred.c index 933155c96922..e10c15f51c1f 100644 --- a/kernel/cred.c +++ b/kernel/cred.c @@ -870,7 +870,7 @@ static void dump_invalid_creds(const struct cred *cred, const char *label, /* * report use of invalid credentials */ -void __invalid_creds(const struct cred *cred, const char *file, unsigned line) +void __noreturn __invalid_creds(const struct cred *cred, const char *file, unsigned line) { printk(KERN_ERR "CRED: Invalid credentials\n"); printk(KERN_ERR "CRED: At %s:%u\n", file, line); diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 9896562350a8..0c857e74c852 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -183,6 +183,7 @@ static bool __dead_end_function(struct objtool_file *file, struct symbol *func, "cpu_bringup_and_idle", "do_group_exit", "stop_this_cpu", + "__invalid_creds", }; if (!func) -- cgit From 0e5b613b4d4be3345dda349fb90dd73d2103302f Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Tue, 8 Mar 2022 16:30:49 +0100 Subject: objtool: Rework ASM_REACHABLE Currently ASM_REACHABLE only works for UD2 instructions; reorder things to also allow over-riding dead_end_function(). To that end: - Mark INSN_BUG instructions in decode_instructions(), this saves having to iterate all instructions yet again. - Have add_call_destinations() set insn->dead_end for dead_end_function() calls. - Move add_dead_ends() *after* add_call_destinations() such that ASM_REACHABLE can clear the ->dead_end mark. - have validate_branch() only check ->dead_end. Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://lore.kernel.org/r/20220308154319.410010807@infradead.org --- tools/objtool/check.c | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 0c857e74c852..894c9a722555 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -395,6 +395,14 @@ static int decode_instructions(struct objtool_file *file) if (ret) goto err; + /* + * By default, "ud2" is a dead end unless otherwise + * annotated, because GCC 7 inserts it for certain + * divide-by-zero cases. + */ + if (insn->type == INSN_BUG) + insn->dead_end = true; + hash_add(file->insn_hash, &insn->hash, sec_offset_hash(sec, insn->offset)); list_add_tail(&insn->list, &file->insn_list); nr_insns++; @@ -523,14 +531,6 @@ static int add_dead_ends(struct objtool_file *file) struct reloc *reloc; struct instruction *insn; - /* - * By default, "ud2" is a dead end unless otherwise annotated, because - * GCC 7 inserts it for certain divide-by-zero cases. - */ - for_each_insn(file, insn) - if (insn->type == INSN_BUG) - insn->dead_end = true; - /* * Check for manually annotated dead ends. */ @@ -1114,6 +1114,9 @@ static void annotate_call_site(struct objtool_file *file, list_add_tail(&insn->call_node, &file->mcount_loc_list); return; } + + if (!sibling && dead_end_function(file, sym)) + insn->dead_end = true; } static void add_call_dest(struct objtool_file *file, struct instruction *insn, @@ -2089,10 +2092,6 @@ static int decode_sections(struct objtool_file *file) if (ret) return ret; - ret = add_dead_ends(file); - if (ret) - return ret; - add_ignores(file); add_uaccess_safe(file); @@ -2131,6 +2130,14 @@ static int decode_sections(struct objtool_file *file) if (ret) return ret; + /* + * Must be after add_call_destinations() such that it can override + * dead_end_function() marks. + */ + ret = add_dead_ends(file); + if (ret) + return ret; + ret = add_jump_table_alts(file); if (ret) return ret; @@ -3138,7 +3145,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, return 1; } - if (dead_end_function(file, insn->call_dest)) + if (insn->dead_end) return 0; break; -- cgit From 96db4a988d653a7f18b518c25367f7bf238f4667 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Tue, 8 Mar 2022 16:30:52 +0100 Subject: objtool: Read the NOENDBR annotation Read the new NOENDBR annotation. While there, attempt to not bloat struct instruction. Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://lore.kernel.org/r/20220308154319.586815435@infradead.org --- tools/objtool/check.c | 27 +++++++++++++++++++++++++++ tools/objtool/include/objtool/check.h | 13 ++++++++++--- 2 files changed, 37 insertions(+), 3 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 894c9a722555..63993945ff9f 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -1866,6 +1866,29 @@ static int read_unwind_hints(struct objtool_file *file) return 0; } +static int read_noendbr_hints(struct objtool_file *file) +{ + struct section *sec; + struct instruction *insn; + struct reloc *reloc; + + sec = find_section_by_name(file->elf, ".rela.discard.noendbr"); + if (!sec) + return 0; + + list_for_each_entry(reloc, &sec->reloc_list, list) { + insn = find_insn(file, reloc->sym->sec, reloc->sym->offset + reloc->addend); + if (!insn) { + WARN("bad .discard.noendbr entry"); + return -1; + } + + insn->noendbr = 1; + } + + return 0; +} + static int read_retpoline_hints(struct objtool_file *file) { struct section *sec; @@ -2099,6 +2122,10 @@ static int decode_sections(struct objtool_file *file) if (ret) return ret; + ret = read_noendbr_hints(file); + if (ret) + return ret; + /* * Must be before add_{jump_call}_destination. */ diff --git a/tools/objtool/include/objtool/check.h b/tools/objtool/include/objtool/check.h index 6cfff078897f..f10d7374f388 100644 --- a/tools/objtool/include/objtool/check.h +++ b/tools/objtool/include/objtool/check.h @@ -45,11 +45,18 @@ struct instruction { unsigned int len; enum insn_type type; unsigned long immediate; - bool dead_end, ignore, ignore_alts; - bool hint; - bool retpoline_safe; + + u8 dead_end : 1, + ignore : 1, + ignore_alts : 1, + hint : 1, + retpoline_safe : 1, + noendbr : 1; + /* 2 bit hole */ s8 instr; u8 visited; + /* u8 hole */ + struct alt_group *alt_group; struct symbol *call_dest; struct instruction *jump_dest; -- cgit From 7d209d13e7c3a3d60dc262f11a8ae4e6b4454d30 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Tue, 8 Mar 2022 16:30:53 +0100 Subject: objtool: Add IBT/ENDBR decoding Intel IBT requires the target of any indirect CALL or JMP instruction to be the ENDBR instruction; optionally it allows those two instructions to have a NOTRACK prefix in order to avoid this requirement. The kernel will not enable the use of NOTRACK, as such any occurence of it in compiler generated code should be flagged. Teach objtool to Decode ENDBR instructions and WARN about NOTRACK prefixes. Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://lore.kernel.org/r/20220308154319.645963517@infradead.org --- tools/objtool/arch/x86/decode.c | 34 +++++++++++++++++++++++++++++----- tools/objtool/include/objtool/arch.h | 1 + 2 files changed, 30 insertions(+), 5 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c index 479e769ca324..943cb41cddf7 100644 --- a/tools/objtool/arch/x86/decode.c +++ b/tools/objtool/arch/x86/decode.c @@ -103,6 +103,18 @@ unsigned long arch_jump_destination(struct instruction *insn) #define rm_is_mem(reg) (mod_is_mem() && !is_RIP() && rm_is(reg)) #define rm_is_reg(reg) (mod_is_reg() && modrm_rm == (reg)) +static bool has_notrack_prefix(struct insn *insn) +{ + int i; + + for (i = 0; i < insn->prefixes.nbytes; i++) { + if (insn->prefixes.bytes[i] == 0x3e) + return true; + } + + return false; +} + int arch_decode_instruction(struct objtool_file *file, const struct section *sec, unsigned long offset, unsigned int maxlen, unsigned int *len, enum insn_type *type, @@ -112,7 +124,7 @@ int arch_decode_instruction(struct objtool_file *file, const struct section *sec const struct elf *elf = file->elf; struct insn insn; int x86_64, ret; - unsigned char op1, op2, op3, + unsigned char op1, op2, op3, prefix, rex = 0, rex_b = 0, rex_r = 0, rex_w = 0, rex_x = 0, modrm = 0, modrm_mod = 0, modrm_rm = 0, modrm_reg = 0, sib = 0, /* sib_scale = 0, */ sib_index = 0, sib_base = 0; @@ -137,6 +149,8 @@ int arch_decode_instruction(struct objtool_file *file, const struct section *sec if (insn.vex_prefix.nbytes) return 0; + prefix = insn.prefixes.bytes[0]; + op1 = insn.opcode.bytes[0]; op2 = insn.opcode.bytes[1]; op3 = insn.opcode.bytes[2]; @@ -492,6 +506,12 @@ int arch_decode_instruction(struct objtool_file *file, const struct section *sec /* nopl/nopw */ *type = INSN_NOP; + } else if (op2 == 0x1e) { + + if (prefix == 0xf3 && (modrm == 0xfa || modrm == 0xfb)) + *type = INSN_ENDBR; + + } else if (op2 == 0x38 && op3 == 0xf8) { if (insn.prefixes.nbytes == 1 && insn.prefixes.bytes[0] == 0xf2) { @@ -636,20 +656,24 @@ int arch_decode_instruction(struct objtool_file *file, const struct section *sec break; case 0xff: - if (modrm_reg == 2 || modrm_reg == 3) + if (modrm_reg == 2 || modrm_reg == 3) { *type = INSN_CALL_DYNAMIC; + if (has_notrack_prefix(&insn)) + WARN("notrack prefix found at %s:0x%lx", sec->name, offset); - else if (modrm_reg == 4) + } else if (modrm_reg == 4) { *type = INSN_JUMP_DYNAMIC; + if (has_notrack_prefix(&insn)) + WARN("notrack prefix found at %s:0x%lx", sec->name, offset); - else if (modrm_reg == 5) + } else if (modrm_reg == 5) { /* jmpf */ *type = INSN_CONTEXT_SWITCH; - else if (modrm_reg == 6) { + } else if (modrm_reg == 6) { /* push from mem */ ADD_OP(op) { diff --git a/tools/objtool/include/objtool/arch.h b/tools/objtool/include/objtool/arch.h index 76bae3078286..9b19cc304195 100644 --- a/tools/objtool/include/objtool/arch.h +++ b/tools/objtool/include/objtool/arch.h @@ -27,6 +27,7 @@ enum insn_type { INSN_STD, INSN_CLD, INSN_TRAP, + INSN_ENDBR, INSN_OTHER, }; -- cgit From 08f87a93c8ec709698edba66a5167077181fc978 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Tue, 8 Mar 2022 16:30:54 +0100 Subject: objtool: Validate IBT assumptions Intel IBT requires that every indirect JMP/CALL targets an ENDBR instructions, failing this #CP happens and we die. Similarly, all exception entries should be ENDBR. Find all code relocations and ensure they're either an ENDBR instruction or ANNOTATE_NOENDBR. For the exceptions look for UNWIND_HINT_IRET_REGS at sym+0 not being ENDBR. Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://lore.kernel.org/r/20220308154319.705110141@infradead.org --- tools/objtool/builtin-check.c | 4 +- tools/objtool/check.c | 210 +++++++++++++++++++++++++++++++- tools/objtool/include/objtool/builtin.h | 3 +- tools/objtool/include/objtool/objtool.h | 3 + 4 files changed, 215 insertions(+), 5 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index 5c2fcaa2c260..fc6975ab8b06 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -20,7 +20,8 @@ #include bool no_fp, no_unreachable, retpoline, module, backtrace, uaccess, stats, - lto, vmlinux, mcount, noinstr, backup, sls, dryrun; + lto, vmlinux, mcount, noinstr, backup, sls, dryrun, + ibt; static const char * const check_usage[] = { "objtool check [] file.o", @@ -47,6 +48,7 @@ const struct option check_options[] = { OPT_BOOLEAN('B', "backup", &backup, "create .orig files before modification"), OPT_BOOLEAN('S', "sls", &sls, "validate straight-line-speculation"), OPT_BOOLEAN(0, "dry-run", &dryrun, "don't write the modifications"), + OPT_BOOLEAN(0, "ibt", &ibt, "validate ENDBR placement"), OPT_END(), }; diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 63993945ff9f..d4cf831edc28 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -418,8 +418,16 @@ static int decode_instructions(struct objtool_file *file) return -1; } - sym_for_each_insn(file, func, insn) + sym_for_each_insn(file, func, insn) { insn->func = func; + if (insn->type == INSN_ENDBR) { + if (insn->offset == insn->func->offset) { + file->nr_endbr++; + } else { + file->nr_endbr_int++; + } + } + } } } @@ -1171,6 +1179,19 @@ static void add_retpoline_call(struct objtool_file *file, struct instruction *in annotate_call_site(file, insn, false); } + +static bool same_function(struct instruction *insn1, struct instruction *insn2) +{ + return insn1->func->pfunc == insn2->func->pfunc; +} + +static bool is_first_func_insn(struct instruction *insn) +{ + return insn->offset == insn->func->offset || + (insn->type == INSN_ENDBR && + insn->offset == insn->func->offset + insn->len); +} + /* * Find the destination instructions for all jumps. */ @@ -1251,8 +1272,8 @@ static int add_jump_destinations(struct objtool_file *file) insn->func->cfunc = insn->jump_dest->func; insn->jump_dest->func->pfunc = insn->func; - } else if (insn->jump_dest->func->pfunc != insn->func->pfunc && - insn->jump_dest->offset == insn->jump_dest->func->offset) { + } else if (!same_function(insn, insn->jump_dest) && + is_first_func_insn(insn->jump_dest)) { /* internal sibling call (without reloc) */ add_call_dest(file, insn, insn->jump_dest->func, true); } @@ -1842,6 +1863,16 @@ static int read_unwind_hints(struct objtool_file *file) insn->hint = true; + if (ibt && hint->type == UNWIND_HINT_TYPE_REGS_PARTIAL) { + struct symbol *sym = find_symbol_by_offset(insn->sec, insn->offset); + + if (sym && sym->bind == STB_GLOBAL && + insn->type != INSN_ENDBR && !insn->noendbr) { + WARN_FUNC("UNWIND_HINT_IRET_REGS without ENDBR", + insn->sec, insn->offset); + } + } + if (hint->type == UNWIND_HINT_TYPE_FUNC) { insn->cfi = &func_cfi; continue; @@ -1883,6 +1914,9 @@ static int read_noendbr_hints(struct objtool_file *file) return -1; } + if (insn->type == INSN_ENDBR) + WARN_FUNC("ANNOTATE_NOENDBR on ENDBR", insn->sec, insn->offset); + insn->noendbr = 1; } @@ -2122,6 +2156,9 @@ static int decode_sections(struct objtool_file *file) if (ret) return ret; + /* + * Must be before read_unwind_hints() since that needs insn->noendbr. + */ ret = read_noendbr_hints(file); if (ret) return ret; @@ -3063,6 +3100,111 @@ static struct instruction *next_insn_to_validate(struct objtool_file *file, return next_insn_same_sec(file, insn); } +static struct instruction * +validate_ibt_reloc(struct objtool_file *file, struct reloc *reloc) +{ + struct instruction *dest; + struct section *sec; + unsigned long off; + + sec = reloc->sym->sec; + off = reloc->sym->offset; + + if ((reloc->sec->base->sh.sh_flags & SHF_EXECINSTR) && + (reloc->type == R_X86_64_PC32 || reloc->type == R_X86_64_PLT32)) + off += arch_dest_reloc_offset(reloc->addend); + else + off += reloc->addend; + + dest = find_insn(file, sec, off); + if (!dest) + return NULL; + + if (dest->type == INSN_ENDBR) + return NULL; + + if (reloc->sym->static_call_tramp) + return NULL; + + return dest; +} + +static void warn_noendbr(const char *msg, struct section *sec, unsigned long offset, + struct instruction *dest) +{ + WARN_FUNC("%srelocation to !ENDBR: %s+0x%lx", sec, offset, msg, + dest->func ? dest->func->name : dest->sec->name, + dest->func ? dest->offset - dest->func->offset : dest->offset); +} + +static void validate_ibt_dest(struct objtool_file *file, struct instruction *insn, + struct instruction *dest) +{ + if (dest->func && dest->func == insn->func) { + /* + * Anything from->to self is either _THIS_IP_ or IRET-to-self. + * + * There is no sane way to annotate _THIS_IP_ since the compiler treats the + * relocation as a constant and is happy to fold in offsets, skewing any + * annotation we do, leading to vast amounts of false-positives. + * + * There's also compiler generated _THIS_IP_ through KCOV and + * such which we have no hope of annotating. + * + * As such, blanket accept self-references without issue. + */ + return; + } + + if (dest->noendbr) + return; + + warn_noendbr("", insn->sec, insn->offset, dest); +} + +static void validate_ibt_insn(struct objtool_file *file, struct instruction *insn) +{ + struct instruction *dest; + struct reloc *reloc; + + switch (insn->type) { + case INSN_CALL: + case INSN_CALL_DYNAMIC: + case INSN_JUMP_CONDITIONAL: + case INSN_JUMP_UNCONDITIONAL: + case INSN_JUMP_DYNAMIC: + case INSN_JUMP_DYNAMIC_CONDITIONAL: + case INSN_RETURN: + /* + * We're looking for code references setting up indirect code + * flow. As such, ignore direct code flow and the actual + * dynamic branches. + */ + return; + + case INSN_NOP: + /* + * handle_group_alt() will create INSN_NOP instruction that + * don't belong to any section, ignore all NOP since they won't + * carry a (useful) relocation anyway. + */ + return; + + default: + break; + } + + for (reloc = insn_reloc(file, insn); + reloc; + reloc = find_reloc_by_dest_range(file->elf, insn->sec, + reloc->offset + 1, + (insn->offset + insn->len) - (reloc->offset + 1))) { + dest = validate_ibt_reloc(file, reloc); + if (dest) + validate_ibt_dest(file, insn, dest); + } +} + /* * Follow the branch starting at the given instruction, and recursively follow * any other branches (jumps). Meanwhile, track the frame pointer state at @@ -3272,6 +3414,9 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, break; } + if (ibt) + validate_ibt_insn(file, insn); + if (insn->dead_end) return 0; @@ -3557,6 +3702,53 @@ static int validate_functions(struct objtool_file *file) return warnings; } +static int validate_ibt(struct objtool_file *file) +{ + struct section *sec; + struct reloc *reloc; + + for_each_sec(file, sec) { + bool is_data; + + /* already done in validate_branch() */ + if (sec->sh.sh_flags & SHF_EXECINSTR) + continue; + + if (!sec->reloc) + continue; + + if (!strncmp(sec->name, ".orc", 4)) + continue; + + if (!strncmp(sec->name, ".discard", 8)) + continue; + + if (!strncmp(sec->name, ".debug", 6)) + continue; + + if (!strcmp(sec->name, "_error_injection_whitelist")) + continue; + + if (!strcmp(sec->name, "_kprobe_blacklist")) + continue; + + is_data = strstr(sec->name, ".data") || strstr(sec->name, ".rodata"); + + list_for_each_entry(reloc, &sec->reloc->reloc_list, list) { + struct instruction *dest; + + dest = validate_ibt_reloc(file, reloc); + if (is_data && dest && !dest->noendbr) { + warn_noendbr("data ", reloc->sym->sec, + reloc->sym->offset + reloc->addend, + dest); + } + } + } + + return 0; +} + static int validate_reachable_instructions(struct objtool_file *file) { struct instruction *insn; @@ -3584,6 +3776,11 @@ int check(struct objtool_file *file) return 1; } + if (ibt && !lto) { + fprintf(stderr, "--ibt requires: --lto\n"); + return 1; + } + arch_initial_func_cfi_state(&initial_func_cfi); init_cfi_state(&init_cfi); init_cfi_state(&func_cfi); @@ -3630,6 +3827,13 @@ int check(struct objtool_file *file) goto out; warnings += ret; + if (ibt) { + ret = validate_ibt(file); + if (ret < 0) + goto out; + warnings += ret; + } + if (!warnings) { ret = validate_reachable_instructions(file); if (ret < 0) diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h index 0cbe739ab0c8..c39dbfaef6dc 100644 --- a/tools/objtool/include/objtool/builtin.h +++ b/tools/objtool/include/objtool/builtin.h @@ -9,7 +9,8 @@ extern const struct option check_options[]; extern bool no_fp, no_unreachable, retpoline, module, backtrace, uaccess, stats, - lto, vmlinux, mcount, noinstr, backup, sls, dryrun; + lto, vmlinux, mcount, noinstr, backup, sls, dryrun, + ibt; extern int cmd_parse_options(int argc, const char **argv, const char * const usage[]); diff --git a/tools/objtool/include/objtool/objtool.h b/tools/objtool/include/objtool/objtool.h index f99fbc6078d5..fa3c7fa1ca9c 100644 --- a/tools/objtool/include/objtool/objtool.h +++ b/tools/objtool/include/objtool/objtool.h @@ -28,6 +28,9 @@ struct objtool_file { struct list_head mcount_loc_list; bool ignore_unreachables, c_file, hints, rodata; + unsigned int nr_endbr; + unsigned int nr_endbr_int; + unsigned long jl_short, jl_long; unsigned long jl_nop_short, jl_nop_long; -- cgit From 89bc853eae4ad125030ef99f207ba76c2f00a26e Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Tue, 8 Mar 2022 16:30:55 +0100 Subject: objtool: Find unused ENDBR instructions Find all ENDBR instructions which are never referenced and stick them in a section such that the kernel can poison them, sealing the functions from ever being an indirect call target. This removes about 1-in-4 ENDBR instructions. Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://lore.kernel.org/r/20220308154319.763643193@infradead.org --- arch/x86/kernel/vmlinux.lds.S | 9 +++++ tools/objtool/check.c | 69 ++++++++++++++++++++++++++++++++- tools/objtool/include/objtool/objtool.h | 1 + tools/objtool/objtool.c | 1 + 4 files changed, 78 insertions(+), 2 deletions(-) (limited to 'tools/objtool') diff --git a/arch/x86/kernel/vmlinux.lds.S b/arch/x86/kernel/vmlinux.lds.S index 27f830345b6f..7fda7f27e762 100644 --- a/arch/x86/kernel/vmlinux.lds.S +++ b/arch/x86/kernel/vmlinux.lds.S @@ -285,6 +285,15 @@ SECTIONS } #endif +#ifdef CONFIG_X86_KERNEL_IBT + . = ALIGN(8); + .ibt_endbr_seal : AT(ADDR(.ibt_endbr_seal) - LOAD_OFFSET) { + __ibt_endbr_seal = .; + *(.ibt_endbr_seal) + __ibt_endbr_seal_end = .; + } +#endif + /* * struct alt_inst entries. From the header (alternative.h): * "Alternative instructions for different CPU types or capabilities" diff --git a/tools/objtool/check.c b/tools/objtool/check.c index d4cf831edc28..6de5085e3e5a 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -383,6 +383,7 @@ static int decode_instructions(struct objtool_file *file) memset(insn, 0, sizeof(*insn)); INIT_LIST_HEAD(&insn->alts); INIT_LIST_HEAD(&insn->stack_ops); + INIT_LIST_HEAD(&insn->call_node); insn->sec = sec; insn->offset = offset; @@ -420,8 +421,9 @@ static int decode_instructions(struct objtool_file *file) sym_for_each_insn(file, func, insn) { insn->func = func; - if (insn->type == INSN_ENDBR) { + if (insn->type == INSN_ENDBR && list_empty(&insn->call_node)) { if (insn->offset == insn->func->offset) { + list_add_tail(&insn->call_node, &file->endbr_list); file->nr_endbr++; } else { file->nr_endbr_int++; @@ -742,6 +744,58 @@ static int create_retpoline_sites_sections(struct objtool_file *file) return 0; } +static int create_ibt_endbr_seal_sections(struct objtool_file *file) +{ + struct instruction *insn; + struct section *sec; + int idx; + + sec = find_section_by_name(file->elf, ".ibt_endbr_seal"); + if (sec) { + WARN("file already has .ibt_endbr_seal, skipping"); + return 0; + } + + idx = 0; + list_for_each_entry(insn, &file->endbr_list, call_node) + idx++; + + if (stats) { + printf("ibt: ENDBR at function start: %d\n", file->nr_endbr); + printf("ibt: ENDBR inside functions: %d\n", file->nr_endbr_int); + printf("ibt: superfluous ENDBR: %d\n", idx); + } + + if (!idx) + return 0; + + sec = elf_create_section(file->elf, ".ibt_endbr_seal", 0, + sizeof(int), idx); + if (!sec) { + WARN("elf_create_section: .ibt_endbr_seal"); + return -1; + } + + idx = 0; + list_for_each_entry(insn, &file->endbr_list, call_node) { + + int *site = (int *)sec->data->d_buf + idx; + *site = 0; + + if (elf_add_reloc_to_insn(file->elf, sec, + idx * sizeof(int), + R_X86_64_PC32, + insn->sec, insn->offset)) { + WARN("elf_add_reloc_to_insn: .ibt_endbr_seal"); + return -1; + } + + idx++; + } + + return 0; +} + static int create_mcount_loc_sections(struct objtool_file *file) { struct section *sec; @@ -3120,8 +3174,12 @@ validate_ibt_reloc(struct objtool_file *file, struct reloc *reloc) if (!dest) return NULL; - if (dest->type == INSN_ENDBR) + if (dest->type == INSN_ENDBR) { + if (!list_empty(&dest->call_node)) + list_del_init(&dest->call_node); + return NULL; + } if (reloc->sym->static_call_tramp) return NULL; @@ -3860,6 +3918,13 @@ int check(struct objtool_file *file) warnings += ret; } + if (ibt) { + ret = create_ibt_endbr_seal_sections(file); + if (ret < 0) + goto out; + warnings += ret; + } + if (stats) { printf("nr_insns_visited: %ld\n", nr_insns_visited); printf("nr_cfi: %ld\n", nr_cfi); diff --git a/tools/objtool/include/objtool/objtool.h b/tools/objtool/include/objtool/objtool.h index fa3c7fa1ca9c..7a5c13a78f87 100644 --- a/tools/objtool/include/objtool/objtool.h +++ b/tools/objtool/include/objtool/objtool.h @@ -26,6 +26,7 @@ struct objtool_file { struct list_head retpoline_call_list; struct list_head static_call_list; struct list_head mcount_loc_list; + struct list_head endbr_list; bool ignore_unreachables, c_file, hints, rodata; unsigned int nr_endbr; diff --git a/tools/objtool/objtool.c b/tools/objtool/objtool.c index bdf699f6552b..b09946f4e1d6 100644 --- a/tools/objtool/objtool.c +++ b/tools/objtool/objtool.c @@ -128,6 +128,7 @@ struct objtool_file *objtool_open_read(const char *_objname) INIT_LIST_HEAD(&file.retpoline_call_list); INIT_LIST_HEAD(&file.static_call_list); INIT_LIST_HEAD(&file.mcount_loc_list); + INIT_LIST_HEAD(&file.endbr_list); file.c_file = !vmlinux && find_section_by_name(file.elf, ".comment"); file.ignore_unreachables = no_unreachable; file.hints = false; -- cgit From d5ea4fece4508bf8e72b659cd22fa4840d8d61e5 Mon Sep 17 00:00:00 2001 From: Chun-Tse Shao Date: Fri, 1 Apr 2022 23:18:02 +0000 Subject: kbuild: Allow kernel installation packaging to override pkg-config Add HOSTPKG_CONFIG to allow tooling that builds the kernel to override what pkg-config and parameters are used. Signed-off-by: Chun-Tse Shao Reviewed-by: Nick Desaulniers Signed-off-by: Masahiro Yamada --- Makefile | 3 ++- certs/Makefile | 4 ++-- scripts/Makefile | 4 ++-- scripts/kconfig/gconf-cfg.sh | 12 ++++++------ scripts/kconfig/mconf-cfg.sh | 16 ++++++++-------- scripts/kconfig/nconf-cfg.sh | 16 ++++++++-------- scripts/kconfig/qconf-cfg.sh | 14 +++++++------- tools/objtool/Makefile | 4 ++-- 8 files changed, 37 insertions(+), 36 deletions(-) (limited to 'tools/objtool') diff --git a/Makefile b/Makefile index 8c7de9a72ea2..d9336e783be3 100644 --- a/Makefile +++ b/Makefile @@ -436,6 +436,7 @@ else HOSTCC = gcc HOSTCXX = g++ endif +HOSTPKG_CONFIG = pkg-config KBUILD_USERHOSTCFLAGS := -Wall -Wmissing-prototypes -Wstrict-prototypes \ -O2 -fomit-frame-pointer -std=gnu11 \ @@ -533,7 +534,7 @@ KBUILD_LDFLAGS_MODULE := KBUILD_LDFLAGS := CLANG_FLAGS := -export ARCH SRCARCH CONFIG_SHELL BASH HOSTCC KBUILD_HOSTCFLAGS CROSS_COMPILE LD CC +export ARCH SRCARCH CONFIG_SHELL BASH HOSTCC KBUILD_HOSTCFLAGS CROSS_COMPILE LD CC HOSTPKG_CONFIG export CPP AR NM STRIP OBJCOPY OBJDUMP READELF PAHOLE RESOLVE_BTFIDS LEX YACC AWK INSTALLKERNEL export PERL PYTHON3 CHECK CHECKFLAGS MAKE UTS_MACHINE HOSTCXX export KGZIP KBZIP2 KLZOP LZMA LZ4 XZ ZSTD diff --git a/certs/Makefile b/certs/Makefile index d8443cfb1c40..e4a0488133f0 100644 --- a/certs/Makefile +++ b/certs/Makefile @@ -74,5 +74,5 @@ targets += x509_revocation_list hostprogs := extract-cert -HOSTCFLAGS_extract-cert.o = $(shell pkg-config --cflags libcrypto 2> /dev/null) -HOSTLDLIBS_extract-cert = $(shell pkg-config --libs libcrypto 2> /dev/null || echo -lcrypto) +HOSTCFLAGS_extract-cert.o = $(shell $(HOSTPKG_CONFIG) --cflags libcrypto 2> /dev/null) +HOSTLDLIBS_extract-cert = $(shell $(HOSTPKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto) diff --git a/scripts/Makefile b/scripts/Makefile index ce5aa9030b74..f084f08ed176 100644 --- a/scripts/Makefile +++ b/scripts/Makefile @@ -14,8 +14,8 @@ hostprogs-always-$(CONFIG_SYSTEM_EXTRA_CERTIFICATE) += insert-sys-cert HOSTCFLAGS_sorttable.o = -I$(srctree)/tools/include HOSTLDLIBS_sorttable = -lpthread HOSTCFLAGS_asn1_compiler.o = -I$(srctree)/include -HOSTCFLAGS_sign-file.o = $(shell pkg-config --cflags libcrypto 2> /dev/null) -HOSTLDLIBS_sign-file = $(shell pkg-config --libs libcrypto 2> /dev/null || echo -lcrypto) +HOSTCFLAGS_sign-file.o = $(shell $(HOSTPKG_CONFIG) --cflags libcrypto 2> /dev/null) +HOSTLDLIBS_sign-file = $(shell $(HOSTPKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto) ifdef CONFIG_UNWINDER_ORC ifeq ($(ARCH),x86_64) diff --git a/scripts/kconfig/gconf-cfg.sh b/scripts/kconfig/gconf-cfg.sh index 480ecd8b9f41..cbd90c28c05f 100755 --- a/scripts/kconfig/gconf-cfg.sh +++ b/scripts/kconfig/gconf-cfg.sh @@ -3,14 +3,14 @@ PKG="gtk+-2.0 gmodule-2.0 libglade-2.0" -if [ -z "$(command -v pkg-config)" ]; then +if [ -z "$(command -v ${HOSTPKG_CONFIG})" ]; then echo >&2 "*" - echo >&2 "* 'make gconfig' requires 'pkg-config'. Please install it." + echo >&2 "* 'make gconfig' requires '${HOSTPKG_CONFIG}'. Please install it." echo >&2 "*" exit 1 fi -if ! pkg-config --exists $PKG; then +if ! ${HOSTPKG_CONFIG} --exists $PKG; then echo >&2 "*" echo >&2 "* Unable to find the GTK+ installation. Please make sure that" echo >&2 "* the GTK+ 2.0 development package is correctly installed." @@ -19,12 +19,12 @@ if ! pkg-config --exists $PKG; then exit 1 fi -if ! pkg-config --atleast-version=2.0.0 gtk+-2.0; then +if ! ${HOSTPKG_CONFIG} --atleast-version=2.0.0 gtk+-2.0; then echo >&2 "*" echo >&2 "* GTK+ is present but version >= 2.0.0 is required." echo >&2 "*" exit 1 fi -echo cflags=\"$(pkg-config --cflags $PKG)\" -echo libs=\"$(pkg-config --libs $PKG)\" +echo cflags=\"$(${HOSTPKG_CONFIG} --cflags $PKG)\" +echo libs=\"$(${HOSTPKG_CONFIG} --libs $PKG)\" diff --git a/scripts/kconfig/mconf-cfg.sh b/scripts/kconfig/mconf-cfg.sh index b520e407a8eb..025b565e0b7c 100755 --- a/scripts/kconfig/mconf-cfg.sh +++ b/scripts/kconfig/mconf-cfg.sh @@ -4,16 +4,16 @@ PKG="ncursesw" PKG2="ncurses" -if [ -n "$(command -v pkg-config)" ]; then - if pkg-config --exists $PKG; then - echo cflags=\"$(pkg-config --cflags $PKG)\" - echo libs=\"$(pkg-config --libs $PKG)\" +if [ -n "$(command -v ${HOSTPKG_CONFIG})" ]; then + if ${HOSTPKG_CONFIG} --exists $PKG; then + echo cflags=\"$(${HOSTPKG_CONFIG} --cflags $PKG)\" + echo libs=\"$(${HOSTPKG_CONFIG} --libs $PKG)\" exit 0 fi - if pkg-config --exists $PKG2; then - echo cflags=\"$(pkg-config --cflags $PKG2)\" - echo libs=\"$(pkg-config --libs $PKG2)\" + if ${HOSTPKG_CONFIG} --exists $PKG2; then + echo cflags=\"$(${HOSTPKG_CONFIG} --cflags $PKG2)\" + echo libs=\"$(${HOSTPKG_CONFIG} --libs $PKG2)\" exit 0 fi fi @@ -46,7 +46,7 @@ echo >&2 "* Unable to find the ncurses package." echo >&2 "* Install ncurses (ncurses-devel or libncurses-dev" echo >&2 "* depending on your distribution)." echo >&2 "*" -echo >&2 "* You may also need to install pkg-config to find the" +echo >&2 "* You may also need to install ${HOSTPKG_CONFIG} to find the" echo >&2 "* ncurses installed in a non-default location." echo >&2 "*" exit 1 diff --git a/scripts/kconfig/nconf-cfg.sh b/scripts/kconfig/nconf-cfg.sh index c212255070c0..3a10bac2adb3 100755 --- a/scripts/kconfig/nconf-cfg.sh +++ b/scripts/kconfig/nconf-cfg.sh @@ -4,16 +4,16 @@ PKG="ncursesw menuw panelw" PKG2="ncurses menu panel" -if [ -n "$(command -v pkg-config)" ]; then - if pkg-config --exists $PKG; then - echo cflags=\"$(pkg-config --cflags $PKG)\" - echo libs=\"$(pkg-config --libs $PKG)\" +if [ -n "$(command -v ${HOSTPKG_CONFIG})" ]; then + if ${HOSTPKG_CONFIG} --exists $PKG; then + echo cflags=\"$(${HOSTPKG_CONFIG} --cflags $PKG)\" + echo libs=\"$(${HOSTPKG_CONFIG} --libs $PKG)\" exit 0 fi - if pkg-config --exists $PKG2; then - echo cflags=\"$(pkg-config --cflags $PKG2)\" - echo libs=\"$(pkg-config --libs $PKG2)\" + if ${HOSTPKG_CONFIG} --exists $PKG2; then + echo cflags=\"$(${HOSTPKG_CONFIG} --cflags $PKG2)\" + echo libs=\"$(${HOSTPKG_CONFIG} --libs $PKG2)\" exit 0 fi fi @@ -44,7 +44,7 @@ echo >&2 "* Unable to find the ncurses package." echo >&2 "* Install ncurses (ncurses-devel or libncurses-dev" echo >&2 "* depending on your distribution)." echo >&2 "*" -echo >&2 "* You may also need to install pkg-config to find the" +echo >&2 "* You may also need to install ${HOSTPKG_CONFIG} to find the" echo >&2 "* ncurses installed in a non-default location." echo >&2 "*" exit 1 diff --git a/scripts/kconfig/qconf-cfg.sh b/scripts/kconfig/qconf-cfg.sh index fa564cd795b7..9b695e5cd9b3 100755 --- a/scripts/kconfig/qconf-cfg.sh +++ b/scripts/kconfig/qconf-cfg.sh @@ -3,22 +3,22 @@ PKG="Qt5Core Qt5Gui Qt5Widgets" -if [ -z "$(command -v pkg-config)" ]; then +if [ -z "$(command -v ${HOSTPKG_CONFIG})" ]; then echo >&2 "*" - echo >&2 "* 'make xconfig' requires 'pkg-config'. Please install it." + echo >&2 "* 'make xconfig' requires '${HOSTPKG_CONFIG}'. Please install it." echo >&2 "*" exit 1 fi -if pkg-config --exists $PKG; then - echo cflags=\"-std=c++11 -fPIC $(pkg-config --cflags $PKG)\" - echo libs=\"$(pkg-config --libs $PKG)\" - echo moc=\"$(pkg-config --variable=host_bins Qt5Core)/moc\" +if ${HOSTPKG_CONFIG} --exists $PKG; then + echo cflags=\"-std=c++11 -fPIC $(${HOSTPKG_CONFIG} --cflags $PKG)\" + echo libs=\"$(${HOSTPKG_CONFIG} --libs $PKG)\" + echo moc=\"$(${HOSTPKG_CONFIG} --variable=host_bins Qt5Core)/moc\" exit 0 fi echo >&2 "*" -echo >&2 "* Could not find Qt5 via pkg-config." +echo >&2 "* Could not find Qt5 via ${HOSTPKG_CONFIG}." echo >&2 "* Please install Qt5 and make sure it's in PKG_CONFIG_PATH" echo >&2 "*" exit 1 diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index 0dbd397f319d..29a92781b2ae 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -19,8 +19,8 @@ LIBSUBCMD = $(LIBSUBCMD_OUTPUT)libsubcmd.a OBJTOOL := $(OUTPUT)objtool OBJTOOL_IN := $(OBJTOOL)-in.o -LIBELF_FLAGS := $(shell pkg-config libelf --cflags 2>/dev/null) -LIBELF_LIBS := $(shell pkg-config libelf --libs 2>/dev/null || echo -lelf) +LIBELF_FLAGS := $(shell $(HOSTPKG_CONFIG) libelf --cflags 2>/dev/null) +LIBELF_LIBS := $(shell $(HOSTPKG_CONFIG) libelf --libs 2>/dev/null || echo -lelf) all: $(OBJTOOL) -- cgit From d139bca4b824ffb9731763c31b271a24b595948a Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Tue, 22 Mar 2022 12:33:31 +0100 Subject: objtool: Fix IBT tail-call detection Objtool reports: arch/x86/crypto/poly1305-x86_64.o: warning: objtool: poly1305_blocks_avx() falls through to next function poly1305_blocks_x86_64() arch/x86/crypto/poly1305-x86_64.o: warning: objtool: poly1305_emit_avx() falls through to next function poly1305_emit_x86_64() arch/x86/crypto/poly1305-x86_64.o: warning: objtool: poly1305_blocks_avx2() falls through to next function poly1305_blocks_x86_64() Which reads like: 0000000000000040 : 40: f3 0f 1e fa endbr64 ... 0000000000000400 : 400: f3 0f 1e fa endbr64 404: 44 8b 47 14 mov 0x14(%rdi),%r8d 408: 48 81 fa 80 00 00 00 cmp $0x80,%rdx 40f: 73 09 jae 41a 411: 45 85 c0 test %r8d,%r8d 414: 0f 84 2a fc ff ff je 44 ... These are simple conditional tail-calls and *should* be recognised as such by objtool, however due to a mistake in commit 08f87a93c8ec ("objtool: Validate IBT assumptions") this is failing. Specifically, the jump_dest is +4, this means the instruction pointed at will not be ENDBR and as such it will fail the second clause of is_first_func_insn() that was supposed to capture this exact case. Instead, have is_first_func_insn() look at the previous instruction. Fixes: 08f87a93c8ec ("objtool: Validate IBT assumptions") Reported-by: Stephen Rothwell Signed-off-by: Peter Zijlstra (Intel) Link: https://lkml.kernel.org/r/20220322115125.811582125@infradead.org --- tools/objtool/check.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 6de5085e3e5a..b848e1ddd5d8 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -1239,11 +1239,20 @@ static bool same_function(struct instruction *insn1, struct instruction *insn2) return insn1->func->pfunc == insn2->func->pfunc; } -static bool is_first_func_insn(struct instruction *insn) +static bool is_first_func_insn(struct objtool_file *file, struct instruction *insn) { - return insn->offset == insn->func->offset || - (insn->type == INSN_ENDBR && - insn->offset == insn->func->offset + insn->len); + if (insn->offset == insn->func->offset) + return true; + + if (ibt) { + struct instruction *prev = prev_insn_same_sym(file, insn); + + if (prev && prev->type == INSN_ENDBR && + insn->offset == insn->func->offset + prev->len) + return true; + } + + return false; } /* @@ -1327,7 +1336,7 @@ static int add_jump_destinations(struct objtool_file *file) insn->jump_dest->func->pfunc = insn->func; } else if (!same_function(insn, insn->jump_dest) && - is_first_func_insn(insn->jump_dest)) { + is_first_func_insn(file, insn->jump_dest)) { /* internal sibling call (without reloc) */ add_call_dest(file, insn, insn->jump_dest->func, true); } -- cgit From 7a53f408902d913cd541b4f8ad7dbcd4961f5b82 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Wed, 23 Mar 2022 23:35:01 +0100 Subject: objtool: Fix SLS validation for kcov tail-call replacement Since not all compilers have a function attribute to disable KCOV instrumentation, objtool can rewrite KCOV instrumentation in noinstr functions as per commit: f56dae88a81f ("objtool: Handle __sanitize_cov*() tail calls") However, this has subtle interaction with the SLS validation from commit: 1cc1e4c8aab4 ("objtool: Add straight-line-speculation validation") In that when a tail-call instrucion is replaced with a RET an additional INT3 instruction is also written, but is not represented in the decoded instruction stream. This then leads to false positive missing INT3 objtool warnings in noinstr code. Instead of adding additional struct instruction objects, mark the RET instruction with retpoline_safe to suppress the warning (since we know there really is an INT3). Fixes: 1cc1e4c8aab4 ("objtool: Add straight-line-speculation validation") Signed-off-by: Peter Zijlstra (Intel) Link: https://lkml.kernel.org/r/20220323230712.GA8939@worktop.programming.kicks-ass.net --- tools/objtool/check.c | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'tools/objtool') diff --git a/tools/objtool/check.c b/tools/objtool/check.c index b848e1ddd5d8..bd0c2c828940 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -1155,6 +1155,17 @@ static void annotate_call_site(struct objtool_file *file, : arch_nop_insn(insn->len)); insn->type = sibling ? INSN_RETURN : INSN_NOP; + + if (sibling) { + /* + * We've replaced the tail-call JMP insn by two new + * insn: RET; INT3, except we only have a single struct + * insn here. Mark it retpoline_safe to avoid the SLS + * warning, instead of adding another insn. + */ + insn->retpoline_safe = true; + } + return; } -- cgit From d4e5268a08b211b536fed29beb24271ecd85187e Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Fri, 8 Apr 2022 11:45:55 +0200 Subject: x86,objtool: Mark cpu_startup_entry() __noreturn GCC-8 isn't clever enough to figure out that cpu_start_entry() is a noreturn while objtool is. This results in code after the call in start_secondary(). Give GCC a hand so that they all agree on things. vmlinux.o: warning: objtool: start_secondary()+0x10e: unreachable Reported-by: Rick Edgecombe Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://lore.kernel.org/r/20220408094718.383658532@infradead.org --- include/linux/cpu.h | 2 +- tools/objtool/check.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'tools/objtool') diff --git a/include/linux/cpu.h b/include/linux/cpu.h index 9cf51e41e697..54dc2f9a2d56 100644 --- a/include/linux/cpu.h +++ b/include/linux/cpu.h @@ -167,7 +167,7 @@ static inline int suspend_disable_secondary_cpus(void) { return 0; } static inline void suspend_enable_secondary_cpus(void) { } #endif /* !CONFIG_PM_SLEEP_SMP */ -void cpu_startup_entry(enum cpuhp_state state); +void __noreturn cpu_startup_entry(enum cpuhp_state state); void cpu_idle_poll_ctrl(bool enable); diff --git a/tools/objtool/check.c b/tools/objtool/check.c index bd0c2c828940..e3a675d6a704 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -184,6 +184,7 @@ static bool __dead_end_function(struct objtool_file *file, struct symbol *func, "do_group_exit", "stop_this_cpu", "__invalid_creds", + "cpu_startup_entry", }; if (!func) -- cgit From 4baae989e638e9bf4b7d29bc5e36b581fddcca52 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 18 Apr 2022 09:50:29 -0700 Subject: objtool: Print data address for "!ENDBR" data warnings When a "!ENDBR" warning is reported for a data section, objtool just prints the text address of the relocation target twice, without giving any clues about the location of the original data reference: vmlinux.o: warning: objtool: dcbnl_netdevice_event()+0x0: .text+0xb64680: data relocation to !ENDBR: dcbnl_netdevice_event+0x0 Instead, print the address of the data reference, in addition to the address of the relocation target. vmlinux.o: warning: objtool: dcbnl_nb+0x0: .data..read_mostly+0xe260: data relocation to !ENDBR: dcbnl_netdevice_event+0x0 Fixes: 89bc853eae4a ("objtool: Find unused ENDBR instructions") Signed-off-by: Josh Poimboeuf Signed-off-by: Peter Zijlstra (Intel) Link: https://lkml.kernel.org/r/762e88d51300e8eaf0f933a5b0feae20ac033bea.1650300597.git.jpoimboe@redhat.com --- tools/objtool/check.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/check.c b/tools/objtool/check.c index e3a675d6a704..b822a6d5a172 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -3817,11 +3817,8 @@ static int validate_ibt(struct objtool_file *file) struct instruction *dest; dest = validate_ibt_reloc(file, reloc); - if (is_data && dest && !dest->noendbr) { - warn_noendbr("data ", reloc->sym->sec, - reloc->sym->offset + reloc->addend, - dest); - } + if (is_data && dest && !dest->noendbr) + warn_noendbr("data ", sec, reloc->offset, dest); } } -- cgit From 1d08b92fa2c41c43e4efe9787413e9ac9a434f83 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 18 Apr 2022 09:50:30 -0700 Subject: objtool: Use offstr() to print address of missing ENDBR Fixes: 89bc853eae4a ("objtool: Find unused ENDBR instructions") Signed-off-by: Josh Poimboeuf Signed-off-by: Peter Zijlstra (Intel) Link: https://lkml.kernel.org/r/95d12e800c736a3f7d08d61dabb760b2d5251a8e.1650300597.git.jpoimboe@redhat.com --- tools/objtool/check.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/check.c b/tools/objtool/check.c index b822a6d5a172..5285edd41da8 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -3211,9 +3211,8 @@ validate_ibt_reloc(struct objtool_file *file, struct reloc *reloc) static void warn_noendbr(const char *msg, struct section *sec, unsigned long offset, struct instruction *dest) { - WARN_FUNC("%srelocation to !ENDBR: %s+0x%lx", sec, offset, msg, - dest->func ? dest->func->name : dest->sec->name, - dest->func ? dest->offset - dest->func->offset : dest->offset); + WARN_FUNC("%srelocation to !ENDBR: %s", sec, offset, msg, + offstr(dest->sec, dest->offset)); } static void validate_ibt_dest(struct objtool_file *file, struct instruction *insn, -- cgit From 26ff604102c98df79c3fe2614d1b9bb068d4c28c Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 11 Apr 2022 16:10:30 -0700 Subject: objtool: Don't set 'jump_dest' for sibling calls For most sibling calls, 'jump_dest' is NULL because objtool treats the jump like a call and sets 'call_dest'. But there are a few edge cases where that's not true. Make it consistent to avoid unexpected behavior. Signed-off-by: Josh Poimboeuf Signed-off-by: Peter Zijlstra (Intel) Link: https://lkml.kernel.org/r/8737d6b9d1691831aed73375f444f0f42da3e2c9.1649718562.git.jpoimboe@redhat.com --- tools/objtool/check.c | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/check.c b/tools/objtool/check.c index bd0c2c828940..6f492789c8c0 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -1271,7 +1271,7 @@ static bool is_first_func_insn(struct objtool_file *file, struct instruction *in */ static int add_jump_destinations(struct objtool_file *file) { - struct instruction *insn; + struct instruction *insn, *jump_dest; struct reloc *reloc; struct section *dest_sec; unsigned long dest_off; @@ -1291,7 +1291,10 @@ static int add_jump_destinations(struct objtool_file *file) add_retpoline_call(file, insn); continue; } else if (insn->func) { - /* internal or external sibling call (with reloc) */ + /* + * External sibling call or internal sibling call with + * STT_FUNC reloc. + */ add_call_dest(file, insn, reloc->sym, true); continue; } else if (reloc->sym->sec->idx) { @@ -1303,8 +1306,8 @@ static int add_jump_destinations(struct objtool_file *file) continue; } - insn->jump_dest = find_insn(file, dest_sec, dest_off); - if (!insn->jump_dest) { + jump_dest = find_insn(file, dest_sec, dest_off); + if (!jump_dest) { /* * This is a special case where an alt instruction @@ -1323,8 +1326,8 @@ static int add_jump_destinations(struct objtool_file *file) /* * Cross-function jump. */ - if (insn->func && insn->jump_dest->func && - insn->func != insn->jump_dest->func) { + if (insn->func && jump_dest->func && + insn->func != jump_dest->func) { /* * For GCC 8+, create parent/child links for any cold @@ -1342,16 +1345,22 @@ static int add_jump_destinations(struct objtool_file *file) * subfunction is through a jump table. */ if (!strstr(insn->func->name, ".cold") && - strstr(insn->jump_dest->func->name, ".cold")) { - insn->func->cfunc = insn->jump_dest->func; - insn->jump_dest->func->pfunc = insn->func; + strstr(jump_dest->func->name, ".cold")) { + insn->func->cfunc = jump_dest->func; + jump_dest->func->pfunc = insn->func; - } else if (!same_function(insn, insn->jump_dest) && - is_first_func_insn(file, insn->jump_dest)) { - /* internal sibling call (without reloc) */ - add_call_dest(file, insn, insn->jump_dest->func, true); + } else if (!same_function(insn, jump_dest) && + is_first_func_insn(file, jump_dest)) { + /* + * Internal sibling call without reloc or with + * STT_SECTION reloc. + */ + add_call_dest(file, insn, jump_dest->func, true); + continue; } } + + insn->jump_dest = jump_dest; } return 0; -- cgit From 34c861e806478ac2ea4032721defbf1d6967df08 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 11 Apr 2022 16:10:31 -0700 Subject: objtool: Fix sibling call detection in alternatives In add_jump_destinations(), sibling call detection requires 'insn->func' to be valid. But alternative instructions get their 'func' set in handle_group_alt(), which runs *after* add_jump_destinations(). So sibling calls in alternatives code don't get properly detected. Fix that by changing the initialization order: call add_special_section_alts() *before* add_jump_destinations(). This also means the special case for a missing 'jump_dest' in add_jump_destinations() can be removed, as it has already been dealt with. Signed-off-by: Josh Poimboeuf Signed-off-by: Peter Zijlstra (Intel) Link: https://lkml.kernel.org/r/c02e0a0a2a4286b5f848d17c77fdcb7e0caf709c.1649718562.git.jpoimboe@redhat.com --- tools/objtool/check.c | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 6f492789c8c0..0f5d3de30e0d 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -1277,6 +1277,13 @@ static int add_jump_destinations(struct objtool_file *file) unsigned long dest_off; for_each_insn(file, insn) { + if (insn->jump_dest) { + /* + * handle_group_alt() may have previously set + * 'jump_dest' for some alternatives. + */ + continue; + } if (!is_static_jump(insn)) continue; @@ -1308,15 +1315,6 @@ static int add_jump_destinations(struct objtool_file *file) jump_dest = find_insn(file, dest_sec, dest_off); if (!jump_dest) { - - /* - * This is a special case where an alt instruction - * jumps past the end of the section. These are - * handled later in handle_group_alt(). - */ - if (!strcmp(insn->sec->name, ".altinstr_replacement")) - continue; - WARN_FUNC("can't find jump dest instruction at %s+0x%lx", insn->sec, insn->offset, dest_sec->name, dest_off); @@ -1549,13 +1547,13 @@ static int handle_group_alt(struct objtool_file *file, continue; dest_off = arch_jump_destination(insn); - if (dest_off == special_alt->new_off + special_alt->new_len) + if (dest_off == special_alt->new_off + special_alt->new_len) { insn->jump_dest = next_insn_same_sec(file, last_orig_insn); - - if (!insn->jump_dest) { - WARN_FUNC("can't find alternative jump destination", - insn->sec, insn->offset); - return -1; + if (!insn->jump_dest) { + WARN_FUNC("can't find alternative jump destination", + insn->sec, insn->offset); + return -1; + } } } @@ -2254,14 +2252,14 @@ static int decode_sections(struct objtool_file *file) return ret; /* - * Must be before add_special_section_alts() as that depends on - * jump_dest being set. + * Must be before add_jump_destinations(), which depends on 'func' + * being set for alternatives, to enable proper sibling call detection. */ - ret = add_jump_destinations(file); + ret = add_special_section_alts(file); if (ret) return ret; - ret = add_special_section_alts(file); + ret = add_jump_destinations(file); if (ret) return ret; -- cgit From 08feafe8d1958febf3a9733a3d1564d8fc23340e Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 11 Apr 2022 16:10:32 -0700 Subject: objtool: Fix function fallthrough detection for vmlinux Objtool's function fallthrough detection only works on C objects. The distinction between C and assembly objects no longer makes sense with objtool running on vmlinux.o. Now that copy_user_64.S has been fixed up, and an objtool sibling call detection bug has been fixed, the asm code is in "compliance" and this hack is no longer needed. Remove it. Fixes: ed53a0d97192 ("x86/alternative: Use .ibt_endbr_seal to seal indirect calls") Signed-off-by: Josh Poimboeuf Signed-off-by: Peter Zijlstra (Intel) Link: https://lkml.kernel.org/r/b434cff98eca3a60dcc64c620d7d5d405a0f441c.1649718562.git.jpoimboe@redhat.com --- tools/objtool/check.c | 2 +- tools/objtool/include/objtool/objtool.h | 2 +- tools/objtool/objtool.c | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 0f5d3de30e0d..5f10653eb5c2 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -3310,7 +3310,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, while (1) { next_insn = next_insn_to_validate(file, insn); - if (file->c_file && func && insn->func && func != insn->func->pfunc) { + if (func && insn->func && func != insn->func->pfunc) { WARN("%s() falls through to next function %s()", func->name, insn->func->name); return 1; diff --git a/tools/objtool/include/objtool/objtool.h b/tools/objtool/include/objtool/objtool.h index 7a5c13a78f87..a6e72d916807 100644 --- a/tools/objtool/include/objtool/objtool.h +++ b/tools/objtool/include/objtool/objtool.h @@ -27,7 +27,7 @@ struct objtool_file { struct list_head static_call_list; struct list_head mcount_loc_list; struct list_head endbr_list; - bool ignore_unreachables, c_file, hints, rodata; + bool ignore_unreachables, hints, rodata; unsigned int nr_endbr; unsigned int nr_endbr_int; diff --git a/tools/objtool/objtool.c b/tools/objtool/objtool.c index b09946f4e1d6..843ff3c2f28e 100644 --- a/tools/objtool/objtool.c +++ b/tools/objtool/objtool.c @@ -129,7 +129,6 @@ struct objtool_file *objtool_open_read(const char *_objname) INIT_LIST_HEAD(&file.static_call_list); INIT_LIST_HEAD(&file.mcount_loc_list); INIT_LIST_HEAD(&file.endbr_list); - file.c_file = !vmlinux && find_section_by_name(file.elf, ".comment"); file.ignore_unreachables = no_unreachable; file.hints = false; -- cgit From c087c6e7b551b7f208c0b852304f044954cf2bb3 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Sun, 17 Apr 2022 17:03:40 +0200 Subject: objtool: Fix type of reloc::addend Elf{32,64}_Rela::r_addend is of type: Elf{32,64}_Sword, that means that our reloc::addend needs to be long or face tuncation issues when we do elf_rebuild_reloc_section(): - 107: 48 b8 00 00 00 00 00 00 00 00 movabs $0x0,%rax 109: R_X86_64_64 level4_kernel_pgt+0x80000067 + 107: 48 b8 00 00 00 00 00 00 00 00 movabs $0x0,%rax 109: R_X86_64_64 level4_kernel_pgt-0x7fffff99 Fixes: 627fce14809b ("objtool: Add ORC unwind table generation") Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://lkml.kernel.org/r/20220419203807.596871927@infradead.org --- tools/objtool/check.c | 8 ++++---- tools/objtool/elf.c | 2 +- tools/objtool/include/objtool/elf.h | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 5f10653eb5c2..3f6785415894 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -559,12 +559,12 @@ static int add_dead_ends(struct objtool_file *file) else if (reloc->addend == reloc->sym->sec->sh.sh_size) { insn = find_last_insn(file, reloc->sym->sec); if (!insn) { - WARN("can't find unreachable insn at %s+0x%x", + WARN("can't find unreachable insn at %s+0x%lx", reloc->sym->sec->name, reloc->addend); return -1; } } else { - WARN("can't find unreachable insn at %s+0x%x", + WARN("can't find unreachable insn at %s+0x%lx", reloc->sym->sec->name, reloc->addend); return -1; } @@ -594,12 +594,12 @@ reachable: else if (reloc->addend == reloc->sym->sec->sh.sh_size) { insn = find_last_insn(file, reloc->sym->sec); if (!insn) { - WARN("can't find reachable insn at %s+0x%x", + WARN("can't find reachable insn at %s+0x%lx", reloc->sym->sec->name, reloc->addend); return -1; } } else { - WARN("can't find reachable insn at %s+0x%x", + WARN("can't find reachable insn at %s+0x%lx", reloc->sym->sec->name, reloc->addend); return -1; } diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index d7b99a737496..0cfe84ac4cdb 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -546,7 +546,7 @@ static struct section *elf_create_reloc_section(struct elf *elf, int reltype); int elf_add_reloc(struct elf *elf, struct section *sec, unsigned long offset, - unsigned int type, struct symbol *sym, int addend) + unsigned int type, struct symbol *sym, long addend) { struct reloc *reloc; diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h index 22ba7e2b816e..9b36802ed86f 100644 --- a/tools/objtool/include/objtool/elf.h +++ b/tools/objtool/include/objtool/elf.h @@ -73,7 +73,7 @@ struct reloc { struct symbol *sym; unsigned long offset; unsigned int type; - int addend; + long addend; int idx; bool jump_table_start; }; @@ -135,7 +135,7 @@ struct elf *elf_open_read(const char *name, int flags); struct section *elf_create_section(struct elf *elf, const char *name, unsigned int sh_flags, size_t entsize, int nr); int elf_add_reloc(struct elf *elf, struct section *sec, unsigned long offset, - unsigned int type, struct symbol *sym, int addend); + unsigned int type, struct symbol *sym, long addend); int elf_add_reloc_to_insn(struct elf *elf, struct section *sec, unsigned long offset, unsigned int type, struct section *insn_sec, unsigned long insn_off); -- cgit From 4abff6d48dbcea8200c7ea35ba70c242d128ebf3 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Sun, 17 Apr 2022 17:03:36 +0200 Subject: objtool: Fix code relocs vs weak symbols Occasionally objtool driven code patching (think .static_call_sites .retpoline_sites etc..) goes sideways and it tries to patch an instruction that doesn't match. Much head-scatching and cursing later the problem is as outlined below and affects every section that objtool generates for us, very much including the ORC data. The below uses .static_call_sites because it's convenient for demonstration purposes, but as mentioned the ORC sections, .retpoline_sites and __mount_loc are all similarly affected. Consider: foo-weak.c: extern void __SCT__foo(void); __attribute__((weak)) void foo(void) { return __SCT__foo(); } foo.c: extern void __SCT__foo(void); extern void my_foo(void); void foo(void) { my_foo(); return __SCT__foo(); } These generate the obvious code (gcc -O2 -fcf-protection=none -fno-asynchronous-unwind-tables -c foo*.c): foo-weak.o: 0000000000000000 : 0: e9 00 00 00 00 jmpq 5 1: R_X86_64_PLT32 __SCT__foo-0x4 foo.o: 0000000000000000 : 0: 48 83 ec 08 sub $0x8,%rsp 4: e8 00 00 00 00 callq 9 5: R_X86_64_PLT32 my_foo-0x4 9: 48 83 c4 08 add $0x8,%rsp d: e9 00 00 00 00 jmpq 12 e: R_X86_64_PLT32 __SCT__foo-0x4 Now, when we link these two files together, you get something like (ld -r -o foos.o foo-weak.o foo.o): foos.o: 0000000000000000 : 0: e9 00 00 00 00 jmpq 5 1: R_X86_64_PLT32 __SCT__foo-0x4 5: 66 2e 0f 1f 84 00 00 00 00 00 nopw %cs:0x0(%rax,%rax,1) f: 90 nop 0000000000000010 : 10: 48 83 ec 08 sub $0x8,%rsp 14: e8 00 00 00 00 callq 19 15: R_X86_64_PLT32 my_foo-0x4 19: 48 83 c4 08 add $0x8,%rsp 1d: e9 00 00 00 00 jmpq 22 1e: R_X86_64_PLT32 __SCT__foo-0x4 Noting that ld preserves the weak function text, but strips the symbol off of it (hence objdump doing that funny negative offset thing). This does lead to 'interesting' unused code issues with objtool when ran on linked objects, but that seems to be working (fingers crossed). So far so good.. Now lets consider the objtool static_call output section (readelf output, old binutils): foo-weak.o: Relocation section '.rela.static_call_sites' at offset 0x2c8 contains 1 entry: Offset Info Type Symbol's Value Symbol's Name + Addend 0000000000000000 0000000200000002 R_X86_64_PC32 0000000000000000 .text + 0 0000000000000004 0000000d00000002 R_X86_64_PC32 0000000000000000 __SCT__foo + 1 foo.o: Relocation section '.rela.static_call_sites' at offset 0x310 contains 2 entries: Offset Info Type Symbol's Value Symbol's Name + Addend 0000000000000000 0000000200000002 R_X86_64_PC32 0000000000000000 .text + d 0000000000000004 0000000d00000002 R_X86_64_PC32 0000000000000000 __SCT__foo + 1 foos.o: Relocation section '.rela.static_call_sites' at offset 0x430 contains 4 entries: Offset Info Type Symbol's Value Symbol's Name + Addend 0000000000000000 0000000100000002 R_X86_64_PC32 0000000000000000 .text + 0 0000000000000004 0000000d00000002 R_X86_64_PC32 0000000000000000 __SCT__foo + 1 0000000000000008 0000000100000002 R_X86_64_PC32 0000000000000000 .text + 1d 000000000000000c 0000000d00000002 R_X86_64_PC32 0000000000000000 __SCT__foo + 1 So we have two patch sites, one in the dead code of the weak foo and one in the real foo. All is well. *HOWEVER*, when the toolchain strips unused section symbols it generates things like this (using new enough binutils): foo-weak.o: Relocation section '.rela.static_call_sites' at offset 0x2c8 contains 1 entry: Offset Info Type Symbol's Value Symbol's Name + Addend 0000000000000000 0000000200000002 R_X86_64_PC32 0000000000000000 foo + 0 0000000000000004 0000000d00000002 R_X86_64_PC32 0000000000000000 __SCT__foo + 1 foo.o: Relocation section '.rela.static_call_sites' at offset 0x310 contains 2 entries: Offset Info Type Symbol's Value Symbol's Name + Addend 0000000000000000 0000000200000002 R_X86_64_PC32 0000000000000000 foo + d 0000000000000004 0000000d00000002 R_X86_64_PC32 0000000000000000 __SCT__foo + 1 foos.o: Relocation section '.rela.static_call_sites' at offset 0x430 contains 4 entries: Offset Info Type Symbol's Value Symbol's Name + Addend 0000000000000000 0000000100000002 R_X86_64_PC32 0000000000000000 foo + 0 0000000000000004 0000000d00000002 R_X86_64_PC32 0000000000000000 __SCT__foo + 1 0000000000000008 0000000100000002 R_X86_64_PC32 0000000000000000 foo + d 000000000000000c 0000000d00000002 R_X86_64_PC32 0000000000000000 __SCT__foo + 1 And now we can see how that foos.o .static_call_sites goes side-ways, we now have _two_ patch sites in foo. One for the weak symbol at foo+0 (which is no longer a static_call site!) and one at foo+d which is in fact the right location. This seems to happen when objtool cannot find a section symbol, in which case it falls back to any other symbol to key off of, however in this case that goes terribly wrong! As such, teach objtool to create a section symbol when there isn't one. Fixes: 44f6a7c0755d ("objtool: Fix seg fault with Clang non-section symbols") Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://lkml.kernel.org/r/20220419203807.655552918@infradead.org --- tools/objtool/elf.c | 187 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 165 insertions(+), 22 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index 0cfe84ac4cdb..ebf2ba5755c1 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -575,37 +575,180 @@ int elf_add_reloc(struct elf *elf, struct section *sec, unsigned long offset, return 0; } -int elf_add_reloc_to_insn(struct elf *elf, struct section *sec, - unsigned long offset, unsigned int type, - struct section *insn_sec, unsigned long insn_off) +/* + * Ensure that any reloc section containing references to @sym is marked + * changed such that it will get re-generated in elf_rebuild_reloc_sections() + * with the new symbol index. + */ +static void elf_dirty_reloc_sym(struct elf *elf, struct symbol *sym) +{ + struct section *sec; + + list_for_each_entry(sec, &elf->sections, list) { + struct reloc *reloc; + + if (sec->changed) + continue; + + list_for_each_entry(reloc, &sec->reloc_list, list) { + if (reloc->sym == sym) { + sec->changed = true; + break; + } + } + } +} + +/* + * Move the first global symbol, as per sh_info, into a new, higher symbol + * index. This fees up the shndx for a new local symbol. + */ +static int elf_move_global_symbol(struct elf *elf, struct section *symtab, + struct section *symtab_shndx) { + Elf_Data *data, *shndx_data = NULL; + Elf32_Word first_non_local; struct symbol *sym; - int addend; + Elf_Scn *s; - if (insn_sec->sym) { - sym = insn_sec->sym; - addend = insn_off; + first_non_local = symtab->sh.sh_info; - } else { - /* - * The Clang assembler strips section symbols, so we have to - * reference the function symbol instead: - */ - sym = find_symbol_containing(insn_sec, insn_off); - if (!sym) { - /* - * Hack alert. This happens when we need to reference - * the NOP pad insn immediately after the function. - */ - sym = find_symbol_containing(insn_sec, insn_off - 1); + sym = find_symbol_by_index(elf, first_non_local); + if (!sym) { + WARN("no non-local symbols !?"); + return first_non_local; + } + + s = elf_getscn(elf->elf, symtab->idx); + if (!s) { + WARN_ELF("elf_getscn"); + return -1; + } + + data = elf_newdata(s); + if (!data) { + WARN_ELF("elf_newdata"); + return -1; + } + + data->d_buf = &sym->sym; + data->d_size = sizeof(sym->sym); + data->d_align = 1; + data->d_type = ELF_T_SYM; + + sym->idx = symtab->sh.sh_size / sizeof(sym->sym); + elf_dirty_reloc_sym(elf, sym); + + symtab->sh.sh_info += 1; + symtab->sh.sh_size += data->d_size; + symtab->changed = true; + + if (symtab_shndx) { + s = elf_getscn(elf->elf, symtab_shndx->idx); + if (!s) { + WARN_ELF("elf_getscn"); + return -1; } - if (!sym) { - WARN("can't find symbol containing %s+0x%lx", insn_sec->name, insn_off); + shndx_data = elf_newdata(s); + if (!shndx_data) { + WARN_ELF("elf_newshndx_data"); return -1; } - addend = insn_off - sym->offset; + shndx_data->d_buf = &sym->sec->idx; + shndx_data->d_size = sizeof(Elf32_Word); + shndx_data->d_align = 4; + shndx_data->d_type = ELF_T_WORD; + + symtab_shndx->sh.sh_size += 4; + symtab_shndx->changed = true; + } + + return first_non_local; +} + +static struct symbol * +elf_create_section_symbol(struct elf *elf, struct section *sec) +{ + struct section *symtab, *symtab_shndx; + Elf_Data *shndx_data = NULL; + struct symbol *sym; + Elf32_Word shndx; + + symtab = find_section_by_name(elf, ".symtab"); + if (symtab) { + symtab_shndx = find_section_by_name(elf, ".symtab_shndx"); + if (symtab_shndx) + shndx_data = symtab_shndx->data; + } else { + WARN("no .symtab"); + return NULL; + } + + sym = malloc(sizeof(*sym)); + if (!sym) { + perror("malloc"); + return NULL; + } + memset(sym, 0, sizeof(*sym)); + + sym->idx = elf_move_global_symbol(elf, symtab, symtab_shndx); + if (sym->idx < 0) { + WARN("elf_move_global_symbol"); + return NULL; + } + + sym->name = sec->name; + sym->sec = sec; + + // st_name 0 + sym->sym.st_info = GELF_ST_INFO(STB_LOCAL, STT_SECTION); + // st_other 0 + // st_value 0 + // st_size 0 + shndx = sec->idx; + if (shndx >= SHN_UNDEF && shndx < SHN_LORESERVE) { + sym->sym.st_shndx = shndx; + if (!shndx_data) + shndx = 0; + } else { + sym->sym.st_shndx = SHN_XINDEX; + if (!shndx_data) { + WARN("no .symtab_shndx"); + return NULL; + } + } + + if (!gelf_update_symshndx(symtab->data, shndx_data, sym->idx, &sym->sym, shndx)) { + WARN_ELF("gelf_update_symshndx"); + return NULL; + } + + elf_add_symbol(elf, sym); + + return sym; +} + +int elf_add_reloc_to_insn(struct elf *elf, struct section *sec, + unsigned long offset, unsigned int type, + struct section *insn_sec, unsigned long insn_off) +{ + struct symbol *sym = insn_sec->sym; + int addend = insn_off; + + if (!sym) { + /* + * Due to how weak functions work, we must use section based + * relocations. Symbol based relocations would result in the + * weak and non-weak function annotations being overlaid on the + * non-weak function after linking. + */ + sym = elf_create_section_symbol(elf, insn_sec); + if (!sym) + return -1; + + insn_sec->sym = sym; } return elf_add_reloc(elf, sec, offset, type, sym, addend); -- cgit From 2daf7faba7ded8703e4b4ebc8b161f22272fc91a Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 18 Apr 2022 09:50:26 -0700 Subject: objtool: Reorganize cmdline options Split the existing options into two groups: actions, which actually do something; and options, which modify the actions in some way. Also there's no need to have short flags for all the non-action options. Reserve short flags for the more important actions. While at it: - change a few of the short flags to be more intuitive - make option descriptions more consistently descriptive - sort options in the source like they are when printed - move options to a global struct Signed-off-by: Josh Poimboeuf Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Miroslav Benes Link: https://lkml.kernel.org/r/9dcaa752f83aca24b1b21f0b0eeb28a0c181c0b0.1650300597.git.jpoimboe@redhat.com --- scripts/Makefile.build | 10 +++--- scripts/link-vmlinux.sh | 30 ++++++++++------ tools/objtool/arch/x86/decode.c | 2 +- tools/objtool/arch/x86/special.c | 2 +- tools/objtool/builtin-check.c | 40 +++++++++++---------- tools/objtool/check.c | 62 ++++++++++++++++----------------- tools/objtool/elf.c | 8 ++--- tools/objtool/include/objtool/builtin.h | 26 ++++++++++++-- tools/objtool/objtool.c | 6 ++-- 9 files changed, 108 insertions(+), 78 deletions(-) (limited to 'tools/objtool') diff --git a/scripts/Makefile.build b/scripts/Makefile.build index 33c1ed581522..dd9d582808d6 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -228,14 +228,14 @@ objtool := $(objtree)/tools/objtool/objtool objtool_args = \ $(if $(CONFIG_UNWINDER_ORC),orc generate,check) \ - $(if $(part-of-module), --module) \ $(if $(CONFIG_X86_KERNEL_IBT), --lto --ibt) \ - $(if $(CONFIG_FRAME_POINTER),, --no-fp) \ - $(if $(CONFIG_GCOV_KERNEL), --no-unreachable) \ + $(if $(CONFIG_FTRACE_MCOUNT_USE_OBJTOOL), --mcount) \ $(if $(CONFIG_RETPOLINE), --retpoline) \ + $(if $(CONFIG_SLS), --sls) \ $(if $(CONFIG_X86_SMAP), --uaccess) \ - $(if $(CONFIG_FTRACE_MCOUNT_USE_OBJTOOL), --mcount) \ - $(if $(CONFIG_SLS), --sls) + $(if $(part-of-module), --module) \ + $(if $(CONFIG_FRAME_POINTER),, --no-fp) \ + $(if $(CONFIG_GCOV_KERNEL), --no-unreachable) cmd_objtool = $(if $(objtool-enabled), ; $(objtool) $(objtool_args) $@) cmd_gen_objtooldep = $(if $(objtool-enabled), { echo ; echo '$@: $$(wildcard $(objtool))' ; } >> $(dot-target).cmd) diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh index 9361a1ef02c9..c6e9fef61b11 100755 --- a/scripts/link-vmlinux.sh +++ b/scripts/link-vmlinux.sh @@ -117,8 +117,6 @@ objtool_link() objtoolcmd="orc generate" fi - objtoolopt="${objtoolopt} --lto" - if is_enabled CONFIG_X86_KERNEL_IBT; then objtoolopt="${objtoolopt} --ibt" fi @@ -126,6 +124,8 @@ objtool_link() if is_enabled CONFIG_FTRACE_MCOUNT_USE_OBJTOOL; then objtoolopt="${objtoolopt} --mcount" fi + + objtoolopt="${objtoolopt} --lto" fi if is_enabled CONFIG_VMLINUX_VALIDATION; then @@ -133,25 +133,33 @@ objtool_link() fi if [ -n "${objtoolopt}" ]; then + if [ -z "${objtoolcmd}" ]; then objtoolcmd="check" fi - objtoolopt="${objtoolopt} --vmlinux" - if ! is_enabled CONFIG_FRAME_POINTER; then - objtoolopt="${objtoolopt} --no-fp" - fi - if is_enabled CONFIG_GCOV_KERNEL; then - objtoolopt="${objtoolopt} --no-unreachable" - fi + if is_enabled CONFIG_RETPOLINE; then objtoolopt="${objtoolopt} --retpoline" fi + + if is_enabled CONFIG_SLS; then + objtoolopt="${objtoolopt} --sls" + fi + if is_enabled CONFIG_X86_SMAP; then objtoolopt="${objtoolopt} --uaccess" fi - if is_enabled CONFIG_SLS; then - objtoolopt="${objtoolopt} --sls" + + if ! is_enabled CONFIG_FRAME_POINTER; then + objtoolopt="${objtoolopt} --no-fp" fi + + if is_enabled CONFIG_GCOV_KERNEL; then + objtoolopt="${objtoolopt} --no-unreachable" + fi + + objtoolopt="${objtoolopt} --vmlinux" + info OBJTOOL ${1} tools/objtool/objtool ${objtoolcmd} ${objtoolopt} ${1} fi diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c index 943cb41cddf7..8b990a52aada 100644 --- a/tools/objtool/arch/x86/decode.c +++ b/tools/objtool/arch/x86/decode.c @@ -581,7 +581,7 @@ int arch_decode_instruction(struct objtool_file *file, const struct section *sec break; case 0xc7: /* mov imm, r/m */ - if (!noinstr) + if (!opts.noinstr) break; if (insn.length == 3+4+4 && !strncmp(sec->name, ".init.text", 10)) { diff --git a/tools/objtool/arch/x86/special.c b/tools/objtool/arch/x86/special.c index e707d9bcd161..7c97b7391279 100644 --- a/tools/objtool/arch/x86/special.c +++ b/tools/objtool/arch/x86/special.c @@ -20,7 +20,7 @@ void arch_handle_alternative(unsigned short feature, struct special_alt *alt) * find paths that see the STAC but take the NOP instead of * CLAC and the other way around. */ - if (uaccess) + if (opts.uaccess) alt->skip_orig = true; else alt->skip_alt = true; diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index fc6975ab8b06..bc447b3cd9f2 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -19,12 +19,10 @@ #include #include -bool no_fp, no_unreachable, retpoline, module, backtrace, uaccess, stats, - lto, vmlinux, mcount, noinstr, backup, sls, dryrun, - ibt; +struct opts opts; static const char * const check_usage[] = { - "objtool check [] file.o", + "objtool check [] file.o", NULL, }; @@ -34,21 +32,25 @@ static const char * const env_usage[] = { }; const struct option check_options[] = { - OPT_BOOLEAN('f', "no-fp", &no_fp, "Skip frame pointer validation"), - OPT_BOOLEAN('u', "no-unreachable", &no_unreachable, "Skip 'unreachable instruction' warnings"), - OPT_BOOLEAN('r', "retpoline", &retpoline, "Validate retpoline assumptions"), - OPT_BOOLEAN('m', "module", &module, "Indicates the object will be part of a kernel module"), - OPT_BOOLEAN('b', "backtrace", &backtrace, "unwind on error"), - OPT_BOOLEAN('a', "uaccess", &uaccess, "enable uaccess checking"), - OPT_BOOLEAN('s', "stats", &stats, "print statistics"), - OPT_BOOLEAN(0, "lto", <o, "whole-archive like runs"), - OPT_BOOLEAN('n', "noinstr", &noinstr, "noinstr validation for vmlinux.o"), - OPT_BOOLEAN('l', "vmlinux", &vmlinux, "vmlinux.o validation"), - OPT_BOOLEAN('M', "mcount", &mcount, "generate __mcount_loc"), - OPT_BOOLEAN('B', "backup", &backup, "create .orig files before modification"), - OPT_BOOLEAN('S', "sls", &sls, "validate straight-line-speculation"), - OPT_BOOLEAN(0, "dry-run", &dryrun, "don't write the modifications"), - OPT_BOOLEAN(0, "ibt", &ibt, "validate ENDBR placement"), + OPT_GROUP("Actions:"), + OPT_BOOLEAN('i', "ibt", &opts.ibt, "validate and annotate IBT"), + OPT_BOOLEAN('m', "mcount", &opts.mcount, "annotate mcount/fentry calls for ftrace"), + OPT_BOOLEAN('n', "noinstr", &opts.noinstr, "validate noinstr rules"), + OPT_BOOLEAN('r', "retpoline", &opts.retpoline, "validate and annotate retpoline usage"), + OPT_BOOLEAN('l', "sls", &opts.sls, "validate straight-line-speculation mitigations"), + OPT_BOOLEAN('u', "uaccess", &opts.uaccess, "validate uaccess rules for SMAP"), + + OPT_GROUP("Options:"), + OPT_BOOLEAN(0, "backtrace", &opts.backtrace, "unwind on error"), + OPT_BOOLEAN(0, "backup", &opts.backup, "create .orig files before modification"), + OPT_BOOLEAN(0, "dry-run", &opts.dryrun, "don't write modifications"), + OPT_BOOLEAN(0, "lto", &opts.lto, "whole-archive like runs"), + OPT_BOOLEAN(0, "module", &opts.module, "object is part of a kernel module"), + OPT_BOOLEAN(0, "no-fp", &opts.no_fp, "skip frame pointer validation"), + OPT_BOOLEAN(0, "no-unreachable", &opts.no_unreachable, "skip 'unreachable instruction' warnings"), + OPT_BOOLEAN(0, "stats", &opts.stats, "print statistics"), + OPT_BOOLEAN(0, "vmlinux", &opts.vmlinux, "vmlinux.o validation"), + OPT_END(), }; diff --git a/tools/objtool/check.c b/tools/objtool/check.c index ca5b74603008..3362cc3bdf1a 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -273,7 +273,7 @@ static void init_insn_state(struct insn_state *state, struct section *sec) * not correctly determine insn->call_dest->sec (external symbols do * not have a section). */ - if (vmlinux && noinstr && sec) + if (opts.vmlinux && opts.noinstr && sec) state->noinstr = sec->noinstr; } @@ -339,7 +339,7 @@ static void *cfi_hash_alloc(unsigned long size) if (cfi_hash == (void *)-1L) { WARN("mmap fail cfi_hash"); cfi_hash = NULL; - } else if (stats) { + } else if (opts.stats) { printf("cfi_bits: %d\n", cfi_bits); } @@ -434,7 +434,7 @@ static int decode_instructions(struct objtool_file *file) } } - if (stats) + if (opts.stats) printf("nr_insns: %lu\n", nr_insns); return 0; @@ -497,7 +497,7 @@ static int init_pv_ops(struct objtool_file *file) struct symbol *sym; int idx, nr; - if (!noinstr) + if (!opts.noinstr) return 0; file->pv_ops = NULL; @@ -668,7 +668,7 @@ static int create_static_call_sections(struct objtool_file *file) key_sym = find_symbol_by_name(file->elf, tmp); if (!key_sym) { - if (!module) { + if (!opts.module) { WARN("static_call: can't find static_call_key symbol: %s", tmp); return -1; } @@ -761,7 +761,7 @@ static int create_ibt_endbr_seal_sections(struct objtool_file *file) list_for_each_entry(insn, &file->endbr_list, call_node) idx++; - if (stats) { + if (opts.stats) { printf("ibt: ENDBR at function start: %d\n", file->nr_endbr); printf("ibt: ENDBR inside functions: %d\n", file->nr_endbr_int); printf("ibt: superfluous ENDBR: %d\n", idx); @@ -1028,7 +1028,7 @@ static void add_uaccess_safe(struct objtool_file *file) struct symbol *func; const char **name; - if (!uaccess) + if (!opts.uaccess) return; for (name = uaccess_safe_builtin; *name; name++) { @@ -1170,7 +1170,7 @@ static void annotate_call_site(struct objtool_file *file, return; } - if (mcount && sym->fentry) { + if (opts.mcount && sym->fentry) { if (sibling) WARN_FUNC("Tail call to __fentry__ !?!?", insn->sec, insn->offset); @@ -1256,7 +1256,7 @@ static bool is_first_func_insn(struct objtool_file *file, struct instruction *in if (insn->offset == insn->func->offset) return true; - if (ibt) { + if (opts.ibt) { struct instruction *prev = prev_insn_same_sym(file, insn); if (prev && prev->type == INSN_ENDBR && @@ -1699,7 +1699,7 @@ static int add_special_section_alts(struct objtool_file *file) free(special_alt); } - if (stats) { + if (opts.stats) { printf("jl\\\tNOP\tJMP\n"); printf("short:\t%ld\t%ld\n", file->jl_nop_short, file->jl_short); printf("long:\t%ld\t%ld\n", file->jl_nop_long, file->jl_long); @@ -1945,7 +1945,7 @@ static int read_unwind_hints(struct objtool_file *file) insn->hint = true; - if (ibt && hint->type == UNWIND_HINT_TYPE_REGS_PARTIAL) { + if (opts.ibt && hint->type == UNWIND_HINT_TYPE_REGS_PARTIAL) { struct symbol *sym = find_symbol_by_offset(insn->sec, insn->offset); if (sym && sym->bind == STB_GLOBAL && @@ -2806,7 +2806,7 @@ static int update_cfi_state(struct instruction *insn, } /* detect when asm code uses rbp as a scratch register */ - if (!no_fp && insn->func && op->src.reg == CFI_BP && + if (!opts.no_fp && insn->func && op->src.reg == CFI_BP && cfa->base != CFI_BP) cfi->bp_scratch = true; break; @@ -3363,7 +3363,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, ret = validate_branch(file, func, alt->insn, state); if (ret) { - if (backtrace) + if (opts.backtrace) BT_FUNC("(alt)", insn); return ret; } @@ -3379,7 +3379,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, switch (insn->type) { case INSN_RETURN: - if (sls && !insn->retpoline_safe && + if (opts.sls && !insn->retpoline_safe && next_insn && next_insn->type != INSN_TRAP) { WARN_FUNC("missing int3 after ret", insn->sec, insn->offset); @@ -3392,7 +3392,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, if (ret) return ret; - if (!no_fp && func && !is_fentry_call(insn) && + if (!opts.no_fp && func && !is_fentry_call(insn) && !has_valid_stack_frame(&state)) { WARN_FUNC("call without frame pointer save/setup", sec, insn->offset); @@ -3415,7 +3415,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, ret = validate_branch(file, func, insn->jump_dest, state); if (ret) { - if (backtrace) + if (opts.backtrace) BT_FUNC("(branch)", insn); return ret; } @@ -3427,7 +3427,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, break; case INSN_JUMP_DYNAMIC: - if (sls && !insn->retpoline_safe && + if (opts.sls && !insn->retpoline_safe && next_insn && next_insn->type != INSN_TRAP) { WARN_FUNC("missing int3 after indirect jump", insn->sec, insn->offset); @@ -3499,7 +3499,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, break; } - if (ibt) + if (opts.ibt) validate_ibt_insn(file, insn); if (insn->dead_end) @@ -3541,7 +3541,7 @@ static int validate_unwind_hints(struct objtool_file *file, struct section *sec) while (&insn->list != &file->insn_list && (!sec || insn->sec == sec)) { if (insn->hint && !insn->visited && !insn->ignore) { ret = validate_branch(file, insn->func, insn, state); - if (ret && backtrace) + if (ret && opts.backtrace) BT_FUNC("<=== (hint)", insn); warnings += ret; } @@ -3571,7 +3571,7 @@ static int validate_retpoline(struct objtool_file *file) * loaded late, they very much do need retpoline in their * .init.text */ - if (!strcmp(insn->sec->name, ".init.text") && !module) + if (!strcmp(insn->sec->name, ".init.text") && !opts.module) continue; WARN_FUNC("indirect %s found in RETPOLINE build", @@ -3621,7 +3621,7 @@ static bool ignore_unreachable_insn(struct objtool_file *file, struct instructio * In this case we'll find a piece of code (whole function) that is not * covered by a !section symbol. Ignore them. */ - if (!insn->func && lto) { + if (!insn->func && opts.lto) { int size = find_symbol_hole_containing(insn->sec, insn->offset); unsigned long end = insn->offset + size; @@ -3728,7 +3728,7 @@ static int validate_symbol(struct objtool_file *file, struct section *sec, state->uaccess = sym->uaccess_safe; ret = validate_branch(file, insn->func, insn, *state); - if (ret && backtrace) + if (ret && opts.backtrace) BT_FUNC("<=== (sym)", insn); return ret; } @@ -3853,12 +3853,12 @@ int check(struct objtool_file *file) { int ret, warnings = 0; - if (lto && !(vmlinux || module)) { + if (opts.lto && !(opts.vmlinux || opts.module)) { fprintf(stderr, "--lto requires: --vmlinux or --module\n"); return 1; } - if (ibt && !lto) { + if (opts.ibt && !opts.lto) { fprintf(stderr, "--ibt requires: --lto\n"); return 1; } @@ -3883,7 +3883,7 @@ int check(struct objtool_file *file) if (list_empty(&file->insn_list)) goto out; - if (vmlinux && !lto) { + if (opts.vmlinux && !opts.lto) { ret = validate_vmlinux_functions(file); if (ret < 0) goto out; @@ -3892,7 +3892,7 @@ int check(struct objtool_file *file) goto out; } - if (retpoline) { + if (opts.retpoline) { ret = validate_retpoline(file); if (ret < 0) return ret; @@ -3909,7 +3909,7 @@ int check(struct objtool_file *file) goto out; warnings += ret; - if (ibt) { + if (opts.ibt) { ret = validate_ibt(file); if (ret < 0) goto out; @@ -3928,28 +3928,28 @@ int check(struct objtool_file *file) goto out; warnings += ret; - if (retpoline) { + if (opts.retpoline) { ret = create_retpoline_sites_sections(file); if (ret < 0) goto out; warnings += ret; } - if (mcount) { + if (opts.mcount) { ret = create_mcount_loc_sections(file); if (ret < 0) goto out; warnings += ret; } - if (ibt) { + if (opts.ibt) { ret = create_ibt_endbr_seal_sections(file); if (ret < 0) goto out; warnings += ret; } - if (stats) { + if (opts.stats) { printf("nr_insns_visited: %ld\n", nr_insns_visited); printf("nr_cfi: %ld\n", nr_cfi); printf("nr_cfi_reused: %ld\n", nr_cfi_reused); diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index ebf2ba5755c1..0f6fa372e10e 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -355,7 +355,7 @@ static int read_sections(struct elf *elf) elf_hash_add(section_name, &sec->name_hash, str_hash(sec->name)); } - if (stats) { + if (opts.stats) { printf("nr_sections: %lu\n", (unsigned long)sections_nr); printf("section_bits: %d\n", elf->section_bits); } @@ -475,7 +475,7 @@ static int read_symbols(struct elf *elf) elf_add_symbol(elf, sym); } - if (stats) { + if (opts.stats) { printf("nr_symbols: %lu\n", (unsigned long)symbols_nr); printf("symbol_bits: %d\n", elf->symbol_bits); } @@ -843,7 +843,7 @@ static int read_relocs(struct elf *elf) tot_reloc += nr_reloc; } - if (stats) { + if (opts.stats) { printf("max_reloc: %lu\n", max_reloc); printf("tot_reloc: %lu\n", tot_reloc); printf("reloc_bits: %d\n", elf->reloc_bits); @@ -1222,7 +1222,7 @@ int elf_write(struct elf *elf) struct section *sec; Elf_Scn *s; - if (dryrun) + if (opts.dryrun) return 0; /* Update changed relocation sections and section headers: */ diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h index c39dbfaef6dc..87c1a7351e3c 100644 --- a/tools/objtool/include/objtool/builtin.h +++ b/tools/objtool/include/objtool/builtin.h @@ -8,9 +8,29 @@ #include extern const struct option check_options[]; -extern bool no_fp, no_unreachable, retpoline, module, backtrace, uaccess, stats, - lto, vmlinux, mcount, noinstr, backup, sls, dryrun, - ibt; + +struct opts { + /* actions: */ + bool ibt; + bool mcount; + bool noinstr; + bool retpoline; + bool sls; + bool uaccess; + + /* options: */ + bool backtrace; + bool backup; + bool dryrun; + bool lto; + bool module; + bool no_fp; + bool no_unreachable; + bool stats; + bool vmlinux; +}; + +extern struct opts opts; extern int cmd_parse_options(int argc, const char **argv, const char * const usage[]); diff --git a/tools/objtool/objtool.c b/tools/objtool/objtool.c index 843ff3c2f28e..a0f3d3c9558d 100644 --- a/tools/objtool/objtool.c +++ b/tools/objtool/objtool.c @@ -118,7 +118,7 @@ struct objtool_file *objtool_open_read(const char *_objname) if (!file.elf) return NULL; - if (backup && !objtool_create_backup(objname)) { + if (opts.backup && !objtool_create_backup(objname)) { WARN("can't create backup file"); return NULL; } @@ -129,7 +129,7 @@ struct objtool_file *objtool_open_read(const char *_objname) INIT_LIST_HEAD(&file.static_call_list); INIT_LIST_HEAD(&file.mcount_loc_list); INIT_LIST_HEAD(&file.endbr_list); - file.ignore_unreachables = no_unreachable; + file.ignore_unreachables = opts.no_unreachable; file.hints = false; return &file; @@ -137,7 +137,7 @@ struct objtool_file *objtool_open_read(const char *_objname) void objtool_pv_add(struct objtool_file *f, int idx, struct symbol *func) { - if (!noinstr) + if (!opts.noinstr) return; if (!f->pv_ops) { -- cgit From b51277eb9775ce916f9efd2c51533e481180c1e8 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 18 Apr 2022 09:50:27 -0700 Subject: objtool: Ditch subcommands Objtool has a fairly singular focus. It runs on object files and does validations and transformations which can be combined in various ways. The subcommand model has never been a good fit, making it awkward to combine and remove options. Remove the "check" and "orc" subcommands in favor of a more traditional cmdline option model. This makes it much more flexible to use, and easier to port individual features to other arches. Signed-off-by: Josh Poimboeuf Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Miroslav Benes Link: https://lkml.kernel.org/r/5c61ebf805e90aefc5fa62bc63468ffae53b9df6.1650300597.git.jpoimboe@redhat.com --- scripts/Makefile.build | 2 +- scripts/link-vmlinux.sh | 13 ++--- tools/objtool/Build | 12 ++-- tools/objtool/Makefile | 8 +-- tools/objtool/builtin-check.c | 56 +++++++++++++++---- tools/objtool/builtin-orc.c | 73 ------------------------- tools/objtool/check.c | 8 +++ tools/objtool/include/objtool/builtin.h | 5 +- tools/objtool/objtool.c | 97 +-------------------------------- tools/objtool/weak.c | 9 +-- 10 files changed, 72 insertions(+), 211 deletions(-) delete mode 100644 tools/objtool/builtin-orc.c (limited to 'tools/objtool') diff --git a/scripts/Makefile.build b/scripts/Makefile.build index dd9d582808d6..116c7272b41c 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -227,9 +227,9 @@ ifdef CONFIG_STACK_VALIDATION objtool := $(objtree)/tools/objtool/objtool objtool_args = \ - $(if $(CONFIG_UNWINDER_ORC),orc generate,check) \ $(if $(CONFIG_X86_KERNEL_IBT), --lto --ibt) \ $(if $(CONFIG_FTRACE_MCOUNT_USE_OBJTOOL), --mcount) \ + $(if $(CONFIG_UNWINDER_ORC), --orc) \ $(if $(CONFIG_RETPOLINE), --retpoline) \ $(if $(CONFIG_SLS), --sls) \ $(if $(CONFIG_X86_SMAP), --uaccess) \ diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh index c6e9fef61b11..f6db79b11573 100755 --- a/scripts/link-vmlinux.sh +++ b/scripts/link-vmlinux.sh @@ -113,9 +113,6 @@ objtool_link() # Don't perform vmlinux validation unless explicitly requested, # but run objtool on vmlinux.o now that we have an object file. - if is_enabled CONFIG_UNWINDER_ORC; then - objtoolcmd="orc generate" - fi if is_enabled CONFIG_X86_KERNEL_IBT; then objtoolopt="${objtoolopt} --ibt" @@ -125,6 +122,10 @@ objtool_link() objtoolopt="${objtoolopt} --mcount" fi + if is_enabled CONFIG_UNWINDER_ORC; then + objtoolopt="${objtoolopt} --orc" + fi + objtoolopt="${objtoolopt} --lto" fi @@ -134,10 +135,6 @@ objtool_link() if [ -n "${objtoolopt}" ]; then - if [ -z "${objtoolcmd}" ]; then - objtoolcmd="check" - fi - if is_enabled CONFIG_RETPOLINE; then objtoolopt="${objtoolopt} --retpoline" fi @@ -161,7 +158,7 @@ objtool_link() objtoolopt="${objtoolopt} --vmlinux" info OBJTOOL ${1} - tools/objtool/objtool ${objtoolcmd} ${objtoolopt} ${1} + tools/objtool/objtool ${objtoolopt} ${1} fi } diff --git a/tools/objtool/Build b/tools/objtool/Build index b7222d5cc7bc..33f2ee5a46d3 100644 --- a/tools/objtool/Build +++ b/tools/objtool/Build @@ -2,17 +2,15 @@ objtool-y += arch/$(SRCARCH)/ objtool-y += weak.o -objtool-$(SUBCMD_CHECK) += check.o -objtool-$(SUBCMD_CHECK) += special.o -objtool-$(SUBCMD_ORC) += check.o -objtool-$(SUBCMD_ORC) += orc_gen.o -objtool-$(SUBCMD_ORC) += orc_dump.o - +objtool-y += check.o +objtool-y += special.o objtool-y += builtin-check.o -objtool-y += builtin-orc.o objtool-y += elf.o objtool-y += objtool.o +objtool-$(BUILD_ORC) += orc_gen.o +objtool-$(BUILD_ORC) += orc_dump.o + objtool-y += libstring.o objtool-y += libctype.o objtool-y += str_error_r.o diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index 0dbd397f319d..061cf1cd42c4 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -39,15 +39,13 @@ CFLAGS += $(if $(elfshdr),,-DLIBELF_USE_DEPRECATED) AWK = awk -SUBCMD_CHECK := n -SUBCMD_ORC := n +BUILD_ORC := n ifeq ($(SRCARCH),x86) - SUBCMD_CHECK := y - SUBCMD_ORC := y + BUILD_ORC := y endif -export SUBCMD_CHECK SUBCMD_ORC +export BUILD_ORC export srctree OUTPUT CFLAGS SRCARCH AWK include $(srctree)/tools/build/Makefile.include diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index bc447b3cd9f2..8c3eed5b67e4 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -3,16 +3,6 @@ * Copyright (C) 2015-2017 Josh Poimboeuf */ -/* - * objtool check: - * - * This command analyzes every .o file and ensures the validity of its stack - * trace metadata. It enforces a set of rules on asm code and C inline - * assembly code so that stack traces can be reliable. - * - * For more information, see tools/objtool/Documentation/stack-validation.txt. - */ - #include #include #include @@ -22,7 +12,7 @@ struct opts opts; static const char * const check_usage[] = { - "objtool check [] file.o", + "objtool [] file.o", NULL, }; @@ -31,14 +21,26 @@ static const char * const env_usage[] = { NULL, }; +static int parse_dump(const struct option *opt, const char *str, int unset) +{ + if (!str || !strcmp(str, "orc")) { + opts.dump_orc = true; + return 0; + } + + return -1; +} + const struct option check_options[] = { OPT_GROUP("Actions:"), OPT_BOOLEAN('i', "ibt", &opts.ibt, "validate and annotate IBT"), OPT_BOOLEAN('m', "mcount", &opts.mcount, "annotate mcount/fentry calls for ftrace"), OPT_BOOLEAN('n', "noinstr", &opts.noinstr, "validate noinstr rules"), + OPT_BOOLEAN('o', "orc", &opts.orc, "generate ORC metadata"), OPT_BOOLEAN('r', "retpoline", &opts.retpoline, "validate and annotate retpoline usage"), OPT_BOOLEAN('l', "sls", &opts.sls, "validate straight-line-speculation mitigations"), OPT_BOOLEAN('u', "uaccess", &opts.uaccess, "validate uaccess rules for SMAP"), + OPT_CALLBACK_OPTARG(0, "dump", NULL, NULL, "orc", "dump metadata", parse_dump), OPT_GROUP("Options:"), OPT_BOOLEAN(0, "backtrace", &opts.backtrace, "unwind on error"), @@ -81,7 +83,31 @@ int cmd_parse_options(int argc, const char **argv, const char * const usage[]) return argc; } -int cmd_check(int argc, const char **argv) +static bool opts_valid(void) +{ + if (opts.ibt || + opts.mcount || + opts.noinstr || + opts.orc || + opts.retpoline || + opts.sls || + opts.uaccess) { + if (opts.dump_orc) { + fprintf(stderr, "--dump can't be combined with other options\n"); + return false; + } + + return true; + } + + if (opts.dump_orc) + return true; + + fprintf(stderr, "At least one command required\n"); + return false; +} + +int objtool_run(int argc, const char **argv) { const char *objname; struct objtool_file *file; @@ -90,6 +116,12 @@ int cmd_check(int argc, const char **argv) argc = cmd_parse_options(argc, argv, check_usage); objname = argv[0]; + if (!opts_valid()) + return 1; + + if (opts.dump_orc) + return orc_dump(objname); + file = objtool_open_read(objname); if (!file) return 1; diff --git a/tools/objtool/builtin-orc.c b/tools/objtool/builtin-orc.c deleted file mode 100644 index 17f8b9307738..000000000000 --- a/tools/objtool/builtin-orc.c +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2017 Josh Poimboeuf - */ - -/* - * objtool orc: - * - * This command analyzes a .o file and adds .orc_unwind and .orc_unwind_ip - * sections to it, which is used by the in-kernel ORC unwinder. - * - * This command is a superset of "objtool check". - */ - -#include -#include -#include - -static const char *orc_usage[] = { - "objtool orc generate [] file.o", - "objtool orc dump file.o", - NULL, -}; - -int cmd_orc(int argc, const char **argv) -{ - const char *objname; - - argc--; argv++; - if (argc <= 0) - usage_with_options(orc_usage, check_options); - - if (!strncmp(argv[0], "gen", 3)) { - struct objtool_file *file; - int ret; - - argc = cmd_parse_options(argc, argv, orc_usage); - objname = argv[0]; - - file = objtool_open_read(objname); - if (!file) - return 1; - - ret = check(file); - if (ret) - return ret; - - if (list_empty(&file->insn_list)) - return 0; - - ret = orc_create(file); - if (ret) - return ret; - - if (!file->elf->changed) - return 0; - - return elf_write(file->elf); - } - - if (!strcmp(argv[0], "dump")) { - if (argc != 2) - usage_with_options(orc_usage, check_options); - - objname = argv[1]; - - return orc_dump(objname); - } - - usage_with_options(orc_usage, check_options); - - return 0; -} diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 3362cc3bdf1a..16a6c4b4f6bb 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -3949,6 +3949,14 @@ int check(struct objtool_file *file) warnings += ret; } + if (opts.orc && !list_empty(&file->insn_list)) { + ret = orc_create(file); + if (ret < 0) + goto out; + warnings += ret; + } + + if (opts.stats) { printf("nr_insns_visited: %ld\n", nr_insns_visited); printf("nr_cfi: %ld\n", nr_cfi); diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h index 87c1a7351e3c..44548e24473c 100644 --- a/tools/objtool/include/objtool/builtin.h +++ b/tools/objtool/include/objtool/builtin.h @@ -11,9 +11,11 @@ extern const struct option check_options[]; struct opts { /* actions: */ + bool dump_orc; bool ibt; bool mcount; bool noinstr; + bool orc; bool retpoline; bool sls; bool uaccess; @@ -34,7 +36,6 @@ extern struct opts opts; extern int cmd_parse_options(int argc, const char **argv, const char * const usage[]); -extern int cmd_check(int argc, const char **argv); -extern int cmd_orc(int argc, const char **argv); +extern int objtool_run(int argc, const char **argv); #endif /* _BUILTIN_H */ diff --git a/tools/objtool/objtool.c b/tools/objtool/objtool.c index a0f3d3c9558d..512669ce064c 100644 --- a/tools/objtool/objtool.c +++ b/tools/objtool/objtool.c @@ -3,16 +3,6 @@ * Copyright (C) 2015 Josh Poimboeuf */ -/* - * objtool: - * - * The 'check' subcmd analyzes every .o file and ensures the validity of its - * stack trace metadata. It enforces a set of rules on asm code and C inline - * assembly code so that stack traces can be reliable. - * - * For more information, see tools/objtool/Documentation/stack-validation.txt. - */ - #include #include #include @@ -26,20 +16,6 @@ #include #include -struct cmd_struct { - const char *name; - int (*fn)(int, const char **); - const char *help; -}; - -static const char objtool_usage_string[] = - "objtool COMMAND [ARGS]"; - -static struct cmd_struct objtool_cmds[] = { - {"check", cmd_check, "Perform stack metadata validation on an object file" }, - {"orc", cmd_orc, "Generate in-place ORC unwind tables for an object file" }, -}; - bool help; const char *objname; @@ -161,70 +137,6 @@ void objtool_pv_add(struct objtool_file *f, int idx, struct symbol *func) f->pv_ops[idx].clean = false; } -static void cmd_usage(void) -{ - unsigned int i, longest = 0; - - printf("\n usage: %s\n\n", objtool_usage_string); - - for (i = 0; i < ARRAY_SIZE(objtool_cmds); i++) { - if (longest < strlen(objtool_cmds[i].name)) - longest = strlen(objtool_cmds[i].name); - } - - puts(" Commands:"); - for (i = 0; i < ARRAY_SIZE(objtool_cmds); i++) { - printf(" %-*s ", longest, objtool_cmds[i].name); - puts(objtool_cmds[i].help); - } - - printf("\n"); - - if (!help) - exit(129); - exit(0); -} - -static void handle_options(int *argc, const char ***argv) -{ - while (*argc > 0) { - const char *cmd = (*argv)[0]; - - if (cmd[0] != '-') - break; - - if (!strcmp(cmd, "--help") || !strcmp(cmd, "-h")) { - help = true; - break; - } else { - fprintf(stderr, "Unknown option: %s\n", cmd); - cmd_usage(); - } - - (*argv)++; - (*argc)--; - } -} - -static void handle_internal_command(int argc, const char **argv) -{ - const char *cmd = argv[0]; - unsigned int i, ret; - - for (i = 0; i < ARRAY_SIZE(objtool_cmds); i++) { - struct cmd_struct *p = objtool_cmds+i; - - if (strcmp(p->name, cmd)) - continue; - - ret = p->fn(argc, argv); - - exit(ret); - } - - cmd_usage(); -} - int main(int argc, const char **argv) { static const char *UNUSED = "OBJTOOL_NOT_IMPLEMENTED"; @@ -233,14 +145,7 @@ int main(int argc, const char **argv) exec_cmd_init("objtool", UNUSED, UNUSED, UNUSED); pager_init(UNUSED); - argv++; - argc--; - handle_options(&argc, &argv); - - if (!argc || help) - cmd_usage(); - - handle_internal_command(argc, argv); + objtool_run(argc, argv); return 0; } diff --git a/tools/objtool/weak.c b/tools/objtool/weak.c index 8314e824db4a..d83f607733b0 100644 --- a/tools/objtool/weak.c +++ b/tools/objtool/weak.c @@ -15,17 +15,12 @@ return ENOSYS; \ }) -int __weak check(struct objtool_file *file) -{ - UNSUPPORTED("check subcommand"); -} - int __weak orc_dump(const char *_objname) { - UNSUPPORTED("orc"); + UNSUPPORTED("ORC"); } int __weak orc_create(struct objtool_file *file) { - UNSUPPORTED("orc"); + UNSUPPORTED("ORC"); } -- cgit From 2bc3dec7055e34c2c2e497f109da6748544c0791 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 18 Apr 2022 09:50:28 -0700 Subject: objtool: Don't print parentheses in function addresses The parentheses in the "func()+off" address output are inconsistent with how the kernel prints function addresses, breaking Peter's scripts. Remove them. Suggested-by: Peter Zijlstra Signed-off-by: Josh Poimboeuf Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Miroslav Benes Link: https://lkml.kernel.org/r/f2bec70312f62ef4f1ea21c134d9def627182ad3.1650300597.git.jpoimboe@redhat.com --- tools/objtool/include/objtool/warn.h | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/include/objtool/warn.h b/tools/objtool/include/objtool/warn.h index 802cfda0a6f6..c4bde3e2a79c 100644 --- a/tools/objtool/include/objtool/warn.h +++ b/tools/objtool/include/objtool/warn.h @@ -33,11 +33,7 @@ static inline char *offstr(struct section *sec, unsigned long offset) } str = malloc(strlen(name) + 20); - - if (func) - sprintf(str, "%s()+0x%lx", name, name_off); - else - sprintf(str, "%s+0x%lx", name, name_off); + sprintf(str, "%s+0x%lx", name, name_off); return str; } -- cgit From 99c0beb547a3e0ec3a63edeba0960c6ddf2226b0 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 18 Apr 2022 09:50:31 -0700 Subject: objtool: Add option to print section addresses To help prevent objtool users from having to do math to convert function addresses to section addresses, and to help out with finding data addresses reported by IBT validation, add an option to print the section address in addition to the function address. Normal: vmlinux.o: warning: objtool: fixup_exception()+0x2d1: unreachable instruction With '--sec-address': vmlinux.o: warning: objtool: fixup_exception()+0x2d1 (.text+0x76c51): unreachable instruction Suggested-by: Nick Desaulniers Signed-off-by: Josh Poimboeuf Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Miroslav Benes Link: https://lkml.kernel.org/r/2cea4d5299d53d1a4c09212a6ad7820aa46fda7a.1650300597.git.jpoimboe@redhat.com --- tools/objtool/builtin-check.c | 1 + tools/objtool/include/objtool/builtin.h | 1 + tools/objtool/include/objtool/warn.h | 31 +++++++++++++++++-------------- 3 files changed, 19 insertions(+), 14 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index 8c3eed5b67e4..6acfebd2c6ca 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -50,6 +50,7 @@ const struct option check_options[] = { OPT_BOOLEAN(0, "module", &opts.module, "object is part of a kernel module"), OPT_BOOLEAN(0, "no-fp", &opts.no_fp, "skip frame pointer validation"), OPT_BOOLEAN(0, "no-unreachable", &opts.no_unreachable, "skip 'unreachable instruction' warnings"), + OPT_BOOLEAN(0, "sec-address", &opts.sec_address, "print section addresses in warnings"), OPT_BOOLEAN(0, "stats", &opts.stats, "print statistics"), OPT_BOOLEAN(0, "vmlinux", &opts.vmlinux, "vmlinux.o validation"), diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h index 44548e24473c..e0972fbfa09e 100644 --- a/tools/objtool/include/objtool/builtin.h +++ b/tools/objtool/include/objtool/builtin.h @@ -28,6 +28,7 @@ struct opts { bool module; bool no_fp; bool no_unreachable; + bool sec_address; bool stats; bool vmlinux; }; diff --git a/tools/objtool/include/objtool/warn.h b/tools/objtool/include/objtool/warn.h index c4bde3e2a79c..a3e79ae75f2e 100644 --- a/tools/objtool/include/objtool/warn.h +++ b/tools/objtool/include/objtool/warn.h @@ -11,30 +11,33 @@ #include #include #include +#include #include extern const char *objname; static inline char *offstr(struct section *sec, unsigned long offset) { - struct symbol *func; - char *name, *str; - unsigned long name_off; + bool is_text = (sec->sh.sh_flags & SHF_EXECINSTR); + struct symbol *sym = NULL; + char *str; + int len; - func = find_func_containing(sec, offset); - if (!func) - func = find_symbol_containing(sec, offset); - if (func) { - name = func->name; - name_off = offset - func->offset; + if (is_text) + sym = find_func_containing(sec, offset); + if (!sym) + sym = find_symbol_containing(sec, offset); + + if (sym) { + str = malloc(strlen(sym->name) + strlen(sec->name) + 40); + len = sprintf(str, "%s+0x%lx", sym->name, offset - sym->offset); + if (opts.sec_address) + sprintf(str+len, " (%s+0x%lx)", sec->name, offset); } else { - name = sec->name; - name_off = offset; + str = malloc(strlen(sec->name) + 20); + sprintf(str, "%s+0x%lx", sec->name, offset); } - str = malloc(strlen(name) + 20); - sprintf(str, "%s+0x%lx", name, name_off); - return str; } -- cgit From 7dce62041ac34b613a5ed1bd937117e492e06dc8 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 18 Apr 2022 09:50:33 -0700 Subject: objtool: Make stack validation optional Make stack validation an explicit cmdline option so that individual objtool features can be enabled individually by other arches. Signed-off-by: Josh Poimboeuf Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Miroslav Benes Link: https://lkml.kernel.org/r/52da143699574d756e65ca4c9d4acaffe9b0fe5f.1650300597.git.jpoimboe@redhat.com --- scripts/Makefile.build | 1 + scripts/link-vmlinux.sh | 4 ++++ tools/objtool/builtin-check.c | 2 ++ tools/objtool/check.c | 28 +++++++++++++++------------- tools/objtool/include/objtool/builtin.h | 1 + 5 files changed, 23 insertions(+), 13 deletions(-) (limited to 'tools/objtool') diff --git a/scripts/Makefile.build b/scripts/Makefile.build index 116c7272b41c..d5e15ae29156 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -232,6 +232,7 @@ objtool_args = \ $(if $(CONFIG_UNWINDER_ORC), --orc) \ $(if $(CONFIG_RETPOLINE), --retpoline) \ $(if $(CONFIG_SLS), --sls) \ + $(if $(CONFIG_STACK_VALIDATION), --stackval) \ $(if $(CONFIG_X86_SMAP), --uaccess) \ $(if $(part-of-module), --module) \ $(if $(CONFIG_FRAME_POINTER),, --no-fp) \ diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh index f6db79b11573..0140bfa32c0c 100755 --- a/scripts/link-vmlinux.sh +++ b/scripts/link-vmlinux.sh @@ -126,6 +126,10 @@ objtool_link() objtoolopt="${objtoolopt} --orc" fi + if is_enabled CONFIG_STACK_VALIDATION; then + objtoolopt="${objtoolopt} --stackval" + fi + objtoolopt="${objtoolopt} --lto" fi diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index 6acfebd2c6ca..d4e6930ad0a0 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -39,6 +39,7 @@ const struct option check_options[] = { OPT_BOOLEAN('o', "orc", &opts.orc, "generate ORC metadata"), OPT_BOOLEAN('r', "retpoline", &opts.retpoline, "validate and annotate retpoline usage"), OPT_BOOLEAN('l', "sls", &opts.sls, "validate straight-line-speculation mitigations"), + OPT_BOOLEAN('s', "stackval", &opts.stackval, "validate stack unwinding rules"), OPT_BOOLEAN('u', "uaccess", &opts.uaccess, "validate uaccess rules for SMAP"), OPT_CALLBACK_OPTARG(0, "dump", NULL, NULL, "orc", "dump metadata", parse_dump), @@ -92,6 +93,7 @@ static bool opts_valid(void) opts.orc || opts.retpoline || opts.sls || + opts.stackval || opts.uaccess) { if (opts.dump_orc) { fprintf(stderr, "--dump can't be combined with other options\n"); diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 16a6c4b4f6bb..3456eb99b06e 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -3899,25 +3899,27 @@ int check(struct objtool_file *file) warnings += ret; } - ret = validate_functions(file); - if (ret < 0) - goto out; - warnings += ret; - - ret = validate_unwind_hints(file, NULL); - if (ret < 0) - goto out; - warnings += ret; + if (opts.stackval || opts.orc || opts.uaccess || opts.ibt || opts.sls) { + ret = validate_functions(file); + if (ret < 0) + goto out; + warnings += ret; - if (opts.ibt) { - ret = validate_ibt(file); + ret = validate_unwind_hints(file, NULL); if (ret < 0) goto out; warnings += ret; + + if (!warnings) { + ret = validate_reachable_instructions(file); + if (ret < 0) + goto out; + warnings += ret; + } } - if (!warnings) { - ret = validate_reachable_instructions(file); + if (opts.ibt) { + ret = validate_ibt(file); if (ret < 0) goto out; warnings += ret; diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h index e0972fbfa09e..8618585bb742 100644 --- a/tools/objtool/include/objtool/builtin.h +++ b/tools/objtool/include/objtool/builtin.h @@ -18,6 +18,7 @@ struct opts { bool orc; bool retpoline; bool sls; + bool stackval; bool uaccess; /* options: */ -- cgit From 3c6f9f77e6188ca4d283633d66e91b3821a505ae Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 18 Apr 2022 09:50:34 -0700 Subject: objtool: Rework ibt and extricate from stack validation Extricate ibt from validate_branch() so they can be executed (or ported) independently from each other. While shuffling code around, simplify and improve the ibt logic: - Ignore an explicit list of known sections which reference functions for reasons other than indirect branching to them. This helps prevent unnnecesary sealing. - Warn on missing !ENDBR for all other sections, not just .data and .rodata. This finds additional warnings, because there are sections other than .[ro]data which reference function pointers. For example, the ksymtab sections which are used for exporting symbols. Signed-off-by: Josh Poimboeuf Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Miroslav Benes Link: https://lkml.kernel.org/r/fd1435e46bb95f81031b8fb1fa360f5f787e4316.1650300597.git.jpoimboe@redhat.com --- tools/objtool/check.c | 280 ++++++++++++++++++++++++++------------------------ 1 file changed, 147 insertions(+), 133 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 3456eb99b06e..85c288807f2e 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -3182,114 +3182,6 @@ static struct instruction *next_insn_to_validate(struct objtool_file *file, return next_insn_same_sec(file, insn); } -static struct instruction * -validate_ibt_reloc(struct objtool_file *file, struct reloc *reloc) -{ - struct instruction *dest; - struct section *sec; - unsigned long off; - - sec = reloc->sym->sec; - off = reloc->sym->offset; - - if ((reloc->sec->base->sh.sh_flags & SHF_EXECINSTR) && - (reloc->type == R_X86_64_PC32 || reloc->type == R_X86_64_PLT32)) - off += arch_dest_reloc_offset(reloc->addend); - else - off += reloc->addend; - - dest = find_insn(file, sec, off); - if (!dest) - return NULL; - - if (dest->type == INSN_ENDBR) { - if (!list_empty(&dest->call_node)) - list_del_init(&dest->call_node); - - return NULL; - } - - if (reloc->sym->static_call_tramp) - return NULL; - - return dest; -} - -static void warn_noendbr(const char *msg, struct section *sec, unsigned long offset, - struct instruction *dest) -{ - WARN_FUNC("%srelocation to !ENDBR: %s", sec, offset, msg, - offstr(dest->sec, dest->offset)); -} - -static void validate_ibt_dest(struct objtool_file *file, struct instruction *insn, - struct instruction *dest) -{ - if (dest->func && dest->func == insn->func) { - /* - * Anything from->to self is either _THIS_IP_ or IRET-to-self. - * - * There is no sane way to annotate _THIS_IP_ since the compiler treats the - * relocation as a constant and is happy to fold in offsets, skewing any - * annotation we do, leading to vast amounts of false-positives. - * - * There's also compiler generated _THIS_IP_ through KCOV and - * such which we have no hope of annotating. - * - * As such, blanket accept self-references without issue. - */ - return; - } - - if (dest->noendbr) - return; - - warn_noendbr("", insn->sec, insn->offset, dest); -} - -static void validate_ibt_insn(struct objtool_file *file, struct instruction *insn) -{ - struct instruction *dest; - struct reloc *reloc; - - switch (insn->type) { - case INSN_CALL: - case INSN_CALL_DYNAMIC: - case INSN_JUMP_CONDITIONAL: - case INSN_JUMP_UNCONDITIONAL: - case INSN_JUMP_DYNAMIC: - case INSN_JUMP_DYNAMIC_CONDITIONAL: - case INSN_RETURN: - /* - * We're looking for code references setting up indirect code - * flow. As such, ignore direct code flow and the actual - * dynamic branches. - */ - return; - - case INSN_NOP: - /* - * handle_group_alt() will create INSN_NOP instruction that - * don't belong to any section, ignore all NOP since they won't - * carry a (useful) relocation anyway. - */ - return; - - default: - break; - } - - for (reloc = insn_reloc(file, insn); - reloc; - reloc = find_reloc_by_dest_range(file->elf, insn->sec, - reloc->offset + 1, - (insn->offset + insn->len) - (reloc->offset + 1))) { - dest = validate_ibt_reloc(file, reloc); - if (dest) - validate_ibt_dest(file, insn, dest); - } -} - /* * Follow the branch starting at the given instruction, and recursively follow * any other branches (jumps). Meanwhile, track the frame pointer state at @@ -3499,9 +3391,6 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, break; } - if (opts.ibt) - validate_ibt_insn(file, insn); - if (insn->dead_end) return 0; @@ -3787,48 +3676,173 @@ static int validate_functions(struct objtool_file *file) return warnings; } -static int validate_ibt(struct objtool_file *file) +static void mark_endbr_used(struct instruction *insn) { - struct section *sec; + if (!list_empty(&insn->call_node)) + list_del_init(&insn->call_node); +} + +static int validate_ibt_insn(struct objtool_file *file, struct instruction *insn) +{ + struct instruction *dest; struct reloc *reloc; + unsigned long off; + int warnings = 0; - for_each_sec(file, sec) { - bool is_data; + /* + * Looking for function pointer load relocations. Ignore + * direct/indirect branches: + */ + switch (insn->type) { + case INSN_CALL: + case INSN_CALL_DYNAMIC: + case INSN_JUMP_CONDITIONAL: + case INSN_JUMP_UNCONDITIONAL: + case INSN_JUMP_DYNAMIC: + case INSN_JUMP_DYNAMIC_CONDITIONAL: + case INSN_RETURN: + case INSN_NOP: + return 0; + default: + break; + } - /* already done in validate_branch() */ - if (sec->sh.sh_flags & SHF_EXECINSTR) - continue; + for (reloc = insn_reloc(file, insn); + reloc; + reloc = find_reloc_by_dest_range(file->elf, insn->sec, + reloc->offset + 1, + (insn->offset + insn->len) - (reloc->offset + 1))) { - if (!sec->reloc) + /* + * static_call_update() references the trampoline, which + * doesn't have (or need) ENDBR. Skip warning in that case. + */ + if (reloc->sym->static_call_tramp) continue; - if (!strncmp(sec->name, ".orc", 4)) + off = reloc->sym->offset; + if (reloc->type == R_X86_64_PC32 || reloc->type == R_X86_64_PLT32) + off += arch_dest_reloc_offset(reloc->addend); + else + off += reloc->addend; + + dest = find_insn(file, reloc->sym->sec, off); + if (!dest) continue; - if (!strncmp(sec->name, ".discard", 8)) + if (dest->type == INSN_ENDBR) { + mark_endbr_used(dest); continue; + } - if (!strncmp(sec->name, ".debug", 6)) + if (dest->func && dest->func == insn->func) { + /* + * Anything from->to self is either _THIS_IP_ or + * IRET-to-self. + * + * There is no sane way to annotate _THIS_IP_ since the + * compiler treats the relocation as a constant and is + * happy to fold in offsets, skewing any annotation we + * do, leading to vast amounts of false-positives. + * + * There's also compiler generated _THIS_IP_ through + * KCOV and such which we have no hope of annotating. + * + * As such, blanket accept self-references without + * issue. + */ continue; + } - if (!strcmp(sec->name, "_error_injection_whitelist")) + if (dest->noendbr) continue; - if (!strcmp(sec->name, "_kprobe_blacklist")) + WARN_FUNC("relocation to !ENDBR: %s", + insn->sec, insn->offset, + offstr(dest->sec, dest->offset)); + + warnings++; + } + + return warnings; +} + +static int validate_ibt_data_reloc(struct objtool_file *file, + struct reloc *reloc) +{ + struct instruction *dest; + + dest = find_insn(file, reloc->sym->sec, + reloc->sym->offset + reloc->addend); + if (!dest) + return 0; + + if (dest->type == INSN_ENDBR) { + mark_endbr_used(dest); + return 0; + } + + if (dest->noendbr) + return 0; + + WARN_FUNC("data relocation to !ENDBR: %s", + reloc->sec->base, reloc->offset, + offstr(dest->sec, dest->offset)); + + return 1; +} + +/* + * Validate IBT rules and remove used ENDBR instructions from the seal list. + * Unused ENDBR instructions will be annotated for sealing (i.e., replaced with + * NOPs) later, in create_ibt_endbr_seal_sections(). + */ +static int validate_ibt(struct objtool_file *file) +{ + struct section *sec; + struct reloc *reloc; + struct instruction *insn; + int warnings = 0; + + for_each_insn(file, insn) + warnings += validate_ibt_insn(file, insn); + + for_each_sec(file, sec) { + + /* Already done by validate_ibt_insn() */ + if (sec->sh.sh_flags & SHF_EXECINSTR) continue; - is_data = strstr(sec->name, ".data") || strstr(sec->name, ".rodata"); + if (!sec->reloc) + continue; - list_for_each_entry(reloc, &sec->reloc->reloc_list, list) { - struct instruction *dest; + /* + * These sections can reference text addresses, but not with + * the intent to indirect branch to them. + */ + if (!strncmp(sec->name, ".discard", 8) || + !strncmp(sec->name, ".debug", 6) || + !strcmp(sec->name, ".altinstructions") || + !strcmp(sec->name, ".ibt_endbr_seal") || + !strcmp(sec->name, ".orc_unwind_ip") || + !strcmp(sec->name, ".parainstructions") || + !strcmp(sec->name, ".retpoline_sites") || + !strcmp(sec->name, ".smp_locks") || + !strcmp(sec->name, ".static_call_sites") || + !strcmp(sec->name, "_error_injection_whitelist") || + !strcmp(sec->name, "_kprobe_blacklist") || + !strcmp(sec->name, "__bug_table") || + !strcmp(sec->name, "__ex_table") || + !strcmp(sec->name, "__jump_table") || + !strcmp(sec->name, "__mcount_loc") || + !strcmp(sec->name, "__tracepoints")) + continue; - dest = validate_ibt_reloc(file, reloc); - if (is_data && dest && !dest->noendbr) - warn_noendbr("data ", sec, reloc->offset, dest); - } + list_for_each_entry(reloc, &sec->reloc->reloc_list, list) + warnings += validate_ibt_data_reloc(file, reloc); } - return 0; + return warnings; } static int validate_reachable_instructions(struct objtool_file *file) @@ -3899,7 +3913,7 @@ int check(struct objtool_file *file) warnings += ret; } - if (opts.stackval || opts.orc || opts.uaccess || opts.ibt || opts.sls) { + if (opts.stackval || opts.orc || opts.uaccess || opts.sls) { ret = validate_functions(file); if (ret < 0) goto out; -- cgit From c2bdd61c98d915ad2cc1f8cd4661fcda1f1e4c16 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 18 Apr 2022 09:50:35 -0700 Subject: objtool: Extricate sls from stack validation Extricate sls functionality from validate_branch() so they can be executed (or ported) independently from each other. Signed-off-by: Josh Poimboeuf Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Miroslav Benes Link: https://lkml.kernel.org/r/2545c86ffa5f27497f0d0c542540ad4a4be3c5a5.1650300597.git.jpoimboe@redhat.com --- tools/objtool/check.c | 56 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 13 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 85c288807f2e..27126fff91dc 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -3271,11 +3271,6 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, switch (insn->type) { case INSN_RETURN: - if (opts.sls && !insn->retpoline_safe && - next_insn && next_insn->type != INSN_TRAP) { - WARN_FUNC("missing int3 after ret", - insn->sec, insn->offset); - } return validate_return(func, insn, &state); case INSN_CALL: @@ -3319,13 +3314,6 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, break; case INSN_JUMP_DYNAMIC: - if (opts.sls && !insn->retpoline_safe && - next_insn && next_insn->type != INSN_TRAP) { - WARN_FUNC("missing int3 after indirect jump", - insn->sec, insn->offset); - } - - /* fallthrough */ case INSN_JUMP_DYNAMIC_CONDITIONAL: if (is_sibling_call(insn)) { ret = validate_sibling_call(file, insn, &state); @@ -3845,6 +3833,41 @@ static int validate_ibt(struct objtool_file *file) return warnings; } +static int validate_sls(struct objtool_file *file) +{ + struct instruction *insn, *next_insn; + int warnings = 0; + + for_each_insn(file, insn) { + next_insn = next_insn_same_sec(file, insn); + + if (insn->retpoline_safe) + continue; + + switch (insn->type) { + case INSN_RETURN: + if (!next_insn || next_insn->type != INSN_TRAP) { + WARN_FUNC("missing int3 after ret", + insn->sec, insn->offset); + warnings++; + } + + break; + case INSN_JUMP_DYNAMIC: + if (!next_insn || next_insn->type != INSN_TRAP) { + WARN_FUNC("missing int3 after indirect jump", + insn->sec, insn->offset); + warnings++; + } + break; + default: + break; + } + } + + return warnings; +} + static int validate_reachable_instructions(struct objtool_file *file) { struct instruction *insn; @@ -3913,7 +3936,7 @@ int check(struct objtool_file *file) warnings += ret; } - if (opts.stackval || opts.orc || opts.uaccess || opts.sls) { + if (opts.stackval || opts.orc || opts.uaccess) { ret = validate_functions(file); if (ret < 0) goto out; @@ -3939,6 +3962,13 @@ int check(struct objtool_file *file) warnings += ret; } + if (opts.sls) { + ret = validate_sls(file); + if (ret < 0) + goto out; + warnings += ret; + } + ret = create_static_call_sections(file); if (ret < 0) goto out; -- cgit From 72064474964724c59ddff58a581a31b1ede75cf9 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 18 Apr 2022 09:50:37 -0700 Subject: objtool: Make stack validation frame-pointer-specific Now that CONFIG_STACK_VALIDATION is frame-pointer specific, do the same for the '--stackval' option. Now the '--no-fp' option is redundant and can be removed. Signed-off-by: Josh Poimboeuf Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Miroslav Benes Link: https://lkml.kernel.org/r/f563fa064b3b63d528de250c72012d49e14742a3.1650300597.git.jpoimboe@redhat.com --- scripts/Makefile.build | 1 - scripts/link-vmlinux.sh | 4 ---- tools/objtool/builtin-check.c | 3 +-- tools/objtool/check.c | 4 ++-- tools/objtool/include/objtool/builtin.h | 1 - 5 files changed, 3 insertions(+), 10 deletions(-) (limited to 'tools/objtool') diff --git a/scripts/Makefile.build b/scripts/Makefile.build index 0f73e02b7cf1..6eb99cb08821 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -235,7 +235,6 @@ objtool_args = \ $(if $(CONFIG_STACK_VALIDATION), --stackval) \ $(if $(CONFIG_X86_SMAP), --uaccess) \ $(if $(part-of-module), --module) \ - $(if $(CONFIG_FRAME_POINTER),, --no-fp) \ $(if $(CONFIG_GCOV_KERNEL), --no-unreachable) cmd_objtool = $(if $(objtool-enabled), ; $(objtool) $(objtool_args) $@) diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh index 5101a7fbfaaf..1be01163a9c5 100755 --- a/scripts/link-vmlinux.sh +++ b/scripts/link-vmlinux.sh @@ -155,10 +155,6 @@ objtool_link() if [ -n "${objtoolopt}" ]; then - if ! is_enabled CONFIG_FRAME_POINTER; then - objtoolopt="${objtoolopt} --no-fp" - fi - if is_enabled CONFIG_GCOV_KERNEL; then objtoolopt="${objtoolopt} --no-unreachable" fi diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index d4e6930ad0a0..30971cc50c63 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -39,7 +39,7 @@ const struct option check_options[] = { OPT_BOOLEAN('o', "orc", &opts.orc, "generate ORC metadata"), OPT_BOOLEAN('r', "retpoline", &opts.retpoline, "validate and annotate retpoline usage"), OPT_BOOLEAN('l', "sls", &opts.sls, "validate straight-line-speculation mitigations"), - OPT_BOOLEAN('s', "stackval", &opts.stackval, "validate stack unwinding rules"), + OPT_BOOLEAN('s', "stackval", &opts.stackval, "validate frame pointer rules"), OPT_BOOLEAN('u', "uaccess", &opts.uaccess, "validate uaccess rules for SMAP"), OPT_CALLBACK_OPTARG(0, "dump", NULL, NULL, "orc", "dump metadata", parse_dump), @@ -49,7 +49,6 @@ const struct option check_options[] = { OPT_BOOLEAN(0, "dry-run", &opts.dryrun, "don't write modifications"), OPT_BOOLEAN(0, "lto", &opts.lto, "whole-archive like runs"), OPT_BOOLEAN(0, "module", &opts.module, "object is part of a kernel module"), - OPT_BOOLEAN(0, "no-fp", &opts.no_fp, "skip frame pointer validation"), OPT_BOOLEAN(0, "no-unreachable", &opts.no_unreachable, "skip 'unreachable instruction' warnings"), OPT_BOOLEAN(0, "sec-address", &opts.sec_address, "print section addresses in warnings"), OPT_BOOLEAN(0, "stats", &opts.stats, "print statistics"), diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 27126fff91dc..3e02126738a1 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -2806,7 +2806,7 @@ static int update_cfi_state(struct instruction *insn, } /* detect when asm code uses rbp as a scratch register */ - if (!opts.no_fp && insn->func && op->src.reg == CFI_BP && + if (opts.stackval && insn->func && op->src.reg == CFI_BP && cfa->base != CFI_BP) cfi->bp_scratch = true; break; @@ -3279,7 +3279,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, if (ret) return ret; - if (!opts.no_fp && func && !is_fentry_call(insn) && + if (opts.stackval && func && !is_fentry_call(insn) && !has_valid_stack_frame(&state)) { WARN_FUNC("call without frame pointer save/setup", sec, insn->offset); diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h index 8618585bb742..24a7ff4f37cc 100644 --- a/tools/objtool/include/objtool/builtin.h +++ b/tools/objtool/include/objtool/builtin.h @@ -27,7 +27,6 @@ struct opts { bool dryrun; bool lto; bool module; - bool no_fp; bool no_unreachable; bool sec_address; bool stats; -- cgit From 26e176896a5bb9222ae3433da902edd2566a61a4 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 18 Apr 2022 09:50:38 -0700 Subject: objtool: Make static call annotation optional As part of making objtool more modular, put the existing static call code behind a new '--static-call' option. Signed-off-by: Josh Poimboeuf Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Miroslav Benes Link: https://lkml.kernel.org/r/d59ac57ef3d6d8380cdce20322314c9e2e556750.1650300597.git.jpoimboe@redhat.com --- scripts/Makefile.build | 1 + scripts/link-vmlinux.sh | 5 ++++- tools/objtool/builtin-check.c | 2 ++ tools/objtool/check.c | 10 ++++++---- tools/objtool/include/objtool/builtin.h | 1 + 5 files changed, 14 insertions(+), 5 deletions(-) (limited to 'tools/objtool') diff --git a/scripts/Makefile.build b/scripts/Makefile.build index 6eb99cb08821..3f20d565733c 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -233,6 +233,7 @@ objtool_args = \ $(if $(CONFIG_RETPOLINE), --retpoline) \ $(if $(CONFIG_SLS), --sls) \ $(if $(CONFIG_STACK_VALIDATION), --stackval) \ + $(if $(CONFIG_HAVE_STATIC_CALL_INLINE), --static-call) \ $(if $(CONFIG_X86_SMAP), --uaccess) \ $(if $(part-of-module), --module) \ $(if $(CONFIG_GCOV_KERNEL), --no-unreachable) diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh index 1be01163a9c5..33f14fe1ddde 100755 --- a/scripts/link-vmlinux.sh +++ b/scripts/link-vmlinux.sh @@ -141,11 +141,14 @@ objtool_link() objtoolopt="${objtoolopt} --stackval" fi + if is_enabled CONFIG_HAVE_STATIC_CALL_INLINE; then + objtoolopt="${objtoolopt} --static-call" + fi + if is_enabled CONFIG_X86_SMAP; then objtoolopt="${objtoolopt} --uaccess" fi - objtoolopt="${objtoolopt} --lto" fi diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index 30971cc50c63..c8c4d2bab42f 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -40,6 +40,7 @@ const struct option check_options[] = { OPT_BOOLEAN('r', "retpoline", &opts.retpoline, "validate and annotate retpoline usage"), OPT_BOOLEAN('l', "sls", &opts.sls, "validate straight-line-speculation mitigations"), OPT_BOOLEAN('s', "stackval", &opts.stackval, "validate frame pointer rules"), + OPT_BOOLEAN('t', "static-call", &opts.static_call, "annotate static calls"), OPT_BOOLEAN('u', "uaccess", &opts.uaccess, "validate uaccess rules for SMAP"), OPT_CALLBACK_OPTARG(0, "dump", NULL, NULL, "orc", "dump metadata", parse_dump), @@ -93,6 +94,7 @@ static bool opts_valid(void) opts.retpoline || opts.sls || opts.stackval || + opts.static_call || opts.uaccess) { if (opts.dump_orc) { fprintf(stderr, "--dump can't be combined with other options\n"); diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 3e02126738a1..b9ac351ea08b 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -3969,10 +3969,12 @@ int check(struct objtool_file *file) warnings += ret; } - ret = create_static_call_sections(file); - if (ret < 0) - goto out; - warnings += ret; + if (opts.static_call) { + ret = create_static_call_sections(file); + if (ret < 0) + goto out; + warnings += ret; + } if (opts.retpoline) { ret = create_retpoline_sites_sections(file); diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h index 24a7ff4f37cc..dc4757205b8d 100644 --- a/tools/objtool/include/objtool/builtin.h +++ b/tools/objtool/include/objtool/builtin.h @@ -19,6 +19,7 @@ struct opts { bool retpoline; bool sls; bool stackval; + bool static_call; bool uaccess; /* options: */ -- cgit From 4ab7674f5951ac6a8ac4fa8828090edb64a4771f Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 18 Apr 2022 09:50:39 -0700 Subject: objtool: Make jump label hack optional Objtool secretly does a jump label hack to overcome the limitations of the toolchain. Make the hack explicit (and optional for other arches) by turning it into a cmdline option and kernel config option. Signed-off-by: Josh Poimboeuf Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Miroslav Benes Link: https://lkml.kernel.org/r/3bdcbfdd27ecb01ddec13c04bdf756a583b13d24.1650300597.git.jpoimboe@redhat.com --- arch/Kconfig | 4 ++++ arch/x86/Kconfig | 1 + arch/x86/include/asm/jump_label.h | 6 +++--- scripts/Makefile.build | 1 + scripts/link-vmlinux.sh | 4 ++++ tools/objtool/builtin-check.c | 37 ++++++++++++++++++++++++++------- tools/objtool/check.c | 2 +- tools/objtool/include/objtool/builtin.h | 1 + 8 files changed, 44 insertions(+), 12 deletions(-) (limited to 'tools/objtool') diff --git a/arch/Kconfig b/arch/Kconfig index 04cdef16db24..9dce6d6e3bc3 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -46,6 +46,7 @@ config JUMP_LABEL bool "Optimize very unlikely/likely branches" depends on HAVE_ARCH_JUMP_LABEL depends on CC_HAS_ASM_GOTO + select OBJTOOL if HAVE_JUMP_LABEL_HACK help This option enables a transparent branch optimization that makes certain almost-always-true or almost-always-false branch @@ -1031,6 +1032,9 @@ config ARCH_WANT_DEFAULT_TOPDOWN_MMAP_LAYOUT config HAVE_OBJTOOL bool +config HAVE_JUMP_LABEL_HACK + bool + config HAVE_STACK_VALIDATION bool help diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 43e26ee1611e..26d012f1eeb8 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -212,6 +212,7 @@ config X86 select HAVE_IOREMAP_PROT select HAVE_IRQ_EXIT_ON_IRQ_STACK if X86_64 select HAVE_IRQ_TIME_ACCOUNTING + select HAVE_JUMP_LABEL_HACK if HAVE_OBJTOOL select HAVE_KERNEL_BZIP2 select HAVE_KERNEL_GZIP select HAVE_KERNEL_LZ4 diff --git a/arch/x86/include/asm/jump_label.h b/arch/x86/include/asm/jump_label.h index 3ce0e67c579c..071572e23d3a 100644 --- a/arch/x86/include/asm/jump_label.h +++ b/arch/x86/include/asm/jump_label.h @@ -20,7 +20,7 @@ _ASM_PTR "%c0 + %c1 - .\n\t" \ ".popsection \n\t" -#ifdef CONFIG_OBJTOOL +#ifdef CONFIG_HAVE_JUMP_LABEL_HACK static __always_inline bool arch_static_branch(struct static_key *key, bool branch) { @@ -34,7 +34,7 @@ l_yes: return true; } -#else /* !CONFIG_OBJTOOL */ +#else /* !CONFIG_HAVE_JUMP_LABEL_HACK */ static __always_inline bool arch_static_branch(struct static_key * const key, const bool branch) { @@ -48,7 +48,7 @@ l_yes: return true; } -#endif /* CONFIG_OBJTOOL */ +#endif /* CONFIG_HAVE_JUMP_LABEL_HACK */ static __always_inline bool arch_static_branch_jump(struct static_key * const key, const bool branch) { diff --git a/scripts/Makefile.build b/scripts/Makefile.build index 3f20d565733c..f1d2c2e4f15b 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -227,6 +227,7 @@ ifdef CONFIG_OBJTOOL objtool := $(objtree)/tools/objtool/objtool objtool_args = \ + $(if $(CONFIG_HAVE_JUMP_LABEL_HACK), --hacks=jump_label) \ $(if $(CONFIG_X86_KERNEL_IBT), --lto --ibt) \ $(if $(CONFIG_FTRACE_MCOUNT_USE_OBJTOOL), --mcount) \ $(if $(CONFIG_UNWINDER_ORC), --orc) \ diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh index 33f14fe1ddde..fa1f16840e76 100755 --- a/scripts/link-vmlinux.sh +++ b/scripts/link-vmlinux.sh @@ -117,6 +117,10 @@ objtool_link() # Don't perform vmlinux validation unless explicitly requested, # but run objtool on vmlinux.o now that we have an object file. + if is_enabled CONFIG_HAVE_JUMP_LABEL_HACK; then + objtoolopt="${objtoolopt} --hacks=jump_label" + fi + if is_enabled CONFIG_X86_KERNEL_IBT; then objtoolopt="${objtoolopt} --ibt" fi diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index c8c4d2bab42f..b2c626d9e2bf 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -31,8 +31,28 @@ static int parse_dump(const struct option *opt, const char *str, int unset) return -1; } +static int parse_hacks(const struct option *opt, const char *str, int unset) +{ + bool found = false; + + /* + * Use strstr() as a lazy method of checking for comma-separated + * options. + * + * No string provided == enable all options. + */ + + if (!str || strstr(str, "jump_label")) { + opts.hack_jump_label = true; + found = true; + } + + return found ? 0 : -1; +} + const struct option check_options[] = { OPT_GROUP("Actions:"), + OPT_CALLBACK_OPTARG('h', "hacks", NULL, NULL, "jump_label", "patch toolchain bugs/limitations", parse_hacks), OPT_BOOLEAN('i', "ibt", &opts.ibt, "validate and annotate IBT"), OPT_BOOLEAN('m', "mcount", &opts.mcount, "annotate mcount/fentry calls for ftrace"), OPT_BOOLEAN('n', "noinstr", &opts.noinstr, "validate noinstr rules"), @@ -87,14 +107,15 @@ int cmd_parse_options(int argc, const char **argv, const char * const usage[]) static bool opts_valid(void) { - if (opts.ibt || - opts.mcount || - opts.noinstr || - opts.orc || - opts.retpoline || - opts.sls || - opts.stackval || - opts.static_call || + if (opts.hack_jump_label || + opts.ibt || + opts.mcount || + opts.noinstr || + opts.orc || + opts.retpoline || + opts.sls || + opts.stackval || + opts.static_call || opts.uaccess) { if (opts.dump_orc) { fprintf(stderr, "--dump can't be combined with other options\n"); diff --git a/tools/objtool/check.c b/tools/objtool/check.c index b9ac351ea08b..d157978c58b3 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -1592,7 +1592,7 @@ static int handle_jump_alt(struct objtool_file *file, return -1; } - if (special_alt->key_addend & 2) { + if (opts.hack_jump_label && special_alt->key_addend & 2) { struct reloc *reloc = insn_reloc(file, orig_insn); if (reloc) { diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h index dc4757205b8d..c6acf05ec859 100644 --- a/tools/objtool/include/objtool/builtin.h +++ b/tools/objtool/include/objtool/builtin.h @@ -12,6 +12,7 @@ extern const struct option check_options[]; struct opts { /* actions: */ bool dump_orc; + bool hack_jump_label; bool ibt; bool mcount; bool noinstr; -- cgit From 22102f4559beaabcea614b29ee090c6a214f002f Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 18 Apr 2022 09:50:40 -0700 Subject: objtool: Make noinstr hacks optional Objtool has some hacks in place to workaround toolchain limitations which otherwise would break no-instrumentation rules. Make the hacks explicit (and optional for other arches) by turning it into a cmdline option and kernel config option. Signed-off-by: Josh Poimboeuf Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Miroslav Benes Link: https://lkml.kernel.org/r/b326eeb9c33231b9dfbb925f194ed7ee40edcd7c.1650300597.git.jpoimboe@redhat.com --- arch/Kconfig | 3 +++ arch/x86/Kconfig | 1 + lib/Kconfig.debug | 4 ++-- lib/Kconfig.kcsan | 5 +++-- scripts/Makefile.build | 1 + scripts/link-vmlinux.sh | 4 ++++ tools/objtool/builtin-check.c | 8 +++++++- tools/objtool/check.c | 2 +- tools/objtool/include/objtool/builtin.h | 1 + 9 files changed, 23 insertions(+), 6 deletions(-) (limited to 'tools/objtool') diff --git a/arch/Kconfig b/arch/Kconfig index 9dce6d6e3bc3..6ba6e34db0ea 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -1035,6 +1035,9 @@ config HAVE_OBJTOOL config HAVE_JUMP_LABEL_HACK bool +config HAVE_NOINSTR_HACK + bool + config HAVE_STACK_VALIDATION bool help diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 26d012f1eeb8..06e7cdd76691 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -231,6 +231,7 @@ config X86 select HAVE_MOD_ARCH_SPECIFIC select HAVE_MOVE_PMD select HAVE_MOVE_PUD + select HAVE_NOINSTR_HACK if HAVE_OBJTOOL select HAVE_NMI select HAVE_OBJTOOL if X86_64 select HAVE_OPTPROBES diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index c0e4e47f3ce3..7d2bbc3e558e 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -2036,11 +2036,11 @@ config KCOV bool "Code coverage for fuzzing" depends on ARCH_HAS_KCOV depends on CC_HAS_SANCOV_TRACE_PC || GCC_PLUGINS - depends on !ARCH_WANTS_NO_INSTR || HAVE_OBJTOOL || \ + depends on !ARCH_WANTS_NO_INSTR || HAVE_NOINSTR_HACK || \ GCC_VERSION >= 120000 || CLANG_VERSION >= 130000 select DEBUG_FS select GCC_PLUGIN_SANCOV if !CC_HAS_SANCOV_TRACE_PC - select OBJTOOL if HAVE_OBJTOOL + select OBJTOOL if HAVE_NOINSTR_HACK help KCOV exposes kernel code coverage information in a form suitable for coverage-guided fuzzing (randomized testing). diff --git a/lib/Kconfig.kcsan b/lib/Kconfig.kcsan index 901c3b509aca..47a693c45864 100644 --- a/lib/Kconfig.kcsan +++ b/lib/Kconfig.kcsan @@ -187,8 +187,9 @@ config KCSAN_WEAK_MEMORY # We can either let objtool nop __tsan_func_{entry,exit}() and builtin # atomics instrumentation in .noinstr.text, or use a compiler that can # implement __no_kcsan to really remove all instrumentation. - depends on HAVE_OBJTOOL || CC_IS_GCC || CLANG_VERSION >= 140000 - select OBJTOOL if HAVE_OBJTOOL + depends on !ARCH_WANTS_NO_INSTR || HAVE_NOINSTR_HACK || \ + CC_IS_GCC || CLANG_VERSION >= 140000 + select OBJTOOL if HAVE_NOINSTR_HACK help Enable support for modeling a subset of weak memory, which allows detecting a subset of data races due to missing memory barriers. diff --git a/scripts/Makefile.build b/scripts/Makefile.build index f1d2c2e4f15b..6c206a1bab97 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -228,6 +228,7 @@ objtool := $(objtree)/tools/objtool/objtool objtool_args = \ $(if $(CONFIG_HAVE_JUMP_LABEL_HACK), --hacks=jump_label) \ + $(if $(CONFIG_HAVE_NOINSTR_HACK), --hacks=noinstr) \ $(if $(CONFIG_X86_KERNEL_IBT), --lto --ibt) \ $(if $(CONFIG_FTRACE_MCOUNT_USE_OBJTOOL), --mcount) \ $(if $(CONFIG_UNWINDER_ORC), --orc) \ diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh index fa1f16840e76..90c9c4c05d95 100755 --- a/scripts/link-vmlinux.sh +++ b/scripts/link-vmlinux.sh @@ -121,6 +121,10 @@ objtool_link() objtoolopt="${objtoolopt} --hacks=jump_label" fi + if is_enabled CONFIG_HAVE_NOINSTR_HACK; then + objtoolopt="${objtoolopt} --hacks=noinstr" + fi + if is_enabled CONFIG_X86_KERNEL_IBT; then objtoolopt="${objtoolopt} --ibt" fi diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index b2c626d9e2bf..1803a63147e4 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -47,12 +47,17 @@ static int parse_hacks(const struct option *opt, const char *str, int unset) found = true; } + if (!str || strstr(str, "noinstr")) { + opts.hack_noinstr = true; + found = true; + } + return found ? 0 : -1; } const struct option check_options[] = { OPT_GROUP("Actions:"), - OPT_CALLBACK_OPTARG('h', "hacks", NULL, NULL, "jump_label", "patch toolchain bugs/limitations", parse_hacks), + OPT_CALLBACK_OPTARG('h', "hacks", NULL, NULL, "jump_label,noinstr", "patch toolchain bugs/limitations", parse_hacks), OPT_BOOLEAN('i', "ibt", &opts.ibt, "validate and annotate IBT"), OPT_BOOLEAN('m', "mcount", &opts.mcount, "annotate mcount/fentry calls for ftrace"), OPT_BOOLEAN('n', "noinstr", &opts.noinstr, "validate noinstr rules"), @@ -108,6 +113,7 @@ int cmd_parse_options(int argc, const char **argv, const char * const usage[]) static bool opts_valid(void) { if (opts.hack_jump_label || + opts.hack_noinstr || opts.ibt || opts.mcount || opts.noinstr || diff --git a/tools/objtool/check.c b/tools/objtool/check.c index d157978c58b3..30b24dce48b5 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -1144,7 +1144,7 @@ static void annotate_call_site(struct objtool_file *file, * attribute so they need a little help, NOP out any such calls from * noinstr text. */ - if (insn->sec->noinstr && sym->profiling_func) { + if (opts.hack_noinstr && insn->sec->noinstr && sym->profiling_func) { if (reloc) { reloc->type = R_NONE; elf_write_reloc(file->elf, reloc); diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h index c6acf05ec859..f3a1a754b5c4 100644 --- a/tools/objtool/include/objtool/builtin.h +++ b/tools/objtool/include/objtool/builtin.h @@ -13,6 +13,7 @@ struct opts { /* actions: */ bool dump_orc; bool hack_jump_label; + bool hack_noinstr; bool ibt; bool mcount; bool noinstr; -- cgit From 753da4179d08b625d8df72e97724e22749969fd3 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 18 Apr 2022 09:50:43 -0700 Subject: objtool: Remove --lto and --vmlinux in favor of --link The '--lto' option is a confusing way of telling objtool to do stack validation despite it being a linked object. It's no longer needed now that an explicit '--stackval' option exists. The '--vmlinux' option is also redundant. Remove both options in favor of a straightforward '--link' option which identifies a linked object. Also, implicitly set '--link' with a warning if the user forgets to do so and we can tell that it's a linked object. This makes it easier for manual vmlinux runs. Signed-off-by: Josh Poimboeuf Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Miroslav Benes Link: https://lkml.kernel.org/r/dcd3ceffd15a54822c6183e5766d21ad06082b45.1650300597.git.jpoimboe@redhat.com --- scripts/Makefile.build | 4 +++- scripts/link-vmlinux.sh | 8 +++---- tools/objtool/builtin-check.c | 39 ++++++++++++++++++++++++++++---- tools/objtool/check.c | 40 ++++++++++++--------------------- tools/objtool/elf.c | 3 +++ tools/objtool/include/objtool/builtin.h | 3 +-- tools/objtool/include/objtool/elf.h | 12 +++++++++- 7 files changed, 70 insertions(+), 39 deletions(-) (limited to 'tools/objtool') diff --git a/scripts/Makefile.build b/scripts/Makefile.build index 6c206a1bab97..ac8167227bc0 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -229,7 +229,7 @@ objtool := $(objtree)/tools/objtool/objtool objtool_args = \ $(if $(CONFIG_HAVE_JUMP_LABEL_HACK), --hacks=jump_label) \ $(if $(CONFIG_HAVE_NOINSTR_HACK), --hacks=noinstr) \ - $(if $(CONFIG_X86_KERNEL_IBT), --lto --ibt) \ + $(if $(CONFIG_X86_KERNEL_IBT), --ibt) \ $(if $(CONFIG_FTRACE_MCOUNT_USE_OBJTOOL), --mcount) \ $(if $(CONFIG_UNWINDER_ORC), --orc) \ $(if $(CONFIG_RETPOLINE), --retpoline) \ @@ -237,6 +237,7 @@ objtool_args = \ $(if $(CONFIG_STACK_VALIDATION), --stackval) \ $(if $(CONFIG_HAVE_STATIC_CALL_INLINE), --static-call) \ $(if $(CONFIG_X86_SMAP), --uaccess) \ + $(if $(linked-object), --link) \ $(if $(part-of-module), --module) \ $(if $(CONFIG_GCOV_KERNEL), --no-unreachable) @@ -306,6 +307,7 @@ quiet_cmd_cc_prelink_modules = LD [M] $@ # modules into native code $(obj)/%.prelink.o: objtool-enabled = y $(obj)/%.prelink.o: part-of-module := y +$(obj)/%.prelink.o: linked-object := y $(obj)/%.prelink.o: $(obj)/%.o FORCE $(call if_changed,cc_prelink_modules) diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh index fce4f41816cd..eb9324f07f3d 100755 --- a/scripts/link-vmlinux.sh +++ b/scripts/link-vmlinux.sh @@ -114,8 +114,8 @@ objtool_link() if is_enabled CONFIG_LTO_CLANG || is_enabled CONFIG_X86_KERNEL_IBT; then - # Don't perform vmlinux validation unless explicitly requested, - # but run objtool on vmlinux.o now that we have an object file. + # For LTO and IBT, objtool doesn't run on individual + # translation units. Run everything on vmlinux instead. if is_enabled CONFIG_HAVE_JUMP_LABEL_HACK; then objtoolopt="${objtoolopt} --hacks=jump_label" @@ -156,8 +156,6 @@ objtool_link() if is_enabled CONFIG_X86_SMAP; then objtoolopt="${objtoolopt} --uaccess" fi - - objtoolopt="${objtoolopt} --lto" fi if is_enabled CONFIG_NOINSTR_VALIDATION; then @@ -170,7 +168,7 @@ objtool_link() objtoolopt="${objtoolopt} --no-unreachable" fi - objtoolopt="${objtoolopt} --vmlinux" + objtoolopt="${objtoolopt} --link" info OBJTOOL ${1} tools/objtool/objtool ${objtoolopt} ${1} diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index 1803a63147e4..f4c3a5091737 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -9,6 +9,11 @@ #include #include +#define ERROR(format, ...) \ + fprintf(stderr, \ + "error: objtool: " format "\n", \ + ##__VA_ARGS__) + struct opts opts; static const char * const check_usage[] = { @@ -73,12 +78,11 @@ const struct option check_options[] = { OPT_BOOLEAN(0, "backtrace", &opts.backtrace, "unwind on error"), OPT_BOOLEAN(0, "backup", &opts.backup, "create .orig files before modification"), OPT_BOOLEAN(0, "dry-run", &opts.dryrun, "don't write modifications"), - OPT_BOOLEAN(0, "lto", &opts.lto, "whole-archive like runs"), + OPT_BOOLEAN(0, "link", &opts.link, "object is a linked object"), OPT_BOOLEAN(0, "module", &opts.module, "object is part of a kernel module"), OPT_BOOLEAN(0, "no-unreachable", &opts.no_unreachable, "skip 'unreachable instruction' warnings"), OPT_BOOLEAN(0, "sec-address", &opts.sec_address, "print section addresses in warnings"), OPT_BOOLEAN(0, "stats", &opts.stats, "print statistics"), - OPT_BOOLEAN(0, "vmlinux", &opts.vmlinux, "vmlinux.o validation"), OPT_END(), }; @@ -124,7 +128,7 @@ static bool opts_valid(void) opts.static_call || opts.uaccess) { if (opts.dump_orc) { - fprintf(stderr, "--dump can't be combined with other options\n"); + ERROR("--dump can't be combined with other options"); return false; } @@ -134,10 +138,34 @@ static bool opts_valid(void) if (opts.dump_orc) return true; - fprintf(stderr, "At least one command required\n"); + ERROR("At least one command required"); return false; } +static bool link_opts_valid(struct objtool_file *file) +{ + if (opts.link) + return true; + + if (has_multiple_files(file->elf)) { + ERROR("Linked object detected, forcing --link"); + opts.link = true; + return true; + } + + if (opts.noinstr) { + ERROR("--noinstr requires --link"); + return false; + } + + if (opts.ibt) { + ERROR("--ibt requires --link"); + return false; + } + + return true; +} + int objtool_run(int argc, const char **argv) { const char *objname; @@ -157,6 +185,9 @@ int objtool_run(int argc, const char **argv) if (!file) return 1; + if (!link_opts_valid(file)) + return 1; + ret = check(file); if (ret) return ret; diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 30b24dce48b5..2063f9fea1a2 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -263,7 +263,8 @@ static void init_cfi_state(struct cfi_state *cfi) cfi->drap_offset = -1; } -static void init_insn_state(struct insn_state *state, struct section *sec) +static void init_insn_state(struct objtool_file *file, struct insn_state *state, + struct section *sec) { memset(state, 0, sizeof(*state)); init_cfi_state(&state->cfi); @@ -273,7 +274,7 @@ static void init_insn_state(struct insn_state *state, struct section *sec) * not correctly determine insn->call_dest->sec (external symbols do * not have a section). */ - if (opts.vmlinux && opts.noinstr && sec) + if (opts.link && opts.noinstr && sec) state->noinstr = sec->noinstr; } @@ -3405,7 +3406,7 @@ static int validate_unwind_hints(struct objtool_file *file, struct section *sec) if (!file->hints) return 0; - init_insn_state(&state, sec); + init_insn_state(file, &state, sec); if (sec) { insn = find_insn(file, sec, 0); @@ -3491,14 +3492,14 @@ static bool ignore_unreachable_insn(struct objtool_file *file, struct instructio return true; /* - * Whole archive runs might encounder dead code from weak symbols. + * Whole archive runs might encounter dead code from weak symbols. * This is where the linker will have dropped the weak symbol in * favour of a regular symbol, but leaves the code in place. * * In this case we'll find a piece of code (whole function) that is not * covered by a !section symbol. Ignore them. */ - if (!insn->func && opts.lto) { + if (opts.link && !insn->func) { int size = find_symbol_hole_containing(insn->sec, insn->offset); unsigned long end = insn->offset + size; @@ -3620,7 +3621,7 @@ static int validate_section(struct objtool_file *file, struct section *sec) if (func->type != STT_FUNC) continue; - init_insn_state(&state, sec); + init_insn_state(file, &state, sec); set_func_state(&state.cfi); warnings += validate_symbol(file, sec, func, &state); @@ -3629,7 +3630,7 @@ static int validate_section(struct objtool_file *file, struct section *sec) return warnings; } -static int validate_vmlinux_functions(struct objtool_file *file) +static int validate_noinstr_sections(struct objtool_file *file) { struct section *sec; int warnings = 0; @@ -3890,16 +3891,6 @@ int check(struct objtool_file *file) { int ret, warnings = 0; - if (opts.lto && !(opts.vmlinux || opts.module)) { - fprintf(stderr, "--lto requires: --vmlinux or --module\n"); - return 1; - } - - if (opts.ibt && !opts.lto) { - fprintf(stderr, "--ibt requires: --lto\n"); - return 1; - } - arch_initial_func_cfi_state(&initial_func_cfi); init_cfi_state(&init_cfi); init_cfi_state(&func_cfi); @@ -3920,15 +3911,6 @@ int check(struct objtool_file *file) if (list_empty(&file->insn_list)) goto out; - if (opts.vmlinux && !opts.lto) { - ret = validate_vmlinux_functions(file); - if (ret < 0) - goto out; - - warnings += ret; - goto out; - } - if (opts.retpoline) { ret = validate_retpoline(file); if (ret < 0) @@ -3953,6 +3935,12 @@ int check(struct objtool_file *file) goto out; warnings += ret; } + + } else if (opts.noinstr) { + ret = validate_noinstr_sections(file); + if (ret < 0) + goto out; + warnings += ret; } if (opts.ibt) { diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index 0f6fa372e10e..583a3ec987b5 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -377,6 +377,9 @@ static void elf_add_symbol(struct elf *elf, struct symbol *sym) sym->type = GELF_ST_TYPE(sym->sym.st_info); sym->bind = GELF_ST_BIND(sym->sym.st_info); + if (sym->type == STT_FILE) + elf->num_files++; + sym->offset = sym->sym.st_value; sym->len = sym->sym.st_size; diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h index f3a1a754b5c4..280ea18b7f2b 100644 --- a/tools/objtool/include/objtool/builtin.h +++ b/tools/objtool/include/objtool/builtin.h @@ -28,12 +28,11 @@ struct opts { bool backtrace; bool backup; bool dryrun; - bool lto; + bool link; bool module; bool no_unreachable; bool sec_address; bool stats; - bool vmlinux; }; extern struct opts opts; diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h index 9b36802ed86f..de0cb2f313ba 100644 --- a/tools/objtool/include/objtool/elf.h +++ b/tools/objtool/include/objtool/elf.h @@ -86,7 +86,7 @@ struct elf { int fd; bool changed; char *name; - unsigned int text_size; + unsigned int text_size, num_files; struct list_head sections; int symbol_bits; @@ -131,6 +131,16 @@ static inline u32 reloc_hash(struct reloc *reloc) return sec_offset_hash(reloc->sec, reloc->offset); } +/* + * Try to see if it's a whole archive (vmlinux.o or module). + * + * Note this will miss the case where a module only has one source file. + */ +static inline bool has_multiple_files(struct elf *elf) +{ + return elf->num_files > 1; +} + struct elf *elf_open_read(const char *name, int flags); struct section *elf_create_section(struct elf *elf, const char *name, unsigned int sh_flags, size_t entsize, int nr); -- cgit From a8e35fece49b16b20de000aab687ca075e4463af Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 18 Apr 2022 09:50:44 -0700 Subject: objtool: Update documentation The objtool documentation is very stack validation centric. Broaden the documentation and describe all the features objtool supports. Signed-off-by: Josh Poimboeuf Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Miroslav Benes Link: https://lkml.kernel.org/r/b6a84d301d9f73ec6725752654097f4e31fa1b69.1650300597.git.jpoimboe@redhat.com --- tools/objtool/Documentation/objtool.txt | 442 +++++++++++++++++++++++ tools/objtool/Documentation/stack-validation.txt | 360 ------------------ 2 files changed, 442 insertions(+), 360 deletions(-) create mode 100644 tools/objtool/Documentation/objtool.txt delete mode 100644 tools/objtool/Documentation/stack-validation.txt (limited to 'tools/objtool') diff --git a/tools/objtool/Documentation/objtool.txt b/tools/objtool/Documentation/objtool.txt new file mode 100644 index 000000000000..8a671902a187 --- /dev/null +++ b/tools/objtool/Documentation/objtool.txt @@ -0,0 +1,442 @@ +Objtool +======= + +The kernel CONFIG_OBJTOOL option enables a host tool named 'objtool' +which runs at compile time. It can do various validations and +transformations on .o files. + +Objtool has become an integral part of the x86-64 kernel toolchain. The +kernel depends on it for a variety of security and performance features +(and other types of features as well). + + +Features +-------- + +Objtool has the following features: + +- Stack unwinding metadata validation -- useful for helping to ensure + stack traces are reliable for live patching + +- ORC unwinder metadata generation -- a faster and more precise + alternative to frame pointer based unwinding + +- Retpoline validation -- ensures that all indirect calls go through + retpoline thunks, for Spectre v2 mitigations + +- Retpoline call site annotation -- annotates all retpoline thunk call + sites, enabling the kernel to patch them inline, to prevent "thunk + funneling" for both security and performance reasons + +- Non-instrumentation validation -- validates non-instrumentable + ("noinstr") code rules, preventing instrumentation in low-level C + entry code + +- Static call annotation -- annotates static call sites, enabling the + kernel to implement inline static calls, a faster alternative to some + indirect branches + +- Uaccess validation -- validates uaccess rules for a proper + implementation of Supervisor Mode Access Protection (SMAP) + +- Straight Line Speculation validation -- validates certain SLS + mitigations + +- Indirect Branch Tracking validation -- validates Intel CET IBT rules + to ensure that all functions referenced by function pointers have + corresponding ENDBR instructions + +- Indirect Branch Tracking annotation -- annotates unused ENDBR + instruction sites, enabling the kernel to "seal" them (replace them + with NOPs) to further harden IBT + +- Function entry annotation -- annotates function entries, enabling + kernel function tracing + +- Other toolchain hacks which will go unmentioned at this time... + +Each feature can be enabled individually or in combination using the +objtool cmdline. + + +Objects +------- + +Typically, objtool runs on every translation unit (TU, aka ".o file") in +the kernel. If a TU is part of a kernel module, the '--module' option +is added. + +However: + +- If noinstr validation is enabled, it also runs on vmlinux.o, with all + options removed and '--noinstr' added. + +- If IBT or LTO is enabled, it doesn't run on TUs at all. Instead it + runs on vmlinux.o and linked modules, with all options. + +In summary: + + A) Legacy mode: + TU: objtool [--module] + vmlinux: N/A + module: N/A + + B) CONFIG_NOINSTR_VALIDATION=y && !(CONFIG_X86_KERNEL_IBT=y || CONFIG_LTO=y): + TU: objtool [--module] // no --noinstr + vmlinux: objtool --noinstr // other options removed + module: N/A + + C) CONFIG_X86_KERNEL_IBT=y || CONFIG_LTO=y: + TU: N/A + vmlinux: objtool --noinstr + module: objtool --module --noinstr + + +Stack validation +---------------- + +Objtool's stack validation feature analyzes every .o file and ensures +the validity of its stack metadata. It enforces a set of rules on asm +code and C inline assembly code so that stack traces can be reliable. + +For each function, it recursively follows all possible code paths and +validates the correct frame pointer state at each instruction. + +It also follows code paths involving special sections, like +.altinstructions, __jump_table, and __ex_table, which can add +alternative execution paths to a given instruction (or set of +instructions). Similarly, it knows how to follow switch statements, for +which gcc sometimes uses jump tables. + +Here are some of the benefits of validating stack metadata: + +a) More reliable stack traces for frame pointer enabled kernels + + Frame pointers are used for debugging purposes. They allow runtime + code and debug tools to be able to walk the stack to determine the + chain of function call sites that led to the currently executing + code. + + For some architectures, frame pointers are enabled by + CONFIG_FRAME_POINTER. For some other architectures they may be + required by the ABI (sometimes referred to as "backchain pointers"). + + For C code, gcc automatically generates instructions for setting up + frame pointers when the -fno-omit-frame-pointer option is used. + + But for asm code, the frame setup instructions have to be written by + hand, which most people don't do. So the end result is that + CONFIG_FRAME_POINTER is honored for C code but not for most asm code. + + For stack traces based on frame pointers to be reliable, all + functions which call other functions must first create a stack frame + and update the frame pointer. If a first function doesn't properly + create a stack frame before calling a second function, the *caller* + of the first function will be skipped on the stack trace. + + For example, consider the following example backtrace with frame + pointers enabled: + + [] dump_stack+0x4b/0x63 + [] cmdline_proc_show+0x12/0x30 + [] seq_read+0x108/0x3e0 + [] proc_reg_read+0x42/0x70 + [] __vfs_read+0x37/0x100 + [] vfs_read+0x86/0x130 + [] SyS_read+0x58/0xd0 + [] entry_SYSCALL_64_fastpath+0x12/0x76 + + It correctly shows that the caller of cmdline_proc_show() is + seq_read(). + + If we remove the frame pointer logic from cmdline_proc_show() by + replacing the frame pointer related instructions with nops, here's + what it looks like instead: + + [] dump_stack+0x4b/0x63 + [] cmdline_proc_show+0x12/0x30 + [] proc_reg_read+0x42/0x70 + [] __vfs_read+0x37/0x100 + [] vfs_read+0x86/0x130 + [] SyS_read+0x58/0xd0 + [] entry_SYSCALL_64_fastpath+0x12/0x76 + + Notice that cmdline_proc_show()'s caller, seq_read(), has been + skipped. Instead the stack trace seems to show that + cmdline_proc_show() was called by proc_reg_read(). + + The benefit of objtool here is that because it ensures that *all* + functions honor CONFIG_FRAME_POINTER, no functions will ever[*] be + skipped on a stack trace. + + [*] unless an interrupt or exception has occurred at the very + beginning of a function before the stack frame has been created, + or at the very end of the function after the stack frame has been + destroyed. This is an inherent limitation of frame pointers. + +b) ORC (Oops Rewind Capability) unwind table generation + + An alternative to frame pointers and DWARF, ORC unwind data can be + used to walk the stack. Unlike frame pointers, ORC data is out of + band. So it doesn't affect runtime performance and it can be + reliable even when interrupts or exceptions are involved. + + For more details, see Documentation/x86/orc-unwinder.rst. + +c) Higher live patching compatibility rate + + Livepatch has an optional "consistency model", which is needed for + more complex patches. In order for the consistency model to work, + stack traces need to be reliable (or an unreliable condition needs to + be detectable). Objtool makes that possible. + + For more details, see the livepatch documentation in the Linux kernel + source tree at Documentation/livepatch/livepatch.rst. + +To achieve the validation, objtool enforces the following rules: + +1. Each callable function must be annotated as such with the ELF + function type. In asm code, this is typically done using the + ENTRY/ENDPROC macros. If objtool finds a return instruction + outside of a function, it flags an error since that usually indicates + callable code which should be annotated accordingly. + + This rule is needed so that objtool can properly identify each + callable function in order to analyze its stack metadata. + +2. Conversely, each section of code which is *not* callable should *not* + be annotated as an ELF function. The ENDPROC macro shouldn't be used + in this case. + + This rule is needed so that objtool can ignore non-callable code. + Such code doesn't have to follow any of the other rules. + +3. Each callable function which calls another function must have the + correct frame pointer logic, if required by CONFIG_FRAME_POINTER or + the architecture's back chain rules. This can by done in asm code + with the FRAME_BEGIN/FRAME_END macros. + + This rule ensures that frame pointer based stack traces will work as + designed. If function A doesn't create a stack frame before calling + function B, the _caller_ of function A will be skipped on the stack + trace. + +4. Dynamic jumps and jumps to undefined symbols are only allowed if: + + a) the jump is part of a switch statement; or + + b) the jump matches sibling call semantics and the frame pointer has + the same value it had on function entry. + + This rule is needed so that objtool can reliably analyze all of a + function's code paths. If a function jumps to code in another file, + and it's not a sibling call, objtool has no way to follow the jump + because it only analyzes a single file at a time. + +5. A callable function may not execute kernel entry/exit instructions. + The only code which needs such instructions is kernel entry code, + which shouldn't be be in callable functions anyway. + + This rule is just a sanity check to ensure that callable functions + return normally. + + +Objtool warnings +---------------- + +For asm files, if you're getting an error which doesn't make sense, +first make sure that the affected code follows the above rules. + +For C files, the common culprits are inline asm statements and calls to +"noreturn" functions. See below for more details. + +Another possible cause for errors in C code is if the Makefile removes +-fno-omit-frame-pointer or adds -fomit-frame-pointer to the gcc options. + +Here are some examples of common warnings reported by objtool, what +they mean, and suggestions for how to fix them. When in doubt, ping +the objtool maintainers. + + +1. file.o: warning: objtool: func()+0x128: call without frame pointer save/setup + + The func() function made a function call without first saving and/or + updating the frame pointer, and CONFIG_FRAME_POINTER is enabled. + + If the error is for an asm file, and func() is indeed a callable + function, add proper frame pointer logic using the FRAME_BEGIN and + FRAME_END macros. Otherwise, if it's not a callable function, remove + its ELF function annotation by changing ENDPROC to END, and instead + use the manual unwind hint macros in asm/unwind_hints.h. + + If it's a GCC-compiled .c file, the error may be because the function + uses an inline asm() statement which has a "call" instruction. An + asm() statement with a call instruction must declare the use of the + stack pointer in its output operand. On x86_64, this means adding + the ASM_CALL_CONSTRAINT as an output constraint: + + asm volatile("call func" : ASM_CALL_CONSTRAINT); + + Otherwise the stack frame may not get created before the call. + + +2. file.o: warning: objtool: .text+0x53: unreachable instruction + + Objtool couldn't find a code path to reach the instruction. + + If the error is for an asm file, and the instruction is inside (or + reachable from) a callable function, the function should be annotated + with the ENTRY/ENDPROC macros (ENDPROC is the important one). + Otherwise, the code should probably be annotated with the unwind hint + macros in asm/unwind_hints.h so objtool and the unwinder can know the + stack state associated with the code. + + If you're 100% sure the code won't affect stack traces, or if you're + a just a bad person, you can tell objtool to ignore it. See the + "Adding exceptions" section below. + + If it's not actually in a callable function (e.g. kernel entry code), + change ENDPROC to END. + + +4. file.o: warning: objtool: func(): can't find starting instruction + or + file.o: warning: objtool: func()+0x11dd: can't decode instruction + + Does the file have data in a text section? If so, that can confuse + objtool's instruction decoder. Move the data to a more appropriate + section like .data or .rodata. + + +5. file.o: warning: objtool: func()+0x6: unsupported instruction in callable function + + This is a kernel entry/exit instruction like sysenter or iret. Such + instructions aren't allowed in a callable function, and are most + likely part of the kernel entry code. They should usually not have + the callable function annotation (ENDPROC) and should always be + annotated with the unwind hint macros in asm/unwind_hints.h. + + +6. file.o: warning: objtool: func()+0x26: sibling call from callable instruction with modified stack frame + + This is a dynamic jump or a jump to an undefined symbol. Objtool + assumed it's a sibling call and detected that the frame pointer + wasn't first restored to its original state. + + If it's not really a sibling call, you may need to move the + destination code to the local file. + + If the instruction is not actually in a callable function (e.g. + kernel entry code), change ENDPROC to END and annotate manually with + the unwind hint macros in asm/unwind_hints.h. + + +7. file: warning: objtool: func()+0x5c: stack state mismatch + + The instruction's frame pointer state is inconsistent, depending on + which execution path was taken to reach the instruction. + + Make sure that, when CONFIG_FRAME_POINTER is enabled, the function + pushes and sets up the frame pointer (for x86_64, this means rbp) at + the beginning of the function and pops it at the end of the function. + Also make sure that no other code in the function touches the frame + pointer. + + Another possibility is that the code has some asm or inline asm which + does some unusual things to the stack or the frame pointer. In such + cases it's probably appropriate to use the unwind hint macros in + asm/unwind_hints.h. + + +8. file.o: warning: objtool: funcA() falls through to next function funcB() + + This means that funcA() doesn't end with a return instruction or an + unconditional jump, and that objtool has determined that the function + can fall through into the next function. There could be different + reasons for this: + + 1) funcA()'s last instruction is a call to a "noreturn" function like + panic(). In this case the noreturn function needs to be added to + objtool's hard-coded global_noreturns array. Feel free to bug the + objtool maintainer, or you can submit a patch. + + 2) funcA() uses the unreachable() annotation in a section of code + that is actually reachable. + + 3) If funcA() calls an inline function, the object code for funcA() + might be corrupt due to a gcc bug. For more details, see: + https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70646 + +9. file.o: warning: objtool: funcA() call to funcB() with UACCESS enabled + + This means that an unexpected call to a non-whitelisted function exists + outside of arch-specific guards. + X86: SMAP (stac/clac): __uaccess_begin()/__uaccess_end() + ARM: PAN: uaccess_enable()/uaccess_disable() + + These functions should be called to denote a minimal critical section around + access to __user variables. See also: https://lwn.net/Articles/517475/ + + The intention of the warning is to prevent calls to funcB() from eventually + calling schedule(), potentially leaking the AC flags state, and not + restoring them correctly. + + It also helps verify that there are no unexpected calls to funcB() which may + access user space pages with protections against doing so disabled. + + To fix, either: + 1) remove explicit calls to funcB() from funcA(). + 2) add the correct guards before and after calls to low level functions like + __get_user_size()/__put_user_size(). + 3) add funcB to uaccess_safe_builtin whitelist in tools/objtool/check.c, if + funcB obviously does not call schedule(), and is marked notrace (since + function tracing inserts additional calls, which is not obvious from the + sources). + +10. file.o: warning: func()+0x5c: stack layout conflict in alternatives + + This means that in the use of the alternative() or ALTERNATIVE() + macro, the code paths have conflicting modifications to the stack. + The problem is that there is only one ORC unwind table, which means + that the ORC unwind entries must be consistent for all possible + instruction boundaries regardless of which code has been patched. + This limitation can be overcome by massaging the alternatives with + NOPs to shift the stack changes around so they no longer conflict. + +11. file.o: warning: unannotated intra-function call + + This warning means that a direct call is done to a destination which + is not at the beginning of a function. If this is a legit call, you + can remove this warning by putting the ANNOTATE_INTRA_FUNCTION_CALL + directive right before the call. + + +If the error doesn't seem to make sense, it could be a bug in objtool. +Feel free to ask the objtool maintainer for help. + + +Adding exceptions +----------------- + +If you _really_ need objtool to ignore something, and are 100% sure +that it won't affect kernel stack traces, you can tell objtool to +ignore it: + +- To skip validation of a function, use the STACK_FRAME_NON_STANDARD + macro. + +- To skip validation of a file, add + + OBJECT_FILES_NON_STANDARD_filename.o := y + + to the Makefile. + +- To skip validation of a directory, add + + OBJECT_FILES_NON_STANDARD := y + + to the Makefile. + +NOTE: OBJECT_FILES_NON_STANDARD doesn't work for link time validation of +vmlinux.o or a linked module. So it should only be used for files which +aren't linked into vmlinux or a module. diff --git a/tools/objtool/Documentation/stack-validation.txt b/tools/objtool/Documentation/stack-validation.txt deleted file mode 100644 index 30f38fdc0d56..000000000000 --- a/tools/objtool/Documentation/stack-validation.txt +++ /dev/null @@ -1,360 +0,0 @@ -Compile-time stack metadata validation -====================================== - - -Overview --------- - -The kernel CONFIG_STACK_VALIDATION option enables a host tool named -objtool which runs at compile time. It has a "check" subcommand which -analyzes every .o file and ensures the validity of its stack metadata. -It enforces a set of rules on asm code and C inline assembly code so -that stack traces can be reliable. - -For each function, it recursively follows all possible code paths and -validates the correct frame pointer state at each instruction. - -It also follows code paths involving special sections, like -.altinstructions, __jump_table, and __ex_table, which can add -alternative execution paths to a given instruction (or set of -instructions). Similarly, it knows how to follow switch statements, for -which gcc sometimes uses jump tables. - -(Objtool also has an 'orc generate' subcommand which generates debuginfo -for the ORC unwinder. See Documentation/x86/orc-unwinder.rst in the -kernel tree for more details.) - - -Why do we need stack metadata validation? ------------------------------------------ - -Here are some of the benefits of validating stack metadata: - -a) More reliable stack traces for frame pointer enabled kernels - - Frame pointers are used for debugging purposes. They allow runtime - code and debug tools to be able to walk the stack to determine the - chain of function call sites that led to the currently executing - code. - - For some architectures, frame pointers are enabled by - CONFIG_FRAME_POINTER. For some other architectures they may be - required by the ABI (sometimes referred to as "backchain pointers"). - - For C code, gcc automatically generates instructions for setting up - frame pointers when the -fno-omit-frame-pointer option is used. - - But for asm code, the frame setup instructions have to be written by - hand, which most people don't do. So the end result is that - CONFIG_FRAME_POINTER is honored for C code but not for most asm code. - - For stack traces based on frame pointers to be reliable, all - functions which call other functions must first create a stack frame - and update the frame pointer. If a first function doesn't properly - create a stack frame before calling a second function, the *caller* - of the first function will be skipped on the stack trace. - - For example, consider the following example backtrace with frame - pointers enabled: - - [] dump_stack+0x4b/0x63 - [] cmdline_proc_show+0x12/0x30 - [] seq_read+0x108/0x3e0 - [] proc_reg_read+0x42/0x70 - [] __vfs_read+0x37/0x100 - [] vfs_read+0x86/0x130 - [] SyS_read+0x58/0xd0 - [] entry_SYSCALL_64_fastpath+0x12/0x76 - - It correctly shows that the caller of cmdline_proc_show() is - seq_read(). - - If we remove the frame pointer logic from cmdline_proc_show() by - replacing the frame pointer related instructions with nops, here's - what it looks like instead: - - [] dump_stack+0x4b/0x63 - [] cmdline_proc_show+0x12/0x30 - [] proc_reg_read+0x42/0x70 - [] __vfs_read+0x37/0x100 - [] vfs_read+0x86/0x130 - [] SyS_read+0x58/0xd0 - [] entry_SYSCALL_64_fastpath+0x12/0x76 - - Notice that cmdline_proc_show()'s caller, seq_read(), has been - skipped. Instead the stack trace seems to show that - cmdline_proc_show() was called by proc_reg_read(). - - The benefit of objtool here is that because it ensures that *all* - functions honor CONFIG_FRAME_POINTER, no functions will ever[*] be - skipped on a stack trace. - - [*] unless an interrupt or exception has occurred at the very - beginning of a function before the stack frame has been created, - or at the very end of the function after the stack frame has been - destroyed. This is an inherent limitation of frame pointers. - -b) ORC (Oops Rewind Capability) unwind table generation - - An alternative to frame pointers and DWARF, ORC unwind data can be - used to walk the stack. Unlike frame pointers, ORC data is out of - band. So it doesn't affect runtime performance and it can be - reliable even when interrupts or exceptions are involved. - - For more details, see Documentation/x86/orc-unwinder.rst. - -c) Higher live patching compatibility rate - - Livepatch has an optional "consistency model", which is needed for - more complex patches. In order for the consistency model to work, - stack traces need to be reliable (or an unreliable condition needs to - be detectable). Objtool makes that possible. - - For more details, see the livepatch documentation in the Linux kernel - source tree at Documentation/livepatch/livepatch.rst. - -Rules ------ - -To achieve the validation, objtool enforces the following rules: - -1. Each callable function must be annotated as such with the ELF - function type. In asm code, this is typically done using the - ENTRY/ENDPROC macros. If objtool finds a return instruction - outside of a function, it flags an error since that usually indicates - callable code which should be annotated accordingly. - - This rule is needed so that objtool can properly identify each - callable function in order to analyze its stack metadata. - -2. Conversely, each section of code which is *not* callable should *not* - be annotated as an ELF function. The ENDPROC macro shouldn't be used - in this case. - - This rule is needed so that objtool can ignore non-callable code. - Such code doesn't have to follow any of the other rules. - -3. Each callable function which calls another function must have the - correct frame pointer logic, if required by CONFIG_FRAME_POINTER or - the architecture's back chain rules. This can by done in asm code - with the FRAME_BEGIN/FRAME_END macros. - - This rule ensures that frame pointer based stack traces will work as - designed. If function A doesn't create a stack frame before calling - function B, the _caller_ of function A will be skipped on the stack - trace. - -4. Dynamic jumps and jumps to undefined symbols are only allowed if: - - a) the jump is part of a switch statement; or - - b) the jump matches sibling call semantics and the frame pointer has - the same value it had on function entry. - - This rule is needed so that objtool can reliably analyze all of a - function's code paths. If a function jumps to code in another file, - and it's not a sibling call, objtool has no way to follow the jump - because it only analyzes a single file at a time. - -5. A callable function may not execute kernel entry/exit instructions. - The only code which needs such instructions is kernel entry code, - which shouldn't be be in callable functions anyway. - - This rule is just a sanity check to ensure that callable functions - return normally. - - -Objtool warnings ----------------- - -For asm files, if you're getting an error which doesn't make sense, -first make sure that the affected code follows the above rules. - -For C files, the common culprits are inline asm statements and calls to -"noreturn" functions. See below for more details. - -Another possible cause for errors in C code is if the Makefile removes --fno-omit-frame-pointer or adds -fomit-frame-pointer to the gcc options. - -Here are some examples of common warnings reported by objtool, what -they mean, and suggestions for how to fix them. - - -1. file.o: warning: objtool: func()+0x128: call without frame pointer save/setup - - The func() function made a function call without first saving and/or - updating the frame pointer, and CONFIG_FRAME_POINTER is enabled. - - If the error is for an asm file, and func() is indeed a callable - function, add proper frame pointer logic using the FRAME_BEGIN and - FRAME_END macros. Otherwise, if it's not a callable function, remove - its ELF function annotation by changing ENDPROC to END, and instead - use the manual unwind hint macros in asm/unwind_hints.h. - - If it's a GCC-compiled .c file, the error may be because the function - uses an inline asm() statement which has a "call" instruction. An - asm() statement with a call instruction must declare the use of the - stack pointer in its output operand. On x86_64, this means adding - the ASM_CALL_CONSTRAINT as an output constraint: - - asm volatile("call func" : ASM_CALL_CONSTRAINT); - - Otherwise the stack frame may not get created before the call. - - -2. file.o: warning: objtool: .text+0x53: unreachable instruction - - Objtool couldn't find a code path to reach the instruction. - - If the error is for an asm file, and the instruction is inside (or - reachable from) a callable function, the function should be annotated - with the ENTRY/ENDPROC macros (ENDPROC is the important one). - Otherwise, the code should probably be annotated with the unwind hint - macros in asm/unwind_hints.h so objtool and the unwinder can know the - stack state associated with the code. - - If you're 100% sure the code won't affect stack traces, or if you're - a just a bad person, you can tell objtool to ignore it. See the - "Adding exceptions" section below. - - If it's not actually in a callable function (e.g. kernel entry code), - change ENDPROC to END. - - -4. file.o: warning: objtool: func(): can't find starting instruction - or - file.o: warning: objtool: func()+0x11dd: can't decode instruction - - Does the file have data in a text section? If so, that can confuse - objtool's instruction decoder. Move the data to a more appropriate - section like .data or .rodata. - - -5. file.o: warning: objtool: func()+0x6: unsupported instruction in callable function - - This is a kernel entry/exit instruction like sysenter or iret. Such - instructions aren't allowed in a callable function, and are most - likely part of the kernel entry code. They should usually not have - the callable function annotation (ENDPROC) and should always be - annotated with the unwind hint macros in asm/unwind_hints.h. - - -6. file.o: warning: objtool: func()+0x26: sibling call from callable instruction with modified stack frame - - This is a dynamic jump or a jump to an undefined symbol. Objtool - assumed it's a sibling call and detected that the frame pointer - wasn't first restored to its original state. - - If it's not really a sibling call, you may need to move the - destination code to the local file. - - If the instruction is not actually in a callable function (e.g. - kernel entry code), change ENDPROC to END and annotate manually with - the unwind hint macros in asm/unwind_hints.h. - - -7. file: warning: objtool: func()+0x5c: stack state mismatch - - The instruction's frame pointer state is inconsistent, depending on - which execution path was taken to reach the instruction. - - Make sure that, when CONFIG_FRAME_POINTER is enabled, the function - pushes and sets up the frame pointer (for x86_64, this means rbp) at - the beginning of the function and pops it at the end of the function. - Also make sure that no other code in the function touches the frame - pointer. - - Another possibility is that the code has some asm or inline asm which - does some unusual things to the stack or the frame pointer. In such - cases it's probably appropriate to use the unwind hint macros in - asm/unwind_hints.h. - - -8. file.o: warning: objtool: funcA() falls through to next function funcB() - - This means that funcA() doesn't end with a return instruction or an - unconditional jump, and that objtool has determined that the function - can fall through into the next function. There could be different - reasons for this: - - 1) funcA()'s last instruction is a call to a "noreturn" function like - panic(). In this case the noreturn function needs to be added to - objtool's hard-coded global_noreturns array. Feel free to bug the - objtool maintainer, or you can submit a patch. - - 2) funcA() uses the unreachable() annotation in a section of code - that is actually reachable. - - 3) If funcA() calls an inline function, the object code for funcA() - might be corrupt due to a gcc bug. For more details, see: - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70646 - -9. file.o: warning: objtool: funcA() call to funcB() with UACCESS enabled - - This means that an unexpected call to a non-whitelisted function exists - outside of arch-specific guards. - X86: SMAP (stac/clac): __uaccess_begin()/__uaccess_end() - ARM: PAN: uaccess_enable()/uaccess_disable() - - These functions should be called to denote a minimal critical section around - access to __user variables. See also: https://lwn.net/Articles/517475/ - - The intention of the warning is to prevent calls to funcB() from eventually - calling schedule(), potentially leaking the AC flags state, and not - restoring them correctly. - - It also helps verify that there are no unexpected calls to funcB() which may - access user space pages with protections against doing so disabled. - - To fix, either: - 1) remove explicit calls to funcB() from funcA(). - 2) add the correct guards before and after calls to low level functions like - __get_user_size()/__put_user_size(). - 3) add funcB to uaccess_safe_builtin whitelist in tools/objtool/check.c, if - funcB obviously does not call schedule(), and is marked notrace (since - function tracing inserts additional calls, which is not obvious from the - sources). - -10. file.o: warning: func()+0x5c: stack layout conflict in alternatives - - This means that in the use of the alternative() or ALTERNATIVE() - macro, the code paths have conflicting modifications to the stack. - The problem is that there is only one ORC unwind table, which means - that the ORC unwind entries must be consistent for all possible - instruction boundaries regardless of which code has been patched. - This limitation can be overcome by massaging the alternatives with - NOPs to shift the stack changes around so they no longer conflict. - -11. file.o: warning: unannotated intra-function call - - This warning means that a direct call is done to a destination which - is not at the beginning of a function. If this is a legit call, you - can remove this warning by putting the ANNOTATE_INTRA_FUNCTION_CALL - directive right before the call. - - -If the error doesn't seem to make sense, it could be a bug in objtool. -Feel free to ask the objtool maintainer for help. - - -Adding exceptions ------------------ - -If you _really_ need objtool to ignore something, and are 100% sure -that it won't affect kernel stack traces, you can tell objtool to -ignore it: - -- To skip validation of a function, use the STACK_FRAME_NON_STANDARD - macro. - -- To skip validation of a file, add - - OBJECT_FILES_NON_STANDARD_filename.o := y - - to the Makefile. - -- To skip validation of a directory, add - - OBJECT_FILES_NON_STANDARD := y - - to the Makefile. -- cgit From f193c32cad2ddc79ad55a2e2fb3bc35e7d92946a Mon Sep 17 00:00:00 2001 From: Tiezhu Yang Date: Wed, 11 May 2022 16:37:49 +0800 Subject: objtool: Remove inat-tables.c when make clean When build objtool on x86, the generated file inat-tables.c is in arch/x86/lib instead of arch/x86, use the correct dir to remove it when make clean. $ cd tools/objtool $ make [...] GEN arch/x86/lib/inat-tables.c [...] Signed-off-by: Tiezhu Yang Signed-off-by: Josh Poimboeuf Link: https://lore.kernel.org/r/1652258270-6278-2-git-send-email-yangtiezhu@loongson.cn --- tools/objtool/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tools/objtool') diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index 061cf1cd42c4..8a90763cef12 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -63,7 +63,7 @@ $(LIBSUBCMD): fixdep FORCE clean: $(call QUIET_CLEAN, objtool) $(RM) $(OBJTOOL) $(Q)find $(OUTPUT) -name '*.o' -delete -o -name '\.*.cmd' -delete -o -name '\.*.d' -delete - $(Q)$(RM) $(OUTPUT)arch/x86/inat-tables.c $(OUTPUT)fixdep + $(Q)$(RM) $(OUTPUT)arch/x86/lib/inat-tables.c $(OUTPUT)fixdep FORCE: -- cgit From 4bc78005887f6fca60b624822943708652fda01a Mon Sep 17 00:00:00 2001 From: Tiezhu Yang Date: Wed, 11 May 2022 16:37:50 +0800 Subject: objtool: Remove libsubcmd.a when make clean The file libsubcmd.a still exists after make clean, remove it. Signed-off-by: Tiezhu Yang Signed-off-by: Josh Poimboeuf Link: https://lore.kernel.org/r/1652258270-6278-3-git-send-email-yangtiezhu@loongson.cn --- tools/objtool/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tools/objtool') diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index 8a90763cef12..e66d717c245d 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -63,7 +63,7 @@ $(LIBSUBCMD): fixdep FORCE clean: $(call QUIET_CLEAN, objtool) $(RM) $(OBJTOOL) $(Q)find $(OUTPUT) -name '*.o' -delete -o -name '\.*.cmd' -delete -o -name '\.*.d' -delete - $(Q)$(RM) $(OUTPUT)arch/x86/lib/inat-tables.c $(OUTPUT)fixdep + $(Q)$(RM) $(OUTPUT)arch/x86/lib/inat-tables.c $(OUTPUT)fixdep $(LIBSUBCMD) FORCE: -- cgit From ead165fa1042247b033afad7be4be9b815d04ade Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Tue, 17 May 2022 17:42:04 +0200 Subject: objtool: Fix symbol creation Nathan reported objtool failing with the following messages: warning: objtool: no non-local symbols !? warning: objtool: gelf_update_symshndx: invalid section index The problem is due to commit 4abff6d48dbc ("objtool: Fix code relocs vs weak symbols") failing to consider the case where an object would have no non-local symbols. The problem that commit tries to address is adding a STB_LOCAL symbol to the symbol table in light of the ELF spec's requirement that: In each symbol table, all symbols with STB_LOCAL binding preced the weak and global symbols. As ``Sections'' above describes, a symbol table section's sh_info section header member holds the symbol table index for the first non-local symbol. The approach taken is to find this first non-local symbol, move that to the end and then re-use the freed spot to insert a new local symbol and increment sh_info. Except it never considered the case of object files without global symbols and got a whole bunch of details wrong -- so many in fact that it is a wonder it ever worked :/ Specifically: - It failed to re-hash the symbol on the new index, so a subsequent find_symbol_by_index() would not find it at the new location and a query for the old location would now return a non-deterministic choice between the old and new symbol. - It failed to appreciate that the GElf wrappers are not a valid disk format (it works because GElf is basically Elf64 and we only support x86_64 atm.) - It failed to fully appreciate how horrible the libelf API really is and got the gelf_update_symshndx() call pretty much completely wrong; with the direct consequence that if inserting a second STB_LOCAL symbol would require moving the same STB_GLOBAL symbol again it would completely come unstuck. Write a new elf_update_symbol() function that wraps all the magic required to update or create a new symbol at a given index. Specifically, gelf_update_sym*() require an @ndx argument that is relative to the @data argument; this means you have to manually iterate the section data descriptor list and update @ndx. Fixes: 4abff6d48dbc ("objtool: Fix code relocs vs weak symbols") Reported-by: Nathan Chancellor Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Borislav Petkov Acked-by: Josh Poimboeuf Tested-by: Nathan Chancellor Cc: Link: https://lkml.kernel.org/r/YoPCTEYjoPqE4ZxB@hirez.programming.kicks-ass.net --- tools/objtool/elf.c | 198 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 129 insertions(+), 69 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index 583a3ec987b5..7fb6720e510b 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -374,6 +374,9 @@ static void elf_add_symbol(struct elf *elf, struct symbol *sym) struct list_head *entry; struct rb_node *pnode; + INIT_LIST_HEAD(&sym->pv_target); + sym->alias = sym; + sym->type = GELF_ST_TYPE(sym->sym.st_info); sym->bind = GELF_ST_BIND(sym->sym.st_info); @@ -438,8 +441,6 @@ static int read_symbols(struct elf *elf) return -1; } memset(sym, 0, sizeof(*sym)); - INIT_LIST_HEAD(&sym->pv_target); - sym->alias = sym; sym->idx = i; @@ -603,24 +604,21 @@ static void elf_dirty_reloc_sym(struct elf *elf, struct symbol *sym) } /* - * Move the first global symbol, as per sh_info, into a new, higher symbol - * index. This fees up the shndx for a new local symbol. + * The libelf API is terrible; gelf_update_sym*() takes a data block relative + * index value, *NOT* the symbol index. As such, iterate the data blocks and + * adjust index until it fits. + * + * If no data block is found, allow adding a new data block provided the index + * is only one past the end. */ -static int elf_move_global_symbol(struct elf *elf, struct section *symtab, - struct section *symtab_shndx) +static int elf_update_symbol(struct elf *elf, struct section *symtab, + struct section *symtab_shndx, struct symbol *sym) { - Elf_Data *data, *shndx_data = NULL; - Elf32_Word first_non_local; - struct symbol *sym; - Elf_Scn *s; - - first_non_local = symtab->sh.sh_info; - - sym = find_symbol_by_index(elf, first_non_local); - if (!sym) { - WARN("no non-local symbols !?"); - return first_non_local; - } + Elf32_Word shndx = sym->sec ? sym->sec->idx : SHN_UNDEF; + Elf_Data *symtab_data = NULL, *shndx_data = NULL; + Elf64_Xword entsize = symtab->sh.sh_entsize; + int max_idx, idx = sym->idx; + Elf_Scn *s, *t = NULL; s = elf_getscn(elf->elf, symtab->idx); if (!s) { @@ -628,79 +626,124 @@ static int elf_move_global_symbol(struct elf *elf, struct section *symtab, return -1; } - data = elf_newdata(s); - if (!data) { - WARN_ELF("elf_newdata"); - return -1; + if (symtab_shndx) { + t = elf_getscn(elf->elf, symtab_shndx->idx); + if (!t) { + WARN_ELF("elf_getscn"); + return -1; + } } - data->d_buf = &sym->sym; - data->d_size = sizeof(sym->sym); - data->d_align = 1; - data->d_type = ELF_T_SYM; + for (;;) { + /* get next data descriptor for the relevant sections */ + symtab_data = elf_getdata(s, symtab_data); + if (t) + shndx_data = elf_getdata(t, shndx_data); - sym->idx = symtab->sh.sh_size / sizeof(sym->sym); - elf_dirty_reloc_sym(elf, sym); + /* end-of-list */ + if (!symtab_data) { + void *buf; - symtab->sh.sh_info += 1; - symtab->sh.sh_size += data->d_size; - symtab->changed = true; + if (idx) { + /* we don't do holes in symbol tables */ + WARN("index out of range"); + return -1; + } - if (symtab_shndx) { - s = elf_getscn(elf->elf, symtab_shndx->idx); - if (!s) { - WARN_ELF("elf_getscn"); + /* if @idx == 0, it's the next contiguous entry, create it */ + symtab_data = elf_newdata(s); + if (t) + shndx_data = elf_newdata(t); + + buf = calloc(1, entsize); + if (!buf) { + WARN("malloc"); + return -1; + } + + symtab_data->d_buf = buf; + symtab_data->d_size = entsize; + symtab_data->d_align = 1; + symtab_data->d_type = ELF_T_SYM; + + symtab->sh.sh_size += entsize; + symtab->changed = true; + + if (t) { + shndx_data->d_buf = &sym->sec->idx; + shndx_data->d_size = sizeof(Elf32_Word); + shndx_data->d_align = sizeof(Elf32_Word); + shndx_data->d_type = ELF_T_WORD; + + symtab_shndx->sh.sh_size += sizeof(Elf32_Word); + symtab_shndx->changed = true; + } + + break; + } + + /* empty blocks should not happen */ + if (!symtab_data->d_size) { + WARN("zero size data"); return -1; } - shndx_data = elf_newdata(s); + /* is this the right block? */ + max_idx = symtab_data->d_size / entsize; + if (idx < max_idx) + break; + + /* adjust index and try again */ + idx -= max_idx; + } + + /* something went side-ways */ + if (idx < 0) { + WARN("negative index"); + return -1; + } + + /* setup extended section index magic and write the symbol */ + if (shndx >= SHN_UNDEF && shndx < SHN_LORESERVE) { + sym->sym.st_shndx = shndx; + if (!shndx_data) + shndx = 0; + } else { + sym->sym.st_shndx = SHN_XINDEX; if (!shndx_data) { - WARN_ELF("elf_newshndx_data"); + WARN("no .symtab_shndx"); return -1; } + } - shndx_data->d_buf = &sym->sec->idx; - shndx_data->d_size = sizeof(Elf32_Word); - shndx_data->d_align = 4; - shndx_data->d_type = ELF_T_WORD; - - symtab_shndx->sh.sh_size += 4; - symtab_shndx->changed = true; + if (!gelf_update_symshndx(symtab_data, shndx_data, idx, &sym->sym, shndx)) { + WARN_ELF("gelf_update_symshndx"); + return -1; } - return first_non_local; + return 0; } static struct symbol * elf_create_section_symbol(struct elf *elf, struct section *sec) { struct section *symtab, *symtab_shndx; - Elf_Data *shndx_data = NULL; - struct symbol *sym; - Elf32_Word shndx; + Elf32_Word first_non_local, new_idx; + struct symbol *sym, *old; symtab = find_section_by_name(elf, ".symtab"); if (symtab) { symtab_shndx = find_section_by_name(elf, ".symtab_shndx"); - if (symtab_shndx) - shndx_data = symtab_shndx->data; } else { WARN("no .symtab"); return NULL; } - sym = malloc(sizeof(*sym)); + sym = calloc(1, sizeof(*sym)); if (!sym) { perror("malloc"); return NULL; } - memset(sym, 0, sizeof(*sym)); - - sym->idx = elf_move_global_symbol(elf, symtab, symtab_shndx); - if (sym->idx < 0) { - WARN("elf_move_global_symbol"); - return NULL; - } sym->name = sec->name; sym->sec = sec; @@ -710,24 +753,41 @@ elf_create_section_symbol(struct elf *elf, struct section *sec) // st_other 0 // st_value 0 // st_size 0 - shndx = sec->idx; - if (shndx >= SHN_UNDEF && shndx < SHN_LORESERVE) { - sym->sym.st_shndx = shndx; - if (!shndx_data) - shndx = 0; - } else { - sym->sym.st_shndx = SHN_XINDEX; - if (!shndx_data) { - WARN("no .symtab_shndx"); + + /* + * Move the first global symbol, as per sh_info, into a new, higher + * symbol index. This fees up a spot for a new local symbol. + */ + first_non_local = symtab->sh.sh_info; + new_idx = symtab->sh.sh_size / symtab->sh.sh_entsize; + old = find_symbol_by_index(elf, first_non_local); + if (old) { + old->idx = new_idx; + + hlist_del(&old->hash); + elf_hash_add(symbol, &old->hash, old->idx); + + elf_dirty_reloc_sym(elf, old); + + if (elf_update_symbol(elf, symtab, symtab_shndx, old)) { + WARN("elf_update_symbol move"); return NULL; } + + new_idx = first_non_local; } - if (!gelf_update_symshndx(symtab->data, shndx_data, sym->idx, &sym->sym, shndx)) { - WARN_ELF("gelf_update_symshndx"); + sym->idx = new_idx; + if (elf_update_symbol(elf, symtab, symtab_shndx, sym)) { + WARN("elf_update_symbol"); return NULL; } + /* + * Either way, we added a LOCAL symbol. + */ + symtab->sh.sh_info += 1; + elf_add_symbol(elf, sym); return sym; -- cgit From 22682a07acc308ef78681572e19502ce8893c4d4 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Mon, 16 May 2022 11:06:36 -0400 Subject: objtool: Fix objtool regression on x32 systems Commit c087c6e7b551 ("objtool: Fix type of reloc::addend") failed to appreciate cross building from ILP32 hosts, where 'int' == 'long' and the issue persists. As such, use s64/int64_t/Elf64_Sxword for this field and suffer the pain that is ISO C99 printf formats for it. Fixes: c087c6e7b551 ("objtool: Fix type of reloc::addend") Signed-off-by: Mikulas Patocka [peterz: reword changelog, s/long long/s64/] Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Borislav Petkov Cc: Link: https://lkml.kernel.org/r/alpine.LRH.2.02.2205161041260.11556@file01.intranet.prod.int.rdu2.redhat.com --- tools/objtool/check.c | 9 +++++---- tools/objtool/elf.c | 2 +- tools/objtool/include/objtool/elf.h | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 2063f9fea1a2..190b2f6e360a 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -561,12 +562,12 @@ static int add_dead_ends(struct objtool_file *file) else if (reloc->addend == reloc->sym->sec->sh.sh_size) { insn = find_last_insn(file, reloc->sym->sec); if (!insn) { - WARN("can't find unreachable insn at %s+0x%lx", + WARN("can't find unreachable insn at %s+0x%" PRIx64, reloc->sym->sec->name, reloc->addend); return -1; } } else { - WARN("can't find unreachable insn at %s+0x%lx", + WARN("can't find unreachable insn at %s+0x%" PRIx64, reloc->sym->sec->name, reloc->addend); return -1; } @@ -596,12 +597,12 @@ reachable: else if (reloc->addend == reloc->sym->sec->sh.sh_size) { insn = find_last_insn(file, reloc->sym->sec); if (!insn) { - WARN("can't find reachable insn at %s+0x%lx", + WARN("can't find reachable insn at %s+0x%" PRIx64, reloc->sym->sec->name, reloc->addend); return -1; } } else { - WARN("can't find reachable insn at %s+0x%lx", + WARN("can't find reachable insn at %s+0x%" PRIx64, reloc->sym->sec->name, reloc->addend); return -1; } diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index 7fb6720e510b..c25e957c1e52 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -550,7 +550,7 @@ static struct section *elf_create_reloc_section(struct elf *elf, int reltype); int elf_add_reloc(struct elf *elf, struct section *sec, unsigned long offset, - unsigned int type, struct symbol *sym, long addend) + unsigned int type, struct symbol *sym, s64 addend) { struct reloc *reloc; diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h index de0cb2f313ba..adebfbc2b518 100644 --- a/tools/objtool/include/objtool/elf.h +++ b/tools/objtool/include/objtool/elf.h @@ -73,7 +73,7 @@ struct reloc { struct symbol *sym; unsigned long offset; unsigned int type; - long addend; + s64 addend; int idx; bool jump_table_start; }; @@ -145,7 +145,7 @@ struct elf *elf_open_read(const char *name, int flags); struct section *elf_create_section(struct elf *elf, const char *name, unsigned int sh_flags, size_t entsize, int nr); int elf_add_reloc(struct elf *elf, struct section *sec, unsigned long offset, - unsigned int type, struct symbol *sym, long addend); + unsigned int type, struct symbol *sym, s64 addend); int elf_add_reloc_to_insn(struct elf *elf, struct section *sec, unsigned long offset, unsigned int type, struct section *insn_sec, unsigned long insn_off); -- cgit From 385bd430c011a8cb8278e61c32d602d11e06f414 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Mon, 2 May 2022 11:15:14 +0200 Subject: objtool: Mark __ubsan_handle_builtin_unreachable() as noreturn fs/ntfs3/ntfs3.prelink.o: warning: objtool: ni_read_frame() falls through to next function ni_readpage_cmpr.cold() That is in fact: 000000000000124a : 124a: 44 89 e0 mov %r12d,%eax 124d: 0f b6 55 98 movzbl -0x68(%rbp),%edx 1251: 48 c7 c7 00 00 00 00 mov $0x0,%rdi 1254: R_X86_64_32S .data+0x1380 1258: 48 89 c6 mov %rax,%rsi 125b: e8 00 00 00 00 call 1260 125c: R_X86_64_PLT32 __ubsan_handle_shift_out_of_bounds-0x4 1260: 48 8d 7d cc lea -0x34(%rbp),%rdi 1264: e8 00 00 00 00 call 1269 1265: R_X86_64_PLT32 __tsan_read4-0x4 1269: 8b 45 cc mov -0x34(%rbp),%eax 126c: e9 00 00 00 00 jmp 1271 126d: R_X86_64_PC32 .text+0x19109 1271: 48 8b 75 a0 mov -0x60(%rbp),%rsi 1275: 48 63 d0 movslq %eax,%rdx 1278: 48 c7 c7 00 00 00 00 mov $0x0,%rdi 127b: R_X86_64_32S .data+0x13a0 127f: 89 45 88 mov %eax,-0x78(%rbp) 1282: e8 00 00 00 00 call 1287 1283: R_X86_64_PLT32 __ubsan_handle_shift_out_of_bounds-0x4 1287: 8b 45 88 mov -0x78(%rbp),%eax 128a: e9 00 00 00 00 jmp 128f 128b: R_X86_64_PC32 .text+0x19098 128f: 48 c7 c7 00 00 00 00 mov $0x0,%rdi 1292: R_X86_64_32S .data+0x11f0 1296: e8 00 00 00 00 call 129b 1297: R_X86_64_PLT32 __ubsan_handle_builtin_unreachable-0x4 000000000000129b : Tell objtool that __ubsan_handle_builtin_unreachable() is a noreturn. Reported-by: kernel test robot Signed-off-by: Peter Zijlstra (Intel) Link: https://lkml.kernel.org/r/20220502091514.GB479834@worktop.programming.kicks-ass.net --- tools/objtool/check.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'tools/objtool') diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 190b2f6e360a..7a187dae2fcf 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -185,7 +185,8 @@ static bool __dead_end_function(struct objtool_file *file, struct symbol *func, "do_group_exit", "stop_this_cpu", "__invalid_creds", - "cpu_startup_entry", + "cpu_startup_entry", + "__ubsan_handle_builtin_unreachable", }; if (!func) -- cgit From 2028a255f4df3af9e759f01f958d3237f825f256 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Fri, 20 May 2022 21:27:29 +0200 Subject: x86/extable: Annotate ex_handler_msr_mce() as a dead end Fix vmlinux.o: warning: objtool: fixup_exception+0x2d6: unreachable instruction Signed-off-by: Borislav Petkov Signed-off-by: Peter Zijlstra (Intel) Link: https://lkml.kernel.org/r/20220520192729.23969-1-bp@alien8.de --- arch/x86/include/asm/extable.h | 8 ++++++-- tools/objtool/check.c | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'tools/objtool') diff --git a/arch/x86/include/asm/extable.h b/arch/x86/include/asm/extable.h index 155c991ba95e..eeed395c3177 100644 --- a/arch/x86/include/asm/extable.h +++ b/arch/x86/include/asm/extable.h @@ -42,9 +42,13 @@ extern int ex_get_fixup_type(unsigned long ip); extern void early_fixup_exception(struct pt_regs *regs, int trapnr); #ifdef CONFIG_X86_MCE -extern void ex_handler_msr_mce(struct pt_regs *regs, bool wrmsr); +extern void __noreturn ex_handler_msr_mce(struct pt_regs *regs, bool wrmsr); #else -static inline void ex_handler_msr_mce(struct pt_regs *regs, bool wrmsr) { } +static inline void __noreturn ex_handler_msr_mce(struct pt_regs *regs, bool wrmsr) +{ + for (;;) + cpu_relax(); +} #endif #if defined(CONFIG_BPF_JIT) && defined(CONFIG_X86_64) diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 7a187dae2fcf..864bb9dd3584 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -187,6 +187,7 @@ static bool __dead_end_function(struct objtool_file *file, struct symbol *func, "__invalid_creds", "cpu_startup_entry", "__ubsan_handle_builtin_unreachable", + "ex_handler_msr_mce", }; if (!func) -- cgit