A couple of unwinder fixes
Subsystems (3)
cover A couple of unwinder fixes
Patch 1 reverts a previous fix because it introduced a new bug.
Patch 2 re-fixes the original bug.
Josh Poimboeuf (2):
Revert "x86/entry: Fix the end of the stack for newly forked tasks" x86/unwind: Add end-of-stack check for ftrace handlers
arch/x86/entry/entry_32.S | 30 ++++++++++++++++---------- arch/x86/entry/entry_64.S | 11 ++++------ arch/x86/kernel/unwind_frame.c | 49 ++++++++++++++++++++++++++++++++++-------- 3 files changed, 63 insertions(+), 27 deletions(-)
Signature · 1 line
2.7.4
PATCH 1/2 Revert "x86/entry: Fix the end of the stack for newly forked tasks"
Petr Mladek reported the following warning when loading the livepatch sample module:
WARNING: CPU: 1 PID: 3699 at arch/x86/kernel/stacktrace.c:132 save_stack_trace_tsk_reliable+0x133/0x1a0 ... Call Trace: __schedule+0x273/0x820 schedule+0x36/0x80 kthreadd+0x305/0x310 ? kthread_create_on_cpu+0x80/0x80 ? icmp_echo.part.32+0x50/0x50 ret_from_fork+0x2c/0x40
That warning means the end of the stack is no longer recognized as such for newly forked tasks. The problem was introduced with the following commit:
ff3f7e2475bb ("x86/entry: Fix the end of the stack for newly forked tasks")
... which was completely misguided. It only partially fixed the reported issue, and it introduced another bug in the process. None of the other entry code saves the frame pointer before calling into C code, so it doesn't make sense for ret_from_fork to do so either.
Contrary to what I originally thought, the original issue wasn't related to newly forked tasks. It was actually related to ftrace. When entry code calls into a function which then calls into an ftrace handler, the stack frame looks different than normal.
The original issue will be fixed in the unwinder, in a subsequent patch.
--- arch/x86/entry/entry_32.S | 30 +++++++++++++++++++----------- arch/x86/entry/entry_64.S | 11 ++++------- 2 files changed, 23 insertions(+), 18 deletions(-)
arch/x86/entry/entry_32.S +19 -11
@@ -252,6 +252,23 @@ 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
@@ -259,24 +276,15 @@ END(__switch_to_asm) * edi: kernel thread arg */ ENTRY(ret_from_fork) - 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 + call schedule_tail_wrapper testl %ebx, %ebx jnz 1f /* kernel threads are uncommon */ 2: /* When we fork, we trace the syscall return in the child, too. */ - leal FRAME_OFFSET(%esp), %eax + movl %esp, %eax call syscall_return_slowpath - FRAME_END jmp restore_all /* kernel thread */
arch/x86/entry/entry_64.S +4 -7
@@ -36,7 +36,6 @@ #include <asm/smap.h> #include <asm/pgtable_types.h> #include <asm/export.h> -#include <asm/frame.h> #include <linux/err.h> .code64
@@ -406,19 +405,17 @@ 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: - leaq FRAME_OFFSET(%rsp),%rdi /* pt_regs pointer */ + movq %rsp, %rdi 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] Revert "x86/entry: Fix the end of the stack for newly forked tasks"
Revert "x86/entry: Fix the end of the stack for newly forked tasks"
Commit-ID: ebd574994c63164d538a197172157318f58ac647 Gitweb: http://git.kernel.org/tip/ebd574994c63164d538a197172157318f58ac647 Author: Josh Poimboeuf <[email protected]> AuthorDate: Tue, 23 May 2017 10:37:29 -0500 Committer: Ingo Molnar <[email protected]> CommitDate: Wed, 24 May 2017 09:05:16 +0200
Revert "x86/entry: Fix the end of the stack for newly forked tasks"
Petr Mladek reported the following warning when loading the livepatch sample module:
WARNING: CPU: 1 PID: 3699 at arch/x86/kernel/stacktrace.c:132 save_stack_trace_tsk_reliable+0x133/0x1a0 ... Call Trace: __schedule+0x273/0x820 schedule+0x36/0x80 kthreadd+0x305/0x310 ? kthread_create_on_cpu+0x80/0x80 ? icmp_echo.part.32+0x50/0x50 ret_from_fork+0x2c/0x40
That warning means the end of the stack is no longer recognized as such for newly forked tasks. The problem was introduced with the following commit:
ff3f7e2475bb ("x86/entry: Fix the end of the stack for newly forked tasks")
... which was completely misguided. It only partially fixed the reported issue, and it introduced another bug in the process. None of the other entry code saves the frame pointer before calling into C code, so it doesn't make sense for ret_from_fork to do so either.
Contrary to what I originally thought, the original issue wasn't related to newly forked tasks. It was actually related to ftrace. When entry code calls into a function which then calls into an ftrace handler, the stack frame looks different than normal.
The original issue will be fixed in the unwinder, in a subsequent patch.
--- arch/x86/entry/entry_32.S | 30 +++++++++++++++++++----------- arch/x86/entry/entry_64.S | 11 ++++------- 2 files changed, 23 insertions(+), 18 deletions(-)
arch/x86/entry/entry_32.S +19 -11
@@ -252,6 +252,23 @@ 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
@@ -259,24 +276,15 @@ END(__switch_to_asm) * edi: kernel thread arg */ ENTRY(ret_from_fork) - 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 + call schedule_tail_wrapper testl %ebx, %ebx jnz 1f /* kernel threads are uncommon */ 2: /* When we fork, we trace the syscall return in the child, too. */ - leal FRAME_OFFSET(%esp), %eax + movl %esp, %eax call syscall_return_slowpath - FRAME_END jmp restore_all /* kernel thread */
arch/x86/entry/entry_64.S +4 -7
@@ -36,7 +36,6 @@ #include <asm/smap.h> #include <asm/pgtable_types.h> #include <asm/export.h> -#include <asm/frame.h> #include <linux/err.h> .code64
@@ -406,19 +405,17 @@ 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: - leaq FRAME_OFFSET(%rsp),%rdi /* pt_regs pointer */ + movq %rsp, %rdi 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:
PATCH 2/2 x86/unwind: Add end-of-stack check for ftrace handlers
In reply to Josh PoimboeufDave Jones and Steven Rostedt reported unwinder warnings like the following:
WARNING: kernel stack frame pointer at ffff8800bda0ff30 in sshd:1090 has bad value 000055b32abf1fa8
In both cases, the unwinder was attempting to unwind from an ftrace handler into entry code. The callchain was something like:
syscall entry code
C function
ftrace handler
save_stack_trace()
The problem is that the unwinder's end-of-stack logic gets confused by the way ftrace lays out the stack frame (with fentry enabled).
I was able to recreate this warning with:
echo call_usermodehelper_exec_async:stacktrace > /sys/kernel/debug/tracing/set_ftrace_filter (exit login session)
I considered fixing this by changing the ftrace code to rewrite the stack to make the unwinder happy. But that seemed too intrusive after I implemented it. Instead, just add another check to the unwinder's end-of-stack logic to detect this special case.
Side note: We could probably get rid of these end-of-stack checks by encoding the frame pointer for syscall entry just like we do for interrupt entry. That would be simpler, but it would also be a lot more intrusive since it would slightly affect the performance of every syscall.
--- arch/x86/kernel/unwind_frame.c | 49 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 9 deletions(-)
arch/x86/kernel/unwind_frame.c +40 -9
@@ -104,6 +104,11 @@ static inline unsigned long *last_frame(struct unwind_state *state)return (unsigned long *)task_pt_regs (state->task) -2 ; } +static bool is_last_frame (struct unwind_state *state) +{ +return state->bp ==last_frame (state); +} +#ifdef CONFIG_X86_32 #define GCC_REALIGN_WORDS 3#else
@@ -115,16 +120,15 @@ static inline unsigned long *last_aligned_frame(struct unwind_state *state)return last_frame (state) -GCC_REALIGN_WORDS ; } -static bool is_last_task_frame (struct unwind_state *state) +static bool is_last_aligned_frame (struct unwind_state *state) {unsigned long *last_bp =last_frame (state);unsigned long *aligned_bp =last_aligned_frame (state);/* -* We have to check for the last task frame at two different locations -* because gcc can occasionally decide to realign the stack pointer and -* change the offset of the stack frame in the prologue of a function -* called by head/entry code. Examples: +* GCC can occasionally decide to realign the stack pointer and change +* the offset of the stack frame in the prologue of a function called +* by head/entry code. Examples: * * <start_secondary>: * push %edi
@@ -141,11 +145,38 @@ static bool is_last_task_frame(struct unwind_state *state)* push %rbp * mov %rsp,%rbp * -* Note that after aligning the stack, it pushes a duplicate copy of -* the return address before pushing the frame pointer. +* After aligning the stack, it pushes a duplicate copy of the return +* address before pushing the frame pointer. +*/ +return (state->bp == aligned_bp && *(aligned_bp +1 ) == *(last_bp +1 )); +} + +static bool is_last_ftrace_frame (struct unwind_state *state) +{ +unsigned long *last_bp =last_frame (state); +unsigned long *last_ftrace_bp = last_bp -3 ; + +/* +* When unwinding from an ftrace handler of a function called by entry +* code, the stack layout of the last frame is: +* +* bp +* parent ret addr +* bp +* function ret addr +* parent ret addr +* pt_regs +* ----------------- */ -return (state->bp == last_bp || - (state->bp == aligned_bp && *(aligned_bp+1 ) == *(last_bp+1 ))); +return (state->bp == last_ftrace_bp && + *state->bp == *(state->bp +2 ) && + *(state->bp +1 ) == *(state->bp +4 )); +} + +static bool is_last_task_frame (struct unwind_state *state) +{ +return is_last_frame (state) ||is_last_aligned_frame (state) || +is_last_ftrace_frame (state); }/*
Signature · 1 line
2.7.4
tip-bot for Josh Poimboeuf
merge bot[tip:x86/urgent] x86/unwind: Add end-of-stack check for ftrace handlers
x86/unwind: Add end-of-stack check for ftrace handlers
Commit-ID: 519fb5c3350d1b5225b27b1cac55144f79351718 Gitweb: http://git.kernel.org/tip/519fb5c3350d1b5225b27b1cac55144f79351718 Author: Josh Poimboeuf <[email protected]> AuthorDate: Tue, 23 May 2017 10:37:30 -0500 Committer: Ingo Molnar <[email protected]> CommitDate: Wed, 24 May 2017 09:05:16 +0200
x86/unwind: Add end-of-stack check for ftrace handlers
Dave Jones and Steven Rostedt reported unwinder warnings like the following:
WARNING: kernel stack frame pointer at ffff8800bda0ff30 in sshd:1090 has bad value 000055b32abf1fa8
In both cases, the unwinder was attempting to unwind from an ftrace handler into entry code. The callchain was something like:
syscall entry code
C function
ftrace handler
save_stack_trace()
The problem is that the unwinder's end-of-stack logic gets confused by the way ftrace lays out the stack frame (with fentry enabled).
I was able to recreate this warning with:
echo call_usermodehelper_exec_async:stacktrace > /sys/kernel/debug/tracing/set_ftrace_filter (exit login session)
I considered fixing this by changing the ftrace code to rewrite the stack to make the unwinder happy. But that seemed too intrusive after I implemented it. Instead, just add another check to the unwinder's end-of-stack logic to detect this special case.
Side note: We could probably get rid of these end-of-stack checks by encoding the frame pointer for syscall entry just like we do for interrupt entry. That would be simpler, but it would also be a lot more intrusive since it would slightly affect the performance of every syscall.
--- arch/x86/kernel/unwind_frame.c | 49 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 9 deletions(-)
arch/x86/kernel/unwind_frame.c +40 -9
@@ -104,6 +104,11 @@ static inline unsigned long *last_frame(struct unwind_state *state)return (unsigned long *)task_pt_regs (state->task) -2 ; } +static bool is_last_frame (struct unwind_state *state) +{ +return state->bp ==last_frame (state); +} +#ifdef CONFIG_X86_32 #define GCC_REALIGN_WORDS 3#else
@@ -115,16 +120,15 @@ static inline unsigned long *last_aligned_frame(struct unwind_state *state)return last_frame (state) -GCC_REALIGN_WORDS ; } -static bool is_last_task_frame (struct unwind_state *state) +static bool is_last_aligned_frame (struct unwind_state *state) {unsigned long *last_bp =last_frame (state);unsigned long *aligned_bp =last_aligned_frame (state);/* -* We have to check for the last task frame at two different locations -* because gcc can occasionally decide to realign the stack pointer and -* change the offset of the stack frame in the prologue of a function -* called by head/entry code. Examples: +* GCC can occasionally decide to realign the stack pointer and change +* the offset of the stack frame in the prologue of a function called +* by head/entry code. Examples: * * <start_secondary>: * push %edi
@@ -141,11 +145,38 @@ static bool is_last_task_frame(struct unwind_state *state)* push %rbp * mov %rsp,%rbp * -* Note that after aligning the stack, it pushes a duplicate copy of -* the return address before pushing the frame pointer. +* After aligning the stack, it pushes a duplicate copy of the return +* address before pushing the frame pointer. +*/ +return (state->bp == aligned_bp && *(aligned_bp +1 ) == *(last_bp +1 )); +} + +static bool is_last_ftrace_frame (struct unwind_state *state) +{ +unsigned long *last_bp =last_frame (state); +unsigned long *last_ftrace_bp = last_bp -3 ; + +/* +* When unwinding from an ftrace handler of a function called by entry +* code, the stack layout of the last frame is: +* +* bp +* parent ret addr +* bp +* function ret addr +* parent ret addr +* pt_regs +* ----------------- */ -return (state->bp == last_bp || - (state->bp == aligned_bp && *(aligned_bp+1 ) == *(last_bp+1 ))); +return (state->bp == last_ftrace_bp && + *state->bp == *(state->bp +2 ) && + *(state->bp +1 ) == *(state->bp +4 )); +} + +static bool is_last_task_frame (struct unwind_state *state) +{ +return is_last_frame (state) ||is_last_aligned_frame (state) || +is_last_ftrace_frame (state); }/*
Quoted 3 lines · Josh Poimboeuf
Patch 1 reverts a previous fix because it introduced a new bug.
Patch 2 re-fixes the original bug.
I could confirm that the new bug has gone after applying both patches. Also I think that I have reproduced the original bug and it seems to have gone as well. Therefore feel free to use for both patches:
Best Regards,
Petr
Quoted 12 lines
Josh Poimboeuf (2):
Revert "x86/entry: Fix the end of the stack for newly forked tasks" x86/unwind: Add end-of-stack check for ftrace handlersarch/x86/entry/entry_32.S | 30 ++++++++++++++++---------- arch/x86/entry/entry_64.S | 11 ++++------ arch/x86/kernel/unwind_frame.c | 49 ++++++++++++++++++++++++++++++++++-------- 3 files changed, 63 insertions(+), 27 deletions(-)Signature · 1 line
2.7.4