mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-10-31 16:38:12 +00:00
77cf2b6dee
Currently we over-estimate the region of stack which must be erased. To determine the region to be erased, we scan downwards for a contiguous block of poison values (or the low bound of the stack). There are a few minor problems with this today: * When we find a block of poison values, we include this block within the region to erase. As this is included within the region to erase, this causes us to redundantly overwrite 'STACKLEAK_SEARCH_DEPTH' (128) bytes with poison. * As the loop condition checks 'poison_count <= depth', it will run an additional iteration after finding the contiguous block of poison, decrementing 'erase_low' once more than necessary. As this is included within the region to erase, this causes us to redundantly overwrite an additional unsigned long with poison. * As we always decrement 'erase_low' after checking an element on the stack, we always include the element below this within the region to erase. As this is included within the region to erase, this causes us to redundantly overwrite an additional unsigned long with poison. Note that this is not a functional problem. As the loop condition checks 'erase_low > task_stack_low', we'll never clobber the STACK_END_MAGIC. As we always decrement 'erase_low' after this, we'll never fail to erase the element immediately above the STACK_END_MAGIC. In total, this can cause us to erase `128 + 2 * sizeof(unsigned long)` bytes more than necessary, which is unfortunate. This patch reworks the logic to find the address immediately above the poisoned region, by finding the lowest non-poisoned address. This is factored into a stackleak_find_top_of_poison() helper both for clarity and so that this can be shared with the LKDTM test in subsequent patches. Signed-off-by: Mark Rutland <mark.rutland@arm.com> Cc: Alexander Popov <alex.popov@linux.com> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Andy Lutomirski <luto@kernel.org> Cc: Kees Cook <keescook@chromium.org> Signed-off-by: Kees Cook <keescook@chromium.org> Link: https://lore.kernel.org/r/20220427173128.2603085-8-mark.rutland@arm.com
83 lines
2.1 KiB
C
83 lines
2.1 KiB
C
/* 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>
|
|
|
|
/*
|
|
* 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 = STACKLEAK_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 == STACKLEAK_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_STACKLEAK_METRICS
|
|
t->prev_lowest_stack = t->lowest_stack;
|
|
# endif
|
|
}
|
|
|
|
#else /* !CONFIG_GCC_PLUGIN_STACKLEAK */
|
|
static inline void stackleak_task_init(struct task_struct *t) { }
|
|
#endif
|
|
|
|
#endif
|