History

stackleak: rework stack high bound handling

Prior to returning to userspace, we reset current->lowest_stack to a reasonable high bound. Currently we do this by subtracting the arbitrary value `THREAD_SIZE/64` from the top of the stack, for reasons lost to history. Looking at configurations today: * On i386 where THREAD_SIZE is 8K, the bound will be 128 bytes. The pt_regs at the top of the stack is 68 bytes (with 0 to 16 bytes of padding above), and so this covers an additional portion of 44 to 60 bytes. * On x86_64 where THREAD_SIZE is at least 16K (up to 32K with KASAN) the bound will be at least 256 bytes (up to 512 with KASAN). The pt_regs at the top of the stack is 168 bytes, and so this cover an additional 88 bytes of stack (up to 344 with KASAN). * On arm64 where THREAD_SIZE is at least 16K (up to 64K with 64K pages and VMAP_STACK), the bound will be at least 256 bytes (up to 1024 with KASAN). The pt_regs at the top of the stack is 336 bytes, so this can fall within the pt_regs, or can cover an additional 688 bytes of stack. Clearly the `THREAD_SIZE/64` value doesn't make much sense -- in the worst case, this will cause more than 600 bytes of stack to be erased for every syscall, even if actual stack usage were substantially smaller. This patches makes this slightly less nonsensical by consistently resetting current->lowest_stack to the base of the task pt_regs. For clarity and for consistency with the handling of the low bound, the generation of the high bound is split into a helper with commentary explaining why. Since the pt_regs at the top of the stack will be clobbered upon the next exception entry, we don't need to poison these at exception exit. By using task_pt_regs() as the high stack boundary instead of current_top_of_stack() we avoid some redundant poisoning, and the compiler can share the address generation between the poisoning and resetting of `current->lowest_stack`, making the generated code more optimal. It's not clear to me whether the existing `THREAD_SIZE/64` offset was a dodgy heuristic to skip the pt_regs, or whether it was attempting to minimize the number of times stackleak_check_stack() would have to update `current->lowest_stack` when stack usage was shallow at the cost of unconditionally poisoning a small portion of the stack for every exit to userspace. For now I've simply removed the offset, and if we need/want to minimize updates for shallow stack usage it should be easy to add a better heuristic atop, with appropriate commentary so we know what's going on.

Mark Rutland committed 4 years ago

Commit 0cfa2cc1 parent 1723d392 files · +28 −5Browse files

Showing the changes to kernel (1 of 2 files). Show all

Unified · Split

kernel/stackleak.c+14−5
@@ -73,6 +73,7 @@ late_initcall(stackleak_sysctls_init); · 72 unmodified lines
73static __always_inline void __stackleak_erase(void)73static __always_inline void __stackleak_erase(void)
74{74{
75 const unsigned long task_stack_low = stackleak_task_low_bound(current);75 const unsigned long task_stack_low = stackleak_task_low_bound(current);
76 const unsigned long task_stack_high = stackleak_task_high_bound(current);
76 unsigned long erase_low = current->lowest_stack;77 unsigned long erase_low = current->lowest_stack;
77 unsigned long erase_high;78 unsigned long erase_high;
78 unsigned int poison_count = 0;79 unsigned int poison_count = 0;
@@ -93,14 +94,22 @@ static __always_inline void __stackleak_erase(void) · 14 unmodified lines
93#endif94#endif
94 95
95 /*96 /*
96 * Now write the poison value to the kernel stack between 'erase_low'97 * Write poison to the task's stack between 'erase_low' and
97 * and 'erase_high'. We assume that the stack pointer doesn't change98 * 'erase_high'.
98 * when we write poison.99 *
100 * If we're running on a different stack (e.g. an entry trampoline
101 * stack) we can erase everything below the pt_regs at the top of the
102 * task stack.
103 *
104 * If we're running on the task stack itself, we must not clobber any
105 * stack used by this function and its caller. We assume that this
106 * function has a fixed-size stack frame, and the current stack pointer
107 * doesn't change while we write poison.
99 */108 */
100 if (on_thread_stack())109 if (on_thread_stack())
101 erase_high = current_stack_pointer;110 erase_high = current_stack_pointer;
102 else111 else
103 erase_high = current_top_of_stack();112 erase_high = task_stack_high;
104 113
105 while (erase_low < erase_high) {114 while (erase_low < erase_high) {
106 *(unsigned long *)erase_low = STACKLEAK_POISON;115 *(unsigned long *)erase_low = STACKLEAK_POISON;
@@ -108,7 +117,7 @@ static __always_inline void __stackleak_erase(void) · 1 unmodified line
108 }117 }
109 118
110 /* Reset the 'lowest_stack' value for the next syscall */119 /* Reset the 'lowest_stack' value for the next syscall */
111 current->lowest_stack = current_top_of_stack() - THREAD_SIZE/64;120 current->lowest_stack = task_stack_high;
112}121}
113 122
114asmlinkage void noinstr stackleak_erase(void)123asmlinkage void noinstr stackleak_erase(void)

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t