[PATCH 11/35] objtool: Add retpoline validation

From: Peter Zijlstra
Date: Thu Jan 18 2018 - 10:06:16 EST


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

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

Requested-by: David Woodhouse <dwmw2@xxxxxxxxxxxxx>
Signed-off-by: Peter Zijlstra (Intel) <peterz@xxxxxxxxxxxxx>
---
scripts/Makefile.build | 6 ++
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, 100 insertions(+), 11 deletions(-)

--- a/scripts/Makefile.build
+++ b/scripts/Makefile.build
@@ -269,6 +269,10 @@ objtool_args += --no-unreachable
else
objtool_args += $(call cc-ifversion, -lt, 0405, --no-unreachable)
endif
+ifdef CONFIG_RETPOLINE
+ objtool_args += --retpoline
+endif
+

ifdef CONFIG_MODVERSIONS
objtool_o = $(@D)/.tmp_$(@F)
--- 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)
{
@@ -51,7 +51,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);

}

--- 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);
@@ -1158,6 +1157,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 indirect jump/call",
+ insn->sec, insn->offset);
+ return -1;
+ }
+
+ insn->retpoline_safe = true;
+ }
+
+ return 0;
+}
+
static int decode_sections(struct objtool_file *file)
{
int ret;
@@ -1196,6 +1243,10 @@ static int decode_sections(struct objtoo
if (ret)
return ret;

+ ret = read_retpoline_hints(file);
+ if (ret)
+ return ret;
+
return 0;
}

@@ -1939,6 +1990,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("indirect %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 &&
@@ -2064,13 +2138,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)
@@ -2098,6 +2173,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);