x86/entry: Fix the end of the stack for newly forked tasks

When unwinding a task, the end of the stack is always at the same offset
right below the saved pt_regs, regardless of which syscall was used to
enter the kernel.  That convention allows the unwinder to verify that a
stack is sane.

However, newly forked tasks don't always follow that convention, as
reported by the following unwinder warning seen by Dave Jones:

  WARNING: kernel stack frame pointer at ffffc90001443f30 in kworker/u8:8:30468 has bad value           (null)

The warning was due to the following call chain:

  (ftrace handler)
  call_usermodehelper_exec_async+0x5/0x140
  ret_from_fork+0x22/0x30

The problem is that ret_from_fork() doesn't create a stack frame before
calling other functions.  Fix that by carefully using the frame pointer
macros.

In addition to conforming to the end of stack convention, this also
makes related stack traces more sensible by making it clear to the user
that ret_from_fork() was involved.

Reported-by: Dave Jones <davej@codemonkey.org.uk>
Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
Cc: Andy Lutomirski <luto@amacapital.net>
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: Dmitry Vyukov <dvyukov@google.com>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Miroslav Benes <mbenes@suse.cz>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Link: http://lkml.kernel.org/r/8854cdaab980e9700a81e9ebf0d4238e4bbb68ef.1483978430.git.jpoimboe@redhat.com
Signed-off-by: Ingo Molnar <mingo@kernel.org>
This commit is contained in:
Josh Poimboeuf 2017-01-09 12:00:25 -06:00 committed by Ingo Molnar
parent 2c96b2fe9c
commit ff3f7e2475
2 changed files with 18 additions and 23 deletions

View File

@ -254,23 +254,6 @@ ENTRY(__switch_to_asm)
jmp __switch_to jmp __switch_to
END(__switch_to_asm) END(__switch_to_asm)
/*
* The unwinder expects the last frame on the stack to always be at the same
* offset from the end of the page, which allows it to validate the stack.
* Calling schedule_tail() directly would break that convention because its an
* asmlinkage function so its argument has to be pushed on the stack. This
* wrapper creates a proper "end of stack" frame header before the call.
*/
ENTRY(schedule_tail_wrapper)
FRAME_BEGIN
pushl %eax
call schedule_tail
popl %eax
FRAME_END
ret
ENDPROC(schedule_tail_wrapper)
/* /*
* A newly forked process directly context switches into this address. * A newly forked process directly context switches into this address.
* *
@ -279,15 +262,24 @@ ENDPROC(schedule_tail_wrapper)
* edi: kernel thread arg * edi: kernel thread arg
*/ */
ENTRY(ret_from_fork) ENTRY(ret_from_fork)
call schedule_tail_wrapper FRAME_BEGIN /* help unwinder find end of stack */
/*
* schedule_tail() is asmlinkage so we have to put its 'prev' argument
* on the stack.
*/
pushl %eax
call schedule_tail
popl %eax
testl %ebx, %ebx testl %ebx, %ebx
jnz 1f /* kernel threads are uncommon */ jnz 1f /* kernel threads are uncommon */
2: 2:
/* When we fork, we trace the syscall return in the child, too. */ /* When we fork, we trace the syscall return in the child, too. */
movl %esp, %eax leal FRAME_OFFSET(%esp), %eax
call syscall_return_slowpath call syscall_return_slowpath
FRAME_END
jmp restore_all jmp restore_all
/* kernel thread */ /* kernel thread */

View File

@ -36,6 +36,7 @@
#include <asm/smap.h> #include <asm/smap.h>
#include <asm/pgtable_types.h> #include <asm/pgtable_types.h>
#include <asm/export.h> #include <asm/export.h>
#include <asm/frame.h>
#include <linux/err.h> #include <linux/err.h>
.code64 .code64
@ -408,6 +409,7 @@ END(__switch_to_asm)
* r12: kernel thread arg * r12: kernel thread arg
*/ */
ENTRY(ret_from_fork) ENTRY(ret_from_fork)
FRAME_BEGIN /* help unwinder find end of stack */
movq %rax, %rdi movq %rax, %rdi
call schedule_tail /* rdi: 'prev' task parameter */ call schedule_tail /* rdi: 'prev' task parameter */
@ -415,10 +417,11 @@ ENTRY(ret_from_fork)
jnz 1f /* kernel threads are uncommon */ jnz 1f /* kernel threads are uncommon */
2: 2:
movq %rsp, %rdi leaq FRAME_OFFSET(%rsp),%rdi /* pt_regs pointer */
call syscall_return_slowpath /* returns with IRQs disabled */ call syscall_return_slowpath /* returns with IRQs disabled */
TRACE_IRQS_ON /* user mode is traced as IRQS on */ TRACE_IRQS_ON /* user mode is traced as IRQS on */
SWAPGS SWAPGS
FRAME_END
jmp restore_regs_and_iret jmp restore_regs_and_iret
1: 1: