Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Josh 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: baa41469a7b9 ("objtool: Implement stack validation 2.0")
Link: http://lkml.kernel.org/r/3da23a6d4c5b3c1e21fc2ccc21a73941b97ff20a.1502401017.git.jpoimboe@redhat.com
Signed-off-by: Ingo Molnar <mingo@kernel.org>

authored by

Josh Poimboeuf and committed by
Ingo Molnar
bf4d1a83 12b25729

+31 -25
+30 -24
tools/objtool/check.c
··· 221 221 for (i = 0; i < CFI_NUM_REGS; i++) 222 222 state->regs[i].base = CFI_UNDEFINED; 223 223 state->drap_reg = CFI_UNDEFINED; 224 + state->drap_offset = -1; 224 225 } 225 226 226 227 /* ··· 1111 1110 static void save_reg(struct insn_state *state, unsigned char reg, int base, 1112 1111 int offset) 1113 1112 { 1114 - if ((arch_callee_saved_reg(reg) || 1115 - (state->drap && reg == state->drap_reg)) && 1113 + if (arch_callee_saved_reg(reg) && 1116 1114 state->regs[reg].base == CFI_UNDEFINED) { 1117 1115 state->regs[reg].base = base; 1118 1116 state->regs[reg].offset = offset; ··· 1281 1281 cfa->base = state->drap_reg; 1282 1282 cfa->offset = state->stack_size = 0; 1283 1283 state->drap = true; 1284 - 1285 1284 } 1286 1285 1287 1286 /* ··· 1298 1299 cfa->base = CFI_SP; 1299 1300 } 1300 1301 1301 - if (regs[op->dest.reg].offset == -state->stack_size) { 1302 + if (state->drap && cfa->base == CFI_BP_INDIRECT && 1303 + op->dest.type == OP_DEST_REG && 1304 + op->dest.reg == state->drap_reg && 1305 + state->drap_offset == -state->stack_size) { 1302 1306 1303 - if (state->drap && cfa->base == CFI_BP_INDIRECT && 1304 - op->dest.type == OP_DEST_REG && 1305 - op->dest.reg == state->drap_reg) { 1307 + /* drap: pop %drap */ 1308 + cfa->base = state->drap_reg; 1309 + cfa->offset = 0; 1310 + state->drap_offset = -1; 1306 1311 1307 - /* drap: pop %drap */ 1308 - cfa->base = state->drap_reg; 1309 - cfa->offset = 0; 1310 - } 1312 + } else if (regs[op->dest.reg].offset == -state->stack_size) { 1311 1313 1314 + /* pop %reg */ 1312 1315 restore_reg(state, op->dest.reg); 1313 1316 } 1314 1317 ··· 1322 1321 1323 1322 case OP_SRC_REG_INDIRECT: 1324 1323 if (state->drap && op->src.reg == CFI_BP && 1324 + op->src.offset == state->drap_offset) { 1325 + 1326 + /* drap: mov disp(%rbp), %drap */ 1327 + cfa->base = state->drap_reg; 1328 + cfa->offset = 0; 1329 + state->drap_offset = -1; 1330 + } 1331 + 1332 + if (state->drap && op->src.reg == CFI_BP && 1325 1333 op->src.offset == regs[op->dest.reg].offset) { 1326 1334 1327 1335 /* drap: mov disp(%rbp), %reg */ 1328 - if (op->dest.reg == state->drap_reg) { 1329 - cfa->base = state->drap_reg; 1330 - cfa->offset = 0; 1331 - } 1332 - 1333 1336 restore_reg(state, op->dest.reg); 1334 1337 1335 1338 } else if (op->src.reg == cfa->base && ··· 1369 1364 cfa->base = CFI_BP_INDIRECT; 1370 1365 cfa->offset = -state->stack_size; 1371 1366 1372 - /* save drap so we know when to undefine it */ 1373 - save_reg(state, op->src.reg, CFI_CFA, -state->stack_size); 1367 + /* save drap so we know when to restore it */ 1368 + state->drap_offset = -state->stack_size; 1374 1369 1375 1370 } else if (op->src.reg == CFI_BP && cfa->base == state->drap_reg) { 1376 1371 ··· 1404 1399 cfa->base = CFI_BP_INDIRECT; 1405 1400 cfa->offset = op->dest.offset; 1406 1401 1407 - /* save drap so we know when to undefine it */ 1408 - save_reg(state, op->src.reg, CFI_CFA, op->dest.offset); 1402 + /* save drap offset so we know when to restore it */ 1403 + state->drap_offset = op->dest.offset; 1409 1404 } 1410 1405 1411 1406 else if (regs[op->src.reg].base == CFI_UNDEFINED) { ··· 1496 1491 insn->sec, insn->offset, state1->type, state2->type); 1497 1492 1498 1493 } else if (state1->drap != state2->drap || 1499 - (state1->drap && state1->drap_reg != state2->drap_reg)) { 1500 - WARN_FUNC("stack state mismatch: drap1=%d(%d) drap2=%d(%d)", 1494 + (state1->drap && state1->drap_reg != state2->drap_reg) || 1495 + (state1->drap && state1->drap_offset != state2->drap_offset)) { 1496 + WARN_FUNC("stack state mismatch: drap1=%d(%d,%d) drap2=%d(%d,%d)", 1501 1497 insn->sec, insn->offset, 1502 - state1->drap, state1->drap_reg, 1503 - state2->drap, state2->drap_reg); 1498 + state1->drap, state1->drap_reg, state1->drap_offset, 1499 + state2->drap, state2->drap_reg, state2->drap_offset); 1504 1500 1505 1501 } else 1506 1502 return true;
+1 -1
tools/objtool/check.h
··· 32 32 unsigned char type; 33 33 bool bp_scratch; 34 34 bool drap; 35 - int drap_reg; 35 + int drap_reg, drap_offset; 36 36 }; 37 37 38 38 struct instruction {