objtool,x86: Replace alternatives with .retpoline_sites
Instead of writing complete alternatives, simply provide a list of all the retpoline thunk calls. Then the kernel is free to do with them as it pleases. Simpler code all-round. Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Reviewed-by: Borislav Petkov <bp@suse.de> Acked-by: Josh Poimboeuf <jpoimboe@redhat.com> Tested-by: Alexei Starovoitov <ast@kernel.org> Link: https://lore.kernel.org/r/20211026120309.850007165@infradead.org
This commit is contained in:
parent
c509331b41
commit
134ab5bd18
|
@ -272,6 +272,20 @@ SECTIONS
|
||||||
__parainstructions_end = .;
|
__parainstructions_end = .;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_RETPOLINE
|
||||||
|
/*
|
||||||
|
* List of instructions that call/jmp/jcc to retpoline thunks
|
||||||
|
* __x86_indirect_thunk_*(). These instructions can be patched along
|
||||||
|
* with alternatives, after which the section can be freed.
|
||||||
|
*/
|
||||||
|
. = ALIGN(8);
|
||||||
|
.retpoline_sites : AT(ADDR(.retpoline_sites) - LOAD_OFFSET) {
|
||||||
|
__retpoline_sites = .;
|
||||||
|
*(.retpoline_sites)
|
||||||
|
__retpoline_sites_end = .;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* struct alt_inst entries. From the header (alternative.h):
|
* struct alt_inst entries. From the header (alternative.h):
|
||||||
* "Alternative instructions for different CPU types or capabilities"
|
* "Alternative instructions for different CPU types or capabilities"
|
||||||
|
|
|
@ -711,126 +711,6 @@ const char *arch_ret_insn(int len)
|
||||||
return ret[len-1];
|
return ret[len-1];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* asm/alternative.h ? */
|
|
||||||
|
|
||||||
#define ALTINSTR_FLAG_INV (1 << 15)
|
|
||||||
#define ALT_NOT(feat) ((feat) | ALTINSTR_FLAG_INV)
|
|
||||||
|
|
||||||
struct alt_instr {
|
|
||||||
s32 instr_offset; /* original instruction */
|
|
||||||
s32 repl_offset; /* offset to replacement instruction */
|
|
||||||
u16 cpuid; /* cpuid bit set for replacement */
|
|
||||||
u8 instrlen; /* length of original instruction */
|
|
||||||
u8 replacementlen; /* length of new instruction */
|
|
||||||
} __packed;
|
|
||||||
|
|
||||||
static int elf_add_alternative(struct elf *elf,
|
|
||||||
struct instruction *orig, struct symbol *sym,
|
|
||||||
int cpuid, u8 orig_len, u8 repl_len)
|
|
||||||
{
|
|
||||||
const int size = sizeof(struct alt_instr);
|
|
||||||
struct alt_instr *alt;
|
|
||||||
struct section *sec;
|
|
||||||
Elf_Scn *s;
|
|
||||||
|
|
||||||
sec = find_section_by_name(elf, ".altinstructions");
|
|
||||||
if (!sec) {
|
|
||||||
sec = elf_create_section(elf, ".altinstructions",
|
|
||||||
SHF_ALLOC, 0, 0);
|
|
||||||
|
|
||||||
if (!sec) {
|
|
||||||
WARN_ELF("elf_create_section");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s = elf_getscn(elf->elf, sec->idx);
|
|
||||||
if (!s) {
|
|
||||||
WARN_ELF("elf_getscn");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
sec->data = elf_newdata(s);
|
|
||||||
if (!sec->data) {
|
|
||||||
WARN_ELF("elf_newdata");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
sec->data->d_size = size;
|
|
||||||
sec->data->d_align = 1;
|
|
||||||
|
|
||||||
alt = sec->data->d_buf = malloc(size);
|
|
||||||
if (!sec->data->d_buf) {
|
|
||||||
perror("malloc");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
memset(sec->data->d_buf, 0, size);
|
|
||||||
|
|
||||||
if (elf_add_reloc_to_insn(elf, sec, sec->sh.sh_size,
|
|
||||||
R_X86_64_PC32, orig->sec, orig->offset)) {
|
|
||||||
WARN("elf_create_reloc: alt_instr::instr_offset");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (elf_add_reloc(elf, sec, sec->sh.sh_size + 4,
|
|
||||||
R_X86_64_PC32, sym, 0)) {
|
|
||||||
WARN("elf_create_reloc: alt_instr::repl_offset");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
alt->cpuid = bswap_if_needed(cpuid);
|
|
||||||
alt->instrlen = orig_len;
|
|
||||||
alt->replacementlen = repl_len;
|
|
||||||
|
|
||||||
sec->sh.sh_size += size;
|
|
||||||
sec->changed = true;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define X86_FEATURE_RETPOLINE ( 7*32+12)
|
|
||||||
|
|
||||||
int arch_rewrite_retpolines(struct objtool_file *file)
|
|
||||||
{
|
|
||||||
struct instruction *insn;
|
|
||||||
struct reloc *reloc;
|
|
||||||
struct symbol *sym;
|
|
||||||
char name[32] = "";
|
|
||||||
|
|
||||||
list_for_each_entry(insn, &file->retpoline_call_list, call_node) {
|
|
||||||
|
|
||||||
if (insn->type != INSN_JUMP_DYNAMIC &&
|
|
||||||
insn->type != INSN_CALL_DYNAMIC)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!strcmp(insn->sec->name, ".text.__x86.indirect_thunk"))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
reloc = insn->reloc;
|
|
||||||
|
|
||||||
sprintf(name, "__x86_indirect_alt_%s_%s",
|
|
||||||
insn->type == INSN_JUMP_DYNAMIC ? "jmp" : "call",
|
|
||||||
reloc->sym->name + 21);
|
|
||||||
|
|
||||||
sym = find_symbol_by_name(file->elf, name);
|
|
||||||
if (!sym) {
|
|
||||||
sym = elf_create_undef_symbol(file->elf, name);
|
|
||||||
if (!sym) {
|
|
||||||
WARN("elf_create_undef_symbol");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (elf_add_alternative(file->elf, insn, sym,
|
|
||||||
ALT_NOT(X86_FEATURE_RETPOLINE), 5, 5)) {
|
|
||||||
WARN("elf_add_alternative");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int arch_decode_hint_reg(u8 sp_reg, int *base)
|
int arch_decode_hint_reg(u8 sp_reg, int *base)
|
||||||
{
|
{
|
||||||
switch (sp_reg) {
|
switch (sp_reg) {
|
||||||
|
|
|
@ -683,6 +683,52 @@ static int create_static_call_sections(struct objtool_file *file)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int create_retpoline_sites_sections(struct objtool_file *file)
|
||||||
|
{
|
||||||
|
struct instruction *insn;
|
||||||
|
struct section *sec;
|
||||||
|
int idx;
|
||||||
|
|
||||||
|
sec = find_section_by_name(file->elf, ".retpoline_sites");
|
||||||
|
if (sec) {
|
||||||
|
WARN("file already has .retpoline_sites, skipping");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx = 0;
|
||||||
|
list_for_each_entry(insn, &file->retpoline_call_list, call_node)
|
||||||
|
idx++;
|
||||||
|
|
||||||
|
if (!idx)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
sec = elf_create_section(file->elf, ".retpoline_sites", 0,
|
||||||
|
sizeof(int), idx);
|
||||||
|
if (!sec) {
|
||||||
|
WARN("elf_create_section: .retpoline_sites");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx = 0;
|
||||||
|
list_for_each_entry(insn, &file->retpoline_call_list, call_node) {
|
||||||
|
|
||||||
|
int *site = (int *)sec->data->d_buf + idx;
|
||||||
|
*site = 0;
|
||||||
|
|
||||||
|
if (elf_add_reloc_to_insn(file->elf, sec,
|
||||||
|
idx * sizeof(int),
|
||||||
|
R_X86_64_PC32,
|
||||||
|
insn->sec, insn->offset)) {
|
||||||
|
WARN("elf_add_reloc_to_insn: .retpoline_sites");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int create_mcount_loc_sections(struct objtool_file *file)
|
static int create_mcount_loc_sections(struct objtool_file *file)
|
||||||
{
|
{
|
||||||
struct section *sec;
|
struct section *sec;
|
||||||
|
@ -1016,6 +1062,11 @@ static void annotate_call_site(struct objtool_file *file,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sym->retpoline_thunk) {
|
||||||
|
list_add_tail(&insn->call_node, &file->retpoline_call_list);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Many compilers cannot disable KCOV with a function attribute
|
* Many compilers cannot disable KCOV with a function attribute
|
||||||
* so they need a little help, NOP out any KCOV calls from noinstr
|
* so they need a little help, NOP out any KCOV calls from noinstr
|
||||||
|
@ -1075,6 +1126,39 @@ static void add_call_dest(struct objtool_file *file, struct instruction *insn,
|
||||||
annotate_call_site(file, insn, sibling);
|
annotate_call_site(file, insn, sibling);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void add_retpoline_call(struct objtool_file *file, struct instruction *insn)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Retpoline calls/jumps are really dynamic calls/jumps in disguise,
|
||||||
|
* so convert them accordingly.
|
||||||
|
*/
|
||||||
|
switch (insn->type) {
|
||||||
|
case INSN_CALL:
|
||||||
|
insn->type = INSN_CALL_DYNAMIC;
|
||||||
|
break;
|
||||||
|
case INSN_JUMP_UNCONDITIONAL:
|
||||||
|
insn->type = INSN_JUMP_DYNAMIC;
|
||||||
|
break;
|
||||||
|
case INSN_JUMP_CONDITIONAL:
|
||||||
|
insn->type = INSN_JUMP_DYNAMIC_CONDITIONAL;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
insn->retpoline_safe = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Whatever stack impact regular CALLs have, should be undone
|
||||||
|
* by the RETURN of the called function.
|
||||||
|
*
|
||||||
|
* Annotated intra-function calls retain the stack_ops but
|
||||||
|
* are converted to JUMP, see read_intra_function_calls().
|
||||||
|
*/
|
||||||
|
remove_insn_ops(insn);
|
||||||
|
|
||||||
|
annotate_call_site(file, insn, false);
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
* Find the destination instructions for all jumps.
|
* Find the destination instructions for all jumps.
|
||||||
*/
|
*/
|
||||||
|
@ -1097,19 +1181,7 @@ static int add_jump_destinations(struct objtool_file *file)
|
||||||
dest_sec = reloc->sym->sec;
|
dest_sec = reloc->sym->sec;
|
||||||
dest_off = arch_dest_reloc_offset(reloc->addend);
|
dest_off = arch_dest_reloc_offset(reloc->addend);
|
||||||
} else if (reloc->sym->retpoline_thunk) {
|
} else if (reloc->sym->retpoline_thunk) {
|
||||||
/*
|
add_retpoline_call(file, insn);
|
||||||
* Retpoline jumps are really dynamic jumps in
|
|
||||||
* disguise, so convert them accordingly.
|
|
||||||
*/
|
|
||||||
if (insn->type == INSN_JUMP_UNCONDITIONAL)
|
|
||||||
insn->type = INSN_JUMP_DYNAMIC;
|
|
||||||
else
|
|
||||||
insn->type = INSN_JUMP_DYNAMIC_CONDITIONAL;
|
|
||||||
|
|
||||||
list_add_tail(&insn->call_node,
|
|
||||||
&file->retpoline_call_list);
|
|
||||||
|
|
||||||
insn->retpoline_safe = true;
|
|
||||||
continue;
|
continue;
|
||||||
} else if (insn->func) {
|
} else if (insn->func) {
|
||||||
/* internal or external sibling call (with reloc) */
|
/* internal or external sibling call (with reloc) */
|
||||||
|
@ -1238,18 +1310,7 @@ static int add_call_destinations(struct objtool_file *file)
|
||||||
add_call_dest(file, insn, dest, false);
|
add_call_dest(file, insn, dest, false);
|
||||||
|
|
||||||
} else if (reloc->sym->retpoline_thunk) {
|
} else if (reloc->sym->retpoline_thunk) {
|
||||||
/*
|
add_retpoline_call(file, insn);
|
||||||
* Retpoline calls are really dynamic calls in
|
|
||||||
* disguise, so convert them accordingly.
|
|
||||||
*/
|
|
||||||
insn->type = INSN_CALL_DYNAMIC;
|
|
||||||
insn->retpoline_safe = true;
|
|
||||||
|
|
||||||
list_add_tail(&insn->call_node,
|
|
||||||
&file->retpoline_call_list);
|
|
||||||
|
|
||||||
remove_insn_ops(insn);
|
|
||||||
continue;
|
|
||||||
|
|
||||||
} else
|
} else
|
||||||
add_call_dest(file, insn, reloc->sym, false);
|
add_call_dest(file, insn, reloc->sym, false);
|
||||||
|
@ -1980,11 +2041,6 @@ static void mark_rodata(struct objtool_file *file)
|
||||||
file->rodata = found;
|
file->rodata = found;
|
||||||
}
|
}
|
||||||
|
|
||||||
__weak int arch_rewrite_retpolines(struct objtool_file *file)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int decode_sections(struct objtool_file *file)
|
static int decode_sections(struct objtool_file *file)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
@ -2057,15 +2113,6 @@ static int decode_sections(struct objtool_file *file)
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
/*
|
|
||||||
* Must be after add_special_section_alts(), since this will emit
|
|
||||||
* alternatives. Must be after add_{jump,call}_destination(), since
|
|
||||||
* those create the call insn lists.
|
|
||||||
*/
|
|
||||||
ret = arch_rewrite_retpolines(file);
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3468,6 +3515,13 @@ int check(struct objtool_file *file)
|
||||||
goto out;
|
goto out;
|
||||||
warnings += ret;
|
warnings += ret;
|
||||||
|
|
||||||
|
if (retpoline) {
|
||||||
|
ret = create_retpoline_sites_sections(file);
|
||||||
|
if (ret < 0)
|
||||||
|
goto out;
|
||||||
|
warnings += ret;
|
||||||
|
}
|
||||||
|
|
||||||
if (mcount) {
|
if (mcount) {
|
||||||
ret = create_mcount_loc_sections(file);
|
ret = create_mcount_loc_sections(file);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
|
|
|
@ -740,90 +740,6 @@ static int elf_add_string(struct elf *elf, struct section *strtab, char *str)
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct symbol *elf_create_undef_symbol(struct elf *elf, const char *name)
|
|
||||||
{
|
|
||||||
struct section *symtab, *symtab_shndx;
|
|
||||||
struct symbol *sym;
|
|
||||||
Elf_Data *data;
|
|
||||||
Elf_Scn *s;
|
|
||||||
|
|
||||||
sym = malloc(sizeof(*sym));
|
|
||||||
if (!sym) {
|
|
||||||
perror("malloc");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
memset(sym, 0, sizeof(*sym));
|
|
||||||
|
|
||||||
sym->name = strdup(name);
|
|
||||||
|
|
||||||
sym->sym.st_name = elf_add_string(elf, NULL, sym->name);
|
|
||||||
if (sym->sym.st_name == -1)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
sym->sym.st_info = GELF_ST_INFO(STB_GLOBAL, STT_NOTYPE);
|
|
||||||
// st_other 0
|
|
||||||
// st_shndx 0
|
|
||||||
// st_value 0
|
|
||||||
// st_size 0
|
|
||||||
|
|
||||||
symtab = find_section_by_name(elf, ".symtab");
|
|
||||||
if (!symtab) {
|
|
||||||
WARN("can't find .symtab");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
s = elf_getscn(elf->elf, symtab->idx);
|
|
||||||
if (!s) {
|
|
||||||
WARN_ELF("elf_getscn");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
data = elf_newdata(s);
|
|
||||||
if (!data) {
|
|
||||||
WARN_ELF("elf_newdata");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
data->d_buf = &sym->sym;
|
|
||||||
data->d_size = sizeof(sym->sym);
|
|
||||||
data->d_align = 1;
|
|
||||||
data->d_type = ELF_T_SYM;
|
|
||||||
|
|
||||||
sym->idx = symtab->sh.sh_size / sizeof(sym->sym);
|
|
||||||
|
|
||||||
symtab->sh.sh_size += data->d_size;
|
|
||||||
symtab->changed = true;
|
|
||||||
|
|
||||||
symtab_shndx = find_section_by_name(elf, ".symtab_shndx");
|
|
||||||
if (symtab_shndx) {
|
|
||||||
s = elf_getscn(elf->elf, symtab_shndx->idx);
|
|
||||||
if (!s) {
|
|
||||||
WARN_ELF("elf_getscn");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
data = elf_newdata(s);
|
|
||||||
if (!data) {
|
|
||||||
WARN_ELF("elf_newdata");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
data->d_buf = &sym->sym.st_size; /* conveniently 0 */
|
|
||||||
data->d_size = sizeof(Elf32_Word);
|
|
||||||
data->d_align = 4;
|
|
||||||
data->d_type = ELF_T_WORD;
|
|
||||||
|
|
||||||
symtab_shndx->sh.sh_size += 4;
|
|
||||||
symtab_shndx->changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
sym->sec = find_section_by_index(elf, 0);
|
|
||||||
|
|
||||||
elf_add_symbol(elf, sym);
|
|
||||||
|
|
||||||
return sym;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct section *elf_create_section(struct elf *elf, const char *name,
|
struct section *elf_create_section(struct elf *elf, const char *name,
|
||||||
unsigned int sh_flags, size_t entsize, int nr)
|
unsigned int sh_flags, size_t entsize, int nr)
|
||||||
{
|
{
|
||||||
|
|
|
@ -144,7 +144,6 @@ int elf_write_insn(struct elf *elf, struct section *sec,
|
||||||
unsigned long offset, unsigned int len,
|
unsigned long offset, unsigned int len,
|
||||||
const char *insn);
|
const char *insn);
|
||||||
int elf_write_reloc(struct elf *elf, struct reloc *reloc);
|
int elf_write_reloc(struct elf *elf, struct reloc *reloc);
|
||||||
struct symbol *elf_create_undef_symbol(struct elf *elf, const char *name);
|
|
||||||
int elf_write(struct elf *elf);
|
int elf_write(struct elf *elf);
|
||||||
void elf_close(struct elf *elf);
|
void elf_close(struct elf *elf);
|
||||||
|
|
||||||
|
|
|
@ -109,14 +109,6 @@ static int get_alt_entry(struct elf *elf, struct special_entry *entry,
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Skip retpoline .altinstr_replacement... we already rewrite the
|
|
||||||
* instructions for retpolines anyway, see arch_is_retpoline()
|
|
||||||
* usage in add_{call,jump}_destinations().
|
|
||||||
*/
|
|
||||||
if (arch_is_retpoline(new_reloc->sym))
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
reloc_to_sec_off(new_reloc, &alt->new_sec, &alt->new_off);
|
reloc_to_sec_off(new_reloc, &alt->new_sec, &alt->new_off);
|
||||||
|
|
||||||
/* _ASM_EXTABLE_EX hack */
|
/* _ASM_EXTABLE_EX hack */
|
||||||
|
|
Loading…
Reference in New Issue