forked from Minki/linux
0df8bdd5e3
kernel/sysctl.c is a kitchen sink where everyone leaves their dirty dishes, this makes it very difficult to maintain. To help with this maintenance let's start by moving sysctls to places where they actually belong. The proc sysctl maintainers do not want to know what sysctl knobs you wish to add for your own piece of code, we just care about the core logic. So move the stack_erasing sysctl from kernel/sysctl.c to kernel/stackleak.c and use register_sysctl() to register the sysctl interface. [mcgrof@kernel.org: commit log update] Link: https://lkml.kernel.org/r/20211124231435.1445213-8-mcgrof@kernel.org Signed-off-by: Xiaoming Ni <nixiaoming@huawei.com> Signed-off-by: Luis Chamberlain <mcgrof@kernel.org> Cc: Al Viro <viro@zeniv.linux.org.uk> Cc: Amir Goldstein <amir73il@gmail.com> Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Cc: Antti Palosaari <crope@iki.fi> Cc: Arnd Bergmann <arnd@arndb.de> Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org> Cc: Benjamin LaHaise <bcrl@kvack.org> Cc: Clemens Ladisch <clemens@ladisch.de> Cc: David Airlie <airlied@linux.ie> Cc: Douglas Gilbert <dgilbert@interlog.com> Cc: Eric Biederman <ebiederm@xmission.com> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Cc: Iurii Zaikin <yzaikin@google.com> Cc: James E.J. Bottomley <jejb@linux.ibm.com> Cc: Jani Nikula <jani.nikula@intel.com> Cc: Jani Nikula <jani.nikula@linux.intel.com> Cc: Jan Kara <jack@suse.cz> Cc: Joel Becker <jlbec@evilplan.org> Cc: John Ogness <john.ogness@linutronix.de> Cc: Joonas Lahtinen <joonas.lahtinen@linux.intel.com> Cc: Joseph Qi <joseph.qi@linux.alibaba.com> Cc: Julia Lawall <julia.lawall@inria.fr> Cc: Kees Cook <keescook@chromium.org> Cc: Lukas Middendorf <kernel@tuxforce.de> Cc: Mark Fasheh <mark@fasheh.com> Cc: Martin K. Petersen <martin.petersen@oracle.com> Cc: Paul Turner <pjt@google.com> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Petr Mladek <pmladek@suse.com> Cc: Phillip Potter <phil@philpotter.co.uk> Cc: Qing Wang <wangqing@vivo.com> Cc: "Rafael J. Wysocki" <rafael@kernel.org> Cc: Rodrigo Vivi <rodrigo.vivi@intel.com> Cc: Sebastian Reichel <sre@kernel.org> Cc: Sergey Senozhatsky <senozhatsky@chromium.org> Cc: Stephen Kitt <steve@sk2.org> Cc: Steven Rostedt (VMware) <rostedt@goodmis.org> Cc: Suren Baghdasaryan <surenb@google.com> Cc: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp> Cc: "Theodore Ts'o" <tytso@mit.edu> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
149 lines
4.1 KiB
C
149 lines
4.1 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>
|
|
*
|
|
* STACKLEAK reduces the information which kernel stack leak bugs can
|
|
* reveal and blocks some uninitialized stack variable attacks.
|
|
*/
|
|
|
|
#include <linux/stackleak.h>
|
|
#include <linux/kprobes.h>
|
|
|
|
#ifdef CONFIG_STACKLEAK_RUNTIME_DISABLE
|
|
#include <linux/jump_label.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(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;
|
|
|
|
table->data = &state;
|
|
table->maxlen = sizeof(int);
|
|
ret = proc_dointvec_minmax(table, 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",
|
|
state ? "enabled" : "disabled");
|
|
return ret;
|
|
}
|
|
static 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_STACKLEAK_RUNTIME_DISABLE */
|
|
|
|
asmlinkage void notrace stackleak_erase(void)
|
|
{
|
|
/* It would be nice not to have 'kstack_ptr' and 'boundary' on stack */
|
|
unsigned long kstack_ptr = current->lowest_stack;
|
|
unsigned long boundary = (unsigned long)end_of_stack(current);
|
|
unsigned int poison_count = 0;
|
|
const unsigned int depth = STACKLEAK_SEARCH_DEPTH / sizeof(unsigned long);
|
|
|
|
if (skip_erasing())
|
|
return;
|
|
|
|
/* Check that 'lowest_stack' value is sane */
|
|
if (unlikely(kstack_ptr - boundary >= THREAD_SIZE))
|
|
kstack_ptr = boundary;
|
|
|
|
/* Search for the poison value in the kernel stack */
|
|
while (kstack_ptr > boundary && poison_count <= depth) {
|
|
if (*(unsigned long *)kstack_ptr == STACKLEAK_POISON)
|
|
poison_count++;
|
|
else
|
|
poison_count = 0;
|
|
|
|
kstack_ptr -= 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=y).
|
|
*/
|
|
if (kstack_ptr == boundary)
|
|
kstack_ptr += sizeof(unsigned long);
|
|
|
|
#ifdef CONFIG_STACKLEAK_METRICS
|
|
current->prev_lowest_stack = kstack_ptr;
|
|
#endif
|
|
|
|
/*
|
|
* Now write the poison value to the kernel stack. Start from
|
|
* 'kstack_ptr' and move up till the new 'boundary'. We assume that
|
|
* the stack pointer doesn't change when we write poison.
|
|
*/
|
|
if (on_thread_stack())
|
|
boundary = current_stack_pointer;
|
|
else
|
|
boundary = current_top_of_stack();
|
|
|
|
while (kstack_ptr < boundary) {
|
|
*(unsigned long *)kstack_ptr = STACKLEAK_POISON;
|
|
kstack_ptr += sizeof(unsigned long);
|
|
}
|
|
|
|
/* Reset the 'lowest_stack' value for the next syscall */
|
|
current->lowest_stack = current_top_of_stack() - THREAD_SIZE/64;
|
|
}
|
|
NOKPROBE_SYMBOL(stackleak_erase);
|
|
|
|
void __used __no_caller_saved_registers notrace stackleak_track_stack(void)
|
|
{
|
|
unsigned long sp = current_stack_pointer;
|
|
|
|
/*
|
|
* Having CONFIG_STACKLEAK_TRACK_MIN_SIZE larger than
|
|
* STACKLEAK_SEARCH_DEPTH makes the poison search in
|
|
* stackleak_erase() unreliable. Let's prevent that.
|
|
*/
|
|
BUILD_BUG_ON(CONFIG_STACKLEAK_TRACK_MIN_SIZE > STACKLEAK_SEARCH_DEPTH);
|
|
|
|
/* 'lowest_stack' should be aligned on the register width boundary */
|
|
sp = ALIGN(sp, sizeof(unsigned long));
|
|
if (sp < current->lowest_stack &&
|
|
sp >= (unsigned long)task_stack_page(current) +
|
|
sizeof(unsigned long)) {
|
|
current->lowest_stack = sp;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(stackleak_track_stack);
|