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

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

objtool: Track DRAP separately from callee-saved registers



When GCC realigns a function's stack, it sometimes uses %r13 as the DRAP
register, like:

  push	%r13
  lea	0x10(%rsp), %r13
  and	$0xfffffffffffffff0, %rsp
  pushq	-0x8(%r13)
  push	%rbp
  mov	%rsp, %rbp
  push	%r13
  ...
  mov	-0x8(%rbp),%r13
  leaveq
  lea	-0x10(%r13), %rsp
  pop	%r13
  retq

Since %r13 was pushed onto the stack twice, its two stack locations need
to be stored separately.  The first push of %r13 is its original value,
and the second push of %r13 is the caller's stack frame address.

Since %r13 is a callee-saved register, we need to track the stack
location of its original value separately from the DRAP register.

This fixes the following false positive warning:

  lib/ubsan.o: warning: objtool: val_to_string.constprop.7()+0x97: leave instruction with modified stack frame

Reported-by: default avatarArnd Bergmann <arnd@arndb.de>
Signed-off-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>
Fixes: baa41469 ("objtool: Implement stack validation 2.0")
Link: http://lkml.kernel.org/r/3da23a6d4c5b3c1e21fc2ccc21a73941b97ff20a.1502401017.git.jpoimboe@redhat.com


Signed-off-by: default avatarIngo Molnar <mingo@kernel.org>
parent 12b25729
Loading
Loading
Loading
Loading
+30 −24
Original line number Diff line number Diff line
@@ -221,6 +221,7 @@ static void clear_insn_state(struct insn_state *state)
	for (i = 0; i < CFI_NUM_REGS; i++)
		state->regs[i].base = CFI_UNDEFINED;
	state->drap_reg = CFI_UNDEFINED;
	state->drap_offset = -1;
}

/*
@@ -1110,8 +1111,7 @@ static int update_insn_state_regs(struct instruction *insn, struct insn_state *s
static void save_reg(struct insn_state *state, unsigned char reg, int base,
		     int offset)
{
	if ((arch_callee_saved_reg(reg) ||
	    (state->drap && reg == state->drap_reg)) &&
	if (arch_callee_saved_reg(reg) &&
	    state->regs[reg].base == CFI_UNDEFINED) {
		state->regs[reg].base = base;
		state->regs[reg].offset = offset;
@@ -1281,7 +1281,6 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state)
				cfa->base = state->drap_reg;
				cfa->offset = state->stack_size = 0;
				state->drap = true;

			}

			/*
@@ -1299,17 +1298,19 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state)
				cfa->base = CFI_SP;
			}

			if (regs[op->dest.reg].offset == -state->stack_size) {

			if (state->drap && cfa->base == CFI_BP_INDIRECT &&
			    op->dest.type == OP_DEST_REG &&
				    op->dest.reg == state->drap_reg) {
			    op->dest.reg == state->drap_reg &&
			    state->drap_offset == -state->stack_size) {

				/* drap: pop %drap */
				cfa->base = state->drap_reg;
				cfa->offset = 0;
				}
				state->drap_offset = -1;

			} else if (regs[op->dest.reg].offset == -state->stack_size) {

				/* pop %reg */
				restore_reg(state, op->dest.reg);
			}

@@ -1321,14 +1322,18 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state)

		case OP_SRC_REG_INDIRECT:
			if (state->drap && op->src.reg == CFI_BP &&
			    op->src.offset == regs[op->dest.reg].offset) {
			    op->src.offset == state->drap_offset) {

				/* drap: mov disp(%rbp), %reg */
				if (op->dest.reg == state->drap_reg) {
				/* drap: mov disp(%rbp), %drap */
				cfa->base = state->drap_reg;
				cfa->offset = 0;
				state->drap_offset = -1;
			}

			if (state->drap && op->src.reg == CFI_BP &&
			    op->src.offset == regs[op->dest.reg].offset) {

				/* drap: mov disp(%rbp), %reg */
				restore_reg(state, op->dest.reg);

			} else if (op->src.reg == cfa->base &&
@@ -1364,8 +1369,8 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state)
				cfa->base = CFI_BP_INDIRECT;
				cfa->offset = -state->stack_size;

				/* save drap so we know when to undefine it */
				save_reg(state, op->src.reg, CFI_CFA, -state->stack_size);
				/* save drap so we know when to restore it */
				state->drap_offset = -state->stack_size;

			} else if (op->src.reg == CFI_BP && cfa->base == state->drap_reg) {

@@ -1399,8 +1404,8 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state)
				cfa->base = CFI_BP_INDIRECT;
				cfa->offset = op->dest.offset;

				/* save drap so we know when to undefine it */
				save_reg(state, op->src.reg, CFI_CFA, op->dest.offset);
				/* save drap offset so we know when to restore it */
				state->drap_offset = op->dest.offset;
			}

			else if (regs[op->src.reg].base == CFI_UNDEFINED) {
@@ -1491,11 +1496,12 @@ static bool insn_state_match(struct instruction *insn, struct insn_state *state)
			  insn->sec, insn->offset, state1->type, state2->type);

	} else if (state1->drap != state2->drap ||
		 (state1->drap && state1->drap_reg != state2->drap_reg)) {
		WARN_FUNC("stack state mismatch: drap1=%d(%d) drap2=%d(%d)",
		 (state1->drap && state1->drap_reg != state2->drap_reg) ||
		 (state1->drap && state1->drap_offset != state2->drap_offset)) {
		WARN_FUNC("stack state mismatch: drap1=%d(%d,%d) drap2=%d(%d,%d)",
			  insn->sec, insn->offset,
			  state1->drap, state1->drap_reg,
			  state2->drap, state2->drap_reg);
			  state1->drap, state1->drap_reg, state1->drap_offset,
			  state2->drap, state2->drap_reg, state2->drap_offset);

	} else
		return true;
+1 −1
Original line number Diff line number Diff line
@@ -32,7 +32,7 @@ struct insn_state {
	unsigned char type;
	bool bp_scratch;
	bool drap;
	int drap_reg;
	int drap_reg, drap_offset;
};

struct instruction {