2019-05-19 21:51:43 +08:00
|
|
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
2017-06-28 23:11:05 +08:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef _CHECK_H
|
|
|
|
#define _CHECK_H
|
|
|
|
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include "elf.h"
|
2017-06-28 23:11:07 +08:00
|
|
|
#include "cfi.h"
|
2017-06-28 23:11:05 +08:00
|
|
|
#include "arch.h"
|
2017-07-11 23:33:42 +08:00
|
|
|
#include "orc.h"
|
2017-06-28 23:11:05 +08:00
|
|
|
#include <linux/hashtable.h>
|
|
|
|
|
2017-06-28 23:11:07 +08:00
|
|
|
struct insn_state {
|
|
|
|
struct cfi_reg cfa;
|
|
|
|
struct cfi_reg regs[CFI_NUM_REGS];
|
|
|
|
int stack_size;
|
2017-07-11 23:33:42 +08:00
|
|
|
unsigned char type;
|
2017-06-28 23:11:07 +08:00
|
|
|
bool bp_scratch;
|
2019-02-25 18:10:55 +08:00
|
|
|
bool drap, end, uaccess, df;
|
2019-02-25 19:50:09 +08:00
|
|
|
unsigned int uaccess_stack;
|
objtool: Track DRAP separately from callee-saved registers
When GCC realigns a function's stack, it sometimes uses %r13 as the DRAP
register, like:
push %r13
lea 0x10(%rsp), %r13
and $0xfffffffffffffff0, %rsp
pushq -0x8(%r13)
push %rbp
mov %rsp, %rbp
push %r13
...
mov -0x8(%rbp),%r13
leaveq
lea -0x10(%r13), %rsp
pop %r13
retq
Since %r13 was pushed onto the stack twice, its two stack locations need
to be stored separately. The first push of %r13 is its original value,
and the second push of %r13 is the caller's stack frame address.
Since %r13 is a callee-saved register, we need to track the stack
location of its original value separately from the DRAP register.
This fixes the following false positive warning:
lib/ubsan.o: warning: objtool: val_to_string.constprop.7()+0x97: leave instruction with modified stack frame
Reported-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Borislav Petkov <bp@alien8.de>
Cc: Brian Gerst <brgerst@gmail.com>
Cc: Denys Vlasenko <dvlasenk@redhat.com>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Fixes: baa41469a7b9 ("objtool: Implement stack validation 2.0")
Link: http://lkml.kernel.org/r/3da23a6d4c5b3c1e21fc2ccc21a73941b97ff20a.1502401017.git.jpoimboe@redhat.com
Signed-off-by: Ingo Molnar <mingo@kernel.org>
2017-08-11 05:37:26 +08:00
|
|
|
int drap_reg, drap_offset;
|
2017-08-30 01:51:03 +08:00
|
|
|
struct cfi_reg vals[CFI_NUM_REGS];
|
2017-06-28 23:11:07 +08:00
|
|
|
};
|
|
|
|
|
2017-06-28 23:11:05 +08:00
|
|
|
struct instruction {
|
|
|
|
struct list_head list;
|
|
|
|
struct hlist_node hash;
|
|
|
|
struct section *sec;
|
|
|
|
unsigned long offset;
|
2017-06-28 23:11:07 +08:00
|
|
|
unsigned int len;
|
2019-07-18 09:36:56 +08:00
|
|
|
enum insn_type type;
|
2017-06-28 23:11:05 +08:00
|
|
|
unsigned long immediate;
|
2019-07-25 06:47:26 +08:00
|
|
|
bool alt_group, dead_end, ignore, hint, save, restore, ignore_alts;
|
2018-01-16 17:24:06 +08:00
|
|
|
bool retpoline_safe;
|
2019-07-25 06:47:26 +08:00
|
|
|
u8 visited;
|
2017-06-28 23:11:05 +08:00
|
|
|
struct symbol *call_dest;
|
|
|
|
struct instruction *jump_dest;
|
2018-02-08 21:02:32 +08:00
|
|
|
struct instruction *first_jump_src;
|
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>
2019-07-18 09:36:54 +08:00
|
|
|
struct rela *jump_table;
|
2017-06-28 23:11:05 +08:00
|
|
|
struct list_head alts;
|
|
|
|
struct symbol *func;
|
2017-06-28 23:11:07 +08:00
|
|
|
struct stack_op stack_op;
|
|
|
|
struct insn_state state;
|
2017-07-11 23:33:42 +08:00
|
|
|
struct orc_entry orc;
|
2017-06-28 23:11:05 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
struct objtool_file {
|
|
|
|
struct elf *elf;
|
|
|
|
struct list_head insn_list;
|
2020-03-12 17:20:46 +08:00
|
|
|
DECLARE_HASHTABLE(insn_hash, 20);
|
2018-09-07 21:12:01 +08:00
|
|
|
bool ignore_unreachables, c_file, hints, rodata;
|
2017-06-28 23:11:05 +08:00
|
|
|
};
|
|
|
|
|
2018-01-17 00:16:32 +08:00
|
|
|
int check(const char *objname, bool orc);
|
2017-07-11 23:33:42 +08:00
|
|
|
|
|
|
|
struct instruction *find_insn(struct objtool_file *file,
|
|
|
|
struct section *sec, unsigned long offset);
|
2017-06-28 23:11:05 +08:00
|
|
|
|
2017-06-28 23:11:07 +08:00
|
|
|
#define for_each_insn(file, insn) \
|
|
|
|
list_for_each_entry(insn, &file->insn_list, list)
|
|
|
|
|
2017-07-11 23:33:42 +08:00
|
|
|
#define sec_for_each_insn(file, sec, insn) \
|
|
|
|
for (insn = find_insn(file, sec, 0); \
|
|
|
|
insn && &insn->list != &file->insn_list && \
|
|
|
|
insn->sec == sec; \
|
|
|
|
insn = list_next_entry(insn, list))
|
|
|
|
|
|
|
|
|
2017-06-28 23:11:05 +08:00
|
|
|
#endif /* _CHECK_H */
|