Commit baa41469 authored by Josh Poimboeuf's avatar Josh Poimboeuf Committed by Ingo Molnar

objtool: Implement stack validation 2.0

This is a major rewrite of objtool.  Instead of only tracking frame
pointer changes, it now tracks all stack-related operations, including
all register saves/restores.

In addition to making stack validation more robust, this also paves the
way for undwarf generation.
Signed-off-by: default avatarJosh Poimboeuf <jpoimboe@redhat.com>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Jiri Slaby <jslaby@suse.cz>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: live-patching@vger.kernel.org
Link: http://lkml.kernel.org/r/678bd94c0566c6129bcc376cddb259c4c5633004.1498659915.git.jpoimboe@redhat.comSigned-off-by: default avatarIngo Molnar <mingo@kernel.org>
parent c207aee4
...@@ -25,7 +25,7 @@ OBJTOOL_IN := $(OBJTOOL)-in.o ...@@ -25,7 +25,7 @@ OBJTOOL_IN := $(OBJTOOL)-in.o
all: $(OBJTOOL) all: $(OBJTOOL)
INCLUDES := -I$(srctree)/tools/include -I$(srctree)/tools/arch/$(HOSTARCH)/include/uapi INCLUDES := -I$(srctree)/tools/include -I$(srctree)/tools/arch/$(HOSTARCH)/include/uapi
CFLAGS += -Wall -Werror $(EXTRA_WARNINGS) -fomit-frame-pointer -O2 -g $(INCLUDES) CFLAGS += -Wall -Werror $(EXTRA_WARNINGS) -Wno-switch-default -Wno-switch-enum -fomit-frame-pointer -O2 -g $(INCLUDES)
LDFLAGS += -lelf $(LIBSUBCMD) LDFLAGS += -lelf $(LIBSUBCMD)
# Allow old libelf to be used: # Allow old libelf to be used:
......
...@@ -19,25 +19,63 @@ ...@@ -19,25 +19,63 @@
#define _ARCH_H #define _ARCH_H
#include <stdbool.h> #include <stdbool.h>
#include <linux/list.h>
#include "elf.h" #include "elf.h"
#include "cfi.h"
#define INSN_FP_SAVE 1 #define INSN_JUMP_CONDITIONAL 1
#define INSN_FP_SETUP 2 #define INSN_JUMP_UNCONDITIONAL 2
#define INSN_FP_RESTORE 3 #define INSN_JUMP_DYNAMIC 3
#define INSN_JUMP_CONDITIONAL 4 #define INSN_CALL 4
#define INSN_JUMP_UNCONDITIONAL 5 #define INSN_CALL_DYNAMIC 5
#define INSN_JUMP_DYNAMIC 6 #define INSN_RETURN 6
#define INSN_CALL 7 #define INSN_CONTEXT_SWITCH 7
#define INSN_CALL_DYNAMIC 8 #define INSN_STACK 8
#define INSN_RETURN 9 #define INSN_NOP 9
#define INSN_CONTEXT_SWITCH 10 #define INSN_OTHER 10
#define INSN_NOP 11
#define INSN_OTHER 12
#define INSN_LAST INSN_OTHER #define INSN_LAST INSN_OTHER
enum op_dest_type {
OP_DEST_REG,
OP_DEST_REG_INDIRECT,
OP_DEST_MEM,
OP_DEST_PUSH,
OP_DEST_LEAVE,
};
struct op_dest {
enum op_dest_type type;
unsigned char reg;
int offset;
};
enum op_src_type {
OP_SRC_REG,
OP_SRC_REG_INDIRECT,
OP_SRC_CONST,
OP_SRC_POP,
OP_SRC_ADD,
OP_SRC_AND,
};
struct op_src {
enum op_src_type type;
unsigned char reg;
int offset;
};
struct stack_op {
struct op_dest dest;
struct op_src src;
};
void arch_initial_func_cfi_state(struct cfi_state *state);
int arch_decode_instruction(struct elf *elf, struct section *sec, int arch_decode_instruction(struct elf *elf, struct section *sec,
unsigned long offset, unsigned int maxlen, unsigned long offset, unsigned int maxlen,
unsigned int *len, unsigned char *type, unsigned int *len, unsigned char *type,
unsigned long *displacement); unsigned long *immediate, struct stack_op *op);
bool arch_callee_saved_reg(unsigned char reg);
#endif /* _ARCH_H */ #endif /* _ARCH_H */
This diff is collapsed.
/*
* 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/>.
*/
#ifndef _OBJTOOL_CFI_H
#define _OBJTOOL_CFI_H
#define CFI_UNDEFINED -1
#define CFI_CFA -2
#define CFI_SP_INDIRECT -3
#define CFI_BP_INDIRECT -4
#define CFI_AX 0
#define CFI_DX 1
#define CFI_CX 2
#define CFI_BX 3
#define CFI_SI 4
#define CFI_DI 5
#define CFI_BP 6
#define CFI_SP 7
#define CFI_R8 8
#define CFI_R9 9
#define CFI_R10 10
#define CFI_R11 11
#define CFI_R12 12
#define CFI_R13 13
#define CFI_R14 14
#define CFI_R15 15
#define CFI_RA 16
#define CFI_NUM_REGS 17
struct cfi_reg {
int base;
int offset;
};
struct cfi_state {
struct cfi_reg cfa;
struct cfi_reg regs[CFI_NUM_REGS];
};
#endif /* _OBJTOOL_CFI_H */
This diff is collapsed.
...@@ -20,22 +20,34 @@ ...@@ -20,22 +20,34 @@
#include <stdbool.h> #include <stdbool.h>
#include "elf.h" #include "elf.h"
#include "cfi.h"
#include "arch.h" #include "arch.h"
#include <linux/hashtable.h> #include <linux/hashtable.h>
struct insn_state {
struct cfi_reg cfa;
struct cfi_reg regs[CFI_NUM_REGS];
int stack_size;
bool bp_scratch;
bool drap;
int drap_reg;
};
struct instruction { struct instruction {
struct list_head list; struct list_head list;
struct hlist_node hash; struct hlist_node hash;
struct section *sec; struct section *sec;
unsigned long offset; unsigned long offset;
unsigned int len, state; unsigned int len;
unsigned char type; unsigned char type;
unsigned long immediate; unsigned long immediate;
bool alt_group, visited, dead_end; bool alt_group, visited, dead_end, ignore;
struct symbol *call_dest; struct symbol *call_dest;
struct instruction *jump_dest; struct instruction *jump_dest;
struct list_head alts; struct list_head alts;
struct symbol *func; struct symbol *func;
struct stack_op stack_op;
struct insn_state state;
}; };
struct objtool_file { struct objtool_file {
...@@ -48,4 +60,7 @@ struct objtool_file { ...@@ -48,4 +60,7 @@ struct objtool_file {
int check(const char *objname, bool nofp); int check(const char *objname, bool nofp);
#define for_each_insn(file, insn) \
list_for_each_entry(insn, &file->insn_list, list)
#endif /* _CHECK_H */ #endif /* _CHECK_H */
...@@ -37,6 +37,9 @@ ...@@ -37,6 +37,9 @@
#define ELF_C_READ_MMAP ELF_C_READ #define ELF_C_READ_MMAP ELF_C_READ
#endif #endif
#define WARN_ELF(format, ...) \
WARN(format ": %s", ##__VA_ARGS__, elf_errmsg(-1))
struct section *find_section_by_name(struct elf *elf, const char *name) struct section *find_section_by_name(struct elf *elf, const char *name)
{ {
struct section *sec; struct section *sec;
...@@ -139,12 +142,12 @@ static int read_sections(struct elf *elf) ...@@ -139,12 +142,12 @@ static int read_sections(struct elf *elf)
int i; int i;
if (elf_getshdrnum(elf->elf, &sections_nr)) { if (elf_getshdrnum(elf->elf, &sections_nr)) {
perror("elf_getshdrnum"); WARN_ELF("elf_getshdrnum");
return -1; return -1;
} }
if (elf_getshdrstrndx(elf->elf, &shstrndx)) { if (elf_getshdrstrndx(elf->elf, &shstrndx)) {
perror("elf_getshdrstrndx"); WARN_ELF("elf_getshdrstrndx");
return -1; return -1;
} }
...@@ -165,37 +168,36 @@ static int read_sections(struct elf *elf) ...@@ -165,37 +168,36 @@ static int read_sections(struct elf *elf)
s = elf_getscn(elf->elf, i); s = elf_getscn(elf->elf, i);
if (!s) { if (!s) {
perror("elf_getscn"); WARN_ELF("elf_getscn");
return -1; return -1;
} }
sec->idx = elf_ndxscn(s); sec->idx = elf_ndxscn(s);
if (!gelf_getshdr(s, &sec->sh)) { if (!gelf_getshdr(s, &sec->sh)) {
perror("gelf_getshdr"); WARN_ELF("gelf_getshdr");
return -1; return -1;
} }
sec->name = elf_strptr(elf->elf, shstrndx, sec->sh.sh_name); sec->name = elf_strptr(elf->elf, shstrndx, sec->sh.sh_name);
if (!sec->name) { if (!sec->name) {
perror("elf_strptr"); WARN_ELF("elf_strptr");
return -1; return -1;
} }
sec->elf_data = elf_getdata(s, NULL); sec->data = elf_getdata(s, NULL);
if (!sec->elf_data) { if (!sec->data) {
perror("elf_getdata"); WARN_ELF("elf_getdata");
return -1; return -1;
} }
if (sec->elf_data->d_off != 0 || if (sec->data->d_off != 0 ||
sec->elf_data->d_size != sec->sh.sh_size) { sec->data->d_size != sec->sh.sh_size) {
WARN("unexpected data attributes for %s", sec->name); WARN("unexpected data attributes for %s", sec->name);
return -1; return -1;
} }
sec->data = (unsigned long)sec->elf_data->d_buf; sec->len = sec->data->d_size;
sec->len = sec->elf_data->d_size;
} }
/* sanity check, one more call to elf_nextscn() should return NULL */ /* sanity check, one more call to elf_nextscn() should return NULL */
...@@ -232,15 +234,15 @@ static int read_symbols(struct elf *elf) ...@@ -232,15 +234,15 @@ static int read_symbols(struct elf *elf)
sym->idx = i; sym->idx = i;
if (!gelf_getsym(symtab->elf_data, i, &sym->sym)) { if (!gelf_getsym(symtab->data, i, &sym->sym)) {
perror("gelf_getsym"); WARN_ELF("gelf_getsym");
goto err; goto err;
} }
sym->name = elf_strptr(elf->elf, symtab->sh.sh_link, sym->name = elf_strptr(elf->elf, symtab->sh.sh_link,
sym->sym.st_name); sym->sym.st_name);
if (!sym->name) { if (!sym->name) {
perror("elf_strptr"); WARN_ELF("elf_strptr");
goto err; goto err;
} }
...@@ -322,8 +324,8 @@ static int read_relas(struct elf *elf) ...@@ -322,8 +324,8 @@ static int read_relas(struct elf *elf)
} }
memset(rela, 0, sizeof(*rela)); memset(rela, 0, sizeof(*rela));
if (!gelf_getrela(sec->elf_data, i, &rela->rela)) { if (!gelf_getrela(sec->data, i, &rela->rela)) {
perror("gelf_getrela"); WARN_ELF("gelf_getrela");
return -1; return -1;
} }
...@@ -362,12 +364,6 @@ struct elf *elf_open(const char *name) ...@@ -362,12 +364,6 @@ struct elf *elf_open(const char *name)
INIT_LIST_HEAD(&elf->sections); INIT_LIST_HEAD(&elf->sections);
elf->name = strdup(name);
if (!elf->name) {
perror("strdup");
goto err;
}
elf->fd = open(name, O_RDONLY); elf->fd = open(name, O_RDONLY);
if (elf->fd == -1) { if (elf->fd == -1) {
perror("open"); perror("open");
...@@ -376,12 +372,12 @@ struct elf *elf_open(const char *name) ...@@ -376,12 +372,12 @@ struct elf *elf_open(const char *name)
elf->elf = elf_begin(elf->fd, ELF_C_READ_MMAP, NULL); elf->elf = elf_begin(elf->fd, ELF_C_READ_MMAP, NULL);
if (!elf->elf) { if (!elf->elf) {
perror("elf_begin"); WARN_ELF("elf_begin");
goto err; goto err;
} }
if (!gelf_getehdr(elf->elf, &elf->ehdr)) { if (!gelf_getehdr(elf->elf, &elf->ehdr)) {
perror("gelf_getehdr"); WARN_ELF("gelf_getehdr");
goto err; goto err;
} }
...@@ -407,6 +403,12 @@ void elf_close(struct elf *elf) ...@@ -407,6 +403,12 @@ void elf_close(struct elf *elf)
struct symbol *sym, *tmpsym; struct symbol *sym, *tmpsym;
struct rela *rela, *tmprela; struct rela *rela, *tmprela;
if (elf->elf)
elf_end(elf->elf);
if (elf->fd > 0)
close(elf->fd);
list_for_each_entry_safe(sec, tmpsec, &elf->sections, list) { list_for_each_entry_safe(sec, tmpsec, &elf->sections, list) {
list_for_each_entry_safe(sym, tmpsym, &sec->symbol_list, list) { list_for_each_entry_safe(sym, tmpsym, &sec->symbol_list, list) {
list_del(&sym->list); list_del(&sym->list);
...@@ -421,11 +423,6 @@ void elf_close(struct elf *elf) ...@@ -421,11 +423,6 @@ void elf_close(struct elf *elf)
list_del(&sec->list); list_del(&sec->list);
free(sec); free(sec);
} }
if (elf->name)
free(elf->name);
if (elf->fd > 0)
close(elf->fd);
if (elf->elf)
elf_end(elf->elf);
free(elf); free(elf);
} }
...@@ -37,10 +37,9 @@ struct section { ...@@ -37,10 +37,9 @@ struct section {
DECLARE_HASHTABLE(rela_hash, 16); DECLARE_HASHTABLE(rela_hash, 16);
struct section *base, *rela; struct section *base, *rela;
struct symbol *sym; struct symbol *sym;
Elf_Data *elf_data; Elf_Data *data;
char *name; char *name;
int idx; int idx;
unsigned long data;
unsigned int len; unsigned int len;
}; };
...@@ -86,6 +85,7 @@ struct rela *find_rela_by_dest_range(struct section *sec, unsigned long offset, ...@@ -86,6 +85,7 @@ struct rela *find_rela_by_dest_range(struct section *sec, unsigned long offset,
struct symbol *find_containing_func(struct section *sec, unsigned long offset); struct symbol *find_containing_func(struct section *sec, unsigned long offset);
void elf_close(struct elf *elf); void elf_close(struct elf *elf);
#define for_each_sec(file, sec) \
list_for_each_entry(sec, &file->elf->sections, list)
#endif /* _OBJTOOL_ELF_H */ #endif /* _OBJTOOL_ELF_H */
...@@ -91,16 +91,16 @@ static int get_alt_entry(struct elf *elf, struct special_entry *entry, ...@@ -91,16 +91,16 @@ static int get_alt_entry(struct elf *elf, struct special_entry *entry,
alt->jump_or_nop = entry->jump_or_nop; alt->jump_or_nop = entry->jump_or_nop;
if (alt->group) { if (alt->group) {
alt->orig_len = *(unsigned char *)(sec->data + offset + alt->orig_len = *(unsigned char *)(sec->data->d_buf + offset +
entry->orig_len); entry->orig_len);
alt->new_len = *(unsigned char *)(sec->data + offset + alt->new_len = *(unsigned char *)(sec->data->d_buf + offset +
entry->new_len); entry->new_len);
} }
if (entry->feature) { if (entry->feature) {
unsigned short feature; unsigned short feature;
feature = *(unsigned short *)(sec->data + offset + feature = *(unsigned short *)(sec->data->d_buf + offset +
entry->feature); entry->feature);
/* /*
......
...@@ -18,6 +18,13 @@ ...@@ -18,6 +18,13 @@
#ifndef _WARN_H #ifndef _WARN_H
#define _WARN_H #define _WARN_H
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "elf.h"
extern const char *objname; extern const char *objname;
static inline char *offstr(struct section *sec, unsigned long offset) static inline char *offstr(struct section *sec, unsigned long offset)
...@@ -57,4 +64,7 @@ static inline char *offstr(struct section *sec, unsigned long offset) ...@@ -57,4 +64,7 @@ static inline char *offstr(struct section *sec, unsigned long offset)
free(_str); \ free(_str); \
}) })
#define WARN_ELF(format, ...) \
WARN(format ": %s", ##__VA_ARGS__, elf_errmsg(-1))
#endif /* _WARN_H */ #endif /* _WARN_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment