diff options
author | Kees Cook <kees@kernel.org> | 2025-07-17 16:25:06 -0700 |
---|---|---|
committer | Kees Cook <kees@kernel.org> | 2025-07-21 21:35:01 -0700 |
commit | 57fbad15c2eee77276a541c616589b32976d2b8e (patch) | |
tree | 7ef2b8ac0372560cf39665ae44bc67ec34b2bef4 /include/linux/kstack_erase.h | |
parent | fc07839203f3b98fa9afac370aaba283afc10433 (diff) |
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>
Diffstat (limited to 'include/linux/kstack_erase.h')
-rw-r--r-- | include/linux/kstack_erase.h | 89 |
1 files changed, 89 insertions, 0 deletions
diff --git a/include/linux/kstack_erase.h b/include/linux/kstack_erase.h new file mode 100644 index 000000000000..4e432eefa4d0 --- /dev/null +++ b/include/linux/kstack_erase.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_KSTACK_ERASE_H +#define _LINUX_KSTACK_ERASE_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 KSTACK_ERASE_POISON -0xBEEF +#define KSTACK_ERASE_SEARCH_DEPTH 128 + +#ifdef CONFIG_KSTACK_ERASE +#include <asm/stacktrace.h> +#include <linux/linkage.h> + +/* + * The lowest address on tsk's stack which we can plausibly erase. + */ +static __always_inline unsigned long +stackleak_task_low_bound(const struct task_struct *tsk) +{ + /* + * The lowest unsigned long on the task stack contains STACK_END_MAGIC, + * which we must not corrupt. + */ + return (unsigned long)end_of_stack(tsk) + sizeof(unsigned long); +} + +/* + * The address immediately after the highest address on tsk's stack which we + * can plausibly erase. + */ +static __always_inline unsigned long +stackleak_task_high_bound(const struct task_struct *tsk) +{ + /* + * The task's pt_regs lives at the top of the task stack and will be + * overwritten by exception entry, so there's no need to erase them. + */ + return (unsigned long)task_pt_regs(tsk); +} + +/* + * Find the address immediately above the poisoned region of the stack, where + * that region falls between 'low' (inclusive) and 'high' (exclusive). + */ +static __always_inline unsigned long +stackleak_find_top_of_poison(const unsigned long low, const unsigned long high) +{ + const unsigned int depth = KSTACK_ERASE_SEARCH_DEPTH / sizeof(unsigned long); + unsigned int poison_count = 0; + unsigned long poison_high = high; + unsigned long sp = high; + + while (sp > low && poison_count < depth) { + sp -= sizeof(unsigned long); + + if (*(unsigned long *)sp == KSTACK_ERASE_POISON) { + poison_count++; + } else { + poison_count = 0; + poison_high = sp; + } + } + + return poison_high; +} + +static inline void stackleak_task_init(struct task_struct *t) +{ + t->lowest_stack = stackleak_task_low_bound(t); +# ifdef CONFIG_KSTACK_ERASE_METRICS + t->prev_lowest_stack = t->lowest_stack; +# endif +} + +asmlinkage void noinstr stackleak_erase(void); +asmlinkage void noinstr stackleak_erase_on_task_stack(void); +asmlinkage void noinstr stackleak_erase_off_task_stack(void); +void __no_caller_saved_registers noinstr stackleak_track_stack(void); + +#else /* !CONFIG_KSTACK_ERASE */ +static inline void stackleak_task_init(struct task_struct *t) { } +#endif + +#endif |