Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

stack: Constrain and fix stack offset randomization with Clang builds

All supported versions of Clang perform auto-init of __builtin_alloca()
when stack auto-init is on (CONFIG_INIT_STACK_ALL_{ZERO,PATTERN}).

add_random_kstack_offset() uses __builtin_alloca() to add a stack
offset. This means, when CONFIG_INIT_STACK_ALL_{ZERO,PATTERN} is
enabled, add_random_kstack_offset() will auto-init that unused portion
of the stack used to add an offset.

There are several problems with this:

1. These offsets can be as large as 1023 bytes. Performing
memset() on them isn't exactly cheap, and this is done on
every syscall entry.

2. Architectures adding add_random_kstack_offset() to syscall
entry implemented in C require them to be 'noinstr' (e.g. see
x86 and s390). The potential problem here is that a call to
memset may occur, which is not noinstr.

A x86_64 defconfig kernel with Clang 11 and CONFIG_VMLINUX_VALIDATION shows:

| vmlinux.o: warning: objtool: do_syscall_64()+0x9d: call to memset() leaves .noinstr.text section
| vmlinux.o: warning: objtool: do_int80_syscall_32()+0xab: call to memset() leaves .noinstr.text section
| vmlinux.o: warning: objtool: __do_fast_syscall_32()+0xe2: call to memset() leaves .noinstr.text section
| vmlinux.o: warning: objtool: fixup_bad_iret()+0x2f: call to memset() leaves .noinstr.text section

Clang 14 (unreleased) will introduce a way to skip alloca initialization
via __builtin_alloca_uninitialized() (https://reviews.llvm.org/D115440).

Constrain RANDOMIZE_KSTACK_OFFSET to only be enabled if no stack
auto-init is enabled, the compiler is GCC, or Clang is version 14+. Use
__builtin_alloca_uninitialized() if the compiler provides it, as is done
by Clang 14.

Link: https://lkml.kernel.org/r/YbHTKUjEejZCLyhX@elver.google.com
Fixes: 39218ff4c625 ("stack: Optionally randomize kernel stack offset each syscall")
Signed-off-by: Marco Elver <elver@google.com>
Reviewed-by: Nathan Chancellor <nathan@kernel.org>
Acked-by: Kees Cook <keescook@chromium.org>
Signed-off-by: Kees Cook <keescook@chromium.org>
Link: https://lore.kernel.org/r/20220131090521.1947110-2-elver@google.com

authored by

Marco Elver and committed by
Kees Cook
efa90c11 8cb37a59

+15 -2
+1
arch/Kconfig
··· 1163 1163 bool "Support for randomizing kernel stack offset on syscall entry" if EXPERT 1164 1164 default y 1165 1165 depends on HAVE_ARCH_RANDOMIZE_KSTACK_OFFSET 1166 + depends on INIT_STACK_NONE || !CC_IS_CLANG || CLANG_VERSION >= 140000 1166 1167 help 1167 1168 The kernel stack offset can be randomized (after pt_regs) by 1168 1169 roughly 5 bits of entropy, frustrating memory corruption
+14 -2
include/linux/randomize_kstack.h
··· 17 17 * alignment. Also, since this use is being explicitly masked to a max of 18 18 * 10 bits, stack-clash style attacks are unlikely. For more details see 19 19 * "VLAs" in Documentation/process/deprecated.rst 20 + * 21 + * The normal __builtin_alloca() is initialized with INIT_STACK_ALL (currently 22 + * only with Clang and not GCC). Initializing the unused area on each syscall 23 + * entry is expensive, and generating an implicit call to memset() may also be 24 + * problematic (such as in noinstr functions). Therefore, if the compiler 25 + * supports it (which it should if it initializes allocas), always use the 26 + * "uninitialized" variant of the builtin. 20 27 */ 21 - void *__builtin_alloca(size_t size); 28 + #if __has_builtin(__builtin_alloca_uninitialized) 29 + #define __kstack_alloca __builtin_alloca_uninitialized 30 + #else 31 + #define __kstack_alloca __builtin_alloca 32 + #endif 33 + 22 34 /* 23 35 * Use, at most, 10 bits of entropy. We explicitly cap this to keep the 24 36 * "VLA" from being unbounded (see above). 10 bits leaves enough room for ··· 49 37 if (static_branch_maybe(CONFIG_RANDOMIZE_KSTACK_OFFSET_DEFAULT, \ 50 38 &randomize_kstack_offset)) { \ 51 39 u32 offset = raw_cpu_read(kstack_offset); \ 52 - u8 *ptr = __builtin_alloca(KSTACK_OFFSET_MAX(offset)); \ 40 + u8 *ptr = __kstack_alloca(KSTACK_OFFSET_MAX(offset)); \ 53 41 /* Keep allocation even after "ptr" loses scope. */ \ 54 42 asm volatile("" :: "r"(ptr) : "memory"); \ 55 43 } \