UML: add support for KASAN under x86_64
authorPatricia Alfonso <trishalfonso@google.com>
Fri, 1 Jul 2022 09:16:20 +0000 (17:16 +0800)
committerRichard Weinberger <richard@nod.at>
Sun, 17 Jul 2022 21:35:22 +0000 (23:35 +0200)
Make KASAN run on User Mode Linux on x86_64.

The UML-specific KASAN initializer uses mmap to map the ~16TB of shadow
memory to the location defined by KASAN_SHADOW_OFFSET.  kasan_init()
utilizes constructors to initialize KASAN before main().

The location of the KASAN shadow memory, starting at
KASAN_SHADOW_OFFSET, can be configured using the KASAN_SHADOW_OFFSET
option. The default location of this offset is 0x100000000000, which
keeps it out-of-the-way even on UML setups with more "physical" memory.

For low-memory setups, 0x7fff8000 can be used instead, which fits in an
immediate and is therefore faster, as suggested by Dmitry Vyukov. There
is usually enough free space at this location; however, it is a config
option so that it can be easily changed if needed.

Note that, unlike KASAN on other architectures, vmalloc allocations
still use the shadow memory allocated upfront, rather than allocating
and free-ing it per-vmalloc allocation.

If another architecture chooses to go down the same path, we should
replace the checks for CONFIG_UML with something more generic, such
as:
- A CONFIG_KASAN_NO_SHADOW_ALLOC option, which architectures could set
- or, a way of having architecture-specific versions of these vmalloc
  and module shadow memory allocation options.

Also note that, while UML supports both KASAN in inline mode
(CONFIG_KASAN_INLINE) and static linking (CONFIG_STATIC_LINK), it does
not support both at the same time.

Signed-off-by: Patricia Alfonso <trishalfonso@google.com>
Co-developed-by: Vincent Whitchurch <vincent.whitchurch@axis.com>
Signed-off-by: Vincent Whitchurch <vincent.whitchurch@axis.com>
Signed-off-by: David Gow <davidgow@google.com>
Reviewed-by: Johannes Berg <johannes@sipsolutions.net>
Reviewed-by: Dmitry Vyukov <dvyukov@google.com>
Reviewed-by: Andrey Konovalov <andreyknvl@gmail.com>
Signed-off-by: Richard Weinberger <richard@nod.at>
arch/um/Kconfig
arch/um/include/asm/common.lds.S
arch/um/include/asm/kasan.h [new file with mode: 0644]
arch/um/kernel/dyn.lds.S
arch/um/kernel/mem.c
arch/um/kernel/stacktrace.c
arch/um/os-Linux/mem.c
arch/um/os-Linux/user_syms.c
arch/x86/um/Makefile
arch/x86/um/vdso/Makefile
mm/kasan/shadow.c

index 7b0f953e7d6c92eb1d183723b5bdbb2284624fd5..78de31ac1da7c66422153cb6bca05ce6dab801f2 100644 (file)
@@ -12,6 +12,8 @@ config UML
        select ARCH_HAS_STRNLEN_USER
        select ARCH_NO_PREEMPT
        select HAVE_ARCH_AUDITSYSCALL
+       select HAVE_ARCH_KASAN if X86_64
+       select HAVE_ARCH_KASAN_VMALLOC if HAVE_ARCH_KASAN
        select HAVE_ARCH_SECCOMP_FILTER
        select HAVE_ASM_MODVERSIONS
        select HAVE_UID16
@@ -219,6 +221,19 @@ config UML_TIME_TRAVEL_SUPPORT
 
          It is safe to say Y, but you probably don't need this.
 
+config KASAN_SHADOW_OFFSET
+       hex
+       depends on KASAN
+       default 0x100000000000
+       help
+         This is the offset at which the ~16TB of shadow memory is
+         mapped and used by KASAN for memory debugging. This can be any
+         address that has at least KASAN_SHADOW_SIZE (total address space divided
+         by 8) amount of space so that the KASAN shadow memory does not conflict
+         with anything. The default is 0x100000000000, which works even if mem is
+         set to a large value. On low-memory systems, try 0x7fff8000, as it fits
+         into the immediate of most instructions, improving performance.
+
 endmenu
 
 source "arch/um/drivers/Kconfig"
index eca6c452a41bd16d14a4631a3e61e65cc3467ada..fd481ac371de0b5fd6b58f896dfeb6a3b2b17816 100644 (file)
@@ -83,6 +83,8 @@
   }
   .init_array : {
        __init_array_start = .;
+       *(.kasan_init)
+       *(.init_array.*)
        *(.init_array)
        __init_array_end = .;
   }
diff --git a/arch/um/include/asm/kasan.h b/arch/um/include/asm/kasan.h
new file mode 100644 (file)
index 0000000..0d6547f
--- /dev/null
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __ASM_UM_KASAN_H
+#define __ASM_UM_KASAN_H
+
+#include <linux/init.h>
+#include <linux/const.h>
+
+#define KASAN_SHADOW_OFFSET _AC(CONFIG_KASAN_SHADOW_OFFSET, UL)
+
+/* used in kasan_mem_to_shadow to divide by 8 */
+#define KASAN_SHADOW_SCALE_SHIFT 3
+
+#ifdef CONFIG_X86_64
+#define KASAN_HOST_USER_SPACE_END_ADDR 0x00007fffffffffffUL
+/* KASAN_SHADOW_SIZE is the size of total address space divided by 8 */
+#define KASAN_SHADOW_SIZE ((KASAN_HOST_USER_SPACE_END_ADDR + 1) >> \
+                       KASAN_SHADOW_SCALE_SHIFT)
+#else
+#error "KASAN_SHADOW_SIZE is not defined for this sub-architecture"
+#endif /* CONFIG_X86_64 */
+
+#define KASAN_SHADOW_START (KASAN_SHADOW_OFFSET)
+#define KASAN_SHADOW_END (KASAN_SHADOW_START + KASAN_SHADOW_SIZE)
+
+#ifdef CONFIG_KASAN
+void kasan_init(void);
+void kasan_map_memory(void *start, unsigned long len);
+extern int kasan_um_is_ready;
+
+#ifdef CONFIG_STATIC_LINK
+#define kasan_arch_is_ready() (kasan_um_is_ready)
+#endif
+#else
+static inline void kasan_init(void) { }
+#endif /* CONFIG_KASAN */
+
+#endif /* __ASM_UM_KASAN_H */
index 2f2a8ce92f1ee505f30395dd21e036d9b62f3b5f..2b7fc5b54164881073e27faec51c27610fa61620 100644 (file)
@@ -109,7 +109,11 @@ SECTIONS
      be empty, which isn't pretty.  */
   . = ALIGN(32 / 8);
   .preinit_array     : { *(.preinit_array) }
-  .init_array     : { *(.init_array) }
+  .init_array     : {
+    *(.kasan_init)
+    *(.init_array.*)
+    *(.init_array)
+  }
   .fini_array     : { *(.fini_array) }
   .data           : {
     INIT_TASK_DATA(KERNEL_STACK_SIZE)
index 15295c3237a005527e67bcbdfa0df2447dd3e88d..276a1f0b91f1ade730ad540ca7e640e9b3295b26 100644 (file)
 #include <kern_util.h>
 #include <mem_user.h>
 #include <os.h>
+#include <linux/sched/task.h>
+
+#ifdef CONFIG_KASAN
+int kasan_um_is_ready;
+void kasan_init(void)
+{
+       /*
+        * kasan_map_memory will map all of the required address space and
+        * the host machine will allocate physical memory as necessary.
+        */
+       kasan_map_memory((void *)KASAN_SHADOW_START, KASAN_SHADOW_SIZE);
+       init_task.kasan_depth = 0;
+       kasan_um_is_ready = true;
+}
+
+static void (*kasan_init_ptr)(void)
+__section(".kasan_init") __used
+= kasan_init;
+#endif
 
 /* allocated in paging_init, zeroed in mem_init, and unchanged thereafter */
 unsigned long *empty_zero_page = NULL;
index 86df52168bd94cd45a9b4456afdabf0a8fc473cd..fd3b61b3d4d2bd836e354d0e4c42dcc82c1c1c40 100644 (file)
@@ -27,7 +27,7 @@ void dump_trace(struct task_struct *tsk,
 
        frame = (struct stack_frame *)bp;
        while (((long) sp & (THREAD_SIZE-1)) != 0) {
-               addr = *sp;
+               addr = READ_ONCE_NOCHECK(*sp);
                if (__kernel_text_address(addr)) {
                        reliable = 0;
                        if ((unsigned long) sp == bp + sizeof(long)) {
index 3c1b77474d2d1eeb0b1b36c89547d126d1a6e931..8530b2e086049b0e72fb19304a712e9594623730 100644 (file)
 #include <init.h>
 #include <os.h>
 
+/*
+ * kasan_map_memory - maps memory from @start with a size of @len.
+ * The allocated memory is filled with zeroes upon success.
+ * @start: the start address of the memory to be mapped
+ * @len: the length of the memory to be mapped
+ *
+ * This function is used to map shadow memory for KASAN in uml
+ */
+void kasan_map_memory(void *start, size_t len)
+{
+       if (mmap(start,
+                len,
+                PROT_READ|PROT_WRITE,
+                MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE|MAP_NORESERVE,
+                -1,
+                0) == MAP_FAILED) {
+               os_info("Couldn't allocate shadow memory: %s\n.",
+                       strerror(errno));
+               exit(1);
+       }
+}
+
 /* Set by make_tempfile() during early boot. */
 static char *tempdir = NULL;
 
index 715594fe57190688c64906ee20ee5d2328604d2c..cb667c9225abd7ae344fe602e9d0ac619913ad95 100644 (file)
@@ -27,10 +27,10 @@ EXPORT_SYMBOL(strstr);
 #ifndef __x86_64__
 extern void *memcpy(void *, const void *, size_t);
 EXPORT_SYMBOL(memcpy);
-#endif
-
 EXPORT_SYMBOL(memmove);
 EXPORT_SYMBOL(memset);
+#endif
+
 EXPORT_SYMBOL(printf);
 
 /* Here, instead, I can provide a fake prototype. Yes, someone cares: genksyms.
index ba5789c3580945508f5c1963913643de1e3b74db..f778e37494bab2654b62139012db2791651d597f 100644 (file)
@@ -28,7 +28,8 @@ else
 
 obj-y += syscalls_64.o vdso/
 
-subarch-y = ../lib/csum-partial_64.o ../lib/memcpy_64.o ../entry/thunk_64.o
+subarch-y = ../lib/csum-partial_64.o ../lib/memcpy_64.o ../entry/thunk_64.o \
+       ../lib/memmove_64.o ../lib/memset_64.o
 
 endif
 
index 5943387e3f3570e28cb376a4df2645b727be429b..8c0396fd0e6f691e72a36bad703cf4172f8693da 100644 (file)
@@ -3,6 +3,9 @@
 # Building vDSO images for x86.
 #
 
+# do not instrument on vdso because KASAN is not compatible with user mode
+KASAN_SANITIZE                 := n
+
 # Prevents link failures: __sanitizer_cov_trace_pc() is not linked in.
 KCOV_INSTRUMENT                := n
 
index a4f07de21771122c937b8213c643631e8f5f6873..0e3648b603a6fc190dc75ea1bfb842b0c6ba3b86 100644 (file)
@@ -295,9 +295,22 @@ int kasan_populate_vmalloc(unsigned long addr, unsigned long size)
                return 0;
 
        shadow_start = (unsigned long)kasan_mem_to_shadow((void *)addr);
-       shadow_start = ALIGN_DOWN(shadow_start, PAGE_SIZE);
        shadow_end = (unsigned long)kasan_mem_to_shadow((void *)addr + size);
-       shadow_end = ALIGN(shadow_end, PAGE_SIZE);
+
+       /*
+        * User Mode Linux maps enough shadow memory for all of virtual memory
+        * at boot, so doesn't need to allocate more on vmalloc, just clear it.
+        *
+        * The remaining CONFIG_UML checks in this file exist for the same
+        * reason.
+        */
+       if (IS_ENABLED(CONFIG_UML)) {
+               __memset((void *)shadow_start, KASAN_VMALLOC_INVALID, shadow_end - shadow_start);
+               return 0;
+       }
+
+       shadow_start = PAGE_ALIGN_DOWN(shadow_start);
+       shadow_end = PAGE_ALIGN(shadow_end);
 
        ret = apply_to_page_range(&init_mm, shadow_start,
                                  shadow_end - shadow_start,
@@ -466,6 +479,10 @@ void kasan_release_vmalloc(unsigned long start, unsigned long end,
 
        if (shadow_end > shadow_start) {
                size = shadow_end - shadow_start;
+               if (IS_ENABLED(CONFIG_UML)) {
+                       __memset(shadow_start, KASAN_SHADOW_INIT, shadow_end - shadow_start);
+                       return;
+               }
                apply_to_existing_page_range(&init_mm,
                                             (unsigned long)shadow_start,
                                             size, kasan_depopulate_vmalloc_pte,
@@ -531,6 +548,11 @@ int kasan_alloc_module_shadow(void *addr, size_t size, gfp_t gfp_mask)
        if (WARN_ON(!PAGE_ALIGNED(shadow_start)))
                return -EINVAL;
 
+       if (IS_ENABLED(CONFIG_UML)) {
+               __memset((void *)shadow_start, KASAN_SHADOW_INIT, shadow_size);
+               return 0;
+       }
+
        ret = __vmalloc_node_range(shadow_size, 1, shadow_start,
                        shadow_start + shadow_size,
                        GFP_KERNEL,
@@ -554,6 +576,9 @@ int kasan_alloc_module_shadow(void *addr, size_t size, gfp_t gfp_mask)
 
 void kasan_free_module_shadow(const struct vm_struct *vm)
 {
+       if (IS_ENABLED(CONFIG_UML))
+               return;
+
        if (vm->flags & VM_KASAN)
                vfree(kasan_mem_to_shadow(vm->addr));
 }