[PATCH v2 04/10] objtool: Implement base jump_assert support

From: Peter Zijlstra
Date: Tue Jan 16 2018 - 09:35:27 EST


Implement a jump_label assertion that asserts that the code location
is indeed only reachable through a static_branch. Because if GCC is
absolutely retaded it could generate code like:

xor rax,rax
NOP/JMP 1f
mov $1, rax
1:
test rax,rax
jz 2f
<do-code>
2:

instead of the sensible:

NOP/JMP 1f
<do-code>
1:

This implements objtool infrastructure for ensuring the code ends up
sane, since we'll rely on that for correctness and security.

We tag the instructions after the static branch with static_jump_dest=true;
that is the instruction after the NOP and the instruction at the
JMP+disp site.

Then, when we read the .discard.jump_assert section, we assert that
each entry points to an instruction that has static_jump_dest set.

With this we can assert that the code emitted for the if statement
ends up at the static jump location and nothing untowards happened.

Cc: Borislav Petkov <bp@xxxxxxxxx>
Cc: Thomas Gleixner <tglx@xxxxxxxxxxxxx>
Cc: Josh Poimboeuf <jpoimboe@xxxxxxxxxx>
Signed-off-by: Peter Zijlstra (Intel) <peterz@xxxxxxxxxxxxx>
---
tools/objtool/check.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++++--
tools/objtool/check.h | 1
2 files changed, 69 insertions(+), 2 deletions(-)

--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -687,8 +687,17 @@ static int handle_jump_alt(struct objtoo
struct instruction *orig_insn,
struct instruction **new_insn)
{
- if (orig_insn->type == INSN_NOP)
+ struct instruction *next_insn = list_next_entry(orig_insn, list);
+
+ if (orig_insn->type == INSN_NOP) {
+ /*
+ * If orig_insn is a NOP, then new_insn is the branch target
+ * for when it would've been a JMP.
+ */
+ next_insn->static_jump_dest = true;
+ (*new_insn)->static_jump_dest = true;
return 0;
+ }

if (orig_insn->type != INSN_JUMP_UNCONDITIONAL) {
WARN_FUNC("unsupported instruction at jump label",
@@ -696,7 +705,16 @@ static int handle_jump_alt(struct objtoo
return -1;
}

- *new_insn = list_next_entry(orig_insn, list);
+ /*
+ * Otherwise, orig_insn is a JMP and it will have orig_insn->jump_dest.
+ * In this case we'll effectively NOP the alt by pointing new_insn at
+ * next_insn.
+ */
+ orig_insn->jump_dest->static_jump_dest = true;
+ next_insn->static_jump_dest = true;
+
+ *new_insn = next_insn;
+
return 0;
}

@@ -1067,6 +1085,50 @@ static int read_unwind_hints(struct objt
return 0;
}

+static int assert_static_jumps(struct objtool_file *file)
+{
+ struct section *sec, *relasec;
+ struct instruction *insn;
+ struct rela *rela;
+ int i;
+
+ sec = find_section_by_name(file->elf, ".discard.jump_assert");
+ if (!sec)
+ return 0;
+
+ relasec = sec->rela;
+ if (!relasec) {
+ WARN("missing .rela.discard.jump_assert section");
+ return -1;
+ }
+
+ if (sec->len % sizeof(unsigned long)) {
+ WARN("jump_assert size mismatch: %d %ld", sec->len, sizeof(unsigned long));
+ return -1;
+ }
+
+ for (i = 0; i < sec->len / sizeof(unsigned long); i++) {
+ rela = find_rela_by_dest(sec, i * sizeof(unsigned long));
+ if (!rela) {
+ WARN("can't find rela for jump_assert[%d]", i);
+ return -1;
+ }
+
+ insn = find_insn(file, rela->sym->sec, rela->addend);
+ if (!insn) {
+ WARN("can't find insn for jump_assert[%d]", i);
+ return -1;
+ }
+
+ if (!insn->static_jump_dest) {
+ WARN_FUNC("static assert FAIL", insn->sec, insn->offset);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
static int decode_sections(struct objtool_file *file)
{
int ret;
@@ -2000,6 +2062,10 @@ int check(const char *_objname, bool _no
goto out;
warnings += ret;

+ ret = assert_static_jumps(&file);
+ if (ret < 0)
+ return ret;
+
if (list_empty(&file.insn_list))
goto out;

--- a/tools/objtool/check.h
+++ b/tools/objtool/check.h
@@ -45,6 +45,7 @@ struct instruction {
unsigned char type;
unsigned long immediate;
bool alt_group, visited, dead_end, ignore, hint, save, restore, ignore_alts;
+ bool static_jump_dest;
struct symbol *call_dest;
struct instruction *jump_dest;
struct list_head alts;