kasan: enable instrumentation of global variables
authorAndrey Ryabinin <a.ryabinin@samsung.com>
Fri, 13 Feb 2015 22:40:17 +0000 (14:40 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sat, 14 Feb 2015 05:21:42 +0000 (21:21 -0800)
This feature let us to detect accesses out of bounds of global variables.
This will work as for globals in kernel image, so for globals in modules.
Currently this won't work for symbols in user-specified sections (e.g.
__init, __read_mostly, ...)

The idea of this is simple.  Compiler increases each global variable by
redzone size and add constructors invoking __asan_register_globals()
function.  Information about global variable (address, size, size with
redzone ...) passed to __asan_register_globals() so we could poison
variable's redzone.

This patch also forces module_alloc() to return 8*PAGE_SIZE aligned
address making shadow memory handling (
kasan_module_alloc()/kasan_module_free() ) more simple.  Such alignment
guarantees that each shadow page backing modules address space correspond
to only one module_alloc() allocation.

Signed-off-by: Andrey Ryabinin <a.ryabinin@samsung.com>
Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: Konstantin Serebryany <kcc@google.com>
Cc: Dmitry Chernenkov <dmitryc@google.com>
Signed-off-by: Andrey Konovalov <adech.fo@gmail.com>
Cc: Yuri Gribov <tetra2005@gmail.com>
Cc: Konstantin Khlebnikov <koct9i@gmail.com>
Cc: Sasha Levin <sasha.levin@oracle.com>
Cc: Christoph Lameter <cl@linux.com>
Cc: Joonsoo Kim <iamjoonsoo.kim@lge.com>
Cc: Dave Hansen <dave.hansen@intel.com>
Cc: Andi Kleen <andi@firstfloor.org>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: "H. Peter Anvin" <hpa@zytor.com>
Cc: Christoph Lameter <cl@linux.com>
Cc: Pekka Enberg <penberg@kernel.org>
Cc: David Rientjes <rientjes@google.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
12 files changed:
Documentation/kasan.txt
arch/x86/kernel/module.c
arch/x86/mm/kasan_init_64.c
include/linux/compiler-gcc4.h
include/linux/compiler-gcc5.h
include/linux/kasan.h
kernel/module.c
lib/Kconfig.kasan
mm/kasan/kasan.c
mm/kasan/kasan.h
mm/kasan/report.c
scripts/Makefile.kasan

index f0645a8a992f112089d31b13dd94fca35ea70572..092fc10961fede482be1f7983e72db0e6c8ec3a8 100644 (file)
@@ -9,7 +9,7 @@ a fast and comprehensive solution for finding use-after-free and out-of-bounds
 bugs.
 
 KASan uses compile-time instrumentation for checking every memory access,
-therefore you will need a certain version of GCC >= 4.9.2
+therefore you will need a certain version of GCC > 4.9.2
 
 Currently KASan is supported only for x86_64 architecture and requires that the
 kernel be built with the SLUB allocator.
index e830e61aae059309a3a16ef0b30a08b5c56e1ca4..d1ac80b72c72184a0b999c2b299b5e265d26de7a 100644 (file)
@@ -24,6 +24,7 @@
 #include <linux/fs.h>
 #include <linux/string.h>
 #include <linux/kernel.h>
+#include <linux/kasan.h>
 #include <linux/bug.h>
 #include <linux/mm.h>
 #include <linux/gfp.h>
@@ -83,13 +84,22 @@ static unsigned long int get_module_load_offset(void)
 
 void *module_alloc(unsigned long size)
 {
+       void *p;
+
        if (PAGE_ALIGN(size) > MODULES_LEN)
                return NULL;
-       return __vmalloc_node_range(size, 1,
+
+       p = __vmalloc_node_range(size, MODULE_ALIGN,
                                    MODULES_VADDR + get_module_load_offset(),
                                    MODULES_END, GFP_KERNEL | __GFP_HIGHMEM,
                                    PAGE_KERNEL_EXEC, 0, NUMA_NO_NODE,
                                    __builtin_return_address(0));
+       if (p && (kasan_module_alloc(p, size) < 0)) {
+               vfree(p);
+               return NULL;
+       }
+
+       return p;
 }
 
 #ifdef CONFIG_X86_32
index 53508708b7aa22af7dd62eec1e0544102f08d2a7..4860906c6b9f8e751b39ad3d5d4874c6da92c2e3 100644 (file)
@@ -196,7 +196,7 @@ void __init kasan_init(void)
                        (unsigned long)kasan_mem_to_shadow(_end),
                        NUMA_NO_NODE);
 
-       populate_zero_shadow(kasan_mem_to_shadow((void *)MODULES_VADDR),
+       populate_zero_shadow(kasan_mem_to_shadow((void *)MODULES_END),
                        (void *)KASAN_SHADOW_END);
 
        memset(kasan_zero_page, 0, PAGE_SIZE);
index d1a558239b1a189768d804c13b39617677cd93b4..769e19864632087956ad773bb87c855d64123afe 100644 (file)
@@ -85,3 +85,7 @@
 #define __HAVE_BUILTIN_BSWAP16__
 #endif
 #endif /* CONFIG_ARCH_USE_BUILTIN_BSWAP */
+
+#if GCC_VERSION >= 40902
+#define KASAN_ABI_VERSION 3
+#endif
index c8c565952548e6ca834425b51dce65c34e5255e7..efee493714eba9776138825e87add2ab1915cf2e 100644 (file)
@@ -63,3 +63,5 @@
 #define __HAVE_BUILTIN_BSWAP64__
 #define __HAVE_BUILTIN_BSWAP16__
 #endif /* CONFIG_ARCH_USE_BUILTIN_BSWAP */
+
+#define KASAN_ABI_VERSION 4
index d5310eef3e3874dd13e9d3a4f83a2736fff2f638..72ba725ddf9c73054533256a7246f5425c95771b 100644 (file)
@@ -49,8 +49,15 @@ void kasan_krealloc(const void *object, size_t new_size);
 void kasan_slab_alloc(struct kmem_cache *s, void *object);
 void kasan_slab_free(struct kmem_cache *s, void *object);
 
+#define MODULE_ALIGN (PAGE_SIZE << KASAN_SHADOW_SCALE_SHIFT)
+
+int kasan_module_alloc(void *addr, size_t size);
+void kasan_module_free(void *addr);
+
 #else /* CONFIG_KASAN */
 
+#define MODULE_ALIGN 1
+
 static inline void kasan_unpoison_shadow(const void *address, size_t size) {}
 
 static inline void kasan_enable_current(void) {}
@@ -74,6 +81,9 @@ static inline void kasan_krealloc(const void *object, size_t new_size) {}
 static inline void kasan_slab_alloc(struct kmem_cache *s, void *object) {}
 static inline void kasan_slab_free(struct kmem_cache *s, void *object) {}
 
+static inline int kasan_module_alloc(void *addr, size_t size) { return 0; }
+static inline void kasan_module_free(void *addr) {}
+
 #endif /* CONFIG_KASAN */
 
 #endif /* LINUX_KASAN_H */
index 82dc1f899e6db9cd5f5f0c47ce16b01d89b0c734..8426ad48362c73f96e8031a7c9b0cd16eba1d426 100644 (file)
@@ -56,6 +56,7 @@
 #include <linux/async.h>
 #include <linux/percpu.h>
 #include <linux/kmemleak.h>
+#include <linux/kasan.h>
 #include <linux/jump_label.h>
 #include <linux/pfn.h>
 #include <linux/bsearch.h>
@@ -1813,6 +1814,7 @@ static void unset_module_init_ro_nx(struct module *mod) { }
 void __weak module_memfree(void *module_region)
 {
        vfree(module_region);
+       kasan_module_free(module_region);
 }
 
 void __weak module_arch_cleanup(struct module *mod)
index 4d47d874335ce9d1b6c8dad94e45b4abb0515fc8..4fecaedc80a27adadb25bfa2d4e4a13dc182ab72 100644 (file)
@@ -6,6 +6,7 @@ if HAVE_ARCH_KASAN
 config KASAN
        bool "KASan: runtime memory debugger"
        depends on SLUB_DEBUG
+       select CONSTRUCTORS
        help
          Enables kernel address sanitizer - runtime memory debugger,
          designed to find out-of-bounds accesses and use-after-free bugs.
index 799c52b9826ca1667ffa858a0b10573afd95424c..78fee632a7ee9b73d9d0d5498d76aeb58460e711 100644 (file)
@@ -22,6 +22,7 @@
 #include <linux/memblock.h>
 #include <linux/memory.h>
 #include <linux/mm.h>
+#include <linux/module.h>
 #include <linux/printk.h>
 #include <linux/sched.h>
 #include <linux/slab.h>
@@ -395,6 +396,57 @@ void kasan_kfree_large(const void *ptr)
                        KASAN_FREE_PAGE);
 }
 
+int kasan_module_alloc(void *addr, size_t size)
+{
+       void *ret;
+       size_t shadow_size;
+       unsigned long shadow_start;
+
+       shadow_start = (unsigned long)kasan_mem_to_shadow(addr);
+       shadow_size = round_up(size >> KASAN_SHADOW_SCALE_SHIFT,
+                       PAGE_SIZE);
+
+       if (WARN_ON(!PAGE_ALIGNED(shadow_start)))
+               return -EINVAL;
+
+       ret = __vmalloc_node_range(shadow_size, 1, shadow_start,
+                       shadow_start + shadow_size,
+                       GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO,
+                       PAGE_KERNEL, VM_NO_GUARD, NUMA_NO_NODE,
+                       __builtin_return_address(0));
+       return ret ? 0 : -ENOMEM;
+}
+
+void kasan_module_free(void *addr)
+{
+       vfree(kasan_mem_to_shadow(addr));
+}
+
+static void register_global(struct kasan_global *global)
+{
+       size_t aligned_size = round_up(global->size, KASAN_SHADOW_SCALE_SIZE);
+
+       kasan_unpoison_shadow(global->beg, global->size);
+
+       kasan_poison_shadow(global->beg + aligned_size,
+               global->size_with_redzone - aligned_size,
+               KASAN_GLOBAL_REDZONE);
+}
+
+void __asan_register_globals(struct kasan_global *globals, size_t size)
+{
+       int i;
+
+       for (i = 0; i < size; i++)
+               register_global(&globals[i]);
+}
+EXPORT_SYMBOL(__asan_register_globals);
+
+void __asan_unregister_globals(struct kasan_global *globals, size_t size)
+{
+}
+EXPORT_SYMBOL(__asan_unregister_globals);
+
 #define DEFINE_ASAN_LOAD_STORE(size)                           \
        void __asan_load##size(unsigned long addr)              \
        {                                                       \
index 1fcc1d81a9cf5a6cf40a71f225fe0f631795dfb7..4986b0acab212e5780bb8e0547f4b53a51aac1a4 100644 (file)
@@ -11,6 +11,7 @@
 #define KASAN_PAGE_REDZONE      0xFE  /* redzone for kmalloc_large allocations */
 #define KASAN_KMALLOC_REDZONE   0xFC  /* redzone inside slub object */
 #define KASAN_KMALLOC_FREE      0xFB  /* object was freed (kmem_cache_free/kfree) */
+#define KASAN_GLOBAL_REDZONE    0xFA  /* redzone for global variable */
 
 /*
  * Stack redzone shadow values
 #define KASAN_STACK_RIGHT       0xF3
 #define KASAN_STACK_PARTIAL     0xF4
 
+/* Don't break randconfig/all*config builds */
+#ifndef KASAN_ABI_VERSION
+#define KASAN_ABI_VERSION 1
+#endif
 
 struct kasan_access_info {
        const void *access_addr;
@@ -30,6 +35,26 @@ struct kasan_access_info {
        unsigned long ip;
 };
 
+/* The layout of struct dictated by compiler */
+struct kasan_source_location {
+       const char *filename;
+       int line_no;
+       int column_no;
+};
+
+/* The layout of struct dictated by compiler */
+struct kasan_global {
+       const void *beg;                /* Address of the beginning of the global variable. */
+       size_t size;                    /* Size of the global variable. */
+       size_t size_with_redzone;       /* Size of the variable + size of the red zone. 32 bytes aligned */
+       const void *name;
+       const void *module_name;        /* Name of the module where the global variable is declared. */
+       unsigned long has_dynamic_init; /* This needed for C++ */
+#if KASAN_ABI_VERSION >= 4
+       struct kasan_source_location *location;
+#endif
+};
+
 void kasan_report_error(struct kasan_access_info *info);
 void kasan_report_user_access(struct kasan_access_info *info);
 
index 866732ef3db332d946c637d79ccb8e45d6ac209a..680ceedf810ab4c9cd08c9929f5d445de9f5aa6a 100644 (file)
@@ -23,6 +23,8 @@
 #include <linux/types.h>
 #include <linux/kasan.h>
 
+#include <asm/sections.h>
+
 #include "kasan.h"
 #include "../slab.h"
 
@@ -61,6 +63,7 @@ static void print_error_description(struct kasan_access_info *info)
                break;
        case KASAN_PAGE_REDZONE:
        case KASAN_KMALLOC_REDZONE:
+       case KASAN_GLOBAL_REDZONE:
        case 0 ... KASAN_SHADOW_SCALE_SIZE - 1:
                bug_type = "out of bounds access";
                break;
@@ -80,6 +83,20 @@ static void print_error_description(struct kasan_access_info *info)
                info->access_size, current->comm, task_pid_nr(current));
 }
 
+static inline bool kernel_or_module_addr(const void *addr)
+{
+       return (addr >= (void *)_stext && addr < (void *)_end)
+               || (addr >= (void *)MODULES_VADDR
+                       && addr < (void *)MODULES_END);
+}
+
+static inline bool init_task_stack_addr(const void *addr)
+{
+       return addr >= (void *)&init_thread_union.stack &&
+               (addr <= (void *)&init_thread_union.stack +
+                       sizeof(init_thread_union.stack));
+}
+
 static void print_address_description(struct kasan_access_info *info)
 {
        const void *addr = info->access_addr;
@@ -107,6 +124,11 @@ static void print_address_description(struct kasan_access_info *info)
                dump_page(page, "kasan: bad access detected");
        }
 
+       if (kernel_or_module_addr(addr)) {
+               if (!init_task_stack_addr(addr))
+                       pr_err("Address belongs to variable %pS\n", addr);
+       }
+
        dump_stack();
 }
 
index 2163b8cc446ebdcbe61a6559bd8719d17c6d5c98..631619b2b118996277d45b2843a4a1469644477c 100644 (file)
@@ -9,7 +9,7 @@ CFLAGS_KASAN_MINIMAL := -fsanitize=kernel-address
 
 CFLAGS_KASAN := $(call cc-option, -fsanitize=kernel-address \
                -fasan-shadow-offset=$(CONFIG_KASAN_SHADOW_OFFSET) \
-               --param asan-stack=1 \
+               --param asan-stack=1 --param asan-globals=1 \
                --param asan-instrumentation-with-call-threshold=$(call_threshold))
 
 ifeq ($(call cc-option, $(CFLAGS_KASAN_MINIMAL) -Werror),)