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

kasan, module, vmalloc: rework shadow allocation for modules

Current approach in handling shadow memory for modules is broken.

Shadow memory could be freed only after memory shadow corresponds it is no
longer used. vfree() called from interrupt context could use memory its
freeing to store 'struct llist_node' in it:

void vfree(const void *addr)
{
...
if (unlikely(in_interrupt())) {
struct vfree_deferred *p = this_cpu_ptr(&vfree_deferred);
if (llist_add((struct llist_node *)addr, &p->list))
schedule_work(&p->wq);

Later this list node used in free_work() which actually frees memory.
Currently module_memfree() called in interrupt context will free shadow
before freeing module's memory which could provoke kernel crash.

So shadow memory should be freed after module's memory. However, such
deallocation order could race with kasan_module_alloc() in module_alloc().

Free shadow right before releasing vm area. At this point vfree()'d
memory is not used anymore and yet not available for other allocations.
New VM_KASAN flag used to indicate that vm area has dynamically allocated
shadow memory so kasan frees shadow only if it was previously allocated.

Signed-off-by: Andrey Ryabinin <a.ryabinin@samsung.com>
Acked-by: Rusty Russell <rusty@rustcorp.com.au>
Cc: Dmitry Vyukov <dvyukov@google.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by

Andrey Ryabinin and committed by
Linus Torvalds
a5af5aa8 b3c1030d

+16 -7
+3 -2
include/linux/kasan.h
··· 5 5 6 6 struct kmem_cache; 7 7 struct page; 8 + struct vm_struct; 8 9 9 10 #ifdef CONFIG_KASAN 10 11 ··· 53 52 #define MODULE_ALIGN (PAGE_SIZE << KASAN_SHADOW_SCALE_SHIFT) 54 53 55 54 int kasan_module_alloc(void *addr, size_t size); 56 - void kasan_module_free(void *addr); 55 + void kasan_free_shadow(const struct vm_struct *vm); 57 56 58 57 #else /* CONFIG_KASAN */ 59 58 ··· 83 82 static inline void kasan_slab_free(struct kmem_cache *s, void *object) {} 84 83 85 84 static inline int kasan_module_alloc(void *addr, size_t size) { return 0; } 86 - static inline void kasan_module_free(void *addr) {} 85 + static inline void kasan_free_shadow(const struct vm_struct *vm) {} 87 86 88 87 #endif /* CONFIG_KASAN */ 89 88
+1
include/linux/vmalloc.h
··· 17 17 #define VM_VPAGES 0x00000010 /* buffer for pages was vmalloc'ed */ 18 18 #define VM_UNINITIALIZED 0x00000020 /* vm_struct is not fully initialized */ 19 19 #define VM_NO_GUARD 0x00000040 /* don't add guard page */ 20 + #define VM_KASAN 0x00000080 /* has allocated kasan shadow memory */ 20 21 /* bits [20..32] reserved for arch specific ioremap internals */ 21 22 22 23 /*
-2
kernel/module.c
··· 56 56 #include <linux/async.h> 57 57 #include <linux/percpu.h> 58 58 #include <linux/kmemleak.h> 59 - #include <linux/kasan.h> 60 59 #include <linux/jump_label.h> 61 60 #include <linux/pfn.h> 62 61 #include <linux/bsearch.h> ··· 1813 1814 void __weak module_memfree(void *module_region) 1814 1815 { 1815 1816 vfree(module_region); 1816 - kasan_module_free(module_region); 1817 1817 } 1818 1818 1819 1819 void __weak module_arch_cleanup(struct module *mod)
+11 -3
mm/kasan/kasan.c
··· 29 29 #include <linux/stacktrace.h> 30 30 #include <linux/string.h> 31 31 #include <linux/types.h> 32 + #include <linux/vmalloc.h> 32 33 #include <linux/kasan.h> 33 34 34 35 #include "kasan.h" ··· 415 414 GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO, 416 415 PAGE_KERNEL, VM_NO_GUARD, NUMA_NO_NODE, 417 416 __builtin_return_address(0)); 418 - return ret ? 0 : -ENOMEM; 417 + 418 + if (ret) { 419 + find_vm_area(addr)->flags |= VM_KASAN; 420 + return 0; 421 + } 422 + 423 + return -ENOMEM; 419 424 } 420 425 421 - void kasan_module_free(void *addr) 426 + void kasan_free_shadow(const struct vm_struct *vm) 422 427 { 423 - vfree(kasan_mem_to_shadow(addr)); 428 + if (vm->flags & VM_KASAN) 429 + vfree(kasan_mem_to_shadow(vm->addr)); 424 430 } 425 431 426 432 static void register_global(struct kasan_global *global)
+1
mm/vmalloc.c
··· 1418 1418 spin_unlock(&vmap_area_lock); 1419 1419 1420 1420 vmap_debug_free_range(va->va_start, va->va_end); 1421 + kasan_free_shadow(vm); 1421 1422 free_unmap_vmap_area(va); 1422 1423 vm->size -= PAGE_SIZE; 1423 1424