Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d31a5802 authored by Josh Poimboeuf's avatar Josh Poimboeuf Committed by Ingo Molnar
Browse files

x86/unwind/orc: Detect the end of the stack



The existing UNWIND_HINT_EMPTY annotations happen to be good indicators
of where entry code calls into C code for the first time.  So also use
them to mark the end of the stack for the ORC unwinder.

Use that information to set unwind->error if the ORC unwinder doesn't
unwind all the way to the end.  This will be needed for enabling
HAVE_RELIABLE_STACKTRACE for the ORC unwinder so we can use it with the
livepatch consistency model.

Thanks to Jiri Slaby for teaching the ORCs about the unwind hints.

Signed-off-by: default avatarJosh Poimboeuf <jpoimboe@redhat.com>
Signed-off-by: default avatarJiri Slaby <jslaby@suse.cz>
Acked-by: default avatarJosh Poimboeuf <jpoimboe@redhat.com>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Borislav Petkov <bp@alien8.de>
Cc: Brian Gerst <brgerst@gmail.com>
Cc: Denys Vlasenko <dvlasenk@redhat.com>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Link: https://lkml.kernel.org/lkml/20180518064713.26440-5-jslaby@suse.cz


Signed-off-by: default avatarIngo Molnar <mingo@kernel.org>
parent 0c414367
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -408,6 +408,7 @@ ENTRY(ret_from_fork)

1:
	/* kernel thread */
	UNWIND_HINT_EMPTY
	movq	%r12, %rdi
	CALL_NOSPEC %rbx
	/*
+2 −0
Original line number Diff line number Diff line
@@ -88,6 +88,7 @@ struct orc_entry {
	unsigned	sp_reg:4;
	unsigned	bp_reg:4;
	unsigned	type:2;
	unsigned	end:1;
} __packed;

/*
@@ -101,6 +102,7 @@ struct unwind_hint {
	s16		sp_offset;
	u8		sp_reg;
	u8		type;
	u8		end;
};
#endif /* __ASSEMBLY__ */

+10 −6
Original line number Diff line number Diff line
@@ -26,7 +26,7 @@
 * the debuginfo as necessary.  It will also warn if it sees any
 * inconsistencies.
 */
.macro UNWIND_HINT sp_reg=ORC_REG_SP sp_offset=0 type=ORC_TYPE_CALL
.macro UNWIND_HINT sp_reg=ORC_REG_SP sp_offset=0 type=ORC_TYPE_CALL end=0
#ifdef CONFIG_STACK_VALIDATION
.Lunwind_hint_ip_\@:
	.pushsection .discard.unwind_hints
@@ -35,12 +35,14 @@
		.short \sp_offset
		.byte \sp_reg
		.byte \type
		.byte \end
		.balign 4
	.popsection
#endif
.endm

.macro UNWIND_HINT_EMPTY
	UNWIND_HINT sp_reg=ORC_REG_UNDEFINED
	UNWIND_HINT sp_reg=ORC_REG_UNDEFINED end=1
.endm

.macro UNWIND_HINT_REGS base=%rsp offset=0 indirect=0 extra=1 iret=0
@@ -86,7 +88,7 @@

#else /* !__ASSEMBLY__ */

#define UNWIND_HINT(sp_reg, sp_offset, type)			\
#define UNWIND_HINT(sp_reg, sp_offset, type, end)		\
	"987: \n\t"						\
	".pushsection .discard.unwind_hints\n\t"		\
	/* struct unwind_hint */				\
@@ -94,11 +96,13 @@
	".short " __stringify(sp_offset) "\n\t"			\
	".byte " __stringify(sp_reg) "\n\t"			\
	".byte " __stringify(type) "\n\t"			\
	".byte " __stringify(end) "\n\t"			\
	".balign 4 \n\t"					\
	".popsection\n\t"

#define UNWIND_HINT_SAVE UNWIND_HINT(0, 0, UNWIND_HINT_TYPE_SAVE)
#define UNWIND_HINT_SAVE UNWIND_HINT(0, 0, UNWIND_HINT_TYPE_SAVE, 0)

#define UNWIND_HINT_RESTORE UNWIND_HINT(0, 0, UNWIND_HINT_TYPE_RESTORE)
#define UNWIND_HINT_RESTORE UNWIND_HINT(0, 0, UNWIND_HINT_TYPE_RESTORE, 0)

#endif /* __ASSEMBLY__ */

+31 −21
Original line number Diff line number Diff line
@@ -198,7 +198,7 @@ static int orc_sort_cmp(const void *_a, const void *_b)
	 * whitelisted .o files which didn't get objtool generation.
	 */
	orc_a = cur_orc_table + (a - cur_orc_ip_table);
	return orc_a->sp_reg == ORC_REG_UNDEFINED ? -1 : 1;
	return orc_a->sp_reg == ORC_REG_UNDEFINED && !orc_a->end ? -1 : 1;
}

#ifdef CONFIG_MODULES
@@ -352,7 +352,7 @@ static bool deref_stack_iret_regs(struct unwind_state *state, unsigned long addr

bool unwind_next_frame(struct unwind_state *state)
{
	unsigned long ip_p, sp, orig_ip, prev_sp = state->sp;
	unsigned long ip_p, sp, orig_ip = state->ip, prev_sp = state->sp;
	enum stack_type prev_type = state->stack_info.type;
	struct orc_entry *orc;
	bool indirect = false;
@@ -363,9 +363,9 @@ bool unwind_next_frame(struct unwind_state *state)
	/* Don't let modules unload while we're reading their ORC data. */
	preempt_disable();

	/* Have we reached the end? */
	/* End-of-stack check for user tasks: */
	if (state->regs && user_mode(state->regs))
		goto done;
		goto the_end;

	/*
	 * Find the orc_entry associated with the text address.
@@ -374,9 +374,16 @@ bool unwind_next_frame(struct unwind_state *state)
	 * calls and calls to noreturn functions.
	 */
	orc = orc_find(state->signal ? state->ip : state->ip - 1);
	if (!orc || orc->sp_reg == ORC_REG_UNDEFINED)
		goto done;
	orig_ip = state->ip;
	if (!orc)
		goto err;

	/* End-of-stack check for kernel threads: */
	if (orc->sp_reg == ORC_REG_UNDEFINED) {
		if (!orc->end)
			goto err;

		goto the_end;
	}

	/* Find the previous frame's stack: */
	switch (orc->sp_reg) {
@@ -402,7 +409,7 @@ bool unwind_next_frame(struct unwind_state *state)
		if (!state->regs || !state->full_regs) {
			orc_warn("missing regs for base reg R10 at ip %pB\n",
				 (void *)state->ip);
			goto done;
			goto err;
		}
		sp = state->regs->r10;
		break;
@@ -411,7 +418,7 @@ bool unwind_next_frame(struct unwind_state *state)
		if (!state->regs || !state->full_regs) {
			orc_warn("missing regs for base reg R13 at ip %pB\n",
				 (void *)state->ip);
			goto done;
			goto err;
		}
		sp = state->regs->r13;
		break;
@@ -420,7 +427,7 @@ bool unwind_next_frame(struct unwind_state *state)
		if (!state->regs || !state->full_regs) {
			orc_warn("missing regs for base reg DI at ip %pB\n",
				 (void *)state->ip);
			goto done;
			goto err;
		}
		sp = state->regs->di;
		break;
@@ -429,7 +436,7 @@ bool unwind_next_frame(struct unwind_state *state)
		if (!state->regs || !state->full_regs) {
			orc_warn("missing regs for base reg DX at ip %pB\n",
				 (void *)state->ip);
			goto done;
			goto err;
		}
		sp = state->regs->dx;
		break;
@@ -437,12 +444,12 @@ bool unwind_next_frame(struct unwind_state *state)
	default:
		orc_warn("unknown SP base reg %d for ip %pB\n",
			 orc->sp_reg, (void *)state->ip);
		goto done;
		goto err;
	}

	if (indirect) {
		if (!deref_stack_reg(state, sp, &sp))
			goto done;
			goto err;
	}

	/* Find IP, SP and possibly regs: */
@@ -451,7 +458,7 @@ bool unwind_next_frame(struct unwind_state *state)
		ip_p = sp - sizeof(long);

		if (!deref_stack_reg(state, ip_p, &state->ip))
			goto done;
			goto err;

		state->ip = ftrace_graph_ret_addr(state->task, &state->graph_idx,
						  state->ip, (void *)ip_p);
@@ -465,7 +472,7 @@ bool unwind_next_frame(struct unwind_state *state)
		if (!deref_stack_regs(state, sp, &state->ip, &state->sp)) {
			orc_warn("can't dereference registers at %p for ip %pB\n",
				 (void *)sp, (void *)orig_ip);
			goto done;
			goto err;
		}

		state->regs = (struct pt_regs *)sp;
@@ -477,7 +484,7 @@ bool unwind_next_frame(struct unwind_state *state)
		if (!deref_stack_iret_regs(state, sp, &state->ip, &state->sp)) {
			orc_warn("can't dereference iret registers at %p for ip %pB\n",
				 (void *)sp, (void *)orig_ip);
			goto done;
			goto err;
		}

		state->regs = (void *)sp - IRET_FRAME_OFFSET;
@@ -500,18 +507,18 @@ bool unwind_next_frame(struct unwind_state *state)

	case ORC_REG_PREV_SP:
		if (!deref_stack_reg(state, sp + orc->bp_offset, &state->bp))
			goto done;
			goto err;
		break;

	case ORC_REG_BP:
		if (!deref_stack_reg(state, state->bp + orc->bp_offset, &state->bp))
			goto done;
			goto err;
		break;

	default:
		orc_warn("unknown BP base reg %d for ip %pB\n",
			 orc->bp_reg, (void *)orig_ip);
		goto done;
		goto err;
	}

	/* Prevent a recursive loop due to bad ORC data: */
@@ -520,13 +527,16 @@ bool unwind_next_frame(struct unwind_state *state)
	    state->sp <= prev_sp) {
		orc_warn("stack going in the wrong direction? ip=%pB\n",
			 (void *)orig_ip);
		goto done;
		goto err;
	}

	preempt_enable();
	return true;

done:
err:
	state->error = true;

the_end:
	preempt_enable();
	state->stack_info.type = STACK_TYPE_UNKNOWN;
	return false;
+2 −0
Original line number Diff line number Diff line
@@ -88,6 +88,7 @@ struct orc_entry {
	unsigned	sp_reg:4;
	unsigned	bp_reg:4;
	unsigned	type:2;
	unsigned	end:1;
} __packed;

/*
@@ -101,6 +102,7 @@ struct unwind_hint {
	s16		sp_offset;
	u8		sp_reg;
	u8		type;
	u8		end;
};
#endif /* __ASSEMBLY__ */

Loading