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 20 static DEFINE_STATIC_KEY_FALSE(stack_erasing_bypass); 21 22 int stack_erasing_sysctl(struct ctl_table *table, int write, 23 void __user *buffer, size_t *lenp, loff_t *ppos) 24 { 25 int ret = 0; 26 int state = !static_branch_unlikely(&stack_erasing_bypass); 27 int prev_state = state; 28 29 table->data = &state; 30 table->maxlen = sizeof(int); 31 ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos); 32 state = !!state; 33 if (ret || !write || state == prev_state) 34 return ret; 35 36 if (state) 37 static_branch_disable(&stack_erasing_bypass); 38 else 39 static_branch_enable(&stack_erasing_bypass); 40 41 pr_warn("stackleak: kernel stack erasing is %s\n", 42 state ? "enabled" : "disabled"); 43 return ret; 44 } 45 46 #define skip_erasing() static_branch_unlikely(&stack_erasing_bypass) 47 #else 48 #define skip_erasing() false 49 #endif /* CONFIG_STACKLEAK_RUNTIME_DISABLE */ 50 51 asmlinkage void notrace stackleak_erase(void) 52 { 53 /* It would be nice not to have 'kstack_ptr' and 'boundary' on stack */ 54 unsigned long kstack_ptr = current->lowest_stack; 55 unsigned long boundary = (unsigned long)end_of_stack(current); 56 unsigned int poison_count = 0; 57 const unsigned int depth = STACKLEAK_SEARCH_DEPTH / sizeof(unsigned long); 58 59 if (skip_erasing()) 60 return; 61 62 /* Check that 'lowest_stack' value is sane */ 63 if (unlikely(kstack_ptr - boundary >= THREAD_SIZE)) 64 kstack_ptr = boundary; 65 66 /* Search for the poison value in the kernel stack */ 67 while (kstack_ptr > boundary && poison_count <= depth) { 68 if (*(unsigned long *)kstack_ptr == STACKLEAK_POISON) 69 poison_count++; 70 else 71 poison_count = 0; 72 73 kstack_ptr -= sizeof(unsigned long); 74 } 75 76 /* 77 * One 'long int' at the bottom of the thread stack is reserved and 78 * should not be poisoned (see CONFIG_SCHED_STACK_END_CHECK=y). 79 */ 80 if (kstack_ptr == boundary) 81 kstack_ptr += sizeof(unsigned long); 82 83 #ifdef CONFIG_STACKLEAK_METRICS 84 current->prev_lowest_stack = kstack_ptr; 85 #endif 86 87 /* 88 * Now write the poison value to the kernel stack. Start from 89 * 'kstack_ptr' and move up till the new 'boundary'. We assume that 90 * the stack pointer doesn't change when we write poison. 91 */ 92 if (on_thread_stack()) 93 boundary = current_stack_pointer; 94 else 95 boundary = current_top_of_stack(); 96 97 while (kstack_ptr < boundary) { 98 *(unsigned long *)kstack_ptr = STACKLEAK_POISON; 99 kstack_ptr += sizeof(unsigned long); 100 } 101 102 /* Reset the 'lowest_stack' value for the next syscall */ 103 current->lowest_stack = current_top_of_stack() - THREAD_SIZE/64; 104 } 105 NOKPROBE_SYMBOL(stackleak_erase); 106 107 void __used notrace stackleak_track_stack(void) 108 { 109 /* 110 * N.B. stackleak_erase() fills the kernel stack with the poison value, 111 * which has the register width. That code assumes that the value 112 * of 'lowest_stack' is aligned on the register width boundary. 113 * 114 * That is true for x86 and x86_64 because of the kernel stack 115 * alignment on these platforms (for details, see 'cc_stack_align' in 116 * arch/x86/Makefile). Take care of that when you port STACKLEAK to 117 * new platforms. 118 */ 119 unsigned long sp = (unsigned long)&sp; 120 121 /* 122 * Having CONFIG_STACKLEAK_TRACK_MIN_SIZE larger than 123 * STACKLEAK_SEARCH_DEPTH makes the poison search in 124 * stackleak_erase() unreliable. Let's prevent that. 125 */ 126 BUILD_BUG_ON(CONFIG_STACKLEAK_TRACK_MIN_SIZE > STACKLEAK_SEARCH_DEPTH); 127 128 if (sp < current->lowest_stack && 129 sp >= (unsigned long)task_stack_page(current) + 130 sizeof(unsigned long)) { 131 current->lowest_stack = sp; 132 } 133 } 134 EXPORT_SYMBOL(stackleak_track_stack); 135