Commit 99ce7962 authored by Peter Zijlstra's avatar Peter Zijlstra Committed by Ingo Molnar

objtool: Fix switch-table detection

Linus reported that GCC-7.3 generated a switch-table construct that
confused objtool. It turns out that, in particular due to KASAN, it is
possible to have unrelated .rodata usage in between the .rodata setup
for the switch-table and the following indirect jump.

The simple linear reverse search from the indirect jump would hit upon
the KASAN .rodata usage first and fail to find a switch_table,
resulting in a spurious 'sibling call with modified stack frame'
warning.

Fix this by creating a 'jump-stack' which we can 'unwind' during
reversal, thereby skipping over much of the in-between code.

This is not fool proof by any means, but is sufficient to make the
known cases work. Future work would be to construct more comprehensive
flow analysis code.
Reported-and-tested-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: default avatarPeter Zijlstra (Intel) <peterz@infradead.org>
Acked-by: default avatarJosh Poimboeuf <jpoimboe@redhat.com>
Cc: Borislav Petkov <bp@alien8.de>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Link: http://lkml.kernel.org/r/20180208130232.GF25235@hirez.programming.kicks-ass.netSigned-off-by: default avatarIngo Molnar <mingo@kernel.org>
parent 6b8cf5cc
...@@ -851,8 +851,14 @@ static int add_switch_table(struct objtool_file *file, struct symbol *func, ...@@ -851,8 +851,14 @@ static int add_switch_table(struct objtool_file *file, struct symbol *func,
* This is a fairly uncommon pattern which is new for GCC 6. As of this * This is a fairly uncommon pattern which is new for GCC 6. As of this
* writing, there are 11 occurrences of it in the allmodconfig kernel. * writing, there are 11 occurrences of it in the allmodconfig kernel.
* *
* As of GCC 7 there are quite a few more of these and the 'in between' code
* is significant. Esp. with KASAN enabled some of the code between the mov
* and jmpq uses .rodata itself, which can confuse things.
*
* TODO: Once we have DWARF CFI and smarter instruction decoding logic, * TODO: Once we have DWARF CFI and smarter instruction decoding logic,
* ensure the same register is used in the mov and jump instructions. * ensure the same register is used in the mov and jump instructions.
*
* NOTE: RETPOLINE made it harder still to decode dynamic jumps.
*/ */
static struct rela *find_switch_table(struct objtool_file *file, static struct rela *find_switch_table(struct objtool_file *file,
struct symbol *func, struct symbol *func,
...@@ -874,12 +880,25 @@ static struct rela *find_switch_table(struct objtool_file *file, ...@@ -874,12 +880,25 @@ static struct rela *find_switch_table(struct objtool_file *file,
text_rela->addend + 4); text_rela->addend + 4);
if (!rodata_rela) if (!rodata_rela)
return NULL; return NULL;
file->ignore_unreachables = true; file->ignore_unreachables = true;
return rodata_rela; return rodata_rela;
} }
/* case 3 */ /* case 3 */
func_for_each_insn_continue_reverse(file, func, insn) { /*
* Backward search using the @first_jump_src links, these help avoid
* much of the 'in between' code. Which avoids us getting confused by
* it.
*/
for (insn = list_prev_entry(insn, list);
&insn->list != &file->insn_list &&
insn->sec == func->sec &&
insn->offset >= func->offset;
insn = insn->first_jump_src ?: list_prev_entry(insn, list)) {
if (insn->type == INSN_JUMP_DYNAMIC) if (insn->type == INSN_JUMP_DYNAMIC)
break; break;
...@@ -909,14 +928,32 @@ static struct rela *find_switch_table(struct objtool_file *file, ...@@ -909,14 +928,32 @@ static struct rela *find_switch_table(struct objtool_file *file,
return NULL; return NULL;
} }
static int add_func_switch_tables(struct objtool_file *file, static int add_func_switch_tables(struct objtool_file *file,
struct symbol *func) struct symbol *func)
{ {
struct instruction *insn, *prev_jump = NULL; struct instruction *insn, *last = NULL, *prev_jump = NULL;
struct rela *rela, *prev_rela = NULL; struct rela *rela, *prev_rela = NULL;
int ret; int ret;
func_for_each_insn(file, func, insn) { func_for_each_insn(file, func, insn) {
if (!last)
last = insn;
/*
* Store back-pointers for unconditional forward jumps such
* that find_switch_table() can back-track using those and
* avoid some potentially confusing code.
*/
if (insn->type == INSN_JUMP_UNCONDITIONAL && insn->jump_dest &&
insn->offset > last->offset &&
insn->jump_dest->offset > insn->offset &&
!insn->jump_dest->first_jump_src) {
insn->jump_dest->first_jump_src = insn;
last = insn->jump_dest;
}
if (insn->type != INSN_JUMP_DYNAMIC) if (insn->type != INSN_JUMP_DYNAMIC)
continue; continue;
......
...@@ -47,6 +47,7 @@ struct instruction { ...@@ -47,6 +47,7 @@ struct instruction {
bool alt_group, visited, dead_end, ignore, hint, save, restore, ignore_alts; bool alt_group, visited, dead_end, ignore, hint, save, restore, ignore_alts;
struct symbol *call_dest; struct symbol *call_dest;
struct instruction *jump_dest; struct instruction *jump_dest;
struct instruction *first_jump_src;
struct list_head alts; struct list_head alts;
struct symbol *func; struct symbol *func;
struct stack_op stack_op; struct stack_op stack_op;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment