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:
Peter Zijlstra 2021-10-26 14:01:36 +02:00
parent c509331b41
commit 134ab5bd18
6 changed files with 107 additions and 252 deletions

View File

@ -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"

View File

@ -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) {

View File

@ -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)

View File

@ -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)
{ {

View File

@ -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);

View File

@ -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 */