Commit b70ed23c authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag 'objtool_urgent_for_v5.18_rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull objtool fixes from Borislav Petkov:
 "A bunch of objtool fixes to improve unwinding, sibling call detection,
  fallthrough detection and relocation handling of weak symbols when the
  toolchain strips section symbols"

* tag 'objtool_urgent_for_v5.18_rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
  objtool: Fix code relocs vs weak symbols
  objtool: Fix type of reloc::addend
  objtool: Fix function fallthrough detection for vmlinux
  objtool: Fix sibling call detection in alternatives
  objtool: Don't set 'jump_dest' for sibling calls
  x86/uaccess: Don't jump between functions
parents d4af0c17 4abff6d4
Loading
Loading
Loading
Loading
+52 −35
Original line number Diff line number Diff line
@@ -53,12 +53,12 @@
SYM_FUNC_START(copy_user_generic_unrolled)
	ASM_STAC
	cmpl $8,%edx
	jb 20f		/* less then 8 bytes, go to byte copy loop */
	jb .Lcopy_user_short_string_bytes
	ALIGN_DESTINATION
	movl %edx,%ecx
	andl $63,%edx
	shrl $6,%ecx
	jz .L_copy_short_string
	jz copy_user_short_string
1:	movq (%rsi),%r8
2:	movq 1*8(%rsi),%r9
3:	movq 2*8(%rsi),%r10
@@ -79,37 +79,11 @@ SYM_FUNC_START(copy_user_generic_unrolled)
	leaq 64(%rdi),%rdi
	decl %ecx
	jnz 1b
.L_copy_short_string:
	movl %edx,%ecx
	andl $7,%edx
	shrl $3,%ecx
	jz 20f
18:	movq (%rsi),%r8
19:	movq %r8,(%rdi)
	leaq 8(%rsi),%rsi
	leaq 8(%rdi),%rdi
	decl %ecx
	jnz 18b
20:	andl %edx,%edx
	jz 23f
	movl %edx,%ecx
21:	movb (%rsi),%al
22:	movb %al,(%rdi)
	incq %rsi
	incq %rdi
	decl %ecx
	jnz 21b
23:	xor %eax,%eax
	ASM_CLAC
	RET
	jmp copy_user_short_string

30:	shll $6,%ecx
	addl %ecx,%edx
	jmp 60f
40:	leal (%rdx,%rcx,8),%edx
	jmp 60f
50:	movl %ecx,%edx
60:	jmp .Lcopy_user_handle_tail /* ecx is zerorest also */
	jmp .Lcopy_user_handle_tail

	_ASM_EXTABLE_CPY(1b, 30b)
	_ASM_EXTABLE_CPY(2b, 30b)
@@ -127,10 +101,6 @@ SYM_FUNC_START(copy_user_generic_unrolled)
	_ASM_EXTABLE_CPY(14b, 30b)
	_ASM_EXTABLE_CPY(15b, 30b)
	_ASM_EXTABLE_CPY(16b, 30b)
	_ASM_EXTABLE_CPY(18b, 40b)
	_ASM_EXTABLE_CPY(19b, 40b)
	_ASM_EXTABLE_CPY(21b, 50b)
	_ASM_EXTABLE_CPY(22b, 50b)
SYM_FUNC_END(copy_user_generic_unrolled)
EXPORT_SYMBOL(copy_user_generic_unrolled)

@@ -191,7 +161,7 @@ EXPORT_SYMBOL(copy_user_generic_string)
SYM_FUNC_START(copy_user_enhanced_fast_string)
	ASM_STAC
	/* CPUs without FSRM should avoid rep movsb for short copies */
	ALTERNATIVE "cmpl $64, %edx; jb .L_copy_short_string", "", X86_FEATURE_FSRM
	ALTERNATIVE "cmpl $64, %edx; jb copy_user_short_string", "", X86_FEATURE_FSRM
	movl %edx,%ecx
1:	rep movsb
	xorl %eax,%eax
@@ -243,6 +213,53 @@ SYM_CODE_START_LOCAL(.Lcopy_user_handle_tail)

SYM_CODE_END(.Lcopy_user_handle_tail)

/*
 * Finish memcpy of less than 64 bytes.  #AC should already be set.
 *
 * Input:
 * rdi destination
 * rsi source
 * rdx count (< 64)
 *
 * Output:
 * eax uncopied bytes or 0 if successful.
 */
SYM_CODE_START_LOCAL(copy_user_short_string)
	movl %edx,%ecx
	andl $7,%edx
	shrl $3,%ecx
	jz .Lcopy_user_short_string_bytes
18:	movq (%rsi),%r8
19:	movq %r8,(%rdi)
	leaq 8(%rsi),%rsi
	leaq 8(%rdi),%rdi
	decl %ecx
	jnz 18b
.Lcopy_user_short_string_bytes:
	andl %edx,%edx
	jz 23f
	movl %edx,%ecx
21:	movb (%rsi),%al
22:	movb %al,(%rdi)
	incq %rsi
	incq %rdi
	decl %ecx
	jnz 21b
23:	xor %eax,%eax
	ASM_CLAC
	RET

40:	leal (%rdx,%rcx,8),%edx
	jmp 60f
50:	movl %ecx,%edx		/* ecx is zerorest also */
60:	jmp .Lcopy_user_handle_tail

	_ASM_EXTABLE_CPY(18b, 40b)
	_ASM_EXTABLE_CPY(19b, 40b)
	_ASM_EXTABLE_CPY(21b, 50b)
	_ASM_EXTABLE_CPY(22b, 50b)
SYM_CODE_END(copy_user_short_string)

/*
 * copy_user_nocache - Uncached memory copy with exception handling
 * This will force destination out of cache for more performance.
+44 −37
Original line number Diff line number Diff line
@@ -559,12 +559,12 @@ static int add_dead_ends(struct objtool_file *file)
		else if (reloc->addend == reloc->sym->sec->sh.sh_size) {
			insn = find_last_insn(file, reloc->sym->sec);
			if (!insn) {
				WARN("can't find unreachable insn at %s+0x%x",
				WARN("can't find unreachable insn at %s+0x%lx",
				     reloc->sym->sec->name, reloc->addend);
				return -1;
			}
		} else {
			WARN("can't find unreachable insn at %s+0x%x",
			WARN("can't find unreachable insn at %s+0x%lx",
			     reloc->sym->sec->name, reloc->addend);
			return -1;
		}
@@ -594,12 +594,12 @@ static int add_dead_ends(struct objtool_file *file)
		else if (reloc->addend == reloc->sym->sec->sh.sh_size) {
			insn = find_last_insn(file, reloc->sym->sec);
			if (!insn) {
				WARN("can't find reachable insn at %s+0x%x",
				WARN("can't find reachable insn at %s+0x%lx",
				     reloc->sym->sec->name, reloc->addend);
				return -1;
			}
		} else {
			WARN("can't find reachable insn at %s+0x%x",
			WARN("can't find reachable insn at %s+0x%lx",
			     reloc->sym->sec->name, reloc->addend);
			return -1;
		}
@@ -1271,12 +1271,19 @@ static bool is_first_func_insn(struct objtool_file *file, struct instruction *in
 */
static int add_jump_destinations(struct objtool_file *file)
{
	struct instruction *insn;
	struct instruction *insn, *jump_dest;
	struct reloc *reloc;
	struct section *dest_sec;
	unsigned long dest_off;

	for_each_insn(file, insn) {
		if (insn->jump_dest) {
			/*
			 * handle_group_alt() may have previously set
			 * 'jump_dest' for some alternatives.
			 */
			continue;
		}
		if (!is_static_jump(insn))
			continue;

@@ -1291,7 +1298,10 @@ static int add_jump_destinations(struct objtool_file *file)
			add_retpoline_call(file, insn);
			continue;
		} else if (insn->func) {
			/* internal or external sibling call (with reloc) */
			/*
			 * External sibling call or internal sibling call with
			 * STT_FUNC reloc.
			 */
			add_call_dest(file, insn, reloc->sym, true);
			continue;
		} else if (reloc->sym->sec->idx) {
@@ -1303,17 +1313,8 @@ static int add_jump_destinations(struct objtool_file *file)
			continue;
		}

		insn->jump_dest = find_insn(file, dest_sec, dest_off);
		if (!insn->jump_dest) {

			/*
			 * This is a special case where an alt instruction
			 * jumps past the end of the section.  These are
			 * handled later in handle_group_alt().
			 */
			if (!strcmp(insn->sec->name, ".altinstr_replacement"))
				continue;

		jump_dest = find_insn(file, dest_sec, dest_off);
		if (!jump_dest) {
			WARN_FUNC("can't find jump dest instruction at %s+0x%lx",
				  insn->sec, insn->offset, dest_sec->name,
				  dest_off);
@@ -1323,8 +1324,8 @@ static int add_jump_destinations(struct objtool_file *file)
		/*
		 * Cross-function jump.
		 */
		if (insn->func && insn->jump_dest->func &&
		    insn->func != insn->jump_dest->func) {
		if (insn->func && jump_dest->func &&
		    insn->func != jump_dest->func) {

			/*
			 * For GCC 8+, create parent/child links for any cold
@@ -1342,16 +1343,22 @@ static int add_jump_destinations(struct objtool_file *file)
			 * subfunction is through a jump table.
			 */
			if (!strstr(insn->func->name, ".cold") &&
			    strstr(insn->jump_dest->func->name, ".cold")) {
				insn->func->cfunc = insn->jump_dest->func;
				insn->jump_dest->func->pfunc = insn->func;
			    strstr(jump_dest->func->name, ".cold")) {
				insn->func->cfunc = jump_dest->func;
				jump_dest->func->pfunc = insn->func;

			} else if (!same_function(insn, insn->jump_dest) &&
				   is_first_func_insn(file, insn->jump_dest)) {
				/* internal sibling call (without reloc) */
				add_call_dest(file, insn, insn->jump_dest->func, true);
			} else if (!same_function(insn, jump_dest) &&
				   is_first_func_insn(file, jump_dest)) {
				/*
				 * Internal sibling call without reloc or with
				 * STT_SECTION reloc.
				 */
				add_call_dest(file, insn, jump_dest->func, true);
				continue;
			}
		}

		insn->jump_dest = jump_dest;
	}

	return 0;
@@ -1540,15 +1547,15 @@ static int handle_group_alt(struct objtool_file *file,
			continue;

		dest_off = arch_jump_destination(insn);
		if (dest_off == special_alt->new_off + special_alt->new_len)
		if (dest_off == special_alt->new_off + special_alt->new_len) {
			insn->jump_dest = next_insn_same_sec(file, last_orig_insn);

			if (!insn->jump_dest) {
				WARN_FUNC("can't find alternative jump destination",
					  insn->sec, insn->offset);
				return -1;
			}
		}
	}

	if (!last_new_insn) {
		WARN_FUNC("can't find last new alternative instruction",
@@ -2245,14 +2252,14 @@ static int decode_sections(struct objtool_file *file)
		return ret;

	/*
	 * Must be before add_special_section_alts() as that depends on
	 * jump_dest being set.
	 * Must be before add_jump_destinations(), which depends on 'func'
	 * being set for alternatives, to enable proper sibling call detection.
	 */
	ret = add_jump_destinations(file);
	ret = add_special_section_alts(file);
	if (ret)
		return ret;

	ret = add_special_section_alts(file);
	ret = add_jump_destinations(file);
	if (ret)
		return ret;

@@ -3303,7 +3310,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
	while (1) {
		next_insn = next_insn_to_validate(file, insn);

		if (file->c_file && func && insn->func && func != insn->func->pfunc) {
		if (func && insn->func && func != insn->func->pfunc) {
			WARN("%s() falls through to next function %s()",
			     func->name, insn->func->name);
			return 1;
+166 −23
Original line number Diff line number Diff line
@@ -546,7 +546,7 @@ static struct section *elf_create_reloc_section(struct elf *elf,
						int reltype);

int elf_add_reloc(struct elf *elf, struct section *sec, unsigned long offset,
		  unsigned int type, struct symbol *sym, int addend)
		  unsigned int type, struct symbol *sym, long addend)
{
	struct reloc *reloc;

@@ -575,37 +575,180 @@ int elf_add_reloc(struct elf *elf, struct section *sec, unsigned long offset,
	return 0;
}

int elf_add_reloc_to_insn(struct elf *elf, struct section *sec,
			  unsigned long offset, unsigned int type,
			  struct section *insn_sec, unsigned long insn_off)
/*
 * Ensure that any reloc section containing references to @sym is marked
 * changed such that it will get re-generated in elf_rebuild_reloc_sections()
 * with the new symbol index.
 */
static void elf_dirty_reloc_sym(struct elf *elf, struct symbol *sym)
{
	struct symbol *sym;
	int addend;
	struct section *sec;

	if (insn_sec->sym) {
		sym = insn_sec->sym;
		addend = insn_off;
	list_for_each_entry(sec, &elf->sections, list) {
		struct reloc *reloc;

		if (sec->changed)
			continue;

		list_for_each_entry(reloc, &sec->reloc_list, list) {
			if (reloc->sym == sym) {
				sec->changed = true;
				break;
			}
		}
	}
}

	} else {
/*
		 * The Clang assembler strips section symbols, so we have to
		 * reference the function symbol instead:
 * Move the first global symbol, as per sh_info, into a new, higher symbol
 * index. This fees up the shndx for a new local symbol.
 */
		sym = find_symbol_containing(insn_sec, insn_off);
static int elf_move_global_symbol(struct elf *elf, struct section *symtab,
				  struct section *symtab_shndx)
{
	Elf_Data *data, *shndx_data = NULL;
	Elf32_Word first_non_local;
	struct symbol *sym;
	Elf_Scn *s;

	first_non_local = symtab->sh.sh_info;

	sym = find_symbol_by_index(elf, first_non_local);
	if (!sym) {
			/*
			 * Hack alert.  This happens when we need to reference
			 * the NOP pad insn immediately after the function.
			 */
			sym = find_symbol_containing(insn_sec, insn_off - 1);
		WARN("no non-local symbols !?");
		return first_non_local;
	}

		if (!sym) {
			WARN("can't find symbol containing %s+0x%lx", insn_sec->name, insn_off);
	s = elf_getscn(elf->elf, symtab->idx);
	if (!s) {
		WARN_ELF("elf_getscn");
		return -1;
	}

		addend = insn_off - sym->offset;
	data = elf_newdata(s);
	if (!data) {
		WARN_ELF("elf_newdata");
		return -1;
	}

	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);
	elf_dirty_reloc_sym(elf, sym);

	symtab->sh.sh_info += 1;
	symtab->sh.sh_size += data->d_size;
	symtab->changed = true;

	if (symtab_shndx) {
		s = elf_getscn(elf->elf, symtab_shndx->idx);
		if (!s) {
			WARN_ELF("elf_getscn");
			return -1;
		}

		shndx_data = elf_newdata(s);
		if (!shndx_data) {
			WARN_ELF("elf_newshndx_data");
			return -1;
		}

		shndx_data->d_buf = &sym->sec->idx;
		shndx_data->d_size = sizeof(Elf32_Word);
		shndx_data->d_align = 4;
		shndx_data->d_type = ELF_T_WORD;

		symtab_shndx->sh.sh_size += 4;
		symtab_shndx->changed = true;
	}

	return first_non_local;
}

static struct symbol *
elf_create_section_symbol(struct elf *elf, struct section *sec)
{
	struct section *symtab, *symtab_shndx;
	Elf_Data *shndx_data = NULL;
	struct symbol *sym;
	Elf32_Word shndx;

	symtab = find_section_by_name(elf, ".symtab");
	if (symtab) {
		symtab_shndx = find_section_by_name(elf, ".symtab_shndx");
		if (symtab_shndx)
			shndx_data = symtab_shndx->data;
	} else {
		WARN("no .symtab");
		return NULL;
	}

	sym = malloc(sizeof(*sym));
	if (!sym) {
		perror("malloc");
		return NULL;
	}
	memset(sym, 0, sizeof(*sym));

	sym->idx = elf_move_global_symbol(elf, symtab, symtab_shndx);
	if (sym->idx < 0) {
		WARN("elf_move_global_symbol");
		return NULL;
	}

	sym->name = sec->name;
	sym->sec = sec;

	// st_name 0
	sym->sym.st_info = GELF_ST_INFO(STB_LOCAL, STT_SECTION);
	// st_other 0
	// st_value 0
	// st_size 0
	shndx = sec->idx;
	if (shndx >= SHN_UNDEF && shndx < SHN_LORESERVE) {
		sym->sym.st_shndx = shndx;
		if (!shndx_data)
			shndx = 0;
	} else {
		sym->sym.st_shndx = SHN_XINDEX;
		if (!shndx_data) {
			WARN("no .symtab_shndx");
			return NULL;
		}
	}

	if (!gelf_update_symshndx(symtab->data, shndx_data, sym->idx, &sym->sym, shndx)) {
		WARN_ELF("gelf_update_symshndx");
		return NULL;
	}

	elf_add_symbol(elf, sym);

	return sym;
}

int elf_add_reloc_to_insn(struct elf *elf, struct section *sec,
			  unsigned long offset, unsigned int type,
			  struct section *insn_sec, unsigned long insn_off)
{
	struct symbol *sym = insn_sec->sym;
	int addend = insn_off;

	if (!sym) {
		/*
		 * Due to how weak functions work, we must use section based
		 * relocations. Symbol based relocations would result in the
		 * weak and non-weak function annotations being overlaid on the
		 * non-weak function after linking.
		 */
		sym = elf_create_section_symbol(elf, insn_sec);
		if (!sym)
			return -1;

		insn_sec->sym = sym;
	}

	return elf_add_reloc(elf, sec, offset, type, sym, addend);
+2 −2
Original line number Diff line number Diff line
@@ -73,7 +73,7 @@ struct reloc {
	struct symbol *sym;
	unsigned long offset;
	unsigned int type;
	int addend;
	long addend;
	int idx;
	bool jump_table_start;
};
@@ -135,7 +135,7 @@ struct elf *elf_open_read(const char *name, int flags);
struct section *elf_create_section(struct elf *elf, const char *name, unsigned int sh_flags, size_t entsize, int nr);

int elf_add_reloc(struct elf *elf, struct section *sec, unsigned long offset,
		  unsigned int type, struct symbol *sym, int addend);
		  unsigned int type, struct symbol *sym, long addend);
int elf_add_reloc_to_insn(struct elf *elf, struct section *sec,
			  unsigned long offset, unsigned int type,
			  struct section *insn_sec, unsigned long insn_off);
+1 −1
Original line number Diff line number Diff line
@@ -27,7 +27,7 @@ struct objtool_file {
	struct list_head static_call_list;
	struct list_head mcount_loc_list;
	struct list_head endbr_list;
	bool ignore_unreachables, c_file, hints, rodata;
	bool ignore_unreachables, hints, rodata;

	unsigned int nr_endbr;
	unsigned int nr_endbr_int;
Loading