David requested a objtool validation pass for RETPOLINE enabled
builds, where it validates no unannotated dynamic jumps or calls are
left.

Add an additional .discard.retpoline_safe section to allow annotating
the few dynamic sites that are required and safe.

Requested-by: David Woodhouse <[email protected]>
Signed-off-by: Peter Zijlstra (Intel) <[email protected]>
---
 scripts/Makefile.build        |    4 +
 tools/objtool/builtin-check.c |    5 +-
 tools/objtool/builtin-orc.c   |    4 -
 tools/objtool/check.c         |   92 +++++++++++++++++++++++++++++++++++++++---
 tools/objtool/check.h         |    4 -
 5 files changed, 98 insertions(+), 11 deletions(-)

--- a/scripts/Makefile.build
+++ b/scripts/Makefile.build
@@ -264,6 +264,10 @@ objtool_args += --no-unreachable
 else
 objtool_args += $(call cc-ifversion, -lt, 0405, --no-unreachable)
 endif
+ifdef CONFIG_RETPOLINE
+objtool_args += --retpoline
+endif
+
 
 # 'OBJECT_FILES_NON_STANDARD := y': skip objtool checking for a directory
 # 'OBJECT_FILES_NON_STANDARD_foo.o := 'y': skip objtool checking for a file
--- a/tools/objtool/builtin-check.c
+++ b/tools/objtool/builtin-check.c
@@ -29,7 +29,7 @@
 #include "builtin.h"
 #include "check.h"
 
-bool no_fp, no_unreachable;
+bool no_fp, no_unreachable, retpoline;
 
 static const char * const check_usage[] = {
        "objtool check [<options>] file.o",
@@ -39,6 +39,7 @@ static const char * const check_usage[]
 const struct option check_options[] = {
        OPT_BOOLEAN('f', "no-fp", &no_fp, "Skip frame pointer validation"),
        OPT_BOOLEAN('u', "no-unreachable", &no_unreachable, "Skip 'unreachable 
instruction' warnings"),
+       OPT_BOOLEAN('r', "retpoline", &retpoline, "Validate retpoline 
assumptions"),
        OPT_END(),
 };
 
@@ -53,5 +54,5 @@ int cmd_check(int argc, const char **arg
 
        objname = argv[0];
 
-       return check(objname, no_fp, no_unreachable, false);
+       return check(objname, no_fp, no_unreachable, retpoline, false);
 }
--- a/tools/objtool/builtin-orc.c
+++ b/tools/objtool/builtin-orc.c
@@ -37,7 +37,7 @@ static const char *orc_usage[] = {
 };
 
 extern const struct option check_options[];
-extern bool no_fp, no_unreachable;
+extern bool no_fp, no_unreachable, retpoline;
 
 int cmd_orc(int argc, const char **argv)
 {
@@ -54,7 +54,7 @@ int cmd_orc(int argc, const char **argv)
 
                objname = argv[0];
 
-               return check(objname, no_fp, no_unreachable, true);
+               return check(objname, no_fp, no_unreachable, retpoline, true);
        }
 
        if (!strcmp(argv[0], "dump")) {
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -33,7 +33,7 @@ struct alternative {
 };
 
 const char *objname;
-static bool no_fp;
+static bool no_fp, retpoline;
 struct cfi_state initial_func_cfi;
 
 struct instruction *find_insn(struct objtool_file *file,
@@ -496,6 +496,7 @@ static int add_jump_destinations(struct
                         * disguise, so convert them accordingly.
                         */
                        insn->type = INSN_JUMP_DYNAMIC;
+                       insn->retpoline_safe = true;
                        continue;
                } else {
                        /* sibling call */
@@ -548,13 +549,11 @@ static int add_call_destinations(struct
                         * normal for a function to call within itself.  So
                         * disable this warning for now.
                         */
-#if 0
-                       if (!insn->call_dest) {
+                       if (retpoline && !insn->call_dest) {
                                WARN_FUNC("can't find call dest symbol at 
offset 0x%lx",
                                          insn->sec, insn->offset, dest_off);
                                return -1;
                        }
-#endif
                } else if (rela->sym->type == STT_SECTION) {
                        insn->call_dest = find_symbol_by_offset(rela->sym->sec,
                                                                rela->addend+4);
@@ -1150,6 +1149,54 @@ static int assert_static_jumps(struct ob
        return 0;
 }
 
+static int read_retpoline_hints(struct objtool_file *file)
+{
+       struct section *sec, *relasec;
+       struct instruction *insn;
+       struct rela *rela;
+       int i;
+
+       sec = find_section_by_name(file->elf, ".discard.retpoline_safe");
+       if (!sec)
+               return 0;
+
+       relasec = sec->rela;
+       if (!relasec) {
+               WARN("missing .rela.discard.retpoline_safe section");
+               return -1;
+       }
+
+       if (sec->len % sizeof(unsigned long)) {
+               WARN("retpoline_safe 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 retpoline_safe[%d]", i);
+                       return -1;
+               }
+
+               insn = find_insn(file, rela->sym->sec, rela->addend);
+               if (!insn) {
+                       WARN("can't find insn for retpoline_safe[%d]", i);
+                       return -1;
+               }
+
+               if (insn->type != INSN_JUMP_DYNAMIC &&
+                   insn->type != INSN_CALL_DYNAMIC) {
+                       WARN_FUNC("retpoline_safe hint not a dynamic jump/call",
+                                 insn->sec, insn->offset);
+                       return -1;
+               }
+
+               insn->retpoline_safe = true;
+       }
+
+       return 0;
+}
+
 static int decode_sections(struct objtool_file *file)
 {
        int ret;
@@ -1188,6 +1235,10 @@ static int decode_sections(struct objtoo
        if (ret)
                return ret;
 
+       ret = read_retpoline_hints(file);
+       if (ret)
+               return ret;
+
        return 0;
 }
 
@@ -1931,6 +1982,29 @@ static int validate_unwind_hints(struct
        return warnings;
 }
 
+static int validate_retpoline(struct objtool_file *file)
+{
+       struct instruction *insn;
+       int warnings = 0;
+
+       for_each_insn(file, insn) {
+               if (insn->type != INSN_JUMP_DYNAMIC &&
+                   insn->type != INSN_CALL_DYNAMIC)
+                       continue;
+
+               if (insn->retpoline_safe)
+                       continue;
+
+               WARN_FUNC("dynamic %s found in RETPOLINE build",
+                         insn->sec, insn->offset,
+                         insn->type == INSN_JUMP_DYNAMIC ? "jump" : "call");
+
+               warnings++;
+       }
+
+       return warnings;
+}
+
 static bool is_kasan_insn(struct instruction *insn)
 {
        return (insn->type == INSN_CALL &&
@@ -2056,13 +2130,14 @@ static void cleanup(struct objtool_file
        elf_close(file->elf);
 }
 
-int check(const char *_objname, bool _no_fp, bool no_unreachable, bool orc)
+int check(const char *_objname, bool _no_fp, bool no_unreachable, bool 
_retpoline, bool orc)
 {
        struct objtool_file file;
        int ret, warnings = 0;
 
        objname = _objname;
        no_fp = _no_fp;
+       retpoline = _retpoline;
 
        file.elf = elf_open(objname, orc ? O_RDWR : O_RDONLY);
        if (!file.elf)
@@ -2090,6 +2165,13 @@ int check(const char *_objname, bool _no
        if (list_empty(&file.insn_list))
                goto out;
 
+       if (retpoline) {
+               ret = validate_retpoline(&file);
+               if (ret < 0)
+                       return ret;
+               warnings += ret;
+       }
+
        ret = validate_functions(&file);
        if (ret < 0)
                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 static_jump_dest;
+       bool static_jump_dest, retpoline_safe;
        struct symbol *call_dest;
        struct instruction *jump_dest;
        struct list_head alts;
@@ -63,7 +63,7 @@ struct objtool_file {
        bool ignore_unreachables, c_file, hints;
 };
 
-int check(const char *objname, bool no_fp, bool no_unreachable, bool orc);
+int check(const char *objname, bool no_fp, bool no_unreachable, bool 
retpoline, bool orc);
 
 struct instruction *find_insn(struct objtool_file *file,
                              struct section *sec, unsigned long offset);


Reply via email to