Commit e25eea89 authored by Peter Zijlstra's avatar Peter Zijlstra Committed by Ingo Molnar
Browse files

objtool: Introduce HINT_RET_OFFSET



Normally objtool ensures a function keeps the stack layout invariant.
But there is a useful exception, it is possible to stuff the return
stack in order to 'inject' a 'call':

	push $fun
	ret

In this case the invariant mentioned above is violated.

Add an objtool HINT to annotate this and allow a function exit with a
modified stack frame.

Signed-off-by: default avatarPeter Zijlstra (Intel) <peterz@infradead.org>
Reviewed-by: default avatarMiroslav Benes <mbenes@suse.cz>
Reviewed-by: default avatarAlexandre Chartre <alexandre.chartre@oracle.com>
Acked-by: default avatarJosh Poimboeuf <jpoimboe@redhat.com>
Link: https://lkml.kernel.org/r/20200416115118.690601403@infradead.org


Signed-off-by: default avatarIngo Molnar <mingo@kernel.org>
parent b7460462
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -60,6 +60,7 @@
#define ORC_TYPE_REGS_IRET		2
#define UNWIND_HINT_TYPE_SAVE		3
#define UNWIND_HINT_TYPE_RESTORE	4
#define UNWIND_HINT_TYPE_RET_OFFSET	5

#ifndef __ASSEMBLY__
/*
+10 −0
Original line number Diff line number Diff line
@@ -94,6 +94,16 @@
	UNWIND_HINT type=UNWIND_HINT_TYPE_RESTORE
.endm


/*
 * RET_OFFSET: Used on instructions that terminate a function; mostly RETURN
 * and sibling calls. On these, sp_offset denotes the expected offset from
 * initial_func_cfi.
 */
.macro UNWIND_HINT_RET_OFFSET sp_offset=8
	UNWIND_HINT type=UNWIND_HINT_TYPE_RET_OFFSET sp_offset=\sp_offset
.endm

#else /* !__ASSEMBLY__ */

#define UNWIND_HINT(sp_reg, sp_offset, type, end)		\
+1 −0
Original line number Diff line number Diff line
@@ -60,6 +60,7 @@
#define ORC_TYPE_REGS_IRET		2
#define UNWIND_HINT_TYPE_SAVE		3
#define UNWIND_HINT_TYPE_RESTORE	4
#define UNWIND_HINT_TYPE_RET_OFFSET	5

#ifndef __ASSEMBLY__
/*
+16 −8
Original line number Diff line number Diff line
@@ -1261,6 +1261,9 @@ static int read_unwind_hints(struct objtool_file *file)
		} else if (hint->type == UNWIND_HINT_TYPE_RESTORE) {
			insn->restore = true;
			insn->hint = true;

		} else if (hint->type == UNWIND_HINT_TYPE_RET_OFFSET) {
			insn->ret_offset = hint->sp_offset;
			continue;
		}

@@ -1424,20 +1427,25 @@ static bool is_fentry_call(struct instruction *insn)
	return false;
}

static bool has_modified_stack_frame(struct insn_state *state)
static bool has_modified_stack_frame(struct instruction *insn, struct insn_state *state)
{
	u8 ret_offset = insn->ret_offset;
	int i;

	if (state->cfa.base != initial_func_cfi.cfa.base ||
	    state->cfa.offset != initial_func_cfi.cfa.offset ||
	    state->stack_size != initial_func_cfi.cfa.offset ||
	    state->drap)
	if (state->cfa.base != initial_func_cfi.cfa.base || state->drap)
		return true;

	if (state->cfa.offset != initial_func_cfi.cfa.offset + ret_offset)
		return true;

	for (i = 0; i < CFI_NUM_REGS; i++)
	if (state->stack_size != initial_func_cfi.cfa.offset + ret_offset)
		return true;

	for (i = 0; i < CFI_NUM_REGS; i++) {
		if (state->regs[i].base != initial_func_cfi.regs[i].base ||
		    state->regs[i].offset != initial_func_cfi.regs[i].offset)
			return true;
	}

	return false;
}
@@ -2014,7 +2022,7 @@ static int validate_call(struct instruction *insn, struct insn_state *state)

static int validate_sibling_call(struct instruction *insn, struct insn_state *state)
{
	if (has_modified_stack_frame(state)) {
	if (has_modified_stack_frame(insn, state)) {
		WARN_FUNC("sibling call from callable instruction with modified stack frame",
				insn->sec, insn->offset);
		return 1;
@@ -2043,7 +2051,7 @@ static int validate_return(struct symbol *func, struct instruction *insn, struct
		return 1;
	}

	if (func && has_modified_stack_frame(state)) {
	if (func && has_modified_stack_frame(insn, state)) {
		WARN_FUNC("return with modified stack frame",
			  insn->sec, insn->offset);
		return 1;
+3 −1
Original line number Diff line number Diff line
@@ -33,9 +33,11 @@ struct instruction {
	unsigned int len;
	enum insn_type type;
	unsigned long immediate;
	bool alt_group, dead_end, ignore, hint, save, restore, ignore_alts;
	bool alt_group, dead_end, ignore, ignore_alts;
	bool hint, save, restore;
	bool retpoline_safe;
	u8 visited;
	u8 ret_offset;
	struct symbol *call_dest;
	struct instruction *jump_dest;
	struct instruction *first_jump_src;