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: Thomas Gleixner <t...@linutronix.de>
Cc: Borislav Petkov <b...@alien8.de>
Cc: Josh Poimboeuf <jpoim...@redhat.com>

Signed-off-by: Peter Zijlstra (Intel) <pet...@infradead.org>
---
 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;


Reply via email to