/* * 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> #include "builtin.h"#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;struct cfi_state initial_func_cfi; struct instruction *find_insn(struct objtool_file *file, struct section *sec, unsigned long offset){ 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); if (!next || &next->list == &file->insn_list || next->sec != insn->sec) 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)) #define sec_for_each_insn_continue(file, insn) \ for (insn = next_insn_same_sec(file, insn); insn; \ insn = next_insn_same_sec(file, insn)) /* * 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", "usercopy_abort", }; 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);} static void clear_insn_state(struct insn_state *state){ int i; memset(state, 0, sizeof(*state)); state->cfa.base = CFI_UNDEFINED; for (i = 0; i < CFI_NUM_REGS; i++) { state->regs[i].base = CFI_UNDEFINED; state->vals[i].base = CFI_UNDEFINED; } state->drap_reg = CFI_UNDEFINED; state->drap_offset = -1;} /* * 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; for_each_sec(file, sec) { if (!(sec->sh.sh_flags & SHF_EXECINSTR)) continue; if (strcmp(sec->name, ".altinstr_replacement") && strcmp(sec->name, ".altinstr_aux") && strncmp(sec->name, ".discard.", 9)) sec->text = true; for (offset = 0; offset < sec->len; offset += insn->len) { insn = malloc(sizeof(*insn)); if (!insn) { WARN("malloc failed"); return -1; } memset(insn, 0, sizeof(*insn)); INIT_LIST_HEAD(&insn->alts); clear_insn_state(&insn->state); insn->sec = sec; insn->offset = offset; ret = arch_decode_instruction(file->elf, sec, offset, sec->len - offset, &insn->len, &insn->type, &insn->immediate, &insn->stack_op); if (ret) goto err; if (!insn->type || insn->type > INSN_LAST) { WARN_FUNC("invalid instruction type %d", insn->sec, insn->offset, insn->type); ret = -1; goto err; } 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; err: free(insn); return ret;} /* * Mark "ud2" instructions and manually annotated dead ends. */static int add_dead_ends(struct objtool_file *file){ struct section *sec; struct rela *rela; struct instruction *insn; bool found; /* * 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. */ sec = find_section_by_name(file->elf, ".rela.discard.unreachable"); if (!sec) goto reachable; 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; } 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; } 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; for_each_sec(file, sec) { 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) insn->ignore = true; } }} /* * 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;} /* * 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; if (insn->ignore) 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; } 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; insn->retpoline_safe = true;Lines 1–500 of 2187. Load more
/* * 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> #include "builtin.h" #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; struct cfi_state initial_func_cfi; struct instruction *find_insn(struct objtool_file *file, struct section *sec, unsigned long offset) { 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); if (!next || &next->list == &file->insn_list || next->sec != insn->sec) 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)) #define sec_for_each_insn_continue(file, insn) \ for (insn = next_insn_same_sec(file, insn); insn; \ insn = next_insn_same_sec(file, insn)) /* * 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", "usercopy_abort", }; 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); } static void clear_insn_state(struct insn_state *state) { int i; memset(state, 0, sizeof(*state)); state->cfa.base = CFI_UNDEFINED; for (i = 0; i < CFI_NUM_REGS; i++) { state->regs[i].base = CFI_UNDEFINED; state->vals[i].base = CFI_UNDEFINED; } state->drap_reg = CFI_UNDEFINED; state->drap_offset = -1; } /* * 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; for_each_sec(file, sec) { if (!(sec->sh.sh_flags & SHF_EXECINSTR)) continue; if (strcmp(sec->name, ".altinstr_replacement") && strcmp(sec->name, ".altinstr_aux") && strncmp(sec->name, ".discard.", 9)) sec->text = true; for (offset = 0; offset < sec->len; offset += insn->len) { insn = malloc(sizeof(*insn)); if (!insn) { WARN("malloc failed"); return -1; } memset(insn, 0, sizeof(*insn)); INIT_LIST_HEAD(&insn->alts); clear_insn_state(&insn->state); insn->sec = sec; insn->offset = offset; ret = arch_decode_instruction(file->elf, sec, offset, sec->len - offset, &insn->len, &insn->type, &insn->immediate, &insn->stack_op); if (ret) goto err; if (!insn->type || insn->type > INSN_LAST) { WARN_FUNC("invalid instruction type %d", insn->sec, insn->offset, insn->type); ret = -1; goto err; } 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; err: free(insn); return ret; } /* * Mark "ud2" instructions and manually annotated dead ends. */ static int add_dead_ends(struct objtool_file *file) { struct section *sec; struct rela *rela; struct instruction *insn; bool found; /* * 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. */ sec = find_section_by_name(file->elf, ".rela.discard.unreachable"); if (!sec) goto reachable; 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; } 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; } 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; for_each_sec(file, sec) { 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) insn->ignore = true; } } } /* * 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; } /* * 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; if (insn->ignore) 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; } 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; insn->retpoline_safe = true;