Commit 134ab5bd authored by Peter Zijlstra's avatar Peter Zijlstra
Browse files

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: default avatarPeter Zijlstra (Intel) <peterz@infradead.org>
Reviewed-by: default avatarBorislav Petkov <bp@suse.de>
Acked-by: default avatarJosh Poimboeuf <jpoimboe@redhat.com>
Tested-by: default avatarAlexei Starovoitov <ast@kernel.org>
Link: https://lore.kernel.org/r/20211026120309.850007165@infradead.org
parent c509331b
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -272,6 +272,20 @@ SECTIONS
		__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):
	 * "Alternative instructions for different CPU types or capabilities"
+0 −120
Original line number Diff line number Diff line
@@ -711,126 +711,6 @@ const char *arch_ret_insn(int len)
	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)
{
	switch (sp_reg) {
+93 −39
Original line number Diff line number Diff line
@@ -683,6 +683,52 @@ static int create_static_call_sections(struct objtool_file *file)
	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)
{
	struct section *sec;
@@ -1016,6 +1062,11 @@ static void annotate_call_site(struct objtool_file *file,
		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
	 * 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);
}

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.
 */
@@ -1097,19 +1181,7 @@ static int add_jump_destinations(struct objtool_file *file)
			dest_sec = reloc->sym->sec;
			dest_off = arch_dest_reloc_offset(reloc->addend);
		} else if (reloc->sym->retpoline_thunk) {
			/*
			 * 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;
			add_retpoline_call(file, insn);
			continue;
		} else if (insn->func) {
			/* 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);

		} else if (reloc->sym->retpoline_thunk) {
			/*
			 * 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;
			add_retpoline_call(file, insn);

		} else
			add_call_dest(file, insn, reloc->sym, false);
@@ -1980,11 +2041,6 @@ static void mark_rodata(struct objtool_file *file)
	file->rodata = found;
}

__weak int arch_rewrite_retpolines(struct objtool_file *file)
{
	return 0;
}

static int decode_sections(struct objtool_file *file)
{
	int ret;
@@ -2057,15 +2113,6 @@ static int decode_sections(struct objtool_file *file)
	if (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;
}

@@ -3468,6 +3515,13 @@ int check(struct objtool_file *file)
		goto out;
	warnings += ret;

	if (retpoline) {
		ret = create_retpoline_sites_sections(file);
		if (ret < 0)
			goto out;
		warnings += ret;
	}

	if (mcount) {
		ret = create_mcount_loc_sections(file);
		if (ret < 0)
+0 −84
Original line number Diff line number Diff line
@@ -740,90 +740,6 @@ static int elf_add_string(struct elf *elf, struct section *strtab, char *str)
	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,
				   unsigned int sh_flags, size_t entsize, int nr)
{
+0 −1
Original line number Diff line number Diff line
@@ -144,7 +144,6 @@ int elf_write_insn(struct elf *elf, struct section *sec,
		   unsigned long offset, unsigned int len,
		   const char *insn);
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);
void elf_close(struct elf *elf);

Loading