2017-06-28 22:11:05 +07:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2015-2017 Josh Poimboeuf <jpoimboe@redhat.com>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 2
|
|
|
|
* of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
2018-01-16 23:16:32 +07:00
|
|
|
#include "builtin.h"
|
2017-06-28 22:11:05 +07:00
|
|
|
#include "check.h"
|
|
|
|
#include "elf.h"
|
|
|
|
#include "special.h"
|
|
|
|
#include "arch.h"
|
|
|
|
#include "warn.h"
|
|
|
|
|
|
|
|
#include <linux/hashtable.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
|
|
|
|
struct alternative {
|
|
|
|
struct list_head list;
|
|
|
|
struct instruction *insn;
|
|
|
|
};
|
|
|
|
|
|
|
|
const char *objname;
|
2017-06-28 22:11:07 +07:00
|
|
|
struct cfi_state initial_func_cfi;
|
2017-06-28 22:11:05 +07:00
|
|
|
|
2017-07-11 22:33:42 +07:00
|
|
|
struct instruction *find_insn(struct objtool_file *file,
|
|
|
|
struct section *sec, unsigned long offset)
|
2017-06-28 22:11:05 +07:00
|
|
|
{
|
|
|
|
struct instruction *insn;
|
|
|
|
|
|
|
|
hash_for_each_possible(file->insn_hash, insn, hash, offset)
|
|
|
|
if (insn->sec == sec && insn->offset == offset)
|
|
|
|
return insn;
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct instruction *next_insn_same_sec(struct objtool_file *file,
|
|
|
|
struct instruction *insn)
|
|
|
|
{
|
|
|
|
struct instruction *next = list_next_entry(insn, list);
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
if (!next || &next->list == &file->insn_list || next->sec != insn->sec)
|
2017-06-28 22:11:05 +07:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return next;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define func_for_each_insn(file, func, insn) \
|
|
|
|
for (insn = find_insn(file, func->sec, func->offset); \
|
|
|
|
insn && &insn->list != &file->insn_list && \
|
|
|
|
insn->sec == func->sec && \
|
|
|
|
insn->offset < func->offset + func->len; \
|
|
|
|
insn = list_next_entry(insn, list))
|
|
|
|
|
|
|
|
#define func_for_each_insn_continue_reverse(file, func, insn) \
|
|
|
|
for (insn = list_prev_entry(insn, list); \
|
|
|
|
&insn->list != &file->insn_list && \
|
|
|
|
insn->sec == func->sec && insn->offset >= func->offset; \
|
|
|
|
insn = list_prev_entry(insn, list))
|
|
|
|
|
|
|
|
#define sec_for_each_insn_from(file, insn) \
|
|
|
|
for (; insn; insn = next_insn_same_sec(file, insn))
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
#define sec_for_each_insn_continue(file, insn) \
|
|
|
|
for (insn = next_insn_same_sec(file, insn); insn; \
|
|
|
|
insn = next_insn_same_sec(file, insn))
|
2017-06-28 22:11:05 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Check if the function has been manually whitelisted with the
|
|
|
|
* STACK_FRAME_NON_STANDARD macro, or if it should be automatically whitelisted
|
|
|
|
* due to its use of a context switching instruction.
|
|
|
|
*/
|
|
|
|
static bool ignore_func(struct objtool_file *file, struct symbol *func)
|
|
|
|
{
|
|
|
|
struct rela *rela;
|
|
|
|
|
|
|
|
/* check for STACK_FRAME_NON_STANDARD */
|
|
|
|
if (file->whitelist && file->whitelist->rela)
|
|
|
|
list_for_each_entry(rela, &file->whitelist->rela->rela_list, list) {
|
|
|
|
if (rela->sym->type == STT_SECTION &&
|
|
|
|
rela->sym->sec == func->sec &&
|
|
|
|
rela->addend == func->offset)
|
|
|
|
return true;
|
|
|
|
if (rela->sym->type == STT_FUNC && rela->sym == func)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This checks to see if the given function is a "noreturn" function.
|
|
|
|
*
|
|
|
|
* For global functions which are outside the scope of this object file, we
|
|
|
|
* have to keep a manual list of them.
|
|
|
|
*
|
|
|
|
* For local functions, we have to detect them manually by simply looking for
|
|
|
|
* the lack of a return instruction.
|
|
|
|
*
|
|
|
|
* Returns:
|
|
|
|
* -1: error
|
|
|
|
* 0: no dead end
|
|
|
|
* 1: dead end
|
|
|
|
*/
|
|
|
|
static int __dead_end_function(struct objtool_file *file, struct symbol *func,
|
|
|
|
int recursion)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
struct instruction *insn;
|
|
|
|
bool empty = true;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Unfortunately these have to be hard coded because the noreturn
|
|
|
|
* attribute isn't provided in ELF data.
|
|
|
|
*/
|
|
|
|
static const char * const global_noreturns[] = {
|
|
|
|
"__stack_chk_fail",
|
|
|
|
"panic",
|
|
|
|
"do_exit",
|
|
|
|
"do_task_dead",
|
|
|
|
"__module_put_and_exit",
|
|
|
|
"complete_and_exit",
|
|
|
|
"kvm_spurious_fault",
|
|
|
|
"__reiserfs_panic",
|
|
|
|
"lbug_with_loc",
|
|
|
|
"fortify_panic",
|
2018-01-11 05:22:38 +07:00
|
|
|
"usercopy_abort",
|
2017-06-28 22:11:05 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
if (func->bind == STB_WEAK)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (func->bind == STB_GLOBAL)
|
|
|
|
for (i = 0; i < ARRAY_SIZE(global_noreturns); i++)
|
|
|
|
if (!strcmp(func->name, global_noreturns[i]))
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
if (!func->sec)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
func_for_each_insn(file, func, insn) {
|
|
|
|
empty = false;
|
|
|
|
|
|
|
|
if (insn->type == INSN_RETURN)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (empty)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A function can have a sibling call instead of a return. In that
|
|
|
|
* case, the function's dead-end status depends on whether the target
|
|
|
|
* of the sibling call returns.
|
|
|
|
*/
|
|
|
|
func_for_each_insn(file, func, insn) {
|
|
|
|
if (insn->sec != func->sec ||
|
|
|
|
insn->offset >= func->offset + func->len)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (insn->type == INSN_JUMP_UNCONDITIONAL) {
|
|
|
|
struct instruction *dest = insn->jump_dest;
|
|
|
|
struct symbol *dest_func;
|
|
|
|
|
|
|
|
if (!dest)
|
|
|
|
/* sibling call to another file */
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (dest->sec != func->sec ||
|
|
|
|
dest->offset < func->offset ||
|
|
|
|
dest->offset >= func->offset + func->len) {
|
|
|
|
/* local sibling call */
|
|
|
|
dest_func = find_symbol_by_offset(dest->sec,
|
|
|
|
dest->offset);
|
|
|
|
if (!dest_func)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (recursion == 5) {
|
|
|
|
WARN_FUNC("infinite recursion (objtool bug!)",
|
|
|
|
dest->sec, dest->offset);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return __dead_end_function(file, dest_func,
|
|
|
|
recursion + 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (insn->type == INSN_JUMP_DYNAMIC && list_empty(&insn->alts))
|
|
|
|
/* sibling call */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dead_end_function(struct objtool_file *file, struct symbol *func)
|
|
|
|
{
|
|
|
|
return __dead_end_function(file, func, 0);
|
|
|
|
}
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
static void clear_insn_state(struct insn_state *state)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
memset(state, 0, sizeof(*state));
|
|
|
|
state->cfa.base = CFI_UNDEFINED;
|
2017-08-30 00:51:03 +07:00
|
|
|
for (i = 0; i < CFI_NUM_REGS; i++) {
|
2017-06-28 22:11:07 +07:00
|
|
|
state->regs[i].base = CFI_UNDEFINED;
|
2017-08-30 00:51:03 +07:00
|
|
|
state->vals[i].base = CFI_UNDEFINED;
|
|
|
|
}
|
2017-06-28 22:11:07 +07:00
|
|
|
state->drap_reg = CFI_UNDEFINED;
|
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 04:37:26 +07:00
|
|
|
state->drap_offset = -1;
|
2017-06-28 22:11:07 +07:00
|
|
|
}
|
|
|
|
|
2017-06-28 22:11:05 +07:00
|
|
|
/*
|
|
|
|
* Call the arch-specific instruction decoder for all the instructions and add
|
|
|
|
* them to the global instruction list.
|
|
|
|
*/
|
|
|
|
static int decode_instructions(struct objtool_file *file)
|
|
|
|
{
|
|
|
|
struct section *sec;
|
|
|
|
struct symbol *func;
|
|
|
|
unsigned long offset;
|
|
|
|
struct instruction *insn;
|
|
|
|
int ret;
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
for_each_sec(file, sec) {
|
2017-06-28 22:11:05 +07:00
|
|
|
|
|
|
|
if (!(sec->sh.sh_flags & SHF_EXECINSTR))
|
|
|
|
continue;
|
|
|
|
|
2017-07-11 22:33:42 +07:00
|
|
|
if (strcmp(sec->name, ".altinstr_replacement") &&
|
|
|
|
strcmp(sec->name, ".altinstr_aux") &&
|
|
|
|
strncmp(sec->name, ".discard.", 9))
|
|
|
|
sec->text = true;
|
|
|
|
|
2017-06-28 22:11:05 +07:00
|
|
|
for (offset = 0; offset < sec->len; offset += insn->len) {
|
|
|
|
insn = malloc(sizeof(*insn));
|
2017-06-28 22:11:07 +07:00
|
|
|
if (!insn) {
|
|
|
|
WARN("malloc failed");
|
|
|
|
return -1;
|
|
|
|
}
|
2017-06-28 22:11:05 +07:00
|
|
|
memset(insn, 0, sizeof(*insn));
|
|
|
|
INIT_LIST_HEAD(&insn->alts);
|
2017-06-28 22:11:07 +07:00
|
|
|
clear_insn_state(&insn->state);
|
|
|
|
|
2017-06-28 22:11:05 +07:00
|
|
|
insn->sec = sec;
|
|
|
|
insn->offset = offset;
|
|
|
|
|
|
|
|
ret = arch_decode_instruction(file->elf, sec, offset,
|
|
|
|
sec->len - offset,
|
|
|
|
&insn->len, &insn->type,
|
2017-06-28 22:11:07 +07:00
|
|
|
&insn->immediate,
|
|
|
|
&insn->stack_op);
|
2017-06-28 22:11:05 +07:00
|
|
|
if (ret)
|
2017-10-19 23:27:24 +07:00
|
|
|
goto err;
|
2017-06-28 22:11:05 +07:00
|
|
|
|
|
|
|
if (!insn->type || insn->type > INSN_LAST) {
|
|
|
|
WARN_FUNC("invalid instruction type %d",
|
|
|
|
insn->sec, insn->offset, insn->type);
|
2017-10-19 23:27:24 +07:00
|
|
|
ret = -1;
|
|
|
|
goto err;
|
2017-06-28 22:11:05 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
hash_add(file->insn_hash, &insn->hash, insn->offset);
|
|
|
|
list_add_tail(&insn->list, &file->insn_list);
|
|
|
|
}
|
|
|
|
|
|
|
|
list_for_each_entry(func, &sec->symbol_list, list) {
|
|
|
|
if (func->type != STT_FUNC)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!find_insn(file, sec, func->offset)) {
|
|
|
|
WARN("%s(): can't find starting instruction",
|
|
|
|
func->name);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
func_for_each_insn(file, func, insn)
|
|
|
|
if (!insn->func)
|
|
|
|
insn->func = func;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2017-10-19 23:27:24 +07:00
|
|
|
|
|
|
|
err:
|
|
|
|
free(insn);
|
|
|
|
return ret;
|
2017-06-28 22:11:05 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
objtool: Assume unannotated UD2 instructions are dead ends
Arnd reported some false positive warnings with GCC 7:
drivers/hid/wacom_wac.o: warning: objtool: wacom_bpt3_touch()+0x2a5: stack state mismatch: cfa1=7+8 cfa2=6+16
drivers/iio/adc/vf610_adc.o: warning: objtool: vf610_adc_calculate_rates() falls through to next function vf610_adc_sample_set()
drivers/pwm/pwm-hibvt.o: warning: objtool: hibvt_pwm_get_state() falls through to next function hibvt_pwm_remove()
drivers/pwm/pwm-mediatek.o: warning: objtool: mtk_pwm_config() falls through to next function mtk_pwm_enable()
drivers/spi/spi-bcm2835.o: warning: objtool: .text: unexpected end of section
drivers/spi/spi-bcm2835aux.o: warning: objtool: .text: unexpected end of section
drivers/watchdog/digicolor_wdt.o: warning: objtool: dc_wdt_get_timeleft() falls through to next function dc_wdt_restart()
When GCC 7 detects a potential divide-by-zero condition, it sometimes
inserts a UD2 instruction for the case where the divisor is zero,
instead of letting the hardware trap on the divide instruction.
Objtool doesn't consider UD2 to be fatal unless it's annotated with
unreachable(). So it considers the GCC-generated UD2 to be non-fatal,
and it tries to follow the control flow past the UD2 and gets
confused.
Previously, objtool *did* assume UD2 was always a dead end. That
changed with the following commit:
d1091c7fa3d5 ("objtool: Improve detection of BUG() and other dead ends")
The motivation behind that change was that Peter was planning on using
UD2 for __WARN(), which is *not* a dead end. However, it turns out
that some emulators rely on UD2 being fatal, so he ended up using
'ud0' instead:
9a93848fe787 ("x86/debug: Implement __WARN() using UD0")
For GCC 4.5+, it should be safe to go back to the previous assumption
that UD2 is fatal, even when it's not annotated with unreachable().
But for pre-4.5 versions of GCC, the unreachable() macro isn't
supported, so such cases of UD2 need to be explicitly annotated as
reachable.
Reported-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Fixes: d1091c7fa3d5 ("objtool: Improve detection of BUG() and other dead ends")
Link: http://lkml.kernel.org/r/e57fa9dfede25f79487da8126ee9cdf7b856db65.1501188854.git.jpoimboe@redhat.com
Signed-off-by: Ingo Molnar <mingo@kernel.org>
2017-07-28 03:56:53 +07:00
|
|
|
* Mark "ud2" instructions and manually annotated dead ends.
|
2017-06-28 22:11:05 +07:00
|
|
|
*/
|
|
|
|
static int add_dead_ends(struct objtool_file *file)
|
|
|
|
{
|
|
|
|
struct section *sec;
|
|
|
|
struct rela *rela;
|
|
|
|
struct instruction *insn;
|
|
|
|
bool found;
|
|
|
|
|
objtool: Assume unannotated UD2 instructions are dead ends
Arnd reported some false positive warnings with GCC 7:
drivers/hid/wacom_wac.o: warning: objtool: wacom_bpt3_touch()+0x2a5: stack state mismatch: cfa1=7+8 cfa2=6+16
drivers/iio/adc/vf610_adc.o: warning: objtool: vf610_adc_calculate_rates() falls through to next function vf610_adc_sample_set()
drivers/pwm/pwm-hibvt.o: warning: objtool: hibvt_pwm_get_state() falls through to next function hibvt_pwm_remove()
drivers/pwm/pwm-mediatek.o: warning: objtool: mtk_pwm_config() falls through to next function mtk_pwm_enable()
drivers/spi/spi-bcm2835.o: warning: objtool: .text: unexpected end of section
drivers/spi/spi-bcm2835aux.o: warning: objtool: .text: unexpected end of section
drivers/watchdog/digicolor_wdt.o: warning: objtool: dc_wdt_get_timeleft() falls through to next function dc_wdt_restart()
When GCC 7 detects a potential divide-by-zero condition, it sometimes
inserts a UD2 instruction for the case where the divisor is zero,
instead of letting the hardware trap on the divide instruction.
Objtool doesn't consider UD2 to be fatal unless it's annotated with
unreachable(). So it considers the GCC-generated UD2 to be non-fatal,
and it tries to follow the control flow past the UD2 and gets
confused.
Previously, objtool *did* assume UD2 was always a dead end. That
changed with the following commit:
d1091c7fa3d5 ("objtool: Improve detection of BUG() and other dead ends")
The motivation behind that change was that Peter was planning on using
UD2 for __WARN(), which is *not* a dead end. However, it turns out
that some emulators rely on UD2 being fatal, so he ended up using
'ud0' instead:
9a93848fe787 ("x86/debug: Implement __WARN() using UD0")
For GCC 4.5+, it should be safe to go back to the previous assumption
that UD2 is fatal, even when it's not annotated with unreachable().
But for pre-4.5 versions of GCC, the unreachable() macro isn't
supported, so such cases of UD2 need to be explicitly annotated as
reachable.
Reported-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Fixes: d1091c7fa3d5 ("objtool: Improve detection of BUG() and other dead ends")
Link: http://lkml.kernel.org/r/e57fa9dfede25f79487da8126ee9cdf7b856db65.1501188854.git.jpoimboe@redhat.com
Signed-off-by: Ingo Molnar <mingo@kernel.org>
2017-07-28 03:56:53 +07:00
|
|
|
/*
|
|
|
|
* By default, "ud2" is a dead end unless otherwise annotated, because
|
|
|
|
* GCC 7 inserts it for certain divide-by-zero cases.
|
|
|
|
*/
|
|
|
|
for_each_insn(file, insn)
|
|
|
|
if (insn->type == INSN_BUG)
|
|
|
|
insn->dead_end = true;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check for manually annotated dead ends.
|
|
|
|
*/
|
2017-06-28 22:11:05 +07:00
|
|
|
sec = find_section_by_name(file->elf, ".rela.discard.unreachable");
|
|
|
|
if (!sec)
|
objtool: Assume unannotated UD2 instructions are dead ends
Arnd reported some false positive warnings with GCC 7:
drivers/hid/wacom_wac.o: warning: objtool: wacom_bpt3_touch()+0x2a5: stack state mismatch: cfa1=7+8 cfa2=6+16
drivers/iio/adc/vf610_adc.o: warning: objtool: vf610_adc_calculate_rates() falls through to next function vf610_adc_sample_set()
drivers/pwm/pwm-hibvt.o: warning: objtool: hibvt_pwm_get_state() falls through to next function hibvt_pwm_remove()
drivers/pwm/pwm-mediatek.o: warning: objtool: mtk_pwm_config() falls through to next function mtk_pwm_enable()
drivers/spi/spi-bcm2835.o: warning: objtool: .text: unexpected end of section
drivers/spi/spi-bcm2835aux.o: warning: objtool: .text: unexpected end of section
drivers/watchdog/digicolor_wdt.o: warning: objtool: dc_wdt_get_timeleft() falls through to next function dc_wdt_restart()
When GCC 7 detects a potential divide-by-zero condition, it sometimes
inserts a UD2 instruction for the case where the divisor is zero,
instead of letting the hardware trap on the divide instruction.
Objtool doesn't consider UD2 to be fatal unless it's annotated with
unreachable(). So it considers the GCC-generated UD2 to be non-fatal,
and it tries to follow the control flow past the UD2 and gets
confused.
Previously, objtool *did* assume UD2 was always a dead end. That
changed with the following commit:
d1091c7fa3d5 ("objtool: Improve detection of BUG() and other dead ends")
The motivation behind that change was that Peter was planning on using
UD2 for __WARN(), which is *not* a dead end. However, it turns out
that some emulators rely on UD2 being fatal, so he ended up using
'ud0' instead:
9a93848fe787 ("x86/debug: Implement __WARN() using UD0")
For GCC 4.5+, it should be safe to go back to the previous assumption
that UD2 is fatal, even when it's not annotated with unreachable().
But for pre-4.5 versions of GCC, the unreachable() macro isn't
supported, so such cases of UD2 need to be explicitly annotated as
reachable.
Reported-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Fixes: d1091c7fa3d5 ("objtool: Improve detection of BUG() and other dead ends")
Link: http://lkml.kernel.org/r/e57fa9dfede25f79487da8126ee9cdf7b856db65.1501188854.git.jpoimboe@redhat.com
Signed-off-by: Ingo Molnar <mingo@kernel.org>
2017-07-28 03:56:53 +07:00
|
|
|
goto reachable;
|
2017-06-28 22:11:05 +07:00
|
|
|
|
|
|
|
list_for_each_entry(rela, &sec->rela_list, list) {
|
|
|
|
if (rela->sym->type != STT_SECTION) {
|
|
|
|
WARN("unexpected relocation symbol type in %s", sec->name);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
insn = find_insn(file, rela->sym->sec, rela->addend);
|
|
|
|
if (insn)
|
|
|
|
insn = list_prev_entry(insn, list);
|
|
|
|
else if (rela->addend == rela->sym->sec->len) {
|
|
|
|
found = false;
|
|
|
|
list_for_each_entry_reverse(insn, &file->insn_list, list) {
|
|
|
|
if (insn->sec == rela->sym->sec) {
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!found) {
|
|
|
|
WARN("can't find unreachable insn at %s+0x%x",
|
|
|
|
rela->sym->sec->name, rela->addend);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
WARN("can't find unreachable insn at %s+0x%x",
|
|
|
|
rela->sym->sec->name, rela->addend);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
insn->dead_end = true;
|
|
|
|
}
|
|
|
|
|
objtool: Assume unannotated UD2 instructions are dead ends
Arnd reported some false positive warnings with GCC 7:
drivers/hid/wacom_wac.o: warning: objtool: wacom_bpt3_touch()+0x2a5: stack state mismatch: cfa1=7+8 cfa2=6+16
drivers/iio/adc/vf610_adc.o: warning: objtool: vf610_adc_calculate_rates() falls through to next function vf610_adc_sample_set()
drivers/pwm/pwm-hibvt.o: warning: objtool: hibvt_pwm_get_state() falls through to next function hibvt_pwm_remove()
drivers/pwm/pwm-mediatek.o: warning: objtool: mtk_pwm_config() falls through to next function mtk_pwm_enable()
drivers/spi/spi-bcm2835.o: warning: objtool: .text: unexpected end of section
drivers/spi/spi-bcm2835aux.o: warning: objtool: .text: unexpected end of section
drivers/watchdog/digicolor_wdt.o: warning: objtool: dc_wdt_get_timeleft() falls through to next function dc_wdt_restart()
When GCC 7 detects a potential divide-by-zero condition, it sometimes
inserts a UD2 instruction for the case where the divisor is zero,
instead of letting the hardware trap on the divide instruction.
Objtool doesn't consider UD2 to be fatal unless it's annotated with
unreachable(). So it considers the GCC-generated UD2 to be non-fatal,
and it tries to follow the control flow past the UD2 and gets
confused.
Previously, objtool *did* assume UD2 was always a dead end. That
changed with the following commit:
d1091c7fa3d5 ("objtool: Improve detection of BUG() and other dead ends")
The motivation behind that change was that Peter was planning on using
UD2 for __WARN(), which is *not* a dead end. However, it turns out
that some emulators rely on UD2 being fatal, so he ended up using
'ud0' instead:
9a93848fe787 ("x86/debug: Implement __WARN() using UD0")
For GCC 4.5+, it should be safe to go back to the previous assumption
that UD2 is fatal, even when it's not annotated with unreachable().
But for pre-4.5 versions of GCC, the unreachable() macro isn't
supported, so such cases of UD2 need to be explicitly annotated as
reachable.
Reported-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Fixes: d1091c7fa3d5 ("objtool: Improve detection of BUG() and other dead ends")
Link: http://lkml.kernel.org/r/e57fa9dfede25f79487da8126ee9cdf7b856db65.1501188854.git.jpoimboe@redhat.com
Signed-off-by: Ingo Molnar <mingo@kernel.org>
2017-07-28 03:56:53 +07:00
|
|
|
reachable:
|
|
|
|
/*
|
|
|
|
* These manually annotated reachable checks are needed for GCC 4.4,
|
|
|
|
* where the Linux unreachable() macro isn't supported. In that case
|
|
|
|
* GCC doesn't know the "ud2" is fatal, so it generates code as if it's
|
|
|
|
* not a dead end.
|
|
|
|
*/
|
|
|
|
sec = find_section_by_name(file->elf, ".rela.discard.reachable");
|
|
|
|
if (!sec)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
list_for_each_entry(rela, &sec->rela_list, list) {
|
|
|
|
if (rela->sym->type != STT_SECTION) {
|
|
|
|
WARN("unexpected relocation symbol type in %s", sec->name);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
insn = find_insn(file, rela->sym->sec, rela->addend);
|
|
|
|
if (insn)
|
|
|
|
insn = list_prev_entry(insn, list);
|
|
|
|
else if (rela->addend == rela->sym->sec->len) {
|
|
|
|
found = false;
|
|
|
|
list_for_each_entry_reverse(insn, &file->insn_list, list) {
|
|
|
|
if (insn->sec == rela->sym->sec) {
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!found) {
|
|
|
|
WARN("can't find reachable insn at %s+0x%x",
|
|
|
|
rela->sym->sec->name, rela->addend);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
WARN("can't find reachable insn at %s+0x%x",
|
|
|
|
rela->sym->sec->name, rela->addend);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
insn->dead_end = false;
|
|
|
|
}
|
|
|
|
|
2017-06-28 22:11:05 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Warnings shouldn't be reported for ignored functions.
|
|
|
|
*/
|
|
|
|
static void add_ignores(struct objtool_file *file)
|
|
|
|
{
|
|
|
|
struct instruction *insn;
|
|
|
|
struct section *sec;
|
|
|
|
struct symbol *func;
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
for_each_sec(file, sec) {
|
2017-06-28 22:11:05 +07:00
|
|
|
list_for_each_entry(func, &sec->symbol_list, list) {
|
|
|
|
if (func->type != STT_FUNC)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!ignore_func(file, func))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
func_for_each_insn(file, func, insn)
|
2017-06-28 22:11:07 +07:00
|
|
|
insn->ignore = true;
|
2017-06-28 22:11:05 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-12 04:46:24 +07:00
|
|
|
/*
|
|
|
|
* FIXME: For now, just ignore any alternatives which add retpolines. This is
|
|
|
|
* a temporary hack, as it doesn't allow ORC to unwind from inside a retpoline.
|
|
|
|
* But it at least allows objtool to understand the control flow *around* the
|
|
|
|
* retpoline.
|
|
|
|
*/
|
|
|
|
static int add_nospec_ignores(struct objtool_file *file)
|
|
|
|
{
|
|
|
|
struct section *sec;
|
|
|
|
struct rela *rela;
|
|
|
|
struct instruction *insn;
|
|
|
|
|
|
|
|
sec = find_section_by_name(file->elf, ".rela.discard.nospec");
|
|
|
|
if (!sec)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
list_for_each_entry(rela, &sec->rela_list, list) {
|
|
|
|
if (rela->sym->type != STT_SECTION) {
|
|
|
|
WARN("unexpected relocation symbol type in %s", sec->name);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
insn = find_insn(file, rela->sym->sec, rela->addend);
|
|
|
|
if (!insn) {
|
|
|
|
WARN("bad .discard.nospec entry");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
insn->ignore_alts = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-06-28 22:11:05 +07:00
|
|
|
/*
|
|
|
|
* Find the destination instructions for all jumps.
|
|
|
|
*/
|
|
|
|
static int add_jump_destinations(struct objtool_file *file)
|
|
|
|
{
|
|
|
|
struct instruction *insn;
|
|
|
|
struct rela *rela;
|
|
|
|
struct section *dest_sec;
|
|
|
|
unsigned long dest_off;
|
|
|
|
|
|
|
|
for_each_insn(file, insn) {
|
|
|
|
if (insn->type != INSN_JUMP_CONDITIONAL &&
|
|
|
|
insn->type != INSN_JUMP_UNCONDITIONAL)
|
|
|
|
continue;
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
if (insn->ignore)
|
2017-06-28 22:11:05 +07:00
|
|
|
continue;
|
|
|
|
|
|
|
|
rela = find_rela_by_dest_range(insn->sec, insn->offset,
|
|
|
|
insn->len);
|
|
|
|
if (!rela) {
|
|
|
|
dest_sec = insn->sec;
|
|
|
|
dest_off = insn->offset + insn->len + insn->immediate;
|
|
|
|
} else if (rela->sym->type == STT_SECTION) {
|
|
|
|
dest_sec = rela->sym->sec;
|
|
|
|
dest_off = rela->addend + 4;
|
|
|
|
} else if (rela->sym->sec->idx) {
|
|
|
|
dest_sec = rela->sym->sec;
|
|
|
|
dest_off = rela->sym->sym.st_value + rela->addend + 4;
|
2018-01-12 04:46:23 +07:00
|
|
|
} else if (strstr(rela->sym->name, "_indirect_thunk_")) {
|
|
|
|
/*
|
|
|
|
* Retpoline jumps are really dynamic jumps in
|
|
|
|
* disguise, so convert them accordingly.
|
|
|
|
*/
|
|
|
|
insn->type = INSN_JUMP_DYNAMIC;
|
2018-01-16 16:24:06 +07:00
|
|
|
insn->retpoline_safe = true;
|
2018-01-12 04:46:23 +07:00
|
|
|
continue;
|
2017-06-28 22:11:05 +07:00
|
|
|
} else {
|
|
|
|
/* sibling call */
|
|
|
|
insn->jump_dest = 0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
insn->jump_dest = find_insn(file, dest_sec, dest_off);
|
|
|
|
if (!insn->jump_dest) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This is a special case where an alt instruction
|
|
|
|
* jumps past the end of the section. These are
|
|
|
|
* handled later in handle_group_alt().
|
|
|
|
*/
|
|
|
|
if (!strcmp(insn->sec->name, ".altinstr_replacement"))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
WARN_FUNC("can't find jump dest instruction at %s+0x%lx",
|
|
|
|
insn->sec, insn->offset, dest_sec->name,
|
|
|
|
dest_off);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Find the destination instructions for all calls.
|
|
|
|
*/
|
|
|
|
static int add_call_destinations(struct objtool_file *file)
|
|
|
|
{
|
|
|
|
struct instruction *insn;
|
|
|
|
unsigned long dest_off;
|
|
|
|
struct rela *rela;
|
|
|
|
|
|
|
|
for_each_insn(file, insn) {
|
|
|
|
if (insn->type != INSN_CALL)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
rela = find_rela_by_dest_range(insn->sec, insn->offset,
|
|
|
|
insn->len);
|
|
|
|
if (!rela) {
|
|
|
|
dest_off = insn->offset + insn->len + insn->immediate;
|
|
|
|
insn->call_dest = find_symbol_by_offset(insn->sec,
|
|
|
|
dest_off);
|
2018-01-30 11:00:39 +07:00
|
|
|
|
|
|
|
if (!insn->call_dest && !insn->ignore) {
|
|
|
|
WARN_FUNC("unsupported intra-function call",
|
|
|
|
insn->sec, insn->offset);
|
2018-01-16 16:24:06 +07:00
|
|
|
if (retpoline)
|
|
|
|
WARN("If this is a retpoline, please patch it in with alternatives and annotate it with ANNOTATE_NOSPEC_ALTERNATIVE.");
|
2017-06-28 22:11:05 +07:00
|
|
|
return -1;
|
|
|
|
}
|
2018-01-30 11:00:39 +07:00
|
|
|
|
2017-06-28 22:11:05 +07:00
|
|
|
} else if (rela->sym->type == STT_SECTION) {
|
|
|
|
insn->call_dest = find_symbol_by_offset(rela->sym->sec,
|
|
|
|
rela->addend+4);
|
|
|
|
if (!insn->call_dest ||
|
|
|
|
insn->call_dest->type != STT_FUNC) {
|
|
|
|
WARN_FUNC("can't find call dest symbol at %s+0x%x",
|
|
|
|
insn->sec, insn->offset,
|
|
|
|
rela->sym->sec->name,
|
|
|
|
rela->addend + 4);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
insn->call_dest = rela->sym;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The .alternatives section requires some extra special care, over and above
|
|
|
|
* what other special sections require:
|
|
|
|
*
|
|
|
|
* 1. Because alternatives are patched in-place, we need to insert a fake jump
|
|
|
|
* instruction at the end so that validate_branch() skips all the original
|
|
|
|
* replaced instructions when validating the new instruction path.
|
|
|
|
*
|
|
|
|
* 2. An added wrinkle is that the new instruction length might be zero. In
|
|
|
|
* that case the old instructions are replaced with noops. We simulate that
|
|
|
|
* by creating a fake jump as the only new instruction.
|
|
|
|
*
|
|
|
|
* 3. In some cases, the alternative section includes an instruction which
|
|
|
|
* conditionally jumps to the _end_ of the entry. We have to modify these
|
|
|
|
* jumps' destinations to point back to .text rather than the end of the
|
|
|
|
* entry in .altinstr_replacement.
|
|
|
|
*
|
|
|
|
* 4. It has been requested that we don't validate the !POPCNT feature path
|
|
|
|
* which is a "very very small percentage of machines".
|
|
|
|
*/
|
|
|
|
static int handle_group_alt(struct objtool_file *file,
|
|
|
|
struct special_alt *special_alt,
|
|
|
|
struct instruction *orig_insn,
|
|
|
|
struct instruction **new_insn)
|
|
|
|
{
|
2018-01-30 11:00:40 +07:00
|
|
|
struct instruction *last_orig_insn, *last_new_insn, *insn, *fake_jump = NULL;
|
2017-06-28 22:11:05 +07:00
|
|
|
unsigned long dest_off;
|
|
|
|
|
|
|
|
last_orig_insn = NULL;
|
|
|
|
insn = orig_insn;
|
|
|
|
sec_for_each_insn_from(file, insn) {
|
|
|
|
if (insn->offset >= special_alt->orig_off + special_alt->orig_len)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (special_alt->skip_orig)
|
|
|
|
insn->type = INSN_NOP;
|
|
|
|
|
|
|
|
insn->alt_group = true;
|
|
|
|
last_orig_insn = insn;
|
|
|
|
}
|
|
|
|
|
2018-01-30 11:00:40 +07:00
|
|
|
if (next_insn_same_sec(file, last_orig_insn)) {
|
|
|
|
fake_jump = malloc(sizeof(*fake_jump));
|
|
|
|
if (!fake_jump) {
|
|
|
|
WARN("malloc failed");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
memset(fake_jump, 0, sizeof(*fake_jump));
|
|
|
|
INIT_LIST_HEAD(&fake_jump->alts);
|
|
|
|
clear_insn_state(&fake_jump->state);
|
|
|
|
|
|
|
|
fake_jump->sec = special_alt->new_sec;
|
|
|
|
fake_jump->offset = -1;
|
|
|
|
fake_jump->type = INSN_JUMP_UNCONDITIONAL;
|
|
|
|
fake_jump->jump_dest = list_next_entry(last_orig_insn, list);
|
|
|
|
fake_jump->ignore = true;
|
2017-06-28 22:11:05 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!special_alt->new_len) {
|
2018-01-30 11:00:40 +07:00
|
|
|
if (!fake_jump) {
|
|
|
|
WARN("%s: empty alternative at end of section",
|
|
|
|
special_alt->orig_sec->name);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2017-06-28 22:11:05 +07:00
|
|
|
*new_insn = fake_jump;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
last_new_insn = NULL;
|
|
|
|
insn = *new_insn;
|
|
|
|
sec_for_each_insn_from(file, insn) {
|
|
|
|
if (insn->offset >= special_alt->new_off + special_alt->new_len)
|
|
|
|
break;
|
|
|
|
|
|
|
|
last_new_insn = insn;
|
|
|
|
|
2018-01-30 11:00:39 +07:00
|
|
|
insn->ignore = orig_insn->ignore_alts;
|
|
|
|
|
2017-06-28 22:11:05 +07:00
|
|
|
if (insn->type != INSN_JUMP_CONDITIONAL &&
|
|
|
|
insn->type != INSN_JUMP_UNCONDITIONAL)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!insn->immediate)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
dest_off = insn->offset + insn->len + insn->immediate;
|
2018-01-30 11:00:40 +07:00
|
|
|
if (dest_off == special_alt->new_off + special_alt->new_len) {
|
|
|
|
if (!fake_jump) {
|
|
|
|
WARN("%s: alternative jump to end of section",
|
|
|
|
special_alt->orig_sec->name);
|
|
|
|
return -1;
|
|
|
|
}
|
2017-06-28 22:11:05 +07:00
|
|
|
insn->jump_dest = fake_jump;
|
2018-01-30 11:00:40 +07:00
|
|
|
}
|
2017-06-28 22:11:05 +07:00
|
|
|
|
|
|
|
if (!insn->jump_dest) {
|
|
|
|
WARN_FUNC("can't find alternative jump destination",
|
|
|
|
insn->sec, insn->offset);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!last_new_insn) {
|
|
|
|
WARN_FUNC("can't find last new alternative instruction",
|
|
|
|
special_alt->new_sec, special_alt->new_off);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2018-01-30 11:00:40 +07:00
|
|
|
if (fake_jump)
|
|
|
|
list_add(&fake_jump->list, &last_new_insn->list);
|
2017-06-28 22:11:05 +07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A jump table entry can either convert a nop to a jump or a jump to a nop.
|
|
|
|
* If the original instruction is a jump, make the alt entry an effective nop
|
|
|
|
* by just skipping the original instruction.
|
|
|
|
*/
|
|
|
|
static int handle_jump_alt(struct objtool_file *file,
|
|
|
|
struct special_alt *special_alt,
|
|
|
|
struct instruction *orig_insn,
|
|
|
|
struct instruction **new_insn)
|
|
|
|
{
|
|
|
|
if (orig_insn->type == INSN_NOP)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (orig_insn->type != INSN_JUMP_UNCONDITIONAL) {
|
|
|
|
WARN_FUNC("unsupported instruction at jump label",
|
|
|
|
orig_insn->sec, orig_insn->offset);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*new_insn = list_next_entry(orig_insn, list);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read all the special sections which have alternate instructions which can be
|
|
|
|
* patched in or redirected to at runtime. Each instruction having alternate
|
|
|
|
* instruction(s) has them added to its insn->alts list, which will be
|
|
|
|
* traversed in validate_branch().
|
|
|
|
*/
|
|
|
|
static int add_special_section_alts(struct objtool_file *file)
|
|
|
|
{
|
|
|
|
struct list_head special_alts;
|
|
|
|
struct instruction *orig_insn, *new_insn;
|
|
|
|
struct special_alt *special_alt, *tmp;
|
|
|
|
struct alternative *alt;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = special_get_alts(file->elf, &special_alts);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
list_for_each_entry_safe(special_alt, tmp, &special_alts, list) {
|
|
|
|
|
|
|
|
orig_insn = find_insn(file, special_alt->orig_sec,
|
|
|
|
special_alt->orig_off);
|
|
|
|
if (!orig_insn) {
|
|
|
|
WARN_FUNC("special: can't find orig instruction",
|
|
|
|
special_alt->orig_sec, special_alt->orig_off);
|
|
|
|
ret = -1;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
new_insn = NULL;
|
|
|
|
if (!special_alt->group || special_alt->new_len) {
|
|
|
|
new_insn = find_insn(file, special_alt->new_sec,
|
|
|
|
special_alt->new_off);
|
|
|
|
if (!new_insn) {
|
|
|
|
WARN_FUNC("special: can't find new instruction",
|
|
|
|
special_alt->new_sec,
|
|
|
|
special_alt->new_off);
|
|
|
|
ret = -1;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (special_alt->group) {
|
|
|
|
ret = handle_group_alt(file, special_alt, orig_insn,
|
|
|
|
&new_insn);
|
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
} else if (special_alt->jump_or_nop) {
|
|
|
|
ret = handle_jump_alt(file, special_alt, orig_insn,
|
|
|
|
&new_insn);
|
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2018-01-12 04:46:24 +07:00
|
|
|
alt = malloc(sizeof(*alt));
|
|
|
|
if (!alt) {
|
|
|
|
WARN("malloc failed");
|
|
|
|
ret = -1;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2017-06-28 22:11:05 +07:00
|
|
|
alt->insn = new_insn;
|
|
|
|
list_add_tail(&alt->list, &orig_insn->alts);
|
|
|
|
|
|
|
|
list_del(&special_alt->list);
|
|
|
|
free(special_alt);
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int add_switch_table(struct objtool_file *file, struct symbol *func,
|
|
|
|
struct instruction *insn, struct rela *table,
|
|
|
|
struct rela *next_table)
|
|
|
|
{
|
|
|
|
struct rela *rela = table;
|
|
|
|
struct instruction *alt_insn;
|
|
|
|
struct alternative *alt;
|
|
|
|
|
|
|
|
list_for_each_entry_from(rela, &file->rodata->rela->rela_list, list) {
|
|
|
|
if (rela == next_table)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (rela->sym->sec != insn->sec ||
|
|
|
|
rela->addend <= func->offset ||
|
|
|
|
rela->addend >= func->offset + func->len)
|
|
|
|
break;
|
|
|
|
|
|
|
|
alt_insn = find_insn(file, insn->sec, rela->addend);
|
|
|
|
if (!alt_insn) {
|
|
|
|
WARN("%s: can't find instruction at %s+0x%x",
|
|
|
|
file->rodata->rela->name, insn->sec->name,
|
|
|
|
rela->addend);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
alt = malloc(sizeof(*alt));
|
|
|
|
if (!alt) {
|
|
|
|
WARN("malloc failed");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
alt->insn = alt_insn;
|
|
|
|
list_add_tail(&alt->list, &insn->alts);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* find_switch_table() - Given a dynamic jump, find the switch jump table in
|
|
|
|
* .rodata associated with it.
|
|
|
|
*
|
|
|
|
* There are 3 basic patterns:
|
|
|
|
*
|
|
|
|
* 1. jmpq *[rodata addr](,%reg,8)
|
|
|
|
*
|
|
|
|
* This is the most common case by far. It jumps to an address in a simple
|
|
|
|
* jump table which is stored in .rodata.
|
|
|
|
*
|
|
|
|
* 2. jmpq *[rodata addr](%rip)
|
|
|
|
*
|
|
|
|
* This is caused by a rare GCC quirk, currently only seen in three driver
|
|
|
|
* functions in the kernel, only with certain obscure non-distro configs.
|
|
|
|
*
|
|
|
|
* As part of an optimization, GCC makes a copy of an existing switch jump
|
|
|
|
* table, modifies it, and then hard-codes the jump (albeit with an indirect
|
|
|
|
* jump) to use a single entry in the table. The rest of the jump table and
|
|
|
|
* some of its jump targets remain as dead code.
|
|
|
|
*
|
|
|
|
* In such a case we can just crudely ignore all unreachable instruction
|
|
|
|
* warnings for the entire object file. Ideally we would just ignore them
|
|
|
|
* for the function, but that would require redesigning the code quite a
|
|
|
|
* bit. And honestly that's just not worth doing: unreachable instruction
|
|
|
|
* warnings are of questionable value anyway, and this is such a rare issue.
|
|
|
|
*
|
|
|
|
* 3. mov [rodata addr],%reg1
|
|
|
|
* ... some instructions ...
|
|
|
|
* jmpq *(%reg1,%reg2,8)
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*
|
2018-02-08 20:02:32 +07:00
|
|
|
* 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.
|
|
|
|
*
|
2017-06-28 22:11:05 +07:00
|
|
|
* TODO: Once we have DWARF CFI and smarter instruction decoding logic,
|
|
|
|
* ensure the same register is used in the mov and jump instructions.
|
2018-02-08 20:02:32 +07:00
|
|
|
*
|
|
|
|
* NOTE: RETPOLINE made it harder still to decode dynamic jumps.
|
2017-06-28 22:11:05 +07:00
|
|
|
*/
|
|
|
|
static struct rela *find_switch_table(struct objtool_file *file,
|
|
|
|
struct symbol *func,
|
|
|
|
struct instruction *insn)
|
|
|
|
{
|
|
|
|
struct rela *text_rela, *rodata_rela;
|
|
|
|
struct instruction *orig_insn = insn;
|
|
|
|
|
|
|
|
text_rela = find_rela_by_dest_range(insn->sec, insn->offset, insn->len);
|
|
|
|
if (text_rela && text_rela->sym == file->rodata->sym) {
|
|
|
|
/* case 1 */
|
|
|
|
rodata_rela = find_rela_by_dest(file->rodata,
|
|
|
|
text_rela->addend);
|
|
|
|
if (rodata_rela)
|
|
|
|
return rodata_rela;
|
|
|
|
|
|
|
|
/* case 2 */
|
|
|
|
rodata_rela = find_rela_by_dest(file->rodata,
|
|
|
|
text_rela->addend + 4);
|
|
|
|
if (!rodata_rela)
|
|
|
|
return NULL;
|
2018-02-08 20:02:32 +07:00
|
|
|
|
2017-06-28 22:11:05 +07:00
|
|
|
file->ignore_unreachables = true;
|
|
|
|
return rodata_rela;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* case 3 */
|
2018-02-08 20:02:32 +07:00
|
|
|
/*
|
|
|
|
* 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)) {
|
|
|
|
|
2017-06-28 22:11:05 +07:00
|
|
|
if (insn->type == INSN_JUMP_DYNAMIC)
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* allow small jumps within the range */
|
|
|
|
if (insn->type == INSN_JUMP_UNCONDITIONAL &&
|
|
|
|
insn->jump_dest &&
|
|
|
|
(insn->jump_dest->offset <= insn->offset ||
|
|
|
|
insn->jump_dest->offset > orig_insn->offset))
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* look for a relocation which references .rodata */
|
|
|
|
text_rela = find_rela_by_dest_range(insn->sec, insn->offset,
|
|
|
|
insn->len);
|
|
|
|
if (!text_rela || text_rela->sym != file->rodata->sym)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Make sure the .rodata address isn't associated with a
|
|
|
|
* symbol. gcc jump tables are anonymous data.
|
|
|
|
*/
|
|
|
|
if (find_symbol_containing(file->rodata, text_rela->addend))
|
|
|
|
continue;
|
|
|
|
|
2018-02-28 20:19:21 +07:00
|
|
|
rodata_rela = find_rela_by_dest(file->rodata, text_rela->addend);
|
|
|
|
if (!rodata_rela)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
return rodata_rela;
|
2017-06-28 22:11:05 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2018-02-08 20:02:32 +07:00
|
|
|
|
2017-06-28 22:11:05 +07:00
|
|
|
static int add_func_switch_tables(struct objtool_file *file,
|
|
|
|
struct symbol *func)
|
|
|
|
{
|
2018-02-08 20:02:32 +07:00
|
|
|
struct instruction *insn, *last = NULL, *prev_jump = NULL;
|
2017-06-28 22:11:05 +07:00
|
|
|
struct rela *rela, *prev_rela = NULL;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
func_for_each_insn(file, func, insn) {
|
2018-02-08 20:02:32 +07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-06-28 22:11:05 +07:00
|
|
|
if (insn->type != INSN_JUMP_DYNAMIC)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
rela = find_switch_table(file, func, insn);
|
|
|
|
if (!rela)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We found a switch table, but we don't know yet how big it
|
|
|
|
* is. Don't add it until we reach the end of the function or
|
|
|
|
* the beginning of another switch table in the same function.
|
|
|
|
*/
|
|
|
|
if (prev_jump) {
|
|
|
|
ret = add_switch_table(file, func, prev_jump, prev_rela,
|
|
|
|
rela);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
prev_jump = insn;
|
|
|
|
prev_rela = rela;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (prev_jump) {
|
|
|
|
ret = add_switch_table(file, func, prev_jump, prev_rela, NULL);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For some switch statements, gcc generates a jump table in the .rodata
|
|
|
|
* section which contains a list of addresses within the function to jump to.
|
|
|
|
* This finds these jump tables and adds them to the insn->alts lists.
|
|
|
|
*/
|
|
|
|
static int add_switch_table_alts(struct objtool_file *file)
|
|
|
|
{
|
|
|
|
struct section *sec;
|
|
|
|
struct symbol *func;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!file->rodata || !file->rodata->rela)
|
|
|
|
return 0;
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
for_each_sec(file, sec) {
|
2017-06-28 22:11:05 +07:00
|
|
|
list_for_each_entry(func, &sec->symbol_list, list) {
|
|
|
|
if (func->type != STT_FUNC)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
ret = add_func_switch_tables(file, func);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-07-11 22:33:43 +07:00
|
|
|
static int read_unwind_hints(struct objtool_file *file)
|
|
|
|
{
|
|
|
|
struct section *sec, *relasec;
|
|
|
|
struct rela *rela;
|
|
|
|
struct unwind_hint *hint;
|
|
|
|
struct instruction *insn;
|
|
|
|
struct cfi_reg *cfa;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
sec = find_section_by_name(file->elf, ".discard.unwind_hints");
|
|
|
|
if (!sec)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
relasec = sec->rela;
|
|
|
|
if (!relasec) {
|
|
|
|
WARN("missing .rela.discard.unwind_hints section");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sec->len % sizeof(struct unwind_hint)) {
|
|
|
|
WARN("struct unwind_hint size mismatch");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
file->hints = true;
|
|
|
|
|
|
|
|
for (i = 0; i < sec->len / sizeof(struct unwind_hint); i++) {
|
|
|
|
hint = (struct unwind_hint *)sec->data->d_buf + i;
|
|
|
|
|
|
|
|
rela = find_rela_by_dest(sec, i * sizeof(*hint));
|
|
|
|
if (!rela) {
|
|
|
|
WARN("can't find rela for unwind_hints[%d]", i);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
insn = find_insn(file, rela->sym->sec, rela->addend);
|
|
|
|
if (!insn) {
|
|
|
|
WARN("can't find insn for unwind_hints[%d]", i);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
cfa = &insn->state.cfa;
|
|
|
|
|
|
|
|
if (hint->type == UNWIND_HINT_TYPE_SAVE) {
|
|
|
|
insn->save = true;
|
|
|
|
continue;
|
|
|
|
|
|
|
|
} else if (hint->type == UNWIND_HINT_TYPE_RESTORE) {
|
|
|
|
insn->restore = true;
|
|
|
|
insn->hint = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
insn->hint = true;
|
|
|
|
|
|
|
|
switch (hint->sp_reg) {
|
|
|
|
case ORC_REG_UNDEFINED:
|
|
|
|
cfa->base = CFI_UNDEFINED;
|
|
|
|
break;
|
|
|
|
case ORC_REG_SP:
|
|
|
|
cfa->base = CFI_SP;
|
|
|
|
break;
|
|
|
|
case ORC_REG_BP:
|
|
|
|
cfa->base = CFI_BP;
|
|
|
|
break;
|
|
|
|
case ORC_REG_SP_INDIRECT:
|
|
|
|
cfa->base = CFI_SP_INDIRECT;
|
|
|
|
break;
|
|
|
|
case ORC_REG_R10:
|
|
|
|
cfa->base = CFI_R10;
|
|
|
|
break;
|
|
|
|
case ORC_REG_R13:
|
|
|
|
cfa->base = CFI_R13;
|
|
|
|
break;
|
|
|
|
case ORC_REG_DI:
|
|
|
|
cfa->base = CFI_DI;
|
|
|
|
break;
|
|
|
|
case ORC_REG_DX:
|
|
|
|
cfa->base = CFI_DX;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
WARN_FUNC("unsupported unwind_hint sp base reg %d",
|
|
|
|
insn->sec, insn->offset, hint->sp_reg);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
cfa->offset = hint->sp_offset;
|
|
|
|
insn->state.type = hint->type;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-01-16 16:24:06 +07:00
|
|
|
static int read_retpoline_hints(struct objtool_file *file)
|
|
|
|
{
|
2018-03-07 06:58:15 +07:00
|
|
|
struct section *sec;
|
2018-01-16 16:24:06 +07:00
|
|
|
struct instruction *insn;
|
|
|
|
struct rela *rela;
|
|
|
|
|
2018-03-07 06:58:15 +07:00
|
|
|
sec = find_section_by_name(file->elf, ".rela.discard.retpoline_safe");
|
2018-01-16 16:24:06 +07:00
|
|
|
if (!sec)
|
|
|
|
return 0;
|
|
|
|
|
2018-03-07 06:58:15 +07:00
|
|
|
list_for_each_entry(rela, &sec->rela_list, list) {
|
|
|
|
if (rela->sym->type != STT_SECTION) {
|
|
|
|
WARN("unexpected relocation symbol type in %s", sec->name);
|
2018-01-16 16:24:06 +07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
insn = find_insn(file, rela->sym->sec, rela->addend);
|
|
|
|
if (!insn) {
|
2018-03-07 06:58:15 +07:00
|
|
|
WARN("bad .discard.retpoline_safe entry");
|
2018-01-16 16:24:06 +07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (insn->type != INSN_JUMP_DYNAMIC &&
|
|
|
|
insn->type != INSN_CALL_DYNAMIC) {
|
2018-03-07 06:58:15 +07:00
|
|
|
WARN_FUNC("retpoline_safe hint not an indirect jump/call",
|
2018-01-16 16:24:06 +07:00
|
|
|
insn->sec, insn->offset);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
insn->retpoline_safe = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-06-28 22:11:05 +07:00
|
|
|
static int decode_sections(struct objtool_file *file)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = decode_instructions(file);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = add_dead_ends(file);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
add_ignores(file);
|
|
|
|
|
2018-01-12 04:46:24 +07:00
|
|
|
ret = add_nospec_ignores(file);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2017-06-28 22:11:05 +07:00
|
|
|
ret = add_jump_destinations(file);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2018-01-30 11:00:39 +07:00
|
|
|
ret = add_special_section_alts(file);
|
2017-06-28 22:11:05 +07:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2018-01-30 11:00:39 +07:00
|
|
|
ret = add_call_destinations(file);
|
2017-06-28 22:11:05 +07:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = add_switch_table_alts(file);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2017-07-11 22:33:43 +07:00
|
|
|
ret = read_unwind_hints(file);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2018-01-16 16:24:06 +07:00
|
|
|
ret = read_retpoline_hints(file);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2017-06-28 22:11:05 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool is_fentry_call(struct instruction *insn)
|
|
|
|
{
|
|
|
|
if (insn->type == INSN_CALL &&
|
|
|
|
insn->call_dest->type == STT_NOTYPE &&
|
|
|
|
!strcmp(insn->call_dest->name, "__fentry__"))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
static bool has_modified_stack_frame(struct insn_state *state)
|
2017-06-28 22:11:05 +07:00
|
|
|
{
|
2017-06-28 22:11:07 +07:00
|
|
|
int i;
|
|
|
|
|
|
|
|
if (state->cfa.base != initial_func_cfi.cfa.base ||
|
|
|
|
state->cfa.offset != initial_func_cfi.cfa.offset ||
|
|
|
|
state->stack_size != initial_func_cfi.cfa.offset ||
|
|
|
|
state->drap)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
for (i = 0; i < CFI_NUM_REGS; i++)
|
|
|
|
if (state->regs[i].base != initial_func_cfi.regs[i].base ||
|
|
|
|
state->regs[i].offset != initial_func_cfi.regs[i].offset)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool has_valid_stack_frame(struct insn_state *state)
|
|
|
|
{
|
|
|
|
if (state->cfa.base == CFI_BP && state->regs[CFI_BP].base == CFI_CFA &&
|
|
|
|
state->regs[CFI_BP].offset == -16)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if (state->drap && state->regs[CFI_BP].base == CFI_BP)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
2017-06-28 22:11:05 +07:00
|
|
|
}
|
|
|
|
|
2017-07-11 22:33:42 +07:00
|
|
|
static int update_insn_state_regs(struct instruction *insn, struct insn_state *state)
|
|
|
|
{
|
|
|
|
struct cfi_reg *cfa = &state->cfa;
|
|
|
|
struct stack_op *op = &insn->stack_op;
|
|
|
|
|
|
|
|
if (cfa->base != CFI_SP)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* push */
|
|
|
|
if (op->dest.type == OP_DEST_PUSH)
|
|
|
|
cfa->offset += 8;
|
|
|
|
|
|
|
|
/* pop */
|
|
|
|
if (op->src.type == OP_SRC_POP)
|
|
|
|
cfa->offset -= 8;
|
|
|
|
|
|
|
|
/* add immediate to sp */
|
|
|
|
if (op->dest.type == OP_DEST_REG && op->src.type == OP_SRC_ADD &&
|
|
|
|
op->dest.reg == CFI_SP && op->src.reg == CFI_SP)
|
|
|
|
cfa->offset -= op->src.offset;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
static void save_reg(struct insn_state *state, unsigned char reg, int base,
|
|
|
|
int offset)
|
2017-06-28 22:11:05 +07:00
|
|
|
{
|
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 04:37:26 +07:00
|
|
|
if (arch_callee_saved_reg(reg) &&
|
2017-06-28 22:11:07 +07:00
|
|
|
state->regs[reg].base == CFI_UNDEFINED) {
|
|
|
|
state->regs[reg].base = base;
|
|
|
|
state->regs[reg].offset = offset;
|
|
|
|
}
|
2017-06-28 22:11:05 +07:00
|
|
|
}
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
static void restore_reg(struct insn_state *state, unsigned char reg)
|
2017-06-28 22:11:05 +07:00
|
|
|
{
|
2017-06-28 22:11:07 +07:00
|
|
|
state->regs[reg].base = CFI_UNDEFINED;
|
|
|
|
state->regs[reg].offset = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A note about DRAP stack alignment:
|
|
|
|
*
|
|
|
|
* GCC has the concept of a DRAP register, which is used to help keep track of
|
|
|
|
* the stack pointer when aligning the stack. r10 or r13 is used as the DRAP
|
|
|
|
* register. The typical DRAP pattern is:
|
|
|
|
*
|
|
|
|
* 4c 8d 54 24 08 lea 0x8(%rsp),%r10
|
|
|
|
* 48 83 e4 c0 and $0xffffffffffffffc0,%rsp
|
|
|
|
* 41 ff 72 f8 pushq -0x8(%r10)
|
|
|
|
* 55 push %rbp
|
|
|
|
* 48 89 e5 mov %rsp,%rbp
|
|
|
|
* (more pushes)
|
|
|
|
* 41 52 push %r10
|
|
|
|
* ...
|
|
|
|
* 41 5a pop %r10
|
|
|
|
* (more pops)
|
|
|
|
* 5d pop %rbp
|
|
|
|
* 49 8d 62 f8 lea -0x8(%r10),%rsp
|
|
|
|
* c3 retq
|
|
|
|
*
|
|
|
|
* There are some variations in the epilogues, like:
|
|
|
|
*
|
|
|
|
* 5b pop %rbx
|
|
|
|
* 41 5a pop %r10
|
|
|
|
* 41 5c pop %r12
|
|
|
|
* 41 5d pop %r13
|
|
|
|
* 41 5e pop %r14
|
|
|
|
* c9 leaveq
|
|
|
|
* 49 8d 62 f8 lea -0x8(%r10),%rsp
|
|
|
|
* c3 retq
|
|
|
|
*
|
|
|
|
* and:
|
|
|
|
*
|
|
|
|
* 4c 8b 55 e8 mov -0x18(%rbp),%r10
|
|
|
|
* 48 8b 5d e0 mov -0x20(%rbp),%rbx
|
|
|
|
* 4c 8b 65 f0 mov -0x10(%rbp),%r12
|
|
|
|
* 4c 8b 6d f8 mov -0x8(%rbp),%r13
|
|
|
|
* c9 leaveq
|
|
|
|
* 49 8d 62 f8 lea -0x8(%r10),%rsp
|
|
|
|
* c3 retq
|
|
|
|
*
|
|
|
|
* Sometimes r13 is used as the DRAP register, in which case it's saved and
|
|
|
|
* restored beforehand:
|
|
|
|
*
|
|
|
|
* 41 55 push %r13
|
|
|
|
* 4c 8d 6c 24 10 lea 0x10(%rsp),%r13
|
|
|
|
* 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
|
|
|
|
* ...
|
|
|
|
* 49 8d 65 f0 lea -0x10(%r13),%rsp
|
|
|
|
* 41 5d pop %r13
|
|
|
|
* c3 retq
|
|
|
|
*/
|
|
|
|
static int update_insn_state(struct instruction *insn, struct insn_state *state)
|
|
|
|
{
|
|
|
|
struct stack_op *op = &insn->stack_op;
|
|
|
|
struct cfi_reg *cfa = &state->cfa;
|
|
|
|
struct cfi_reg *regs = state->regs;
|
|
|
|
|
|
|
|
/* stack operations don't make sense with an undefined CFA */
|
|
|
|
if (cfa->base == CFI_UNDEFINED) {
|
|
|
|
if (insn->func) {
|
|
|
|
WARN_FUNC("undefined stack state", insn->sec, insn->offset);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-07-11 22:33:42 +07:00
|
|
|
if (state->type == ORC_TYPE_REGS || state->type == ORC_TYPE_REGS_IRET)
|
|
|
|
return update_insn_state_regs(insn, state);
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
switch (op->dest.type) {
|
|
|
|
|
|
|
|
case OP_DEST_REG:
|
|
|
|
switch (op->src.type) {
|
|
|
|
|
|
|
|
case OP_SRC_REG:
|
2017-09-21 04:24:32 +07:00
|
|
|
if (op->src.reg == CFI_SP && op->dest.reg == CFI_BP &&
|
|
|
|
cfa->base == CFI_SP &&
|
|
|
|
regs[CFI_BP].base == CFI_CFA &&
|
|
|
|
regs[CFI_BP].offset == -cfa->offset) {
|
|
|
|
|
|
|
|
/* mov %rsp, %rbp */
|
|
|
|
cfa->base = op->dest.reg;
|
|
|
|
state->bp_scratch = false;
|
|
|
|
}
|
2017-08-30 00:51:03 +07:00
|
|
|
|
2017-09-21 04:24:32 +07:00
|
|
|
else if (op->src.reg == CFI_SP &&
|
|
|
|
op->dest.reg == CFI_BP && state->drap) {
|
2017-08-30 00:51:03 +07:00
|
|
|
|
2017-09-21 04:24:32 +07:00
|
|
|
/* drap: mov %rsp, %rbp */
|
|
|
|
regs[CFI_BP].base = CFI_BP;
|
|
|
|
regs[CFI_BP].offset = -state->stack_size;
|
|
|
|
state->bp_scratch = false;
|
|
|
|
}
|
2017-08-30 00:51:03 +07:00
|
|
|
|
2017-09-21 04:24:32 +07:00
|
|
|
else if (op->src.reg == CFI_SP && cfa->base == CFI_SP) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
* mov %rsp, %reg
|
|
|
|
*
|
|
|
|
* This is needed for the rare case where GCC
|
|
|
|
* does:
|
|
|
|
*
|
|
|
|
* mov %rsp, %rax
|
|
|
|
* ...
|
|
|
|
* mov %rax, %rsp
|
|
|
|
*/
|
|
|
|
state->vals[op->dest.reg].base = CFI_CFA;
|
|
|
|
state->vals[op->dest.reg].offset = -state->stack_size;
|
2017-08-30 00:51:03 +07:00
|
|
|
}
|
|
|
|
|
2018-03-23 01:00:37 +07:00
|
|
|
else if (op->src.reg == CFI_BP && op->dest.reg == CFI_SP &&
|
|
|
|
cfa->base == CFI_BP) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
* mov %rbp, %rsp
|
|
|
|
*
|
|
|
|
* Restore the original stack pointer (Clang).
|
|
|
|
*/
|
|
|
|
state->stack_size = -state->regs[CFI_BP].offset;
|
|
|
|
}
|
|
|
|
|
2017-08-30 00:51:03 +07:00
|
|
|
else if (op->dest.reg == cfa->base) {
|
|
|
|
|
|
|
|
/* mov %reg, %rsp */
|
|
|
|
if (cfa->base == CFI_SP &&
|
|
|
|
state->vals[op->src.reg].base == CFI_CFA) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This is needed for the rare case
|
|
|
|
* where GCC does something dumb like:
|
|
|
|
*
|
|
|
|
* lea 0x8(%rsp), %rcx
|
|
|
|
* ...
|
|
|
|
* mov %rcx, %rsp
|
|
|
|
*/
|
|
|
|
cfa->offset = -state->vals[op->src.reg].offset;
|
|
|
|
state->stack_size = cfa->offset;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
cfa->base = CFI_UNDEFINED;
|
|
|
|
cfa->offset = 0;
|
|
|
|
}
|
2017-06-28 22:11:07 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case OP_SRC_ADD:
|
|
|
|
if (op->dest.reg == CFI_SP && op->src.reg == CFI_SP) {
|
|
|
|
|
|
|
|
/* add imm, %rsp */
|
|
|
|
state->stack_size -= op->src.offset;
|
|
|
|
if (cfa->base == CFI_SP)
|
|
|
|
cfa->offset -= op->src.offset;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (op->dest.reg == CFI_SP && op->src.reg == CFI_BP) {
|
|
|
|
|
|
|
|
/* lea disp(%rbp), %rsp */
|
|
|
|
state->stack_size = -(op->src.offset + regs[CFI_BP].offset);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-08-30 00:51:03 +07:00
|
|
|
if (op->src.reg == CFI_SP && cfa->base == CFI_SP) {
|
2017-06-28 22:11:07 +07:00
|
|
|
|
|
|
|
/* drap: lea disp(%rsp), %drap */
|
|
|
|
state->drap_reg = op->dest.reg;
|
2017-08-30 00:51:03 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* lea disp(%rsp), %reg
|
|
|
|
*
|
|
|
|
* This is needed for the rare case where GCC
|
|
|
|
* does something dumb like:
|
|
|
|
*
|
|
|
|
* lea 0x8(%rsp), %rcx
|
|
|
|
* ...
|
|
|
|
* mov %rcx, %rsp
|
|
|
|
*/
|
|
|
|
state->vals[op->dest.reg].base = CFI_CFA;
|
|
|
|
state->vals[op->dest.reg].offset = \
|
|
|
|
-state->stack_size + op->src.offset;
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state->drap && op->dest.reg == CFI_SP &&
|
|
|
|
op->src.reg == state->drap_reg) {
|
|
|
|
|
|
|
|
/* drap: lea disp(%drap), %rsp */
|
|
|
|
cfa->base = CFI_SP;
|
|
|
|
cfa->offset = state->stack_size = -op->src.offset;
|
|
|
|
state->drap_reg = CFI_UNDEFINED;
|
|
|
|
state->drap = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (op->dest.reg == state->cfa.base) {
|
|
|
|
WARN_FUNC("unsupported stack register modification",
|
|
|
|
insn->sec, insn->offset);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case OP_SRC_AND:
|
|
|
|
if (op->dest.reg != CFI_SP ||
|
|
|
|
(state->drap_reg != CFI_UNDEFINED && cfa->base != CFI_SP) ||
|
|
|
|
(state->drap_reg == CFI_UNDEFINED && cfa->base != CFI_BP)) {
|
|
|
|
WARN_FUNC("unsupported stack pointer realignment",
|
|
|
|
insn->sec, insn->offset);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state->drap_reg != CFI_UNDEFINED) {
|
|
|
|
/* drap: and imm, %rsp */
|
|
|
|
cfa->base = state->drap_reg;
|
|
|
|
cfa->offset = state->stack_size = 0;
|
|
|
|
state->drap = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Older versions of GCC (4.8ish) realign the stack
|
|
|
|
* without DRAP, with a frame pointer.
|
|
|
|
*/
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case OP_SRC_POP:
|
|
|
|
if (!state->drap && op->dest.type == OP_DEST_REG &&
|
|
|
|
op->dest.reg == cfa->base) {
|
|
|
|
|
|
|
|
/* pop %rbp */
|
|
|
|
cfa->base = CFI_SP;
|
|
|
|
}
|
|
|
|
|
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 04:37:26 +07:00
|
|
|
if (state->drap && cfa->base == CFI_BP_INDIRECT &&
|
|
|
|
op->dest.type == OP_DEST_REG &&
|
|
|
|
op->dest.reg == state->drap_reg &&
|
|
|
|
state->drap_offset == -state->stack_size) {
|
2017-06-28 22:11:07 +07:00
|
|
|
|
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 04:37:26 +07:00
|
|
|
/* drap: pop %drap */
|
|
|
|
cfa->base = state->drap_reg;
|
|
|
|
cfa->offset = 0;
|
|
|
|
state->drap_offset = -1;
|
2017-06-28 22:11:07 +07:00
|
|
|
|
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 04:37:26 +07:00
|
|
|
} else if (regs[op->dest.reg].offset == -state->stack_size) {
|
2017-06-28 22:11:07 +07:00
|
|
|
|
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 04:37:26 +07:00
|
|
|
/* pop %reg */
|
2017-06-28 22:11:07 +07:00
|
|
|
restore_reg(state, op->dest.reg);
|
|
|
|
}
|
|
|
|
|
|
|
|
state->stack_size -= 8;
|
|
|
|
if (cfa->base == CFI_SP)
|
|
|
|
cfa->offset -= 8;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case OP_SRC_REG_INDIRECT:
|
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 04:37:26 +07:00
|
|
|
if (state->drap && op->src.reg == CFI_BP &&
|
|
|
|
op->src.offset == state->drap_offset) {
|
|
|
|
|
|
|
|
/* drap: mov disp(%rbp), %drap */
|
|
|
|
cfa->base = state->drap_reg;
|
|
|
|
cfa->offset = 0;
|
|
|
|
state->drap_offset = -1;
|
|
|
|
}
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
if (state->drap && op->src.reg == CFI_BP &&
|
|
|
|
op->src.offset == regs[op->dest.reg].offset) {
|
|
|
|
|
|
|
|
/* drap: mov disp(%rbp), %reg */
|
|
|
|
restore_reg(state, op->dest.reg);
|
|
|
|
|
|
|
|
} else if (op->src.reg == cfa->base &&
|
|
|
|
op->src.offset == regs[op->dest.reg].offset + cfa->offset) {
|
|
|
|
|
|
|
|
/* mov disp(%rbp), %reg */
|
|
|
|
/* mov disp(%rsp), %reg */
|
|
|
|
restore_reg(state, op->dest.reg);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
WARN_FUNC("unknown stack-related instruction",
|
|
|
|
insn->sec, insn->offset);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case OP_DEST_PUSH:
|
|
|
|
state->stack_size += 8;
|
|
|
|
if (cfa->base == CFI_SP)
|
|
|
|
cfa->offset += 8;
|
|
|
|
|
|
|
|
if (op->src.type != OP_SRC_REG)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (state->drap) {
|
|
|
|
if (op->src.reg == cfa->base && op->src.reg == state->drap_reg) {
|
|
|
|
|
|
|
|
/* drap: push %drap */
|
|
|
|
cfa->base = CFI_BP_INDIRECT;
|
|
|
|
cfa->offset = -state->stack_size;
|
|
|
|
|
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 04:37:26 +07:00
|
|
|
/* save drap so we know when to restore it */
|
|
|
|
state->drap_offset = -state->stack_size;
|
2017-06-28 22:11:07 +07:00
|
|
|
|
|
|
|
} else if (op->src.reg == CFI_BP && cfa->base == state->drap_reg) {
|
|
|
|
|
|
|
|
/* drap: push %rbp */
|
|
|
|
state->stack_size = 0;
|
|
|
|
|
|
|
|
} else if (regs[op->src.reg].base == CFI_UNDEFINED) {
|
|
|
|
|
|
|
|
/* drap: push %reg */
|
|
|
|
save_reg(state, op->src.reg, CFI_BP, -state->stack_size);
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
/* push %reg */
|
|
|
|
save_reg(state, op->src.reg, CFI_CFA, -state->stack_size);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* detect when asm code uses rbp as a scratch register */
|
2017-07-25 06:34:14 +07:00
|
|
|
if (!no_fp && insn->func && op->src.reg == CFI_BP &&
|
2017-06-28 22:11:07 +07:00
|
|
|
cfa->base != CFI_BP)
|
|
|
|
state->bp_scratch = true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case OP_DEST_REG_INDIRECT:
|
|
|
|
|
|
|
|
if (state->drap) {
|
|
|
|
if (op->src.reg == cfa->base && op->src.reg == state->drap_reg) {
|
|
|
|
|
|
|
|
/* drap: mov %drap, disp(%rbp) */
|
|
|
|
cfa->base = CFI_BP_INDIRECT;
|
|
|
|
cfa->offset = op->dest.offset;
|
|
|
|
|
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 04:37:26 +07:00
|
|
|
/* save drap offset so we know when to restore it */
|
|
|
|
state->drap_offset = op->dest.offset;
|
2017-06-28 22:11:07 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
else if (regs[op->src.reg].base == CFI_UNDEFINED) {
|
|
|
|
|
|
|
|
/* drap: mov reg, disp(%rbp) */
|
|
|
|
save_reg(state, op->src.reg, CFI_BP, op->dest.offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (op->dest.reg == cfa->base) {
|
|
|
|
|
|
|
|
/* mov reg, disp(%rbp) */
|
|
|
|
/* mov reg, disp(%rsp) */
|
|
|
|
save_reg(state, op->src.reg, CFI_CFA,
|
|
|
|
op->dest.offset - state->cfa.offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case OP_DEST_LEAVE:
|
|
|
|
if ((!state->drap && cfa->base != CFI_BP) ||
|
|
|
|
(state->drap && cfa->base != state->drap_reg)) {
|
|
|
|
WARN_FUNC("leave instruction with modified stack frame",
|
|
|
|
insn->sec, insn->offset);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* leave (mov %rbp, %rsp; pop %rbp) */
|
|
|
|
|
|
|
|
state->stack_size = -state->regs[CFI_BP].offset - 8;
|
|
|
|
restore_reg(state, CFI_BP);
|
|
|
|
|
|
|
|
if (!state->drap) {
|
|
|
|
cfa->base = CFI_SP;
|
|
|
|
cfa->offset -= 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case OP_DEST_MEM:
|
|
|
|
if (op->src.type != OP_SRC_POP) {
|
|
|
|
WARN_FUNC("unknown stack-related memory operation",
|
|
|
|
insn->sec, insn->offset);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* pop mem */
|
|
|
|
state->stack_size -= 8;
|
|
|
|
if (cfa->base == CFI_SP)
|
|
|
|
cfa->offset -= 8;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
WARN_FUNC("unknown stack-related instruction",
|
|
|
|
insn->sec, insn->offset);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool insn_state_match(struct instruction *insn, struct insn_state *state)
|
|
|
|
{
|
|
|
|
struct insn_state *state1 = &insn->state, *state2 = state;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (memcmp(&state1->cfa, &state2->cfa, sizeof(state1->cfa))) {
|
|
|
|
WARN_FUNC("stack state mismatch: cfa1=%d%+d cfa2=%d%+d",
|
|
|
|
insn->sec, insn->offset,
|
|
|
|
state1->cfa.base, state1->cfa.offset,
|
|
|
|
state2->cfa.base, state2->cfa.offset);
|
|
|
|
|
|
|
|
} else if (memcmp(&state1->regs, &state2->regs, sizeof(state1->regs))) {
|
|
|
|
for (i = 0; i < CFI_NUM_REGS; i++) {
|
|
|
|
if (!memcmp(&state1->regs[i], &state2->regs[i],
|
|
|
|
sizeof(struct cfi_reg)))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
WARN_FUNC("stack state mismatch: reg1[%d]=%d%+d reg2[%d]=%d%+d",
|
|
|
|
insn->sec, insn->offset,
|
|
|
|
i, state1->regs[i].base, state1->regs[i].offset,
|
|
|
|
i, state2->regs[i].base, state2->regs[i].offset);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-07-11 22:33:42 +07:00
|
|
|
} else if (state1->type != state2->type) {
|
|
|
|
WARN_FUNC("stack state mismatch: type1=%d type2=%d",
|
|
|
|
insn->sec, insn->offset, state1->type, state2->type);
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
} else if (state1->drap != state2->drap ||
|
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 04:37:26 +07:00
|
|
|
(state1->drap && state1->drap_reg != state2->drap_reg) ||
|
|
|
|
(state1->drap && state1->drap_offset != state2->drap_offset)) {
|
|
|
|
WARN_FUNC("stack state mismatch: drap1=%d(%d,%d) drap2=%d(%d,%d)",
|
2017-06-28 22:11:07 +07:00
|
|
|
insn->sec, insn->offset,
|
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 04:37:26 +07:00
|
|
|
state1->drap, state1->drap_reg, state1->drap_offset,
|
|
|
|
state2->drap, state2->drap_reg, state2->drap_offset);
|
2017-06-28 22:11:07 +07:00
|
|
|
|
|
|
|
} else
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
2017-06-28 22:11:05 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Follow the branch starting at the given instruction, and recursively follow
|
|
|
|
* any other branches (jumps). Meanwhile, track the frame pointer state at
|
|
|
|
* each instruction and validate all the rules described in
|
|
|
|
* tools/objtool/Documentation/stack-validation.txt.
|
|
|
|
*/
|
2017-06-28 22:11:07 +07:00
|
|
|
static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
|
|
struct insn_state state)
|
2017-06-28 22:11:05 +07:00
|
|
|
{
|
|
|
|
struct alternative *alt;
|
2017-07-11 22:33:43 +07:00
|
|
|
struct instruction *insn, *next_insn;
|
2017-06-28 22:11:05 +07:00
|
|
|
struct section *sec;
|
|
|
|
struct symbol *func = NULL;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
insn = first;
|
|
|
|
sec = insn->sec;
|
|
|
|
|
|
|
|
if (insn->alt_group && list_empty(&insn->alts)) {
|
|
|
|
WARN_FUNC("don't know how to handle branch to middle of alternative instruction group",
|
|
|
|
sec, insn->offset);
|
2017-08-11 04:37:25 +07:00
|
|
|
return 1;
|
2017-06-28 22:11:05 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
while (1) {
|
2017-07-11 22:33:43 +07:00
|
|
|
next_insn = next_insn_same_sec(file, insn);
|
|
|
|
|
2017-08-12 00:24:15 +07:00
|
|
|
|
|
|
|
if (file->c_file && func && insn->func && func != insn->func) {
|
|
|
|
WARN("%s() falls through to next function %s()",
|
|
|
|
func->name, insn->func->name);
|
|
|
|
return 1;
|
2017-06-28 22:11:05 +07:00
|
|
|
}
|
|
|
|
|
2017-08-12 00:24:15 +07:00
|
|
|
if (insn->func)
|
|
|
|
func = insn->func;
|
2017-06-28 22:11:07 +07:00
|
|
|
|
2017-07-07 21:19:42 +07:00
|
|
|
if (func && insn->ignore) {
|
|
|
|
WARN_FUNC("BUG: why am I validating an ignored function?",
|
|
|
|
sec, insn->offset);
|
2017-08-11 04:37:25 +07:00
|
|
|
return 1;
|
2017-07-07 21:19:42 +07:00
|
|
|
}
|
|
|
|
|
2017-06-28 22:11:05 +07:00
|
|
|
if (insn->visited) {
|
2017-07-11 22:33:43 +07:00
|
|
|
if (!insn->hint && !insn_state_match(insn, &state))
|
2017-06-28 22:11:05 +07:00
|
|
|
return 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-07-11 22:33:43 +07:00
|
|
|
if (insn->hint) {
|
|
|
|
if (insn->restore) {
|
|
|
|
struct instruction *save_insn, *i;
|
|
|
|
|
|
|
|
i = insn;
|
|
|
|
save_insn = NULL;
|
|
|
|
func_for_each_insn_continue_reverse(file, func, i) {
|
|
|
|
if (i->save) {
|
|
|
|
save_insn = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!save_insn) {
|
|
|
|
WARN_FUNC("no corresponding CFI save for CFI restore",
|
|
|
|
sec, insn->offset);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!save_insn->visited) {
|
|
|
|
/*
|
|
|
|
* Oops, no state to copy yet.
|
|
|
|
* Hopefully we can reach this
|
|
|
|
* instruction from another branch
|
|
|
|
* after the save insn has been
|
|
|
|
* visited.
|
|
|
|
*/
|
|
|
|
if (insn == first)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
WARN_FUNC("objtool isn't smart enough to handle this CFI save/restore combo",
|
|
|
|
sec, insn->offset);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
insn->state = save_insn->state;
|
|
|
|
}
|
|
|
|
|
|
|
|
state = insn->state;
|
|
|
|
|
|
|
|
} else
|
|
|
|
insn->state = state;
|
2017-06-28 22:11:05 +07:00
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
insn->visited = true;
|
|
|
|
|
2018-01-30 11:00:39 +07:00
|
|
|
if (!insn->ignore_alts) {
|
|
|
|
list_for_each_entry(alt, &insn->alts, list) {
|
|
|
|
ret = validate_branch(file, alt->insn, state);
|
|
|
|
if (ret)
|
|
|
|
return 1;
|
|
|
|
}
|
2017-06-28 22:11:05 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
switch (insn->type) {
|
|
|
|
|
|
|
|
case INSN_RETURN:
|
2017-06-28 22:11:07 +07:00
|
|
|
if (func && has_modified_stack_frame(&state)) {
|
|
|
|
WARN_FUNC("return with modified stack frame",
|
2017-06-28 22:11:05 +07:00
|
|
|
sec, insn->offset);
|
|
|
|
return 1;
|
|
|
|
}
|
2017-06-28 22:11:07 +07:00
|
|
|
|
|
|
|
if (state.bp_scratch) {
|
|
|
|
WARN("%s uses BP as a scratch register",
|
|
|
|
insn->func->name);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2017-06-28 22:11:05 +07:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
case INSN_CALL:
|
2017-06-28 22:11:07 +07:00
|
|
|
if (is_fentry_call(insn))
|
2017-06-28 22:11:05 +07:00
|
|
|
break;
|
|
|
|
|
|
|
|
ret = dead_end_function(file, insn->call_dest);
|
|
|
|
if (ret == 1)
|
|
|
|
return 0;
|
|
|
|
if (ret == -1)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
/* fallthrough */
|
|
|
|
case INSN_CALL_DYNAMIC:
|
2017-07-25 06:34:14 +07:00
|
|
|
if (!no_fp && func && !has_valid_stack_frame(&state)) {
|
2017-06-28 22:11:05 +07:00
|
|
|
WARN_FUNC("call without frame pointer save/setup",
|
|
|
|
sec, insn->offset);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case INSN_JUMP_CONDITIONAL:
|
|
|
|
case INSN_JUMP_UNCONDITIONAL:
|
2017-07-07 21:19:42 +07:00
|
|
|
if (insn->jump_dest &&
|
|
|
|
(!func || !insn->jump_dest->func ||
|
|
|
|
func == insn->jump_dest->func)) {
|
2017-06-28 22:11:05 +07:00
|
|
|
ret = validate_branch(file, insn->jump_dest,
|
|
|
|
state);
|
|
|
|
if (ret)
|
|
|
|
return 1;
|
2017-07-07 21:19:42 +07:00
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
} else if (func && has_modified_stack_frame(&state)) {
|
|
|
|
WARN_FUNC("sibling call from callable instruction with modified stack frame",
|
2017-06-28 22:11:05 +07:00
|
|
|
sec, insn->offset);
|
|
|
|
return 1;
|
2017-07-07 21:19:42 +07:00
|
|
|
}
|
2017-06-28 22:11:05 +07:00
|
|
|
|
|
|
|
if (insn->type == INSN_JUMP_UNCONDITIONAL)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case INSN_JUMP_DYNAMIC:
|
2017-06-28 22:11:07 +07:00
|
|
|
if (func && list_empty(&insn->alts) &&
|
|
|
|
has_modified_stack_frame(&state)) {
|
|
|
|
WARN_FUNC("sibling call from callable instruction with modified stack frame",
|
2017-06-28 22:11:05 +07:00
|
|
|
sec, insn->offset);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
2017-07-11 22:33:43 +07:00
|
|
|
case INSN_CONTEXT_SWITCH:
|
|
|
|
if (func && (!next_insn || !next_insn->hint)) {
|
|
|
|
WARN_FUNC("unsupported instruction in callable function",
|
|
|
|
sec, insn->offset);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
case INSN_STACK:
|
|
|
|
if (update_insn_state(insn, &state))
|
2017-08-11 04:37:25 +07:00
|
|
|
return 1;
|
2017-06-28 22:11:07 +07:00
|
|
|
|
|
|
|
break;
|
|
|
|
|
2017-06-28 22:11:05 +07:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (insn->dead_end)
|
|
|
|
return 0;
|
|
|
|
|
2017-09-19 09:43:30 +07:00
|
|
|
if (!next_insn) {
|
|
|
|
if (state.cfa.base == CFI_UNDEFINED)
|
|
|
|
return 0;
|
2017-06-28 22:11:05 +07:00
|
|
|
WARN("%s: unexpected end of section", sec->name);
|
|
|
|
return 1;
|
|
|
|
}
|
2017-09-19 09:43:30 +07:00
|
|
|
|
|
|
|
insn = next_insn;
|
2017-06-28 22:11:05 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-07-11 22:33:43 +07:00
|
|
|
static int validate_unwind_hints(struct objtool_file *file)
|
|
|
|
{
|
|
|
|
struct instruction *insn;
|
|
|
|
int ret, warnings = 0;
|
|
|
|
struct insn_state state;
|
|
|
|
|
|
|
|
if (!file->hints)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
clear_insn_state(&state);
|
|
|
|
|
|
|
|
for_each_insn(file, insn) {
|
|
|
|
if (insn->hint && !insn->visited) {
|
|
|
|
ret = validate_branch(file, insn, state);
|
|
|
|
warnings += ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return warnings;
|
|
|
|
}
|
|
|
|
|
2018-01-16 16:24:06 +07:00
|
|
|
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;
|
|
|
|
|
2018-01-31 16:18:28 +07:00
|
|
|
/*
|
|
|
|
* .init.text code is ran before userspace and thus doesn't
|
|
|
|
* strictly need retpolines, except for modules which are
|
|
|
|
* loaded late, they very much do need retpoline in their
|
|
|
|
* .init.text
|
|
|
|
*/
|
|
|
|
if (!strcmp(insn->sec->name, ".init.text") && !module)
|
|
|
|
continue;
|
|
|
|
|
2018-01-16 16:24:06 +07:00
|
|
|
WARN_FUNC("indirect %s found in RETPOLINE build",
|
|
|
|
insn->sec, insn->offset,
|
|
|
|
insn->type == INSN_JUMP_DYNAMIC ? "jump" : "call");
|
|
|
|
|
|
|
|
warnings++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return warnings;
|
|
|
|
}
|
|
|
|
|
2017-06-28 22:11:05 +07:00
|
|
|
static bool is_kasan_insn(struct instruction *insn)
|
|
|
|
{
|
|
|
|
return (insn->type == INSN_CALL &&
|
|
|
|
!strcmp(insn->call_dest->name, "__asan_handle_no_return"));
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool is_ubsan_insn(struct instruction *insn)
|
|
|
|
{
|
|
|
|
return (insn->type == INSN_CALL &&
|
|
|
|
!strcmp(insn->call_dest->name,
|
|
|
|
"__ubsan_handle_builtin_unreachable"));
|
|
|
|
}
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
static bool ignore_unreachable_insn(struct instruction *insn)
|
2017-06-28 22:11:05 +07:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
if (insn->ignore || insn->type == INSN_NOP)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Ignore any unused exceptions. This can happen when a whitelisted
|
|
|
|
* function has an exception table entry.
|
2017-07-28 03:56:54 +07:00
|
|
|
*
|
|
|
|
* Also ignore alternative replacement instructions. This can happen
|
|
|
|
* when a whitelisted function uses one of the ALTERNATIVE macros.
|
2017-06-28 22:11:07 +07:00
|
|
|
*/
|
2017-07-28 03:56:54 +07:00
|
|
|
if (!strcmp(insn->sec->name, ".fixup") ||
|
|
|
|
!strcmp(insn->sec->name, ".altinstr_replacement") ||
|
|
|
|
!strcmp(insn->sec->name, ".altinstr_aux"))
|
2017-06-28 22:11:05 +07:00
|
|
|
return true;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check if this (or a subsequent) instruction is related to
|
|
|
|
* CONFIG_UBSAN or CONFIG_KASAN.
|
|
|
|
*
|
|
|
|
* End the search at 5 instructions to avoid going into the weeds.
|
|
|
|
*/
|
2017-06-28 22:11:07 +07:00
|
|
|
if (!insn->func)
|
|
|
|
return false;
|
2017-06-28 22:11:05 +07:00
|
|
|
for (i = 0; i < 5; i++) {
|
|
|
|
|
|
|
|
if (is_kasan_insn(insn) || is_ubsan_insn(insn))
|
|
|
|
return true;
|
|
|
|
|
2018-02-09 06:09:25 +07:00
|
|
|
if (insn->type == INSN_JUMP_UNCONDITIONAL) {
|
|
|
|
if (insn->jump_dest &&
|
|
|
|
insn->jump_dest->func == insn->func) {
|
|
|
|
insn = insn->jump_dest;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
2017-06-28 22:11:05 +07:00
|
|
|
}
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
if (insn->offset + insn->len >= insn->func->offset + insn->func->len)
|
2017-06-28 22:11:05 +07:00
|
|
|
break;
|
2018-02-09 06:09:25 +07:00
|
|
|
|
2017-06-28 22:11:05 +07:00
|
|
|
insn = list_next_entry(insn, list);
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int validate_functions(struct objtool_file *file)
|
|
|
|
{
|
|
|
|
struct section *sec;
|
|
|
|
struct symbol *func;
|
|
|
|
struct instruction *insn;
|
2017-06-28 22:11:07 +07:00
|
|
|
struct insn_state state;
|
2017-06-28 22:11:05 +07:00
|
|
|
int ret, warnings = 0;
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
clear_insn_state(&state);
|
|
|
|
|
|
|
|
state.cfa = initial_func_cfi.cfa;
|
|
|
|
memcpy(&state.regs, &initial_func_cfi.regs,
|
|
|
|
CFI_NUM_REGS * sizeof(struct cfi_reg));
|
|
|
|
state.stack_size = initial_func_cfi.cfa.offset;
|
|
|
|
|
|
|
|
for_each_sec(file, sec) {
|
2017-06-28 22:11:05 +07:00
|
|
|
list_for_each_entry(func, &sec->symbol_list, list) {
|
|
|
|
if (func->type != STT_FUNC)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
insn = find_insn(file, sec, func->offset);
|
2017-06-28 22:11:07 +07:00
|
|
|
if (!insn || insn->ignore)
|
2017-06-28 22:11:05 +07:00
|
|
|
continue;
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
ret = validate_branch(file, insn, state);
|
2017-06-28 22:11:05 +07:00
|
|
|
warnings += ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return warnings;
|
|
|
|
}
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
static int validate_reachable_instructions(struct objtool_file *file)
|
2017-06-28 22:11:05 +07:00
|
|
|
{
|
|
|
|
struct instruction *insn;
|
2017-06-28 22:11:07 +07:00
|
|
|
|
|
|
|
if (file->ignore_unreachables)
|
|
|
|
return 0;
|
2017-06-28 22:11:05 +07:00
|
|
|
|
|
|
|
for_each_insn(file, insn) {
|
2017-06-28 22:11:07 +07:00
|
|
|
if (insn->visited || ignore_unreachable_insn(insn))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
WARN_FUNC("unreachable instruction", insn->sec, insn->offset);
|
|
|
|
return 1;
|
2017-06-28 22:11:05 +07:00
|
|
|
}
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
return 0;
|
2017-06-28 22:11:05 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void cleanup(struct objtool_file *file)
|
|
|
|
{
|
|
|
|
struct instruction *insn, *tmpinsn;
|
|
|
|
struct alternative *alt, *tmpalt;
|
|
|
|
|
|
|
|
list_for_each_entry_safe(insn, tmpinsn, &file->insn_list, list) {
|
|
|
|
list_for_each_entry_safe(alt, tmpalt, &insn->alts, list) {
|
|
|
|
list_del(&alt->list);
|
|
|
|
free(alt);
|
|
|
|
}
|
|
|
|
list_del(&insn->list);
|
|
|
|
hash_del(&insn->hash);
|
|
|
|
free(insn);
|
|
|
|
}
|
|
|
|
elf_close(file->elf);
|
|
|
|
}
|
|
|
|
|
2018-01-16 23:16:32 +07:00
|
|
|
int check(const char *_objname, bool orc)
|
2017-06-28 22:11:05 +07:00
|
|
|
{
|
|
|
|
struct objtool_file file;
|
|
|
|
int ret, warnings = 0;
|
|
|
|
|
|
|
|
objname = _objname;
|
|
|
|
|
2017-07-11 22:33:42 +07:00
|
|
|
file.elf = elf_open(objname, orc ? O_RDWR : O_RDONLY);
|
2017-06-28 22:11:07 +07:00
|
|
|
if (!file.elf)
|
2017-06-28 22:11:05 +07:00
|
|
|
return 1;
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&file.insn_list);
|
|
|
|
hash_init(file.insn_hash);
|
|
|
|
file.whitelist = find_section_by_name(file.elf, ".discard.func_stack_frame_non_standard");
|
|
|
|
file.rodata = find_section_by_name(file.elf, ".rodata");
|
|
|
|
file.c_file = find_section_by_name(file.elf, ".comment");
|
2017-07-25 06:34:14 +07:00
|
|
|
file.ignore_unreachables = no_unreachable;
|
2017-07-11 22:33:43 +07:00
|
|
|
file.hints = false;
|
2017-06-28 22:11:05 +07:00
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
arch_initial_func_cfi_state(&initial_func_cfi);
|
|
|
|
|
2017-06-28 22:11:05 +07:00
|
|
|
ret = decode_sections(&file);
|
|
|
|
if (ret < 0)
|
|
|
|
goto out;
|
|
|
|
warnings += ret;
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
if (list_empty(&file.insn_list))
|
2017-06-28 22:11:05 +07:00
|
|
|
goto out;
|
|
|
|
|
2018-01-16 16:24:06 +07:00
|
|
|
if (retpoline) {
|
|
|
|
ret = validate_retpoline(&file);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
warnings += ret;
|
|
|
|
}
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
ret = validate_functions(&file);
|
2017-06-28 22:11:05 +07:00
|
|
|
if (ret < 0)
|
|
|
|
goto out;
|
|
|
|
warnings += ret;
|
|
|
|
|
2017-07-11 22:33:43 +07:00
|
|
|
ret = validate_unwind_hints(&file);
|
|
|
|
if (ret < 0)
|
|
|
|
goto out;
|
|
|
|
warnings += ret;
|
|
|
|
|
2017-06-28 22:11:07 +07:00
|
|
|
if (!warnings) {
|
|
|
|
ret = validate_reachable_instructions(&file);
|
|
|
|
if (ret < 0)
|
|
|
|
goto out;
|
|
|
|
warnings += ret;
|
|
|
|
}
|
|
|
|
|
2017-07-11 22:33:42 +07:00
|
|
|
if (orc) {
|
|
|
|
ret = create_orc(&file);
|
|
|
|
if (ret < 0)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
ret = create_orc_sections(&file);
|
|
|
|
if (ret < 0)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
ret = elf_write(file.elf);
|
|
|
|
if (ret < 0)
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2017-06-28 22:11:05 +07:00
|
|
|
out:
|
|
|
|
cleanup(&file);
|
|
|
|
|
|
|
|
/* ignore warnings for now until we get all the code cleaned up */
|
|
|
|
if (ret || warnings)
|
|
|
|
return 0;
|
|
|
|
return 0;
|
|
|
|
}
|