Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

objtool: Support repeated uses of the same C jump table

This fixes objtool for both a GCC issue and a Clang issue:

1) GCC issue:

kernel/bpf/core.o: warning: objtool: ___bpf_prog_run()+0x8d5: sibling call from callable instruction with modified stack frame

With CONFIG_RETPOLINE=n, GCC is doing the following optimization in
___bpf_prog_run().

Before:

select_insn:
jmp *jumptable(,%rax,8)
...
ALU64_ADD_X:
...
jmp select_insn
ALU_ADD_X:
...
jmp select_insn

After:

select_insn:
jmp *jumptable(, %rax, 8)
...
ALU64_ADD_X:
...
jmp *jumptable(, %rax, 8)
ALU_ADD_X:
...
jmp *jumptable(, %rax, 8)

This confuses objtool. It has never seen multiple indirect jump
sites which use the same jump table.

For GCC switch tables, the only way of detecting the size of a table
is by continuing to scan for more tables. The size of the previous
table can only be determined after another switch table is found, or
when the scan reaches the end of the function.

That logic was reused for C jump tables, and was based on the
assumption that each jump table only has a single jump site. The
above optimization breaks that assumption.

2) Clang issue:

drivers/usb/misc/sisusbvga/sisusb.o: warning: objtool: sisusb_write_mem_bulk()+0x588: can't find switch jump table

With clang 9, code can be generated where a function contains two
indirect jump instructions which use the same switch table.

The fix is the same for both issues: split the jump table parsing into
two passes.

In the first pass, locate the heads of all switch tables for the
function and mark their locations.

In the second pass, parse the switch tables and add them.

Fixes: e55a73251da3 ("bpf: Fix ORC unwinding in non-JIT BPF code")
Reported-by: Randy Dunlap <rdunlap@infradead.org>
Reported-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Jann Horn <jannh@google.com>
Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Tested-by: Nick Desaulniers <ndesaulniers@google.com>
Acked-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://lkml.kernel.org/r/e995befaada9d4d8b2cf788ff3f566ba900d2b4d.1563413318.git.jpoimboe@redhat.com

Co-developed-by: Josh Poimboeuf <jpoimboe@redhat.com>

authored by

Jann Horn and committed by
Thomas Gleixner
bd98c813 e7c2bc37

+31 -26
+29 -26
tools/objtool/check.c
··· 900 900 } 901 901 902 902 static int add_jump_table(struct objtool_file *file, struct instruction *insn, 903 - struct rela *table, struct rela *next_table) 903 + struct rela *table) 904 904 { 905 905 struct rela *rela = table; 906 906 struct instruction *dest_insn; ··· 913 913 * instruction. 914 914 */ 915 915 list_for_each_entry_from(rela, &table->sec->rela_list, list) { 916 - if (rela == next_table) 916 + 917 + /* Check for the end of the table: */ 918 + if (rela != table && rela->jump_table_start) 917 919 break; 918 920 919 921 /* Make sure the table entries are consecutive: */ ··· 1074 1072 return NULL; 1075 1073 } 1076 1074 1077 - 1078 - static int add_func_jump_tables(struct objtool_file *file, 1079 - struct symbol *func) 1075 + /* 1076 + * First pass: Mark the head of each jump table so that in the next pass, 1077 + * we know when a given jump table ends and the next one starts. 1078 + */ 1079 + static void mark_func_jump_tables(struct objtool_file *file, 1080 + struct symbol *func) 1080 1081 { 1081 - struct instruction *insn, *last = NULL, *prev_jump = NULL; 1082 - struct rela *rela, *prev_rela = NULL; 1083 - int ret; 1082 + struct instruction *insn, *last = NULL; 1083 + struct rela *rela; 1084 1084 1085 1085 func_for_each_insn_all(file, func, insn) { 1086 1086 if (!last) ··· 1106 1102 continue; 1107 1103 1108 1104 rela = find_jump_table(file, func, insn); 1109 - if (!rela) 1105 + if (rela) { 1106 + rela->jump_table_start = true; 1107 + insn->jump_table = rela; 1108 + } 1109 + } 1110 + } 1111 + 1112 + static int add_func_jump_tables(struct objtool_file *file, 1113 + struct symbol *func) 1114 + { 1115 + struct instruction *insn; 1116 + int ret; 1117 + 1118 + func_for_each_insn_all(file, func, insn) { 1119 + if (!insn->jump_table) 1110 1120 continue; 1111 1121 1112 - /* 1113 - * We found a jump table, but we don't know yet how big it 1114 - * is. Don't add it until we reach the end of the function or 1115 - * the beginning of another jump table in the same function. 1116 - */ 1117 - if (prev_jump) { 1118 - ret = add_jump_table(file, prev_jump, prev_rela, rela); 1119 - if (ret) 1120 - return ret; 1121 - } 1122 - 1123 - prev_jump = insn; 1124 - prev_rela = rela; 1125 - } 1126 - 1127 - if (prev_jump) { 1128 - ret = add_jump_table(file, prev_jump, prev_rela, NULL); 1122 + ret = add_jump_table(file, insn, insn->jump_table); 1129 1123 if (ret) 1130 1124 return ret; 1131 1125 } ··· 1150 1148 if (func->type != STT_FUNC) 1151 1149 continue; 1152 1150 1151 + mark_func_jump_tables(file, func); 1153 1152 ret = add_func_jump_tables(file, func); 1154 1153 if (ret) 1155 1154 return ret;
+1
tools/objtool/check.h
··· 38 38 struct symbol *call_dest; 39 39 struct instruction *jump_dest; 40 40 struct instruction *first_jump_src; 41 + struct rela *jump_table; 41 42 struct list_head alts; 42 43 struct symbol *func; 43 44 struct stack_op stack_op;
+1
tools/objtool/elf.h
··· 62 62 unsigned int type; 63 63 unsigned long offset; 64 64 int addend; 65 + bool jump_table_start; 65 66 }; 66 67 67 68 struct elf {