Skip to content

Commit 81b6743

Browse files
jpoimboeIngo Molnar
authored andcommitted
x86/unwind/orc: Fix premature unwind stoppage due to IRET frames
The following execution path is possible: fsnotify() [ realign the stack and store previous SP in R10 ] <IRQ> [ only IRET regs saved ] common_interrupt() interrupt_entry() <NMI> [ full pt_regs saved ] ... [ unwind stack ] When the unwinder goes through the NMI and the IRQ on the stack, and then sees fsnotify(), it doesn't have access to the value of R10, because it only has the five IRET registers. So the unwind stops prematurely. However, because the interrupt_entry() code is careful not to clobber R10 before saving the full regs, the unwinder should be able to read R10 from the previously saved full pt_regs associated with the NMI. Handle this case properly. When encountering an IRET regs frame immediately after a full pt_regs frame, use the pt_regs as a backup which can be used to get the C register values. Also, note that a call frame resets the 'prev_regs' value, because a function is free to clobber the registers. For this fix to work, the IRET and full regs frames must be adjacent, with no FUNC frames in between. So replace the FUNC hint in interrupt_entry() with an IRET_REGS hint. Fixes: ee9f8fc ("x86/unwind: Add the ORC unwinder") Reviewed-by: Miroslav Benes <[email protected]> Signed-off-by: Josh Poimboeuf <[email protected]> Signed-off-by: Ingo Molnar <[email protected]> Cc: Andy Lutomirski <[email protected]> Cc: Dave Jones <[email protected]> Cc: Jann Horn <[email protected]> Cc: Peter Zijlstra <[email protected]> Cc: Thomas Gleixner <[email protected]> Cc: Vince Weaver <[email protected]> Link: https://lore.kernel.org/r/97a408167cc09f1cfa0de31a7b70dd88868d743f.1587808742.git.jpoimboe@redhat.com
1 parent a0f81bf commit 81b6743

File tree

3 files changed

+43
-14
lines changed

3 files changed

+43
-14
lines changed

arch/x86/entry/entry_64.S

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,7 @@ SYM_CODE_END(spurious_entries_start)
511511
* +----------------------------------------------------+
512512
*/
513513
SYM_CODE_START(interrupt_entry)
514-
UNWIND_HINT_FUNC
514+
UNWIND_HINT_IRET_REGS offset=16
515515
ASM_CLAC
516516
cld
517517

@@ -543,9 +543,9 @@ SYM_CODE_START(interrupt_entry)
543543
pushq 5*8(%rdi) /* regs->eflags */
544544
pushq 4*8(%rdi) /* regs->cs */
545545
pushq 3*8(%rdi) /* regs->ip */
546+
UNWIND_HINT_IRET_REGS
546547
pushq 2*8(%rdi) /* regs->orig_ax */
547548
pushq 8(%rdi) /* return address */
548-
UNWIND_HINT_FUNC
549549

550550
movq (%rdi), %rdi
551551
jmp 2f

arch/x86/include/asm/unwind.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ struct unwind_state {
1919
#if defined(CONFIG_UNWINDER_ORC)
2020
bool signal, full_regs;
2121
unsigned long sp, bp, ip;
22-
struct pt_regs *regs;
22+
struct pt_regs *regs, *prev_regs;
2323
#elif defined(CONFIG_UNWINDER_FRAME_POINTER)
2424
bool got_irq;
2525
unsigned long *bp, *orig_sp, ip;

arch/x86/kernel/unwind_orc.c

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -384,9 +384,38 @@ static bool deref_stack_iret_regs(struct unwind_state *state, unsigned long addr
384384
return true;
385385
}
386386

387+
/*
388+
* If state->regs is non-NULL, and points to a full pt_regs, just get the reg
389+
* value from state->regs.
390+
*
391+
* Otherwise, if state->regs just points to IRET regs, and the previous frame
392+
* had full regs, it's safe to get the value from the previous regs. This can
393+
* happen when early/late IRQ entry code gets interrupted by an NMI.
394+
*/
395+
static bool get_reg(struct unwind_state *state, unsigned int reg_off,
396+
unsigned long *val)
397+
{
398+
unsigned int reg = reg_off/8;
399+
400+
if (!state->regs)
401+
return false;
402+
403+
if (state->full_regs) {
404+
*val = ((unsigned long *)state->regs)[reg];
405+
return true;
406+
}
407+
408+
if (state->prev_regs) {
409+
*val = ((unsigned long *)state->prev_regs)[reg];
410+
return true;
411+
}
412+
413+
return false;
414+
}
415+
387416
bool unwind_next_frame(struct unwind_state *state)
388417
{
389-
unsigned long ip_p, sp, orig_ip = state->ip, prev_sp = state->sp;
418+
unsigned long ip_p, sp, tmp, orig_ip = state->ip, prev_sp = state->sp;
390419
enum stack_type prev_type = state->stack_info.type;
391420
struct orc_entry *orc;
392421
bool indirect = false;
@@ -448,39 +477,35 @@ bool unwind_next_frame(struct unwind_state *state)
448477
break;
449478

450479
case ORC_REG_R10:
451-
if (!state->regs || !state->full_regs) {
480+
if (!get_reg(state, offsetof(struct pt_regs, r10), &sp)) {
452481
orc_warn_current("missing R10 value at %pB\n",
453482
(void *)state->ip);
454483
goto err;
455484
}
456-
sp = state->regs->r10;
457485
break;
458486

459487
case ORC_REG_R13:
460-
if (!state->regs || !state->full_regs) {
488+
if (!get_reg(state, offsetof(struct pt_regs, r13), &sp)) {
461489
orc_warn_current("missing R13 value at %pB\n",
462490
(void *)state->ip);
463491
goto err;
464492
}
465-
sp = state->regs->r13;
466493
break;
467494

468495
case ORC_REG_DI:
469-
if (!state->regs || !state->full_regs) {
496+
if (!get_reg(state, offsetof(struct pt_regs, di), &sp)) {
470497
orc_warn_current("missing RDI value at %pB\n",
471498
(void *)state->ip);
472499
goto err;
473500
}
474-
sp = state->regs->di;
475501
break;
476502

477503
case ORC_REG_DX:
478-
if (!state->regs || !state->full_regs) {
504+
if (!get_reg(state, offsetof(struct pt_regs, dx), &sp)) {
479505
orc_warn_current("missing DX value at %pB\n",
480506
(void *)state->ip);
481507
goto err;
482508
}
483-
sp = state->regs->dx;
484509
break;
485510

486511
default:
@@ -507,6 +532,7 @@ bool unwind_next_frame(struct unwind_state *state)
507532

508533
state->sp = sp;
509534
state->regs = NULL;
535+
state->prev_regs = NULL;
510536
state->signal = false;
511537
break;
512538

@@ -518,6 +544,7 @@ bool unwind_next_frame(struct unwind_state *state)
518544
}
519545

520546
state->regs = (struct pt_regs *)sp;
547+
state->prev_regs = NULL;
521548
state->full_regs = true;
522549
state->signal = true;
523550
break;
@@ -529,6 +556,8 @@ bool unwind_next_frame(struct unwind_state *state)
529556
goto err;
530557
}
531558

559+
if (state->full_regs)
560+
state->prev_regs = state->regs;
532561
state->regs = (void *)sp - IRET_FRAME_OFFSET;
533562
state->full_regs = false;
534563
state->signal = true;
@@ -543,8 +572,8 @@ bool unwind_next_frame(struct unwind_state *state)
543572
/* Find BP: */
544573
switch (orc->bp_reg) {
545574
case ORC_REG_UNDEFINED:
546-
if (state->regs && state->full_regs)
547-
state->bp = state->regs->bp;
575+
if (get_reg(state, offsetof(struct pt_regs, bp), &tmp))
576+
state->bp = tmp;
548577
break;
549578

550579
case ORC_REG_PREV_SP:

0 commit comments

Comments
 (0)