[PATCH v4 08/13] objtool: Detect loading function pointers across noinstr

From: Peter Zijlstra
Date: Wed Mar 25 2020 - 13:48:26 EST


Detect if noinstr text loads functions pointers from regular text,
doing so is a definite sign that indirect function calls are unsafe.

Signed-off-by: Peter Zijlstra (Intel) <peterz@xxxxxxxxxxxxx>
Acked-by: Josh Poimboeuf <jpoimboe@xxxxxxxxxx>
---
tools/objtool/arch.h | 2 +
tools/objtool/check.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++
tools/objtool/check.h | 2 -
3 files changed, 74 insertions(+), 1 deletion(-)

--- a/tools/objtool/arch.h
+++ b/tools/objtool/arch.h
@@ -75,4 +75,6 @@ int arch_decode_instruction(struct elf *

bool arch_callee_saved_reg(unsigned char reg);

+#define MAX_INSN_SIZE 15
+
#endif /* _ARCH_H */
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -42,6 +42,25 @@ struct instruction *find_insn(struct obj
return NULL;
}

+static struct instruction *find_insn_containing(struct objtool_file *file, struct section *sec,
+ unsigned long offset)
+{
+ struct instruction *insn;
+ unsigned long o;
+
+ for_offset_range(o, offset - MAX_INSN_SIZE - 1, offset) {
+ hash_for_each_possible(file->insn_hash, insn, hash, sec_offset_hash(sec, o)) {
+ if (insn->sec != sec)
+ continue;
+
+ if (insn->offset <= offset && insn->offset + insn->len > offset)
+ return insn;
+ }
+ }
+
+ return NULL;
+}
+
static struct instruction *next_insn_same_sec(struct objtool_file *file,
struct instruction *insn)
{
@@ -2146,6 +2165,32 @@ static int apply_insn_hint(struct objtoo
return 0;
}

+static int validate_rela(struct instruction *insn, struct insn_state *state)
+{
+ /*
+ * Assume that any text rela that's not a CALL or JMP is a load of a
+ * function pointer.
+ */
+
+ switch (insn->type) {
+ case INSN_CALL:
+ case INSN_CALL_DYNAMIC:
+ case INSN_JUMP_CONDITIONAL:
+ case INSN_JUMP_UNCONDITIONAL:
+ return 0;
+
+ default:
+ break;
+ }
+
+ if (state->noinstr && state->instr <= 0 && insn->has_text_rela) {
+ WARN_FUNC("loading non-noinstr function pointer", insn->sec, insn->offset);
+ return 1;
+ }
+
+ return 0;
+}
+
/*
* Follow the branch starting at the given instruction, and recursively follow
* any other branches (jumps). Meanwhile, track the frame pointer state at
@@ -2224,6 +2269,10 @@ static int validate_branch(struct objtoo
return 0;
}

+ ret = validate_rela(insn, &state);
+ if (ret)
+ return ret;
+
switch (insn->type) {

case INSN_RETURN:
@@ -2492,6 +2541,25 @@ static bool ignore_unreachable_insn(stru
return false;
}

+static void prepare_insn_rela(struct objtool_file *file, struct section *sec)
+{
+ struct instruction *insn;
+ struct rela *rela;
+
+ if (!sec->rela)
+ return;
+
+ list_for_each_entry(rela, &sec->rela->rela_list, list) {
+ insn = find_insn_containing(file, sec, rela->offset);
+ if (!insn)
+ continue;
+
+ insn->has_text_rela = rela->sym && rela->sym->sec &&
+ rela->sym->sec->text &&
+ !rela->sym->sec->noinstr;
+ }
+}
+
static int validate_section(struct objtool_file *file, struct section *sec)
{
struct symbol *func;
@@ -2514,6 +2582,9 @@ static int validate_section(struct objto
if (vmlinux)
state.noinstr = sec->noinstr;

+ if (state.noinstr)
+ prepare_insn_rela(file, sec);
+
list_for_each_entry(func, &sec->symbol_list, list) {
if (func->type != STT_FUNC)
continue;
--- a/tools/objtool/check.h
+++ b/tools/objtool/check.h
@@ -31,7 +31,7 @@ struct instruction {
enum insn_type type;
unsigned long immediate;
bool alt_group, dead_end, ignore, hint, save, restore, ignore_alts;
- bool retpoline_safe;
+ bool retpoline_safe, has_text_rela;
s8 instr;
u8 visited;
struct symbol *call_dest;