Skip to content

Commit c60f18c

Browse files
thejhgregkh
authored andcommitted
x86/unwind: Handle NULL pointer calls better in frame unwinder
commit f4f34e1 upstream. When the frame unwinder is invoked for an oops caused by a call to NULL, it currently skips the parent function because BP still points to the parent's stack frame; the (nonexistent) current function only has the first half of a stack frame, and BP doesn't point to it yet. Add a special case for IP==0 that calculates a fake BP from SP, then uses the real BP for the next frame. Note that this handles first_frame specially: Return information about the parent function as long as the saved IP is >=first_frame, even if the fake BP points below it. With an artificially-added NULL call in prctl_set_seccomp(), before this patch, the trace is: Call Trace: ? prctl_set_seccomp+0x3a/0x50 __x64_sys_prctl+0x457/0x6f0 ? __ia32_sys_prctl+0x750/0x750 do_syscall_64+0x72/0x160 entry_SYSCALL_64_after_hwframe+0x44/0xa9 After this patch, the trace is: Call Trace: prctl_set_seccomp+0x3a/0x50 __x64_sys_prctl+0x457/0x6f0 ? __ia32_sys_prctl+0x750/0x750 do_syscall_64+0x72/0x160 entry_SYSCALL_64_after_hwframe+0x44/0xa9 Signed-off-by: Jann Horn <[email protected]> Signed-off-by: Thomas Gleixner <[email protected]> Acked-by: Josh Poimboeuf <[email protected]> Cc: Borislav Petkov <[email protected]> Cc: Andrew Morton <[email protected]> Cc: syzbot <[email protected]> Cc: "H. Peter Anvin" <[email protected]> Cc: Masahiro Yamada <[email protected]> Cc: Michal Marek <[email protected]> Cc: [email protected] Link: https://lkml.kernel.org/r/[email protected] Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent b763bd2 commit c60f18c

File tree

2 files changed

+28
-3
lines changed

2 files changed

+28
-3
lines changed

arch/x86/include/asm/unwind.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ struct unwind_state {
2323
#elif defined(CONFIG_UNWINDER_FRAME_POINTER)
2424
bool got_irq;
2525
unsigned long *bp, *orig_sp, ip;
26+
/*
27+
* If non-NULL: The current frame is incomplete and doesn't contain a
28+
* valid BP. When looking for the next frame, use this instead of the
29+
* non-existent saved BP.
30+
*/
31+
unsigned long *next_bp;
2632
struct pt_regs *regs;
2733
#else
2834
unsigned long *sp;

arch/x86/kernel/unwind_frame.c

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -320,10 +320,14 @@ bool unwind_next_frame(struct unwind_state *state)
320320
}
321321

322322
/* Get the next frame pointer: */
323-
if (state->regs)
323+
if (state->next_bp) {
324+
next_bp = state->next_bp;
325+
state->next_bp = NULL;
326+
} else if (state->regs) {
324327
next_bp = (unsigned long *)state->regs->bp;
325-
else
328+
} else {
326329
next_bp = (unsigned long *)READ_ONCE_TASK_STACK(state->task, *state->bp);
330+
}
327331

328332
/* Move to the next frame if it's safe: */
329333
if (!update_stack_state(state, next_bp))
@@ -398,6 +402,21 @@ void __unwind_start(struct unwind_state *state, struct task_struct *task,
398402

399403
bp = get_frame_pointer(task, regs);
400404

405+
/*
406+
* If we crash with IP==0, the last successfully executed instruction
407+
* was probably an indirect function call with a NULL function pointer.
408+
* That means that SP points into the middle of an incomplete frame:
409+
* *SP is a return pointer, and *(SP-sizeof(unsigned long)) is where we
410+
* would have written a frame pointer if we hadn't crashed.
411+
* Pretend that the frame is complete and that BP points to it, but save
412+
* the real BP so that we can use it when looking for the next frame.
413+
*/
414+
if (regs && regs->ip == 0 &&
415+
(unsigned long *)kernel_stack_pointer(regs) >= first_frame) {
416+
state->next_bp = bp;
417+
bp = ((unsigned long *)kernel_stack_pointer(regs)) - 1;
418+
}
419+
401420
/* Initialize stack info and make sure the frame data is accessible: */
402421
get_stack_info(bp, state->task, &state->stack_info,
403422
&state->stack_mask);
@@ -410,7 +429,7 @@ void __unwind_start(struct unwind_state *state, struct task_struct *task,
410429
*/
411430
while (!unwind_done(state) &&
412431
(!on_stack(&state->stack_info, first_frame, sizeof(long)) ||
413-
state->bp < first_frame))
432+
(state->next_bp == NULL && state->bp < first_frame)))
414433
unwind_next_frame(state);
415434
}
416435
EXPORT_SYMBOL_GPL(__unwind_start);

0 commit comments

Comments
 (0)