Skip to content

Commit

Permalink
Merge tag 'stackleak-v4.20-rc1' of git://git.kernel.org/pub/scm/linux…
Browse files Browse the repository at this point in the history
…/kernel/git/kees/linux

Pull stackleak gcc plugin from Kees Cook:
 "Please pull this new GCC plugin, stackleak, for v4.20-rc1. This plugin
  was ported from grsecurity by Alexander Popov. It provides efficient
  stack content poisoning at syscall exit. This creates a defense
  against at least two classes of flaws:

   - Uninitialized stack usage. (We continue to work on improving the
     compiler to do this in other ways: e.g. unconditional zero init was
     proposed to GCC and Clang, and more plugin work has started too).

   - Stack content exposure. By greatly reducing the lifetime of valid
     stack contents, exposures via either direct read bugs or unknown
     cache side-channels become much more difficult to exploit. This
     complements the existing buddy and heap poisoning options, but
     provides the coverage for stacks.

  The x86 hooks are included in this series (which have been reviewed by
  Ingo, Dave Hansen, and Thomas Gleixner). The arm64 hooks have already
  been merged through the arm64 tree (written by Laura Abbott and
  reviewed by Mark Rutland and Will Deacon).

  With VLAs having been removed this release, there is no need for
  alloca() protection, so it has been removed from the plugin"

* tag 'stackleak-v4.20-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux:
  arm64: Drop unneeded stackleak_check_alloca()
  stackleak: Allow runtime disabling of kernel stack erasing
  doc: self-protection: Add information about STACKLEAK feature
  fs/proc: Show STACKLEAK metrics in the /proc file system
  lkdtm: Add a test for STACKLEAK
  gcc-plugins: Add STACKLEAK plugin for tracking the kernel stack
  x86/entry: Add STACKLEAK erasing the kernel stack at the end of syscalls
  • Loading branch information
torvalds committed Nov 1, 2018
2 parents 7c6c54b + 6fcde90 commit 2d6bb6a
Show file tree
Hide file tree
Showing 24 changed files with 841 additions and 28 deletions.
10 changes: 5 additions & 5 deletions Documentation/security/self-protection.rst
Original file line number Diff line number Diff line change
Expand Up @@ -302,11 +302,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
--------------------
Expand Down
18 changes: 18 additions & 0 deletions Documentation/sysctl/kernel.txt
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ show up in /proc/sys/kernel:
- shmmni
- softlockup_all_cpu_backtrace
- soft_watchdog
- stack_erasing
- stop-a [ SPARC only ]
- sysrq ==> Documentation/admin-guide/sysrq.rst
- sysctl_writes_strict
Expand Down Expand Up @@ -987,6 +988,23 @@ detect a hard lockup condition.

==============================================================

stack_erasing

This parameter can be used to control kernel stack erasing at the end
of syscalls for kernels built with CONFIG_GCC_PLUGIN_STACKLEAK.

That erasing reduces the information which kernel stack leak bugs
can reveal and blocks some uninitialized stack variable attacks.
The tradeoff is the performance impact: on a single CPU system kernel
compilation sees a 1% slowdown, other systems and workloads may vary.

0: kernel stack erasing is disabled, STACKLEAK_METRICS are not updated.

1: kernel stack erasing is enabled (default), it is performed before
returning to the userspace at the end of syscalls.

==============================================================

tainted:

Non-zero if the kernel has been tainted. Numeric values, which can be
Expand Down
3 changes: 3 additions & 0 deletions Documentation/x86/x86_64/mm.txt
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,6 @@ Their order is preserved but their base will be offset early at boot time.
Be very careful vs. KASLR when changing anything here. The KASLR address
range must not overlap with anything except the KASAN shadow area, which is
correct as KASAN disables KASLR.

For both 4- and 5-level layouts, the STACKLEAK_POISON value in the last 2MB
hole: ffffffffffff4111
7 changes: 7 additions & 0 deletions arch/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,13 @@ config SECCOMP_FILTER

See Documentation/userspace-api/seccomp_filter.rst 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_STACKPROTECTOR
bool
help
Expand Down
22 changes: 0 additions & 22 deletions arch/arm64/kernel/process.c
Original file line number Diff line number Diff line change
Expand Up @@ -497,25 +497,3 @@ void arch_setup_new_exec(void)
{
current->mm->context.flags = is_compat_task() ? MMCF_AARCH32 : 0;
}

#ifdef CONFIG_GCC_PLUGIN_STACKLEAK
void __used stackleak_check_alloca(unsigned long size)
{
unsigned long stack_left;
unsigned long current_sp = current_stack_pointer;
struct stack_info info;

BUG_ON(!on_accessible_stack(current, current_sp, &info));

stack_left = current_sp - info.low;

/*
* There's a good chance we're almost out of stack space if this
* is true. Using panic() over BUG() is more likely to give
* reliable debugging output.
*/
if (size >= stack_left)
panic("alloca() over the kernel stack boundary\n");
}
EXPORT_SYMBOL(stackleak_check_alloca);
#endif
1 change: 1 addition & 0 deletions arch/x86/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ config X86
select HAVE_ARCH_PREL32_RELOCATIONS
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
Expand Down
14 changes: 14 additions & 0 deletions arch/x86/entry/calling.h
Original file line number Diff line number Diff line change
Expand Up @@ -329,8 +329,22 @@ For 32-bit we have the following conventions - kernel is built with

#endif

.macro STACKLEAK_ERASE_NOCLOBBER
#ifdef CONFIG_GCC_PLUGIN_STACKLEAK
PUSH_AND_CLEAR_REGS
call stackleak_erase
POP_REGS
#endif
.endm

#endif /* CONFIG_X86_64 */

.macro STACKLEAK_ERASE
#ifdef CONFIG_GCC_PLUGIN_STACKLEAK
call stackleak_erase
#endif
.endm

/*
* This does 'call enter_from_user_mode' unless we can avoid it based on
* kernel config or using the static jump infrastructure.
Expand Down
7 changes: 7 additions & 0 deletions arch/x86/entry/entry_32.S
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
#include <asm/frame.h>
#include <asm/nospec-branch.h>

#include "calling.h"

.section .entry.text, "ax"

/*
Expand Down Expand Up @@ -712,6 +714,7 @@ ENTRY(ret_from_fork)
/* When we fork, we trace the syscall return in the child, too. */
movl %esp, %eax
call syscall_return_slowpath
STACKLEAK_ERASE
jmp restore_all

/* kernel thread */
Expand Down Expand Up @@ -886,6 +889,8 @@ ENTRY(entry_SYSENTER_32)
ALTERNATIVE "testl %eax, %eax; jz .Lsyscall_32_done", \
"jmp .Lsyscall_32_done", X86_FEATURE_XENPV

STACKLEAK_ERASE

/* Opportunistic SYSEXIT */
TRACE_IRQS_ON /* User mode traces as IRQs on. */

Expand Down Expand Up @@ -997,6 +1002,8 @@ ENTRY(entry_INT80_32)
call do_int80_syscall_32
.Lsyscall_32_done:

STACKLEAK_ERASE

restore_all:
TRACE_IRQS_IRET
SWITCH_TO_ENTRY_STACK
Expand Down
3 changes: 3 additions & 0 deletions arch/x86/entry/entry_64.S
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,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.
*/
STACKLEAK_ERASE_NOCLOBBER

SWITCH_TO_USER_CR3_STACK scratch_reg=%rdi

popq %rdi
Expand Down Expand Up @@ -625,6 +627,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.
*/
STACKLEAK_ERASE_NOCLOBBER

SWITCH_TO_USER_CR3_STACK scratch_reg=%rdi

Expand Down
5 changes: 5 additions & 0 deletions arch/x86/entry/entry_64_compat.S
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,11 @@ GLOBAL(entry_SYSCALL_compat_after_hwframe)

/* Opportunistic SYSRET */
sysret32_from_system_call:
/*
* We are not going to return to userspace from the trampoline
* stack. So let's erase the thread stack right now.
*/
STACKLEAK_ERASE
TRACE_IRQS_ON /* User mode traces as IRQs on. */
movq RBX(%rsp), %rbx /* pt_regs->rbx */
movq RBP(%rsp), %rbp /* pt_regs->rbp */
Expand Down
2 changes: 2 additions & 0 deletions drivers/misc/lkdtm/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ lkdtm-$(CONFIG_LKDTM) += perms.o
lkdtm-$(CONFIG_LKDTM) += refcount.o
lkdtm-$(CONFIG_LKDTM) += rodata_objcopy.o
lkdtm-$(CONFIG_LKDTM) += usercopy.o
lkdtm-$(CONFIG_LKDTM) += stackleak.o

KASAN_SANITIZE_stackleak.o := n
KCOV_INSTRUMENT_rodata.o := n

OBJCOPYFLAGS :=
Expand Down
1 change: 1 addition & 0 deletions drivers/misc/lkdtm/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ static const struct crashtype crashtypes[] = {
CRASHTYPE(USERCOPY_STACK_BEYOND),
CRASHTYPE(USERCOPY_KERNEL),
CRASHTYPE(USERCOPY_KERNEL_DS),
CRASHTYPE(STACKLEAK_ERASING),
};


Expand Down
3 changes: 3 additions & 0 deletions drivers/misc/lkdtm/lkdtm.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,7 @@ void lkdtm_USERCOPY_STACK_BEYOND(void);
void lkdtm_USERCOPY_KERNEL(void);
void lkdtm_USERCOPY_KERNEL_DS(void);

/* lkdtm_stackleak.c */
void lkdtm_STACKLEAK_ERASING(void);

#endif
73 changes: 73 additions & 0 deletions drivers/misc/lkdtm/stackleak.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: GPL-2.0
/*
* This code tests that the current task stack is properly erased (filled
* with STACKLEAK_POISON).
*
* Authors:
* Alexander Popov <[email protected]>
* Tycho Andersen <[email protected]>
*/

#include "lkdtm.h"
#include <linux/stackleak.h>

void lkdtm_STACKLEAK_ERASING(void)
{
unsigned long *sp, left, found, i;
const unsigned long check_depth =
STACKLEAK_SEARCH_DEPTH / sizeof(unsigned long);

/*
* For the details about the alignment of the poison values, see
* the comment in stackleak_track_stack().
*/
sp = PTR_ALIGN(&i, sizeof(unsigned long));

left = ((unsigned long)sp & (THREAD_SIZE - 1)) / sizeof(unsigned long);
sp--;

/*
* One 'long int' at the bottom of the thread stack is reserved
* and not poisoned.
*/
if (left > 1) {
left--;
} else {
pr_err("FAIL: not enough stack space for the test\n");
return;
}

pr_info("checking unused part of the thread stack (%lu bytes)...\n",
left * sizeof(unsigned long));

/*
* Search for 'check_depth' poison values in a row (just like
* stackleak_erase() does).
*/
for (i = 0, found = 0; i < left && found <= check_depth; i++) {
if (*(sp - i) == STACKLEAK_POISON)
found++;
else
found = 0;
}

if (found <= check_depth) {
pr_err("FAIL: thread stack is not erased (checked %lu bytes)\n",
i * sizeof(unsigned long));
return;
}

pr_info("first %lu bytes are unpoisoned\n",
(i - found) * sizeof(unsigned long));

/* The rest of thread stack should be erased */
for (; i < left; i++) {
if (*(sp - i) != STACKLEAK_POISON) {
pr_err("FAIL: thread stack is NOT properly erased\n");
return;
}
}

pr_info("OK: the rest of the thread stack is properly erased\n");
return;
}
18 changes: 18 additions & 0 deletions fs/proc/base.c
Original file line number Diff line number Diff line change
Expand Up @@ -2905,6 +2905,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->prev_lowest_stack & (THREAD_SIZE - 1));
unsigned long depth = THREAD_SIZE -
(task->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
*/
Expand Down Expand Up @@ -3006,6 +3021,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)
Expand Down
5 changes: 5 additions & 0 deletions include/linux/sched.h
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,11 @@ struct task_struct {
void *security;
#endif

#ifdef CONFIG_GCC_PLUGIN_STACKLEAK
unsigned long lowest_stack;
unsigned long prev_lowest_stack;
#endif

/*
* New fields for task_struct should be added above here, so that
* they are included in the randomized portion of task_struct.
Expand Down
35 changes: 35 additions & 0 deletions include/linux/stackleak.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _LINUX_STACKLEAK_H
#define _LINUX_STACKLEAK_H

#include <linux/sched.h>
#include <linux/sched/task_stack.h>

/*
* Check that the poison value points to the unused hole in the
* virtual memory map for your platform.
*/
#define STACKLEAK_POISON -0xBEEF
#define STACKLEAK_SEARCH_DEPTH 128

#ifdef CONFIG_GCC_PLUGIN_STACKLEAK
#include <asm/stacktrace.h>

static inline void stackleak_task_init(struct task_struct *t)
{
t->lowest_stack = (unsigned long)end_of_stack(t) + sizeof(unsigned long);
# ifdef CONFIG_STACKLEAK_METRICS
t->prev_lowest_stack = t->lowest_stack;
# endif
}

#ifdef CONFIG_STACKLEAK_RUNTIME_DISABLE
int stack_erasing_sysctl(struct ctl_table *table, int write,
void __user *buffer, size_t *lenp, loff_t *ppos);
#endif

#else /* !CONFIG_GCC_PLUGIN_STACKLEAK */
static inline void stackleak_task_init(struct task_struct *t) { }
#endif

#endif
4 changes: 4 additions & 0 deletions kernel/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ obj-$(CONFIG_HAS_IOMEM) += iomem.o
obj-$(CONFIG_ZONE_DEVICE) += memremap.o
obj-$(CONFIG_RSEQ) += rseq.o

obj-$(CONFIG_GCC_PLUGIN_STACKLEAK) += stackleak.o
KASAN_SANITIZE_stackleak.o := n
KCOV_INSTRUMENT_stackleak.o := n

$(obj)/configs.o: $(obj)/config_data.h

targets += config_data.gz
Expand Down
3 changes: 3 additions & 0 deletions kernel/fork.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
#include <linux/kcov.h>
#include <linux/livepatch.h>
#include <linux/thread_info.h>
#include <linux/stackleak.h>

#include <asm/pgtable.h>
#include <asm/pgalloc.h>
Expand Down Expand Up @@ -1926,6 +1927,8 @@ static __latent_entropy struct task_struct *copy_process(
if (retval)
goto bad_fork_cleanup_io;

stackleak_task_init(p);

if (pid != &init_struct_pid) {
pid = alloc_pid(p->nsproxy->pid_ns_for_children);
if (IS_ERR(pid)) {
Expand Down
Loading

0 comments on commit 2d6bb6a

Please sign in to comment.