diff options
| author | Andrey Ryabinin <aryabinin@virtuozzo.com> | 2018-05-25 17:47:38 -0400 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2018-05-25 21:12:11 -0400 |
| commit | 0f901dcbc31f88ae41a2aaa365f7802b5d520a28 (patch) | |
| tree | 49cf2f300275f997ca2c72fb8715748f8e205b89 /mm | |
| parent | b9ddff9b85036292f8b6d4ac37e21fc229fedea1 (diff) | |
mm/kasan: don't vfree() nonexistent vm_area
KASAN uses different routines to map shadow for hot added memory and
memory obtained in boot process. Attempt to offline memory onlined by
normal boot process leads to this:
Trying to vfree() nonexistent vm area (000000005d3b34b9)
WARNING: CPU: 2 PID: 13215 at mm/vmalloc.c:1525 __vunmap+0x147/0x190
Call Trace:
kasan_mem_notifier+0xad/0xb9
notifier_call_chain+0x166/0x260
__blocking_notifier_call_chain+0xdb/0x140
__offline_pages+0x96a/0xb10
memory_subsys_offline+0x76/0xc0
device_offline+0xb8/0x120
store_mem_state+0xfa/0x120
kernfs_fop_write+0x1d5/0x320
__vfs_write+0xd4/0x530
vfs_write+0x105/0x340
SyS_write+0xb0/0x140
Obviously we can't call vfree() to free memory that wasn't allocated via
vmalloc(). Use find_vm_area() to see if we can call vfree().
Unfortunately it's a bit tricky to properly unmap and free shadow
allocated during boot, so we'll have to keep it. If memory will come
online again that shadow will be reused.
Matthew asked: how can you call vfree() on something that isn't a
vmalloc address?
vfree() is able to free any address returned by
__vmalloc_node_range(). And __vmalloc_node_range() gives you any
address you ask. It doesn't have to be an address in [VMALLOC_START,
VMALLOC_END] range.
That's also how the module_alloc()/module_memfree() works on
architectures that have designated area for modules.
[aryabinin@virtuozzo.com: improve comments]
Link: http://lkml.kernel.org/r/dabee6ab-3a7a-51cd-3b86-5468718e0390@virtuozzo.com
[akpm@linux-foundation.org: fix typos, reflow comment]
Link: http://lkml.kernel.org/r/20180201163349.8700-1-aryabinin@virtuozzo.com
Fixes: fa69b5989bb0 ("mm/kasan: add support for memory hotplug")
Signed-off-by: Andrey Ryabinin <aryabinin@virtuozzo.com>
Reported-by: Paul Menzel <pmenzel+linux-kasan-dev@molgen.mpg.de>
Cc: Alexander Potapenko <glider@google.com>
Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: Matthew Wilcox <willy@infradead.org>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'mm')
| -rw-r--r-- | mm/kasan/kasan.c | 63 |
1 files changed, 61 insertions, 2 deletions
diff --git a/mm/kasan/kasan.c b/mm/kasan/kasan.c index bc0e68f7dc75..7160028efd12 100644 --- a/mm/kasan/kasan.c +++ b/mm/kasan/kasan.c | |||
| @@ -792,6 +792,40 @@ DEFINE_ASAN_SET_SHADOW(f5); | |||
| 792 | DEFINE_ASAN_SET_SHADOW(f8); | 792 | DEFINE_ASAN_SET_SHADOW(f8); |
| 793 | 793 | ||
| 794 | #ifdef CONFIG_MEMORY_HOTPLUG | 794 | #ifdef CONFIG_MEMORY_HOTPLUG |
| 795 | static bool shadow_mapped(unsigned long addr) | ||
| 796 | { | ||
| 797 | pgd_t *pgd = pgd_offset_k(addr); | ||
| 798 | p4d_t *p4d; | ||
| 799 | pud_t *pud; | ||
| 800 | pmd_t *pmd; | ||
| 801 | pte_t *pte; | ||
| 802 | |||
| 803 | if (pgd_none(*pgd)) | ||
| 804 | return false; | ||
| 805 | p4d = p4d_offset(pgd, addr); | ||
| 806 | if (p4d_none(*p4d)) | ||
| 807 | return false; | ||
| 808 | pud = pud_offset(p4d, addr); | ||
| 809 | if (pud_none(*pud)) | ||
| 810 | return false; | ||
| 811 | |||
| 812 | /* | ||
| 813 | * We can't use pud_large() or pud_huge(), the first one is | ||
| 814 | * arch-specific, the last one depends on HUGETLB_PAGE. So let's abuse | ||
| 815 | * pud_bad(), if pud is bad then it's bad because it's huge. | ||
| 816 | */ | ||
| 817 | if (pud_bad(*pud)) | ||
| 818 | return true; | ||
| 819 | pmd = pmd_offset(pud, addr); | ||
| 820 | if (pmd_none(*pmd)) | ||
| 821 | return false; | ||
| 822 | |||
| 823 | if (pmd_bad(*pmd)) | ||
| 824 | return true; | ||
| 825 | pte = pte_offset_kernel(pmd, addr); | ||
| 826 | return !pte_none(*pte); | ||
| 827 | } | ||
| 828 | |||
| 795 | static int __meminit kasan_mem_notifier(struct notifier_block *nb, | 829 | static int __meminit kasan_mem_notifier(struct notifier_block *nb, |
| 796 | unsigned long action, void *data) | 830 | unsigned long action, void *data) |
| 797 | { | 831 | { |
| @@ -813,6 +847,14 @@ static int __meminit kasan_mem_notifier(struct notifier_block *nb, | |||
| 813 | case MEM_GOING_ONLINE: { | 847 | case MEM_GOING_ONLINE: { |
| 814 | void *ret; | 848 | void *ret; |
| 815 | 849 | ||
| 850 | /* | ||
| 851 | * If shadow is mapped already than it must have been mapped | ||
| 852 | * during the boot. This could happen if we onlining previously | ||
| 853 | * offlined memory. | ||
| 854 | */ | ||
| 855 | if (shadow_mapped(shadow_start)) | ||
| 856 | return NOTIFY_OK; | ||
| 857 | |||
| 816 | ret = __vmalloc_node_range(shadow_size, PAGE_SIZE, shadow_start, | 858 | ret = __vmalloc_node_range(shadow_size, PAGE_SIZE, shadow_start, |
| 817 | shadow_end, GFP_KERNEL, | 859 | shadow_end, GFP_KERNEL, |
| 818 | PAGE_KERNEL, VM_NO_GUARD, | 860 | PAGE_KERNEL, VM_NO_GUARD, |
| @@ -824,8 +866,25 @@ static int __meminit kasan_mem_notifier(struct notifier_block *nb, | |||
| 824 | kmemleak_ignore(ret); | 866 | kmemleak_ignore(ret); |
| 825 | return NOTIFY_OK; | 867 | return NOTIFY_OK; |
| 826 | } | 868 | } |
| 827 | case MEM_OFFLINE: | 869 | case MEM_OFFLINE: { |
| 828 | vfree((void *)shadow_start); | 870 | struct vm_struct *vm; |
| 871 | |||
| 872 | /* | ||
| 873 | * shadow_start was either mapped during boot by kasan_init() | ||
| 874 | * or during memory online by __vmalloc_node_range(). | ||
| 875 | * In the latter case we can use vfree() to free shadow. | ||
| 876 | * Non-NULL result of the find_vm_area() will tell us if | ||
| 877 | * that was the second case. | ||
| 878 | * | ||
| 879 | * Currently it's not possible to free shadow mapped | ||
| 880 | * during boot by kasan_init(). It's because the code | ||
| 881 | * to do that hasn't been written yet. So we'll just | ||
| 882 | * leak the memory. | ||
| 883 | */ | ||
| 884 | vm = find_vm_area((void *)shadow_start); | ||
| 885 | if (vm) | ||
| 886 | vfree((void *)shadow_start); | ||
| 887 | } | ||
| 829 | } | 888 | } |
| 830 | 889 | ||
| 831 | return NOTIFY_OK; | 890 | return NOTIFY_OK; |
