Files
linux/kernel/kstack_erase.c
Kees Cook 57fbad15c2 stackleak: Rename STACKLEAK to KSTACK_ERASE
In preparation for adding Clang sanitizer coverage stack depth tracking
that can support stack depth callbacks:

- Add the new top-level CONFIG_KSTACK_ERASE option which will be
  implemented either with the stackleak GCC plugin, or with the Clang
  stack depth callback support.
- Rename CONFIG_GCC_PLUGIN_STACKLEAK as needed to CONFIG_KSTACK_ERASE,
  but keep it for anything specific to the GCC plugin itself.
- Rename all exposed "STACKLEAK" names and files to "KSTACK_ERASE" (named
  for what it does rather than what it protects against), but leave as
  many of the internals alone as possible to avoid even more churn.

While here, also split "prev_lowest_stack" into CONFIG_KSTACK_ERASE_METRICS,
since that's the only place it is referenced from.

Suggested-by: Ingo Molnar <mingo@kernel.org>
Link: https://lore.kernel.org/r/20250717232519.2984886-1-kees@kernel.org
Signed-off-by: Kees Cook <kees@kernel.org>
2025-07-21 21:35:01 -07:00

178 lines
4.8 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* This code fills the used part of the kernel stack with a poison value
* before returning to userspace. It's part of the STACKLEAK feature
* ported from grsecurity/PaX.
*
* Author: Alexander Popov <alex.popov@linux.com>
*
* KSTACK_ERASE reduces the information which kernel stack leak bugs can
* reveal and blocks some uninitialized stack variable attacks.
*/
#include <linux/kstack_erase.h>
#include <linux/kprobes.h>
#ifdef CONFIG_KSTACK_ERASE_RUNTIME_DISABLE
#include <linux/jump_label.h>
#include <linux/string_choices.h>
#include <linux/sysctl.h>
#include <linux/init.h>
static DEFINE_STATIC_KEY_FALSE(stack_erasing_bypass);
#ifdef CONFIG_SYSCTL
static int stack_erasing_sysctl(const struct ctl_table *table, int write,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
int ret = 0;
int state = !static_branch_unlikely(&stack_erasing_bypass);
int prev_state = state;
struct ctl_table table_copy = *table;
table_copy.data = &state;
ret = proc_dointvec_minmax(&table_copy, write, buffer, lenp, ppos);
state = !!state;
if (ret || !write || state == prev_state)
return ret;
if (state)
static_branch_disable(&stack_erasing_bypass);
else
static_branch_enable(&stack_erasing_bypass);
pr_warn("stackleak: kernel stack erasing is %s\n",
str_enabled_disabled(state));
return ret;
}
static const struct ctl_table stackleak_sysctls[] = {
{
.procname = "stack_erasing",
.data = NULL,
.maxlen = sizeof(int),
.mode = 0600,
.proc_handler = stack_erasing_sysctl,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_ONE,
},
};
static int __init stackleak_sysctls_init(void)
{
register_sysctl_init("kernel", stackleak_sysctls);
return 0;
}
late_initcall(stackleak_sysctls_init);
#endif /* CONFIG_SYSCTL */
#define skip_erasing() static_branch_unlikely(&stack_erasing_bypass)
#else
#define skip_erasing() false
#endif /* CONFIG_KSTACK_ERASE_RUNTIME_DISABLE */
#ifndef __stackleak_poison
static __always_inline void __stackleak_poison(unsigned long erase_low,
unsigned long erase_high,
unsigned long poison)
{
while (erase_low < erase_high) {
*(unsigned long *)erase_low = poison;
erase_low += sizeof(unsigned long);
}
}
#endif
static __always_inline void __stackleak_erase(bool on_task_stack)
{
const unsigned long task_stack_low = stackleak_task_low_bound(current);
const unsigned long task_stack_high = stackleak_task_high_bound(current);
unsigned long erase_low, erase_high;
erase_low = stackleak_find_top_of_poison(task_stack_low,
current->lowest_stack);
#ifdef CONFIG_KSTACK_ERASE_METRICS
current->prev_lowest_stack = erase_low;
#endif
/*
* Write poison to the task's stack between 'erase_low' and
* 'erase_high'.
*
* If we're running on a different stack (e.g. an entry trampoline
* stack) we can erase everything below the pt_regs at the top of the
* task stack.
*
* If we're running on the task stack itself, we must not clobber any
* stack used by this function and its caller. We assume that this
* function has a fixed-size stack frame, and the current stack pointer
* doesn't change while we write poison.
*/
if (on_task_stack)
erase_high = current_stack_pointer;
else
erase_high = task_stack_high;
__stackleak_poison(erase_low, erase_high, KSTACK_ERASE_POISON);
/* Reset the 'lowest_stack' value for the next syscall */
current->lowest_stack = task_stack_high;
}
/*
* Erase and poison the portion of the task stack used since the last erase.
* Can be called from the task stack or an entry stack when the task stack is
* no longer in use.
*/
asmlinkage void noinstr stackleak_erase(void)
{
if (skip_erasing())
return;
__stackleak_erase(on_thread_stack());
}
/*
* Erase and poison the portion of the task stack used since the last erase.
* Can only be called from the task stack.
*/
asmlinkage void noinstr stackleak_erase_on_task_stack(void)
{
if (skip_erasing())
return;
__stackleak_erase(true);
}
/*
* Erase and poison the portion of the task stack used since the last erase.
* Can only be called from a stack other than the task stack.
*/
asmlinkage void noinstr stackleak_erase_off_task_stack(void)
{
if (skip_erasing())
return;
__stackleak_erase(false);
}
void __used __no_caller_saved_registers noinstr stackleak_track_stack(void)
{
unsigned long sp = current_stack_pointer;
/*
* Having CONFIG_KSTACK_ERASE_TRACK_MIN_SIZE larger than
* KSTACK_ERASE_SEARCH_DEPTH makes the poison search in
* stackleak_erase() unreliable. Let's prevent that.
*/
BUILD_BUG_ON(CONFIG_KSTACK_ERASE_TRACK_MIN_SIZE > KSTACK_ERASE_SEARCH_DEPTH);
/* 'lowest_stack' should be aligned on the register width boundary */
sp = ALIGN(sp, sizeof(unsigned long));
if (sp < current->lowest_stack &&
sp >= stackleak_task_low_bound(current)) {
current->lowest_stack = sp;
}
}
EXPORT_SYMBOL(stackleak_track_stack);