Introduce the STACKLEAK feature and a test for it
Subsystems (13)
v11 cover Introduce the STACKLEAK feature and a test for it
This is the 11th version of the patch series introducing STACKLEAK to the mainline kernel. The 9th version raised a fervent discussion[0]. The assembly code introduced by that version irritated the reviewers.
I've found the way to bypass the obstacles[1] of the C implementation. So I dare come again. Let me ask you to look at this code without preconception.
Motivation
==========
STACKLEAK (initially developed by PaX Team):
1. reduces the information that can be revealed through kernel stack leak bugs. The idea of erasing the thread stack at the end of syscalls is similar to CONFIG_PAGE_POISONING and memzero_explicit() in kernel crypto, which all comply with FDP_RIP.2 (Full Residual Information Protection) of the Common Criteria standard.
2. blocks some uninitialized stack variable attacks (e.g. CVE-2017-17712, CVE-2010-2963). That kind of bugs should be killed by improving C compilers in future, which might take a long time.
3. blocks stack depth overflow caused by alloca (aka Stack Clash attack). That is orthogonal to the mainline kernel VLA cleanup and protects un-upstreamed code.
Performance impact
==================
Hardware: Intel Core i7-4770, 16 GB RAM
Test #1: building the Linux kernel on a single core
0.91% slowdown
Test #2: hackbench -s 4096 -l 2000 -g 15 -f 25 -P
4.2% slowdown
So the STACKLEAK description in Kconfig includes:
"The tradeoff is the performance impact: on a single CPU system kernel compilation sees a 1% slowdown, other systems and workloads may vary and you are advised to test this feature on your expected workload before deploying it".
Links
=====
[0] http://www.openwall.com/lists/kernel-hardening/2018/03/03/7
[1] http://www.openwall.com/lists/kernel-hardening/2018/03/21/4
Alexander Popov (6):
gcc-plugins: Clean up the cgraph_create_edge* macros x86/entry: Add STACKLEAK erasing the kernel stack at the end of
syscalls
gcc-plugins: Add STACKLEAK plugin for tracking the kernel stack
lkdtm: Add a test for STACKLEAK
fs/proc: Show STACKLEAK metrics in the /proc file system
doc: self-protection: Add information about STACKLEAK feature
Documentation/security/self-protection.rst | 23 +- Documentation/x86/x86_64/mm.txt | 2 + arch/Kconfig | 53 ++++ arch/x86/Kconfig | 1 + arch/x86/entry/Makefile | 3 + arch/x86/entry/calling.h | 14 + arch/x86/entry/entry_32.S | 7 + arch/x86/entry/entry_64.S | 3 + arch/x86/entry/entry_64_compat.S | 5 + arch/x86/entry/erase.c | 58 ++++ arch/x86/include/asm/processor.h | 7 + arch/x86/kernel/dumpstack.c | 19 ++ arch/x86/kernel/process_32.c | 8 + arch/x86/kernel/process_64.c | 8 + drivers/misc/Makefile | 3 + drivers/misc/lkdtm.h | 4 + drivers/misc/lkdtm_core.c | 2 + drivers/misc/lkdtm_stackleak.c | 141 +++++++++ fs/proc/base.c | 18 ++ include/linux/compiler.h | 4 + mm/util.c | 33 ++ scripts/Makefile.gcc-plugins | 3 + scripts/gcc-plugins/gcc-common.h | 26 +- scripts/gcc-plugins/stackleak_plugin.c | 470 +++++++++++++++++++++++++++++ 24 files changed, 896 insertions(+), 19 deletions(-) create mode 100644 arch/x86/entry/erase.c create mode 100644 drivers/misc/lkdtm_stackleak.c create mode 100644 scripts/gcc-plugins/stackleak_plugin.c
Signature · 1 line
2.7.4
PATCH v11 1/6 gcc-plugins: Clean up the cgraph_create_edge* macros
Drop useless redefinitions of cgraph_create_edge* macros. Drop the unused nest argument. Also support gcc-8, which doesn't have freq argument.
--- scripts/gcc-plugins/gcc-common.h | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-)
scripts/gcc-plugins/gcc-common.h +16 -10
@@ -392,13 +392,6 @@ static inline struct cgraph_node *cgraph_alias_target(struct cgraph_node *n) }#endif -#if BUILDING_GCC_VERSION >=4007 &&BUILDING_GCC_VERSION <=4009 -#define cgraph_create_edge (caller, callee, call_stmt, count, freq, nest) \ - cgraph_create_edge((caller), (callee), (call_stmt), (count), (freq)) -#define cgraph_create_edge_including_clones (caller, callee, old_call_stmt, call_stmt, count, freq, nest, reason) \ - cgraph_create_edge_including_clones((caller), (callee), (old_call_stmt), (call_stmt), (count), (freq), (reason)) -#endif -#if BUILDING_GCC_VERSION <=4008 #define ENTRY_BLOCK_PTR_FOR_FN (FN ) ENTRY_BLOCK_PTR_FOR_FUNCTION(FN)#define EXIT_BLOCK_PTR_FOR_FN (FN ) EXIT_BLOCK_PTR_FOR_FUNCTION(FN)
@@ -723,10 +716,23 @@ static inline const char *get_decl_section_name(const_tree decl)#define varpool_get_node (decl) varpool_node::get(decl)#define dump_varpool_node (file, node) (node)->dump(file) -#define cgraph_create_edge (caller, callee, call_stmt, count, freq, nest) \ +#if BUILDING_GCC_VERSION >=8000 +#define cgraph_create_edge (caller, callee, call_stmt, count, freq) \ + (caller)->create_edge((callee), (call_stmt), (count)) + +#define cgraph_create_edge_including_clones (caller, callee, \ + old_call_stmt, call_stmt, count, freq, reason) \ + (caller)->create_edge_including_clones((callee), \ + (old_call_stmt), (call_stmt), (count), (reason)) +#else +#define cgraph_create_edge (caller, callee, call_stmt, count, freq) \ (caller)->create_edge((callee), (call_stmt), (count), (freq)) -#define cgraph_create_edge_including_clones (caller, callee, old_call_stmt, call_stmt, count, freq, nest, reason) \ - (caller)->create_edge_including_clones((callee), (old_call_stmt), (call_stmt), (count), (freq), (reason)) + +#define cgraph_create_edge_including_clones (caller, callee, \ + old_call_stmt, call_stmt, count, freq, reason) \ + (caller)->create_edge_including_clones((callee), \ + (old_call_stmt), (call_stmt), (count), (freq), (reason)) +#endif typedef struct cgraph_node *cgraph_node_ptr ;typedef struct cgraph_edge *cgraph_edge_p ;
Signature · 1 line
2.7.4
PATCH v11 2/6 x86/entry: Add STACKLEAK erasing the kernel stack at the end of syscalls
In reply to Alexander PopovThe STACKLEAK feature erases the kernel stack before returning from syscalls. That reduces the information which kernel stack leak bugs can reveal and blocks some uninitialized stack variable attacks. Moreover, STACKLEAK provides runtime checks for kernel stack overflow detection.
This commit introduces the architecture-specific code filling the used part of the kernel stack with a poison value before returning to the userspace. Full STACKLEAK feature also contains the gcc plugin which comes in a separate commit.
The STACKLEAK feature is ported from grsecurity/PaX. More information at:
https://grsecurity.net/
https://pax.grsecurity.net/
This code is modified from Brad Spengler/PaX Team's code in the last public patch of grsecurity/PaX based on our understanding of the code. Changes or omissions from the original code are ours and don't reflect the original grsecurity/PaX code.
--- Documentation/x86/x86_64/mm.txt | 2 ++ arch/Kconfig | 27 ++++++++++++++++++++ arch/x86/Kconfig | 1 + arch/x86/entry/Makefile | 3 +++ arch/x86/entry/calling.h | 14 +++++++++++ arch/x86/entry/entry_32.S | 7 ++++++ arch/x86/entry/entry_64.S | 3 +++ arch/x86/entry/entry_64_compat.S | 5 ++++ arch/x86/entry/erase.c | 54 ++++++++++++++++++++++++++++++++++++++++ arch/x86/include/asm/processor.h | 4 +++ arch/x86/kernel/process_32.c | 5 ++++ arch/x86/kernel/process_64.c | 5 ++++ include/linux/compiler.h | 4 +++ 13 files changed, 134 insertions(+) create mode 100644 arch/x86/entry/erase.c
Documentation/x86/x86_64/mm.txt +2 -0
@@ -24,6 +24,7 @@ ffffffffa0000000 - [fixmap start] (~1526 MB) module mapping space (variable) [fixmap start] - ffffffffff5fffff kernel-internal fixmap range ffffffffff600000 - ffffffffff600fff (=4 kB) legacy vsyscall ABI ffffffffffe00000 - ffffffffffffffff (=2 MB) unused hole +STACKLEAK_POISON value in this last hole: ffffffffffff4111 Virtual memory map with 5 level page tables:
@@ -50,6 +51,7 @@ ffffffffa0000000 - fffffffffeffffff (1520 MB) module mapping space [fixmap start] - ffffffffff5fffff kernel-internal fixmap range ffffffffff600000 - ffffffffff600fff (=4 kB) legacy vsyscall ABI ffffffffffe00000 - ffffffffffffffff (=2 MB) unused hole +STACKLEAK_POISON value in this last hole: ffffffffffff4111 Architecture defines a 64-bit virtual address. Implementations can support less. Currently supported are 48- and 57-bit virtual addresses. Bits 63
arch/Kconfig +27 -0
@@ -401,6 +401,13 @@ config SECCOMP_FILTER See Documentation/prctl/seccomp_filter.txt for details. +config HAVE_ARCH_STACKLEAK + bool + help + An architecture should select this if it has the code which + fills the used part of the kernel stack with the STACKLEAK_POISON + value before returning from system calls. + config HAVE_GCC_PLUGINS bool help
@@ -531,6 +538,26 @@ config GCC_PLUGIN_RANDSTRUCT_PERFORMANCE in structures. This reduces the performance hit of RANDSTRUCT at the cost of weakened randomization. +config GCC_PLUGIN_STACKLEAK + bool "Erase the kernel stack before returning from syscalls" + depends on GCC_PLUGINS + depends on HAVE_ARCH_STACKLEAK + help + This option makes the kernel erase the kernel stack before it + returns from a system call. That reduces the information which + kernel stack leak bugs can reveal and blocks some uninitialized + stack variable attacks. This option also provides runtime checks + for kernel stack overflow detection. + + The tradeoff is the performance impact: on a single CPU system kernel + compilation sees a 1% slowdown, other systems and workloads may vary + and you are advised to test this feature on your expected workload + before deploying it. + + This plugin was ported from grsecurity/PaX. More information at: + * https://grsecurity.net/ + * https://pax.grsecurity.net/ + config HAVE_CC_STACKPROTECTOR bool help
arch/x86/Kconfig +1 -0
@@ -119,6 +119,7 @@ config X86 select HAVE_ARCH_COMPAT_MMAP_BASES if MMU && COMPAT select HAVE_ARCH_SECCOMP_FILTER select HAVE_ARCH_THREAD_STRUCT_WHITELIST + select HAVE_ARCH_STACKLEAK select HAVE_ARCH_TRACEHOOK select HAVE_ARCH_TRANSPARENT_HUGEPAGE select HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD if X86_64
arch/x86/entry/Makefile +3 -0
@@ -15,3 +15,6 @@ obj-y += vsyscall/ obj-$(CONFIG_IA32_EMULATION) += entry_64_compat.o syscall_32.o +obj-$(CONFIG_GCC_PLUGIN_STACKLEAK) += erase.o +KASAN_SANITIZE_erase.o := n +
arch/x86/entry/calling.h +14 -0
@@ -327,8 +327,22 @@ For 32-bit we have the following conventions - kernel is built with #endif +.macro ERASE_KSTACK_NOCLOBBER +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK + PUSH_AND_CLEAR_REGS + call erase_kstack + POP_REGS +#endif +.endm + #endif /* CONFIG_X86_64 */ +.macro ERASE_KSTACK +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK + call erase_kstack +#endif +.endm + /* * This does 'call enter_from_user_mode' unless we can avoid it based on * kernel config or using the static jump infrastructure.
arch/x86/entry/entry_32.S +7 -0
@@ -46,6 +46,8 @@ #include <asm/frame.h> #include <asm/nospec-branch.h> +#include "calling.h" + .section .entry.text, "ax" /*
@@ -298,6 +300,7 @@ ENTRY(ret_from_fork) /* When we fork, we trace the syscall return in the child, too. */ movl %esp, %eax call syscall_return_slowpath + ERASE_KSTACK jmp restore_all /* kernel thread */
@@ -458,6 +461,8 @@ ENTRY(entry_SYSENTER_32) ALTERNATIVE "testl %eax, %eax; jz .Lsyscall_32_done", \ "jmp .Lsyscall_32_done", X86_FEATURE_XENPV + ERASE_KSTACK + /* Opportunistic SYSEXIT */ TRACE_IRQS_ON /* User mode traces as IRQs on. */ movl PT_EIP(%esp), %edx /* pt_regs->ip */
@@ -544,6 +549,8 @@ ENTRY(entry_INT80_32) call do_int80_syscall_32 .Lsyscall_32_done: + ERASE_KSTACK + restore_all: TRACE_IRQS_IRET .Lrestore_all_notrace:
arch/x86/entry/entry_64.S +3 -0
@@ -323,6 +323,8 @@ syscall_return_via_sysret: * We are on the trampoline stack. All regs except RDI are live. * We can do future final exit work right here. */ + ERASE_KSTACK_NOCLOBBER + SWITCH_TO_USER_CR3_STACK scratch_reg=%rdi popq %rdi
@@ -681,6 +683,7 @@ GLOBAL(swapgs_restore_regs_and_return_to_usermode) * We are on the trampoline stack. All regs except RDI are live. * We can do future final exit work right here. */ + ERASE_KSTACK_NOCLOBBER SWITCH_TO_USER_CR3_STACK scratch_reg=%rdi
arch/x86/entry/entry_64_compat.S +5 -0
@@ -258,6 +258,11 @@ GLOBAL(entry_SYSCALL_compat_after_hwframe) /* Opportunistic SYSRET */ sysret32_from_system_call: + /* + * We are not going to return to the userspace from the trampoline + * stack. So let's erase the thread stack right now. + */ + ERASE_KSTACK TRACE_IRQS_ON /* User mode traces as IRQs on. */ movq RBX(%rsp), %rbx /* pt_regs->rbx */ movq RBP(%rsp), %rbp /* pt_regs->rbp */
arch/x86/entry/erase.c new file +54 -0
54 lines not on this page · raw message
arch/x86/include/asm/processor.h +4 -0
@@ -494,6 +494,10 @@ struct thread_struct {mm_segment_t addr_limit; +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK +unsigned long lowest_stack; +#endif + unsigned int sig_on_uaccess_err:1; unsigned int uaccess_err:1; /* uaccess failed */
arch/x86/kernel/process_32.c +5 -0
@@ -136,6 +136,11 @@ int copy_thread_tls(unsigned long clone_flags, unsigned long sp, p->thread.sp0 = (unsigned long ) (childregs+1 );memset (p->thread.ptrace_bps,0 ,sizeof (p->thread.ptrace_bps)); +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK + p->thread.lowest_stack = (unsigned long )task_stack_page (p) + +sizeof (unsigned long ); +#endif +if (unlikely (p->flags &PF_KTHREAD )) {/* kernel thread */ memset (childregs,0 ,sizeof (struct pt_regs ));
arch/x86/kernel/process_64.c +5 -0
@@ -281,6 +281,11 @@ int copy_thread_tls(unsigned long clone_flags, unsigned long sp, p->thread.sp = (unsigned long ) fork_frame; p->thread.io_bitmap_ptr =NULL ; +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK + p->thread.lowest_stack = (unsigned long )task_stack_page (p) + +sizeof (unsigned long ); +#endif +savesegment (gs, p->thread.gsindex); p->thread.gsbase = p->thread.gsindex ?0 : me->thread.gsbase;savesegment (fs, p->thread.fsindex);
include/linux/compiler.h +4 -0
@@ -342,4 +342,8 @@ unsigned long read_word_at_a_time(const void *addr)compiletime_assert (__native_word (t), \"Need native word sized stores/loads for atomicity." ) +/* Poison value points to the unused hole in the virtual memory map */ +#define STACKLEAK_POISON -0xBEEF +#define STACKLEAK_POISON_CHECK_DEPTH 128 +#endif /* __LINUX_COMPILER_H */
Signature · 1 line
2.7.4
Re: [PATCH v11 2/6] x86/entry: Add STACKLEAK erasing the kernel stack at the end of syscalls
Quoted 4 lines · Alexander Popov
This commit introduces the architecture-specific code filling the used part of the kernel stack with a poison value before returning to the userspace. Full STACKLEAK feature also contains the gcc plugin which comes in a separate commit.
Thanks for sending this again! And thanks for the updated reasoning for why this remains a valuable addition:
https://lkml.kernel.org/r/[email protected]
I, too, remain convinced this is a good protection to have, even as we slowly remove VLAs and try to improve the compiler's initialization of stack variables.
Dave, Ingo, Linus: how does this look? With the assembly rewritten into C, the entry changes are very small:
Quoted 4 lines
arch/x86/entry/entry_32.S | 7 ++++++ arch/x86/entry/entry_64.S | 3 +++ arch/x86/entry/entry_64_compat.S | 5 ++++ arch/x86/entry/erase.c | 54 ++++++++++++++++++++++++++++++++++++++++
I'd really like to get people's Ack/Review. :)
Laura, can this C version work for arm64 as well?
Thanks,
-Kees
Signature · 2 lines
Kees Cook Pixel Security
Re: [PATCH v11 2/6] x86/entry: Add STACKLEAK erasing the kernel stack at the end of syscalls
Quoted 23 lines · Kees Cook
Quoted 4 lines · Alexander Popov
This commit introduces the architecture-specific code filling the used part of the kernel stack with a poison value before returning to the userspace. Full STACKLEAK feature also contains the gcc plugin which comes in a separate commit.
Thanks for sending this again! And thanks for the updated reasoning for why this remains a valuable addition:
https://lkml.kernel.org/r/[email protected]
I, too, remain convinced this is a good protection to have, even as we slowly remove VLAs and try to improve the compiler's initialization of stack variables.
Dave, Ingo, Linus: how does this look? With the assembly rewritten into C, the entry changes are very small:
Quoted 4 lines
arch/x86/entry/entry_32.S | 7 ++++++ arch/x86/entry/entry_64.S | 3 +++ arch/x86/entry/entry_64_compat.S | 5 ++++ arch/x86/entry/erase.c | 54 ++++++++++++++++++++++++++++++++++++++++
Quoted 8 lines
I'd really like to get people's Ack/Review. :)
Laura, can this C version work for arm64 as well?
Thanks,
-Kees
I did a quick port and it seems to work on a minimal system (passes LKDTM tests). I'll clean it up and do a few more tests to send out and see about give this series another review.
Thanks,
Laura
Dave Hansen
maintainerRe: [PATCH v11 2/6] x86/entry: Add STACKLEAK erasing the kernel stack at the end of syscalls
In reply to Kees CookQuoted 2 lines · Kees Cook
Dave, Ingo, Linus: how does this look? With the assembly rewritten into C, the entry changes are very small:
The assembly looks very nice to me now. It is as minimally invasive as it can get. Definitely no objections from me on that part.
Re: [PATCH v11 2/6] x86/entry: Add STACKLEAK erasing the kernel stack at the end of syscalls
Quoted 6 lines · Dave Hansen
Quoted 2 lines · Kees Cook
Dave, Ingo, Linus: how does this look? With the assembly rewritten into C, the entry changes are very small:
The assembly looks very nice to me now. It is as minimally invasive as it can get. Definitely no objections from me on that part.
Can you give an Acked-by for the x86 parts? Or Ingo?
If this is workable, I'd like to carry it in -next to see if anything else shakes out...
-Kees
Signature · 2 lines
Kees Cook Pixel Security
Re: [PATCH v11 2/6] x86/entry: Add STACKLEAK erasing the kernel stack at the end of syscalls
In reply to Alexander PopovHi Alexander,
You can add:
for this patch if you like. I haven't taken a super close look at the rest, but this is certainly minimally invasive from my point of view for the entry code. Thanks, again for reworking it.
Re: [PATCH v11 2/6] x86/entry: Add STACKLEAK erasing the kernel stack at the end of syscalls
Quoted 9 lines · Dave Hansen
Hi Alexander,
You can add:
Reviewed-by Dave Hansen <[email protected]>for this patch if you like. I haven't taken a super close look at the rest, but this is certainly minimally invasive from my point of view for the entry code. Thanks, again for reworking it.
Thanks Dave!
Given this improvement and your review, I'm going to start carrying this for linux-next. Linus, if you're still opposed to this even after the changes here in v11, please let us know. I'd rather hash things out now instead of during a NAK in the 4.18 merge window. :)
Thanks!
-Kees
Signature · 2 lines
Kees Cook Pixel Security
Re: [PATCH v11 2/6] x86/entry: Add STACKLEAK erasing the kernel stack at the end of syscalls
Quoted 12 lines · Kees Cook
Quoted 9 lines · Dave Hansen
Hi Alexander,
You can add:
Reviewed-by Dave Hansen <[email protected]>for this patch if you like. I haven't taken a super close look at the rest, but this is certainly minimally invasive from my point of view for the entry code. Thanks, again for reworking it.
Quoted 6 lines
Thanks Dave!
Given this improvement and your review, I'm going to start carrying this for linux-next. Linus, if you're still opposed to this even after the changes here in v11, please let us know. I'd rather hash things out now instead of during a NAK in the 4.18 merge window. :)
Kees, can we please route that x86/entry stuff through tip to avoid conflicts as there are other changes in that area on the horizon.
Thanks,
tglx
Re: [PATCH v11 2/6] x86/entry: Add STACKLEAK erasing the kernel stack at the end of syscalls
Quoted 16 lines · Thomas Gleixner
Quoted 14 lines · Kees Cook
Quoted 9 lines · Dave Hansen
Hi Alexander,
You can add:
Reviewed-by Dave Hansen <[email protected]>for this patch if you like. I haven't taken a super close look at the rest, but this is certainly minimally invasive from my point of view for the entry code. Thanks, again for reworking it.
Thanks Dave!
Quoted 7 lines
Quoted 4 lines
Given this improvement and your review, I'm going to start carrying this for linux-next. Linus, if you're still opposed to this even after the changes here in v11, please let us know. I'd rather hash things out now instead of during a NAK in the 4.18 merge window. :)
Kees, can we please route that x86/entry stuff through tip to avoid conflicts as there are other changes in that area on the horizon.
Sure, let me figure out how best to split up the patches, since it touch x86/entry, gcc-plugins, and lkdtm. Thanks!
-Kees
Signature · 2 lines
Kees Cook Pixel Security
Re: [PATCH v11 2/6] x86/entry: Add STACKLEAK erasing the kernel stack at the end of syscalls
Quoted 22 lines · Kees Cook
Quoted 21 lines · Thomas Gleixner
Quoted 18 lines · Kees Cook
Quoted 9 lines · Dave Hansen
Hi Alexander,
You can add:
Reviewed-by Dave Hansen <[email protected]>for this patch if you like. I haven't taken a super close look at the rest, but this is certainly minimally invasive from my point of view for the entry code. Thanks, again for reworking it.
Thanks Dave!
Given this improvement and your review, I'm going to start carrying this for linux-next. Linus, if you're still opposed to this even after the changes here in v11, please let us know. I'd rather hash things out now instead of during a NAK in the 4.18 merge window. :)
Quoted 5 lines
Quoted 2 lines
Kees, can we please route that x86/entry stuff through tip to avoid conflicts as there are other changes in that area on the horizon.
Sure, let me figure out how best to split up the patches, since it touch x86/entry, gcc-plugins, and lkdtm. Thanks!
Are they independent or do they carry dependencies?
Thanks,
tglx
Re: [PATCH v11 2/6] x86/entry: Add STACKLEAK erasing the kernel stack at the end of syscalls
Quoted 23 lines · Thomas Gleixner
Quoted 22 lines · Kees Cook
Quoted 21 lines · Thomas Gleixner
Quoted 18 lines · Kees Cook
Quoted 9 lines · Dave Hansen
Hi Alexander,
You can add:
Reviewed-by Dave Hansen <[email protected]>for this patch if you like. I haven't taken a super close look at the rest, but this is certainly minimally invasive from my point of view for the entry code. Thanks, again for reworking it.
Thanks Dave!
Given this improvement and your review, I'm going to start carrying this for linux-next. Linus, if you're still opposed to this even after the changes here in v11, please let us know. I'd rather hash things out now instead of during a NAK in the 4.18 merge window. :)
Quoted 7 lines
Quoted 5 lines
Quoted 2 lines
Kees, can we please route that x86/entry stuff through tip to avoid conflicts as there are other changes in that area on the horizon.
Sure, let me figure out how best to split up the patches, since it touch x86/entry, gcc-plugins, and lkdtm. Thanks!
Are they independent or do they carry dependencies?
They carry dependencies, as it interacts with the gcc plugin (and lkdtm). As I don't have other plugin changes for 4.18 queued, you could take the whole series for x86/entry if you want? Otherwise I can try to split out the x86 change so it's more self-contained.
-Kees
Signature · 2 lines
Kees Cook Pixel Security
Re: [PATCH v11 2/6] x86/entry: Add STACKLEAK erasing the kernel stack at the end of syscalls
Quoted 30 lines · Kees Cook
Quoted 29 lines · Thomas Gleixner
Quoted 27 lines · Kees Cook
Quoted 16 lines · Thomas Gleixner
Quoted 14 lines · Kees Cook
Quoted 9 lines · Dave Hansen
Hi Alexander,
You can add:
Reviewed-by Dave Hansen <[email protected]>for this patch if you like. I haven't taken a super close look at the rest, but this is certainly minimally invasive from my point of view for the entry code. Thanks, again for reworking it.
Thanks Dave!
Quoted 7 lines
Quoted 4 lines
Given this improvement and your review, I'm going to start carrying this for linux-next. Linus, if you're still opposed to this even after the changes here in v11, please let us know. I'd rather hash things out now instead of during a NAK in the 4.18 merge window. :)
Kees, can we please route that x86/entry stuff through tip to avoid conflicts as there are other changes in that area on the horizon.
Sure, let me figure out how best to split up the patches, since it touch x86/entry, gcc-plugins, and lkdtm. Thanks!
Quoted 6 lines
Quoted 1 line
Are they independent or do they carry dependencies?
They carry dependencies, as it interacts with the gcc plugin (and lkdtm). As I don't have other plugin changes for 4.18 queued, you could take the whole series for x86/entry if you want? Otherwise I can try to split out the x86 change so it's more self-contained.
The best way to do this would be to add the x86 entry changes without CONFIG_GCC_PLUGIN_STACKLEAK, which leaves the results not compile-testable. Alternatively, if you carried everything, it'd be weird too, with arm64 coming (which has small changes to the plugin).
I think it'd be better for this to go via my tree with your Ack (where I can carry the plugin, lkdtm, x86, and arm64 changes). How does that sound?
-Kees
Signature · 2 lines
Kees Cook Pixel Security
Thomas Gleixner
Acked-byRe: [PATCH v11 2/6] x86/entry: Add STACKLEAK erasing the kernel stack at the end of syscalls
Quoted 4 lines · Kees Cook
On Wed, May 2, 2018 at 5:51 AM, Kees Cook <[email protected]> wrote:
I think it'd be better for this to go via my tree with your Ack (where I can carry the plugin, lkdtm, x86, and arm64 changes). How does that sound?
Works for me.
PATCH v11 3/6 gcc-plugins: Add STACKLEAK plugin for tracking the kernel stack
In reply to Alexander PopovThe STACKLEAK feature erases the kernel stack before returning from syscalls. That reduces the information which kernel stack leak bugs can reveal and blocks some uninitialized stack variable attacks. Moreover, STACKLEAK provides runtime checks for kernel stack overflow detection.
This commit introduces the STACKLEAK gcc plugin. It is needed for:
- tracking the lowest border of the kernel stack, which is important for the code erasing the used part of the kernel stack at the end of syscalls (comes in a separate commit);
- checking that alloca calls don't cause stack overflow.
So this plugin instruments the kernel code inserting:
- the check_alloca() call before alloca and the track_stack() call
after it;
- the track_stack() call for the functions with a stack frame size greater than or equal to CONFIG_STACKLEAK_TRACK_MIN_SIZE.
The STACKLEAK feature is ported from grsecurity/PaX. More information at:
https://grsecurity.net/
https://pax.grsecurity.net/
This code is modified from Brad Spengler/PaX Team's code in the last public patch of grsecurity/PaX based on our understanding of the code. Changes or omissions from the original code are ours and don't reflect the original grsecurity/PaX code.
--- arch/Kconfig | 14 + arch/x86/kernel/dumpstack.c | 19 ++ mm/util.c | 33 +++ scripts/Makefile.gcc-plugins | 3 + scripts/gcc-plugins/stackleak_plugin.c | 470 +++++++++++++++++++++++++++++++++ 5 files changed, 539 insertions(+) create mode 100644 scripts/gcc-plugins/stackleak_plugin.c
arch/Kconfig +14 -0
@@ -542,6 +542,8 @@ config GCC_PLUGIN_STACKLEAK bool "Erase the kernel stack before returning from syscalls" depends on GCC_PLUGINS depends on HAVE_ARCH_STACKLEAK + imply VMAP_STACK + imply SCHED_STACK_END_CHECK help This option makes the kernel erase the kernel stack before it returns from a system call. That reduces the information which
@@ -558,6 +560,18 @@ config GCC_PLUGIN_STACKLEAK * https://grsecurity.net/ * https://pax.grsecurity.net/ +config STACKLEAK_TRACK_MIN_SIZE + int "Minimum stack frame size of functions tracked by STACKLEAK" + default 100 + range 0 4096 + depends on GCC_PLUGIN_STACKLEAK + help + The STACKLEAK gcc plugin instruments the kernel code for tracking + the lowest border of the kernel stack (and for some other purposes). + It inserts the track_stack() call for the functions with a stack + frame size greater than or equal to this parameter. + If unsure, leave the default value 100. + config HAVE_CC_STACKPROTECTOR bool help
arch/x86/kernel/dumpstack.c +19 -0
@@ -375,3 +375,22 @@ static int __init code_bytes_setup(char *s)return 1 ; }__setup ("code_bytes=" , code_bytes_setup); + +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK +#define MIN_STACK_LEFT 256 + +void __used check_alloca (unsigned long size) +{ +unsigned long sp = (unsigned long )&sp; +struct stack_info stack_info = {0 }; +unsigned long visit_mask =0 ; +unsigned long stack_left; + +BUG_ON (get_stack_info (&sp, current, &stack_info, &visit_mask)); + + stack_left = sp - (unsigned long )stack_info.begin; +BUG_ON (stack_left <MIN_STACK_LEFT || + size >= stack_left -MIN_STACK_LEFT ); +} +EXPORT_SYMBOL (check_alloca); +#endif
mm/util.c +33 -0
@@ -757,3 +757,36 @@ int get_cmdline(struct task_struct *task, char *buffer, int buflen) out:return res; } + +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK +void __used track_stack (void ) +{ +/* +* N.B. The arch-specific part of the STACKLEAK feature fills the +* kernel stack with the poison value, which has the register width. +* That code assumes that the value of thread.lowest_stack is aligned +* on the register width boundary. +* +* That is true for x86 and x86_64 because of the kernel stack +* alignment on these platforms (for details, see cc_stack_align in +* arch/x86/Makefile). Take care of that when you port STACKLEAK to +* new platforms. +*/ +unsigned long sp = (unsigned long )&sp; + +/* +* Having CONFIG_STACKLEAK_TRACK_MIN_SIZE larger than +* STACKLEAK_POISON_CHECK_DEPTH makes the poison search in +* erase_kstack() unreliable. Let's prevent that. +*/ +BUILD_BUG_ON (CONFIG_STACKLEAK_TRACK_MIN_SIZE > +STACKLEAK_POISON_CHECK_DEPTH ); + +if (sp < current->thread.lowest_stack && + sp >= (unsigned long )task_stack_page (current) + +sizeof (unsigned long )) { + current->thread.lowest_stack = sp; + } +} +EXPORT_SYMBOL (track_stack); +#endif /* CONFIG_GCC_PLUGIN_STACKLEAK */
scripts/Makefile.gcc-plugins +3 -0
@@ -35,6 +35,9 @@ ifdef CONFIG_GCC_PLUGINS gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_RANDSTRUCT) += -DRANDSTRUCT_PLUGIN gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_RANDSTRUCT_PERFORMANCE) += -fplugin-arg-randomize_layout_plugin-performance-mode + gcc-plugin-$(CONFIG_GCC_PLUGIN_STACKLEAK) += stackleak_plugin.so + gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_STACKLEAK) += -DSTACKLEAK_PLUGIN -fplugin-arg-stackleak_plugin-track-min-size=$(CONFIG_STACKLEAK_TRACK_MIN_SIZE) + GCC_PLUGINS_CFLAGS := $(strip $(addprefix -fplugin=$(objtree)/scripts/gcc-plugins/, $(gcc-plugin-y)) $(gcc-plugin-cflags-y)) export PLUGINCC GCC_PLUGINS_CFLAGS GCC_PLUGIN GCC_PLUGIN_SUBDIR
scripts/gcc-plugins/stackleak_plugin.c new file +470 -0
470 lines not on this page · raw message
Signature · 1 line
2.7.4
PATCH v11 4/6 lkdtm: Add a test for STACKLEAK
In reply to Alexander PopovIntroduce two lkdtm tests for the STACKLEAK feature: STACKLEAK_ALLOCA and STACKLEAK_DEEP_RECURSION. Both of them check that the current task stack is properly erased (filled with STACKLEAK_POISON).
STACKLEAK_ALLOCA tests that:
- check_alloca() allows alloca calls which don't exhaust the kernel stack;
- alloca calls which exhaust/overflow the kernel stack hit BUG() in
check_alloca().
STACKLEAK_DEEP_RECURSION tests that exhausting the current task stack with a deep recursion is detected by CONFIG_VMAP_STACK (which is implied by CONFIG_GCC_PLUGIN_STACKLEAK).
--- drivers/misc/Makefile | 3 + drivers/misc/lkdtm.h | 4 ++ drivers/misc/lkdtm_core.c | 2 + drivers/misc/lkdtm_stackleak.c | 141 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 150 insertions(+) create mode 100644 drivers/misc/lkdtm_stackleak.c
drivers/misc/Makefile +3 -0
@@ -65,6 +65,9 @@ lkdtm-$(CONFIG_LKDTM) += lkdtm_perms.o lkdtm-$(CONFIG_LKDTM) += lkdtm_refcount.o lkdtm-$(CONFIG_LKDTM) += lkdtm_rodata_objcopy.o lkdtm-$(CONFIG_LKDTM) += lkdtm_usercopy.o +lkdtm-$(CONFIG_LKDTM) += lkdtm_stackleak.o + +KASAN_SANITIZE_lkdtm_stackleak.o := n KCOV_INSTRUMENT_lkdtm_rodata.o := n
drivers/misc/lkdtm.h +4 -0
@@ -83,4 +83,8 @@ void lkdtm_USERCOPY_STACK_FRAME_FROM(void);void lkdtm_USERCOPY_STACK_BEYOND (void );void lkdtm_USERCOPY_KERNEL (void ); +/* lkdtm_stackleak.c */ +void lkdtm_STACKLEAK_ALLOCA (void ); +void lkdtm_STACKLEAK_DEEP_RECURSION (void ); +#endif
drivers/misc/lkdtm_core.c +2 -0
@@ -183,6 +183,8 @@ static const struct crashtype crashtypes[] = {CRASHTYPE (USERCOPY_STACK_FRAME_FROM ),CRASHTYPE (USERCOPY_STACK_BEYOND ),CRASHTYPE (USERCOPY_KERNEL ), +CRASHTYPE (STACKLEAK_ALLOCA ), + CRASHTYPE(STACKLEAK_DEEP_RECURSION), };
drivers/misc/lkdtm_stackleak.c new file +141 -0
141 lines not on this page · raw message
Signature · 1 line
2.7.4
PATCH v11 5/6 fs/proc: Show STACKLEAK metrics in the /proc file system
In reply to Alexander PopovIntroduce CONFIG_STACKLEAK_METRICS providing STACKLEAK information about tasks via the /proc file system. In particular, /proc/<pid>/stack_depth shows the maximum kernel stack consumption for the current and previous syscalls. Although this information is not precise, it can be useful for estimating the STACKLEAK performance impact for your workloads.
--- arch/Kconfig | 12 ++++++++++++ arch/x86/entry/erase.c | 4 ++++ arch/x86/include/asm/processor.h | 3 +++ arch/x86/kernel/process_32.c | 3 +++ arch/x86/kernel/process_64.c | 3 +++ fs/proc/base.c | 18 ++++++++++++++++++ 6 files changed, 43 insertions(+)
arch/Kconfig +12 -0
@@ -572,6 +572,18 @@ config STACKLEAK_TRACK_MIN_SIZE frame size greater than or equal to this parameter. If unsure, leave the default value 100. +config STACKLEAK_METRICS + bool "Show STACKLEAK metrics in the /proc file system" + depends on GCC_PLUGIN_STACKLEAK + depends on PROC_FS + help + If this is set, STACKLEAK metrics for every task are available in + the /proc file system. In particular, /proc/<pid>/stack_depth + shows the maximum kernel stack consumption for the current and + previous syscalls. Although this information is not precise, it + can be useful for estimating the STACKLEAK performance impact for + your workloads. + config HAVE_CC_STACKPROTECTOR bool help
arch/x86/entry/erase.c +4 -0
@@ -32,6 +32,10 @@ asmlinkage void erase_kstack(void)if (p == boundary) p +=sizeof (unsigned long ); +#ifdef CONFIG_STACKLEAK_METRICS + current->thread.prev_lowest_stack = p; +#endif +/* * So let's write the poison value to the kernel stack. * Start from the address in p and move up till the new boundary.
arch/x86/include/asm/processor.h +3 -0
@@ -496,6 +496,9 @@ struct thread_struct {#ifdef CONFIG_GCC_PLUGIN_STACKLEAK unsigned long lowest_stack; +# ifdef CONFIG_STACKLEAK_METRICS +unsigned long prev_lowest_stack; +# endif #endif unsigned int sig_on_uaccess_err:1;
arch/x86/kernel/process_32.c +3 -0
@@ -139,6 +139,9 @@ int copy_thread_tls(unsigned long clone_flags, unsigned long sp,#ifdef CONFIG_GCC_PLUGIN_STACKLEAK p->thread.lowest_stack = (unsigned long )task_stack_page (p) +sizeof (unsigned long ); +# ifdef CONFIG_STACKLEAK_METRICS + p->thread.prev_lowest_stack = p->thread.lowest_stack; +# endif #endif if (unlikely (p->flags &PF_KTHREAD )) {
arch/x86/kernel/process_64.c +3 -0
@@ -284,6 +284,9 @@ int copy_thread_tls(unsigned long clone_flags, unsigned long sp,#ifdef CONFIG_GCC_PLUGIN_STACKLEAK p->thread.lowest_stack = (unsigned long )task_stack_page (p) +sizeof (unsigned long ); +# ifdef CONFIG_STACKLEAK_METRICS + p->thread.prev_lowest_stack = p->thread.lowest_stack; +# endif #endif savesegment (gs, p->thread.gsindex);
fs/proc/base.c +18 -0
@@ -2914,6 +2914,21 @@ static int proc_pid_patch_state(struct seq_file *m, struct pid_namespace *ns, }#endif /* CONFIG_LIVEPATCH */ +#ifdef CONFIG_STACKLEAK_METRICS +static int proc_stack_depth (struct seq_file *m,struct pid_namespace *ns, +struct pid *pid,struct task_struct *task) +{ +unsigned long prev_depth =THREAD_SIZE - + (task->thread.prev_lowest_stack & (THREAD_SIZE -1 )); +unsigned long depth =THREAD_SIZE - + (task->thread.lowest_stack & (THREAD_SIZE -1 )); + +seq_printf (m,"previous stack depth: %lu\nstack depth: %lu\n" , + prev_depth, depth); +return 0 ; +} +#endif /* CONFIG_STACKLEAK_METRICS */ +/* * Thread groups */
@@ -3018,6 +3033,9 @@ static const struct pid_entry tgid_base_stuff[] = {#ifdef CONFIG_LIVEPATCH ONE("patch_state", S_IRUSR, proc_pid_patch_state),#endif +#ifdef CONFIG_STACKLEAK_METRICS + ONE("stack_depth", S_IRUGO, proc_stack_depth), +#endif };static int proc_tgid_base_readdir (struct file *file,struct dir_context *ctx)
Signature · 1 line
2.7.4
PATCH v11 6/6 doc: self-protection: Add information about STACKLEAK feature
In reply to Alexander PopovAdd information about STACKLEAK feature to "Stack depth overflow" and "Memory poisoning" sections of self-protection.rst.
--- Documentation/security/self-protection.rst | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-)
Documentation/security/self-protection.rst +14 -9
@@ -165,10 +165,15 @@ Stack depth overflow A less well understood attack is using a bug that triggers the kernel to consume stack memory with deep function calls or large stack allocations. With this attack it is possible to write beyond the end of -the kernel's preallocated stack space and into sensitive structures. Two -important changes need to be made for better protections: moving the -sensitive thread_info structure elsewhere, and adding a faulting memory -hole at the bottom of the stack to catch these overflows. +the kernel's preallocated stack space and into sensitive structures. +The combination of the following measures gives better protection: + +* moving the sensitive thread_info structure off the stack + (``CONFIG_THREAD_INFO_IN_TASK``); +* adding a faulting memory hole at the bottom of the stack to catch + these overflows (``CONFIG_VMAP_STACK``); +* runtime checking that alloca() calls don't overstep the stack boundary + (``CONFIG_GCC_PLUGIN_STACKLEAK``). Heap memory integrity ---------------------
@@ -302,11 +307,11 @@ sure structure holes are cleared. Memory poisoning ---------------- -When releasing memory, it is best to poison the contents (clear stack on -syscall return, wipe heap memory on a free), to avoid reuse attacks that -rely on the old contents of memory. This frustrates many uninitialized -variable attacks, stack content exposures, heap content exposures, and -use-after-free attacks. +When releasing memory, it is best to poison the contents, to avoid reuse +attacks that rely on the old contents of memory. E.g., clear stack on a +syscall return (``CONFIG_GCC_PLUGIN_STACKLEAK``), wipe heap memory on a +free. This frustrates many uninitialized variable attacks, stack content +exposures, heap content exposures, and use-after-free attacks. Destination tracking --------------------
Signature · 1 line
2.7.4
cover Stackleak for arm64
In reply to Alexander PopovThis is an extension of the arm64 stackleak plugin, based on top of the v11 version for x86. The biggest change from the previous version is the conversion from assembly to C. It's mostly taken straight from the x86 version modulo a few cleanups and seems to work just fine. If this gets Acks, I'd like Kees to start carrying in his tree for -next.
Laura Abbott (2):
stackleak: Update for arm64
arm64: Clear the stack
arch/arm64/Kconfig | 1 + arch/arm64/include/asm/processor.h | 6 ++++ arch/arm64/kernel/Makefile | 3 ++ arch/arm64/kernel/entry.S | 6 ++++ arch/arm64/kernel/erase.c | 55 ++++++++++++++++++++++++++++++++++ arch/arm64/kernel/process.c | 16 ++++++++++ drivers/firmware/efi/libstub/Makefile | 3 +- scripts/Makefile.gcc-plugins | 5 +++- scripts/gcc-plugins/stackleak_plugin.c | 4 +++ 9 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 arch/arm64/kernel/erase.c
Signature · 1 line
2.14.3
PATCH 1/2 stackleak: Update for arm64
arm64 has another layer of indirection in the RTL. Account for this in the plugin.
---
Fixed from previous version to be a vector expression.
--- scripts/gcc-plugins/stackleak_plugin.c | 4 ++++ 1 file changed, 4 insertions(+)
scripts/gcc-plugins/stackleak_plugin.c +4 -0
@@ -253,6 +253,10 @@ static unsigned int stackleak_cleanup_execute(void)* that insn. */ body =PATTERN (insn); +/* arm64 is different */ +if (GET_CODE (body) ==PARALLEL ) + body =XVECEXP (body,0 ,0 ); +if (GET_CODE (body) !=CALL )continue ;
Signature · 1 line
2.14.3
PATCH 2/2 arm64: Clear the stack
In reply to Laura AbbottImplementation of stackleak based heavily on the x86 version
---
Now written in C instead of a bunch of assembly.
--- arch/arm64/Kconfig | 1 + arch/arm64/include/asm/processor.h | 6 ++++ arch/arm64/kernel/Makefile | 3 ++ arch/arm64/kernel/entry.S | 6 ++++ arch/arm64/kernel/erase.c | 55 +++++++++++++++++++++++++++++++++++ arch/arm64/kernel/process.c | 16 ++++++++++ drivers/firmware/efi/libstub/Makefile | 3 +- scripts/Makefile.gcc-plugins | 5 +++- 8 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 arch/arm64/kernel/erase.c
arch/arm64/Kconfig +1 -0
@@ -92,6 +92,7 @@ config ARM64 select HAVE_ARCH_MMAP_RND_BITS select HAVE_ARCH_MMAP_RND_COMPAT_BITS if COMPAT select HAVE_ARCH_SECCOMP_FILTER + select HAVE_ARCH_STACKLEAK select HAVE_ARCH_THREAD_STRUCT_WHITELIST select HAVE_ARCH_TRACEHOOK select HAVE_ARCH_TRANSPARENT_HUGEPAGE
arch/arm64/include/asm/processor.h +6 -0
@@ -124,6 +124,12 @@ struct thread_struct {unsigned long fault_address;/* fault info */ unsigned long fault_code;/* ESR_EL1 value */ struct debug_info debug;/* debugging */ +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK +unsigned long lowest_stack; +#ifdef CONFIG_STACKLEAK_METRICS +unsigned long prev_lowest_stack; +#endif +#endif }; static inline void arch_thread_struct_whitelist(unsigned long *offset,
arch/arm64/kernel/Makefile +3 -0
@@ -55,6 +55,9 @@ arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o arm64-obj-$(CONFIG_CRASH_DUMP) += crash_dump.o arm64-obj-$(CONFIG_ARM_SDE_INTERFACE) += sdei.o +arm64-obj-$(CONFIG_GCC_PLUGIN_STACKLEAK) += erase.o +KASAN_SANITIZE_erase.o := n + obj-y += $(arm64-obj-y) vdso/ probes/ obj-m += $(arm64-obj-m) head-y := head.o
arch/arm64/kernel/entry.S +6 -0
@@ -401,6 +401,11 @@ tsk .req x28 // current thread_info .text + .macro ERASE_KSTACK +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK + bl erase_kstack +#endif + .endm /* * Exception vectors. */
@@ -906,6 +911,7 @@ ret_to_user: cbnz x2, work_pending finish_ret_to_user: enable_step_tsk x1, x2 + ERASE_KSTACK kernel_exit 0 ENDPROC(ret_to_user)
arch/arm64/kernel/erase.c new file +55 -0
55 lines not on this page · raw message
arch/arm64/kernel/process.c +16 -0
@@ -364,6 +364,9 @@ int copy_thread(unsigned long clone_flags, unsigned long stack_start, p->thread.cpu_context.pc = (unsigned long )ret_from_fork; p->thread.cpu_context.sp = (unsigned long )childregs; +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK + p->thread.lowest_stack = (unsigned long )task_stack_page (p); +#endif ptrace_hw_copy_thread (p);return 0 ;
@@ -493,3 +496,16 @@ void arch_setup_new_exec(void) { current->mm->context.flags =is_compat_task () ?MMCF_AARCH32 :0 ; } + +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK +void __used check_alloca (unsigned long size) +{ +unsigned long sp, stack_left; + + sp = current_stack_pointer; + + stack_left = sp & (THREAD_SIZE -1 ); +BUG_ON (stack_left <256 || size >= stack_left -256 ); +} +EXPORT_SYMBOL (check_alloca); +#endif
drivers/firmware/efi/libstub/Makefile +2 -1
@@ -20,7 +20,8 @@ cflags-$(CONFIG_EFI_ARMSTUB) += -I$(srctree)/scripts/dtc/libfdt KBUILD_CFLAGS := $(cflags-y) -DDISABLE_BRANCH_PROFILING \ -D__NO_FORTIFY \ $(call cc-option,-ffreestanding) \ - $(call cc-option,-fno-stack-protector) + $(call cc-option,-fno-stack-protector) \ + $(DISABLE_STACKLEAK_PLUGIN) GCOV_PROFILE := n KASAN_SANITIZE := n
scripts/Makefile.gcc-plugins +4 -1
@@ -37,11 +37,14 @@ ifdef CONFIG_GCC_PLUGINS gcc-plugin-$(CONFIG_GCC_PLUGIN_STACKLEAK) += stackleak_plugin.so gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_STACKLEAK) += -DSTACKLEAK_PLUGIN -fplugin-arg-stackleak_plugin-track-min-size=$(CONFIG_STACKLEAK_TRACK_MIN_SIZE) + ifdef CONFIG_GCC_PLUGIN_STACKLEAK + DISABLE_STACKLEAK_PLUGIN += -fplugin-arg-stackleak_plugin-disable + endif GCC_PLUGINS_CFLAGS := $(strip $(addprefix -fplugin=$(objtree)/scripts/gcc-plugins/, $(gcc-plugin-y)) $(gcc-plugin-cflags-y)) export PLUGINCC GCC_PLUGINS_CFLAGS GCC_PLUGIN GCC_PLUGIN_SUBDIR - export SANCOV_PLUGIN DISABLE_LATENT_ENTROPY_PLUGIN + export SANCOV_PLUGIN DISABLE_LATENT_ENTROPY_PLUGIN DISABLE_STACKLEAK_PLUGIN ifneq ($(PLUGINCC),) # SANCOV_PLUGIN can be only in CFLAGS_KCOV because avoid duplication.
Signature · 1 line
2.14.3
Re: [PATCH 2/2] arm64: Clear the stack
Quoted 2 lines · Laura Abbott
Implementation of stackleak based heavily on the x86 version
Awesome! Notes below for both you and Alexander, since I think we can create a common code base instead of having near-duplicates in the arch/ trees...
Quoted 33 lines
Signed-off-by Laura Abbott <[email protected]>---
Now written in C instead of a bunch of assembly.--- arch/arm64/Kconfig | 1 + arch/arm64/include/asm/processor.h | 6 ++++ arch/arm64/kernel/Makefile | 3 ++ arch/arm64/kernel/entry.S | 6 ++++ arch/arm64/kernel/erase.c | 55 +++++++++++++++++++++++++++++++++++ arch/arm64/kernel/process.c | 16 ++++++++++ drivers/firmware/efi/libstub/Makefile | 3 +- scripts/Makefile.gcc-plugins | 5 +++- 8 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 arch/arm64/kernel/erase.carch/arm64/Kconfig +1
-0@@ -92,6 +92,7 @@ config ARM64 select HAVE_ARCH_MMAP_RND_BITS select HAVE_ARCH_MMAP_RND_COMPAT_BITS if COMPAT select HAVE_ARCH_SECCOMP_FILTER + select HAVE_ARCH_STACKLEAK select HAVE_ARCH_THREAD_STRUCT_WHITELIST select HAVE_ARCH_TRACEHOOK select HAVE_ARCH_TRANSPARENT_HUGEPAGEarch/arm64/include/asm/processor.h +0
-0
arch/arm64/include/asm/processor.h struct thread_struct { · line 132
Quoted 9 lines
arch/arm64/include/asm/processor.h +6
-0@@ -124,6 +124,12 @@ struct thread_struct {unsigned long fault_address;/* fault info */ unsigned long fault_code;/* ESR_EL1 value */ struct debug_info debug;/* debugging */ +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK +unsigned long lowest_stack; +#ifdef CONFIG_STACKLEAK_METRICS +unsigned long prev_lowest_stack; +#endif +#endif
I wonder if x86 and arm64 could include a common struct here that was empty when the plugin is disabled... it would keep the ifdefs in one place. Maybe include/linux/stackleak.h could be:
---start---
/* Poison value points to the unused hole in the virtual memory map */ #define STACKLEAK_POISON -0xBEEF
#define STACKLEAK_POISON_CHECK_DEPTH 128
struct stackleak {
#ifdef CONFIG_GCC_PLUGIN_STACKLEAK
unsigned long lowest;
#ifdef CONFIG_STACKLEAK_METRICS
unsigned long prev_lowest;#endif
#endif
};
asmlinkage void erase_kstack(void);
---eof---
and arch/*/include/asm/processor.h could do:
@@ -124,6 +124,12 @@ struct thread_struct {
unsigned long fault_address; /* fault info */
unsigned long fault_code; /* ESR_EL1 value */
struct debug_info debug; /* debugging */+ struct stackleak stackleak;
and arch/x86/entry/erase.c could move to maybe kernel/stackleak.c? (Oh, I notice this needs an SPDX line too.)
Quoted 32 lines
arch/arm64/include/asm/processor.h +0
-0static inline void arch_thread_struct_whitelist(unsigned long *offset,arch/arm64/kernel/Makefile +3
-0@@ -55,6 +55,9 @@ arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o arm64-obj-$(CONFIG_CRASH_DUMP) += crash_dump.o arm64-obj-$(CONFIG_ARM_SDE_INTERFACE) += sdei.o +arm64-obj-$(CONFIG_GCC_PLUGIN_STACKLEAK) += erase.o +KASAN_SANITIZE_erase.o := n + obj-y += $(arm64-obj-y) vdso/ probes/ obj-m += $(arm64-obj-m) head-y := head.oarch/arm64/kernel/entry.S +5
-0@@ -401,6 +401,11 @@ tsk .req x28 // current thread_info .text + .macro ERASE_KSTACK +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK + bl erase_kstack +#endif + .endm /* * Exception vectors. */
arch/arm64/kernel/entry.S ret_to_user: · line 916
Quoted 6 lines
arch/arm64/kernel/entry.S +1
-0@@ -906,6 +911,7 @@ ret_to_user: cbnz x2, work_pending finish_ret_to_user: enable_step_tsk x1, x2 + ERASE_KSTACK kernel_exit 0 ENDPROC(ret_to_user)
Nice. All of the return paths end up here (I went looking for ret_from_fork's path). :)
Quoted 40 lines
arch/arm64/kernel/erase.c new file +33
-0@@ -0,0 +1,55 @@ +#include <linux/bug.h> +#include <linux/sched.h> +#include <asm/current.h> +#include <asm/linkage.h> +#include <asm/processor.h> + +asmlinkage void erase_kstack(void) +{ +unsigned long p = current->thread.lowest_stack; +unsigned long boundary = p & ~(THREAD_SIZE -1 ); +unsigned long poison =0 ; +const unsigned long check_depth =STACKLEAK_POISON_CHECK_DEPTH / +sizeof (unsigned long ); + +/* +* Let's search for the poison value in the stack. +* Start from the lowest_stack and go to the bottom. +*/ +while (p > boundary && poison <= check_depth) { +if (*(unsigned long *)p ==STACKLEAK_POISON ) + poison++; +else + poison =0 ; + + p -=sizeof (unsigned long ); + } + +/* +* One long int at the bottom of the thread stack is reserved and +* should not be poisoned (see CONFIG_SCHED_STACK_END_CHECK). +*/ +if (p == boundary) + p +=sizeof (unsigned long );
arch/arm64/kernel/erase.c line 43
Quoted 10 lines
arch/arm64/kernel/erase.c new file +10
-0+ +#ifdef CONFIG_STACKLEAK_METRICS + current->thread.prev_lowest_stack = p; +#endif + +/* +* So let's write the poison value to the kernel stack. +* Start from the address in p and move up till the new boundary. +*/ + boundary = current_stack_pointer;
This is the only difference between x86 and arm64 in this code. What do you think about implementing on_thread_stack() to match x86:
if (on_thread_stack())
boundary = current_stack_pointer;
else
boundary = current_top_of_stack();then we could make this common code too instead of having two copies in arch/?
Quoted 31 lines
arch/arm64/kernel/erase.c new file +11
-0+BUG_ON (boundary - p >=THREAD_SIZE ); + +while (p < boundary) { + *(unsigned long *)p =STACKLEAK_POISON ; + p +=sizeof (unsigned long ); + } + +/* Reset the lowest_stack value for the next syscall */ + current->thread.lowest_stack = current_stack_pointer; +} +arch/arm64/kernel/process.c +5
-0@@ -364,6 +364,9 @@ int copy_thread(unsigned long clone_flags, unsigned long stack_start, p->thread.cpu_context.pc = (unsigned long )ret_from_fork; p->thread.cpu_context.sp = (unsigned long )childregs; +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK + p->thread.lowest_stack = (unsigned long )task_stack_page (p); +#endif ptrace_hw_copy_thread (p);return 0 ;@@ -493,3 +496,16 @@ void arch_setup_new_exec(void) { current->mm->context.flags =is_compat_task () ?MMCF_AARCH32 :0 ; } + +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK
arch/arm64/kernel/process.c void arch_setup_new_exec(void) · line 510
Quoted 10 lines
arch/arm64/kernel/process.c +10
-0+void __used check_alloca (unsigned long size) +{ +unsigned long sp, stack_left; + + sp = current_stack_pointer; + + stack_left = sp & (THREAD_SIZE -1 ); +BUG_ON (stack_left <256 || size >= stack_left -256 ); +} +EXPORT_SYMBOL (check_alloca);
This is pretty different from x86. Is this just an artifact of ORC, or something else?
Quoted 36 lines
arch/arm64/kernel/process.c +1
-0+#endif drivers/firmware/efi/libstub/Makefile +2
-1@@ -20,7 +20,8 @@ cflags-$(CONFIG_EFI_ARMSTUB) += -I$(srctree)/scripts/dtc/libfdt KBUILD_CFLAGS := $(cflags-y) -DDISABLE_BRANCH_PROFILING \ -D__NO_FORTIFY \ $(call cc-option,-ffreestanding) \ - $(call cc-option,-fno-stack-protector) + $(call cc-option,-fno-stack-protector) \ + $(DISABLE_STACKLEAK_PLUGIN) GCOV_PROFILE := n KASAN_SANITIZE := nscripts/Makefile.gcc-plugins +4
-2@@ -37,11 +37,14 @@ ifdef CONFIG_GCC_PLUGINS gcc-plugin-$(CONFIG_GCC_PLUGIN_STACKLEAK) += stackleak_plugin.so gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_STACKLEAK) += -DSTACKLEAK_PLUGIN -fplugin-arg-stackleak_plugin-track-min-size=$(CONFIG_STACKLEAK_TRACK_MIN_SIZE) + ifdef CONFIG_GCC_PLUGIN_STACKLEAK + DISABLE_STACKLEAK_PLUGIN += -fplugin-arg-stackleak_plugin-disable + endif GCC_PLUGINS_CFLAGS := $(strip $(addprefix -fplugin=$(objtree)/scripts/gcc-plugins/, $(gcc-plugin-y)) $(gcc-plugin-cflags-y)) export PLUGINCC GCC_PLUGINS_CFLAGS GCC_PLUGIN GCC_PLUGIN_SUBDIR - export SANCOV_PLUGIN DISABLE_LATENT_ENTROPY_PLUGIN + export SANCOV_PLUGIN DISABLE_LATENT_ENTROPY_PLUGIN DISABLE_STACKLEAK_PLUGIN ifneq ($(PLUGINCC),) # SANCOV_PLUGIN can be only in CFLAGS_KCOV because avoid duplication. --
Quoted 2 lines
2.14.3
-Kees
Signature · 2 lines
Kees Cook Pixel Security
Re: [PATCH 2/2] arm64: Clear the stack
Quoted 60 lines · Kees Cook
Quoted 2 lines · Laura Abbott
Implementation of stackleak based heavily on the x86 version
Awesome! Notes below for both you and Alexander, since I think we can create a common code base instead of having near-duplicates in the
arch/trees...Quoted 33 lines
Signed-off-by Laura Abbott <[email protected]>---
Now written in C instead of a bunch of assembly.--- arch/arm64/Kconfig | 1 + arch/arm64/include/asm/processor.h | 6 ++++ arch/arm64/kernel/Makefile | 3 ++ arch/arm64/kernel/entry.S | 6 ++++ arch/arm64/kernel/erase.c | 55 +++++++++++++++++++++++++++++++++++ arch/arm64/kernel/process.c | 16 ++++++++++ drivers/firmware/efi/libstub/Makefile | 3 +- scripts/Makefile.gcc-plugins | 5 +++- 8 files changed, 93 insertions(+), 2 deletions(-)create mode 100644
arch/arm64/kernel/erase.carch/arm64/Kconfig +1
-0@@ -92,6 +92,7 @@ config ARM64 select HAVE_ARCH_MMAP_RND_BITS select HAVE_ARCH_MMAP_RND_COMPAT_BITS if COMPAT select HAVE_ARCH_SECCOMP_FILTER + select HAVE_ARCH_STACKLEAK select HAVE_ARCH_THREAD_STRUCT_WHITELIST select HAVE_ARCH_TRACEHOOK select HAVE_ARCH_TRANSPARENT_HUGEPAGEarch/arm64/include/asm/processor.h +0
-0arch/arm64/include/asm/processor.h struct thread_struct { · line 132
Quoted 9 lines
arch/arm64/include/asm/processor.h +6
-0@@ -124,6 +124,12 @@ struct thread_struct {unsigned long fault_address;/* fault info */ unsigned long fault_code;/* ESR_EL1 value */ struct debug_info debug;/* debugging */ +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK +unsigned long lowest_stack; +#ifdef CONFIG_STACKLEAK_METRICS +unsigned long prev_lowest_stack; +#endif +#endif I wonder if x86 and arm64 could include a common struct here that was empty when the plugin is disabled... it would keep the ifdefs in one place. Maybe
include/linux/stackleak.hcould be:---start---
/* Poison value points to the unused hole in the virtual memory map */ #defineSTACKLEAK_POISON-0xBEEF
#defineSTACKLEAK_POISON_CHECK_DEPTH128
Quoted 9 lines
struct stackleak {
#ifdef CONFIG_GCC_PLUGIN_STACKLEAK unsigned long lowest; #ifdef CONFIG_STACKLEAK_METRICS unsigned long prev_lowest;#endif
#endif
};
Is this well defined across all compilers if the plugin is off? This seems to compile with gcc at least but 0 sized structs make me a little uneasy.
Quoted 110 lines
asmlinkage void
erase_kstack(void);
---eof---and
arch/*/include/asm/processor.hcould do:arch/arm64/include/asm/processor.h +1
-0@@ -124,6 +124,12 @@ struct thread_struct {unsigned long fault_address;/* fault info */ unsigned long fault_code;/* ESR_EL1 value */ struct debug_info debug;/* debugging */ +struct stackleak stackleak;and
arch/x86/entry/erase.ccould move to maybekernel/stackleak.c? (Oh, I notice this needs an SPDX line too.)Quoted 32 lines
arch/arm64/include/asm/processor.h +0
-0static inline void arch_thread_struct_whitelist(unsigned long *offset,arch/arm64/kernel/Makefile +3
-0@@ -55,6 +55,9 @@ arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o arm64-obj-$(CONFIG_CRASH_DUMP) += crash_dump.o arm64-obj-$(CONFIG_ARM_SDE_INTERFACE) += sdei.o +arm64-obj-$(CONFIG_GCC_PLUGIN_STACKLEAK) += erase.o +KASAN_SANITIZE_erase.o := n + obj-y += $(arm64-obj-y) vdso/ probes/ obj-m += $(arm64-obj-m) head-y := head.oarch/arm64/kernel/entry.S +5
-0@@ -401,6 +401,11 @@ tsk .req x28 // current thread_info .text + .macro ERASE_KSTACK +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK + bl erase_kstack +#endif + .endm /* * Exception vectors. */arch/arm64/kernel/entry.S ret_to_user: · line 916
Quoted 6 lines
arch/arm64/kernel/entry.S +1
-0@@ -906,6 +911,7 @@ ret_to_user: cbnz x2, work_pending finish_ret_to_user: enable_step_tsk x1, x2 + ERASE_KSTACK kernel_exit 0 ENDPROC(ret_to_user)Nice. All of the return paths end up here (I went looking for ret_from_fork's path). :)
Quoted 40 lines
arch/arm64/kernel/erase.c new file +33
-0@@ -0,0 +1,55 @@ +#include <linux/bug.h> +#include <linux/sched.h> +#include <asm/current.h> +#include <asm/linkage.h> +#include <asm/processor.h> + +asmlinkage void erase_kstack(void) +{ +unsigned long p = current->thread.lowest_stack; +unsigned long boundary = p & ~(THREAD_SIZE -1 ); +unsigned long poison =0 ; +const unsigned long check_depth =STACKLEAK_POISON_CHECK_DEPTH / +sizeof (unsigned long ); + +/* +* Let's search for the poison value in the stack. +* Start from the lowest_stack and go to the bottom. +*/ +while (p > boundary && poison <= check_depth) { +if (*(unsigned long *)p ==STACKLEAK_POISON ) + poison++; +else + poison =0 ; + + p -=sizeof (unsigned long ); + } + +/* +* One long int at the bottom of the thread stack is reserved and +* should not be poisoned (see CONFIG_SCHED_STACK_END_CHECK). +*/ +if (p == boundary) + p +=sizeof (unsigned long );arch/arm64/kernel/erase.c line 43
Quoted 10 lines
arch/arm64/kernel/erase.c new file +10
-0+ +#ifdef CONFIG_STACKLEAK_METRICS + current->thread.prev_lowest_stack = p; +#endif + +/* +* So let's write the poison value to the kernel stack. +* Start from the address in p and move up till the new boundary. +*/ + boundary = current_stack_pointer;This is the only difference between x86 and arm64 in this code. What do you think about implementing
on_thread_stack()to match x86:
Quoted 7 lines
if (on_thread_stack()) boundary = current_stack_pointer; else boundary = current_top_of_stack();then we could make this common code too instead of having two copies in
arch/?
The issue isn't on_thread_stack, it's current_top_of_stack which isn't defined on arm64. I agree it would be good if the code would be common but I'm not sure how much we want to start trying to force APIs.
Quoted 36 lines
Quoted 36 lines
arch/arm64/kernel/erase.c new file +11
-0+BUG_ON (boundary - p >=THREAD_SIZE ); + +while (p < boundary) { + *(unsigned long *)p =STACKLEAK_POISON ; + p +=sizeof (unsigned long ); + } + +/* Reset the lowest_stack value for the next syscall */ + current->thread.lowest_stack = current_stack_pointer; +} +arch/arm64/kernel/process.c +10
-0@@ -364,6 +364,9 @@ int copy_thread(unsigned long clone_flags, unsigned long stack_start, p->thread.cpu_context.pc = (unsigned long )ret_from_fork; p->thread.cpu_context.sp = (unsigned long )childregs; +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK + p->thread.lowest_stack = (unsigned long )task_stack_page (p); +#endif ptrace_hw_copy_thread (p);return 0 ;@@ -493,3 +496,16 @@ void arch_setup_new_exec(void) { current->mm->context.flags =is_compat_task () ?MMCF_AARCH32 :0 ; } + +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK +void __used check_alloca (unsigned long size) +{ +unsigned long sp, stack_left; + + sp = current_stack_pointer;
Quoted 9 lines
arch/arm64/kernel/process.c void arch_setup_new_exec(void) · line 510
Quoted 5 lines
arch/arm64/kernel/process.c +5
-0+ + stack_left = sp & (THREAD_SIZE -1 ); +BUG_ON (stack_left <256 || size >= stack_left -256 ); +} +EXPORT_SYMBOL (check_alloca);This is pretty different from x86. Is this just an artifact of ORC, or something else?
This was based on the earlier version of x86. I'll confess to not seeing how the current x86 version ended up with get_stack_info but I suspect it's either related to ORC unwinding or it's best practice.
Quoted 33 lines
Quoted 33 lines
arch/arm64/kernel/process.c +1
-0+#endif drivers/firmware/efi/libstub/Makefile +2
-1@@ -20,7 +20,8 @@ cflags-$(CONFIG_EFI_ARMSTUB) += -I$(srctree)/scripts/dtc/libfdt KBUILD_CFLAGS := $(cflags-y) -DDISABLE_BRANCH_PROFILING \ -D__NO_FORTIFY \ $(call cc-option,-ffreestanding) \ - $(call cc-option,-fno-stack-protector) + $(call cc-option,-fno-stack-protector) \ + $(DISABLE_STACKLEAK_PLUGIN) GCOV_PROFILE := n KASAN_SANITIZE := nscripts/Makefile.gcc-plugins +4
-1@@ -37,11 +37,14 @@ ifdef CONFIG_GCC_PLUGINS gcc-plugin-$(CONFIG_GCC_PLUGIN_STACKLEAK) += stackleak_plugin.so gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_STACKLEAK) += -DSTACKLEAK_PLUGIN -fplugin-arg-stackleak_plugin-track-min-size=$(CONFIG_STACKLEAK_TRACK_MIN_SIZE) + ifdef CONFIG_GCC_PLUGIN_STACKLEAK + DISABLE_STACKLEAK_PLUGIN += -fplugin-arg-stackleak_plugin-disable + endif GCC_PLUGINS_CFLAGS := $(strip $(addprefix -fplugin=$(objtree)/scripts/gcc-plugins/, $(gcc-plugin-y)) $(gcc-plugin-cflags-y)) export PLUGINCC GCC_PLUGINS_CFLAGS GCC_PLUGIN GCC_PLUGIN_SUBDIR - export SANCOV_PLUGIN DISABLE_LATENT_ENTROPY_PLUGIN + export SANCOV_PLUGIN DISABLE_LATENT_ENTROPY_PLUGIN DISABLE_STACKLEAK_PLUGIN
Quoted 8 lines
Quoted 5 lines
scripts/Makefile.gcc-plugins +0
-1ifneq ($(PLUGINCC),) # SANCOV_PLUGIN can be only in CFLAGS_KCOV because avoid duplication. --2.14.3
-Kees
Thanks,
Laura
Re: [PATCH 2/2] arm64: Clear the stack
Quoted 11 lines · Laura Abbott
Quoted 9 lines · Kees Cook
struct stackleak {
#ifdef CONFIG_GCC_PLUGIN_STACKLEAK unsigned long lowest; #ifdef CONFIG_STACKLEAK_METRICS unsigned long prev_lowest;#endif
#endif
};
Quoted 3 lines
Is this well defined across all compilers if the plugin is off? This seems to compile with gcc at least but 0 sized structs make me a little uneasy.
Yup! Or at least, there have been no problems with this and the seccomp struct, which is empty when !CONFIG_SECCOMP.
Quoted 8 lines
Quoted 8 lines
This is the only difference between x86 and arm64 in this code. What do you think about implementing
on_thread_stack()to match x86:if (on_thread_stack()) boundary = current_stack_pointer; else boundary = current_top_of_stack();
Quoted 7 lines
Quoted 3 lines
then we could make this common code too instead of having two copies in
arch/?The issue isn't
on_thread_stack, it'scurrent_top_of_stackwhich isn't defined on arm64. I agree it would be good if the code would be common but I'm not sure how much we want to start trying to force APIs.
Ah, gotcha. Well, I'd rather we had an #ifdef here that two copies of the code. ;)
Quoted 17 lines
Quoted 7 lines
Quoted 7 lines
arch/arm64/kernel/process.c +7
-0+#ifdef CONFIG_GCC_PLUGIN_STACKLEAK +void __used check_alloca (unsigned long size) +{ +unsigned long sp, stack_left; + + sp = current_stack_pointer; +Quoted 9 lines
arch/arm64/kernel/process.c void arch_setup_new_exec(void) · line 510
Quoted 4 lines
arch/arm64/kernel/process.c +4
-0+ stack_left = sp & (THREAD_SIZE -1 ); +BUG_ON (stack_left <256 || size >= stack_left -256 ); +} +EXPORT_SYMBOL (check_alloca);This is pretty different from x86. Is this just an artifact of ORC, or something else?
Quoted 4 lines
This was based on the earlier version of x86. I'll confess to not seeing how the current x86 version ended up with
get_stack_infobut I suspect it's either related to ORC unwinding or it's best practice.
Alexander, what was the history here?
-Kees
Signature · 2 lines
Kees Cook Pixel Security
Re: [PATCH 2/2] arm64: Clear the stack
In reply to Laura AbbottHello Laura and Kees,
Quoted 8 lines · Laura Abbott
Quoted 7 lines · Kees Cook
Quoted 2 lines · Laura Abbott
Implementation of stackleak based heavily on the x86 version
Awesome! Notes below for both you and Alexander, since I think we can create a common code base instead of having near-duplicates in the
arch/trees...
Yes, sure.
I will extract the common part and send v12 for x86. Then Laura will be able to add arm64 support in a separate patch series. Is it fine?
Quoted 61 lines
Quoted 51 lines
Quoted 32 lines
Signed-off-by Laura Abbott <[email protected]>---
Now written in C instead of a bunch of assembly.--- arch/arm64/Kconfig | 1 + arch/arm64/include/asm/processor.h | 6 ++++ arch/arm64/kernel/Makefile | 3 ++ arch/arm64/kernel/entry.S | 6 ++++ arch/arm64/kernel/erase.c | 55 +++++++++++++++++++++++++++++++++++ arch/arm64/kernel/process.c | 16 ++++++++++ drivers/firmware/efi/libstub/Makefile | 3 +- scripts/Makefile.gcc-plugins | 5 +++- 8 files changed, 93 insertions(+), 2 deletions(-)create mode 100644
arch/arm64/kernel/erase.carch/arm64/Kconfig +1
-0@@ -92,6 +92,7 @@ config ARM64 select HAVE_ARCH_MMAP_RND_BITS select HAVE_ARCH_MMAP_RND_COMPAT_BITS if COMPAT select HAVE_ARCH_SECCOMP_FILTER + select HAVE_ARCH_STACKLEAK select HAVE_ARCH_THREAD_STRUCT_WHITELIST select HAVE_ARCH_TRACEHOOK select HAVE_ARCH_TRANSPARENT_HUGEPAGEarch/arm64/include/asm/processor.h +0
-0arch/arm64/include/asm/processor.h struct thread_struct { · line 132
Quoted 9 lines
arch/arm64/include/asm/processor.h +6
-0@@ -124,6 +124,12 @@ struct thread_struct {unsigned long fault_address;/* fault info */ unsigned long fault_code;/* ESR_EL1 value */ struct debug_info debug;/* debugging */ +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK +unsigned long lowest_stack; +#ifdef CONFIG_STACKLEAK_METRICS +unsigned long prev_lowest_stack; +#endif +#endif I wonder if x86 and arm64 could include a common struct here that was empty when the plugin is disabled... it would keep the ifdefs in one place. Maybe
include/linux/stackleak.hcould be:---start---
/* Poison value points to the unused hole in the virtual memory map */ #defineSTACKLEAK_POISON-0xBEEF
#defineSTACKLEAK_POISON_CHECK_DEPTH128Quoted 9 lines
struct stackleak {
#ifdef CONFIG_GCC_PLUGIN_STACKLEAK unsigned long lowest; #ifdef CONFIG_STACKLEAK_METRICS unsigned long prev_lowest;#endif
#endif
};
Quoted 3 lines
Is this well defined across all compilers if the plugin is off? This seems to compile with gcc at least but 0 sized structs make me a little uneasy.
Empty struct is not defined by C standard but is permitted by gcc
https://gcc.gnu.org/onlinedocs/gcc/Empty-Structures.html#Empty-Structures
Fast example:
#include <stdio.h>
int main(void)
{
struct a {};
printf("size %zu\n", sizeof(struct a));
return 0;
}# gcc -pedantic t.c -o t
t.c: In function ‘main’:
t.c:5:9: warning: struct has no members [-Wpedantic]
struct a {};
^# clang -Weverything t.c -o tc
t.c:5:2: warning: empty struct has size 0 in C, size 1 in C++ [-Wc++-compat]
struct a {};
^
t.c:5:2: warning: empty struct is a GNU extension [-Wgnu-empty-struct]
2 warnings generated.But both programs print "size 0". There are a lot of empty structs around the kernel, so I'll create another one.
Quoted 5 lines
Quoted 5 lines
asmlinkage void
erase_kstack(void);
---eof---and
arch/*/include/asm/processor.hcould do:
Quoted 8 lines
Quoted 8 lines
arch/arm64/include/asm/processor.h +1
-0@@ -124,6 +124,12 @@ struct thread_struct {unsigned long fault_address;/* fault info */ unsigned long fault_code;/* ESR_EL1 value */ struct debug_info debug;/* debugging */ +struct stackleak stackleak;and
arch/x86/entry/erase.ccould move to maybekernel/stackleak.c? (Oh, I notice this needs an SPDX line too.)
Thanks, I'll add it.
Quoted 127 lines
Quoted 96 lines
Quoted 32 lines
arch/arm64/include/asm/processor.h +0
-0static inline void arch_thread_struct_whitelist(unsigned long *offset,arch/arm64/kernel/Makefile +3
-0@@ -55,6 +55,9 @@ arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o arm64-obj-$(CONFIG_CRASH_DUMP) += crash_dump.o arm64-obj-$(CONFIG_ARM_SDE_INTERFACE) += sdei.o +arm64-obj-$(CONFIG_GCC_PLUGIN_STACKLEAK) += erase.o +KASAN_SANITIZE_erase.o := n + obj-y += $(arm64-obj-y) vdso/ probes/ obj-m += $(arm64-obj-m) head-y := head.oarch/arm64/kernel/entry.S +5
-0@@ -401,6 +401,11 @@ tsk .req x28 // current thread_info .text + .macro ERASE_KSTACK +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK + bl erase_kstack +#endif + .endm /* * Exception vectors. */arch/arm64/kernel/entry.S ret_to_user: · line 916
Quoted 6 lines
arch/arm64/kernel/entry.S +1
-0@@ -906,6 +911,7 @@ ret_to_user: cbnz x2, work_pending finish_ret_to_user: enable_step_tsk x1, x2 + ERASE_KSTACK kernel_exit 0 ENDPROC(ret_to_user)Nice. All of the return paths end up here (I went looking for ret_from_fork's path). :)
Quoted 40 lines
arch/arm64/kernel/erase.c new file +33
-0@@ -0,0 +1,55 @@ +#include <linux/bug.h> +#include <linux/sched.h> +#include <asm/current.h> +#include <asm/linkage.h> +#include <asm/processor.h> + +asmlinkage void erase_kstack(void) +{ +unsigned long p = current->thread.lowest_stack; +unsigned long boundary = p & ~(THREAD_SIZE -1 ); +unsigned long poison =0 ; +const unsigned long check_depth =STACKLEAK_POISON_CHECK_DEPTH / +sizeof (unsigned long ); + +/* +* Let's search for the poison value in the stack. +* Start from the lowest_stack and go to the bottom. +*/ +while (p > boundary && poison <= check_depth) { +if (*(unsigned long *)p ==STACKLEAK_POISON ) + poison++; +else + poison =0 ; + + p -=sizeof (unsigned long ); + } + +/* +* One long int at the bottom of the thread stack is reserved and +* should not be poisoned (see CONFIG_SCHED_STACK_END_CHECK). +*/ +if (p == boundary) + p +=sizeof (unsigned long );arch/arm64/kernel/erase.c line 43
Quoted 10 lines
arch/arm64/kernel/erase.c new file +10
-0+ +#ifdef CONFIG_STACKLEAK_METRICS + current->thread.prev_lowest_stack = p; +#endif + +/* +* So let's write the poison value to the kernel stack. +* Start from the address in p and move up till the new boundary. +*/ + boundary = current_stack_pointer;This is the only difference between x86 and arm64 in this code. What do you think about implementing
on_thread_stack()to match x86:Quoted 7 lines
if (on_thread_stack()) boundary = current_stack_pointer; else boundary = current_top_of_stack();then we could make this common code too instead of having two copies in
arch/?The issue isn't
on_thread_stack, it'scurrent_top_of_stackwhich isn't defined on arm64. I agree it would be good if the code would be common but I'm not sure how much we want to start trying to force APIs.Quoted 19 lines
Quoted 19 lines
arch/arm64/kernel/erase.c new file +11
-0+BUG_ON (boundary - p >=THREAD_SIZE ); + +while (p < boundary) { + *(unsigned long *)p =STACKLEAK_POISON ; + p +=sizeof (unsigned long ); + } + +/* Reset the lowest_stack value for the next syscall */ + current->thread.lowest_stack = current_stack_pointer; +} +arch/arm64/kernel/process.c +0
-0@@ -364,6 +364,9 @@ int copy_thread(unsigned long clone_flags, unsigned long stack_start, p->thread.cpu_context.pc = (unsigned long )ret_from_fork; p->thread.cpu_context.sp = (unsigned long )childregs;
Quoted 3 lines
Quoted 3 lines
arch/arm64/kernel/process.c int copy_thread(unsigned long clone_flags, unsigned long stack_start, · line 369
Quoted 3 lines
arch/arm64/kernel/process.c +3
-0+#ifdef CONFIG_GCC_PLUGIN_STACKLEAK + p->thread.lowest_stack = (unsigned long )task_stack_page (p); +#endif
I think it should be (unsigned long)task_stack_page(p) + sizeof(unsigned long).
Quoted 24 lines
Quoted 14 lines
Quoted 14 lines
arch/arm64/kernel/process.c +7
-0ptrace_hw_copy_thread (p);return 0 ;@@ -493,3 +496,16 @@ void arch_setup_new_exec(void) { current->mm->context.flags =is_compat_task () ?MMCF_AARCH32 :0 ; } + +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK +void __used check_alloca (unsigned long size) +{ +unsigned long sp, stack_left; + + sp = current_stack_pointer;Quoted 9 lines
arch/arm64/kernel/process.c void arch_setup_new_exec(void) · line 510
Quoted 5 lines
arch/arm64/kernel/process.c +5
-0+ + stack_left = sp & (THREAD_SIZE -1 ); +BUG_ON (stack_left <256 || size >= stack_left -256 ); +} +EXPORT_SYMBOL (check_alloca);This is pretty different from x86. Is this just an artifact of ORC, or something else?
Quoted 4 lines
This was based on the earlier version of x86. I'll confess to not seeing how the current x86 version ended up with
get_stack_infobut I suspect it's either related to ORC unwinding or it's best practice.
I've changed that in v4. Quote from the changelog:
- Fixed the surplus and erroneous code for calculating stack_left in check_alloca() on x86_64. That code repeats the work which is already done in get_stack_info() and it misses the fact that different exception stacks on x86_64 have different size.
http://www.openwall.com/lists/kernel-hardening/2017/10/04/68
We can see that in arch/x86/kernel/dumpstack_64.c.
Is it fine if check_alloca() would be arch-specific?
Quoted 33 lines
Quoted 33 lines
Quoted 33 lines
arch/arm64/kernel/process.c +1
-0+#endif drivers/firmware/efi/libstub/Makefile +2
-1@@ -20,7 +20,8 @@ cflags-$(CONFIG_EFI_ARMSTUB) += -I$(srctree)/scripts/dtc/libfdt KBUILD_CFLAGS := $(cflags-y) -DDISABLE_BRANCH_PROFILING \ -D__NO_FORTIFY \ $(call cc-option,-ffreestanding) \ - $(call cc-option,-fno-stack-protector) + $(call cc-option,-fno-stack-protector) \ + $(DISABLE_STACKLEAK_PLUGIN) GCOV_PROFILE := n KASAN_SANITIZE := nscripts/Makefile.gcc-plugins +4
-1@@ -37,11 +37,14 @@ ifdef CONFIG_GCC_PLUGINS gcc-plugin-$(CONFIG_GCC_PLUGIN_STACKLEAK) += stackleak_plugin.so gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_STACKLEAK) += -DSTACKLEAK_PLUGIN -fplugin-arg-stackleak_plugin-track-min-size=$(CONFIG_STACKLEAK_TRACK_MIN_SIZE) + ifdef CONFIG_GCC_PLUGIN_STACKLEAK + DISABLE_STACKLEAK_PLUGIN += -fplugin-arg-stackleak_plugin-disable + endif GCC_PLUGINS_CFLAGS := $(strip $(addprefix -fplugin=$(objtree)/scripts/gcc-plugins/, $(gcc-plugin-y)) $(gcc-plugin-cflags-y)) export PLUGINCC GCC_PLUGINS_CFLAGS GCC_PLUGIN GCC_PLUGIN_SUBDIR - export SANCOV_PLUGIN DISABLE_LATENT_ENTROPY_PLUGIN + export SANCOV_PLUGIN DISABLE_LATENT_ENTROPY_PLUGIN DISABLE_STACKLEAK_PLUGIN
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
scripts/Makefile.gcc-plugins +0
-1ifneq ($(PLUGINCC),) # SANCOV_PLUGIN can be only in CFLAGS_KCOV because avoid duplication. --2.14.3
Best regards,
Alexander
Re: [PATCH 2/2] arm64: Clear the stack
Quoted 8 lines · Alexander Popov
Hello Laura and Kees,
Quoted 5 lines · Laura Abbott
Quoted 4 lines · Kees Cook
Quoted 2 lines · Laura Abbott
Implementation of stackleak based heavily on the x86 version
Quoted 8 lines
Quoted 3 lines
Quoted 3 lines
Awesome! Notes below for both you and Alexander, since I think we can create a common code base instead of having near-duplicates in the
arch/trees...Yes, sure.
I will extract the common part and send v12 for x86. Then Laura will be able to add arm64 support in a separate patch series. Is it fine?
Sure, though if you could fold the plugin fix from her, that would be ideal. I'll likely carry both patch sets together once the arm64 one stabilizes.
Quoted 11 lines
Quoted 4 lines
This was based on the earlier version of x86. I'll confess to not seeing how the current x86 version ended up with
get_stack_infobut I suspect it's either related to ORC unwinding or it's best practice.I've changed that in v4. Quote from the changelog:
- Fixed the surplus and erroneous code for calculatingstack_leftincheck_alloca()onx86_64. That code repeats the work which is already done inget_stack_info()and it misses the fact that different exception stacks onx86_64have different size.
Quoted 5 lines
http://www.openwall.com/lists/kernel-hardening/2017/10/04/68
We can see that in
arch/x86/kernel/dumpstack_64.c.Is it fine if
check_alloca()would be arch-specific?
I'm fine if check_alloca() remains arch-specific.
Thanks!
-Kees
Signature · 2 lines
Kees Cook Pixel Security
Mark Rutland
reviewerRe: [PATCH 2/2] arm64: Clear the stack
In reply to Laura AbbottHi Laura,
Quoted 6 lines · Laura Abbott
Implementation of stackleak based heavily on the x86 version
Signed-off-by Laura Abbott <[email protected]>---
Now written in C instead of a bunch of assembly.
This looks neat!
I have a few minor comments below.
arch/arm64/kernel/Makefile arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o · line 59
Quoted 10 lines
arch/arm64/kernel/Makefile +2
-0@@ -55,6 +55,9 @@ arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o arm64-obj-$(CONFIG_CRASH_DUMP) += crash_dump.o arm64-obj-$(CONFIG_ARM_SDE_INTERFACE) += sdei.o +arm64-obj-$(CONFIG_GCC_PLUGIN_STACKLEAK) += erase.o +KASAN_SANITIZE_erase.o := n
I suspect we want to avoid the full set of instrumentation suspects here, e.g. GKOV, KASAN, UBSAN, and KCOV.
Quoted 9 lines
arch/arm64/kernel/Makefile +1
-0+ obj-y += $(arm64-obj-y) vdso/ probes/ obj-m += $(arm64-obj-m) head-y := head.oarch/arm64/kernel/entry.S +0
-0
arch/arm64/kernel/entry.S tsk .req x28 // current thread_info · line 408
Quoted 8 lines
arch/arm64/kernel/entry.S +5
-0@@ -401,6 +401,11 @@ tsk .req x28 // current thread_info .text + .macro ERASE_KSTACK +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK + bl erase_kstack +#endif + .endm
Nit: The rest of our asm macros are lower-case -- can we stick to that here?
arch/arm64/kernel/entry.S ret_to_user: · line 916
Quoted 10 lines
arch/arm64/kernel/entry.S +1
-0/* * Exception vectors. */@@ -906,6 +911,7 @@ ret_to_user: cbnz x2, work_pending finish_ret_to_user: enable_step_tsk x1, x2 + ERASE_KSTACK kernel_exit 0 ENDPROC(ret_to_user)
I believe we also need this in ret_fast_syscall.
[...]
Quoted 17 lines
arch/arm64/kernel/entry.S +17
-0+asmlinkage void erase_kstack(void) +{ + unsigned long p = current->thread.lowest_stack; + unsigned long boundary = p & ~(THREAD_SIZE - 1); + unsigned long poison = 0; + const unsigned long check_depth = STACKLEAK_POISON_CHECK_DEPTH / + sizeof(unsigned long); + + /* + * Let's search for the poison value in the stack. + * Start from the lowest_stack and go to the bottom. + */ + while (p > boundary && poison <= check_depth) { + if (*(unsigned long *)p == STACKLEAK_POISON) + poison++; + else + poison = 0;
arch/arm64/kernel/entry.S ret_to_user:
Quoted 10 lines
arch/arm64/kernel/entry.S +10
-0+ + p -= sizeof(unsigned long); + } + + /* + * One long int at the bottom of the thread stack is reserved and + * should not be poisoned (see CONFIG_SCHED_STACK_END_CHECK). + */ + if (p == boundary) + p += sizeof(unsigned long);
I wonder if end_of_stack() should be taught about CONFIG_SCHED_STACK_END_CHECK, given that's supposed to return the last *usable* long on the stack, and we don't account for this elsewhere.
If we did, then IIUC we could do:
unsigned long boundary = (unsigned long)end_of_stack(current);
... at the start of the function, and not have to worry about this explicitly.
arch/arm64/kernel/entry.S
Quoted 10 lines
arch/arm64/kernel/entry.S +10
-0+ +#ifdef CONFIG_STACKLEAK_METRICS + current->thread.prev_lowest_stack = p; +#endif + + /* + * So let's write the poison value to the kernel stack. + * Start from the address in p and move up till the new boundary. + */ + boundary = current_stack_pointer;
I worry a little that the compiler can move the SP during a function's lifetime, but maybe that's only the case when there are VLAs, or something like that?
arch/arm64/kernel/entry.S
Quoted 11 lines
arch/arm64/kernel/entry.S +11
-0+ + BUG_ON(boundary - p >= THREAD_SIZE); + + while (p < boundary) { + *(unsigned long *)p = STACKLEAK_POISON; + p += sizeof(unsigned long); + } + + /* Reset the lowest_stack value for the next syscall */ + current->thread.lowest_stack = current_stack_pointer; +}
Once this function returns, its data is left on the stack. Is that not a problem?
No strong feelings either way, but it might be worth mentioning in the commit message.
arch/arm64/kernel/process.c int copy_thread(unsigned long clone_flags, unsigned long stack_start, · line 368
Quoted 10 lines
arch/arm64/kernel/process.c +2
-0@@ -364,6 +364,9 @@ int copy_thread(unsigned long clone_flags, unsigned long stack_start, p->thread.cpu_context.pc = (unsigned long )ret_from_fork; p->thread.cpu_context.sp = (unsigned long )childregs; +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK + p->thread.lowest_stack = (unsigned long )task_stack_page (p);
Nit: end_of_stack(p) would be slightly better semantically, even though currently equivalent to task_stack_page(p).
[...]
arch/arm64/kernel/process.c void arch_setup_new_exec(void) · line 509
Quoted 10 lines
arch/arm64/kernel/process.c +10
-0+#ifdef CONFIG_GCC_PLUGIN_STACKLEAK +void __used check_alloca (unsigned long size) +{ +unsigned long sp, stack_left; + + sp = current_stack_pointer; + + stack_left = sp & (THREAD_SIZE -1 ); +BUG_ON (stack_left <256 || size >= stack_left -256 ); +}
Is this arbitrary, or is there something special about 256?
Even if this is arbitrary, can we give it some mnemonic?
Quoted 7 lines
arch/arm64/kernel/process.c +2
-0+EXPORT_SYMBOL (check_alloca); +#endif drivers/firmware/efi/libstub/Makefile +0
-0
drivers/firmware/efi/libstub/Makefile cflags-$(CONFIG_EFI_ARMSTUB) += -I$(srctree)/scripts/dtc/libfdt · line 27
Quoted 9 lines
drivers/firmware/efi/libstub/Makefile +2
-1@@ -20,7 +20,8 @@ cflags-$(CONFIG_EFI_ARMSTUB) += -I$(srctree)/scripts/dtc/libfdt KBUILD_CFLAGS := $(cflags-y) -DDISABLE_BRANCH_PROFILING \ -D__NO_FORTIFY \ $(call cc-option,-ffreestanding) \ - $(call cc-option,-fno-stack-protector) + $(call cc-option,-fno-stack-protector) \ + $(DISABLE_STACKLEAK_PLUGIN) GCOV_PROFILE := n KASAN_SANITIZE := n
I believe we'll also need to do this for the KVM hyp code in arch/arm64/kvm/hyp/.
Thanks,
Mark.
Re: [PATCH 2/2] arm64: Clear the stack
Quoted 101 lines · Mark Rutland
Hi Laura,
Quoted 6 lines · Laura Abbott
Implementation of stackleak based heavily on the x86 version
Signed-off-by Laura Abbott <[email protected]>---
Now written in C instead of a bunch of assembly.This looks neat!
I have a few minor comments below.
arch/arm64/kernel/Makefile arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o · line 59
Quoted 10 lines
arch/arm64/kernel/Makefile +2
-0@@ -55,6 +55,9 @@ arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o arm64-obj-$(CONFIG_CRASH_DUMP) += crash_dump.o arm64-obj-$(CONFIG_ARM_SDE_INTERFACE) += sdei.o +arm64-obj-$(CONFIG_GCC_PLUGIN_STACKLEAK) += erase.o +KASAN_SANITIZE_erase.o := nI suspect we want to avoid the full set of instrumentation suspects here, e.g. GKOV, KASAN, UBSAN, and KCOV.
Quoted 9 lines
arch/arm64/kernel/Makefile +1
-0+ obj-y += $(arm64-obj-y) vdso/ probes/ obj-m += $(arm64-obj-m) head-y := head.oarch/arm64/kernel/entry.S +0
-0arch/arm64/kernel/entry.S tsk .req x28 // current thread_info · line 408
Quoted 8 lines
arch/arm64/kernel/entry.S +5
-0@@ -401,6 +401,11 @@ tsk .req x28 // current thread_info .text + .macro ERASE_KSTACK +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK + bl erase_kstack +#endif + .endmNit: The rest of our asm macros are lower-case -- can we stick to that here?
arch/arm64/kernel/entry.S ret_to_user: · line 916
Quoted 10 lines
arch/arm64/kernel/entry.S +1
-0/* * Exception vectors. */@@ -906,6 +911,7 @@ ret_to_user: cbnz x2, work_pending finish_ret_to_user: enable_step_tsk x1, x2 + ERASE_KSTACK kernel_exit 0 ENDPROC(ret_to_user)I believe we also need this in
ret_fast_syscall.[...]
Quoted 17 lines
arch/arm64/kernel/entry.S +17
-0+asmlinkage void erase_kstack(void) +{ + unsigned long p = current->thread.lowest_stack; + unsigned long boundary = p & ~(THREAD_SIZE - 1); + unsigned long poison = 0; + const unsigned long check_depth = STACKLEAK_POISON_CHECK_DEPTH / + sizeof(unsigned long); + + /* + * Let's search for the poison value in the stack. + * Start from the lowest_stack and go to the bottom. + */ + while (p > boundary && poison <= check_depth) { + if (*(unsigned long *)p == STACKLEAK_POISON) + poison++; + else + poison = 0;arch/arm64/kernel/entry.S ret_to_user:
Quoted 10 lines
arch/arm64/kernel/entry.S +10
-0+ + p -= sizeof(unsigned long); + } + + /* + * One long int at the bottom of the thread stack is reserved and + * should not be poisoned (see CONFIG_SCHED_STACK_END_CHECK). + */ + if (p == boundary) + p += sizeof(unsigned long);I wonder if
end_of_stack()should be taught aboutCONFIG_SCHED_STACK_END_CHECK, given that's supposed to return the last *usable* long on the stack, and we don't account for this elsewhere.If we did, then IIUC we could do:
unsigned long boundary = (unsigned long)end_of_stack(current);... at the start of the function, and not have to worry about this explicitly.
Quoted 15 lines
arch/arm64/kernel/entry.S
Quoted 10 lines
arch/arm64/kernel/entry.S +10
-0+ +#ifdef CONFIG_STACKLEAK_METRICS + current->thread.prev_lowest_stack = p; +#endif + + /* + * So let's write the poison value to the kernel stack. + * Start from the address in p and move up till the new boundary. + */ + boundary = current_stack_pointer;I worry a little that the compiler can move the SP during a function's lifetime, but maybe that's only the case when there are VLAs, or something like that?
I think the AAPCS permits the compiler to allocate the stack space for outgoing variables (i.e., args 9 and beyond or other argument types that require passing via the stack) at a smaller scope than the entire function, although GCC does appear to allocate it at the beginning (based on some quick experiments)
Quoted 69 lines
arch/arm64/kernel/entry.S
Quoted 11 lines
arch/arm64/kernel/entry.S +11
-0+ + BUG_ON(boundary - p >= THREAD_SIZE); + + while (p < boundary) { + *(unsigned long *)p = STACKLEAK_POISON; + p += sizeof(unsigned long); + } + + /* Reset the lowest_stack value for the next syscall */ + current->thread.lowest_stack = current_stack_pointer; +}Once this function returns, its data is left on the stack. Is that not a problem?
No strong feelings either way, but it might be worth mentioning in the commit message.
arch/arm64/kernel/process.c int copy_thread(unsigned long clone_flags, unsigned long stack_start, · line 368
Quoted 10 lines
arch/arm64/kernel/process.c +2
-0@@ -364,6 +364,9 @@ int copy_thread(unsigned long clone_flags, unsigned long stack_start, p->thread.cpu_context.pc = (unsigned long )ret_from_fork; p->thread.cpu_context.sp = (unsigned long )childregs; +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK + p->thread.lowest_stack = (unsigned long )task_stack_page (p);Nit:
end_of_stack(p)would be slightly better semantically, even though currently equivalent totask_stack_page(p).[...]
arch/arm64/kernel/process.c void arch_setup_new_exec(void) · line 509
Quoted 10 lines
arch/arm64/kernel/process.c +10
-0+#ifdef CONFIG_GCC_PLUGIN_STACKLEAK +void __used check_alloca (unsigned long size) +{ +unsigned long sp, stack_left; + + sp = current_stack_pointer; + + stack_left = sp & (THREAD_SIZE -1 ); +BUG_ON (stack_left <256 || size >= stack_left -256 ); +}Is this arbitrary, or is there something special about 256?
Even if this is arbitrary, can we give it some mnemonic?
Quoted 7 lines
arch/arm64/kernel/process.c +2
-0+EXPORT_SYMBOL (check_alloca); +#endif drivers/firmware/efi/libstub/Makefile +0
-0drivers/firmware/efi/libstub/Makefile cflags-$(CONFIG_EFI_ARMSTUB) += -I$(srctree)/scripts/dtc/libfdt · line 27
Quoted 9 lines
drivers/firmware/efi/libstub/Makefile +2
-1@@ -20,7 +20,8 @@ cflags-$(CONFIG_EFI_ARMSTUB) += -I$(srctree)/scripts/dtc/libfdt KBUILD_CFLAGS := $(cflags-y) -DDISABLE_BRANCH_PROFILING \ -D__NO_FORTIFY \ $(call cc-option,-ffreestanding) \ - $(call cc-option,-fno-stack-protector) + $(call cc-option,-fno-stack-protector) \ + $(DISABLE_STACKLEAK_PLUGIN) GCOV_PROFILE := n KASAN_SANITIZE := nI believe we'll also need to do this for the KVM hyp code in
arch/arm64/kvm/hyp/.Thanks,
Mark.
Re: [PATCH 2/2] arm64: Clear the stack
In reply to Mark RutlandHello Mark and Laura,
Let me join the discussion. Mark, thanks for your feedback!
Quoted 22 lines · Mark Rutland
Hi Laura,
Quoted 6 lines · Laura Abbott
Implementation of stackleak based heavily on the x86 version
Signed-off-by Laura Abbott <[email protected]>---
Now written in C instead of a bunch of assembly.This looks neat!
I have a few minor comments below.
Quoted 8 lines
arch/arm64/kernel/Makefile +0
-0@@ -55,6 +55,9 @@ arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o arm64-obj-$(CONFIG_CRASH_DUMP) += crash_dump.o arm64-obj-$(CONFIG_ARM_SDE_INTERFACE) += sdei.o
Quoted 5 lines
arch/arm64/kernel/Makefile arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o · line 59
Quoted 2 lines
arch/arm64/kernel/Makefile +2
-0+arm64-obj-$(CONFIG_GCC_PLUGIN_STACKLEAK) += erase.o +KASAN_SANITIZE_erase.o := nI suspect we want to avoid the full set of instrumentation suspects here, e.g. GKOV, KASAN, UBSAN, and KCOV.
I've disabled KASAN instrumentation for that file on x86 because erase_kstack() intentionally writes to the stack and causes KASAN false positive reports.
But I didn't see any conflicts with other types of instrumentation that you mentioned.
Quoted 58 lines
Quoted 9 lines
arch/arm64/kernel/Makefile +1
-0+ obj-y += $(arm64-obj-y) vdso/ probes/ obj-m += $(arm64-obj-m) head-y := head.oarch/arm64/kernel/entry.S +0
-0arch/arm64/kernel/entry.S tsk .req x28 // current thread_info · line 408
Quoted 8 lines
arch/arm64/kernel/entry.S +5
-0@@ -401,6 +401,11 @@ tsk .req x28 // current thread_info .text + .macro ERASE_KSTACK +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK + bl erase_kstack +#endif + .endmNit: The rest of our asm macros are lower-case -- can we stick to that here?
arch/arm64/kernel/entry.S ret_to_user: · line 916
Quoted 10 lines
arch/arm64/kernel/entry.S +1
-0/* * Exception vectors. */@@ -906,6 +911,7 @@ ret_to_user: cbnz x2, work_pending finish_ret_to_user: enable_step_tsk x1, x2 + ERASE_KSTACK kernel_exit 0 ENDPROC(ret_to_user)I believe we also need this in
ret_fast_syscall.[...]
Quoted 23 lines
arch/arm64/kernel/entry.S +23
-0+asmlinkage void erase_kstack(void) +{ + unsigned long p = current->thread.lowest_stack; + unsigned long boundary = p & ~(THREAD_SIZE - 1); + unsigned long poison = 0; + const unsigned long check_depth = STACKLEAK_POISON_CHECK_DEPTH / + sizeof(unsigned long); + + /* + * Let's search for the poison value in the stack. + * Start from the lowest_stack and go to the bottom. + */ + while (p > boundary && poison <= check_depth) { + if (*(unsigned long *)p == STACKLEAK_POISON) + poison++; + else + poison = 0; + + p -= sizeof(unsigned long); + } + + /* + * One long int at the bottom of the thread stack is reserved and
Quoted 8 lines
arch/arm64/kernel/entry.S
Quoted 4 lines
arch/arm64/kernel/entry.S +4
-0+ * should not be poisoned (see CONFIG_SCHED_STACK_END_CHECK). + */ + if (p == boundary) + p += sizeof(unsigned long);I wonder if
end_of_stack()should be taught aboutCONFIG_SCHED_STACK_END_CHECK, given that's supposed to return the last *usable* long on the stack, and we don't account for this elsewhere.
I would be afraid to change the meaning of end_of_stack()... Currently it considers that magic long as usable (include/linux/sched/task_stack.h):
#define task_stack_end_corrupted(task) \ (*(end_of_stack(task)) != STACK_END_MAGIC)
Quoted 5 lines
If we did, then IIUC we could do:
unsigned long boundary = (unsigned long)end_of_stack(current);... at the start of the function, and not have to worry about this explicitly.
I should mention that erase_kstack() can be called from x86 trampoline stack. That's why the boundary is calculated from the lowest_stack.
Quoted 6 lines
Quoted 6 lines
arch/arm64/kernel/entry.S +6
-0+ +#ifdef CONFIG_STACKLEAK_METRICS + current->thread.prev_lowest_stack = p; +#endif + + /*
Quoted 8 lines
arch/arm64/kernel/entry.S
Quoted 4 lines
arch/arm64/kernel/entry.S +4
-0+ * So let's write the poison value to the kernel stack. + * Start from the address in p and move up till the new boundary. + */ + boundary = current_stack_pointer;I worry a little that the compiler can move the SP during a function's lifetime, but maybe that's only the case when there are VLAs, or something like that?
Oh, I don't know.
However, erase_kstack() doesn't call anything except simple inline functions. And as I see from its disasm on x86, the local variables reside in registers.
Quoted 10 lines
arch/arm64/kernel/entry.S
Quoted 10 lines
arch/arm64/kernel/entry.S +10
-0+ + BUG_ON(boundary - p >= THREAD_SIZE); + + while (p < boundary) { + *(unsigned long *)p = STACKLEAK_POISON; + p += sizeof(unsigned long); + } + + /* Reset the lowest_stack value for the next syscall */ + current->thread.lowest_stack = current_stack_pointer;
Laura, that might be wrong and introduce huge performance impact.
I think, lowest_stack should be reset similarly to the original version.
Quoted 6 lines
Quoted 1 line
+}
Once this function returns, its data is left on the stack. Is that not a problem?
No strong feelings either way, but it might be worth mentioning in the commit message.
I managed to bypass that with "register" specifier. Although it doesn't give an absolute guarantee.
Quoted 8 lines
Quoted 8 lines
arch/arm64/kernel/process.c +0
-0@@ -364,6 +364,9 @@ int copy_thread(unsigned long clone_flags, unsigned long stack_start, p->thread.cpu_context.pc = (unsigned long )ret_from_fork; p->thread.cpu_context.sp = (unsigned long )childregs;
Quoted 5 lines
arch/arm64/kernel/process.c int copy_thread(unsigned long clone_flags, unsigned long stack_start, · line 368
Quoted 2 lines
arch/arm64/kernel/process.c +2
-0+#ifdef CONFIG_GCC_PLUGIN_STACKLEAK + p->thread.lowest_stack = (unsigned long )task_stack_page (p);Nit:
end_of_stack(p)would be slightly better semantically, even though currently equivalent totask_stack_page(p).
Thanks, I agree, I'll fix it in v12.
Quoted 15 lines
[...]
arch/arm64/kernel/process.c void arch_setup_new_exec(void) · line 509
Quoted 10 lines
arch/arm64/kernel/process.c +10
-0+#ifdef CONFIG_GCC_PLUGIN_STACKLEAK +void __used check_alloca (unsigned long size) +{ +unsigned long sp, stack_left; + + sp = current_stack_pointer; + + stack_left = sp & (THREAD_SIZE -1 ); +BUG_ON (stack_left <256 || size >= stack_left -256 ); +}Is this arbitrary, or is there something special about 256?
Quoted 1 line
Even if this is arbitrary, can we give it some mnemonic?
It's just a reasonable number. We can introduce a macro for it.
Quoted 14 lines
Quoted 14 lines
arch/arm64/kernel/process.c +2
-0+EXPORT_SYMBOL (check_alloca); +#endif drivers/firmware/efi/libstub/Makefile +2
-1@@ -20,7 +20,8 @@ cflags-$(CONFIG_EFI_ARMSTUB) += -I$(srctree)/scripts/dtc/libfdt KBUILD_CFLAGS := $(cflags-y) -DDISABLE_BRANCH_PROFILING \ -D__NO_FORTIFY \ $(call cc-option,-ffreestanding) \ - $(call cc-option,-fno-stack-protector) + $(call cc-option,-fno-stack-protector) \ + $(DISABLE_STACKLEAK_PLUGIN)
Quoted 4 lines
drivers/firmware/efi/libstub/Makefile cflags-$(CONFIG_EFI_ARMSTUB) += -I$(srctree)/scripts/dtc/libfdt · line 27
Quoted 2 lines
drivers/firmware/efi/libstub/Makefile +0
-0GCOV_PROFILE := n KASAN_SANITIZE := nI believe we'll also need to do this for the KVM hyp code in
arch/arm64/kvm/hyp/.
Could you please give more details on that? Why STACKLEAK breaks it?
Thanks a lot!
Best regards,
Alexander
Re: [PATCH 2/2] arm64: Clear the stack
Quoted 155 lines · Alexander Popov
Hello Mark and Laura,
Let me join the discussion. Mark, thanks for your feedback!
Quoted 22 lines · Mark Rutland
Hi Laura,
Quoted 6 lines · Laura Abbott
Implementation of stackleak based heavily on the x86 version
Signed-off-by Laura Abbott <[email protected]>---
Now written in C instead of a bunch of assembly.This looks neat!
I have a few minor comments below.
Quoted 8 lines
arch/arm64/kernel/Makefile +0
-0@@ -55,6 +55,9 @@ arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o arm64-obj-$(CONFIG_CRASH_DUMP) += crash_dump.o arm64-obj-$(CONFIG_ARM_SDE_INTERFACE) += sdei.oQuoted 5 lines
arch/arm64/kernel/Makefile arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o · line 59
Quoted 2 lines
arch/arm64/kernel/Makefile +2
-0+arm64-obj-$(CONFIG_GCC_PLUGIN_STACKLEAK) += erase.o +KASAN_SANITIZE_erase.o := nI suspect we want to avoid the full set of instrumentation suspects here, e.g. GKOV, KASAN, UBSAN, and KCOV.
I've disabled KASAN instrumentation for that file on x86 because
erase_kstack()intentionally writes to the stack and causes KASAN false positive reports.But I didn't see any conflicts with other types of instrumentation that you mentioned.
Quoted 58 lines
Quoted 9 lines
arch/arm64/kernel/Makefile +1
-0+ obj-y += $(arm64-obj-y) vdso/ probes/ obj-m += $(arm64-obj-m) head-y := head.oarch/arm64/kernel/entry.S +0
-0arch/arm64/kernel/entry.S tsk .req x28 // current thread_info · line 408
Quoted 8 lines
arch/arm64/kernel/entry.S +5
-0@@ -401,6 +401,11 @@ tsk .req x28 // current thread_info .text + .macro ERASE_KSTACK +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK + bl erase_kstack +#endif + .endmNit: The rest of our asm macros are lower-case -- can we stick to that here?
arch/arm64/kernel/entry.S ret_to_user: · line 916
Quoted 10 lines
arch/arm64/kernel/entry.S +1
-0/* * Exception vectors. */@@ -906,6 +911,7 @@ ret_to_user: cbnz x2, work_pending finish_ret_to_user: enable_step_tsk x1, x2 + ERASE_KSTACK kernel_exit 0 ENDPROC(ret_to_user)I believe we also need this in
ret_fast_syscall.[...]
Quoted 23 lines
arch/arm64/kernel/entry.S +23
-0+asmlinkage void erase_kstack(void) +{ + unsigned long p = current->thread.lowest_stack; + unsigned long boundary = p & ~(THREAD_SIZE - 1); + unsigned long poison = 0; + const unsigned long check_depth = STACKLEAK_POISON_CHECK_DEPTH / + sizeof(unsigned long); + + /* + * Let's search for the poison value in the stack. + * Start from the lowest_stack and go to the bottom. + */ + while (p > boundary && poison <= check_depth) { + if (*(unsigned long *)p == STACKLEAK_POISON) + poison++; + else + poison = 0; + + p -= sizeof(unsigned long); + } + + /* + * One long int at the bottom of the thread stack is reserved andQuoted 8 lines
arch/arm64/kernel/entry.S
Quoted 4 lines
arch/arm64/kernel/entry.S +4
-0+ * should not be poisoned (see CONFIG_SCHED_STACK_END_CHECK). + */ + if (p == boundary) + p += sizeof(unsigned long);I wonder if
end_of_stack()should be taught aboutCONFIG_SCHED_STACK_END_CHECK, given that's supposed to return the last *usable* long on the stack, and we don't account for this elsewhere.I would be afraid to change the meaning of
end_of_stack()... Currently it considers that magic long as usable (include/linux/sched/task_stack.h):#define task_stack_end_corrupted(task) \ (*(end_of_stack(task)) != STACK_END_MAGIC)Quoted 5 lines
If we did, then IIUC we could do:
unsigned long boundary = (unsigned long)end_of_stack(current);... at the start of the function, and not have to worry about this explicitly.
I should mention that
erase_kstack()can be called from x86 trampoline stack. That's why the boundary is calculated from thelowest_stack.Quoted 6 lines
Quoted 6 lines
arch/arm64/kernel/entry.S +6
-0+ +#ifdef CONFIG_STACKLEAK_METRICS + current->thread.prev_lowest_stack = p; +#endif + + /*Quoted 8 lines
arch/arm64/kernel/entry.S
Quoted 4 lines
arch/arm64/kernel/entry.S +4
-0+ * So let's write the poison value to the kernel stack. + * Start from the address in p and move up till the new boundary. + */ + boundary = current_stack_pointer;I worry a little that the compiler can move the SP during a function's lifetime, but maybe that's only the case when there are VLAs, or something like that?
Oh, I don't know.
However,
erase_kstack()doesn't call anything except simple inline functions. And as I see from its disasm on x86, the local variables reside in registers.Quoted 10 lines
arch/arm64/kernel/entry.S
Quoted 10 lines
arch/arm64/kernel/entry.S +10
-0+ + BUG_ON(boundary - p >= THREAD_SIZE); + + while (p < boundary) { + *(unsigned long *)p = STACKLEAK_POISON; + p += sizeof(unsigned long); + } + + /* Reset the lowest_stack value for the next syscall */ + current->thread.lowest_stack = current_stack_pointer;Laura, that might be wrong and introduce huge performance impact.
Quoted 2 lines
I think,
lowest_stackshould be reset similarly to the original version.
Sorry, I'm not understanding here. What's the performance impact and what do you mean by original version?
Quoted 10 lines
Quoted 6 lines
Quoted 1 line
+}
Once this function returns, its data is left on the stack. Is that not a problem?
No strong feelings either way, but it might be worth mentioning in the commit message.
I managed to bypass that with "register" specifier. Although it doesn't give an absolute guarantee.
I guess I was assuming gcc would be smart enough not to spill stuff on the stack. I also intentionally removed the register keyword since it wasn't clear gcc does much with it on a modern system? I could be completely off base here though so please correct me if I'm wrong. It probably is worth documenting what we are assuming about the compiler here.
Quoted 49 lines
Quoted 8 lines
Quoted 8 lines
arch/arm64/kernel/process.c +0
-0@@ -364,6 +364,9 @@ int copy_thread(unsigned long clone_flags, unsigned long stack_start, p->thread.cpu_context.pc = (unsigned long )ret_from_fork; p->thread.cpu_context.sp = (unsigned long )childregs;Quoted 5 lines
arch/arm64/kernel/process.c int copy_thread(unsigned long clone_flags, unsigned long stack_start, · line 368
Quoted 2 lines
arch/arm64/kernel/process.c +2
-0+#ifdef CONFIG_GCC_PLUGIN_STACKLEAK + p->thread.lowest_stack = (unsigned long )task_stack_page (p);Nit:
end_of_stack(p)would be slightly better semantically, even though currently equivalent totask_stack_page(p).Thanks, I agree, I'll fix it in v12.
Quoted 15 lines
[...]
arch/arm64/kernel/process.c void arch_setup_new_exec(void) · line 509
Quoted 10 lines
arch/arm64/kernel/process.c +10
-0+#ifdef CONFIG_GCC_PLUGIN_STACKLEAK +void __used check_alloca (unsigned long size) +{ +unsigned long sp, stack_left; + + sp = current_stack_pointer; + + stack_left = sp & (THREAD_SIZE -1 ); +BUG_ON (stack_left <256 || size >= stack_left -256 ); +}Is this arbitrary, or is there something special about 256?
Quoted 1 line
Even if this is arbitrary, can we give it some mnemonic?
It's just a reasonable number. We can introduce a macro for it.
Quoted 14 lines
Quoted 14 lines
arch/arm64/kernel/process.c +2
-0+EXPORT_SYMBOL (check_alloca); +#endif drivers/firmware/efi/libstub/Makefile +2
-1@@ -20,7 +20,8 @@ cflags-$(CONFIG_EFI_ARMSTUB) += -I$(srctree)/scripts/dtc/libfdt KBUILD_CFLAGS := $(cflags-y) -DDISABLE_BRANCH_PROFILING \ -D__NO_FORTIFY \ $(call cc-option,-ffreestanding) \ - $(call cc-option,-fno-stack-protector) + $(call cc-option,-fno-stack-protector) \ + $(DISABLE_STACKLEAK_PLUGIN)
Quoted 7 lines
Quoted 4 lines
drivers/firmware/efi/libstub/Makefile cflags-$(CONFIG_EFI_ARMSTUB) += -I$(srctree)/scripts/dtc/libfdt · line 27
Quoted 2 lines
drivers/firmware/efi/libstub/Makefile +0
-0GCOV_PROFILE := n KASAN_SANITIZE := nI believe we'll also need to do this for the KVM hyp code in
arch/arm64/kvm/hyp/.Could you please give more details on that? Why STACKLEAK breaks it?
For reference, I originally added this for the efistub because it would not compile. I did compile this against my Fedora tree which has KVM enabled.
Quoted 5 lines
Thanks a lot!
Best regards,
Alexander
Thanks,
Laura
Re: [PATCH 2/2] arm64: Clear the stack
Quoted 8 lines · Laura Abbott
Quoted 7 lines · Alexander Popov
Quoted 3 lines · Mark Rutland
arch/arm64/kernel/erase.c line 53
Quoted 2 lines · Laura Abbott
arch/arm64/kernel/erase.c new file +2
-0+/* Reset the lowest_stack value for the next syscall */ + current->thread.lowest_stack = current_stack_pointer;Laura, that might be wrong and introduce huge performance impact.
Quoted 5 lines
Quoted 2 lines
I think,
lowest_stackshould be reset similarly to the original version.Sorry, I'm not understanding here. What's the performance impact and what do you mean by original version?
I meant the code for x86:
/* Reset the lowest_stack value for the next syscall */ current->thread.lowest_stack = current_top_of_stack() - 256;
...Now when I'm writing about the performance impact, I see that I was wrong about "huge". Excuse me.
Let me describe the implications of this code change.
So we are at the end of a syscall. We've just erased the used part of the kernel stack. The current stack pointer is near to the top of stack. On x86_64 I see that the stack pointer is stack top minus 56 bytes (just before switching onto the trampoline stack).
I took the idea of resetting lowest_stack to stack top minus 256 from the original PaX Team's code. It should give the speedup when lowest_stack is not updated during a syscall (a lot of functions are not instrumented) and we start to search for the poison value from that reasonable point.
If we speak about the common erase_kstack() code, this code change can break x86, because this function can be called from the trampoline stack (separate from the thread stack).
Quoted 11 lines
Quoted 10 lines
Quoted 6 lines
Quoted 1 line
+}
Once this function returns, its data is left on the stack. Is that not a problem?
No strong feelings either way, but it might be worth mentioning in the commit message.
I managed to bypass that with "register" specifier. Although it doesn't give an absolute guarantee.
Quoted 6 lines
I guess I was assuming gcc would be smart enough not to spill stuff on the stack. I also intentionally removed the register keyword since it wasn't clear gcc does much with it on a modern system? I could be completely off base here though so please correct me if I'm wrong. It probably is worth documenting what we are assuming about the compiler here.
I think having register storage class specifier here is a bit better than nothing. And yes, I'll add a comment. Right now don't see a better solution.
Quoted 17 lines
Quoted 17 lines
Quoted 16 lines
Quoted 5 lines
drivers/firmware/efi/libstub/Makefile +0
-0drivers/firmware/efi/libstub/Makefile cflags-$(CONFIG_EFI_ARMSTUB) += -I$(srctree)/scripts/dtc/libfdt · line 27
Quoted 9 lines
drivers/firmware/efi/libstub/Makefile +2
-1@@ -20,7 +20,8 @@ cflags-$(CONFIG_EFI_ARMSTUB) += -I$(srctree)/scripts/dtc/libfdt KBUILD_CFLAGS := $(cflags-y) -DDISABLE_BRANCH_PROFILING \ -D__NO_FORTIFY \ $(call cc-option,-ffreestanding) \ - $(call cc-option,-fno-stack-protector) + $(call cc-option,-fno-stack-protector) \ + $(DISABLE_STACKLEAK_PLUGIN) GCOV_PROFILE := n KASAN_SANITIZE := nI believe we'll also need to do this for the KVM hyp code in
arch/arm64/kvm/hyp/.
Quoted 5 lines
Quoted 2 lines
Could you please give more details on that? Why STACKLEAK breaks it?
For reference, I originally added this for the efistub because it would not compile.
I guess it was a linkage error, right?
Quoted 1 line
I did compile this against my Fedora tree which has KVM enabled.
Looked through this big article about ARM, KVM and HYP mode:
https://lwn.net/Articles/557132/
So we have some limited amount of kernel code which runs in HYP mode. Is it only in arch/arm64/kvm/hyp/ directory?
Mark, could you give a clue what trouble will we have if we call track_stack() or check_alloca() from that code?
Thanks in advance!
--
Alexander
Mark Rutland
reviewerRe: [PATCH 2/2] arm64: Clear the stack
In reply to Alexander PopovQuoted 33 lines · Alexander Popov
Hello Mark and Laura,
Let me join the discussion. Mark, thanks for your feedback!
Quoted 22 lines · Mark Rutland
Hi Laura,
Quoted 6 lines · Laura Abbott
Implementation of stackleak based heavily on the x86 version
Signed-off-by Laura Abbott <[email protected]>---
Now written in C instead of a bunch of assembly.This looks neat!
I have a few minor comments below.
Quoted 8 lines
arch/arm64/kernel/Makefile +0
-0@@ -55,6 +55,9 @@ arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o arm64-obj-$(CONFIG_CRASH_DUMP) += crash_dump.o arm64-obj-$(CONFIG_ARM_SDE_INTERFACE) += sdei.oQuoted 5 lines
arch/arm64/kernel/Makefile arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o · line 59
Quoted 2 lines
arch/arm64/kernel/Makefile +2
-0+arm64-obj-$(CONFIG_GCC_PLUGIN_STACKLEAK) += erase.o +KASAN_SANITIZE_erase.o := nI suspect we want to avoid the full set of instrumentation suspects here, e.g. GKOV, KASAN, UBSAN, and KCOV.
Quoted 5 lines
I've disabled KASAN instrumentation for that file on x86 because
erase_kstack()intentionally writes to the stack and causes KASAN false positive reports.But I didn't see any conflicts with other types of instrumentation that you mentioned.
The rationale is that any of these can result in implicit calls to C functions at arbitrary points during erase_kstack(). That could interfere with the search for poison, and/or leave data on the stack which is not erased.
They won't result in hard failures, as KASAN would, but we should probably avoid them regardless.
[...]
Quoted 39 lines
Quoted 23 lines
Quoted 23 lines
arch/arm64/kernel/Makefile +23
-0+asmlinkage void erase_kstack(void) +{ + unsigned long p = current->thread.lowest_stack; + unsigned long boundary = p & ~(THREAD_SIZE - 1); + unsigned long poison = 0; + const unsigned long check_depth = STACKLEAK_POISON_CHECK_DEPTH / + sizeof(unsigned long); + + /* + * Let's search for the poison value in the stack. + * Start from the lowest_stack and go to the bottom. + */ + while (p > boundary && poison <= check_depth) { + if (*(unsigned long *)p == STACKLEAK_POISON) + poison++; + else + poison = 0; + + p -= sizeof(unsigned long); + } + + /* + * One long int at the bottom of the thread stack is reserved andQuoted 8 lines
arch/arm64/kernel/Makefile
Quoted 4 lines
arch/arm64/kernel/Makefile +4
-0+ * should not be poisoned (see CONFIG_SCHED_STACK_END_CHECK). + */ + if (p == boundary) + p += sizeof(unsigned long);I wonder if
end_of_stack()should be taught aboutCONFIG_SCHED_STACK_END_CHECK, given that's supposed to return the last *usable* long on the stack, and we don't account for this elsewhere.I would be afraid to change the meaning of
end_of_stack()... Currently it considers that magic long as usable (include/linux/sched/task_stack.h):#define task_stack_end_corrupted(task) \ (*(end_of_stack(task)) != STACK_END_MAGIC)
Quoted 8 lines
Quoted 5 lines
If we did, then IIUC we could do:
unsigned long boundary = (unsigned long)end_of_stack(current);... at the start of the function, and not have to worry about this explicitly.
I should mention that
erase_kstack()can be called from x86 trampoline stack. That's why the boundary is calculated from thelowest_stack.
Ok. Under what circumstances does that happen?
It seems a little scary that curent::thread::lowest_stack might not be on current's task stack. Is that reset when transitioning to/from the trampoile stack?
[...]
Quoted 13 lines
Quoted 13 lines
arch/arm64/kernel/Makefile
Quoted 10 lines
arch/arm64/kernel/Makefile +10
-0+#ifdef CONFIG_GCC_PLUGIN_STACKLEAK +void __used check_alloca(unsigned long size) +{ + unsigned long sp, stack_left; + + sp = current_stack_pointer; + + stack_left = sp & (THREAD_SIZE - 1); + BUG_ON(stack_left < 256 || size >= stack_left - 256); +}Is this arbitrary, or is there something special about 256?
Quoted 3 lines
Quoted 1 line
Even if this is arbitrary, can we give it some mnemonic?
It's just a reasonable number. We can introduce a macro for it.
I'm just not sure I see the point in the offset, given things like VMAP_STACK exist. BUG_ON() handling will likely require *more* than 256 bytes of stack, so it seems superfluous, as we'd be relying on stack overflow detection at that point.
I can see that we should take the CONFIG_SCHED_STACK_END_CHECK offset into account, though.
Quoted 14 lines
Quoted 14 lines
Quoted 14 lines
arch/arm64/kernel/Makefile +2
-0+EXPORT_SYMBOL(check_alloca); +#endifdrivers/firmware/efi/libstub/Makefile +2
-1@@ -20,7 +20,8 @@ cflags-$(CONFIG_EFI_ARMSTUB) += -I$(srctree)/scripts/dtc/libfdt KBUILD_CFLAGS := $(cflags-y) -DDISABLE_BRANCH_PROFILING \ -D__NO_FORTIFY \ $(call cc-option,-ffreestanding) \ - $(call cc-option,-fno-stack-protector) + $(call cc-option,-fno-stack-protector) \ + $(DISABLE_STACKLEAK_PLUGIN)
Quoted 6 lines
Quoted 4 lines
drivers/firmware/efi/libstub/Makefile cflags-$(CONFIG_EFI_ARMSTUB) += -I$(srctree)/scripts/dtc/libfdt · line 27
Quoted 2 lines
drivers/firmware/efi/libstub/Makefile +0
-0GCOV_PROFILE := n KASAN_SANITIZE := nI believe we'll also need to do this for the KVM hyp code in
arch/arm64/kvm/hyp/.Could you please give more details on that? Why STACKLEAK breaks it?
In the hyp/EL2 exception level, we only map the hyp text, and not the rest of the kernel. So erase_kstack and check_alloca won't be mapped, and attempt to branch to them will fault.
Even if it were mapped, things like BUG_ON(), get_current(), etc do not work at hyp.
Additionally, the hyp code is mapped as a different virtual address from the rest of the kernel, so if any of the STACKLEAK code happens to use an absolute address, this will not work correctly.
Thanks,
Mark.
PATCH 2/2 arm64: Clear the stack
Quoted 40 lines · Mark Rutland
Quoted 33 lines · Alexander Popov
Hello Mark and Laura,
Let me join the discussion. Mark, thanks for your feedback!
Quoted 22 lines · Mark Rutland
Hi Laura,
Quoted 6 lines · Laura Abbott
Implementation of stackleak based heavily on the x86 version
Signed-off-by Laura Abbott <[email protected]>---
Now written in C instead of a bunch of assembly.This looks neat!
I have a few minor comments below.
Quoted 8 lines
arch/arm64/kernel/Makefile +0
-0@@ -55,6 +55,9 @@ arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o arm64-obj-$(CONFIG_CRASH_DUMP) += crash_dump.o arm64-obj-$(CONFIG_ARM_SDE_INTERFACE) += sdei.oQuoted 5 lines
arch/arm64/kernel/Makefile arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o · line 59
Quoted 2 lines
arch/arm64/kernel/Makefile +2
-0+arm64-obj-$(CONFIG_GCC_PLUGIN_STACKLEAK) += erase.o +KASAN_SANITIZE_erase.o := nI suspect we want to avoid the full set of instrumentation suspects here, e.g. GKOV, KASAN, UBSAN, and KCOV.
Quoted 5 lines
I've disabled KASAN instrumentation for that file on x86 because
erase_kstack()intentionally writes to the stack and causes KASAN false positive reports.But I didn't see any conflicts with other types of instrumentation that you mentioned.
Quoted 7 lines
The rationale is that any of these can result in implicit calls to C functions at arbitrary points during
erase_kstack(). That could interfere with the search for poison, and/or leave data on the stack which is not erased.They won't result in hard failures, as KASAN would, but we should probably avoid them regardless.
Thanks, Mark! Agree about KCOV, I'll switch it off for that file.
And I think I should _not_ disable UBSAN for that file. I didn't make any intentional UB, so if UBSAN finds anything, that will be a true positive report.
Quoted 47 lines
[...]
Quoted 45 lines
Quoted 23 lines
Quoted 23 lines
arch/arm64/kernel/Makefile +23
-0+asmlinkage void erase_kstack(void) +{ + unsigned long p = current->thread.lowest_stack; + unsigned long boundary = p & ~(THREAD_SIZE - 1); + unsigned long poison = 0; + const unsigned long check_depth = STACKLEAK_POISON_CHECK_DEPTH / + sizeof(unsigned long); + + /* + * Let's search for the poison value in the stack. + * Start from the lowest_stack and go to the bottom. + */ + while (p > boundary && poison <= check_depth) { + if (*(unsigned long *)p == STACKLEAK_POISON) + poison++; + else + poison = 0; + + p -= sizeof(unsigned long); + } + + /* + * One long int at the bottom of the thread stack is reserved andQuoted 8 lines
arch/arm64/kernel/Makefile
Quoted 4 lines
arch/arm64/kernel/Makefile +4
-0+ * should not be poisoned (see CONFIG_SCHED_STACK_END_CHECK). + */ + if (p == boundary) + p += sizeof(unsigned long);I wonder if
end_of_stack()should be taught aboutCONFIG_SCHED_STACK_END_CHECK, given that's supposed to return the last *usable* long on the stack, and we don't account for this elsewhere.I would be afraid to change the meaning of
end_of_stack()... Currently it considers that magic long as usable (include/linux/sched/task_stack.h):#define task_stack_end_corrupted(task) \ (*(end_of_stack(task)) != STACK_END_MAGIC)Quoted 5 lines
If we did, then IIUC we could do:
unsigned long boundary = (unsigned long)end_of_stack(current);... at the start of the function, and not have to worry about this explicitly.
Quoted 7 lines
Quoted 2 lines
I should mention that
erase_kstack()can be called from x86 trampoline stack. That's why the boundary is calculated from thelowest_stack.Ok. Under what circumstances does that happen?
It seems a little scary that
curent::thread::lowest_stackmight not be on current's task stack.
Yes, indeed. That's why I check against that, please see BUG_ON() in erase_kstack() for x86.
1. Calculate the boundary from the lowest_stack.
2. Search for poison between lowest_stack and boundary.
3. Now ready to write the poison.
4. Reset the boundary to current_stack_pointer if we are on the thread stack and to current_top_of_stack otherwise (we are on the trampoline stack).
5. BUG_ON(boundary - p >= THREAD_SIZE);
6. Write poison till the boundary.
Quoted 2 lines
Is that reset when transitioning to/from the
trampoile stack?
We switch to the trampoline stack from the current thread stack just before returning to the userspace. Please search for "trampoline stack" in arch/x86/entry/entry_64.S.
Quoted 19 lines
[...]
Quoted 13 lines
Quoted 13 lines
arch/arm64/kernel/Makefile
Quoted 10 lines
arch/arm64/kernel/Makefile +10
-0+#ifdef CONFIG_GCC_PLUGIN_STACKLEAK +void __used check_alloca(unsigned long size) +{ + unsigned long sp, stack_left; + + sp = current_stack_pointer; + + stack_left = sp & (THREAD_SIZE - 1); + BUG_ON(stack_left < 256 || size >= stack_left - 256); +}Is this arbitrary, or is there something special about 256?
Quoted 3 lines
Quoted 1 line
Even if this is arbitrary, can we give it some mnemonic?
It's just a reasonable number. We can introduce a macro for it.
Quoted 7 lines
I'm just not sure I see the point in the offset, given things like
VMAP_STACKexist.BUG_ON()handling will likely require *more* than 256 bytes of stack, so it seems superfluous, as we'd be relying on stack overflow detection at that point.I can see that we should take the
CONFIG_SCHED_STACK_END_CHECKoffset into account, though.
Mark, thank you for such an important remark!
In Kconfig STACKLEAK implies but doesn't depend on VMAP_STACK. In fact x86_32 doesn't have VMAP_STACK at all but can have STACKLEAK.
[Adding Andy Lutomirski]
I've made some additional experiments: I exhaust the thread stack to have only (MIN_STACK_LEFT - 1) bytes left and then force alloca. If VMAP_STACK is disabled, BUG_ON() handling causes stack depth overflow, which is detected by SCHED_STACK_END_CHECK. If VMAP_STACK is enabled, the kernel hangs on BUG_ON() handling! Enabling CONFIG_PROVE_LOCKING gives the needed report from VMAP_STACK:
[ 43.543962] lkdtm: try a large alloca of 14647 bytes (sp 18446683600580263344)... [ 43.545188] BUG: stack guard page was hit at 00000000830608b8 (stack is 000000009375e943..00000000cb7f52d9) [ 43.545189] kernel stack overflow (double-fault): 0000 [#1] SMP PTI [ 43.545189] Dumping ftrace buffer: [ 43.545190] (ftrace buffer empty) [ 43.545190] Modules linked in: lkdtm [ 43.545192] CPU: 0 PID: 2682 Comm: sh Not tainted 4.17.0-rc3+ #23 [ 43.545192] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Ubuntu-1.8.2-1ubuntu1 04/01/2014 [ 43.545193] RIP: 0010:mark_lock+0xe/0x540 [ 43.545193] RSP: 0018:ffffc900009c0000 EFLAGS: 00010002 [ 43.545194] RAX: 000000000000000c RBX: ffff880079b3b590 RCX: 0000000000000008 [ 43.545194] RDX: 0000000000000008 RSI: ffff880079b3b590 RDI: ffff880079b3ad40 [ 43.545195] RBP: ffffc900009c0100 R08: 0000000000000002 R09: 0000000000000000 [ 43.545195] R10: ffffc900009c0118 R11: 0000000000000000 R12: 0000000000000000 [ 43.545196] R13: ffff880079b3ad40 R14: ffff880079b3ad40 R15: ffffffff810cb8d7 [ 43.545196] FS: 00007f544c7d8700(0000) GS:ffff88007fc00000(0000) knlGS:0000000000000000 [ 43.545197] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 43.545200] CR2: ffffc900009bfff8 CR3: 0000000079194000 CR4: 00000000000006f0 [ 43.545200] Call Trace: [ 43.545201] ? vprintk_emit+0x67/0x440 [ 43.545201] __lock_acquire+0x2e0/0x13e0 [ 43.545201] ? lock_acquire+0x9d/0x1e0 [ 43.545202] lock_acquire+0x9d/0x1e0 [ 43.545202] ? vprintk_emit+0x67/0x440 [ 43.545203] _raw_spin_lock+0x20/0x30 [ 43.545203] ? vprintk_emit+0x67/0x440 [ 43.545203] vprintk_emit+0x67/0x440 [ 43.545204] ? check_alloca+0x9a/0xb0 [ 43.545204] printk+0x50/0x6f [ 43.545204] ? __probe_kernel_read+0x34/0x60 [ 43.545205] ? check_alloca+0x9a/0xb0 [ 43.545205] report_bug+0xd3/0x110 [ 43.545206] fixup_bug.part.10+0x13/0x30 [ 43.545206] do_error_trap+0x158/0x190 [ 43.545206] ? trace_hardirqs_off_thunk+0x1a/0x1c [ 43.545207] invalid_op+0x14/0x20 [ 43.545207] RIP: 0010:check_alloca+0x9a/0xb0 [ 43.545207] RSP: 0018:ffffc900009c0408 EFLAGS: 00010287 [ 43.545208] RAX: 0000000000000008 RBX: 0000000000003936 RCX: 0000000000000001 [ 43.545209] RDX: 0000000000000002 RSI: 0000000000000000 RDI: ffffc900009c0408 [ 43.545209] RBP: ffffc900009c3da0 R08: 0000000000000000 R09: 0000000000000000 [ 43.545210] R10: 0000000000000000 R11: 0000000000000000 R12: 0000000000003936 [ 43.545210] R13: 0000000001ff0610 R14: 0000000000000011 R15: ffffc900009c3f08 [ 43.545210] ? check_alloca+0x64/0xb0 [ 43.545211] do_alloca+0x55/0x71b [lkdtm] [ 43.545211] ? noop_count+0x10/0x10 [ 43.545211] ? check_usage+0xb1/0x4d0 [ 43.545212] ? noop_count+0x10/0x10 [ 43.545212] ? check_usage+0xb1/0x4d0 [ 43.545213] ? serial8250_console_write+0x253/0x2b0 [ 43.545213] ? serial8250_console_write+0x253/0x2b0 [ 43.545213] ? __lock_acquire+0x2e0/0x13e0 [ 43.545214] ? up+0xd/0x50 [ 43.545214] ? console_unlock+0x374/0x660 [ 43.545215] ? __lock_acquire+0x2e0/0x13e0 [ 43.545215] ? retint_kernel+0x10/0x10 [ 43.545215] ? trace_hardirqs_on_caller+0xed/0x180 [ 43.545216] ? find_held_lock+0x2d/0x90 [ 43.545216] ? mark_held_locks+0x4e/0x80 [ 43.545216] ? console_unlock+0x471/0x660 [ 43.545217] ? trace_hardirqs_on_caller+0xed/0x180 [ 43.545217] ? vprintk_emit+0x235/0x440 [ 43.545218] ? get_stack_info+0x32/0x160 [ 43.545218] ? check_alloca+0x64/0xb0 [ 43.545218] ? do_alloca+0x1f/0x71b [lkdtm] [ 43.545219] lkdtm_STACKLEAK_ALLOCA+0x8f/0xb0 [lkdtm] [ 43.545219] direct_entry+0xc5/0x110 [lkdtm] [ 43.545220] full_proxy_write+0x51/0x80 [ 43.545220] __vfs_write+0x49/0x180 [ 43.545220] ? rcu_read_lock_sched_held+0x53/0x60 [ 43.545221] ? rcu_sync_lockdep_assert+0x29/0x50 [ 43.545221] ? __sb_start_write+0x110/0x160 [ 43.545221] ? vfs_write+0x172/0x190 [ 43.545222] vfs_write+0xa8/0x190 [ 43.545222] ksys_write+0x50/0xc0 [ 43.545223] do_syscall_64+0x51/0x1a0 [ 43.545223] entry_SYSCALL_64_after_hwframe+0x49/0xbe [ 43.545223] RIP: 0033:0x7f544c306370 [ 43.545224] RSP: 002b:00007ffc223bacb8 EFLAGS: 00000246 ORIG_RAX: 0000000000000001 [ 43.545225] RAX: ffffffffffffffda RBX: 0000000001ff0610 RCX: 00007f544c306370 [ 43.545225] RDX: 0000000000000011 RSI: 0000000001ff0610 RDI: 0000000000000001 [ 43.545225] RBP: 0000000000000011 R08: 41434f4c4c415f4b R09: 00007f544c5bce90 [ 43.545226] R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000001 [ 43.545226] R13: 0000000000000011 R14: 7fffffffffffffff R15: 0000000000000000 [ 43.545227] Code: 08 08 00 00 48 c7 c7 70 56 2d 82 5b 48 89 d1 e9 a4 48 01 00 66 0f 1f 84 00 00 00 00 00 41 57 41 56 89 d1 41 55 41 54 49 89 fd 55 <53> bb 01 00 00 00 d3 e3 48 89 f5 41 89 d4 48 83 ec 08 0f b7 46 [ 43.545241] RIP: mark_lock+0xe/0x540 RSP: ffffc900009c0000 [ 43.545241] ---[ end trace 63196de7418a092e ]--- [ 43.545242] Kernel panic - not syncing: corrupted stack end detected inside scheduler [ 43.545242]
I can't say why VMAP_STACK report hangs during BUG_ON() handling on defconfig. Andy, can you give a clue?
I see that MIN_STACK_LEFT = 2048 is enough for BUG_ON() handling on both x86_64 and x86_32. So I'm going to:
- set MIN_STACK_LEFT to 2048;
- improve the lkdtm test to cover this case.
Mark, Kees, Laura, does it sound good?
Quoted 19 lines
Quoted 19 lines
Quoted 18 lines
Quoted 7 lines
arch/arm64/kernel/Makefile +2
-0+EXPORT_SYMBOL(check_alloca); +#endifdrivers/firmware/efi/libstub/Makefile +0
-0drivers/firmware/efi/libstub/Makefile cflags-$(CONFIG_EFI_ARMSTUB) += -I$(srctree)/scripts/dtc/libfdt · line 27
Quoted 9 lines
drivers/firmware/efi/libstub/Makefile +2
-1@@ -20,7 +20,8 @@ cflags-$(CONFIG_EFI_ARMSTUB) += -I$(srctree)/scripts/dtc/libfdt KBUILD_CFLAGS := $(cflags-y) -DDISABLE_BRANCH_PROFILING \ -D__NO_FORTIFY \ $(call cc-option,-ffreestanding) \ - $(call cc-option,-fno-stack-protector) + $(call cc-option,-fno-stack-protector) \ + $(DISABLE_STACKLEAK_PLUGIN) GCOV_PROFILE := n KASAN_SANITIZE := nI believe we'll also need to do this for the KVM hyp code in
arch/arm64/kvm/hyp/.
Quoted 5 lines
Quoted 1 line
Could you please give more details on that? Why STACKLEAK breaks it?
In the hyp/EL2 exception level, we only map the hyp text, and not the rest of the kernel. So
erase_kstackandcheck_allocawon't be mapped, and attempt to branch to them will fault.
Here you mean track_stack() and not erase_kstack(), right?
Quoted 6 lines
Even if it were mapped, things like
BUG_ON(),get_current(), etc do not work at hyp.Additionally, the hyp code is mapped as a different virtual address from the rest of the kernel, so if any of the STACKLEAK code happens to use an absolute address, this will not work correctly.
Thanks for the details. This quite old article [1] says:
The code run in HYP mode is limited to a few hundred instructions and isolated to two assembly files: arch/arm/kvm/interrupts.S and arch/arm/kvm/interrupts_head.S.
Is all hyp code now localized in arch/arm64/kvm/hyp/?
[1]: https://lwn.net/Articles/557132/
Best regards,
Alexander
Re: [PATCH 2/2] arm64: Clear the stack
Hello everyone,
Quoted 30 lines · Alexander Popov
Quoted 9 lines · Mark Rutland
Quoted 8 lines
Quoted 6 lines
arch/arm64/kernel/process.c void arch_setup_new_exec(void) · line 508
Quoted 2 lines
arch/arm64/kernel/process.c +2
-0+ stack_left = sp & (THREAD_SIZE -1 ); +BUG_ON (stack_left <256 || size >= stack_left -256 );Is this arbitrary, or is there something special about 256?
Even if this is arbitrary, can we give it some mnemonic?
It's just a reasonable number. We can introduce a macro for it.
Quoted 7 lines
I'm just not sure I see the point in the offset, given things like
VMAP_STACKexist.BUG_ON()handling will likely require *more* than 256 bytes of stack, so it seems superfluous, as we'd be relying on stack overflow detection at that point.I can see that we should take the
CONFIG_SCHED_STACK_END_CHECKoffset into account, though.Mark, thank you for such an important remark!
In
KconfigSTACKLEAK implies but doesn't depend onVMAP_STACK. In factx86_32doesn't haveVMAP_STACKat all but can have STACKLEAK.[Adding Andy Lutomirski]
I've made some additional experiments: I exhaust the thread stack to have only (
MIN_STACK_LEFT- 1) bytes left and then force alloca. IfVMAP_STACKis disabled,BUG_ON()handling causes stack depth overflow, which is detected bySCHED_STACK_END_CHECK. IfVMAP_STACKis enabled, the kernel hangs onBUG_ON()handling! EnablingCONFIG_PROVE_LOCKINGgives the needed report fromVMAP_STACK:
[...]
Quoted 9 lines
I can't say why
VMAP_STACKreport hangs duringBUG_ON()handling on defconfig. Andy, can you give a clue?I see that
MIN_STACK_LEFT= 2048 is enough forBUG_ON()handling on bothx86_64andx86_32. So I'm going to:
- setMIN_STACK_LEFTto 2048;
- improve the lkdtm test to cover this case.Mark, Kees, Laura, does it sound good?
Could you have a look at the following changes in check_alloca() before I send the next version?
If VMAP_STACK is enabled and alloca causes stack depth overflow, I write to guard page below the thread stack to cause double fault and VMAP_STACK report.
If VMAP_STACK is disabled, I use MIN_STACK_LEFT = 2048, which seems to be enough for BUG_ON() handling both on x86_32 and x86_64. Unfortunately, I can't guarantee that it is always enough.
#ifdef CONFIG_GCC_PLUGIN_STACKLEAK
-#define MIN_STACK_LEFT 256
+#define MIN_STACK_LEFT 2048
void __used check_alloca(unsigned long size)
{
unsigned long sp = (unsigned long)&sp;
struct stack_info stack_info = {0};
unsigned long visit_mask = 0;
unsigned long stack_left;
BUG_ON(get_stack_info(&sp, current, &stack_info, &visit_mask));
stack_left = sp - (unsigned long)stack_info.begin;
+
+#ifdef CONFIG_VMAP_STACK
+ /*
+ * If alloca oversteps the thread stack boundary, we touch the guard
+ * page provided by VMAP_STACK to trigger handle_stack_overflow().
+ */
+ if (size >= stack_left)
+ *(stack_info.begin - 1) = 42;
+#else
BUG_ON(stack_left < MIN_STACK_LEFT ||
size >= stack_left - MIN_STACK_LEFT);+#endif
}
EXPORT_SYMBOL(check_alloca);
#endif
Looking forward to your feedback.
Best regards,
Alexander
Mark Rutland
reviewerRe: [PATCH 2/2] arm64: Clear the stack
Quoted 33 lines · Alexander Popov
Hello everyone,
Quoted 30 lines · Alexander Popov
Quoted 9 lines · Mark Rutland
Quoted 8 lines
Quoted 6 lines
arch/arm64/kernel/process.c void arch_setup_new_exec(void) · line 508
Quoted 2 lines
arch/arm64/kernel/process.c +2
-0+ stack_left = sp & (THREAD_SIZE -1 ); +BUG_ON (stack_left <256 || size >= stack_left -256 );Is this arbitrary, or is there something special about 256?
Even if this is arbitrary, can we give it some mnemonic?
It's just a reasonable number. We can introduce a macro for it.
Quoted 7 lines
I'm just not sure I see the point in the offset, given things like
VMAP_STACKexist.BUG_ON()handling will likely require *more* than 256 bytes of stack, so it seems superfluous, as we'd be relying on stack overflow detection at that point.I can see that we should take the
CONFIG_SCHED_STACK_END_CHECKoffset into account, though.Mark, thank you for such an important remark!
In
KconfigSTACKLEAK implies but doesn't depend onVMAP_STACK. In factx86_32doesn't haveVMAP_STACKat all but can have STACKLEAK.[Adding Andy Lutomirski]
I've made some additional experiments: I exhaust the thread stack to have only (
MIN_STACK_LEFT- 1) bytes left and then force alloca. IfVMAP_STACKis disabled,BUG_ON()handling causes stack depth overflow, which is detected bySCHED_STACK_END_CHECK. IfVMAP_STACKis enabled, the kernel hangs onBUG_ON()handling! EnablingCONFIG_PROVE_LOCKINGgives the needed report fromVMAP_STACK:
I can't see why CONFIG_VMAP_STACK would only work in conjunction with CONFIG_PROVE_LOCKING.
On arm64 at least, if we overflow the stack while handling a BUG(), we *should* trigger the overflow handler as usual, and that should work, unless I'm missing something.
Maybe it gets part-way into panic(), sets up some state, stack-overflows, and we get wedged because we're already in a panic? Perhaps CONFIG_PROVE_LOCKING causes more stack to be used, so it dies a little earlier in panic(), before setting up some state that causes wedging.
... which sounds like something best fixed in those code paths, and not here.
Quoted 10 lines
[...]
Quoted 8 lines
I can't say why
VMAP_STACKreport hangs duringBUG_ON()handling on defconfig. Andy, can you give a clue?I see that
MIN_STACK_LEFT= 2048 is enough forBUG_ON()handling on bothx86_64andx86_32. So I'm going to:
- setMIN_STACK_LEFTto 2048;
- improve the lkdtm test to cover this case.
Quoted 8 lines
Quoted 1 line
Mark, Kees, Laura, does it sound good?
Could you have a look at the following changes in
check_alloca()before I send the next version?If
VMAP_STACKis enabled and alloca causes stack depth overflow, I write to guard page below the thread stack to cause double fault andVMAP_STACKreport.
On arm64 at least, writing to the guard page will not itself trigger a stack overflow, but will trigger a data abort. I suspect similar is true on x86, if the stack pointer is sufficiently far above the guard page.
Quoted 3 lines
If
VMAP_STACKis disabled, I useMIN_STACK_LEFT= 2048, which seems to be enough forBUG_ON()handling both onx86_32andx86_64. Unfortunately, I can't guarantee that it is always enough.
I don't think that we can choose something that's guaranteed to be sufficient for BUG() handling and also not wasting a tonne of space under normal operation.
Let's figure out what's going wrong on x86 in the case that you mention, and try to solve that.
Here I don't think we should reserve space at all -- it's completely arbitrary, and as above we can't guarantee that it's sufficient anyway.
Quoted 13 lines
#ifdef
CONFIG_GCC_PLUGIN_STACKLEAK(quoted diff) +1
-1-#define MIN_STACK_LEFT 256 +#define MIN_STACK_LEFT 2048 void __used check_alloca(unsigned long size) { unsigned long sp = (unsigned long)&sp; struct stack_info stack_info = {0}; unsigned long visit_mask = 0; unsigned long stack_left; BUG_ON(get_stack_info(&sp, current, &stack_info, &visit_mask));
Review
Quoted 10 lines
(quoted diff) +9
-0stack_left = sp - (unsigned long)stack_info.begin; + +#ifdef CONFIG_VMAP_STACK + /* + * If alloca oversteps the thread stack boundary, we touch the guard + * page provided by VMAP_STACK to trigger handle_stack_overflow(). + */ + if (size >= stack_left) + *(stack_info.begin - 1) = 42; +#else
On arm64, this won't trigger our stack overflow handler, unless the SP is already very close to the boundary.
Please just use BUG(). If there is an issue on x86, it would be good to solve that in the x86 code.
Quoted 2 lines
BUG_ON(stack_left < MIN_STACK_LEFT ||
size >= stack_left - MIN_STACK_LEFT);I really don't think we should bother with this arbitrary offset at all.
Thanks,
Mark.
PATCH 2/2 arm64: Clear the stack
Hello Mark,
Thanks a lot for your reply!
Quoted 40 lines · Mark Rutland
Quoted 24 lines · Alexander Popov
Quoted 23 lines · Alexander Popov
Quoted 9 lines · Mark Rutland
Quoted 8 lines
Quoted 6 lines
Review
Quoted 2 lines
(quoted diff) +2
-0+ stack_left = sp & (THREAD_SIZE - 1); + BUG_ON(stack_left < 256 || size >= stack_left - 256);Is this arbitrary, or is there something special about 256?
Even if this is arbitrary, can we give it some mnemonic?
It's just a reasonable number. We can introduce a macro for it.
Quoted 7 lines
I'm just not sure I see the point in the offset, given things like
VMAP_STACKexist.BUG_ON()handling will likely require *more* than 256 bytes of stack, so it seems superfluous, as we'd be relying on stack overflow detection at that point.I can see that we should take the
CONFIG_SCHED_STACK_END_CHECKoffset into account, though.Mark, thank you for such an important remark!
In
KconfigSTACKLEAK implies but doesn't depend onVMAP_STACK. In factx86_32doesn't haveVMAP_STACKat all but can have STACKLEAK.Quoted 7 lines
Quoted 7 lines
[Adding Andy Lutomirski]
I've made some additional experiments: I exhaust the thread stack to have only (
MIN_STACK_LEFT- 1) bytes left and then force alloca. IfVMAP_STACKis disabled,BUG_ON()handling causes stack depth overflow, which is detected bySCHED_STACK_END_CHECK. IfVMAP_STACKis enabled, the kernel hangs onBUG_ON()handling! EnablingCONFIG_PROVE_LOCKINGgives the needed report fromVMAP_STACK:I can't see why
CONFIG_VMAP_STACKwould only work in conjunction withCONFIG_PROVE_LOCKING.On arm64 at least, if we overflow the stack while handling a
BUG(), we *should* trigger the overflow handler as usual, and that should work, unless I'm missing something.
Quoted 5 lines
Maybe it gets part-way into
panic(), sets up some state, stack-overflows, and we get wedged because we're already in a panic? PerhapsCONFIG_PROVE_LOCKINGcauses more stack to be used, so it dies a little earlier inpanic(), before setting up some state that causes wedging.
That seems likely. I later noticed that I had oops=panic kernel parameter.
Quoted 19 lines
... which sounds like something best fixed in those code paths, and not here.
Quoted 16 lines
[...]
Quoted 9 lines
I can't say why
VMAP_STACKreport hangs duringBUG_ON()handling on defconfig. Andy, can you give a clue?I see that
MIN_STACK_LEFT= 2048 is enough forBUG_ON()handling on bothx86_64andx86_32. So I'm going to:
- setMIN_STACK_LEFTto 2048;
- improve the lkdtm test to cover this case.Mark, Kees, Laura, does it sound good?
Could you have a look at the following changes in
check_alloca()before I send the next version?
Quoted 6 lines
Quoted 2 lines
If
VMAP_STACKis enabled and alloca causes stack depth overflow, I write to guard page below the thread stack to cause double fault andVMAP_STACKreport.On arm64 at least, writing to the guard page will not itself trigger a stack overflow, but will trigger a data abort. I suspect similar is true on x86, if the stack pointer is sufficiently far above the guard page.
Yes, you are right, my mistake.
The comment about CONFIG_VMAP_STACK in arch/x86/kernel/traps.c says:
"If we overflow the stack into a guard page, the CPU will fail to deliver #PF and will send #DF instead."
Quoted 41 lines
Quoted 3 lines
If
VMAP_STACKis disabled, I useMIN_STACK_LEFT= 2048, which seems to be enough forBUG_ON()handling both onx86_32andx86_64. Unfortunately, I can't guarantee that it is always enough.I don't think that we can choose something that's guaranteed to be sufficient for
BUG()handling and also not wasting a tonne of space under normal operation.Let's figure out what's going wrong on x86 in the case that you mention, and try to solve that.
Here I don't think we should reserve space at all -- it's completely arbitrary, and as above we can't guarantee that it's sufficient anyway.
Quoted 13 lines
#ifdef
CONFIG_GCC_PLUGIN_STACKLEAK(quoted diff) +1
-1-#define MIN_STACK_LEFT 256 +#define MIN_STACK_LEFT 2048 void __used check_alloca(unsigned long size) { unsigned long sp = (unsigned long)&sp; struct stack_info stack_info = {0}; unsigned long visit_mask = 0; unsigned long stack_left; BUG_ON(get_stack_info(&sp, current, &stack_info, &visit_mask));Review
Quoted 10 lines
(quoted diff) +9
-0stack_left = sp - (unsigned long)stack_info.begin; + +#ifdef CONFIG_VMAP_STACK + /* + * If alloca oversteps the thread stack boundary, we touch the guard + * page provided by VMAP_STACK to trigger handle_stack_overflow(). + */ + if (size >= stack_left) + *(stack_info.begin - 1) = 42; +#elseOn arm64, this won't trigger our stack overflow handler, unless the SP is already very close to the boundary.
Quoted 7 lines
Please just use
BUG(). If there is an issue on x86, it would be good to solve that in the x86 code.Quoted 2 lines
BUG_ON(stack_left < MIN_STACK_LEFT || size >= stack_left - MIN_STACK_LEFT);I really don't think we should bother with this arbitrary offset at all.
Thanks. I agree with all your points.
I wrote a third lkdtm test for STACKLEAK which runs deep recursion with alloca. If I have just BUG_ON(size >= stack_left) in check_alloca(), I get the following nice report without any trouble:
[ 8.407261] lkdtm: Performing direct entry STACKLEAK_RECURSION_WITH_ALLOCA [ 8.408641] lkdtm: checking unused part of the thread stack (15744 bytes)... [ 8.409936] lkdtm: first 744 bytes are unpoisoned [ 8.410751] lkdtm: the rest of the thread stack is properly erased [ 8.411760] lkdtm: try to overflow the thread stack using recursion & alloca [ 8.412914] BUG: stack guard page was hit at 00000000b993c2bc (stack is 00000000764adcd4..000000005b443f11) [ 8.414471] kernel stack overflow (double-fault): 0000 [#1] SMP PTI [ 8.415409] Dumping ftrace buffer: [ 8.415907] (ftrace buffer empty) [ 8.416404] Modules linked in: lkdtm [ 8.416905] CPU: 0 PID: 2664 Comm: sh Not tainted 4.17.0-rc3+ #39 [ 8.417766] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Ubuntu-1.8.2-1ubuntu1 04/01/2014 [ 8.419088] RIP: 0010:do_error_trap+0x31/0x130 [ 8.419647] RSP: 0018:ffffc900009b3fc0 EFLAGS: 00010046 [ 8.420263] RAX: 0000000000000000 RBX: ffffc900009b4078 RCX: 0000000000000006 [ 8.421322] RDX: ffffffff81fdbe4d RSI: 0000000000000000 RDI: ffffc900009b4078 [ 8.422837] RBP: 0000000000000006 R08: 0000000000000004 R09: 0000000000000000 [ 8.425095] R10: 0000000000000000 R11: 0000000000000000 R12: 0000000000000004 [ 8.427365] R13: ffffffff81fdbe4d R14: 0000000000000000 R15: 0000000000000000 [ 8.430111] FS: 00007f7c340c1700(0000) GS:ffff88007fc00000(0000) knlGS:0000000000000000 [ 8.432515] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 8.433132] CR2: ffffc900009b3fb8 CR3: 000000007b330000 CR4: 00000000000006f0 [ 8.433904] Call Trace: [ 8.434180] invalid_op+0x14/0x20 [ 8.434546] RIP: 0010:check_alloca+0x8e/0xa0 [ 8.434995] RSP: 0018:ffffc900009b4128 EFLAGS: 00010283 [ 8.435555] RAX: 0000000000000128 RBX: 0000000000000190 RCX: 0000000000000001 [ 8.436479] RDX: 0000000000000002 RSI: 0000000000000000 RDI: ffffc900009b4128 [ 8.437871] RBP: ffffc900009b4180 R08: 000000000000018f R09: 0000000000000007 [ 8.438661] R10: 0000000000000000 R11: 0000000000000030 R12: ffff88007a626000 [ 8.439433] R13: 0000000001cf5610 R14: 0000000000000020 R15: ffffc900009b7f08 [ 8.440329] ? check_alloca+0x64/0xa0 [ 8.440845] do_alloca+0x20/0x60 [lkdtm] [ 8.441937] recursion+0xa0/0xd0 [lkdtm] [ 8.443370] ? vsnprintf+0xf2/0x4b0 [ 8.444289] ? get_stack_info+0x32/0x160 [ 8.445359] ? check_alloca+0x64/0xa0 [ 8.445995] ? do_alloca+0x20/0x60 [lkdtm] [ 8.446449] recursion+0xbb/0xd0 [lkdtm] [ 8.446881] ? vsnprintf+0xf2/0x4b0 [ 8.447259] ? get_stack_info+0x32/0x160 [ 8.447693] ? check_alloca+0x64/0xa0 [ 8.448088] ? do_alloca+0x20/0x60 [lkdtm] [ 8.448539] recursion+0xbb/0xd0 [lkdtm]
...
It seems that previously I was very "lucky" to accidentally have those MIN_STACK_LEFT, call trace depth and oops=panic together to experience a hang on stack overflow during BUG().
When I run my test in a loop _without_ VMAP_STACK, I manage to corrupt the neighbour processes with BUG() handling overstepping the stack boundary. It's a pity, but I have an idea.
In kernel/sched/core.c we already have:
#ifdef CONFIG_SCHED_STACK_END_CHECK
if (task_stack_end_corrupted(prev))
panic("corrupted stack end detected inside scheduler\n");#endif
So what would you think if I do the following in check_alloca():
if (size >= stack_left) {
#if !defined(CONFIG_VMAP_STACK) && defined(CONFIG_SCHED_STACK_END_CHECK)
panic("alloca over the kernel stack boundary\n");
#else
BUG();#endif
I think that fits well to the CONFIG_SCHED_STACK_END_CHECK policy.
Best regards,
Alexander
Mark Rutland
reviewerRe: [PATCH 2/2] arm64: Clear the stack
Quoted 8 lines · Alexander Popov
It seems that previously I was very "lucky" to accidentally have those
MIN_STACK_LEFT, call trace depth and oops=panic together to experience a hang on stack overflow duringBUG().When I run my test in a loop _without_
VMAP_STACK, I manage to corrupt the neighbour processes withBUG()handling overstepping the stack boundary. It's a pity, but I have an idea.
I think that in the absence of VMAP_STACK, there will always be cases where we *could* corrupt a neighbouring stack, but I agree that trying to minimize that possibility would be good.
Quoted 7 lines
In
kernel/sched/core.cwe already have:#ifdef
CONFIG_SCHED_STACK_END_CHECK
if (task_stack_end_corrupted(prev))panic("corrupted stack end detected inside scheduler\n");#endif
Quoted 8 lines
So what would you think if I do the following in
check_alloca():if (size >= stack_left) { #if !defined(CONFIG_VMAP_STACK) && defined(CONFIG_SCHED_STACK_END_CHECK) panic("alloca over the kernel stack boundary\n"); #else BUG();#endif
Given this is already out-of-line, how about we always use panic(), regardless of VMAP_STACK and SCHED_STACK_END_CHECK? i.e. just
if (unlikely(size >= stack_left))
panic("alloca over the kernel stack boundary");If we have VMAP_STACK selected, and overflow during the panic, it's the same as if we overflowed during the BUG(). It's likely that panic() will use less stack space than BUG(), and the compiler can put the call in a slow path that shouldn't affect most calls, so in all cases it's likely preferable.
Thanks,
Mark.
Re: [PATCH 2/2] arm64: Clear the stack
Quoted 6 lines · Mark Rutland
Quoted 5 lines · Alexander Popov
It seems that previously I was very "lucky" to accidentally have those
MIN_STACK_LEFT, call trace depth and oops=panic together to experience a hang on stack overflow duringBUG().
Quoted 7 lines
Quoted 3 lines
When I run my test in a loop _without_
VMAP_STACK, I manage to corrupt the neighbour processes withBUG()handling overstepping the stack boundary. It's a pity, but I have an idea.I think that in the absence of
VMAP_STACK, there will always be cases where we *could* corrupt a neighbouring stack, but I agree that trying to minimize that possibility would be good.
Ok!
Quoted 19 lines
Quoted 7 lines
In
kernel/sched/core.cwe already have:#ifdef
CONFIG_SCHED_STACK_END_CHECK
if (task_stack_end_corrupted(prev))panic("corrupted stack end detected inside scheduler\n");#endif
Quoted 8 lines
So what would you think if I do the following in
check_alloca():if (size >= stack_left) { #if !defined(CONFIG_VMAP_STACK) && defined(CONFIG_SCHED_STACK_END_CHECK) panic("alloca over the kernel stack boundary\n"); #else BUG();#endif
Given this is already out-of-line, how about we always use
panic(), regardless ofVMAP_STACKandSCHED_STACK_END_CHECK? i.e. just
Quoted 7 lines
if (unlikely(size >= stack_left)) panic("alloca over the kernel stack boundary");If we have
VMAP_STACKselected, and overflow during the panic, it's the same as if we overflowed during theBUG(). It's likely thatpanic()will use less stack space thanBUG(), and the compiler can put the call in a slow path that shouldn't affect most calls, so in all cases it's likely preferable.
I'm sure that maintainers and Linus will strongly dislike my patch if I always use panic() here. panic() kills the whole kernel and we shouldn't use it when we can safely continue to work.
Let me describe my logic. So let's have size >= stack_left on a thread stack.
1. If CONFIG_VMAP_STACK is enabled, we can safely use BUG(). Even if BUG() handling overflows the thread stack into the guard page, handle_stack_overflow() is called and the neighbour memory is not corrupted. The kernel can proceed to live.
2. If CONFIG_VMAP_STACK is disabled, BUG() handling can corrupt the neighbour kernel memory and cause the undefined behaviour of the whole kernel. I see it on my lkdtm test. That is a cogent reason for panic().
2.a. If CONFIG_SCHED_STACK_END_CHECK is enabled, the kernel already does panic() when STACK_END_MAGIC is corrupted. So we will _not_ break the safety policy if we do panic() in a similar situation in check_alloca().
2.b. If CONFIG_SCHED_STACK_END_CHECK is disabled, the user has some real reasons not to do panic() when the kernel stack is corrupted. So we should not do it in check_alloca() as well, just use BUG() and hope for the best.
That logic can be expressed this way:
if (size >= stack_left) {
#if !defined(CONFIG_VMAP_STACK) && defined(CONFIG_SCHED_STACK_END_CHECK)
panic("alloca over the kernel stack boundary\n");
#else
BUG();#endif
I think I should add a proper comment to describe it.
Thank you.
Best regards,
Alexander
Mark Rutland
reviewerRe: [PATCH 2/2] arm64: Clear the stack
Quoted 26 lines · Alexander Popov
Quoted 13 lines · Mark Rutland
Quoted 8 lines · Alexander Popov
So what would you think if I do the following in
check_alloca():if (size >= stack_left) { #if !defined(CONFIG_VMAP_STACK) && defined(CONFIG_SCHED_STACK_END_CHECK) panic("alloca over the kernel stack boundary\n"); #else BUG();#endif
Given this is already out-of-line, how about we always use
panic(), regardless ofVMAP_STACKandSCHED_STACK_END_CHECK? i.e. justQuoted 7 lines
if (unlikely(size >= stack_left)) panic("alloca over the kernel stack boundary");If we have
VMAP_STACKselected, and overflow during the panic, it's the same as if we overflowed during theBUG(). It's likely thatpanic()will use less stack space thanBUG(), and the compiler can put the call in a slow path that shouldn't affect most calls, so in all cases it's likely preferable.I'm sure that maintainers and Linus will strongly dislike my patch if I always use
panic()here.panic()kills the whole kernel and we shouldn't use it when we can safely continue to work.
Quoted 5 lines
Let me describe my logic. So let's have size >=
stack_lefton a thread stack.1. If
CONFIG_VMAP_STACKis enabled, we can safely useBUG(). Even ifBUG()handling overflows the thread stack into the guard page,handle_stack_overflow()is called and the neighbour memory is not corrupted. The kernel can proceed to live.
On arm64 with CONFIG_VMAP_STACK, a stack overflow will result in a panic(). My understanding was that the same is true on x86.
Quoted 3 lines
2. If
CONFIG_VMAP_STACKis disabled,BUG()handling can corrupt the neighbour kernel memory and cause the undefined behaviour of the whole kernel. I see it on my lkdtm test. That is a cogent reason forpanic().
In this case, panic() can also corrupt the neighbour stack, and could also fail.
When CONFIG_VMAP_STACK is not selected, a stack overflow simply cannot be handled reliably -- while panic() may be more likely to succeed, it is not gauranteed to.
Quoted 3 lines
2.a. If
CONFIG_SCHED_STACK_END_CHECKis enabled, the kernel already doespanic()whenSTACK_END_MAGICis corrupted. So we will _not_ break the safety policy if we dopanic()in a similar situation incheck_alloca().
Sure, I'm certainly happy with panic() here.
Quoted 2 lines
2.b. If
CONFIG_SCHED_STACK_END_CHECKis disabled, the user has some real reasons not to dopanic()when the kernel stack is corrupted.
I believe that CONFIG_SCHED_STACK_END_CHECK is seen as a debug feature, and hence people don't select it. I strongly doubt that people have reasons to disable it other than not wanting the overhead associated with debug features.
I think it is reasonable to panic() here even with CONFIG_VMAP_STACK selected.
Quoted 2 lines
So we should not do it in
check_alloca()as well, just useBUG()and hope for the best.
Regardless of whether we BUG() or panic(), we're hoping for the best.
Consistently using panic() here will keep things simpler, so any failure reported will be easier to reason about, and easier to debug.
Thanks,
Mark.
Re: [PATCH 2/2] arm64: Clear the stack
Quoted 27 lines · Mark Rutland
Quoted 26 lines · Alexander Popov
Quoted 13 lines · Mark Rutland
Quoted 8 lines · Alexander Popov
So what would you think if I do the following in
check_alloca():if (size >= stack_left) { #if !defined(CONFIG_VMAP_STACK) && defined(CONFIG_SCHED_STACK_END_CHECK) panic("alloca over the kernel stack boundary\n"); #else BUG();#endif
Given this is already out-of-line, how about we always use
panic(), regardless ofVMAP_STACKandSCHED_STACK_END_CHECK? i.e. justQuoted 7 lines
if (unlikely(size >= stack_left)) panic("alloca over the kernel stack boundary");If we have
VMAP_STACKselected, and overflow during the panic, it's the same as if we overflowed during theBUG(). It's likely thatpanic()will use less stack space thanBUG(), and the compiler can put the call in a slow path that shouldn't affect most calls, so in all cases it's likely preferable.I'm sure that maintainers and Linus will strongly dislike my patch if I always use
panic()here.panic()kills the whole kernel and we shouldn't use it when we can safely continue to work.
Quoted 8 lines
Quoted 5 lines
Let me describe my logic. So let's have size >=
stack_lefton a thread stack.1. If
CONFIG_VMAP_STACKis enabled, we can safely useBUG(). Even ifBUG()handling overflows the thread stack into the guard page,handle_stack_overflow()is called and the neighbour memory is not corrupted. The kernel can proceed to live.On arm64 with
CONFIG_VMAP_STACK, a stack overflow will result in apanic(). My understanding was that the same is true on x86.
No, x86 CONFIG_VMAP_STACK only kills the offending process. I see it on my deep recursion test, the kernel continues to live. handle_stack_overflow() in arch/x86/kernel/traps.c calls die().
Quoted 11 lines
Quoted 3 lines
2. If
CONFIG_VMAP_STACKis disabled,BUG()handling can corrupt the neighbour kernel memory and cause the undefined behaviour of the whole kernel. I see it on my lkdtm test. That is a cogent reason forpanic().In this case,
panic()can also corrupt the neighbour stack, and could also fail.When
CONFIG_VMAP_STACKis not selected, a stack overflow simply cannot be handled reliably -- whilepanic()may be more likely to succeed, it is not gauranteed to.
Quoted 5 lines
Quoted 3 lines
2.a. If
CONFIG_SCHED_STACK_END_CHECKis enabled, the kernel already doespanic()whenSTACK_END_MAGICis corrupted. So we will _not_ break the safety policy if we dopanic()in a similar situation incheck_alloca().Sure, I'm certainly happy with
panic()here.
Ok!
Quoted 5 lines
Quoted 2 lines
2.b. If
CONFIG_SCHED_STACK_END_CHECKis disabled, the user has some real reasons not to dopanic()when the kernel stack is corrupted.I believe that
CONFIG_SCHED_STACK_END_CHECKis seen as a debug feature, and hence people don't select it.
I see CONFIG_SCHED_STACK_END_CHECK enabled by default in Ubuntu config...
Quoted 3 lines
I strongly doubt that people have
reasons to disable it other than not wanting the overhead associated with debug features.
I think it's not a question of performance here. There are cases when a system must live as long as possible (even partially corrupted) and must not die entirely. Oops is ok for those systems, but panic (full DoS) is not.
Quoted 2 lines
I think it is reasonable to
panic()here even withCONFIG_VMAP_STACKselected.
It's too tough for CONFIG_VMAP_STACK on x86 - the system can proceed to live. Anyway, the check_alloca() code will not be shared between x86 and arm64, I've described the reasons in this thread. So I can have BUG() for CONFIG_VMAP_STACK on x86 and Laura can consistently use panic() on arm64.
Quoted 7 lines
Quoted 2 lines
So we should not do it in
check_alloca()as well, just useBUG()and hope for the best.Regardless of whether we
BUG()orpanic(), we're hoping for the best.Consistently using
panic()here will keep things simpler, so any failure reported will be easier to reason about, and easier to debug.
Let me keep BUG() for !CONFIG_SCHED_STACK_END_CHECK. I beware of using panic() by default, let distro/user decide this. I remember very well how I was shouted at, when this one was merged:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ce6fa91b93630396ca220c33dd38ffc62686d499
Mark, I'm really grateful to you for such a nice code review! Alexander
Mark Rutland
reviewerRe: [PATCH 2/2] arm64: Clear the stack
Quoted 8 lines · Alexander Popov
Quoted 2 lines · Mark Rutland
I think it is reasonable to
panic()here even withCONFIG_VMAP_STACKselected.It's too tough for
CONFIG_VMAP_STACKon x86 - the system can proceed to live. Anyway, thecheck_alloca()code will not be shared between x86 and arm64, I've described the reasons in this thread. So I can haveBUG()forCONFIG_VMAP_STACKon x86 and Laura can consistently usepanic()on arm64.
If we need arch-specific implementations anyway, then that's fine by me.
Quoted 5 lines
Quoted 5 lines
Quoted 2 lines
So we should not do it in
check_alloca()as well, just useBUG()and hope for the best.Regardless of whether we
BUG()orpanic(), we're hoping for the best.
Quoted 7 lines
Quoted 2 lines
Consistently using
panic()here will keep things simpler, so any failure reported will be easier to reason about, and easier to debug.Let me keep
BUG()for !CONFIG_SCHED_STACK_END_CHECK. I beware of usingpanic()by default, let distro/user decide this. I remember very well how I was shouted at, when this one was merged:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ce6fa91b93630396ca220c33dd38ffc62686d499
Sure; my comments needn't hold up your patches.
Thanks,
Mark.
Re: [PATCH 2/2] arm64: Clear the stack
In reply to Mark RutlandQuoted 61 lines · Mark Rutland
Hi Laura,
Quoted 6 lines · Laura Abbott
Implementation of stackleak based heavily on the x86 version
Signed-off-by Laura Abbott <[email protected]>---
Now written in C instead of a bunch of assembly.This looks neat!
I have a few minor comments below.
arch/arm64/kernel/Makefile arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o · line 59
Quoted 10 lines
arch/arm64/kernel/Makefile +2
-0@@ -55,6 +55,9 @@ arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o arm64-obj-$(CONFIG_CRASH_DUMP) += crash_dump.o arm64-obj-$(CONFIG_ARM_SDE_INTERFACE) += sdei.o +arm64-obj-$(CONFIG_GCC_PLUGIN_STACKLEAK) += erase.o +KASAN_SANITIZE_erase.o := nI suspect we want to avoid the full set of instrumentation suspects here, e.g. GKOV, KASAN, UBSAN, and KCOV.
Quoted 9 lines
arch/arm64/kernel/Makefile +1
-0+ obj-y += $(arm64-obj-y) vdso/ probes/ obj-m += $(arm64-obj-m) head-y := head.oarch/arm64/kernel/entry.S +0
-0arch/arm64/kernel/entry.S tsk .req x28 // current thread_info · line 408
Quoted 8 lines
arch/arm64/kernel/entry.S +5
-0@@ -401,6 +401,11 @@ tsk .req x28 // current thread_info .text + .macro ERASE_KSTACK +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK + bl erase_kstack +#endif + .endmNit: The rest of our asm macros are lower-case -- can we stick to that here?
arch/arm64/kernel/entry.S ret_to_user: · line 916
Quoted 10 lines
arch/arm64/kernel/entry.S +1
-0/* * Exception vectors. */@@ -906,6 +911,7 @@ ret_to_user: cbnz x2, work_pending finish_ret_to_user: enable_step_tsk x1, x2 + ERASE_KSTACK kernel_exit 0 ENDPROC(ret_to_user)I believe we also need this in
ret_fast_syscall.
Quoted 2 lines
[...]
Yeah I had this in previous versions but I managed to out think myself. I'll add it in with a comment to avoid confusion.
Quoted 38 lines
Quoted 17 lines
arch/arm64/kernel/entry.S +17
-0+asmlinkage void erase_kstack(void) +{ + unsigned long p = current->thread.lowest_stack; + unsigned long boundary = p & ~(THREAD_SIZE - 1); + unsigned long poison = 0; + const unsigned long check_depth = STACKLEAK_POISON_CHECK_DEPTH / + sizeof(unsigned long); + + /* + * Let's search for the poison value in the stack. + * Start from the lowest_stack and go to the bottom. + */ + while (p > boundary && poison <= check_depth) { + if (*(unsigned long *)p == STACKLEAK_POISON) + poison++; + else + poison = 0;arch/arm64/kernel/entry.S
Quoted 10 lines
arch/arm64/kernel/entry.S +10
-0+ + p -= sizeof(unsigned long); + } + + /* + * One long int at the bottom of the thread stack is reserved and + * should not be poisoned (see CONFIG_SCHED_STACK_END_CHECK). + */ + if (p == boundary) + p += sizeof(unsigned long);I wonder if
end_of_stack()should be taught aboutCONFIG_SCHED_STACK_END_CHECK, given that's supposed to return the last *usable* long on the stack, and we don't account for this elsewhere.If we did, then IIUC we could do:
unsigned long boundary = (unsigned long)end_of_stack(current);... at the start of the function, and not have to worry about this explicitly.
Quoted 15 lines
arch/arm64/kernel/entry.S
Quoted 10 lines
arch/arm64/kernel/entry.S +10
-0+ +#ifdef CONFIG_STACKLEAK_METRICS + current->thread.prev_lowest_stack = p; +#endif + + /* + * So let's write the poison value to the kernel stack. + * Start from the address in p and move up till the new boundary. + */ + boundary = current_stack_pointer;I worry a little that the compiler can move the SP during a function's lifetime, but maybe that's only the case when there are VLAs, or something like that?
I think that's true and a risk we take writing this in C. Here's the disassembly on gcc-7.3.1:
ffff00000809d4d8 <erase_kstack>:
ffff00000809d4d8: a9bf7bfd stp x29, x30, [sp, #-16]! ffff00000809d4dc: d5384100 mrs x0, sp_el0 ffff00000809d4e0: 910003fd mov x29, sp ffff00000809d4e4: f946e400 ldr x0, [x0, #3528] ffff00000809d4e8: 9272c404 and x4, x0, #0xffffffffffffc000 ffff00000809d4ec: eb04001f cmp x0, x4 ffff00000809d4f0: 540002c9 b.ls ffff00000809d548 <erase_kstack+0x70> // b.plast ffff00000809d4f4: d2800003 mov x3, #0x0 // #0 ffff00000809d4f8: 9297ddc5 mov x5, #0xffffffffffff4111 // #-48879 ffff00000809d4fc: 14000008 b ffff00000809d51c <erase_kstack+0x44> ffff00000809d500: d1002000 sub x0, x0, #0x8 ffff00000809d504: 52800022 mov w2, #0x1 // #1 ffff00000809d508: eb00009f cmp x4, x0 ffff00000809d50c: d2800003 mov x3, #0x0 // #0 ffff00000809d510: 1a9f27e1 cset w1, cc // cc = lo, ul, last ffff00000809d514: 6a01005f tst w2, w1 ffff00000809d518: 54000180 b.eq ffff00000809d548 <erase_kstack+0x70> // b.none ffff00000809d51c: f9400001 ldr x1, [x0] ffff00000809d520: eb05003f cmp x1, x5 ffff00000809d524: 54fffee1 b.ne ffff00000809d500 <erase_kstack+0x28> // b.any ffff00000809d528: 91000463 add x3, x3, #0x1 ffff00000809d52c: d1002000 sub x0, x0, #0x8 ffff00000809d530: f100407f cmp x3, #0x10 ffff00000809d534: 1a9f87e2 cset w2, ls // ls = plast ffff00000809d538: eb00009f cmp x4, x0 ffff00000809d53c: 1a9f27e1 cset w1, cc // cc = lo, ul, last ffff00000809d540: 6a01005f tst w2, w1 ffff00000809d544: 54fffec1 b.ne ffff00000809d51c <erase_kstack+0x44> // b.any ffff00000809d548: eb00009f cmp x4, x0 ffff00000809d54c: 91002001 add x1, x0, #0x8 ffff00000809d550: 9a800020 csel x0, x1, x0, eq // eq = none ffff00000809d554: 910003e1 mov x1, sp ffff00000809d558: d5384102 mrs x2, sp_el0 ffff00000809d55c: f906e840 str x0, [x2, #3536] ffff00000809d560: cb000023 sub x3, x1, x0 ffff00000809d564: d287ffe2 mov x2, #0x3fff // #16383 ffff00000809d568: eb02007f cmp x3, x2 ffff00000809d56c: 540001a8 b.hi ffff00000809d5a0 <erase_kstack+0xc8> // b.pmore ffff00000809d570: 9297ddc2 mov x2, #0xffffffffffff4111 // #-48879 ffff00000809d574: eb01001f cmp x0, x1 ffff00000809d578: 54000082 b.cs ffff00000809d588 <erase_kstack+0xb0> // b.hs, b.nlast ffff00000809d57c: f8008402 str x2, [x0], #8 ffff00000809d580: eb00003f cmp x1, x0 ffff00000809d584: 54ffffc8 b.hi ffff00000809d57c <erase_kstack+0xa4> // b.pmore ffff00000809d588: 910003e1 mov x1, sp ffff00000809d58c: d5384100 mrs x0, sp_el0 ffff00000809d590: f906e401 str x1, [x0, #3528] ffff00000809d594: a8c17bfd ldp x29, x30, [sp], #16 ffff00000809d598: d65f03c0 ret ffff00000809d59c: d503201f nop ffff00000809d5a0: d4210000 brk #0x800 ffff00000809d5a4: 00000000 .inst 0x00000000 ; undefined
It looks to be okay although admittedly that's subject to compiler whims. It might be safer to save the stack pointer almost as soon as we get into the function and use that?
Quoted 62 lines
arch/arm64/kernel/entry.S
Quoted 11 lines
arch/arm64/kernel/entry.S +11
-0+ + BUG_ON(boundary - p >= THREAD_SIZE); + + while (p < boundary) { + *(unsigned long *)p = STACKLEAK_POISON; + p += sizeof(unsigned long); + } + + /* Reset the lowest_stack value for the next syscall */ + current->thread.lowest_stack = current_stack_pointer; +}Once this function returns, its data is left on the stack. Is that not a problem?
No strong feelings either way, but it might be worth mentioning in the commit message.
arch/arm64/kernel/process.c int copy_thread(unsigned long clone_flags, unsigned long stack_start, · line 368
Quoted 10 lines
arch/arm64/kernel/process.c +2
-0@@ -364,6 +364,9 @@ int copy_thread(unsigned long clone_flags, unsigned long stack_start, p->thread.cpu_context.pc = (unsigned long )ret_from_fork; p->thread.cpu_context.sp = (unsigned long )childregs; +#ifdef CONFIG_GCC_PLUGIN_STACKLEAK + p->thread.lowest_stack = (unsigned long )task_stack_page (p);Nit:
end_of_stack(p)would be slightly better semantically, even though currently equivalent totask_stack_page(p).[...]
arch/arm64/kernel/process.c void arch_setup_new_exec(void) · line 509
Quoted 10 lines
arch/arm64/kernel/process.c +10
-0+#ifdef CONFIG_GCC_PLUGIN_STACKLEAK +void __used check_alloca (unsigned long size) +{ +unsigned long sp, stack_left; + + sp = current_stack_pointer; + + stack_left = sp & (THREAD_SIZE -1 ); +BUG_ON (stack_left <256 || size >= stack_left -256 ); +}Is this arbitrary, or is there something special about 256?
Even if this is arbitrary, can we give it some mnemonic?
Quoted 14 lines
arch/arm64/kernel/process.c +2
-0+EXPORT_SYMBOL (check_alloca); +#endif drivers/firmware/efi/libstub/Makefile +2
-1@@ -20,7 +20,8 @@ cflags-$(CONFIG_EFI_ARMSTUB) += -I$(srctree)/scripts/dtc/libfdt KBUILD_CFLAGS := $(cflags-y) -DDISABLE_BRANCH_PROFILING \ -D__NO_FORTIFY \ $(call cc-option,-ffreestanding) \ - $(call cc-option,-fno-stack-protector) + $(call cc-option,-fno-stack-protector) \ + $(DISABLE_STACKLEAK_PLUGIN)
Quoted 8 lines
drivers/firmware/efi/libstub/Makefile cflags-$(CONFIG_EFI_ARMSTUB) += -I$(srctree)/scripts/dtc/libfdt · line 27
Quoted 2 lines
drivers/firmware/efi/libstub/Makefile +0
-0GCOV_PROFILE := n KASAN_SANITIZE := nI believe we'll also need to do this for the KVM hyp code in
arch/arm64/kvm/hyp/.Thanks,
Mark.
Thanks,
Laura
Mark Rutland
reviewerRe: [PATCH 2/2] arm64: Clear the stack
Quoted 74 lines · Laura Abbott
Quoted 5 lines · Mark Rutland
Quoted 2 lines · Laura Abbott
arch/arm64/kernel/erase.c new file +2
-0+asmlinkage void erase_kstack(void) +{Quoted 10 lines
arch/arm64/kernel/erase.c line 43
Quoted 6 lines
arch/arm64/kernel/erase.c new file +6
-0+ +/* +* So let's write the poison value to the kernel stack. +* Start from the address in p and move up till the new boundary. +*/ + boundary = current_stack_pointer;I worry a little that the compiler can move the SP during a function's lifetime, but maybe that's only the case when there are VLAs, or something like that?
I think that's true and a risk we take writing this in C. Here's the disassembly on gcc-7.3.1:
ffff00000809d4d8<erase_kstack>:ffff00000809d4d8: a9bf7bfd stp x29, x30, [sp, #-16]! ffff00000809d4dc: d5384100 mrs x0, sp_el0 ffff00000809d4e0: 910003fd mov x29, sp ffff00000809d4e4: f946e400 ldr x0, [x0, #3528] ffff00000809d4e8: 9272c404 and x4, x0, #0xffffffffffffc000 ffff00000809d4ec: eb04001f cmp x0, x4 ffff00000809d4f0: 540002c9 b.ls ffff00000809d548 <erase_kstack+0x70> // b.plast ffff00000809d4f4: d2800003 mov x3, #0x0 // #0 ffff00000809d4f8: 9297ddc5 mov x5, #0xffffffffffff4111 // #-48879 ffff00000809d4fc: 14000008 b ffff00000809d51c <erase_kstack+0x44> ffff00000809d500: d1002000 sub x0, x0, #0x8 ffff00000809d504: 52800022 mov w2, #0x1 // #1 ffff00000809d508: eb00009f cmp x4, x0 ffff00000809d50c: d2800003 mov x3, #0x0 // #0 ffff00000809d510: 1a9f27e1 cset w1, cc // cc = lo, ul, last ffff00000809d514: 6a01005f tst w2, w1 ffff00000809d518: 54000180 b.eq ffff00000809d548 <erase_kstack+0x70> // b.none ffff00000809d51c: f9400001 ldr x1, [x0] ffff00000809d520: eb05003f cmp x1, x5 ffff00000809d524: 54fffee1 b.ne ffff00000809d500 <erase_kstack+0x28> // b.any ffff00000809d528: 91000463 add x3, x3, #0x1 ffff00000809d52c: d1002000 sub x0, x0, #0x8 ffff00000809d530: f100407f cmp x3, #0x10 ffff00000809d534: 1a9f87e2 cset w2, ls // ls = plast ffff00000809d538: eb00009f cmp x4, x0 ffff00000809d53c: 1a9f27e1 cset w1, cc // cc = lo, ul, last ffff00000809d540: 6a01005f tst w2, w1 ffff00000809d544: 54fffec1 b.ne ffff00000809d51c <erase_kstack+0x44> // b.any ffff00000809d548: eb00009f cmp x4, x0 ffff00000809d54c: 91002001 add x1, x0, #0x8 ffff00000809d550: 9a800020 csel x0, x1, x0, eq // eq = none ffff00000809d554: 910003e1 mov x1, sp ffff00000809d558: d5384102 mrs x2, sp_el0 ffff00000809d55c: f906e840 str x0, [x2, #3536] ffff00000809d560: cb000023 sub x3, x1, x0 ffff00000809d564: d287ffe2 mov x2, #0x3fff // #16383 ffff00000809d568: eb02007f cmp x3, x2 ffff00000809d56c: 540001a8 b.hi ffff00000809d5a0 <erase_kstack+0xc8> // b.pmore ffff00000809d570: 9297ddc2 mov x2, #0xffffffffffff4111 // #-48879 ffff00000809d574: eb01001f cmp x0, x1 ffff00000809d578: 54000082 b.cs ffff00000809d588 <erase_kstack+0xb0> // b.hs, b.nlast ffff00000809d57c: f8008402 str x2, [x0], #8 ffff00000809d580: eb00003f cmp x1, x0 ffff00000809d584: 54ffffc8 b.hi ffff00000809d57c <erase_kstack+0xa4> // b.pmore ffff00000809d588: 910003e1 mov x1, sp ffff00000809d58c: d5384100 mrs x0, sp_el0 ffff00000809d590: f906e401 str x1, [x0, #3528] ffff00000809d594: a8c17bfd ldp x29, x30, [sp], #16 ffff00000809d598: d65f03c0 ret ffff00000809d59c: d503201f nop ffff00000809d5a0: d4210000 brk #0x800 ffff00000809d5a4: 00000000 .inst 0x00000000 ; undefined
Quoted 3 lines
It looks to be okay although admittedly that's subject to compiler whims. It might be safer to save the stack pointer almost as soon as we get into the function and use that?
I think that's still potentially a problem. If the compiler expands the stack frame after we've taken a snaphot of the stack pointer, we might end up erasing portions of the active stackframe.
Maybe we should just document we rely on the compiler not doing that, and if we end up seeing it in practice we rewrite this in asm? I can't think of a simple way we can auto-detect if this happens. :/
Thanks,
Mark.
Quoted 84 lines · Alexander Popov
This is the 11th version of the patch series introducing STACKLEAK to the mainline kernel. The 9th version raised a fervent discussion[0]. The assembly code introduced by that version irritated the reviewers.
I've found the way to bypass the obstacles[1] of the C implementation. So I dare come again. Let me ask you to look at this code without preconception.
Motivation
==========STACKLEAK (initially developed by PaX Team):
1. reduces the information that can be revealed through kernel stack leak bugs. The idea of erasing the thread stack at the end of syscalls is similar to
CONFIG_PAGE_POISONINGandmemzero_explicit()in kernel crypto, which all comply with FDP_RIP.2 (Full Residual Information Protection) of the Common Criteria standard.2. blocks some uninitialized stack variable attacks (e.g. CVE-2017-17712, CVE-2010-2963). That kind of bugs should be killed by improving C compilers in future, which might take a long time.
3. blocks stack depth overflow caused by alloca (aka Stack Clash attack). That is orthogonal to the mainline kernel VLA cleanup and protects un-upstreamed code.
Performance impact
==================Hardware: Intel Core i7-4770, 16 GB RAM
Test #1: building the Linux kernel on a single core
0.91% slowdownTest #2: hackbench -s 4096 -l 2000 -g 15 -f 25 -P
4.2% slowdownSo the STACKLEAK description in
Kconfigincludes:
"The tradeoff is the performance impact: on a single CPU system kernel compilation sees a 1% slowdown, other systems and workloads may vary and you are advised to test this feature on your expected workload before deploying it".Links
=====[0] http://www.openwall.com/lists/kernel-hardening/2018/03/03/7
[1] http://www.openwall.com/lists/kernel-hardening/2018/03/21/4Alexander Popov (6):
gcc-plugins: Clean up the cgraph_create_edge* macros x86/entry: Add STACKLEAK erasing the kernel stack at the end of
syscalls
gcc-plugins: Add STACKLEAK plugin for tracking the kernel stack
lkdtm: Add a test for STACKLEAK
fs/proc: Show STACKLEAK metrics in the /proc file system
doc: self-protection: Add information about STACKLEAK featureDocumentation/security/self-protection.rst | 23 +- Documentation/x86/x86_64/mm.txt | 2 + arch/Kconfig | 53 ++++ arch/x86/Kconfig | 1 + arch/x86/entry/Makefile | 3 + arch/x86/entry/calling.h | 14 + arch/x86/entry/entry_32.S | 7 + arch/x86/entry/entry_64.S | 3 + arch/x86/entry/entry_64_compat.S | 5 + arch/x86/entry/erase.c | 58 ++++ arch/x86/include/asm/processor.h | 7 + arch/x86/kernel/dumpstack.c | 19 ++ arch/x86/kernel/process_32.c | 8 + arch/x86/kernel/process_64.c | 8 + drivers/misc/Makefile | 3 + drivers/misc/lkdtm.h | 4 + drivers/misc/lkdtm_core.c | 2 + drivers/misc/lkdtm_stackleak.c | 141 +++++++++ fs/proc/base.c | 18 ++ include/linux/compiler.h | 4 + mm/util.c | 33 ++ scripts/Makefile.gcc-plugins | 3 + scripts/gcc-plugins/gcc-common.h | 26 +- scripts/gcc-plugins/stackleak_plugin.c | 470 +++++++++++++++++++++++++++++ 24 files changed, 896 insertions(+), 19 deletions(-)
Quoted 4 lines
create mode 100644
arch/x86/entry/erase.c
create mode 100644drivers/misc/lkdtm_stackleak.ccreate mode 100644scripts/gcc-plugins/stackleak_plugin.c
As a point of reference, this series mitigates the leak
fixed by https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=0a0b98734479aa5b3c671d5190e86273372cab95
Thanks,
Laura