summaryrefslogtreecommitdiff
path: root/tools/objtool/check.c
diff options
context:
space:
mode:
authorPeter Zijlstra <peterz@infradead.org>2020-03-10 18:57:41 +0100
committerIngo Molnar <mingo@kernel.org>2020-04-22 10:53:50 +0200
commitc4a33939a7eb396acbb05569e57eebe4374cc57c (patch)
treef170960b466f002fbe394ddb5665cb6f538ebb74 /tools/objtool/check.c
parente7c0219b328c96746767f21b9532eed6a48f61c5 (diff)
objtool: Implement noinstr validation
Validate that any call out of .noinstr.text is in between instr_begin() and instr_end() annotations. This annotation is useful to ensure correct behaviour wrt tracing sensitive code like entry/exit and idle code. When we run code in a sensitive context we want a guarantee no unknown code is ran. Since this validation relies on knowing the section of call destination symbols, we must run it on vmlinux.o instead of on individual object files. Add two options: -d/--duplicate "duplicate validation for vmlinux" -l/--vmlinux "vmlinux.o validation" Where the latter auto-detects when objname ends with "vmlinux.o" and the former will force all validations, also those already done on !vmlinux object files. Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Reviewed-by: Miroslav Benes <mbenes@suse.cz> Reviewed-by: Alexandre Chartre <alexandre.chartre@oracle.com> Acked-by: Josh Poimboeuf <jpoimboe@redhat.com> Link: https://lkml.kernel.org/r/20200416115119.106268040@infradead.org Signed-off-by: Ingo Molnar <mingo@kernel.org>
Diffstat (limited to 'tools/objtool/check.c')
-rw-r--r--tools/objtool/check.c98
1 files changed, 98 insertions, 0 deletions
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index abf97159bf1eb..87e528c2840c5 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -257,6 +257,9 @@ static int decode_instructions(struct objtool_file *file)
strncmp(sec->name, ".discard.", 9))
sec->text = true;
+ if (!strcmp(sec->name, ".noinstr.text"))
+ sec->noinstr = true;
+
for (offset = 0; offset < sec->len; offset += insn->len) {
insn = malloc(sizeof(*insn));
if (!insn) {
@@ -1340,6 +1343,53 @@ static int read_retpoline_hints(struct objtool_file *file)
return 0;
}
+static int read_instr_hints(struct objtool_file *file)
+{
+ struct section *sec;
+ struct instruction *insn;
+ struct rela *rela;
+
+ sec = find_section_by_name(file->elf, ".rela.discard.instr_end");
+ if (!sec)
+ return 0;
+
+ list_for_each_entry(rela, &sec->rela_list, list) {
+ if (rela->sym->type != STT_SECTION) {
+ WARN("unexpected relocation symbol type in %s", sec->name);
+ return -1;
+ }
+
+ insn = find_insn(file, rela->sym->sec, rela->addend);
+ if (!insn) {
+ WARN("bad .discard.instr_end entry");
+ return -1;
+ }
+
+ insn->instr--;
+ }
+
+ sec = find_section_by_name(file->elf, ".rela.discard.instr_begin");
+ if (!sec)
+ return 0;
+
+ list_for_each_entry(rela, &sec->rela_list, list) {
+ if (rela->sym->type != STT_SECTION) {
+ WARN("unexpected relocation symbol type in %s", sec->name);
+ return -1;
+ }
+
+ insn = find_insn(file, rela->sym->sec, rela->addend);
+ if (!insn) {
+ WARN("bad .discard.instr_begin entry");
+ return -1;
+ }
+
+ insn->instr++;
+ }
+
+ return 0;
+}
+
static void mark_rodata(struct objtool_file *file)
{
struct section *sec;
@@ -1411,6 +1461,10 @@ static int decode_sections(struct objtool_file *file)
if (ret)
return ret;
+ ret = read_instr_hints(file);
+ if (ret)
+ return ret;
+
return 0;
}
@@ -2007,6 +2061,13 @@ static inline const char *call_dest_name(struct instruction *insn)
static int validate_call(struct instruction *insn, struct insn_state *state)
{
+ if (state->noinstr && state->instr <= 0 &&
+ (!insn->call_dest || insn->call_dest->sec != insn->sec)) {
+ WARN_FUNC("call to %s() leaves .noinstr.text section",
+ insn->sec, insn->offset, call_dest_name(insn));
+ return 1;
+ }
+
if (state->uaccess && !func_uaccess_safe(insn->call_dest)) {
WARN_FUNC("call to %s() with UACCESS enabled",
insn->sec, insn->offset, call_dest_name(insn));
@@ -2035,6 +2096,12 @@ static int validate_sibling_call(struct instruction *insn, struct insn_state *st
static int validate_return(struct symbol *func, struct instruction *insn, struct insn_state *state)
{
+ if (state->noinstr && state->instr > 0) {
+ WARN_FUNC("return with instrumentation enabled",
+ insn->sec, insn->offset);
+ return 1;
+ }
+
if (state->uaccess && !func_uaccess_safe(func)) {
WARN_FUNC("return with UACCESS enabled",
insn->sec, insn->offset);
@@ -2115,6 +2182,9 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
return 0;
}
+ if (state.noinstr)
+ state.instr += insn->instr;
+
if (insn->hint)
state.cfi = insn->cfi;
else
@@ -2422,6 +2492,14 @@ static int validate_section(struct objtool_file *file, struct section *sec)
struct insn_state state;
int ret, warnings = 0;
+ /*
+ * We need the full vmlinux for noinstr validation, otherwise we can
+ * not correctly determine insn->call_dest->sec (external symbols do
+ * not have a section).
+ */
+ if (vmlinux)
+ state.noinstr = sec->noinstr;
+
list_for_each_entry(func, &sec->symbol_list, list) {
if (func->type != STT_FUNC)
continue;
@@ -2456,6 +2534,17 @@ static int validate_section(struct objtool_file *file, struct section *sec)
return warnings;
}
+static int validate_vmlinux_functions(struct objtool_file *file)
+{
+ struct section *sec;
+
+ sec = find_section_by_name(file->elf, ".noinstr.text");
+ if (!sec)
+ return 0;
+
+ return validate_section(file, sec);
+}
+
static int validate_functions(struct objtool_file *file)
{
struct section *sec;
@@ -2513,6 +2602,15 @@ int check(const char *_objname, bool orc)
if (list_empty(&file.insn_list))
goto out;
+ if (vmlinux && !validate_dup) {
+ ret = validate_vmlinux_functions(&file);
+ if (ret < 0)
+ goto out;
+
+ warnings += ret;
+ goto out;
+ }
+
if (retpoline) {
ret = validate_retpoline(&file);
if (ret < 0)