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 2: instead of the sensible: NOP/JMP 1f 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: Thomas Gleixner Cc: Borislav Petkov Cc: Josh Poimboeuf Signed-off-by: Peter Zijlstra (Intel) --- tools/objtool/check.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++++-- tools/objtool/check.h | 2 - 2 files changed, 69 insertions(+), 3 deletions(-) --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -686,8 +686,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", @@ -695,7 +704,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; } @@ -1114,6 +1132,50 @@ static int read_retpoline_hints(struct o 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; @@ -2073,6 +2135,10 @@ int check(const char *_objname, bool orc 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,7 +45,7 @@ struct instruction { unsigned char type; unsigned long immediate; bool alt_group, visited, dead_end, ignore, hint, save, restore, ignore_alts; - bool retpoline_safe; + bool retpoline_safe, static_jump_dest; struct symbol *call_dest; struct instruction *jump_dest; struct list_head alts;