diff mbox series

[v14,7/7] stackleak: Allow runtime disabling of kernel stack erasing

Message ID 1532603461-21415-1-git-send-email-alex.popov@linux.com (mailing list archive)
State New, archived
Headers show
Series None | expand

Commit Message

Alexander Popov July 26, 2018, 11:11 a.m. UTC
Introduce CONFIG_STACKLEAK_RUNTIME_DISABLE option, which provides
'stack_erasing' sysctl. It can be used in runtime to control kernel
stack erasing for kernels built with CONFIG_GCC_PLUGIN_STACKLEAK.

Suggested-by: Ingo Molnar <mingo@kernel.org>
Signed-off-by: Alexander Popov <alex.popov@linux.com>
---
 Documentation/sysctl/kernel.txt | 18 ++++++++++++++++++
 include/linux/stackleak.h       |  6 ++++++
 kernel/stackleak.c              | 38 ++++++++++++++++++++++++++++++++++++++
 kernel/sysctl.c                 | 15 ++++++++++++++-
 scripts/gcc-plugins/Kconfig     |  8 ++++++++
 5 files changed, 84 insertions(+), 1 deletion(-)

Comments

Kees Cook July 26, 2018, 4:08 p.m. UTC | #1
On Thu, Jul 26, 2018 at 4:11 AM, Alexander Popov <alex.popov@linux.com> wrote:
> Introduce CONFIG_STACKLEAK_RUNTIME_DISABLE option, which provides
> 'stack_erasing' sysctl. It can be used in runtime to control kernel
> stack erasing for kernels built with CONFIG_GCC_PLUGIN_STACKLEAK.
>
> Suggested-by: Ingo Molnar <mingo@kernel.org>
> Signed-off-by: Alexander Popov <alex.popov@linux.com>
> ---
>  Documentation/sysctl/kernel.txt | 18 ++++++++++++++++++
>  include/linux/stackleak.h       |  6 ++++++
>  kernel/stackleak.c              | 38 ++++++++++++++++++++++++++++++++++++++
>  kernel/sysctl.c                 | 15 ++++++++++++++-
>  scripts/gcc-plugins/Kconfig     |  8 ++++++++
>  5 files changed, 84 insertions(+), 1 deletion(-)
>
> diff --git a/Documentation/sysctl/kernel.txt b/Documentation/sysctl/kernel.txt
> index eded671d..1feae79 100644
> --- a/Documentation/sysctl/kernel.txt
> +++ b/Documentation/sysctl/kernel.txt
> @@ -87,6 +87,7 @@ show up in /proc/sys/kernel:
>  - shmmni
>  - softlockup_all_cpu_backtrace
>  - soft_watchdog
> +- stack_erasing

I like the renaming to avoid the double-negative. ("disable bypassing"
is not as clear as "feature enabled or not")

>  - stop-a                      [ SPARC only ]
>  - sysrq                       ==> Documentation/admin-guide/sysrq.rst
>  - sysctl_writes_strict
> @@ -962,6 +963,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
> diff --git a/include/linux/stackleak.h b/include/linux/stackleak.h
> index b911b97..3d5c327 100644
> --- a/include/linux/stackleak.h
> +++ b/include/linux/stackleak.h
> @@ -22,6 +22,12 @@ static inline void stackleak_task_init(struct task_struct *t)
>         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
> diff --git a/kernel/stackleak.c b/kernel/stackleak.c
> index f5c4111..2d21372 100644
> --- a/kernel/stackleak.c
> +++ b/kernel/stackleak.c
> @@ -14,6 +14,41 @@
>
>  #include <linux/stackleak.h>
>
> +#ifdef CONFIG_STACKLEAK_RUNTIME_DISABLE
> +#include <linux/jump_label.h>
> +#include <linux/sysctl.h>
> +
> +static DEFINE_STATIC_KEY_FALSE(stack_erasing_bypass);
> +
> +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");

Looks good to me. I've updated the patch for -next.

> +       return ret;
> +}
> +
> +#define skip_erasing() static_branch_unlikely(&stack_erasing_bypass)
> +#else
> +#define skip_erasing() false
> +#endif /* CONFIG_STACKLEAK_RUNTIME_DISABLE */
> +
>  asmlinkage void stackleak_erase(void)
>  {
>         /* It would be nice not to have 'kstack_ptr' and 'boundary' on stack */
> @@ -22,6 +57,9 @@ asmlinkage void stackleak_erase(void)
>         unsigned int poison_count = 0;
>         const unsigned int depth = STACKLEAK_SEARCH_DEPTH / sizeof(unsigned long);
>
> +       if (skip_erasing())
> +               return;
> +
>         /* Search for the poison value in the kernel stack */
>         while (kstack_ptr > boundary && poison_count <= depth) {
>                 if (*(unsigned long *)kstack_ptr == STACKLEAK_POISON)

Thanks!

-Kees
diff mbox series

Patch

diff --git a/Documentation/sysctl/kernel.txt b/Documentation/sysctl/kernel.txt
index eded671d..1feae79 100644
--- a/Documentation/sysctl/kernel.txt
+++ b/Documentation/sysctl/kernel.txt
@@ -87,6 +87,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
@@ -962,6 +963,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
diff --git a/include/linux/stackleak.h b/include/linux/stackleak.h
index b911b97..3d5c327 100644
--- a/include/linux/stackleak.h
+++ b/include/linux/stackleak.h
@@ -22,6 +22,12 @@  static inline void stackleak_task_init(struct task_struct *t)
 	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
diff --git a/kernel/stackleak.c b/kernel/stackleak.c
index f5c4111..2d21372 100644
--- a/kernel/stackleak.c
+++ b/kernel/stackleak.c
@@ -14,6 +14,41 @@ 
 
 #include <linux/stackleak.h>
 
+#ifdef CONFIG_STACKLEAK_RUNTIME_DISABLE
+#include <linux/jump_label.h>
+#include <linux/sysctl.h>
+
+static DEFINE_STATIC_KEY_FALSE(stack_erasing_bypass);
+
+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;
+}
+
+#define skip_erasing()	static_branch_unlikely(&stack_erasing_bypass)
+#else
+#define skip_erasing()	false
+#endif /* CONFIG_STACKLEAK_RUNTIME_DISABLE */
+
 asmlinkage void stackleak_erase(void)
 {
 	/* It would be nice not to have 'kstack_ptr' and 'boundary' on stack */
@@ -22,6 +57,9 @@  asmlinkage void stackleak_erase(void)
 	unsigned int poison_count = 0;
 	const unsigned int depth = STACKLEAK_SEARCH_DEPTH / sizeof(unsigned long);
 
+	if (skip_erasing())
+		return;
+
 	/* Search for the poison value in the kernel stack */
 	while (kstack_ptr > boundary && poison_count <= depth) {
 		if (*(unsigned long *)kstack_ptr == STACKLEAK_POISON)
diff --git a/kernel/sysctl.c b/kernel/sysctl.c
index 2d9837c..8d7e128 100644
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -91,7 +91,9 @@ 
 #ifdef CONFIG_CHR_DEV_SG
 #include <scsi/sg.h>
 #endif
-
+#ifdef CONFIG_STACKLEAK_RUNTIME_DISABLE
+#include <linux/stackleak.h>
+#endif
 #ifdef CONFIG_LOCKUP_DETECTOR
 #include <linux/nmi.h>
 #endif
@@ -1230,6 +1232,17 @@  static struct ctl_table kern_table[] = {
 		.extra2		= &one,
 	},
 #endif
+#ifdef CONFIG_STACKLEAK_RUNTIME_DISABLE
+	{
+		.procname	= "stack_erasing",
+		.data		= NULL,
+		.maxlen		= sizeof(int),
+		.mode		= 0600,
+		.proc_handler	= stack_erasing_sysctl,
+		.extra1		= &zero,
+		.extra2		= &one,
+	},
+#endif
 	{ }
 };
 
diff --git a/scripts/gcc-plugins/Kconfig b/scripts/gcc-plugins/Kconfig
index 2535b9d..eb358c6 100644
--- a/scripts/gcc-plugins/Kconfig
+++ b/scripts/gcc-plugins/Kconfig
@@ -185,4 +185,12 @@  config STACKLEAK_METRICS
 	  can be useful for estimating the STACKLEAK performance impact for
 	  your workloads.
 
+config STACKLEAK_RUNTIME_DISABLE
+	bool "Allow runtime disabling of kernel stack erasing"
+	depends on GCC_PLUGIN_STACKLEAK
+	help
+	  This option provides 'stack_erasing' sysctl, which can be used in
+	  runtime to control kernel stack erasing for kernels built with
+	  CONFIG_GCC_PLUGIN_STACKLEAK.
+
 endif