Virtually mapped stacks with guard pages (x86, core)
Subsystems (11)
Andy Lutomirski
maintainercover Virtually mapped stacks with guard pages (x86, core)
Since the dawn of time, a kernel stack overflow has been a real PITA to debug, has caused nondeterministic crashes some time after the actual overflow, and has generally been easy to exploit for root.
With this series, arches can enable HAVE_ARCH_VMAP_STACK. Arches that enable it (just x86 for now) get virtually mapped stacks with guard pages. This causes reliable faults when the stack overflows.
If the arch implements it well, we get a nice OOPS on stack overflow (as opposed to panicing directly or otherwise exploding badly). On x86, the OOPS is nice, has a usable call trace, and the overflowing task is killed cleanly.
This does not address interrupt stacks.
Andy Lutomirski (12):
x86/cpa: In populate_pgd, don't set the pgd entry until it's populated x86/cpa: Warn if kernel_unmap_pages_in_pgd is used inappropriately
mm: Track NR_KERNEL_STACK in pages instead of number of stacks
mm: Move memcg stack accounting to account_kernel_stack
fork: Add generic vmalloced stack support
x86/die: Don't try to recover from an OOPS on a non-default stack x86/dumpstack: When OOPSing, rewind the stack before do_exit x86/dumpstack: When dumping stack bytes due to OOPS, start with
regs->sp
x86/dumpstack: Try harder to get a call trace on stack overflow x86/dumpstack/64: Handle faults when printing the "Stack:" part of an
OOPS
x86/mm/64: Enable vmapped stacks
x86/mm: Improve stack-overflow #PF handling
Ingo Molnar (1):
x86/mm/hotplug: Don't remove PGD entries in remove_pagetable()
arch/Kconfig | 12 ++++++++++ arch/x86/Kconfig | 1 + arch/x86/entry/entry_32.S | 11 +++++++++ arch/x86/entry/entry_64.S | 11 +++++++++ arch/x86/include/asm/switch_to.h | 28 +++++++++++++++++++++- arch/x86/include/asm/traps.h | 6 +++++ arch/x86/kernel/dumpstack.c | 17 +++++++++++++- arch/x86/kernel/dumpstack_32.c | 4 +++- arch/x86/kernel/dumpstack_64.c | 16 ++++++++++--- arch/x86/kernel/traps.c | 32 +++++++++++++++++++++++++ arch/x86/mm/fault.c | 39 ++++++++++++++++++++++++++++++ arch/x86/mm/init_64.c | 27 --------------------- arch/x86/mm/pageattr.c | 7 +++++- arch/x86/mm/tlb.c | 15 ++++++++++++ fs/proc/meminfo.c | 2 +- kernel/fork.c | 51 ++++++++++++++++++++++++++++++---------- mm/page_alloc.c | 3 +-- 17 files changed, 233 insertions(+), 49 deletions(-)
Signature · 1 line
2.7.4
Andy Lutomirski
maintainerPATCH 1/13 x86/mm/hotplug: Don't remove PGD entries in remove_pagetable()
From: Ingo Molnar <[email protected]>
So when memory hotplug removes a piece of physical memory from pagetable mappings, it also frees the underlying PGD entry.
This complicates PGD management, so don't do this. We can keep the PGD mapped and the PUD table all clear - it's only a single 4K page per 512 GB of memory hotplugged.
Message-Id: <[email protected]>
--- arch/x86/mm/init_64.c | 27 --------------------------- 1 file changed, 27 deletions(-)
arch/x86/mm/init_64.c +0 -27
@@ -702,27 +702,6 @@ static void __meminit free_pmd_table(pmd_t *pmd_start, pud_t *pud)spin_unlock (&init_mm.page_table_lock); } -/* Return true if pgd is changed, otherwise return false. */ -static bool __meminit free_pud_table (pud_t *pud_start,pgd_t *pgd) -{ -pud_t *pud; -int i; - -for (i =0 ; i <PTRS_PER_PUD ; i++) { - pud = pud_start + i; -if (pud_val (*pud)) -return false; - } - -/* free a pud table */ -free_pagetable (pgd_page (*pgd),0 ); -spin_lock (&init_mm.page_table_lock); -pgd_clear (pgd); -spin_unlock (&init_mm.page_table_lock); - -return true; -} -static void __meminit remove_pte_table (pte_t *pte_start,unsigned long addr,unsigned long end,bool direct)
@@ -913,7 +892,6 @@ remove_pagetable(unsigned long start, unsigned long end, bool direct)unsigned long addr;pgd_t *pgd;pud_t *pud; -bool pgd_changed = false;for (addr = start; addr < end; addr = next) { next =pgd_addr_end (addr, end);
@@ -924,13 +902,8 @@ remove_pagetable(unsigned long start, unsigned long end, bool direct) pud = (pud_t *)pgd_page_vaddr (*pgd);remove_pud_table (pud, addr, next, direct); -if (free_pud_table (pud, pgd)) - pgd_changed = true; } -if (pgd_changed) -sync_global_pgds (start, end -1 ,1 ); -flush_tlb_all (); }
Signature · 1 line
2.7.4
Andy Lutomirski
maintainerPATCH 2/13 x86/cpa: In populate_pgd, don't set the pgd entry until it's populated
In reply to Andy LutomirskiThis avoids pointless races in which another CPU or task might see a partially populated global pgd entry. These races should normally be harmless, but, if another CPU propagates the entry via vmalloc_fault and then populate_pgd fails (due to memory allocation failure, for example), this prevents a use-after-free of the pgd entry.
--- arch/x86/mm/pageattr.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
arch/x86/mm/pageattr.c +3 -1
@@ -1113,7 +1113,9 @@ static int populate_pgd(struct cpa_data *cpa, unsigned long addr) ret =populate_pud (cpa, addr, pgd_entry, pgprot);if (ret <0 ) { -unmap_pgd_range (cpa->pgd, addr, +if (pud) +free_page ((unsigned long )pud); +unmap_pud_range (pgd_entry, addr, addr + (cpa->numpages <<PAGE_SHIFT ));return ret; }
Signature · 1 line
2.7.4
Andy Lutomirski
maintainerPATCH 3/13 x86/cpa: Warn if kernel_unmap_pages_in_pgd is used inappropriately
In reply to Andy LutomirskiIt's currently only used in the EFI code, which is safe AFAICT. Warn if anyone tries to use it on the normal kernel pgd.
--- arch/x86/mm/pageattr.c | 3 +++ 1 file changed, 3 insertions(+)
arch/x86/mm/pageattr.c +3 -0
@@ -1996,6 +1996,9 @@ out:void kernel_unmap_pages_in_pgd (pgd_t *root,unsigned long address,unsigned numpages) { +/* Unmapping kernel entries from init_mm's pgd is not allowed. */ +WARN_ON (root == init_mm.pgd); +unmap_pgd_range (root, address, address + (numpages <<PAGE_SHIFT )); }
Signature · 1 line
2.7.4
Andy Lutomirski
maintainerPATCH 4/13 mm: Track NR_KERNEL_STACK in pages instead of number of stacks
In reply to Andy LutomirskiCurrently, NR_KERNEL_STACK tracks the number of kernel stacks in a zone. This only makes sense if each kernel stack exists entirely in one zone, and allowing vmapped stacks could break this assumption.
It turns out that the code for tracking kernel stack allocations in units of pages is slightly simpler, so just switch to counting pages.
--- fs/proc/meminfo.c | 2 +- kernel/fork.c | 3 ++- mm/page_alloc.c | 3 +-- 3 files changed, 4 insertions(+), 4 deletions(-)
fs/proc/meminfo.c +1 -1
@@ -145,7 +145,7 @@ static int meminfo_proc_show(struct seq_file *m, void *v) global_page_state(NR_SLAB_UNRECLAIMABLE)),K (global_page_state (NR_SLAB_RECLAIMABLE )),K (global_page_state (NR_SLAB_UNRECLAIMABLE )), -global_page_state (NR_KERNEL_STACK ) *THREAD_SIZE /1024 , +K (global_page_state (NR_KERNEL_STACK )),K (global_page_state (NR_PAGETABLE )), #ifdef CONFIG_QUICKLIST K(quicklist_total_size()),
kernel/fork.c +2 -1
@@ -225,7 +225,8 @@ static void account_kernel_stack(struct thread_info *ti, int account) {struct zone *zone =page_zone (virt_to_page (ti)); -mod_zone_page_state (zone,NR_KERNEL_STACK , account); +mod_zone_page_state (zone,NR_KERNEL_STACK , +THREAD_SIZE /PAGE_SIZE * account); }void free_task (struct task_struct *tsk)
mm/page_alloc.c +1 -2
@@ -4457,8 +4457,7 @@ void show_free_areas(unsigned int filter)K (zone_page_state (zone,NR_SHMEM )),K (zone_page_state (zone,NR_SLAB_RECLAIMABLE )),K (zone_page_state (zone,NR_SLAB_UNRECLAIMABLE )), -zone_page_state (zone,NR_KERNEL_STACK ) * -THREAD_SIZE /1024 , +K (zone_page_state (zone,NR_KERNEL_STACK )),K (zone_page_state (zone,NR_PAGETABLE )),K (zone_page_state (zone,NR_UNSTABLE_NFS )), K(zone_page_state(zone, NR_BOUNCE)),
Signature · 1 line
2.7.4
Re: [PATCH 04/13] mm: Track NR_KERNEL_STACK in pages instead of number of stacks
On Wed, Jun 15, 2016 at 05:28:26PM -0700, Andy Lutomirski wrote:
...
kernel/fork.c static void account_kernel_stack(struct thread_info *ti, int account) · line 229
Quoted 7 lines
kernel/fork.c +2
-1@@ -225,7 +225,8 @@ static void account_kernel_stack(struct thread_info *ti, int account) {struct zone *zone =page_zone (virt_to_page (ti)); -mod_zone_page_state (zone,NR_KERNEL_STACK , account); +mod_zone_page_state (zone,NR_KERNEL_STACK , +THREAD_SIZE /PAGE_SIZE * account);
It won't work if THREAD_SIZE < PAGE_SIZE. Is there an arch with such a thread size, anyway? If no, we should probably drop thread_info_cache.
Re: [PATCH 04/13] mm: Track NR_KERNEL_STACK in pages instead of number of stacks
Quoted 6 lines · Vladimir Davydov
On Wed, Jun 15, 2016 at 05:28:26PM -0700, Andy Lutomirski wrote:
...Quoted 4 lines
kernel/fork.c +0
-0@@ -225,7 +225,8 @@ static void account_kernel_stack(struct thread_info *ti, int account) {struct zone *zone =page_zone (virt_to_page (ti));
Quoted 6 lines
kernel/fork.c static void account_kernel_stack(struct thread_info *ti, int account) · line 229
Quoted 3 lines
kernel/fork.c +2
-1-mod_zone_page_state (zone,NR_KERNEL_STACK , account); +mod_zone_page_state (zone,NR_KERNEL_STACK , +THREAD_SIZE /PAGE_SIZE * account);It won't work if
THREAD_SIZE<PAGE_SIZE. Is there an arch with such a thread size, anyway? If no, we should probably dropthread_info_cache.
On a quick grep, I can't find any. I'll add a BUILD_BUG_ON for now.
--Andy
Re: [PATCH 04/13] mm: Track NR_KERNEL_STACK in pages instead of number of stacks
Quoted 8 lines · Andy Lutomirski
Quoted 6 lines · Vladimir Davydov
On Wed, Jun 15, 2016 at 05:28:26PM -0700, Andy Lutomirski wrote:
...Quoted 4 lines
kernel/fork.c +0
-0@@ -225,7 +225,8 @@ static void account_kernel_stack(struct thread_info *ti, int account) {struct zone *zone =page_zone (virt_to_page (ti));
Quoted 8 lines
Quoted 6 lines
kernel/fork.c static void account_kernel_stack(struct thread_info *ti, int account) · line 229
Quoted 3 lines
kernel/fork.c +2
-1-mod_zone_page_state (zone,NR_KERNEL_STACK , account); +mod_zone_page_state (zone,NR_KERNEL_STACK , +THREAD_SIZE /PAGE_SIZE * account);It won't work if
THREAD_SIZE<PAGE_SIZE. Is there an arch with such a thread size, anyway? If no, we should probably dropthread_info_cache.On a quick grep, I can't find any. I'll add a
BUILD_BUG_ONfor now.
Correction. frv has 16KiB pages and 8KiB stacks. I'll rework it to count NR_KERNEL_STACK in KiB. Sigh.
--Andy
Quoted 2 lines
--Andy
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
Re: [PATCH 04/13] mm: Track NR_KERNEL_STACK in pages instead of number of stacks
In reply to Andy LutomirskiQuoted 13 lines · Andy Lutomirski
Currently,
NR_KERNEL_STACKtracks the number of kernel stacks in a zone. This only makes sense if each kernel stack exists entirely in one zone, and allowing vmapped stacks could break this assumption.It turns out that the code for tracking kernel stack allocations in units of pages is slightly simpler, so just switch to counting pages.
Cc Vladimir Davydov <[email protected]>Cc Johannes Weiner <[email protected]>Cc Michal Hocko <[email protected]>Signed-off-by Andy Lutomirski <[email protected]>
Quoted 5 lines
--- fs/proc/meminfo.c | 2 +- kernel/fork.c | 3 ++- mm/page_alloc.c | 3 +-- 3 files changed, 4 insertions(+), 4 deletions(-)
You missed another usage of NR_KERNEL_STACK in drivers/base/node.c.
Signature · 1 line
Josh
Re: [PATCH 04/13] mm: Track NR_KERNEL_STACK in pages instead of number of stacks
Quoted 20 lines · Josh Poimboeuf
Quoted 13 lines · Andy Lutomirski
Currently,
NR_KERNEL_STACKtracks the number of kernel stacks in a zone. This only makes sense if each kernel stack exists entirely in one zone, and allowing vmapped stacks could break this assumption.It turns out that the code for tracking kernel stack allocations in units of pages is slightly simpler, so just switch to counting pages.
Cc Vladimir Davydov <[email protected]>Cc Johannes Weiner <[email protected]>Cc Michal Hocko <[email protected]>Signed-off-by Andy Lutomirski <[email protected]>Quoted 5 lines
--- fs/proc/meminfo.c | 2 +- kernel/fork.c | 3 ++- mm/page_alloc.c | 3 +-- 3 files changed, 4 insertions(+), 4 deletions(-)
Quoted 1 line
You missed another usage of
NR_KERNEL_STACKindrivers/base/node.c.
Thanks.
The real reason I cc'd you was so you could look at rewind_stack_do_exit and the sneaky trick I did in no_context in the last patch, though. :) Both survive objtool, but I figured I'd check with objtool's author as well. If there was a taint bit I could set saying "kernel is hosed -- don't try to apply live patches any more", I'd have extra confidence.
--Andy
Re: [PATCH 04/13] mm: Track NR_KERNEL_STACK in pages instead of number of stacks
Quoted 23 lines · Andy Lutomirski
Quoted 20 lines · Josh Poimboeuf
Quoted 13 lines · Andy Lutomirski
Currently,
NR_KERNEL_STACKtracks the number of kernel stacks in a zone. This only makes sense if each kernel stack exists entirely in one zone, and allowing vmapped stacks could break this assumption.It turns out that the code for tracking kernel stack allocations in units of pages is slightly simpler, so just switch to counting pages.
Cc Vladimir Davydov <[email protected]>Cc Johannes Weiner <[email protected]>Cc Michal Hocko <[email protected]>Signed-off-by Andy Lutomirski <[email protected]>Quoted 5 lines
--- fs/proc/meminfo.c | 2 +- kernel/fork.c | 3 ++- mm/page_alloc.c | 3 +-- 3 files changed, 4 insertions(+), 4 deletions(-)Quoted 1 line
You missed another usage of
NR_KERNEL_STACKindrivers/base/node.c.
Quoted 8 lines
Thanks.
The real reason I cc'd you was so you could look at
rewind_stack_do_exitand the sneaky trick I did inno_contextin the last patch, though. :) Both survive objtool, but I figured I'd check with objtool's author as well. If there was a taint bit I could set saying "kernel is hosed -- don't try to apply live patches any more", I'd have extra confidence.
I think it all looks fine from an objtool and a live patching standpoint. Other than my previous comment about setting the stack pointer correctly before calling do_exit(), I didn't see anything else which would mess up the stack of a sleeping task, which is all I really care about.
Signature · 1 line
Josh
Andy Lutomirski
maintainerPATCH 5/13 mm: Move memcg stack accounting to account_kernel_stack
In reply to Andy LutomirskiWe should account for stacks regardless of stack size. Move it into account_kernel_stack.
--- kernel/fork.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-)
kernel/fork.c +6 -9
@@ -165,20 +165,12 @@ static struct thread_info *alloc_thread_info_node(struct task_struct *tsk,struct page *page =alloc_kmem_pages_node (node,THREADINFO_GFP ,THREAD_SIZE_ORDER ); -if (page) -memcg_kmem_update_page_stat (page,MEMCG_KERNEL_STACK , -1 <<THREAD_SIZE_ORDER ); - return page ? page_address(page) : NULL; }static inline void free_thread_info (struct thread_info *ti) { -struct page *page =virt_to_page (ti); - -memcg_kmem_update_page_stat (page,MEMCG_KERNEL_STACK , - -(1 <<THREAD_SIZE_ORDER )); -__free_kmem_pages (page,THREAD_SIZE_ORDER ); +free_kmem_pages ((unsigned long )ti,THREAD_SIZE_ORDER ); }# else static struct kmem_cache *thread_info_cache;
@@ -227,6 +219,11 @@ static void account_kernel_stack(struct thread_info *ti, int account)mod_zone_page_state (zone,NR_KERNEL_STACK ,THREAD_SIZE /PAGE_SIZE * account); + +/* All stack pages belong to the same memcg. */ +memcg_kmem_update_page_stat ( +virt_to_page (ti),MEMCG_KERNEL_STACK , + account * (THREAD_SIZE /PAGE_SIZE )); }void free_task (struct task_struct *tsk)
Signature · 1 line
2.7.4
Andy Lutomirski
maintainerPATCH 6/13 fork: Add generic vmalloced stack support
In reply to Andy LutomirskiIf CONFIG_VMAP_STACK is selected, kernel stacks are allocated with vmalloc_node.
--- arch/Kconfig | 12 ++++++++++++ kernel/fork.c | 45 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 49 insertions(+), 8 deletions(-)
arch/Kconfig +12 -0
@@ -658,4 +658,16 @@ config ARCH_NO_COHERENT_DMA_MMAP config CPU_NO_EFFICIENT_FFS def_bool n +config HAVE_ARCH_VMAP_STACK + def_bool n + +config VMAP_STACK + bool "Use a virtually-mapped stack" + depends on HAVE_ARCH_VMAP_STACK + ---help--- + Enable this if you want the use virtually-mapped kernel stacks + with guard pages. This causes kernel stack overflows to be + caught immediately rather than causing difficult-to-diagnose + corruption. + source "kernel/gcov/Kconfig"
kernel/fork.c +37 -8
71 lines not on this page · raw message
Signature · 1 line
2.7.4
Re: [PATCH 06/13] fork: Add generic vmalloced stack support
Quoted 14 lines · Andy Lutomirski
If
CONFIG_VMAP_STACKis selected, kernel stacks are allocated withvmalloc_node.Signed-off-by Andy Lutomirski <[email protected]>--- arch/Kconfig | 12 ++++++++++++ kernel/fork.c | 45 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 49 insertions(+), 8 deletions(-)arch/Kconfig +0
-0
arch/Kconfig config ARCH_NO_COHERENT_DMA_MMAP · line 662
Quoted 5 lines
arch/Kconfig +2
-0@@ -658,4 +658,16 @@ config ARCH_NO_COHERENT_DMA_MMAP config CPU_NO_EFFICIENT_FFS def_bool n +config HAVE_ARCH_VMAP_STACK + def_bool n
In the style of HAVE_ARCH_SECCOMP_FILTER, can you detail all the things an architecture needs to do to correctly support a vmap stack? This will help with arch porting.
-Kees
Signature · 2 lines
Kees Cook Chrome OS & Brillo Security
Re: [PATCH 06/13] fork: Add generic vmalloced stack support
Quoted 18 lines · Kees Cook
Quoted 17 lines · Andy Lutomirski
If
CONFIG_VMAP_STACKis selected, kernel stacks are allocated withvmalloc_node.Signed-off-by Andy Lutomirski <[email protected]>--- arch/Kconfig | 12 ++++++++++++ kernel/fork.c | 45 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 49 insertions(+), 8 deletions(-)arch/Kconfig +0
-0@@ -658,4 +658,16 @@ config ARCH_NO_COHERENT_DMA_MMAP config CPU_NO_EFFICIENT_FFS def_bool n
Quoted 6 lines
arch/Kconfig config ARCH_NO_COHERENT_DMA_MMAP · line 662
Quoted 2 lines
arch/Kconfig +2
-0+config HAVE_ARCH_VMAP_STACK + def_bool nIn the style of
HAVE_ARCH_SECCOMP_FILTER, can you detail all the things an architecture needs to do to correctly support a vmap stack? This will help with arch porting.
Done.
Andy Lutomirski
maintainerPATCH 7/13 x86/die: Don't try to recover from an OOPS on a non-default stack
In reply to Andy LutomirskiIt's not going to work, because the scheduler will explode if we try to schedule when running on an IST stack or similar.
This will matter when we let kernel stack overflows (which are #DF) call die().
--- arch/x86/kernel/dumpstack.c | 3 +++ 1 file changed, 3 insertions(+)
arch/x86/kernel/dumpstack.c +3 -0
@@ -247,6 +247,9 @@ void oops_end(unsigned long flags, struct pt_regs *regs, int signr)return ;if (in_interrupt ())panic ("Fatal exception in interrupt" ); +if (((current_stack_pointer () ^ (current_top_of_stack () -1 )) + & ~(THREAD_SIZE -1 )) !=0 ) +panic ("Fatal exception on special stack" );if (panic_on_oops)panic ("Fatal exception" );do_exit (signr);
Signature · 1 line
2.7.4
Andy Lutomirski
maintainerPATCH 8/13 x86/dumpstack: When OOPSing, rewind the stack before do_exit
In reply to Andy LutomirskiIf we call do_exit with a clean stack, we greatly reduce the risk of recursive oopses due to stack overflow in do_exit, and we allow do_exit to work even if we OOPS from an IST stack. The latter gives us a much better chance of surviving long enough after we detect a stack overflow to write out our logs.
I intentionally separated this from the preceding patch that disables do_exit-on-OOPS on IST stacks. This way, if we need to revert this patch, we still end up in an acceptable state wrt stack overflow handling.
--- arch/x86/entry/entry_32.S | 11 +++++++++++ arch/x86/entry/entry_64.S | 11 +++++++++++ arch/x86/kernel/dumpstack.c | 13 +++++++++---- 3 files changed, 31 insertions(+), 4 deletions(-)
arch/x86/entry/entry_32.S +11 -0
@@ -1153,3 +1153,14 @@ ENTRY(async_page_fault) jmp error_code END(async_page_fault) #endif + +ENTRY(rewind_stack_do_exit) + /* Prevent any naive code from trying to unwind to our caller. */ + xorl %ebp, %ebp + + movl PER_CPU_VAR(cpu_current_top_of_stack), %esi + leal -TOP_OF_KERNEL_STACK_PADDING-PT_OLDSS(%esi), %esp + + call do_exit +1: jmp 1b +END(rewind_stack_do_exit)
arch/x86/entry/entry_64.S +11 -0
@@ -1423,3 +1423,14 @@ ENTRY(ignore_sysret) mov $-ENOSYS, %eax sysret END(ignore_sysret) + +ENTRY(rewind_stack_do_exit) + /* Prevent any naive code from trying to unwind to our caller. */ + xorl %ebp, %ebp + + movq PER_CPU_VAR(cpu_current_top_of_stack), %rax + leaq -TOP_OF_KERNEL_STACK_PADDING-SS(%rax), %rsp + + call do_exit +1: jmp 1b +END(rewind_stack_do_exit)
arch/x86/kernel/dumpstack.c +9 -4
@@ -228,6 +228,8 @@ unsigned long oops_begin(void)EXPORT_SYMBOL_GPL (oops_begin);NOKPROBE_SYMBOL (oops_begin); +extern void __noreturn rewind_stack_do_exit (int signr); +void oops_end (unsigned long flags,struct pt_regs *regs,int signr) {if (regs &&kexec_should_crash (current))
@@ -247,12 +249,15 @@ void oops_end(unsigned long flags, struct pt_regs *regs, int signr)return ;if (in_interrupt ())panic ("Fatal exception in interrupt" ); -if (((current_stack_pointer () ^ (current_top_of_stack () -1 )) - & ~(THREAD_SIZE -1 )) !=0 ) -panic ("Fatal exception on special stack" );if (panic_on_oops)panic ("Fatal exception" ); -do_exit (signr); + +/* +* We're not going to return, but we might be on an IST stack or +* have very little stack space left. Rewind the stack and kill +* the task. +*/ +rewind_stack_do_exit (signr); }NOKPROBE_SYMBOL (oops_end);
Signature · 1 line
2.7.4
Re: [PATCH 08/13] x86/dumpstack: When OOPSing, rewind the stack before do_exit
Quoted 42 lines · Andy Lutomirski
If we call
do_exitwith a clean stack, we greatly reduce the risk of recursive oopses due to stack overflow indo_exit, and we allowdo_exitto work even if we OOPS from an IST stack. The latter gives us a much better chance of surviving long enough after we detect a stack overflow to write out our logs.I intentionally separated this from the preceding patch that disables do_exit-on-OOPS on IST stacks. This way, if we need to revert this patch, we still end up in an acceptable state wrt stack overflow handling.
Signed-off-by Andy Lutomirski <[email protected]>--- arch/x86/entry/entry_32.S | 11 +++++++++++ arch/x86/entry/entry_64.S | 11 +++++++++++ arch/x86/kernel/dumpstack.c | 13 +++++++++---- 3 files changed, 31 insertions(+), 4 deletions(-)arch/x86/entry/entry_32.S +11
-0@@ -1153,3 +1153,14 @@ ENTRY(async_page_fault) jmp error_code END(async_page_fault) #endif + +ENTRY(rewind_stack_do_exit) + /* Prevent any naive code from trying to unwind to our caller. */ + xorl %ebp, %ebp + + movl PER_CPU_VAR(cpu_current_top_of_stack), %esi + leal -TOP_OF_KERNEL_STACK_PADDING-PT_OLDSS(%esi), %esp + + call do_exit +1: jmp 1b +END(rewind_stack_do_exit)arch/x86/entry/entry_64.S +0
-0
arch/x86/entry/entry_64.S ENTRY(ignore_sysret) · line 1432
Quoted 10 lines
arch/x86/entry/entry_64.S +7
-0@@ -1423,3 +1423,14 @@ ENTRY(ignore_sysret) mov $-ENOSYS, %eax sysret END(ignore_sysret) + +ENTRY(rewind_stack_do_exit) + /* Prevent any naive code from trying to unwind to our caller. */ + xorl %ebp, %ebp + + movq PER_CPU_VAR(cpu_current_top_of_stack), %rax + leaq -TOP_OF_KERNEL_STACK_PADDING-SS(%rax), %rsp
I think this should be:
leaq -TOP_OF_KERNEL_STACK_PADDING-SIZEOF_PTREGS, %rsp
That way when it calls do_exit(), the stack frame will be placed at the conventional spot where a smart unwinder would expect to find it.
Signature · 1 line
Josh
Re: [PATCH 08/13] x86/dumpstack: When OOPSing, rewind the stack before do_exit
Quoted 56 lines · Josh Poimboeuf
Quoted 42 lines · Andy Lutomirski
If we call
do_exitwith a clean stack, we greatly reduce the risk of recursive oopses due to stack overflow indo_exit, and we allowdo_exitto work even if we OOPS from an IST stack. The latter gives us a much better chance of surviving long enough after we detect a stack overflow to write out our logs.I intentionally separated this from the preceding patch that disables do_exit-on-OOPS on IST stacks. This way, if we need to revert this patch, we still end up in an acceptable state wrt stack overflow handling.
Signed-off-by Andy Lutomirski <[email protected]>--- arch/x86/entry/entry_32.S | 11 +++++++++++ arch/x86/entry/entry_64.S | 11 +++++++++++ arch/x86/kernel/dumpstack.c | 13 +++++++++---- 3 files changed, 31 insertions(+), 4 deletions(-)arch/x86/entry/entry_32.S +11
-0@@ -1153,3 +1153,14 @@ ENTRY(async_page_fault) jmp error_code END(async_page_fault) #endif + +ENTRY(rewind_stack_do_exit) + /* Prevent any naive code from trying to unwind to our caller. */ + xorl %ebp, %ebp + + movl PER_CPU_VAR(cpu_current_top_of_stack), %esi + leal -TOP_OF_KERNEL_STACK_PADDING-PT_OLDSS(%esi), %esp + + call do_exit +1: jmp 1b +END(rewind_stack_do_exit)arch/x86/entry/entry_64.S +0
-0arch/x86/entry/entry_64.S ENTRY(ignore_sysret) · line 1432
Quoted 10 lines
arch/x86/entry/entry_64.S +7
-0@@ -1423,3 +1423,14 @@ ENTRY(ignore_sysret) mov $-ENOSYS, %eax sysret END(ignore_sysret) + +ENTRY(rewind_stack_do_exit) + /* Prevent any naive code from trying to unwind to our caller. */ + xorl %ebp, %ebp + + movq PER_CPU_VAR(cpu_current_top_of_stack), %rax + leaq -TOP_OF_KERNEL_STACK_PADDING-SS(%rax), %rspI think this should be:
Quoted 4 lines
leaq -TOP_OF_KERNEL_STACK_PADDING-SIZEOF_PTREGS, %rsp
That way when it calls
do_exit(), the stack frame will be placed at the conventional spot where a smart unwinder would expect to find it.
Whoops!
--Andy
Andy Lutomirski
maintainerPATCH 9/13 x86/dumpstack: When dumping stack bytes due to OOPS, start with regs->sp
In reply to Andy LutomirskiThe comment suggests that show_stack(NULL, NULL) should backtrace the current context, but the code doesn't match the comment. If regs are given, start the "Stack:" hexdump at regs->sp.
--- arch/x86/kernel/dumpstack_32.c | 4 +++- arch/x86/kernel/dumpstack_64.c | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-)
arch/x86/kernel/dumpstack_32.c +3 -1
@@ -98,7 +98,9 @@ show_stack_log_lvl(struct task_struct *task, struct pt_regs *regs,int i;if (sp ==NULL ) { -if (task) +if (regs) + sp = (unsigned long *)regs->sp; +else if (task) sp = (unsigned long *)task->thread.sp;else sp = (unsigned long *)&sp;
arch/x86/kernel/dumpstack_64.c +3 -1
@@ -266,7 +266,9 @@ show_stack_log_lvl(struct task_struct *task, struct pt_regs *regs,* back trace for this cpu: */ if (sp ==NULL ) { -if (task) +if (regs) + sp = (unsigned long *)regs->sp; +else if (task) sp = (unsigned long *)task->thread.sp;else sp = (unsigned long *)&sp;
Signature · 1 line
2.7.4
Borislav Petkov
maintainerRe: [PATCH 09/13] x86/dumpstack: When dumping stack bytes due to OOPS, start with regs->sp
Quoted 5 lines · Andy Lutomirski
The comment suggests that
show_stack(NULL, NULL)should backtrace the current context, but the code doesn't match the comment. If regs are given, start the "Stack:" hexdump atregs->sp.Signed-off-by Andy Lutomirski <[email protected]>
Very nice:
[ 0.020008] unchecked MSR access error: RDMSR from 0x1b0 at rIP: 0xffffffff8102849f (init_intel_energy_perf.part.3+0xf/0xd0) [ 0.024003] 0000000000000000 ffffffff81c03f10 ffffffff81028a2f 0000000000000000 [ 0.028799] ffffffff81cad5e0 ffffffff81d5e920 ffffffff81c03f38 ffffffff810271f5 [ 0.034887] ffffffffffffffff ffff88007efd18c0 ffffffff81d5e920 ffffffff81c03f48 [ 0.038883] Call Trace: [ 0.040009] [<ffffffff81028a2f>] init_intel+0xdf/0x2b0 [ 0.042055] [<ffffffff810271f5>] identify_cpu+0x2f5/0x4f0 [ 0.044005] [<ffffffff81cdc198>] identify_boot_cpu+0x10/0x7a [ 0.045577] [<ffffffff81cdc236>] check_bugs+0x9/0x2d [ 0.047033] [<ffffffff81cd1ece>] start_kernel+0x3bd/0x3d9 [ 0.048005] [<ffffffff81cd1294>] x86_64_start_reservations+0x2f/0x31 [ 0.049668] [<ffffffff81cd13fe>] x86_64_start_kernel+0x168/0x176
That's exactly what I wanted to see! Thanks for the hints.
Here's what I did:
---
arch/x86/include/asm/kdebug.h +1 -0
@@ -26,6 +26,7 @@ extern void die(const char *, struct pt_regs *,long);extern int __must_check __die (const char *,struct pt_regs *,long );extern void show_trace (struct task_struct *t,struct pt_regs *regs,unsigned long *sp,unsigned long bp); +extern void show_stack_regs (struct pt_regs *regs);extern void __show_regs (struct pt_regs *regs,int all);extern unsigned long oops_begin (void );extern void oops_end (unsigned long ,struct pt_regs *,int signr);
arch/x86/kernel/dumpstack.c +5 -0
@@ -206,6 +206,11 @@ void show_stack(struct task_struct *task, unsigned long *sp)show_stack_log_lvl (task,NULL , sp, bp,"" ); } +void show_stack_regs (struct pt_regs *regs) +{ +show_stack_log_lvl (current, regs, (unsigned long *)regs->sp, regs->bp,"" ); +} +static arch_spinlock_t die_lock = __ARCH_SPIN_LOCK_UNLOCKED;static int die_owner =-1 ;static unsigned int die_nest_count;
arch/x86/mm/extable.c +8 -5
@@ -1,6 +1,7 @@#include <linux/module.h> #include <asm/uaccess.h> #include <asm/traps.h> +#include <asm/kdebug.h> typedef bool (*ex_handler_t )(const struct exception_table_entry *,struct pt_regs *,int );
@@ -46,8 +47,9 @@ EXPORT_SYMBOL(ex_handler_ext);bool ex_handler_rdmsr_unsafe (const struct exception_table_entry *fixup,struct pt_regs *regs,int trapnr) { -WARN_ONCE (1 ,"unchecked MSR access error: RDMSR from 0x%x\n" , - (unsigned int )regs->cx); +if (pr_warn_once ("unchecked MSR access error: RDMSR from 0x%x at rIP: 0x%lx (%pF)\n" , + (unsigned int )regs->cx, regs->ip, (void *)regs->ip)) +show_stack_regs (regs);/* Pretend that the read succeeded and returned 0. */ regs->ip =ex_fixup_addr (fixup);
@@ -60,9 +62,10 @@ EXPORT_SYMBOL(ex_handler_rdmsr_unsafe);bool ex_handler_wrmsr_unsafe (const struct exception_table_entry *fixup,struct pt_regs *regs,int trapnr) { -WARN_ONCE (1 ,"unchecked MSR access error: WRMSR to 0x%x (tried to write 0x%08x%08x)\n" , - (unsigned int )regs->cx, - (unsigned int )regs->dx, (unsigned int )regs->ax); +if (pr_warn_once ("unchecked MSR access error: WRMSR to 0x%x (tried to write 0x%08x%08x) at rIP: 0x%lx (%pF)\n" , + (unsigned int )regs->cx, (unsigned int )regs->dx, + (unsigned int )regs->ax, regs->ip, (void *)regs->ip)) +show_stack_regs (regs);/* Pretend that the write succeeded. */ regs->ip =ex_fixup_addr (fixup);
include/linux/printk.h +5 -0
@@ -309,20 +309,25 @@ extern asmlinkage void dump_stack(void) __cold;#define printk_once (fmt, ...) \ ({ \ static bool __print_once __read_mostly; \ + int __ret_print_once = !__print_once; \ \ if (!__print_once) { \ __print_once = true; \ printk(fmt, ##__VA_ARGS__); \ } \ + __ret_print_once; \ }) +#define printk_deferred_once (fmt, ...) \ ({ \ static bool __print_once __read_mostly; \ + int __ret_print_once = !__print_once; \ \ if (!__print_once) { \ __print_once = true; \ printk_deferred(fmt, ##__VA_ARGS__); \ } \ + __ret_print_once; \ })#else #define printk_once (fmt, ...) \
Signature · 4 lines
Regards/Gruss,
Boris.
ECO tip #101: Trim your mails when you reply.tip-bot for Andy Lutomirski
merge bot[tip:x86/debug] x86/dumpstack: Honor supplied @regs arg
In reply to Andy Lutomirskix86/dumpstack: Honor supplied @regs arg
Commit-ID: ef16dd0c2a523d2e3975bb1bea9f5727e3e7146f Gitweb: http://git.kernel.org/tip/ef16dd0c2a523d2e3975bb1bea9f5727e3e7146f Author: Andy Lutomirski <[email protected]> AuthorDate: Tue, 5 Jul 2016 00:31:25 +0200 Committer: Ingo Molnar <[email protected]> CommitDate: Fri, 8 Jul 2016 11:33:18 +0200
x86/dumpstack: Honor supplied @regs arg
The comment suggests that show_stack(NULL, NULL) should backtrace the current context, but the code doesn't match the comment. If regs are given, start the "Stack:" hexdump at regs->sp.
--- arch/x86/kernel/dumpstack_32.c | 4 +++- arch/x86/kernel/dumpstack_64.c | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-)
arch/x86/kernel/dumpstack_32.c +3 -1
@@ -96,7 +96,9 @@ show_stack_log_lvl(struct task_struct *task, struct pt_regs *regs,int i;if (sp ==NULL ) { -if (task) +if (regs) + sp = (unsigned long *)regs->sp; +else if (task) sp = (unsigned long *)task->thread.sp;else sp = (unsigned long *)&sp;
arch/x86/kernel/dumpstack_64.c +3 -1
@@ -264,7 +264,9 @@ show_stack_log_lvl(struct task_struct *task, struct pt_regs *regs,* back trace for this cpu: */ if (sp ==NULL ) { -if (task) +if (regs) + sp = (unsigned long *)regs->sp; +else if (task) sp = (unsigned long *)task->thread.sp;else sp = (unsigned long *)&sp;
Andy Lutomirski
maintainerPATCH 10/13 x86/dumpstack: Try harder to get a call trace on stack overflow
In reply to Andy LutomirskiIf we overflow the stack, print_context_stack will abort. Detect this case and rewind back into the valid part of the stack so that we can trace it.
--- arch/x86/kernel/dumpstack.c | 7 +++++++ 1 file changed, 7 insertions(+)
arch/x86/kernel/dumpstack.c +7 -0
@@ -100,6 +100,13 @@ print_context_stack(struct thread_info *tinfo, {struct stack_frame *frame = (struct stack_frame *)bp; +/* +* If we overflowed the stack into a guard page, jump back to the +* bottom of the usable stack. +*/ +if ((unsigned long )tinfo - (unsigned long )stack <PAGE_SIZE ) + stack = (unsigned long *)tinfo +1 ; +while (valid_stack_ptr (tinfo, stack,sizeof (*stack), end)) {unsigned long addr;
Signature · 1 line
2.7.4
Re: [PATCH 10/13] x86/dumpstack: Try harder to get a call trace on stack overflow
Quoted 14 lines · Andy Lutomirski
If we overflow the stack,
print_context_stackwill abort. Detect this case and rewind back into the valid part of the stack so that we can trace it.Signed-off-by Andy Lutomirski <[email protected]>--- arch/x86/kernel/dumpstack.c | 7 +++++++ 1 file changed, 7 insertions(+)arch/x86/kernel/dumpstack.c +0
-0
arch/x86/kernel/dumpstack.c print_context_stack(struct thread_info *tinfo, · line 108
Quoted 9 lines
arch/x86/kernel/dumpstack.c +6
-0@@ -100,6 +100,13 @@ print_context_stack(struct thread_info *tinfo, {struct stack_frame *frame = (struct stack_frame *)bp; +/* +* If we overflowed the stack into a guard page, jump back to the +* bottom of the usable stack. +*/ +if ((unsigned long )tinfo - (unsigned long )stack <PAGE_SIZE ) + stack = (unsigned long *)tinfo +1 ;
That will start walking the stack in the middle of the thread_info struct.
I think you meant:
stack = (unsigned long *)(tinfo + 1)
However, thread_info will have been overwritten anyway. So maybe it should just be:
stack = tinfo;
(Though that still wouldn't quite work because the valid_stack_ptr() check would fail...)
Quoted 3 lines
arch/x86/kernel/dumpstack.c +1
-0+while (valid_stack_ptr (tinfo, stack,sizeof (*stack), end)) {unsigned long addr;
Signature · 1 line
Josh
Re: [PATCH 10/13] x86/dumpstack: Try harder to get a call trace on stack overflow
Quoted 32 lines · Josh Poimboeuf
Quoted 14 lines · Andy Lutomirski
If we overflow the stack,
print_context_stackwill abort. Detect this case and rewind back into the valid part of the stack so that we can trace it.Signed-off-by Andy Lutomirski <[email protected]>--- arch/x86/kernel/dumpstack.c | 7 +++++++ 1 file changed, 7 insertions(+)arch/x86/kernel/dumpstack.c +0
-0arch/x86/kernel/dumpstack.c print_context_stack(struct thread_info *tinfo, · line 108
Quoted 9 lines
arch/x86/kernel/dumpstack.c +6
-0@@ -100,6 +100,13 @@ print_context_stack(struct thread_info *tinfo, {struct stack_frame *frame = (struct stack_frame *)bp; +/* +* If we overflowed the stack into a guard page, jump back to the +* bottom of the usable stack. +*/ +if ((unsigned long )tinfo - (unsigned long )stack <PAGE_SIZE ) + stack = (unsigned long *)tinfo +1 ;That will start walking the stack in the middle of the
thread_infostruct.I think you meant:
stack = (unsigned long *)(tinfo + 1)
Quoted 7 lines
However,
thread_infowill have been overwritten anyway. So maybe it should just be:stack = tinfo;(Though that still wouldn't quite work because the
valid_stack_ptr()check would fail...)
I did mean what I wrote, because I wanted to start at the bottom of the validly allocated area. IOW I wanted to do the minimum possible backward jump to make the code display something.
Eventually I want to make thread_info empty, in which case the distinction won't matter so much.
--Andy
Re: [PATCH 10/13] x86/dumpstack: Try harder to get a call trace on stack overflow
Quoted 36 lines · Andy Lutomirski
Quoted 35 lines · Josh Poimboeuf
Quoted 14 lines · Andy Lutomirski
If we overflow the stack,
print_context_stackwill abort. Detect this case and rewind back into the valid part of the stack so that we can trace it.Signed-off-by Andy Lutomirski <[email protected]>--- arch/x86/kernel/dumpstack.c | 7 +++++++ 1 file changed, 7 insertions(+)arch/x86/kernel/dumpstack.c +0
-0arch/x86/kernel/dumpstack.c print_context_stack(struct thread_info *tinfo, · line 108
Quoted 9 lines
arch/x86/kernel/dumpstack.c +6
-0@@ -100,6 +100,13 @@ print_context_stack(struct thread_info *tinfo, {struct stack_frame *frame = (struct stack_frame *)bp; +/* +* If we overflowed the stack into a guard page, jump back to the +* bottom of the usable stack. +*/ +if ((unsigned long )tinfo - (unsigned long )stack <PAGE_SIZE ) + stack = (unsigned long *)tinfo +1 ;That will start walking the stack in the middle of the
thread_infostruct.I think you meant:
stack = (unsigned long *)(tinfo + 1)
However,
thread_infowill have been overwritten anyway. So maybe it should just be:
Quoted 8 lines
Quoted 4 lines
stack = tinfo;(Though that still wouldn't quite work because the
valid_stack_ptr()check would fail...)I did mean what I wrote, because I wanted to start at the bottom of the validly allocated area. IOW I wanted to do the minimum possible backward jump to make the code display something.
But why the "+ 1"? Is that a hack to make it pass the valid_stack_ptr() check?
Signature · 1 line
Josh
Re: [PATCH 10/13] x86/dumpstack: Try harder to get a call trace on stack overflow
Quoted 42 lines · Josh Poimboeuf
Quoted 41 lines · Andy Lutomirski
Quoted 39 lines · Josh Poimboeuf
Quoted 14 lines · Andy Lutomirski
If we overflow the stack,
print_context_stackwill abort. Detect this case and rewind back into the valid part of the stack so that we can trace it.Signed-off-by Andy Lutomirski <[email protected]>--- arch/x86/kernel/dumpstack.c | 7 +++++++ 1 file changed, 7 insertions(+)arch/x86/kernel/dumpstack.c +0
-0arch/x86/kernel/dumpstack.c print_context_stack(struct thread_info *tinfo, · line 108
Quoted 9 lines
arch/x86/kernel/dumpstack.c +6
-0@@ -100,6 +100,13 @@ print_context_stack(struct thread_info *tinfo, {struct stack_frame *frame = (struct stack_frame *)bp; +/* +* If we overflowed the stack into a guard page, jump back to the +* bottom of the usable stack. +*/ +if ((unsigned long )tinfo - (unsigned long )stack <PAGE_SIZE ) + stack = (unsigned long *)tinfo +1 ;That will start walking the stack in the middle of the
thread_infostruct.I think you meant:
stack = (unsigned long *)(tinfo + 1)
However,
thread_infowill have been overwritten anyway. So maybe it should just be:stack = tinfo;(Though that still wouldn't quite work because the
valid_stack_ptr()check would fail...)
Quoted 6 lines
Quoted 3 lines
I did mean what I wrote, because I wanted to start at the bottom of the validly allocated area. IOW I wanted to do the minimum possible backward jump to make the code display something.
But why the "+ 1"? Is that a hack to make it pass the
valid_stack_ptr()check?
Yes.
But hmm. Maybe the right fix is to drop the + 1 and to change the last line of valid_stck_ptr from:
return p > t && p < t + THREAD_SIZE - size;
to:
return p >= t && p < t + THREAD_SIZE - size;
The current definition of valid_stack_ptr is certainly nonsensical. It should either be p >= t or p >= t + 1.
--Andy
Re: [PATCH 10/13] x86/dumpstack: Try harder to get a call trace on stack overflow
Quoted 52 lines · Andy Lutomirski
Quoted 42 lines · Josh Poimboeuf
Quoted 41 lines · Andy Lutomirski
Quoted 39 lines · Josh Poimboeuf
Quoted 14 lines · Andy Lutomirski
If we overflow the stack,
print_context_stackwill abort. Detect this case and rewind back into the valid part of the stack so that we can trace it.Signed-off-by Andy Lutomirski <[email protected]>--- arch/x86/kernel/dumpstack.c | 7 +++++++ 1 file changed, 7 insertions(+)arch/x86/kernel/dumpstack.c +0
-0arch/x86/kernel/dumpstack.c print_context_stack(struct thread_info *tinfo, · line 108
Quoted 9 lines
arch/x86/kernel/dumpstack.c +6
-0@@ -100,6 +100,13 @@ print_context_stack(struct thread_info *tinfo, {struct stack_frame *frame = (struct stack_frame *)bp; +/* +* If we overflowed the stack into a guard page, jump back to the +* bottom of the usable stack. +*/ +if ((unsigned long )tinfo - (unsigned long )stack <PAGE_SIZE ) + stack = (unsigned long *)tinfo +1 ;That will start walking the stack in the middle of the
thread_infostruct.I think you meant:
stack = (unsigned long *)(tinfo + 1)
However,
thread_infowill have been overwritten anyway. So maybe it should just be:stack = tinfo;(Though that still wouldn't quite work because the
valid_stack_ptr()check would fail...)Quoted 6 lines
Quoted 3 lines
I did mean what I wrote, because I wanted to start at the bottom of the validly allocated area. IOW I wanted to do the minimum possible backward jump to make the code display something.
But why the "+ 1"? Is that a hack to make it pass the
valid_stack_ptr()check?Yes.
Quoted 8 lines
But hmm. Maybe the right fix is to drop the + 1 and to change the last line of
valid_stck_ptrfrom:return p > t && p < t + THREAD_SIZE - size;to:
return p >= t && p < t + THREAD_SIZE - size;
Yeah, I think that would be much better. Then it won't skip the first value on the page.
Signature · 1 line
Josh
Andy Lutomirski
maintainerPATCH 11/13 x86/dumpstack/64: Handle faults when printing the "Stack:" part of an OOPS
In reply to Andy LutomirskiIf we overflow the stack into a guard page, we'll recursively fault when trying to dump the contents of the guard page. Use probe_kernel_address so we can recover if this happens.
--- arch/x86/kernel/dumpstack_64.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-)
arch/x86/kernel/dumpstack_64.c +10 -2
@@ -276,6 +276,8 @@ show_stack_log_lvl(struct task_struct *task, struct pt_regs *regs, stack = sp;for (i =0 ; i < kstack_depth_to_print; i++) { +unsigned long word; +if (stack >= irq_stack && stack <= irq_stack_end) {if (stack == irq_stack_end) { stack = (unsigned long *) (irq_stack_end[-1 ]);
@@ -285,12 +287,18 @@ show_stack_log_lvl(struct task_struct *task, struct pt_regs *regs,if (kstack_end (stack))break ; } + +if (probe_kernel_address (stack, word)) +break ; +if ((i %STACKSLOTS_PER_LINE ) ==0 ) {if (i !=0 )pr_cont ("\n" ); -printk ("%s %016lx" , log_lvl, *stack++); +printk ("%s %016lx" , log_lvl, word); }else -pr_cont (" %016lx" , *stack++); +pr_cont (" %016lx" , word); + + stack++;touch_nmi_watchdog (); }preempt_enable ();
Signature · 1 line
2.7.4
Andy Lutomirski
maintainerPATCH 12/13 x86/mm/64: Enable vmapped stacks
In reply to Andy LutomirskiThis allows x86_64 kernels to enable vmapped stacks. There are a couple of interesting bits.
First, x86 lazily faults in top-level paging entries for the vmalloc area. This won't work if we get a page fault while trying to access the stack: the CPU will promote it to a double-fault and we'll die. To avoid this problem, probe the new stack when switching stacks and forcibly populate the pgd entry for the stack when switching mms.
Second, once we have guard pages around the stack, we'll want to detect and handle stack overflow.
I didn't enable it on x86_32. We'd need to rework the double-fault code a bit and I'm concerned about running out of vmalloc virtual addresses under some workloads.
This patch, by itself, will behave somewhat erratically when the stack overflows while RSP is still more than a few tens of bytes above the bottom of the stack. Specifically, we'll get #PF and make it to no_context and an oops without triggering a double-fault, and no_context doesn't know about stack overflows. The next patch will improve that case.
--- arch/x86/Kconfig | 1 + arch/x86/include/asm/switch_to.h | 28 +++++++++++++++++++++++++++- arch/x86/kernel/traps.c | 32 ++++++++++++++++++++++++++++++++ arch/x86/mm/tlb.c | 15 +++++++++++++++ 4 files changed, 75 insertions(+), 1 deletion(-)
arch/x86/Kconfig +1 -0
@@ -92,6 +92,7 @@ config X86 select HAVE_ARCH_TRACEHOOK select HAVE_ARCH_TRANSPARENT_HUGEPAGE select HAVE_EBPF_JIT if X86_64 + select HAVE_ARCH_VMAP_STACK if X86_64 select HAVE_CC_STACKPROTECTOR select HAVE_CMPXCHG_DOUBLE select HAVE_CMPXCHG_LOCAL
arch/x86/include/asm/switch_to.h +27 -1
@@ -8,6 +8,28 @@ struct tss_struct;void __switch_to_xtra (struct task_struct *prev_p,struct task_struct *next_p,struct tss_struct *tss); +/* This runs runs on the previous thread's stack. */ +static inline void prepare_switch_to (struct task_struct *prev, +struct task_struct *next) +{ +#ifdef CONFIG_VMAP_STACK +/* +* If we switch to a stack that has a top-level paging entry +* that is not present in the current mm, the resulting #PF will +* will be promoted to a double-fault and we'll panic. Probe +* the new stack now so that vmalloc_fault can fix up the page +* tables if needed. This can only happen if we use a stack +* in vmap space. +* +* We assume that the stack is aligned so that it never spans +* more than one top-level paging entry. +* +* To minimize cache pollution, just follow the stack pointer. +*/ +READ_ONCE (*(unsigned char *)next->thread.sp); +#endif +} +#ifdef CONFIG_X86_32 #ifdef CONFIG_CC_STACKPROTECTOR
@@ -39,6 +61,8 @@ do { \*/ \unsigned long ebx, ecx, edx, esi, edi; \ \ +prepare_switch_to (prev, next); \ + \ asmvolatile ("pushl %%ebp\n\t" /* save EBP */ \"movl %%esp,%[prev_sp]\n\t" /* save ESP */ \ "movl %[next_sp],%%esp\n\t" /* restore ESP */ \
@@ -103,7 +127,9 @@ do { \* clean in kernel mode, with the possible exception of IOPL. Kernel IOPL * has no effect. */ -#define switch_to (prev, next, last) \ +#define switch_to (prev, next, last) \ + prepare_switch_to(prev, next); \ + \ asm volatile(SAVE_CONTEXT \ "movq %%rsp,%P[threadrsp](%[prev])\n\t"/* save RSP */ \"movq %P[threadrsp](%[next]),%%rsp\n\t" /* restore RSP */ \
arch/x86/kernel/traps.c +32 -0
@@ -292,12 +292,30 @@ DO_ERROR(X86_TRAP_NP, SIGBUS, "segment not present", segment_not_present) DO_ERROR(X86_TRAP_SS, SIGBUS, "stack segment", stack_segment) DO_ERROR(X86_TRAP_AC, SIGBUS, "alignment check", alignment_check) +#ifdef CONFIG_VMAP_STACK +static void __noreturn handle_stack_overflow (const char *message, +struct pt_regs *regs, +unsigned long fault_address) +{ +printk (KERN_EMERG "BUG: stack guard page was hit at %p (stack is %p..%p)\n" , + (void *)fault_address, current->stack, + (char *)current->stack +THREAD_SIZE -1 ); +die (message, regs,0 ); + +/* Be absolutely certain we don't return. */ +panic (message); +} +#endif +#ifdef CONFIG_X86_64 /* Runs on IST stack */ dotraplinkage void do_double_fault(struct pt_regs *regs, long error_code) {static const char str[] ="double fault" ;struct task_struct *tsk = current; +#ifdef CONFIG_VMAP_STACK +unsigned long cr2; +#endif #ifdef CONFIG_X86_ESPFIX64 extern unsigned char native_irq_return_iret[];
@@ -332,6 +350,20 @@ dotraplinkage void do_double_fault(struct pt_regs *regs, long error_code) tsk->thread.error_code = error_code; tsk->thread.trap_nr =X86_TRAP_DF ; +#ifdef CONFIG_VMAP_STACK +/* +* If we overflow the stack into a guard page, the CPU will fail +* to deliver #PF and will send #DF instead. CR2 will contain +* the linear address of the second fault, which will be in the +* guard page below the bottom of the stack. +*/ + cr2 =read_cr2 (); +if ((unsigned long )tsk->stack -1 - cr2 <PAGE_SIZE ) +handle_stack_overflow ( +"kernel stack overflow (double-fault)" , + regs, cr2); +#endif +#ifdef CONFIG_DOUBLEFAULT df_debug (regs, error_code);#endif
arch/x86/mm/tlb.c +15 -0
@@ -77,10 +77,25 @@ void switch_mm_irqs_off(struct mm_struct *prev, struct mm_struct *next,unsigned cpu =smp_processor_id ();if (likely (prev != next)) { +if (IS_ENABLED (CONFIG_VMAP_STACK )) { +/* +* If our current stack is in vmalloc space and isn't +* mapped in the new pgd, we'll double-fault. Forcibly +* map it. +*/ +unsigned int stack_pgd_index = +pgd_index (current_stack_pointer ()); +pgd_t *pgd = next->pgd + stack_pgd_index; + +if (unlikely (pgd_none (*pgd))) +set_pgd (pgd, init_mm.pgd[stack_pgd_index]); + } +#ifdef CONFIG_SMP this_cpu_write (cpu_tlbstate.state,TLBSTATE_OK );this_cpu_write (cpu_tlbstate.active_mm, next);#endif +cpumask_set_cpu (cpu,mm_cpumask (next));/*
Signature · 1 line
2.7.4
Re: [PATCH 12/13] x86/mm/64: Enable vmapped stacks
Hi,
Quoted 158 lines · Andy Lutomirski
This allows
x86_64kernels to enable vmapped stacks. There are a couple of interesting bits.First, x86 lazily faults in top-level paging entries for the vmalloc area. This won't work if we get a page fault while trying to access the stack: the CPU will promote it to a double-fault and we'll die. To avoid this problem, probe the new stack when switching stacks and forcibly populate the pgd entry for the stack when switching mms.
Second, once we have guard pages around the stack, we'll want to detect and handle stack overflow.
I didn't enable it on
x86_32. We'd need to rework the double-fault code a bit and I'm concerned about running out of vmalloc virtual addresses under some workloads.This patch, by itself, will behave somewhat erratically when the stack overflows while RSP is still more than a few tens of bytes above the bottom of the stack. Specifically, we'll get #PF and make it to
no_contextand an oops without triggering a double-fault, andno_contextdoesn't know about stack overflows. The next patch will improve that case.Signed-off-by Andy Lutomirski <[email protected]>--- arch/x86/Kconfig | 1 + arch/x86/include/asm/switch_to.h | 28 +++++++++++++++++++++++++++- arch/x86/kernel/traps.c | 32 ++++++++++++++++++++++++++++++++ arch/x86/mm/tlb.c | 15 +++++++++++++++ 4 files changed, 75 insertions(+), 1 deletion(-)arch/x86/Kconfig +1
-0@@ -92,6 +92,7 @@ config X86 select HAVE_ARCH_TRACEHOOK select HAVE_ARCH_TRANSPARENT_HUGEPAGE select HAVE_EBPF_JIT if X86_64 + select HAVE_ARCH_VMAP_STACK if X86_64 select HAVE_CC_STACKPROTECTOR select HAVE_CMPXCHG_DOUBLE select HAVE_CMPXCHG_LOCALarch/x86/include/asm/switch_to.h +27
-1@@ -8,6 +8,28 @@ struct tss_struct;void __switch_to_xtra (struct task_struct *prev_p,struct task_struct *next_p,struct tss_struct *tss); +/* This runs runs on the previous thread's stack. */ +static inline void prepare_switch_to (struct task_struct *prev, +struct task_struct *next) +{ +#ifdef CONFIG_VMAP_STACK +/* +* If we switch to a stack that has a top-level paging entry +* that is not present in the current mm, the resulting #PF will +* will be promoted to a double-fault and we'll panic. Probe +* the new stack now so that vmalloc_fault can fix up the page +* tables if needed. This can only happen if we use a stack +* in vmap space. +* +* We assume that the stack is aligned so that it never spans +* more than one top-level paging entry. +* +* To minimize cache pollution, just follow the stack pointer. +*/ +READ_ONCE (*(unsigned char *)next->thread.sp); +#endif +} +#ifdef CONFIG_X86_32 #ifdef CONFIG_CC_STACKPROTECTOR @@ -39,6 +61,8 @@ do { \*/ \unsigned long ebx, ecx, edx, esi, edi; \ \ +prepare_switch_to (prev, next); \ + \ asmvolatile ("pushl %%ebp\n\t" /* save EBP */ \"movl %%esp,%[prev_sp]\n\t" /* save ESP */ \ "movl %[next_sp],%%esp\n\t" /* restore ESP */ \@@ -103,7 +127,9 @@ do { \* clean in kernel mode, with the possible exception of IOPL. Kernel IOPL * has no effect. */ -#define switch_to (prev, next, last) \ +#define switch_to (prev, next, last) \ + prepare_switch_to(prev, next); \ + \ asm volatile(SAVE_CONTEXT \ "movq %%rsp,%P[threadrsp](%[prev])\n\t"/* save RSP */ \"movq %P[threadrsp](%[next]),%%rsp\n\t" /* restore RSP */ \arch/x86/kernel/traps.c +32
-0@@ -292,12 +292,30 @@ DO_ERROR(X86_TRAP_NP, SIGBUS, "segment not present", segment_not_present) DO_ERROR(X86_TRAP_SS, SIGBUS, "stack segment", stack_segment) DO_ERROR(X86_TRAP_AC, SIGBUS, "alignment check", alignment_check) +#ifdef CONFIG_VMAP_STACK +static void __noreturn handle_stack_overflow (const char *message, +struct pt_regs *regs, +unsigned long fault_address) +{ +printk (KERN_EMERG "BUG: stack guard page was hit at %p (stack is %p..%p)\n" , + (void *)fault_address, current->stack, + (char *)current->stack +THREAD_SIZE -1 ); +die (message, regs,0 ); + +/* Be absolutely certain we don't return. */ +panic (message); +} +#endif +#ifdef CONFIG_X86_64 /* Runs on IST stack */ dotraplinkage void do_double_fault(struct pt_regs *regs, long error_code) {static const char str[] ="double fault" ;struct task_struct *tsk = current; +#ifdef CONFIG_VMAP_STACK +unsigned long cr2; +#endif #ifdef CONFIG_X86_ESPFIX64 extern unsigned char native_irq_return_iret[];@@ -332,6 +350,20 @@ dotraplinkage void do_double_fault(struct pt_regs *regs, long error_code) tsk->thread.error_code = error_code; tsk->thread.trap_nr =X86_TRAP_DF ; +#ifdef CONFIG_VMAP_STACK +/* +* If we overflow the stack into a guard page, the CPU will fail +* to deliver #PF and will send #DF instead. CR2 will contain +* the linear address of the second fault, which will be in the +* guard page below the bottom of the stack. +*/ + cr2 =read_cr2 (); +if ((unsigned long )tsk->stack -1 - cr2 <PAGE_SIZE ) +handle_stack_overflow ( +"kernel stack overflow (double-fault)" , + regs, cr2); +#endif +#ifdef CONFIG_DOUBLEFAULT df_debug (regs, error_code);#endif arch/x86/mm/tlb.c +0
-0@@ -77,10 +77,25 @@ void switch_mm_irqs_off(struct mm_struct *prev, struct mm_struct *next,unsigned cpu =smp_processor_id ();
arch/x86/mm/tlb.c void switch_mm_irqs_off(struct mm_struct *prev, struct mm_struct *next, · line 87
Quoted 10 lines
arch/x86/mm/tlb.c +8
-0if (likely (prev != next)) { +if (IS_ENABLED (CONFIG_VMAP_STACK )) { +/* +* If our current stack is in vmalloc space and isn't +* mapped in the new pgd, we'll double-fault. Forcibly +* map it. +*/ +unsigned int stack_pgd_index = +pgd_index (current_stack_pointer ());
stack pointer is still the previous task's, current_stack_pointer() returns that, not next task's which was intention I guess. Things may happen to work if on same pgd, but at least the boot cpu init_task_struct is special.
Quoted 5 lines
arch/x86/mm/tlb.c +4
-0+pgd_t *pgd = next->pgd + stack_pgd_index; + +if (unlikely (pgd_none (*pgd))) +set_pgd (pgd, init_mm.pgd[stack_pgd_index]);
arch/x86/mm/tlb.c void switch_mm_irqs_off(struct mm_struct *prev, struct mm_struct *next, · line 101
Quoted 10 lines
arch/x86/mm/tlb.c +3
-0+ } +#ifdef CONFIG_SMP this_cpu_write (cpu_tlbstate.state,TLBSTATE_OK );this_cpu_write (cpu_tlbstate.active_mm, next);#endif +cpumask_set_cpu (cpu,mm_cpumask (next));/*
--Mika
Re: [PATCH 12/13] x86/mm/64: Enable vmapped stacks
Quoted 11 lines · Mika Penttilä
Hi,
Quoted 7 lines
arch/x86/mm/tlb.c +0
-0@@ -77,10 +77,25 @@ void switch_mm_irqs_off(struct mm_struct *prev, struct mm_struct *next,unsigned cpu =smp_processor_id ();
Quoted 14 lines
arch/x86/mm/tlb.c void switch_mm_irqs_off(struct mm_struct *prev, struct mm_struct *next, · line 87
Quoted 9 lines
arch/x86/mm/tlb.c +8
-0if (likely (prev != next)) { +if (IS_ENABLED (CONFIG_VMAP_STACK )) { +/* +* If our current stack is in vmalloc space and isn't +* mapped in the new pgd, we'll double-fault. Forcibly +* map it. +*/ +unsigned int stack_pgd_index = +pgd_index (current_stack_pointer ());stack pointer is still the previous task's,
current_stack_pointer()returns that, not next task's which was intention I guess. Things may happen to work if on same pgd, but at least the boot cpuinit_task_structis special.
This is intentional. When switching processes, we first switch the mm and then switch the task. We need to make sure that the prev stack is mapped in the new mm or we'll double-fault and die after switching the mm which still trying to execute on the old stack.
The change to switch_to makes sure that the new stack is mapped.
--Andy
Re: [kernel-hardening] Re: [PATCH 12/13] x86/mm/64: Enable vmapped stacks
Quoted 31 lines · Andy Lutomirski
Quoted 14 lines
Quoted 14 lines
+++
b/arch/x86/mm/tlb.carch/x86/mm/tlb.c +3
-0@@ -77,10 +77,25 @@ void switch_mm_irqs_off(struct mm_struct *prev, struct mm_struct *next, unsigned cpu = smp_processor_id(); if (likely(prev != next)) { + if (IS_ENABLED(CONFIG_VMAP_STACK)) { + /* +* If our current stack is in vmalloc space and isn't
+ * mapped in the new pgd, we'll double- fault. Forcibly
+ * map it.
+ */Quoted 8 lines
Quoted 2 lines
+ unsigned int
stack_pgd_index=
+ pgd_index(current_stack_pointer());stack pointer is still the previous task's,
current_stack_pointer()returns that, not
next task's which was intention I guess. Things may happen to work if on same pgd, but at least the
boot cpuinit_task_structis special.This is intentional. When switching processes, we first switch the mm
and then switch the task. We need to make sure that the prev stack is
mapped in the new mm or we'll double-fault and die after switching the
mm which still trying to execute on the old stack.
Quoted 2 lines
The change to
switch_tomakes sure that the new stack is mapped.
On a HARDENED_USERCOPY tangential note: by not allowing copy_to/from_user access to vmalloc memory by default, with exception of the stack, a task will only be able to copy_to/from_user from its own stack, not another task's stack, at least using the kernel virtual address the kernel uses to access that stack.
This can be accomplished by simply not adding any vmalloc checking code to the current HARDENED_USERCOPY patch set :)
Signature · 1 line
All rights reversed
Andy Lutomirski
maintainerPATCH 13/13 x86/mm: Improve stack-overflow #PF handling
In reply to Andy LutomirskiIf we get a page fault indicating kernel stack overflow, invoke handle_stack_overflow(). To prevent us from overflowing the stack again while handling the overflow (because we are likely to have very little stack space left), call handle_stack_overflow() on the double-fault stack
--- arch/x86/include/asm/traps.h | 6 ++++++ arch/x86/kernel/traps.c | 6 +++--- arch/x86/mm/fault.c | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-)
arch/x86/include/asm/traps.h +6 -0
@@ -117,6 +117,12 @@ extern void ist_exit(struct pt_regs *regs);extern void ist_begin_non_atomic (struct pt_regs *regs);extern void ist_end_non_atomic (void ); +#ifdef CONFIG_VMAP_STACK +void __noreturn handle_stack_overflow (const char *message, +struct pt_regs *regs, +unsigned long fault_address); +#endif +/* Interrupts/Exceptions */ enum {X86_TRAP_DE =0 ,/* 0, Divide-by-zero */
arch/x86/kernel/traps.c +3 -3
@@ -293,9 +293,9 @@ DO_ERROR(X86_TRAP_SS, SIGBUS, "stack segment", stack_segment) DO_ERROR(X86_TRAP_AC, SIGBUS, "alignment check", alignment_check)#ifdef CONFIG_VMAP_STACK -static void __noreturn handle_stack_overflow (const char *message, -struct pt_regs *regs, -unsigned long fault_address) +__visible void __noreturn handle_stack_overflow(const char *message, +struct pt_regs *regs, +unsigned long fault_address) { printk(KERN_EMERG "BUG: stack guard page was hit at %p (stack is %p..%p)\n", (void *)fault_address, current->stack,
arch/x86/mm/fault.c +39 -0
@@ -753,6 +753,45 @@ no_context(struct pt_regs *regs, unsigned long error_code,return ; } +#ifdef CONFIG_VMAP_STACK +/* +* Stack overflow? During boot, we can fault near the initial +* stack in the direct map, but that's not an overflow -- check +* that we're in vmalloc space to avoid this. +* +* Check this after trying fixup_exception, since there are handful +* of kernel code paths that wander off the top of the stack but +* handle any faults that occur. Once those are fixed, we can +* move this above fixup_exception. +*/ +if (is_vmalloc_addr ((void *)address) && + (((unsigned long )tsk->stack -1 - address <PAGE_SIZE ) || + address - ((unsigned long )tsk->stack +THREAD_SIZE ) <PAGE_SIZE )) { + registervoid *__sp asm("rsp" ); +unsigned long stack = +this_cpu_read (orig_ist.ist[DOUBLEFAULT_STACK ]) - +sizeof (void *); +/* +* We're likely to be running with very little stack space +* left. It's plausible that we'd hit this condition but +* double-fault even before we get this far, in which case +* we're fine: the double-fault handler will deal with it. +* +* We don't want to make it all the way into the oops code +* and then double-fault, though, because we're likely to +* break the console driver and lose most of the stack dump. +*/ + asmvolatile ("movq %[stack], %%rsp\n\t" +"call handle_stack_overflow\n\t" +"1: jmp 1b" + :"+r" (__sp) + :"D" ("kernel stack overflow (page fault)" ), +"S" (regs),"d" (address), + [stack]"rm" (stack)); +unreachable (); + } +#endif +/* * 32-bit: *
Signature · 1 line
2.7.4
Quoted 4 lines · Andy Lutomirski
Since the dawn of time, a kernel stack overflow has been a real PITA to debug, has caused nondeterministic crashes some time after the actual overflow, and has generally been easy to exploit for root.
Quoted 8 lines
With this series, arches can enable
HAVE_ARCH_VMAP_STACK. Arches that enable it (just x86 for now) get virtually mapped stacks with guard pages. This causes reliable faults when the stack overflows.If the arch implements it well, we get a nice OOPS on stack overflow (as opposed to panicing directly or otherwise exploding badly). On x86, the OOPS is nice, has a usable call trace, and the overflowing task is killed cleanly.
Do you have numbers which reflect the performance impact of this change?
Quoted 9 lines · Heiko Carstens
Quoted 8 lines · Andy Lutomirski
Since the dawn of time, a kernel stack overflow has been a real PITA to debug, has caused nondeterministic crashes some time after the actual overflow, and has generally been easy to exploit for root.
With this series, arches can enable
HAVE_ARCH_VMAP_STACK. Arches that enable it (just x86 for now) get virtually mapped stacks with guard pages. This causes reliable faults when the stack overflows.
Quoted 7 lines
Quoted 4 lines
If the arch implements it well, we get a nice OOPS on stack overflow (as opposed to panicing directly or otherwise exploding badly). On x86, the OOPS is nice, has a usable call trace, and the overflowing task is killed cleanly.
Do you have numbers which reflect the performance impact of this change?
Hmm. My attempt to benchmark it caused some of the vmalloc core code to hang. I'll dig around.
FWIW, I expect some overhead on clone/fork (if it's high, then that would be a good reason to improve vmalloc) and a small workload-dependent overhead due to increased TLB pressure.
--Andy
Adding Paul, because RCU blew up.
Quoted 16 lines · Andy Lutomirski
Quoted 14 lines · Heiko Carstens
Quoted 12 lines · Andy Lutomirski
Since the dawn of time, a kernel stack overflow has been a real PITA to debug, has caused nondeterministic crashes some time after the actual overflow, and has generally been easy to exploit for root.
With this series, arches can enable
HAVE_ARCH_VMAP_STACK. Arches that enable it (just x86 for now) get virtually mapped stacks with guard pages. This causes reliable faults when the stack overflows.If the arch implements it well, we get a nice OOPS on stack overflow (as opposed to panicing directly or otherwise exploding badly). On x86, the OOPS is nice, has a usable call trace, and the overflowing task is killed cleanly.
Quoted 5 lines
Quoted 2 lines
Do you have numbers which reflect the performance impact of this change?
Hmm. My attempt to benchmark it caused some of the vmalloc core code to hang. I'll dig around.
[ 488.482010] Call Trace: [ 488.482389] <IRQ> [<ffffffff810da5f6>] sched_show_task+0xb6/0x110 [ 488.483341] [<ffffffff81108c7a>] rcu_check_callbacks+0x83a/0x840 [ 488.484226] [<ffffffff810dd48a>] ? account_system_time+0x7a/0x110 [ 488.485157] [<ffffffff8111c0f0>] ? tick_sched_do_timer+0x30/0x30 [ 488.486133] [<ffffffff8110d314>] update_process_times+0x34/0x60 [ 488.487050] [<ffffffff8111bb51>] tick_sched_handle.isra.13+0x31/0x40 [ 488.488018] [<ffffffff8111c128>] tick_sched_timer+0x38/0x70 [ 488.488853] [<ffffffff8110db2a>] __hrtimer_run_queues+0xda/0x250 [ 488.489739] [<ffffffff8110e263>] hrtimer_interrupt+0xa3/0x190 [ 488.490630] [<ffffffff810952f3>] local_apic_timer_interrupt+0x33/0x50 [ 488.491660] [<ffffffff81095d58>] smp_apic_timer_interrupt+0x38/0x50 [ 488.492644] [<ffffffff8194d022>] apic_timer_interrupt+0x82/0x90 [ 488.493502] [<ffffffff810ee1c0>] ? queued_spin_lock_slowpath+0x20/0x190 [ 488.494550] [<ffffffff8194c21b>] _raw_spin_lock+0x1b/0x20 [ 488.495321] [<ffffffff811b7a54>] find_vmap_area+0x14/0x60 [ 488.496197] [<ffffffff811b8f69>] find_vm_area+0x9/0x20 [ 488.496922] [<ffffffff810afb19>] account_kernel_stack+0x89/0x100 [ 488.497885] [<ffffffff810aff76>] free_task+0x16/0x50 [ 488.498599] [<ffffffff810b0042>] __put_task_struct+0x92/0x120 [ 488.499525] [<ffffffff810b4a66>] delayed_put_task_struct+0x76/0x80 [ 488.500348] [<ffffffff81107969>] rcu_process_callbacks+0x1f9/0x5e0 [ 488.501208] [<ffffffff810b7ca1>] __do_softirq+0xf1/0x280 [ 488.501932] [<ffffffff810b7f7e>] irq_exit+0x9e/0xa0 [ 488.502955] [<ffffffff81095d5d>] smp_apic_timer_interrupt+0x3d/0x50 [ 488.503943] [<ffffffff8194d022>] apic_timer_interrupt+0x82/0x90 [ 488.504886] <EOI> [<ffffffff8194c20b>] ? _raw_spin_lock+0xb/0x20 [ 488.505877] [<ffffffff811b7c33>] ? __get_vm_area_node+0xc3/0x160 [ 488.506812] [<ffffffff810e013e>] ? task_move_group_fair+0x7e/0x90 [ 488.507730] [<ffffffff811b9360>] __vmalloc_node_range+0x70/0x280 [ 488.508689] [<ffffffff810b1ce5>] ? _do_fork+0xc5/0x370 [ 488.509512] [<ffffffff811ceb9b>] ? kmem_cache_alloc_node+0x7b/0x170 [ 488.510502] [<ffffffff81327418>] ? current_has_perm+0x38/0x40 [ 488.511430] [<ffffffff810b0501>] copy_process.part.46+0x141/0x1760 [ 488.512449] [<ffffffff810b1ce5>] ? _do_fork+0xc5/0x370 [ 488.513285] [<ffffffff8111faf3>] ? do_futex+0x293/0xad0 [ 488.514093] [<ffffffff810df14a>] ? check_preempt_wakeup+0x10a/0x240 [ 488.515108] [<ffffffff810d92c2>] ? wake_up_new_task+0xf2/0x180 [ 488.516043] [<ffffffff810b1ce5>] _do_fork+0xc5/0x370 [ 488.516786] [<ffffffff8112039d>] ? SyS_futex+0x6d/0x150 [ 488.517615] [<ffffffff810b2014>] SyS_clone+0x14/0x20 [ 488.518385] [<ffffffff81002b72>] do_syscall_64+0x52/0xb0 [ 488.519239] [<ffffffff8194c4e1>] entry_SYSCALL64_slow_path+0x25/0x25
The bug seems straightforward: vmap_area_lock is held, the RCU softirq fires, and vmap_area_lock recurses and deadlocks. Lockdep agrees with my assessment and catches the bug immediately on boot.
What's the right fix? Change all spin_lock calls on vmap_area_lock to spin_lock_bh? Somehow ask RCU not to call delayed_put_task_struct from bh context? I would naively have expected RCU to only call its callbacks from thread context, but I was clearly wrong.
--Andy
Quoted 69 lines · Andy Lutomirski
Adding Paul, because RCU blew up.
Quoted 16 lines · Andy Lutomirski
Quoted 14 lines · Heiko Carstens
Quoted 12 lines · Andy Lutomirski
Since the dawn of time, a kernel stack overflow has been a real PITA to debug, has caused nondeterministic crashes some time after the actual overflow, and has generally been easy to exploit for root.
With this series, arches can enable
HAVE_ARCH_VMAP_STACK. Arches that enable it (just x86 for now) get virtually mapped stacks with guard pages. This causes reliable faults when the stack overflows.If the arch implements it well, we get a nice OOPS on stack overflow (as opposed to panicing directly or otherwise exploding badly). On x86, the OOPS is nice, has a usable call trace, and the overflowing task is killed cleanly.
Quoted 5 lines
Quoted 2 lines
Do you have numbers which reflect the performance impact of this change?
Hmm. My attempt to benchmark it caused some of the vmalloc core code to hang. I'll dig around.
[ 488.482010] Call Trace: [ 488.482389] <IRQ> [<ffffffff810da5f6>] sched_show_task+0xb6/0x110 [ 488.483341] [<ffffffff81108c7a>] rcu_check_callbacks+0x83a/0x840 [ 488.484226] [<ffffffff810dd48a>] ? account_system_time+0x7a/0x110 [ 488.485157] [<ffffffff8111c0f0>] ? tick_sched_do_timer+0x30/0x30 [ 488.486133] [<ffffffff8110d314>] update_process_times+0x34/0x60 [ 488.487050] [<ffffffff8111bb51>] tick_sched_handle.isra.13+0x31/0x40 [ 488.488018] [<ffffffff8111c128>] tick_sched_timer+0x38/0x70 [ 488.488853] [<ffffffff8110db2a>] __hrtimer_run_queues+0xda/0x250 [ 488.489739] [<ffffffff8110e263>] hrtimer_interrupt+0xa3/0x190 [ 488.490630] [<ffffffff810952f3>] local_apic_timer_interrupt+0x33/0x50 [ 488.491660] [<ffffffff81095d58>] smp_apic_timer_interrupt+0x38/0x50 [ 488.492644] [<ffffffff8194d022>] apic_timer_interrupt+0x82/0x90 [ 488.493502] [<ffffffff810ee1c0>] ? queued_spin_lock_slowpath+0x20/0x190 [ 488.494550] [<ffffffff8194c21b>] _raw_spin_lock+0x1b/0x20 [ 488.495321] [<ffffffff811b7a54>] find_vmap_area+0x14/0x60 [ 488.496197] [<ffffffff811b8f69>] find_vm_area+0x9/0x20 [ 488.496922] [<ffffffff810afb19>] account_kernel_stack+0x89/0x100 [ 488.497885] [<ffffffff810aff76>] free_task+0x16/0x50 [ 488.498599] [<ffffffff810b0042>] __put_task_struct+0x92/0x120 [ 488.499525] [<ffffffff810b4a66>] delayed_put_task_struct+0x76/0x80 [ 488.500348] [<ffffffff81107969>] rcu_process_callbacks+0x1f9/0x5e0 [ 488.501208] [<ffffffff810b7ca1>] __do_softirq+0xf1/0x280 [ 488.501932] [<ffffffff810b7f7e>] irq_exit+0x9e/0xa0 [ 488.502955] [<ffffffff81095d5d>] smp_apic_timer_interrupt+0x3d/0x50 [ 488.503943] [<ffffffff8194d022>] apic_timer_interrupt+0x82/0x90 [ 488.504886] <EOI> [<ffffffff8194c20b>] ? _raw_spin_lock+0xb/0x20 [ 488.505877] [<ffffffff811b7c33>] ? __get_vm_area_node+0xc3/0x160 [ 488.506812] [<ffffffff810e013e>] ? task_move_group_fair+0x7e/0x90 [ 488.507730] [<ffffffff811b9360>] __vmalloc_node_range+0x70/0x280 [ 488.508689] [<ffffffff810b1ce5>] ? _do_fork+0xc5/0x370 [ 488.509512] [<ffffffff811ceb9b>] ? kmem_cache_alloc_node+0x7b/0x170 [ 488.510502] [<ffffffff81327418>] ? current_has_perm+0x38/0x40 [ 488.511430] [<ffffffff810b0501>] copy_process.part.46+0x141/0x1760 [ 488.512449] [<ffffffff810b1ce5>] ? _do_fork+0xc5/0x370 [ 488.513285] [<ffffffff8111faf3>] ? do_futex+0x293/0xad0 [ 488.514093] [<ffffffff810df14a>] ? check_preempt_wakeup+0x10a/0x240 [ 488.515108] [<ffffffff810d92c2>] ? wake_up_new_task+0xf2/0x180 [ 488.516043] [<ffffffff810b1ce5>] _do_fork+0xc5/0x370 [ 488.516786] [<ffffffff8112039d>] ? SyS_futex+0x6d/0x150 [ 488.517615] [<ffffffff810b2014>] SyS_clone+0x14/0x20 [ 488.518385] [<ffffffff81002b72>] do_syscall_64+0x52/0xb0 [ 488.519239] [<ffffffff8194c4e1>] entry_SYSCALL64_slow_path+0x25/0x25
Quoted 8 lines
The bug seems straightforward:
vmap_area_lockis held, the RCU softirq fires, andvmap_area_lockrecurses and deadlocks. Lockdep agrees with my assessment and catches the bug immediately on boot.What's the right fix? Change all
spin_lockcalls onvmap_area_locktospin_lock_bh? Somehow ask RCU not to calldelayed_put_task_structfrom bh context? I would naively have expected RCU to only call its callbacks from thread context, but I was clearly wrong.
I fixed (worked around?) it by caching the vm_struct * so I can skip calling find_vm_area. vfree works in IRQ context. IMO this is still a wee bit ugly.
--Andy
Quoted 9 lines · Heiko Carstens
Quoted 8 lines · Andy Lutomirski
Since the dawn of time, a kernel stack overflow has been a real PITA to debug, has caused nondeterministic crashes some time after the actual overflow, and has generally been easy to exploit for root.
With this series, arches can enable
HAVE_ARCH_VMAP_STACK. Arches that enable it (just x86 for now) get virtually mapped stacks with guard pages. This causes reliable faults when the stack overflows.
Quoted 7 lines
Quoted 4 lines
If the arch implements it well, we get a nice OOPS on stack overflow (as opposed to panicing directly or otherwise exploding badly). On x86, the OOPS is nice, has a usable call trace, and the overflowing task is killed cleanly.
Do you have numbers which reflect the performance impact of this change?
It seems to add ~1.5µs per thread creation/join pair, which is around 15% overhead. I *think* the major cost is that vmalloc calls alloc_kmem_pages_node once per page rather than using a higher-order block if available.
Anyway, if anyone wants this to become faster, I think the way to do it would be to ask some friendly mm folks to see if they can speed up vmalloc. I don't really want to dig in to the guts of the page allocator. My instinct would be to add a new interface (GFP_SMALLER_OK?) to ask the page allocator for a high-order allocation such that, if a high-order block is not immediately available (on the freelist) then it should fall back to a smaller allocation rather than working hard to get a high-order allocation. Then vmalloc could use this, and vfree could free pages in blocks corresponding to whatever orders it got the pages in, thus avoiding the need to merge all the pages back together.
There's another speedup available: actually reuse allocations. We could keep a very small freelist of vmap_areas with their associated pages so we could reuse them. (We can't efficiently reuse a vmap_area without its backing pages because we need to flush the TLB in the middle if we do that.)
--Andy
Quoted 36 lines · Andy Lutomirski
Quoted 9 lines · Heiko Carstens
Quoted 8 lines · Andy Lutomirski
Since the dawn of time, a kernel stack overflow has been a real PITA to debug, has caused nondeterministic crashes some time after the actual overflow, and has generally been easy to exploit for root.
With this series, arches can enable
HAVE_ARCH_VMAP_STACK. Arches that enable it (just x86 for now) get virtually mapped stacks with guard pages. This causes reliable faults when the stack overflows.Quoted 7 lines
Quoted 4 lines
If the arch implements it well, we get a nice OOPS on stack overflow (as opposed to panicing directly or otherwise exploding badly). On x86, the OOPS is nice, has a usable call trace, and the overflowing task is killed cleanly.
Do you have numbers which reflect the performance impact of this change?
It seems to add ~1.5µs per thread creation/join pair, which is around 15% overhead. I *think* the major cost is that vmalloc calls
alloc_kmem_pages_nodeonce per page rather than using a higher-order block if available.Anyway, if anyone wants this to become faster, I think the way to do it would be to ask some friendly mm folks to see if they can speed up vmalloc. I don't really want to dig in to the guts of the page allocator. My instinct would be to add a new interface (
GFP_SMALLER_OK?) to ask the page allocator for a high-order allocation such that, if a high-order block is not immediately available (on the freelist) then it should fall back to a smaller allocation rather than working hard to get a high-order allocation. Then vmalloc could use this, and vfree could free pages in blocks corresponding to whatever orders it got the pages in, thus avoiding the need to merge all the pages back together.
Quoted 5 lines
There's another speedup available: actually reuse allocations. We could keep a very small freelist of
vmap_areaswith their associated pages so we could reuse them. (We can't efficiently reuse avmap_areawithout its backing pages because we need to flush the TLB in the middle if we do that.)
That's rather expensive. Just for the records: on s390 we use gcc's architecture specific compile options (kernel: CONFIG_STACK_GUARD)
-mstack-guard=stack-guard
-mstack-size=stack-size
These generate two additional instructions at the beginning of each function prologue and verify that the stack size left won't be below a specified number of bytes. If so it would execute an illegal instruction.
A disassembly looks like this (r15 is the stackpointer):
0000000000000670 <setup_arch>:
670: eb 6f f0 48 00 24 stmg %r6,%r15,72(%r15)
676: c0 d0 00 00 00 00 larl %r13,676 <setup_arch+0x6>
67c: a7 f1 3f 80 tmll %r15,16256 <--- test if enough space left
680: b9 04 00 ef lgr %r14,%r15
684: a7 84 00 01 je 686 <setup_arch+0x16> <--- branch to illegal op
688: e3 f0 ff 90 ff 71 lay %r15,-112(%r15)The branch jumps actually into the branch instruction itself since the 0001 part of the "je" instruction is an illegal instruction.
This catches at least wild stack overflows because of two many functions being called.
Of course it doesn't catch wild accesses outside the stack because e.g. the index into an array on the stack is wrong.
The runtime overhead is within noise ratio, therefore we have this always enabled.
Quoted 67 lines · Heiko Carstens
Quoted 36 lines · Andy Lutomirski
Quoted 9 lines · Heiko Carstens
Quoted 8 lines · Andy Lutomirski
Since the dawn of time, a kernel stack overflow has been a real PITA to debug, has caused nondeterministic crashes some time after the actual overflow, and has generally been easy to exploit for root.
With this series, arches can enable
HAVE_ARCH_VMAP_STACK. Arches that enable it (just x86 for now) get virtually mapped stacks with guard pages. This causes reliable faults when the stack overflows.Quoted 7 lines
Quoted 4 lines
If the arch implements it well, we get a nice OOPS on stack overflow (as opposed to panicing directly or otherwise exploding badly). On x86, the OOPS is nice, has a usable call trace, and the overflowing task is killed cleanly.
Do you have numbers which reflect the performance impact of this change?
It seems to add ~1.5盜 per thread creation/join pair, which is around 15% overhead. I *think* the major cost is that vmalloc calls
alloc_kmem_pages_nodeonce per page rather than using a higher-order block if available.Anyway, if anyone wants this to become faster, I think the way to do it would be to ask some friendly mm folks to see if they can speed up vmalloc. I don't really want to dig in to the guts of the page allocator. My instinct would be to add a new interface (
GFP_SMALLER_OK?) to ask the page allocator for a high-order allocation such that, if a high-order block is not immediately available (on the freelist) then it should fall back to a smaller allocation rather than working hard to get a high-order allocation. Then vmalloc could use this, and vfree could free pages in blocks corresponding to whatever orders it got the pages in, thus avoiding the need to merge all the pages back together.Quoted 5 lines
There's another speedup available: actually reuse allocations. We could keep a very small freelist of
vmap_areaswith their associated pages so we could reuse them. (We can't efficiently reuse avmap_areawithout its backing pages because we need to flush the TLB in the middle if we do that.)That's rather expensive. Just for the records: on s390 we use gcc's architecture specific compile options (kernel:
CONFIG_STACK_GUARD)-mstack-guard=stack-guard
-mstack-size=stack-sizeThese generate two additional instructions at the beginning of each function prologue and verify that the stack size left won't be below a specified number of bytes. If so it would execute an illegal instruction.
A disassembly looks like this (r15 is the stackpointer):
0000000000000670 <
setup_arch>:670: eb 6f f0 48 00 24 stmg %r6,%r15,72(%r15) 676: c0 d0 00 00 00 00 larl %r13,676 <setup_arch+0x6> 67c: a7 f1 3f 80 tmll %r15,16256 <--- test if enough space left 680: b9 04 00 ef lgr %r14,%r15 684: a7 84 00 01 je 686 <setup_arch+0x16> <--- branch to illegal op 688: e3 f0 ff 90 ff 71 lay %r15,-112(%r15)The branch jumps actually into the branch instruction itself since the 0001 part of the "je" instruction is an illegal instruction.
Quoted 9 lines
This catches at least wild stack overflows because of two many functions being called.
Of course it doesn't catch wild accesses outside the stack because e.g. the index into an array on the stack is wrong.
The runtime overhead is within noise ratio, therefore we have this always enabled.
Neat! What exactly does tmll do? I assume this works by checking the low bits of the stack pointer.
x86_64 would have to do:
movl %esp, %r11d
shll %r11d, $18
cmpl %r11d, <threshold>
jg error
Or similar. I think the cmpl could be eliminated if the threshold were a power of two by simply testing the low bits of the stack pointer.
--Andy
Quoted 28 lines · Andy Lutomirski
Quoted 13 lines
A disassembly looks like this (r15 is the stackpointer):
0000000000000670 <
setup_arch>:670: eb 6f f0 48 00 24 stmg %r6,%r15,72(%r15) 676: c0 d0 00 00 00 00 larl %r13,676 <setup_arch+0x6> 67c: a7 f1 3f 80 tmll %r15,16256 <--- test if enough space left 680: b9 04 00 ef lgr %r14,%r15 684: a7 84 00 01 je 686 <setup_arch+0x16> <--- branch to illegal op 688: e3 f0 ff 90 ff 71 lay %r15,-112(%r15)The branch jumps actually into the branch instruction itself since the 0001 part of the "je" instruction is an illegal instruction.
Quoted 9 lines
This catches at least wild stack overflows because of two many functions being called.
Of course it doesn't catch wild accesses outside the stack because e.g. the index into an array on the stack is wrong.
The runtime overhead is within noise ratio, therefore we have this always enabled.
Neat! What exactly does tmll do? I assume this works by checking the low bits of the stack pointer.
x86_64would have to do:
Quoted 8 lines
movl %esp, %r11d
shll %r11d, $18
cmpl %r11d, <threshold>
jg errorOr similar. I think the cmpl could be eliminated if the threshold were a power of two by simply testing the low bits of the stack pointer.
The tmll instruction tests if any of the higher bits within the 16k stackframe address are set. In this specific case that would be bits 7-15 (mask 0x3f80). If no bit would be set we know that only up to 128 bytes would be left on the stack, and thus trigger an exception.
This check does of course only work if a 16k stack is also 16k aligned, which is always the case.
Quoted 38 lines · Heiko Carstens
Quoted 28 lines · Andy Lutomirski
Quoted 13 lines
A disassembly looks like this (r15 is the stackpointer):
0000000000000670 <
setup_arch>:670: eb 6f f0 48 00 24 stmg %r6,%r15,72(%r15) 676: c0 d0 00 00 00 00 larl %r13,676 <setup_arch+0x6> 67c: a7 f1 3f 80 tmll %r15,16256 <--- test if enough space left 680: b9 04 00 ef lgr %r14,%r15 684: a7 84 00 01 je 686 <setup_arch+0x16> <--- branch to illegal op 688: e3 f0 ff 90 ff 71 lay %r15,-112(%r15)The branch jumps actually into the branch instruction itself since the 0001 part of the "je" instruction is an illegal instruction.
Quoted 9 lines
This catches at least wild stack overflows because of two many functions being called.
Of course it doesn't catch wild accesses outside the stack because e.g. the index into an array on the stack is wrong.
The runtime overhead is within noise ratio, therefore we have this always enabled.
Neat! What exactly does tmll do? I assume this works by checking the low bits of the stack pointer.
x86_64would have to do:Quoted 8 lines
movl %esp, %r11d
shll %r11d, $18
cmpl %r11d, <threshold>
jg errorOr similar. I think the cmpl could be eliminated if the threshold were a power of two by simply testing the low bits of the stack pointer.
Quoted 8 lines
The tmll instruction tests if any of the higher bits within the 16k stackframe address are set. In this specific case that would be bits 7-15 (mask
0x3f80). If no bit would be set we know that only up to 128 bytes would be left on the stack, and thus trigger an exception.This check does of course only work if a 16k stack is also 16k aligned, which is always the case.
Oh, interesting. How do you handle the case of a single function that uses more than 128 bytes of stack?
--Andy
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
Quoted 11 lines · Andy Lutomirski
Quoted 8 lines
The tmll instruction tests if any of the higher bits within the 16k stackframe address are set. In this specific case that would be bits 7-15 (mask
0x3f80). If no bit would be set we know that only up to 128 bytes would be left on the stack, and thus trigger an exception.This check does of course only work if a 16k stack is also 16k aligned, which is always the case.
Oh, interesting. How do you handle the case of a single function that uses more than 128 bytes of stack?
The compiler uses the next larger value of the stackframe size that is a power of 2 for checking. So another example with a stackframe size of 472 bytes would be the below one with a mask of 0x3e00:
0000000000392db8 <htree_inlinedir_to_tree>:
392db8: eb 6f f0 48 00 24 stmg %r6,%r15,72(%r15) 392dbe: a7 f1 3e 00 tmll %r15,15872 392dc2: b9 04 00 ef lgr %r14,%r15 392dc6: a7 84 00 01 je 392dc8 <htree_inlinedir_to_tree+0x10> 392dca: e3 f0 fe 28 ff 71 lay %r15,-472(%r15)
Quoted 44 lines · Andy Lutomirski
Since the dawn of time, a kernel stack overflow has been a real PITA to debug, has caused nondeterministic crashes some time after the actual overflow, and has generally been easy to exploit for root.
With this series, arches can enable
HAVE_ARCH_VMAP_STACK. Arches that enable it (just x86 for now) get virtually mapped stacks with guard pages. This causes reliable faults when the stack overflows.If the arch implements it well, we get a nice OOPS on stack overflow (as opposed to panicing directly or otherwise exploding badly). On x86, the OOPS is nice, has a usable call trace, and the overflowing task is killed cleanly.
This does not address interrupt stacks.
Andy Lutomirski (12):
x86/cpa: Inpopulate_pgd, don't set the pgd entry until it's populated x86/cpa: Warn ifkernel_unmap_pages_in_pgdis used inappropriately
mm: TrackNR_KERNEL_STACKin pages instead of number of stacks
mm: Move memcg stack accounting toaccount_kernel_stack
fork: Add generic vmalloced stack support
x86/die: Don't try to recover from an OOPS on a non-default stack x86/dumpstack: When OOPSing, rewind the stack beforedo_exitx86/dumpstack: When dumping stack bytes due to OOPS, start withregs->spx86/dumpstack: Try harder to get a call trace on stack overflow x86/dumpstack/64: Handle faults when printing the "Stack:" part of an
OOPS
x86/mm/64: Enable vmapped stacks
x86/mm: Improve stack-overflow #PF handlingIngo Molnar (1):
x86/mm/hotplug: Don't remove PGD entries inremove_pagetable()arch/Kconfig | 12 ++++++++++ arch/x86/Kconfig | 1 + arch/x86/entry/entry_32.S | 11 +++++++++ arch/x86/entry/entry_64.S | 11 +++++++++ arch/x86/include/asm/switch_to.h | 28 +++++++++++++++++++++- arch/x86/include/asm/traps.h | 6 +++++ arch/x86/kernel/dumpstack.c | 17 +++++++++++++- arch/x86/kernel/dumpstack_32.c | 4 +++- arch/x86/kernel/dumpstack_64.c | 16 ++++++++++--- arch/x86/kernel/traps.c | 32 +++++++++++++++++++++++++
Quoted 8 lines
arch/x86/mm/fault.c | 39 ++++++++++++++++++++++++++++++ arch/x86/mm/init_64.c | 27 --------------------- arch/x86/mm/pageattr.c | 7 +++++- arch/x86/mm/tlb.c | 15 ++++++++++++ fs/proc/meminfo.c | 2 +- kernel/fork.c | 51 ++++++++++++++++++++++++++++++---------- mm/page_alloc.c | 3 +-- 17 files changed, 233 insertions(+), 49 deletions(-)
This is great, thanks! This helps the up-coming usercopy self-protection code, and makes stack overflows a much less interesting target for attackers.
-Kees
Signature · 2 lines
Kees Cook Chrome OS & Brillo Security
Borislav Petkov
maintainercover x86/MSR: Improve unhandled MSR access error message
From: Borislav Petkov <[email protected]>
Hi all,
here's v2 with review feedback integrated.
Changelog:
v1:
here's a simple patchset improving the error message we're dumping when an unchecked MSR is being done. Patch 3 has an example which explains the improvement in detail.
Andy Lutomirski (1):
x86/dumpstack: Honor supplied @regs arg
Borislav Petkov (2):
printk: Make the printk*once() variants return a value x86/dumpstack: Add show_stack_regs() and use it
arch/x86/include/asm/kdebug.h | 1 + arch/x86/kernel/dumpstack.c | 5 +++++ arch/x86/kernel/dumpstack_32.c | 4 +++- arch/x86/kernel/dumpstack_64.c | 4 +++- arch/x86/mm/extable.c | 13 ++++++++----- include/linux/printk.h | 17 ++++++++++++----- 6 files changed, 32 insertions(+), 12 deletions(-)
Signature · 1 line
2.7.3
Borislav Petkov
maintainerPATCH 1/3 x86/dumpstack: Honor supplied @regs arg
From: Andy Lutomirski <[email protected]>
The comment suggests that show_stack(NULL, NULL) should backtrace the current context, but the code doesn't match the comment. If regs are given, start the "Stack:" hexdump at regs->sp.
--- arch/x86/kernel/dumpstack_32.c | 4 +++- arch/x86/kernel/dumpstack_64.c | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-)
arch/x86/kernel/dumpstack_32.c +3 -1
@@ -96,7 +96,9 @@ show_stack_log_lvl(struct task_struct *task, struct pt_regs *regs,int i;if (sp ==NULL ) { -if (task) +if (regs) + sp = (unsigned long *)regs->sp; +else if (task) sp = (unsigned long *)task->thread.sp;else sp = (unsigned long *)&sp;
arch/x86/kernel/dumpstack_64.c +3 -1
@@ -264,7 +264,9 @@ show_stack_log_lvl(struct task_struct *task, struct pt_regs *regs,* back trace for this cpu: */ if (sp ==NULL ) { -if (task) +if (regs) + sp = (unsigned long *)regs->sp; +else if (task) sp = (unsigned long *)task->thread.sp;else sp = (unsigned long *)&sp;
Signature · 1 line
2.7.3
Borislav Petkov
maintainerPATCH 2/3 printk: Make the printk*once() variants return a value
In reply to Borislav PetkovFrom: Borislav Petkov <[email protected]>
Have printk*once() return a bool which denotes whether the string was printed or not so that calling code can react accordingly.
--- include/linux/printk.h | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-)
include/linux/printk.h +12 -5
@@ -108,11 +108,14 @@ struct va_format {* Dummy printk for disabled debugging statements to use whilst maintaining * gcc's format checking. */ -#define no_printk (fmt, ...) \ -do { \ - if (0) \ - printk(fmt, ##__VA_ARGS__); \ -} while (0) +#define no_printk (fmt, ...) \ +({ \ + do { \ + if (0) \ + printk(fmt, ##__VA_ARGS__); \ + } while (0); \ + 0; \ +})#ifdef CONFIG_EARLY_PRINTK extern asmlinkage __printf(1, 2)
@@ -309,20 +312,24 @@ extern asmlinkage void dump_stack(void) __cold;#define printk_once (fmt, ...) \ ({ \ static bool __print_once __read_mostly; \ + bool __ret_print_once = !__print_once; \ \ if (!__print_once) { \ __print_once = true; \ printk(fmt, ##__VA_ARGS__); \ } \ + unlikely(__ret_print_once); \ })#define printk_deferred_once (fmt, ...) \ ({ \ static bool __print_once __read_mostly; \ + bool __ret_print_once = !__print_once; \ \ if (!__print_once) { \ __print_once = true; \ printk_deferred(fmt, ##__VA_ARGS__); \ } \ + unlikely(__ret_print_once); \ })#else #define printk_once (fmt, ...) \
Signature · 1 line
2.7.3
tip-bot for Borislav Petkov
merge bot[tip:x86/debug] printk: Make the printk*once() variants return a value
printk: Make the printk*once() variants return a value
Commit-ID: 069f0cd00df0abfb9252e0dbdc355e40e6ab75fc Gitweb: http://git.kernel.org/tip/069f0cd00df0abfb9252e0dbdc355e40e6ab75fc Author: Borislav Petkov <[email protected]> AuthorDate: Tue, 5 Jul 2016 00:31:26 +0200 Committer: Ingo Molnar <[email protected]> CommitDate: Fri, 8 Jul 2016 11:33:18 +0200
printk: Make the printk*once() variants return a value
Have printk*once() return a bool which denotes whether the string was printed or not so that calling code can react accordingly.
--- include/linux/printk.h | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-)
include/linux/printk.h +12 -5
@@ -108,11 +108,14 @@ struct va_format {* Dummy printk for disabled debugging statements to use whilst maintaining * gcc's format checking. */ -#define no_printk (fmt, ...) \ -do { \ - if (0) \ - printk(fmt, ##__VA_ARGS__); \ -} while (0) +#define no_printk (fmt, ...) \ +({ \ + do { \ + if (0) \ + printk(fmt, ##__VA_ARGS__); \ + } while (0); \ + 0; \ +})#ifdef CONFIG_EARLY_PRINTK extern asmlinkage __printf(1, 2)
@@ -309,20 +312,24 @@ extern asmlinkage void dump_stack(void) __cold;#define printk_once (fmt, ...) \ ({ \ static bool __print_once __read_mostly; \ + bool __ret_print_once = !__print_once; \ \ if (!__print_once) { \ __print_once = true; \ printk(fmt, ##__VA_ARGS__); \ } \ + unlikely(__ret_print_once); \ })#define printk_deferred_once (fmt, ...) \ ({ \ static bool __print_once __read_mostly; \ + bool __ret_print_once = !__print_once; \ \ if (!__print_once) { \ __print_once = true; \ printk_deferred(fmt, ##__VA_ARGS__); \ } \ + unlikely(__ret_print_once); \ })#else #define printk_once (fmt, ...) \
Re: [tip:x86/debug] printk: Make the printk*once() variants return a value
Quoted 1 line · tip-bot for Borislav Petkov
printk: Make the printk*once() variants return a value
[]
include/linux/printk.h
Quoted 1 line
include/linux/printk.h +0
-0
[]
Quoted 7 lines
include/linux/printk.h +0
-3@@ -108,11 +108,14 @@ struct va_format {* Dummy printk for disabled debugging statements to use whilst maintaining * gcc's format checking. */ -#define no_printk (fmt, ...) \ -do { \ - if (0) \
include/linux/printk.h struct va_format { · line 118
Quoted 10 lines
include/linux/printk.h +8
-2- printk(fmt, ##__VA_ARGS__); \ -}while (0 ) +#define no_printk (fmt, ...) \ +({ \ + do { \ + if (0) \ + printk(fmt, ##__VA_ARGS__); \ + } while (0); \ + 0; \ +})
This change isn't described in the commit message and there doesn't seem to be a need to change this.
And as statement expressions these no longer need "do { <foo> } while (0)" this could have been be simplified to
#define no_printk(fmt, ...)
({
if (0)
printk(fmt, ##__VA_ARGS__);
})Borislav Petkov
maintainerRe: [tip:x86/debug] printk: Make the printk*once() variants return a value
Quoted 2 lines · Joe Perches
This change isn't described in the commit message and there doesn't seem to be a need to change this.
How do *you* know? Did *you* actually sit down and build a kernel with your proposed change before sending a reply?
I'm pretty sure you didn't.
Well, there is a very good reason why I made that change but I'm not going to tell you.
Signature · 4 lines
Regards/Gruss,
Boris.
ECO tip #101: Trim your mails when you reply.Re: [tip:x86/debug] printk: Make the printk*once() variants return a value
Quoted 6 lines · Borislav Petkov
Quoted 2 lines · Joe Perches
This change isn't described in the commit message and there doesn't seem to be a need to change this.
How do *you* know? Did *you* actually sit down and build a kernel with your proposed change before sending a reply?
I'm pretty sure you didn't.
defconfigs both with and without CONFIG_PRINTK build properly with the proposed change to this specific patch.
Quoted 2 lines
Well, there is a very good reason why I made that change but I'm not going to tell you.
Borislav, your delightful personality always impresses. Never change.
If there is a specific reason you know why this 0; value must be added to a do {} while (0) to statement expression macro conversion, it'd be good to write that in the commit message. It'd also be good to remove the useless "do {} while (0);" surrounding a single statement.
Borislav Petkov
maintainerRe: [tip:x86/debug] printk: Make the printk*once() variants return a value
Quoted 2 lines · Joe Perches
defconfigs both with and without
CONFIG_PRINTKbuild properly with the proposed change to this specific patch.
Did you try latest tip/master?
Quoted 2 lines
Borislav, your delightful personality always impresses. Never change.
What goes around comes around.
Signature · 5 lines
Regards/Gruss,
Boris.
ECO tip #101: Trim your mails when you reply.
--Re: [tip:x86/debug] printk: Make the printk*once() variants return a value
Quoted 5 lines · Borislav Petkov
Quoted 3 lines · Joe Perches
defconfigs both with and without
CONFIG_PRINTKbuild properly with the proposed change to this specific patch.Did you try latest tip/master?
Assuming tip is included in linux-next as of july 8, yes.
http://git.kernel.org/cgit/linux/kernel/git/next/linux-next.git/commit/?id=a379b037c5f3bcd0475ab2743b1f8a57fa970e22
[]
+tip 2c387f55aabaf88b6ee6857192a8f778bee05523
Borislav Petkov
maintainerRe: [tip:x86/debug] printk: Make the printk*once() variants return a value
Quoted 1 line · Joe Perches
Assuming tip is included in linux-next as of july 8, yes.
Try one which has http://git.kernel.org/tip/81c2949f7fdcf8ff681326669afde24962232670
Signature · 5 lines
Regards/Gruss,
Boris.
ECO tip #101: Trim your mails when you reply.
--Re: [tip:x86/debug] printk: Make the printk*once() variants return a value
Quoted 5 lines · Borislav Petkov
Quoted 2 lines · Joe Perches
Assuming tip is included in linux-next as of july 8, yes.
Try one which has http://git.kernel.org/tip/81c2949f7fdcf8ff681326669a
fde24962232670
That commit isn't in -next and this change to add a return value to no_printk in _this_ patch is, as I wrote, not required here.
It's fine that no_printk return a 0 value, but it should be introduced in a separate patch that introduces that requirement or at least mentioned in the changelog of this patch _why_ it's it to be changed. Otherwise, the change just appears spurious.
Borislav Petkov
maintainerPATCH 3/3 x86/dumpstack: Add show_stack_regs() and use it
In reply to Borislav PetkovFrom: Borislav Petkov <[email protected]>
Add a helper to dump supplied pt_regs and use it in the MSR exception handling code to have precise stack traces pointing to the actual function causing the MSR access exception and not the stack frame of the exception handler itself.
The new output looks like this:
unchecked MSR access error: RDMSR from 0xdeadbeef at rIP: 0xffffffff8102ddb6 (early_init_intel+0x16/0x3a0)
00000000756e6547 ffffffff81c03f68 ffffffff81dd0940 ffffffff81c03f10
ffffffff81d42e65 0000000001000000 ffffffff81c03f58 ffffffff81d3e5a3
0000800000000000 ffffffff81800080 ffffffffffffffff 0000000000000000
Call Trace: [<ffffffff81d42e65>] early_cpu_init+0xe7/0x136 [<ffffffff81d3e5a3>] setup_arch+0xa5/0x9df [<ffffffff81d38bb9>] start_kernel+0x9f/0x43a [<ffffffff81d38294>] x86_64_start_reservations+0x2f/0x31 [<ffffffff81d383fe>] x86_64_start_kernel+0x168/0x176
--- arch/x86/include/asm/kdebug.h | 1 + arch/x86/kernel/dumpstack.c | 5 +++++ arch/x86/mm/extable.c | 13 ++++++++----- 3 files changed, 14 insertions(+), 5 deletions(-)
arch/x86/include/asm/kdebug.h +1 -0
@@ -26,6 +26,7 @@ extern void die(const char *, struct pt_regs *,long);extern int __must_check __die (const char *,struct pt_regs *,long );extern void show_trace (struct task_struct *t,struct pt_regs *regs,unsigned long *sp,unsigned long bp); +extern void show_stack_regs (struct pt_regs *regs);extern void __show_regs (struct pt_regs *regs,int all);extern unsigned long oops_begin (void );extern void oops_end (unsigned long ,struct pt_regs *,int signr);
arch/x86/kernel/dumpstack.c +5 -0
@@ -197,6 +197,11 @@ void show_stack(struct task_struct *task, unsigned long *sp)show_stack_log_lvl (task,NULL , sp, bp,"" ); } +void show_stack_regs (struct pt_regs *regs) +{ +show_stack_log_lvl (current, regs, (unsigned long *)regs->sp, regs->bp,"" ); +} +static arch_spinlock_t die_lock = __ARCH_SPIN_LOCK_UNLOCKED;static int die_owner =-1 ;static unsigned int die_nest_count;
arch/x86/mm/extable.c +8 -5
@@ -1,6 +1,7 @@#include <linux/module.h> #include <asm/uaccess.h> #include <asm/traps.h> +#include <asm/kdebug.h> typedef bool (*ex_handler_t )(const struct exception_table_entry *,struct pt_regs *,int );
@@ -46,8 +47,9 @@ EXPORT_SYMBOL(ex_handler_ext);bool ex_handler_rdmsr_unsafe (const struct exception_table_entry *fixup,struct pt_regs *regs,int trapnr) { -WARN_ONCE (1 ,"unchecked MSR access error: RDMSR from 0x%x\n" , - (unsigned int )regs->cx); +if (pr_warn_once ("unchecked MSR access error: RDMSR from 0x%x at rIP: 0x%lx (%pF)\n" , + (unsigned int )regs->cx, regs->ip, (void *)regs->ip)) +show_stack_regs (regs);/* Pretend that the read succeeded and returned 0. */ regs->ip =ex_fixup_addr (fixup);
@@ -60,9 +62,10 @@ EXPORT_SYMBOL(ex_handler_rdmsr_unsafe);bool ex_handler_wrmsr_unsafe (const struct exception_table_entry *fixup,struct pt_regs *regs,int trapnr) { -WARN_ONCE (1 ,"unchecked MSR access error: WRMSR to 0x%x (tried to write 0x%08x%08x)\n" , - (unsigned int )regs->cx, - (unsigned int )regs->dx, (unsigned int )regs->ax); +if (pr_warn_once ("unchecked MSR access error: WRMSR to 0x%x (tried to write 0x%08x%08x) at rIP: 0x%lx (%pF)\n" , + (unsigned int )regs->cx, (unsigned int )regs->dx, + (unsigned int )regs->ax, regs->ip, (void *)regs->ip)) +show_stack_regs (regs);/* Pretend that the write succeeded. */ regs->ip =ex_fixup_addr (fixup);
Signature · 1 line
2.7.3
tip-bot for Borislav Petkov
merge bot[tip:x86/debug] x86/dumpstack: Add show_stack_regs() and use it
x86/dumpstack: Add show_stack_regs() and use it
Commit-ID: 81c2949f7fdcf8ff681326669afde24962232670 Gitweb: http://git.kernel.org/tip/81c2949f7fdcf8ff681326669afde24962232670 Author: Borislav Petkov <[email protected]> AuthorDate: Tue, 5 Jul 2016 00:31:27 +0200 Committer: Ingo Molnar <[email protected]> CommitDate: Fri, 8 Jul 2016 11:33:19 +0200
x86/dumpstack: Add show_stack_regs() and use it
Add a helper to dump supplied pt_regs and use it in the MSR exception handling code to have precise stack traces pointing to the actual function causing the MSR access exception and not the stack frame of the exception handler itself.
The new output looks like this:
unchecked MSR access error: RDMSR from 0xdeadbeef at rIP: 0xffffffff8102ddb6 (early_init_intel+0x16/0x3a0)
00000000756e6547 ffffffff81c03f68 ffffffff81dd0940 ffffffff81c03f10
ffffffff81d42e65 0000000001000000 ffffffff81c03f58 ffffffff81d3e5a3
0000800000000000 ffffffff81800080 ffffffffffffffff 0000000000000000
Call Trace: [<ffffffff81d42e65>] early_cpu_init+0xe7/0x136 [<ffffffff81d3e5a3>] setup_arch+0xa5/0x9df [<ffffffff81d38bb9>] start_kernel+0x9f/0x43a [<ffffffff81d38294>] x86_64_start_reservations+0x2f/0x31 [<ffffffff81d383fe>] x86_64_start_kernel+0x168/0x176
--- arch/x86/include/asm/kdebug.h | 1 + arch/x86/kernel/dumpstack.c | 5 +++++ arch/x86/mm/extable.c | 13 ++++++++----- 3 files changed, 14 insertions(+), 5 deletions(-)
arch/x86/include/asm/kdebug.h +1 -0
@@ -26,6 +26,7 @@ extern void die(const char *, struct pt_regs *,long);extern int __must_check __die (const char *,struct pt_regs *,long );extern void show_trace (struct task_struct *t,struct pt_regs *regs,unsigned long *sp,unsigned long bp); +extern void show_stack_regs (struct pt_regs *regs);extern void __show_regs (struct pt_regs *regs,int all);extern unsigned long oops_begin (void );extern void oops_end (unsigned long ,struct pt_regs *,int signr);
arch/x86/kernel/dumpstack.c +5 -0
@@ -197,6 +197,11 @@ void show_stack(struct task_struct *task, unsigned long *sp)show_stack_log_lvl (task,NULL , sp, bp,"" ); } +void show_stack_regs (struct pt_regs *regs) +{ +show_stack_log_lvl (current, regs, (unsigned long *)regs->sp, regs->bp,"" ); +} +static arch_spinlock_t die_lock = __ARCH_SPIN_LOCK_UNLOCKED;static int die_owner =-1 ;static unsigned int die_nest_count;
arch/x86/mm/extable.c +8 -5
@@ -1,6 +1,7 @@#include <linux/module.h> #include <asm/uaccess.h> #include <asm/traps.h> +#include <asm/kdebug.h> typedef bool (*ex_handler_t )(const struct exception_table_entry *,struct pt_regs *,int );
@@ -46,8 +47,9 @@ EXPORT_SYMBOL(ex_handler_ext);bool ex_handler_rdmsr_unsafe (const struct exception_table_entry *fixup,struct pt_regs *regs,int trapnr) { -WARN_ONCE (1 ,"unchecked MSR access error: RDMSR from 0x%x\n" , - (unsigned int )regs->cx); +if (pr_warn_once ("unchecked MSR access error: RDMSR from 0x%x at rIP: 0x%lx (%pF)\n" , + (unsigned int )regs->cx, regs->ip, (void *)regs->ip)) +show_stack_regs (regs);/* Pretend that the read succeeded and returned 0. */ regs->ip =ex_fixup_addr (fixup);
@@ -60,9 +62,10 @@ EXPORT_SYMBOL(ex_handler_rdmsr_unsafe);bool ex_handler_wrmsr_unsafe (const struct exception_table_entry *fixup,struct pt_regs *regs,int trapnr) { -WARN_ONCE (1 ,"unchecked MSR access error: WRMSR to 0x%x (tried to write 0x%08x%08x)\n" , - (unsigned int )regs->cx, - (unsigned int )regs->dx, (unsigned int )regs->ax); +if (pr_warn_once ("unchecked MSR access error: WRMSR to 0x%x (tried to write 0x%08x%08x) at rIP: 0x%lx (%pF)\n" , + (unsigned int )regs->cx, (unsigned int )regs->dx, + (unsigned int )regs->ax, regs->ip, (void *)regs->ip)) +show_stack_regs (regs);/* Pretend that the write succeeded. */ regs->ip =ex_fixup_addr (fixup);
Re: [PATCH -v2 0/3] x86/MSR: Improve unhandled MSR access error message
In reply to Borislav PetkovQuoted 5 lines · Borislav Petkov
From: Borislav Petkov <[email protected]>
Hi all,
here's v2 with review feedback integrated.
Just a silly overall thought: WARN_ON will be noticed by the 0day bot and similar tools. This series changes the message text a bit -- is it still noticed?
--Andy
Borislav Petkov
maintainerRe: [PATCH -v2 0/3] x86/MSR: Improve unhandled MSR access error message
Quoted 10 lines · Andy Lutomirski
Quoted 5 lines · Borislav Petkov
From: Borislav Petkov <[email protected]>
Hi all,
here's v2 with review feedback integrated.
Just a silly overall thought:
WARN_ONwill be noticed by the 0day bot and similar tools. This series changes the message text a bit -- is it still noticed?
No idea, let's CC them.
Signature · 4 lines
Regards/Gruss,
Boris.
ECO tip #101: Trim your mails when you reply.