x86: unwinder/entry fixes
Subsystems (3)
cover x86: unwinder/entry fixes
Some unwinder-related fixes for some issues reported over the holidays. The first three patches are pretty minor, but note the last patch modifies entry code.
Josh Poimboeuf (4):
x86/unwind: silence warnings for non-current tasks x86/unwind: disable KASAN checks for non-current tasks x86/unwind: include __schedule() in stack traces x86/entry: fix the end of the stack for newly forked tasks
arch/x86/entry/entry_32.S | 30 +++++++++++------------------- arch/x86/entry/entry_64.S | 11 +++++++---- arch/x86/include/asm/stacktrace.h | 2 +- arch/x86/include/asm/switch_to.h | 10 +++++++++- arch/x86/kernel/unwind_frame.c | 30 ++++++++++++++++++++++++++++-- 5 files changed, 56 insertions(+), 27 deletions(-)
Signature · 1 line
2.7.4
PATCH 1/4 x86/unwind: silence warnings for non-current tasks
There are a handful of callers to save_stack_trace_tsk() and show_stack() which try to unwind the stack of a task other than current. In such cases, it's remotely possible that the task is running on one CPU while the unwinder is reading its stack from another CPU, causing the unwinder to see stack corruption.
These cases seem to be mostly harmless. The unwinder has checks which prevent it from following bad pointers beyond the bounds of the stack. So it's not really a bug as long as the caller understands that unwinding another task will not always succeed.
Since stack "corruption" on another task's stack isn't necessarily a bug, silence the warnings when unwinding tasks other than current.
--- arch/x86/kernel/unwind_frame.c | 10 ++++++++++ 1 file changed, 10 insertions(+)
arch/x86/kernel/unwind_frame.c +10 -0
@@ -207,6 +207,16 @@ bool unwind_next_frame(struct unwind_state *state)return true; bad_address: +/* +* When unwinding a non-current task, the task might actually be +* running on another CPU, in which case it could be modifying its +* stack while we're reading it. This is generally not a problem and +* can be ignored as long as the caller understands that unwinding +* another task will not always succeed. +*/ +if (state->task != current) + goto the_end; +if (state->regs) { printk_deferred_once(KERN_WARNING "WARNING: kernel stack regs at %p in %s:%d has bad 'bp' value %p\n",
Signature · 1 line
2.7.4
tip-bot for Josh Poimboeuf
merge bot[tip:x86/urgent] x86/unwind: Silence warnings for non-current tasks
x86/unwind: Silence warnings for non-current tasks
Commit-ID: 900742d89c1b4e04bd373aec8470b88e183f08ca Gitweb: http://git.kernel.org/tip/900742d89c1b4e04bd373aec8470b88e183f08ca Author: Josh Poimboeuf <[email protected]> AuthorDate: Mon, 9 Jan 2017 12:00:22 -0600 Committer: Ingo Molnar <[email protected]> CommitDate: Thu, 12 Jan 2017 09:28:27 +0100
x86/unwind: Silence warnings for non-current tasks
There are a handful of callers to save_stack_trace_tsk() and show_stack() which try to unwind the stack of a task other than current. In such cases, it's remotely possible that the task is running on one CPU while the unwinder is reading its stack from another CPU, causing the unwinder to see stack corruption.
These cases seem to be mostly harmless. The unwinder has checks which prevent it from following bad pointers beyond the bounds of the stack. So it's not really a bug as long as the caller understands that unwinding another task will not always succeed.
Since stack "corruption" on another task's stack isn't necessarily a bug, silence the warnings when unwinding tasks other than current.
--- arch/x86/kernel/unwind_frame.c | 10 ++++++++++ 1 file changed, 10 insertions(+)
arch/x86/kernel/unwind_frame.c +10 -0
@@ -207,6 +207,16 @@ bool unwind_next_frame(struct unwind_state *state)return true; bad_address: +/* +* When unwinding a non-current task, the task might actually be +* running on another CPU, in which case it could be modifying its +* stack while we're reading it. This is generally not a problem and +* can be ignored as long as the caller understands that unwinding +* another task will not always succeed. +*/ +if (state->task != current) + goto the_end; +if (state->regs) { printk_deferred_once(KERN_WARNING "WARNING: kernel stack regs at %p in %s:%d has bad 'bp' value %p\n",
PATCH 2/4 x86/unwind: disable KASAN checks for non-current tasks
In reply to Josh PoimboeufThere are a handful of callers to save_stack_trace_tsk() and show_stack() which try to unwind the stack of a task other than current. In such cases, it's remotely possible that the task is running on one CPU while the unwinder is reading its stack from another CPU, causing the unwinder to see stack corruption.
These cases seem to be mostly harmless. The unwinder has checks which prevent it from following bad pointers beyond the bounds of the stack. So it's not really a bug as long as the caller understands that unwinding another task will not always succeed.
In such cases, it's possible that the unwinder may read a KASAN-poisoned region of the stack. Account for that by using READ_ONCE_NOCHECK when reading the stack of another task.
Use READ_ONCE when reading the stack of the current task, since KASAN warnings can still be useful for finding bugs in that case.
--- arch/x86/include/asm/stacktrace.h | 5 ++++- arch/x86/kernel/unwind_frame.c | 20 ++++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-)
arch/x86/include/asm/stacktrace.h +4 -1
@@ -52,13 +52,16 @@ static inline bool on_stack(struct stack_info *info, void *addr, size_t len)static inline unsigned long *get_frame_pointer (struct task_struct *task,struct pt_regs *regs) { +struct inactive_task_frame *frame; +if (regs)return (unsigned long *)regs->bp;if (task == current)return __builtin_frame_address (0 ); -return (unsigned long *)((struct inactive_task_frame *)task->thread.sp)->bp; + frame = (struct inactive_task_frame *)task->thread.sp; +return (unsigned long *)READ_ONCE_NOCHECK (frame->bp); }#else static inline unsigned long *
arch/x86/kernel/unwind_frame.c +18 -2
@@ -6,6 +6,21 @@#define FRAME_HEADER_SIZE (sizeof(long) * 2) +/* +* This disables KASAN checking when reading a value from another task's stack, +* since the other task could be running on another CPU and could have poisoned +* the stack in the meantime. +*/ +#define READ_ONCE_TASK_STACK (task, x) \ +({ \ + unsigned long val; \ + if (task == current) \ + val = READ_ONCE(x); \ + else \ + val = READ_ONCE_NOCHECK(x); \ + val; \ +}) +static void unwind_dump (struct unwind_state *state,unsigned long *sp) {static bool dumped_before = false;
@@ -48,7 +63,8 @@ unsigned long unwind_get_return_address(struct unwind_state *state)if (state->regs &&user_mode (state->regs))return 0 ; - addr =ftrace_graph_ret_addr (state->task, &state->graph_idx, *addr_p, + addr =READ_ONCE_TASK_STACK (state->task, *addr_p); + addr =ftrace_graph_ret_addr (state->task, &state->graph_idx, addr, addr_p);return __kernel_text_address (addr) ? addr :0 ;
@@ -162,7 +178,7 @@ bool unwind_next_frame(struct unwind_state *state)if (state->regs) next_bp = (unsigned long *)state->regs->bp;else - next_bp = (unsigned long *)*state->bp; + next_bp = (unsigned long *)READ_ONCE_TASK_STACK (state->task,*state->bp);/* is the next frame pointer an encoded pointer to pt_regs? */ regs =decode_frame_pointer (next_bp);
Signature · 1 line
2.7.4
tip-bot for Josh Poimboeuf
merge bot[tip:x86/urgent] x86/unwind: Disable KASAN checks for non-current tasks
x86/unwind: Disable KASAN checks for non-current tasks
Commit-ID: 84936118bdf37bda513d4a361c38181a216427e0 Gitweb: http://git.kernel.org/tip/84936118bdf37bda513d4a361c38181a216427e0 Author: Josh Poimboeuf <[email protected]> AuthorDate: Mon, 9 Jan 2017 12:00:23 -0600 Committer: Ingo Molnar <[email protected]> CommitDate: Thu, 12 Jan 2017 09:28:27 +0100
x86/unwind: Disable KASAN checks for non-current tasks
There are a handful of callers to save_stack_trace_tsk() and show_stack() which try to unwind the stack of a task other than current. In such cases, it's remotely possible that the task is running on one CPU while the unwinder is reading its stack from another CPU, causing the unwinder to see stack corruption.
These cases seem to be mostly harmless. The unwinder has checks which prevent it from following bad pointers beyond the bounds of the stack. So it's not really a bug as long as the caller understands that unwinding another task will not always succeed.
In such cases, it's possible that the unwinder may read a KASAN-poisoned region of the stack. Account for that by using READ_ONCE_NOCHECK() when reading the stack of another task.
Use READ_ONCE() when reading the stack of the current task, since KASAN warnings can still be useful for finding bugs in that case.
--- arch/x86/include/asm/stacktrace.h | 5 ++++- arch/x86/kernel/unwind_frame.c | 20 ++++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-)
arch/x86/include/asm/stacktrace.h +4 -1
@@ -52,13 +52,16 @@ static inline bool on_stack(struct stack_info *info, void *addr, size_t len)static inline unsigned long *get_frame_pointer (struct task_struct *task,struct pt_regs *regs) { +struct inactive_task_frame *frame; +if (regs)return (unsigned long *)regs->bp;if (task == current)return __builtin_frame_address (0 ); -return (unsigned long *)((struct inactive_task_frame *)task->thread.sp)->bp; + frame = (struct inactive_task_frame *)task->thread.sp; +return (unsigned long *)READ_ONCE_NOCHECK (frame->bp); }#else static inline unsigned long *
arch/x86/kernel/unwind_frame.c +18 -2
@@ -6,6 +6,21 @@#define FRAME_HEADER_SIZE (sizeof(long) * 2) +/* +* This disables KASAN checking when reading a value from another task's stack, +* since the other task could be running on another CPU and could have poisoned +* the stack in the meantime. +*/ +#define READ_ONCE_TASK_STACK (task, x) \ +({ \ + unsigned long val; \ + if (task == current) \ + val = READ_ONCE(x); \ + else \ + val = READ_ONCE_NOCHECK(x); \ + val; \ +}) +static void unwind_dump (struct unwind_state *state,unsigned long *sp) {static bool dumped_before = false;
@@ -48,7 +63,8 @@ unsigned long unwind_get_return_address(struct unwind_state *state)if (state->regs &&user_mode (state->regs))return 0 ; - addr =ftrace_graph_ret_addr (state->task, &state->graph_idx, *addr_p, + addr =READ_ONCE_TASK_STACK (state->task, *addr_p); + addr =ftrace_graph_ret_addr (state->task, &state->graph_idx, addr, addr_p);return __kernel_text_address (addr) ? addr :0 ;
@@ -162,7 +178,7 @@ bool unwind_next_frame(struct unwind_state *state)if (state->regs) next_bp = (unsigned long *)state->regs->bp;else - next_bp = (unsigned long *)*state->bp; + next_bp = (unsigned long *)READ_ONCE_TASK_STACK (state->task,*state->bp);/* is the next frame pointer an encoded pointer to pt_regs? */ regs =decode_frame_pointer (next_bp);
PATCH 3/4 x86/unwind: include __schedule() in stack traces
In reply to Josh PoimboeufIn the following commit:
0100301bfdf5 ("sched/x86: Rewrite the switch_to() code")
... the layout of the 'inactive_task_frame' struct was designed to have a frame pointer header embedded in it, so that the unwinder could use the 'bp' and 'ret_addr' fields to report __schedule() on the stack (or ret_from_fork() for newly forked tasks which haven't actually run yet).
Finish the job by changing get_frame_pointer() to return a pointer to inactive_task_frame's 'bp' field rather than 'bp' itself. This allows the unwinder to start one frame higher on the stack, so that it properly reports __schedule().
--- arch/x86/include/asm/stacktrace.h | 5 +---- arch/x86/include/asm/switch_to.h | 10 +++++++++- 2 files changed, 10 insertions(+), 5 deletions(-)
arch/x86/include/asm/stacktrace.h +1 -4
@@ -52,16 +52,13 @@ static inline bool on_stack(struct stack_info *info, void *addr, size_t len)static inline unsigned long *get_frame_pointer (struct task_struct *task,struct pt_regs *regs) { -struct inactive_task_frame *frame; -if (regs)return (unsigned long *)regs->bp;if (task == current)return __builtin_frame_address (0 ); - frame = (struct inactive_task_frame *)task->thread.sp; -return (unsigned long *)READ_ONCE_NOCHECK (frame->bp); +return &((struct inactive_task_frame *)task->thread.sp)->bp; }#else static inline unsigned long *
arch/x86/include/asm/switch_to.h +9 -1
@@ -36,7 +36,10 @@ static inline void prepare_switch_to(struct task_struct *prev, asmlinkage void ret_from_fork(void); -/* data that is pointed to by thread.sp */ +/* +* This is the structure pointed to by thread.sp for an inactive task. The +* order of the fields must match the code in __switch_to_asm(). +*/ struct inactive_task_frame {#ifdef CONFIG_X86_64 unsigned long r15;
@@ -48,6 +51,11 @@ struct inactive_task_frame {unsigned long di;#endif unsigned long bx; + +/* +* These two fields must be together. They form a stack frame header, +* needed by get_frame_pointer(). +*/ unsigned long bp;unsigned long ret_addr; };
Signature · 1 line
2.7.4
Re: [PATCH 3/4] x86/unwind: include __schedule() in stack traces
Quoted 9 lines · Josh Poimboeuf
In the following commit:
0100301bfdf5("sched/x86: Rewrite theswitch_to()code")... the layout of the '
inactive_task_frame' struct was designed to have a frame pointer header embedded in it, so that the unwinder could use the 'bp' and 'ret_addr' fields to report__schedule()on the stack (orret_from_fork()for newly forked tasks which haven't actually run yet).
Quoted 6 lines
Finish the job by changing
get_frame_pointer()to return a pointer to inactive_task_frame's 'bp' field rather than 'bp' itself. This allows the unwinder to start one frame higher on the stack, so that it properly reports__schedule().Reported-by Miroslav Benes <[email protected]>
You can also add my
One ignorant question below.
Quoted 15 lines
Signed-off-by Josh Poimboeuf <[email protected]>--- arch/x86/include/asm/stacktrace.h | 5 +---- arch/x86/include/asm/switch_to.h | 10 +++++++++- 2 files changed, 10 insertions(+), 5 deletions(-)arch/x86/include/asm/stacktrace.h +0
-1@@ -52,16 +52,13 @@ static inline bool on_stack(struct stack_info *info, void *addr, size_t len)static inline unsigned long *get_frame_pointer (struct task_struct *task,struct pt_regs *regs) { -struct inactive_task_frame *frame;
arch/x86/include/asm/stacktrace.h static inline bool on_stack(struct stack_info *info, void *addr, size_t len) · line 61
Quoted 10 lines
arch/x86/include/asm/stacktrace.h +1
-3-if (regs)return (unsigned long *)regs->bp;if (task == current)return __builtin_frame_address (0 ); - frame = (struct inactive_task_frame *)task->thread.sp; -return (unsigned long *)READ_ONCE_NOCHECK (frame->bp); +return &((struct inactive_task_frame *)task->thread.sp)->bp;
You effectively remove one of the changes from the previous patch - READ_ONCE_NOCHECK. Is it intentional?
Regards,
Miroslav
Re: [PATCH 3/4] x86/unwind: include __schedule() in stack traces
Quoted 23 lines · Miroslav Benes
Quoted 22 lines · Josh Poimboeuf
Signed-off-by Josh Poimboeuf <[email protected]>--- arch/x86/include/asm/stacktrace.h | 5 +---- arch/x86/include/asm/switch_to.h | 10 +++++++++- 2 files changed, 10 insertions(+), 5 deletions(-)arch/x86/include/asm/stacktrace.h +0
-2@@ -52,16 +52,13 @@ static inline bool on_stack(struct stack_info *info, void *addr, size_t len)static inline unsigned long *get_frame_pointer (struct task_struct *task,struct pt_regs *regs) { -struct inactive_task_frame *frame; -if (regs)return (unsigned long *)regs->bp;if (task == current)return __builtin_frame_address (0 );
Quoted 6 lines
arch/x86/include/asm/stacktrace.h static inline bool on_stack(struct stack_info *info, void *addr, size_t len) · line 61
Quoted 3 lines
arch/x86/include/asm/stacktrace.h +1
-2- frame = (struct inactive_task_frame *)task->thread.sp; -return (unsigned long *)READ_ONCE_NOCHECK (frame->bp); +return &((struct inactive_task_frame *)task->thread.sp)->bp;You effectively remove one of the changes from the previous patch -
READ_ONCE_NOCHECK. Is it intentional?
Yes, notice that it's no longer reading the value of bp on the stack. It's instead getting a pointer to it. Since there's no longer a stack access, READ_ONCE_NOCHECK is no longer needed.
Signature · 1 line
Josh
Re: [PATCH 3/4] x86/unwind: include __schedule() in stack traces
Quoted 31 lines · Josh Poimboeuf
Quoted 23 lines · Miroslav Benes
Quoted 22 lines · Josh Poimboeuf
Signed-off-by Josh Poimboeuf <[email protected]>--- arch/x86/include/asm/stacktrace.h | 5 +---- arch/x86/include/asm/switch_to.h | 10 +++++++++- 2 files changed, 10 insertions(+), 5 deletions(-)arch/x86/include/asm/stacktrace.h +0
-2@@ -52,16 +52,13 @@ static inline bool on_stack(struct stack_info *info, void *addr, size_t len)static inline unsigned long *get_frame_pointer (struct task_struct *task,struct pt_regs *regs) { -struct inactive_task_frame *frame; -if (regs)return (unsigned long *)regs->bp;if (task == current)return __builtin_frame_address (0 );Quoted 6 lines
arch/x86/include/asm/stacktrace.h static inline bool on_stack(struct stack_info *info, void *addr, size_t len) · line 61
Quoted 3 lines
arch/x86/include/asm/stacktrace.h +1
-2- frame = (struct inactive_task_frame *)task->thread.sp; -return (unsigned long *)READ_ONCE_NOCHECK (frame->bp); +return &((struct inactive_task_frame *)task->thread.sp)->bp;You effectively remove one of the changes from the previous patch -
READ_ONCE_NOCHECK. Is it intentional?
Quoted 3 lines
Yes, notice that it's no longer reading the value of bp on the stack. It's instead getting a pointer to it. Since there's no longer a stack access,
READ_ONCE_NOCHECKis no longer needed.
Oh, of course. Sorry for the noise.
Miroslav
tip-bot for Josh Poimboeuf
merge bot[tip:x86/urgent] x86/unwind: Include __schedule() in stack traces
In reply to Josh Poimboeufx86/unwind: Include __schedule() in stack traces
Commit-ID: 2c96b2fe9c57b4267c3f0a680d82d7cc52e1c447 Gitweb: http://git.kernel.org/tip/2c96b2fe9c57b4267c3f0a680d82d7cc52e1c447 Author: Josh Poimboeuf <[email protected]> AuthorDate: Mon, 9 Jan 2017 12:00:24 -0600 Committer: Ingo Molnar <[email protected]> CommitDate: Thu, 12 Jan 2017 09:28:28 +0100
x86/unwind: Include __schedule() in stack traces
In the following commit:
0100301bfdf5 ("sched/x86: Rewrite the switch_to() code")
... the layout of the 'inactive_task_frame' struct was designed to have a frame pointer header embedded in it, so that the unwinder could use the 'bp' and 'ret_addr' fields to report __schedule() on the stack (or ret_from_fork() for newly forked tasks which haven't actually run yet).
Finish the job by changing get_frame_pointer() to return a pointer to inactive_task_frame's 'bp' field rather than 'bp' itself. This allows the unwinder to start one frame higher on the stack, so that it properly reports __schedule().
--- arch/x86/include/asm/stacktrace.h | 5 +---- arch/x86/include/asm/switch_to.h | 10 +++++++++- 2 files changed, 10 insertions(+), 5 deletions(-)
arch/x86/include/asm/stacktrace.h +1 -4
@@ -52,16 +52,13 @@ static inline bool on_stack(struct stack_info *info, void *addr, size_t len)static inline unsigned long *get_frame_pointer (struct task_struct *task,struct pt_regs *regs) { -struct inactive_task_frame *frame; -if (regs)return (unsigned long *)regs->bp;if (task == current)return __builtin_frame_address (0 ); - frame = (struct inactive_task_frame *)task->thread.sp; -return (unsigned long *)READ_ONCE_NOCHECK (frame->bp); +return &((struct inactive_task_frame *)task->thread.sp)->bp; }#else static inline unsigned long *
arch/x86/include/asm/switch_to.h +9 -1
@@ -36,7 +36,10 @@ static inline void prepare_switch_to(struct task_struct *prev, asmlinkage void ret_from_fork(void); -/* data that is pointed to by thread.sp */ +/* +* This is the structure pointed to by thread.sp for an inactive task. The +* order of the fields must match the code in __switch_to_asm(). +*/ struct inactive_task_frame {#ifdef CONFIG_X86_64 unsigned long r15;
@@ -48,6 +51,11 @@ struct inactive_task_frame {unsigned long di;#endif unsigned long bx; + +/* +* These two fields must be together. They form a stack frame header, +* needed by get_frame_pointer(). +*/ unsigned long bp;unsigned long ret_addr; };
PATCH 4/4 x86/entry: fix the end of the stack for newly forked tasks
In reply to Josh PoimboeufWhen 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.
--- arch/x86/entry/entry_32.S | 30 +++++++++++------------------- arch/x86/entry/entry_64.S | 11 +++++++---- 2 files changed, 18 insertions(+), 23 deletions(-)
arch/x86/entry/entry_32.S +11 -19
@@ -255,23 +255,6 @@ ENTRY(__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. * * eax: prev task we switched from
@@ -279,15 +262,24 @@ ENDPROC(schedule_tail_wrapper) * edi: kernel thread arg */ 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 jnz 1f /* kernel threads are uncommon */ 2: /* When we fork, we trace the syscall return in the child, too. */ - movl %esp, %eax + leal FRAME_OFFSET(%esp), %eax call syscall_return_slowpath + FRAME_END jmp restore_all /* kernel thread */
arch/x86/entry/entry_64.S +7 -4
@@ -36,6 +36,7 @@ #include <asm/smap.h> #include <asm/pgtable_types.h> #include <asm/export.h> +#include <asm/frame.h> #include <linux/err.h> .code64
@@ -408,17 +409,19 @@ END(__switch_to_asm) * r12: kernel thread arg */ ENTRY(ret_from_fork) + FRAME_BEGIN /* help unwinder find end of stack */ movq %rax, %rdi - call schedule_tail /* rdi: 'prev' task parameter */ + call schedule_tail /* rdi: 'prev' task parameter */ - testq %rbx, %rbx /* from kernel_thread? */ - jnz 1f /* kernel threads are uncommon */ + testq %rbx, %rbx /* from kernel_thread? */ + jnz 1f /* kernel threads are uncommon */ 2: - movq %rsp, %rdi + leaq FRAME_OFFSET(%rsp),%rdi /* pt_regs pointer */ call syscall_return_slowpath /* returns with IRQs disabled */ TRACE_IRQS_ON /* user mode is traced as IRQS on */ SWAPGS + FRAME_END jmp restore_regs_and_iret 1:
Signature · 1 line
2.7.4
tip-bot for Josh Poimboeuf
merge bot[tip:x86/urgent] x86/entry: Fix the end of the stack for newly forked tasks
x86/entry: Fix the end of the stack for newly forked tasks
Commit-ID: ff3f7e2475bbf9201e95824e72698fcdc5c3d47a Gitweb: http://git.kernel.org/tip/ff3f7e2475bbf9201e95824e72698fcdc5c3d47a Author: Josh Poimboeuf <[email protected]> AuthorDate: Mon, 9 Jan 2017 12:00:25 -0600 Committer: Ingo Molnar <[email protected]> CommitDate: Thu, 12 Jan 2017 09:28:29 +0100
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.
--- arch/x86/entry/entry_32.S | 30 +++++++++++------------------- arch/x86/entry/entry_64.S | 11 +++++++---- 2 files changed, 18 insertions(+), 23 deletions(-)
arch/x86/entry/entry_32.S +11 -19
@@ -255,23 +255,6 @@ ENTRY(__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. * * eax: prev task we switched from
@@ -279,15 +262,24 @@ ENDPROC(schedule_tail_wrapper) * edi: kernel thread arg */ 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 jnz 1f /* kernel threads are uncommon */ 2: /* When we fork, we trace the syscall return in the child, too. */ - movl %esp, %eax + leal FRAME_OFFSET(%esp), %eax call syscall_return_slowpath + FRAME_END jmp restore_all /* kernel thread */
arch/x86/entry/entry_64.S +7 -4
@@ -36,6 +36,7 @@ #include <asm/smap.h> #include <asm/pgtable_types.h> #include <asm/export.h> +#include <asm/frame.h> #include <linux/err.h> .code64
@@ -408,17 +409,19 @@ END(__switch_to_asm) * r12: kernel thread arg */ ENTRY(ret_from_fork) + FRAME_BEGIN /* help unwinder find end of stack */ movq %rax, %rdi - call schedule_tail /* rdi: 'prev' task parameter */ + call schedule_tail /* rdi: 'prev' task parameter */ - testq %rbx, %rbx /* from kernel_thread? */ - jnz 1f /* kernel threads are uncommon */ + testq %rbx, %rbx /* from kernel_thread? */ + jnz 1f /* kernel threads are uncommon */ 2: - movq %rsp, %rdi + leaq FRAME_OFFSET(%rsp),%rdi /* pt_regs pointer */ call syscall_return_slowpath /* returns with IRQs disabled */ TRACE_IRQS_ON /* user mode is traced as IRQS on */ SWAPGS + FRAME_END jmp restore_regs_and_iret 1: