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

usercopy: Check valid lifetime via stack depth

One of the things that CONFIG_HARDENED_USERCOPY sanity-checks is whether
an object that is about to be copied to/from userspace is overlapping
the stack at all. If it is, it performs a number of inexpensive
bounds checks. One of the finer-grained checks is whether an object
crosses stack frames within the stack region. Doing this on x86 with
CONFIG_FRAME_POINTER was cheap/easy. Doing it with ORC was deemed too
heavy, and was left out (a while ago), leaving the courser whole-stack
check.

The LKDTM tests USERCOPY_STACK_FRAME_TO and USERCOPY_STACK_FRAME_FROM
try to exercise these cross-frame cases to validate the defense is
working. They have been failing ever since ORC was added (which was
expected). While Muhammad was investigating various LKDTM failures[1],
he asked me for additional details on them, and I realized that when
exact stack frame boundary checking is not available (i.e. everything
except x86 with FRAME_POINTER), it could check if a stack object is at
least "current depth valid", in the sense that any object within the
stack region but not between start-of-stack and current_stack_pointer
should be considered unavailable (i.e. its lifetime is from a call no
longer present on the stack).

Introduce ARCH_HAS_CURRENT_STACK_POINTER to track which architectures
have actually implemented the common global register alias.

Additionally report usercopy bounds checking failures with an offset
from current_stack_pointer, which may assist with diagnosing failures.

The LKDTM USERCOPY_STACK_FRAME_TO and USERCOPY_STACK_FRAME_FROM tests
(once slightly adjusted in a separate patch) pass again with this fixed.

[1] https://github.com/kernelci/kernelci-project/issues/84

Cc: Matthew Wilcox (Oracle) <willy@infradead.org>
Cc: Josh Poimboeuf <jpoimboe@redhat.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: linux-mm@kvack.org
Reported-by: Muhammad Usama Anjum <usama.anjum@collabora.com>
Signed-off-by: Kees Cook <keescook@chromium.org>
---
v1: https://lore.kernel.org/lkml/20220216201449.2087956-1-keescook@chromium.org
v2: https://lore.kernel.org/lkml/20220224060342.1855457-1-keescook@chromium.org
v3: https://lore.kernel.org/lkml/20220225173345.3358109-1-keescook@chromium.org
v4: - improve commit log (akpm)

+36 -2
+1
arch/arm/Kconfig
··· 5 5 select ARCH_32BIT_OFF_T 6 6 select ARCH_CORRECT_STACKTRACE_ON_KRETPROBE if HAVE_KRETPROBES && FRAME_POINTER && !ARM_UNWIND 7 7 select ARCH_HAS_BINFMT_FLAT 8 + select ARCH_HAS_CURRENT_STACK_POINTER 8 9 select ARCH_HAS_DEBUG_VIRTUAL if MMU 9 10 select ARCH_HAS_DMA_WRITE_COMBINE if !ARM_DMA_MEM_BUFFERABLE 10 11 select ARCH_HAS_ELF_RANDOMIZE
+1
arch/arm64/Kconfig
··· 18 18 select ARCH_ENABLE_SPLIT_PMD_PTLOCK if PGTABLE_LEVELS > 2 19 19 select ARCH_ENABLE_THP_MIGRATION if TRANSPARENT_HUGEPAGE 20 20 select ARCH_HAS_CACHE_LINE_SIZE 21 + select ARCH_HAS_CURRENT_STACK_POINTER 21 22 select ARCH_HAS_DEBUG_VIRTUAL 22 23 select ARCH_HAS_DEBUG_VM_PGTABLE 23 24 select ARCH_HAS_DMA_PREP_COHERENT
+1
arch/powerpc/Kconfig
··· 108 108 select ARCH_ENABLE_MEMORY_HOTPLUG 109 109 select ARCH_ENABLE_MEMORY_HOTREMOVE 110 110 select ARCH_HAS_COPY_MC if PPC64 111 + select ARCH_HAS_CURRENT_STACK_POINTER 111 112 select ARCH_HAS_DEBUG_VIRTUAL 112 113 select ARCH_HAS_DEBUG_VM_PGTABLE 113 114 select ARCH_HAS_DEBUG_WX if STRICT_KERNEL_RWX
+1
arch/s390/Kconfig
··· 60 60 select ARCH_ENABLE_MEMORY_HOTPLUG if SPARSEMEM 61 61 select ARCH_ENABLE_MEMORY_HOTREMOVE 62 62 select ARCH_ENABLE_SPLIT_PMD_PTLOCK if PGTABLE_LEVELS > 2 63 + select ARCH_HAS_CURRENT_STACK_POINTER 63 64 select ARCH_HAS_DEBUG_VM_PGTABLE 64 65 select ARCH_HAS_DEBUG_WX 65 66 select ARCH_HAS_DEVMEM_IS_ALLOWED
+1
arch/sh/Kconfig
··· 7 7 select ARCH_HAVE_CUSTOM_GPIO_H 8 8 select ARCH_HAVE_NMI_SAFE_CMPXCHG if (GUSA_RB || CPU_SH4A) 9 9 select ARCH_HAS_BINFMT_FLAT if !MMU 10 + select ARCH_HAS_CURRENT_STACK_POINTER 10 11 select ARCH_HAS_GIGANTIC_PAGE 11 12 select ARCH_HAS_GCOV_PROFILE_ALL 12 13 select ARCH_HAS_PTE_SPECIAL
+1
arch/x86/Kconfig
··· 69 69 select ARCH_ENABLE_THP_MIGRATION if X86_64 && TRANSPARENT_HUGEPAGE 70 70 select ARCH_HAS_ACPI_TABLE_UPGRADE if ACPI 71 71 select ARCH_HAS_CACHE_LINE_SIZE 72 + select ARCH_HAS_CURRENT_STACK_POINTER 72 73 select ARCH_HAS_DEBUG_VIRTUAL 73 74 select ARCH_HAS_DEBUG_VM_PGTABLE if !X86_PAE 74 75 select ARCH_HAS_DEVMEM_IS_ALLOWED
+9
mm/Kconfig
··· 744 744 config ARCH_HAS_CACHE_LINE_SIZE 745 745 bool 746 746 747 + config ARCH_HAS_CURRENT_STACK_POINTER 748 + bool 749 + help 750 + In support of HARDENED_USERCOPY performing stack variable lifetime 751 + checking, an architecture-agnostic way to find the stack pointer 752 + is needed. Once an architecture defines an unsigned long global 753 + register alias named "current_stack_pointer", this config can be 754 + selected. 755 + 747 756 config ARCH_HAS_PTE_DEVMAP 748 757 bool 749 758
+21 -2
mm/usercopy.c
··· 29 29 * Returns: 30 30 * NOT_STACK: not at all on the stack 31 31 * GOOD_FRAME: fully within a valid stack frame 32 - * GOOD_STACK: fully on the stack (when can't do frame-checking) 32 + * GOOD_STACK: within the current stack (when can't frame-check exactly) 33 33 * BAD_STACK: error condition (invalid stack position or bad stack frame) 34 34 */ 35 35 static noinline int check_stack_object(const void *obj, unsigned long len) ··· 54 54 ret = arch_within_stack_frames(stack, stackend, obj, len); 55 55 if (ret) 56 56 return ret; 57 + 58 + /* Finally, check stack depth if possible. */ 59 + #ifdef CONFIG_ARCH_HAS_CURRENT_STACK_POINTER 60 + if (IS_ENABLED(CONFIG_STACK_GROWSUP)) { 61 + if ((void *)current_stack_pointer < obj + len) 62 + return BAD_STACK; 63 + } else { 64 + if (obj < (void *)current_stack_pointer) 65 + return BAD_STACK; 66 + } 67 + #endif 57 68 58 69 return GOOD_STACK; 59 70 } ··· 291 280 */ 292 281 return; 293 282 default: 294 - usercopy_abort("process stack", NULL, to_user, 0, n); 283 + usercopy_abort("process stack", NULL, to_user, 284 + #ifdef CONFIG_ARCH_HAS_CURRENT_STACK_POINTER 285 + IS_ENABLED(CONFIG_STACK_GROWSUP) ? 286 + ptr - (void *)current_stack_pointer : 287 + (void *)current_stack_pointer - ptr, 288 + #else 289 + 0, 290 + #endif 291 + n); 295 292 } 296 293 297 294 /* Check for bad heap object. */