Merge tag 'pm-6.6-rc1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael...
[platform/kernel/linux-starfive.git] / kernel / stackleak.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * This code fills the used part of the kernel stack with a poison value
4  * before returning to userspace. It's part of the STACKLEAK feature
5  * ported from grsecurity/PaX.
6  *
7  * Author: Alexander Popov <alex.popov@linux.com>
8  *
9  * STACKLEAK reduces the information which kernel stack leak bugs can
10  * reveal and blocks some uninitialized stack variable attacks.
11  */
12
13 #include <linux/stackleak.h>
14 #include <linux/kprobes.h>
15
16 #ifdef CONFIG_STACKLEAK_RUNTIME_DISABLE
17 #include <linux/jump_label.h>
18 #include <linux/sysctl.h>
19 #include <linux/init.h>
20
21 static DEFINE_STATIC_KEY_FALSE(stack_erasing_bypass);
22
23 #ifdef CONFIG_SYSCTL
24 static int stack_erasing_sysctl(struct ctl_table *table, int write,
25                         void __user *buffer, size_t *lenp, loff_t *ppos)
26 {
27         int ret = 0;
28         int state = !static_branch_unlikely(&stack_erasing_bypass);
29         int prev_state = state;
30
31         table->data = &state;
32         table->maxlen = sizeof(int);
33         ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos);
34         state = !!state;
35         if (ret || !write || state == prev_state)
36                 return ret;
37
38         if (state)
39                 static_branch_disable(&stack_erasing_bypass);
40         else
41                 static_branch_enable(&stack_erasing_bypass);
42
43         pr_warn("stackleak: kernel stack erasing is %s\n",
44                                         state ? "enabled" : "disabled");
45         return ret;
46 }
47 static struct ctl_table stackleak_sysctls[] = {
48         {
49                 .procname       = "stack_erasing",
50                 .data           = NULL,
51                 .maxlen         = sizeof(int),
52                 .mode           = 0600,
53                 .proc_handler   = stack_erasing_sysctl,
54                 .extra1         = SYSCTL_ZERO,
55                 .extra2         = SYSCTL_ONE,
56         },
57         {}
58 };
59
60 static int __init stackleak_sysctls_init(void)
61 {
62         register_sysctl_init("kernel", stackleak_sysctls);
63         return 0;
64 }
65 late_initcall(stackleak_sysctls_init);
66 #endif /* CONFIG_SYSCTL */
67
68 #define skip_erasing()  static_branch_unlikely(&stack_erasing_bypass)
69 #else
70 #define skip_erasing()  false
71 #endif /* CONFIG_STACKLEAK_RUNTIME_DISABLE */
72
73 #ifndef __stackleak_poison
74 static __always_inline void __stackleak_poison(unsigned long erase_low,
75                                                unsigned long erase_high,
76                                                unsigned long poison)
77 {
78         while (erase_low < erase_high) {
79                 *(unsigned long *)erase_low = poison;
80                 erase_low += sizeof(unsigned long);
81         }
82 }
83 #endif
84
85 static __always_inline void __stackleak_erase(bool on_task_stack)
86 {
87         const unsigned long task_stack_low = stackleak_task_low_bound(current);
88         const unsigned long task_stack_high = stackleak_task_high_bound(current);
89         unsigned long erase_low, erase_high;
90
91         erase_low = stackleak_find_top_of_poison(task_stack_low,
92                                                  current->lowest_stack);
93
94 #ifdef CONFIG_STACKLEAK_METRICS
95         current->prev_lowest_stack = erase_low;
96 #endif
97
98         /*
99          * Write poison to the task's stack between 'erase_low' and
100          * 'erase_high'.
101          *
102          * If we're running on a different stack (e.g. an entry trampoline
103          * stack) we can erase everything below the pt_regs at the top of the
104          * task stack.
105          *
106          * If we're running on the task stack itself, we must not clobber any
107          * stack used by this function and its caller. We assume that this
108          * function has a fixed-size stack frame, and the current stack pointer
109          * doesn't change while we write poison.
110          */
111         if (on_task_stack)
112                 erase_high = current_stack_pointer;
113         else
114                 erase_high = task_stack_high;
115
116         __stackleak_poison(erase_low, erase_high, STACKLEAK_POISON);
117
118         /* Reset the 'lowest_stack' value for the next syscall */
119         current->lowest_stack = task_stack_high;
120 }
121
122 /*
123  * Erase and poison the portion of the task stack used since the last erase.
124  * Can be called from the task stack or an entry stack when the task stack is
125  * no longer in use.
126  */
127 asmlinkage void noinstr stackleak_erase(void)
128 {
129         if (skip_erasing())
130                 return;
131
132         __stackleak_erase(on_thread_stack());
133 }
134
135 /*
136  * Erase and poison the portion of the task stack used since the last erase.
137  * Can only be called from the task stack.
138  */
139 asmlinkage void noinstr stackleak_erase_on_task_stack(void)
140 {
141         if (skip_erasing())
142                 return;
143
144         __stackleak_erase(true);
145 }
146
147 /*
148  * Erase and poison the portion of the task stack used since the last erase.
149  * Can only be called from a stack other than the task stack.
150  */
151 asmlinkage void noinstr stackleak_erase_off_task_stack(void)
152 {
153         if (skip_erasing())
154                 return;
155
156         __stackleak_erase(false);
157 }
158
159 void __used __no_caller_saved_registers noinstr stackleak_track_stack(void)
160 {
161         unsigned long sp = current_stack_pointer;
162
163         /*
164          * Having CONFIG_STACKLEAK_TRACK_MIN_SIZE larger than
165          * STACKLEAK_SEARCH_DEPTH makes the poison search in
166          * stackleak_erase() unreliable. Let's prevent that.
167          */
168         BUILD_BUG_ON(CONFIG_STACKLEAK_TRACK_MIN_SIZE > STACKLEAK_SEARCH_DEPTH);
169
170         /* 'lowest_stack' should be aligned on the register width boundary */
171         sp = ALIGN(sp, sizeof(unsigned long));
172         if (sp < current->lowest_stack &&
173             sp >= stackleak_task_low_bound(current)) {
174                 current->lowest_stack = sp;
175         }
176 }
177 EXPORT_SYMBOL(stackleak_track_stack);