kasan: support backing vmalloc space with real shadow memory
Patch series "kasan: support backing vmalloc space with real shadow
memory", v11.
Currently, vmalloc space is backed by the early shadow page. This means
that kasan is incompatible with VMAP_STACK.
This series provides a mechanism to back vmalloc space with real,
dynamically allocated memory. I have only wired up x86, because that's
the only currently supported arch I can work with easily, but it's very
easy to wire up other architectures, and it appears that there is some
work-in-progress code to do this on arm64 and s390.
This has been discussed before in the context of VMAP_STACK:
- https://bugzilla.kernel.org/show_bug.cgi?id=202009
- https://lkml.org/lkml/2018/7/22/198
- https://lkml.org/lkml/2019/7/19/822
In terms of implementation details:
Most mappings in vmalloc space are small, requiring less than a full
page of shadow space. Allocating a full shadow page per mapping would
therefore be wasteful. Furthermore, to ensure that different mappings
use different shadow pages, mappings would have to be aligned to
KASAN_SHADOW_SCALE_SIZE * PAGE_SIZE.
Instead, share backing space across multiple mappings. Allocate a
backing page when a mapping in vmalloc space uses a particular page of
the shadow region. This page can be shared by other vmalloc mappings
later on.
We hook in to the vmap infrastructure to lazily clean up unused shadow
memory.
Testing with test_vmalloc.sh on an x86 VM with 2 vCPUs shows that:
- Turning on KASAN, inline instrumentation, without vmalloc, introuduces
a 4.1x-4.2x slowdown in vmalloc operations.
- Turning this on introduces the following slowdowns over KASAN:
* ~1.76x slower single-threaded (test_vmalloc.sh performance)
* ~2.18x slower when both cpus are performing operations
simultaneously (test_vmalloc.sh sequential_test_order=1)
This is unfortunate but given that this is a debug feature only, not the
end of the world. The benchmarks are also a stress-test for the vmalloc
subsystem: they're not indicative of an overall 2x slowdown!
This patch (of 4):
Hook into vmalloc and vmap, and dynamically allocate real shadow memory
to back the mappings.
Most mappings in vmalloc space are small, requiring less than a full
page of shadow space. Allocating a full shadow page per mapping would
therefore be wasteful. Furthermore, to ensure that different mappings
use different shadow pages, mappings would have to be aligned to
KASAN_SHADOW_SCALE_SIZE * PAGE_SIZE.
Instead, share backing space across multiple mappings. Allocate a
backing page when a mapping in vmalloc space uses a particular page of
the shadow region. This page can be shared by other vmalloc mappings
later on.
We hook in to the vmap infrastructure to lazily clean up unused shadow
memory.
To avoid the difficulties around swapping mappings around, this code
expects that the part of the shadow region that covers the vmalloc space
will not be covered by the early shadow page, but will be left unmapped.
This will require changes in arch-specific code.
This allows KASAN with VMAP_STACK, and may be helpful for architectures
that do not have a separate module space (e.g. powerpc64, which I am
currently working on). It also allows relaxing the module alignment
back to PAGE_SIZE.
Testing with test_vmalloc.sh on an x86 VM with 2 vCPUs shows that:
- Turning on KASAN, inline instrumentation, without vmalloc, introuduces
a 4.1x-4.2x slowdown in vmalloc operations.
- Turning this on introduces the following slowdowns over KASAN:
* ~1.76x slower single-threaded (test_vmalloc.sh performance)
* ~2.18x slower when both cpus are performing operations
simultaneously (test_vmalloc.sh sequential_test_order=3D1)
This is unfortunate but given that this is a debug feature only, not the
end of the world.
The full benchmark results are:
Performance
No KASAN KASAN original x baseline KASAN vmalloc x baseline x KASAN
fix_size_alloc_test 662004 11404956 17.23 19144610 28.92 1.68
full_fit_alloc_test 710950 12029752 16.92 13184651 18.55 1.10
long_busy_list_alloc_test 9431875 43990172 4.66 82970178 8.80 1.89
random_size_alloc_test 5033626 23061762 4.58 47158834 9.37 2.04
fix_align_alloc_test 1252514 15276910 12.20 31266116 24.96 2.05
random_size_align_alloc_te 1648501 14578321 8.84 25560052 15.51 1.75
align_shift_alloc_test 147 830 5.65 5692 38.72 6.86
pcpu_alloc_test 80732 125520 1.55 140864 1.74 1.12
Total Cycles 119240774314 763211341128 6.40 1390338696894 11.66 1.82
Sequential, 2 cpus
No KASAN KASAN original x baseline KASAN vmalloc x baseline x KASAN
fix_size_alloc_test 1423150
14276550 10.03 27733022 19.49 1.94
full_fit_alloc_test 1754219 14722640 8.39 15030786 8.57 1.02
long_busy_list_alloc_test 11451858 52154973 4.55 107016027 9.34 2.05
random_size_alloc_test 5989020 26735276 4.46 68885923 11.50 2.58
fix_align_alloc_test 2050976 20166900 9.83 50491675 24.62 2.50
random_size_align_alloc_te 2858229 17971700 6.29 38730225 13.55 2.16
align_shift_alloc_test 405 6428 15.87 26253 64.82 4.08
pcpu_alloc_test 127183 151464 1.19 216263 1.70 1.43
Total Cycles 54181269392 308723699764 5.70 650772566394 12.01 2.11
fix_size_alloc_test 1420404 14289308 10.06 27790035 19.56 1.94
full_fit_alloc_test 1736145 14806234 8.53 15274301 8.80 1.03
long_busy_list_alloc_test 11404638 52270785 4.58 107550254 9.43 2.06
random_size_alloc_test 6017006 26650625 4.43 68696127 11.42 2.58
fix_align_alloc_test 2045504 20280985 9.91 50414862 24.65 2.49
random_size_align_alloc_te 2845338 17931018 6.30 38510276 13.53 2.15
align_shift_alloc_test 472 3760 7.97 9656 20.46 2.57
pcpu_alloc_test 118643 132732 1.12 146504 1.23 1.10
Total Cycles 54040011688 309102805492 5.72 651325675652 12.05 2.11
[dja@axtens.net: fixups]
Link: http://lkml.kernel.org/r/20191120052719.7201-1-dja@axtens.net
Link: https://bugzilla.kernel.org/show_bug.cgi?id=3D202009
Link: http://lkml.kernel.org/r/20191031093909.9228-2-dja@axtens.net
Signed-off-by: Mark Rutland <mark.rutland@arm.com> [shadow rework]
Signed-off-by: Daniel Axtens <dja@axtens.net>
Co-developed-by: Mark Rutland <mark.rutland@arm.com>
Acked-by: Vasily Gorbik <gor@linux.ibm.com>
Reviewed-by: Andrey Ryabinin <aryabinin@virtuozzo.com>
Cc: Alexander Potapenko <glider@google.com>
Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: Christophe Leroy <christophe.leroy@c-s.fr>
Cc: Qian Cai <cai@lca.pw>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
parent
e36176be1c
commit
3c5c3cfb9e
|
@ -218,3 +218,66 @@ brk handler is used to print bug reports.
|
|||
A potential expansion of this mode is a hardware tag-based mode, which would
|
||||
use hardware memory tagging support instead of compiler instrumentation and
|
||||
manual shadow memory manipulation.
|
||||
|
||||
What memory accesses are sanitised by KASAN?
|
||||
--------------------------------------------
|
||||
|
||||
The kernel maps memory in a number of different parts of the address
|
||||
space. This poses something of a problem for KASAN, which requires
|
||||
that all addresses accessed by instrumented code have a valid shadow
|
||||
region.
|
||||
|
||||
The range of kernel virtual addresses is large: there is not enough
|
||||
real memory to support a real shadow region for every address that
|
||||
could be accessed by the kernel.
|
||||
|
||||
By default
|
||||
~~~~~~~~~~
|
||||
|
||||
By default, architectures only map real memory over the shadow region
|
||||
for the linear mapping (and potentially other small areas). For all
|
||||
other areas - such as vmalloc and vmemmap space - a single read-only
|
||||
page is mapped over the shadow area. This read-only shadow page
|
||||
declares all memory accesses as permitted.
|
||||
|
||||
This presents a problem for modules: they do not live in the linear
|
||||
mapping, but in a dedicated module space. By hooking in to the module
|
||||
allocator, KASAN can temporarily map real shadow memory to cover
|
||||
them. This allows detection of invalid accesses to module globals, for
|
||||
example.
|
||||
|
||||
This also creates an incompatibility with ``VMAP_STACK``: if the stack
|
||||
lives in vmalloc space, it will be shadowed by the read-only page, and
|
||||
the kernel will fault when trying to set up the shadow data for stack
|
||||
variables.
|
||||
|
||||
CONFIG_KASAN_VMALLOC
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
With ``CONFIG_KASAN_VMALLOC``, KASAN can cover vmalloc space at the
|
||||
cost of greater memory usage. Currently this is only supported on x86.
|
||||
|
||||
This works by hooking into vmalloc and vmap, and dynamically
|
||||
allocating real shadow memory to back the mappings.
|
||||
|
||||
Most mappings in vmalloc space are small, requiring less than a full
|
||||
page of shadow space. Allocating a full shadow page per mapping would
|
||||
therefore be wasteful. Furthermore, to ensure that different mappings
|
||||
use different shadow pages, mappings would have to be aligned to
|
||||
``KASAN_SHADOW_SCALE_SIZE * PAGE_SIZE``.
|
||||
|
||||
Instead, we share backing space across multiple mappings. We allocate
|
||||
a backing page when a mapping in vmalloc space uses a particular page
|
||||
of the shadow region. This page can be shared by other vmalloc
|
||||
mappings later on.
|
||||
|
||||
We hook in to the vmap infrastructure to lazily clean up unused shadow
|
||||
memory.
|
||||
|
||||
To avoid the difficulties around swapping mappings around, we expect
|
||||
that the part of the shadow region that covers the vmalloc space will
|
||||
not be covered by the early shadow page, but will be left
|
||||
unmapped. This will require changes in arch-specific code.
|
||||
|
||||
This allows ``VMAP_STACK`` support on x86, and can simplify support of
|
||||
architectures that do not have a fixed module region.
|
||||
|
|
|
@ -70,8 +70,18 @@ struct kasan_cache {
|
|||
int free_meta_offset;
|
||||
};
|
||||
|
||||
/*
|
||||
* These functions provide a special case to support backing module
|
||||
* allocations with real shadow memory. With KASAN vmalloc, the special
|
||||
* case is unnecessary, as the work is handled in the generic case.
|
||||
*/
|
||||
#ifndef CONFIG_KASAN_VMALLOC
|
||||
int kasan_module_alloc(void *addr, size_t size);
|
||||
void kasan_free_shadow(const struct vm_struct *vm);
|
||||
#else
|
||||
static inline int kasan_module_alloc(void *addr, size_t size) { return 0; }
|
||||
static inline void kasan_free_shadow(const struct vm_struct *vm) {}
|
||||
#endif
|
||||
|
||||
int kasan_add_zero_shadow(void *start, unsigned long size);
|
||||
void kasan_remove_zero_shadow(void *start, unsigned long size);
|
||||
|
@ -194,4 +204,25 @@ static inline void *kasan_reset_tag(const void *addr)
|
|||
|
||||
#endif /* CONFIG_KASAN_SW_TAGS */
|
||||
|
||||
#ifdef CONFIG_KASAN_VMALLOC
|
||||
int kasan_populate_vmalloc(unsigned long requested_size,
|
||||
struct vm_struct *area);
|
||||
void kasan_poison_vmalloc(void *start, unsigned long size);
|
||||
void kasan_release_vmalloc(unsigned long start, unsigned long end,
|
||||
unsigned long free_region_start,
|
||||
unsigned long free_region_end);
|
||||
#else
|
||||
static inline int kasan_populate_vmalloc(unsigned long requested_size,
|
||||
struct vm_struct *area)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void kasan_poison_vmalloc(void *start, unsigned long size) {}
|
||||
static inline void kasan_release_vmalloc(unsigned long start,
|
||||
unsigned long end,
|
||||
unsigned long free_region_start,
|
||||
unsigned long free_region_end) {}
|
||||
#endif
|
||||
|
||||
#endif /* LINUX_KASAN_H */
|
||||
|
|
|
@ -91,7 +91,7 @@ void module_arch_cleanup(struct module *mod);
|
|||
/* Any cleanup before freeing mod->module_init */
|
||||
void module_arch_freeing_init(struct module *mod);
|
||||
|
||||
#ifdef CONFIG_KASAN
|
||||
#if defined(CONFIG_KASAN) && !defined(CONFIG_KASAN_VMALLOC)
|
||||
#include <linux/kasan.h>
|
||||
#define MODULE_ALIGN (PAGE_SIZE << KASAN_SHADOW_SCALE_SHIFT)
|
||||
#else
|
||||
|
|
|
@ -22,6 +22,18 @@ struct notifier_block; /* in notifier.h */
|
|||
#define VM_UNINITIALIZED 0x00000020 /* vm_struct is not fully initialized */
|
||||
#define VM_NO_GUARD 0x00000040 /* don't add guard page */
|
||||
#define VM_KASAN 0x00000080 /* has allocated kasan shadow memory */
|
||||
|
||||
/*
|
||||
* VM_KASAN is used slighly differently depending on CONFIG_KASAN_VMALLOC.
|
||||
*
|
||||
* If IS_ENABLED(CONFIG_KASAN_VMALLOC), VM_KASAN is set on a vm_struct after
|
||||
* shadow memory has been mapped. It's used to handle allocation errors so that
|
||||
* we don't try to poision shadow on free if it was never allocated.
|
||||
*
|
||||
* Otherwise, VM_KASAN is set for kasan_module_alloc() allocations and used to
|
||||
* determine which allocations need the module shadow freed.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Memory with VM_FLUSH_RESET_PERMS cannot be freed in an interrupt or with
|
||||
* vfree_atomic().
|
||||
|
|
|
@ -6,6 +6,9 @@ config HAVE_ARCH_KASAN
|
|||
config HAVE_ARCH_KASAN_SW_TAGS
|
||||
bool
|
||||
|
||||
config HAVE_ARCH_KASAN_VMALLOC
|
||||
bool
|
||||
|
||||
config CC_HAS_KASAN_GENERIC
|
||||
def_bool $(cc-option, -fsanitize=kernel-address)
|
||||
|
||||
|
@ -142,6 +145,19 @@ config KASAN_SW_TAGS_IDENTIFY
|
|||
(use-after-free or out-of-bounds) at the cost of increased
|
||||
memory consumption.
|
||||
|
||||
config KASAN_VMALLOC
|
||||
bool "Back mappings in vmalloc space with real shadow memory"
|
||||
depends on KASAN && HAVE_ARCH_KASAN_VMALLOC
|
||||
help
|
||||
By default, the shadow region for vmalloc space is the read-only
|
||||
zero page. This means that KASAN cannot detect errors involving
|
||||
vmalloc space.
|
||||
|
||||
Enabling this option will hook in to vmap/vmalloc and back those
|
||||
mappings with real shadow memory allocated on demand. This allows
|
||||
for KASAN to detect more sorts of errors (and to support vmapped
|
||||
stacks), but at the cost of higher memory usage.
|
||||
|
||||
config TEST_KASAN
|
||||
tristate "Module for testing KASAN for bug detection"
|
||||
depends on m && KASAN
|
||||
|
|
|
@ -36,6 +36,8 @@
|
|||
#include <linux/bug.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#include <asm/tlbflush.h>
|
||||
|
||||
#include "kasan.h"
|
||||
#include "../slab.h"
|
||||
|
||||
|
@ -590,6 +592,7 @@ void kasan_kfree_large(void *ptr, unsigned long ip)
|
|||
/* The object will be poisoned by page_alloc. */
|
||||
}
|
||||
|
||||
#ifndef CONFIG_KASAN_VMALLOC
|
||||
int kasan_module_alloc(void *addr, size_t size)
|
||||
{
|
||||
void *ret;
|
||||
|
@ -625,6 +628,7 @@ void kasan_free_shadow(const struct vm_struct *vm)
|
|||
if (vm->flags & VM_KASAN)
|
||||
vfree(kasan_mem_to_shadow(vm->addr));
|
||||
}
|
||||
#endif
|
||||
|
||||
extern void __kasan_report(unsigned long addr, size_t size, bool is_write, unsigned long ip);
|
||||
|
||||
|
@ -744,3 +748,232 @@ static int __init kasan_memhotplug_init(void)
|
|||
|
||||
core_initcall(kasan_memhotplug_init);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_KASAN_VMALLOC
|
||||
static int kasan_populate_vmalloc_pte(pte_t *ptep, unsigned long addr,
|
||||
void *unused)
|
||||
{
|
||||
unsigned long page;
|
||||
pte_t pte;
|
||||
|
||||
if (likely(!pte_none(*ptep)))
|
||||
return 0;
|
||||
|
||||
page = __get_free_page(GFP_KERNEL);
|
||||
if (!page)
|
||||
return -ENOMEM;
|
||||
|
||||
memset((void *)page, KASAN_VMALLOC_INVALID, PAGE_SIZE);
|
||||
pte = pfn_pte(PFN_DOWN(__pa(page)), PAGE_KERNEL);
|
||||
|
||||
spin_lock(&init_mm.page_table_lock);
|
||||
if (likely(pte_none(*ptep))) {
|
||||
set_pte_at(&init_mm, addr, ptep, pte);
|
||||
page = 0;
|
||||
}
|
||||
spin_unlock(&init_mm.page_table_lock);
|
||||
if (page)
|
||||
free_page(page);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int kasan_populate_vmalloc(unsigned long requested_size, struct vm_struct *area)
|
||||
{
|
||||
unsigned long shadow_start, shadow_end;
|
||||
int ret;
|
||||
|
||||
shadow_start = (unsigned long)kasan_mem_to_shadow(area->addr);
|
||||
shadow_start = ALIGN_DOWN(shadow_start, PAGE_SIZE);
|
||||
shadow_end = (unsigned long)kasan_mem_to_shadow(area->addr +
|
||||
area->size);
|
||||
shadow_end = ALIGN(shadow_end, PAGE_SIZE);
|
||||
|
||||
ret = apply_to_page_range(&init_mm, shadow_start,
|
||||
shadow_end - shadow_start,
|
||||
kasan_populate_vmalloc_pte, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
flush_cache_vmap(shadow_start, shadow_end);
|
||||
|
||||
kasan_unpoison_shadow(area->addr, requested_size);
|
||||
|
||||
area->flags |= VM_KASAN;
|
||||
|
||||
/*
|
||||
* We need to be careful about inter-cpu effects here. Consider:
|
||||
*
|
||||
* CPU#0 CPU#1
|
||||
* WRITE_ONCE(p, vmalloc(100)); while (x = READ_ONCE(p)) ;
|
||||
* p[99] = 1;
|
||||
*
|
||||
* With compiler instrumentation, that ends up looking like this:
|
||||
*
|
||||
* CPU#0 CPU#1
|
||||
* // vmalloc() allocates memory
|
||||
* // let a = area->addr
|
||||
* // we reach kasan_populate_vmalloc
|
||||
* // and call kasan_unpoison_shadow:
|
||||
* STORE shadow(a), unpoison_val
|
||||
* ...
|
||||
* STORE shadow(a+99), unpoison_val x = LOAD p
|
||||
* // rest of vmalloc process <data dependency>
|
||||
* STORE p, a LOAD shadow(x+99)
|
||||
*
|
||||
* If there is no barrier between the end of unpoisioning the shadow
|
||||
* and the store of the result to p, the stores could be committed
|
||||
* in a different order by CPU#0, and CPU#1 could erroneously observe
|
||||
* poison in the shadow.
|
||||
*
|
||||
* We need some sort of barrier between the stores.
|
||||
*
|
||||
* In the vmalloc() case, this is provided by a smp_wmb() in
|
||||
* clear_vm_uninitialized_flag(). In the per-cpu allocator and in
|
||||
* get_vm_area() and friends, the caller gets shadow allocated but
|
||||
* doesn't have any pages mapped into the virtual address space that
|
||||
* has been reserved. Mapping those pages in will involve taking and
|
||||
* releasing a page-table lock, which will provide the barrier.
|
||||
*/
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Poison the shadow for a vmalloc region. Called as part of the
|
||||
* freeing process at the time the region is freed.
|
||||
*/
|
||||
void kasan_poison_vmalloc(void *start, unsigned long size)
|
||||
{
|
||||
size = round_up(size, KASAN_SHADOW_SCALE_SIZE);
|
||||
kasan_poison_shadow(start, size, KASAN_VMALLOC_INVALID);
|
||||
}
|
||||
|
||||
static int kasan_depopulate_vmalloc_pte(pte_t *ptep, unsigned long addr,
|
||||
void *unused)
|
||||
{
|
||||
unsigned long page;
|
||||
|
||||
page = (unsigned long)__va(pte_pfn(*ptep) << PAGE_SHIFT);
|
||||
|
||||
spin_lock(&init_mm.page_table_lock);
|
||||
|
||||
if (likely(!pte_none(*ptep))) {
|
||||
pte_clear(&init_mm, addr, ptep);
|
||||
free_page(page);
|
||||
}
|
||||
spin_unlock(&init_mm.page_table_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Release the backing for the vmalloc region [start, end), which
|
||||
* lies within the free region [free_region_start, free_region_end).
|
||||
*
|
||||
* This can be run lazily, long after the region was freed. It runs
|
||||
* under vmap_area_lock, so it's not safe to interact with the vmalloc/vmap
|
||||
* infrastructure.
|
||||
*
|
||||
* How does this work?
|
||||
* -------------------
|
||||
*
|
||||
* We have a region that is page aligned, labelled as A.
|
||||
* That might not map onto the shadow in a way that is page-aligned:
|
||||
*
|
||||
* start end
|
||||
* v v
|
||||
* |????????|????????|AAAAAAAA|AA....AA|AAAAAAAA|????????| < vmalloc
|
||||
* -------- -------- -------- -------- --------
|
||||
* | | | | |
|
||||
* | | | /-------/ |
|
||||
* \-------\|/------/ |/---------------/
|
||||
* ||| ||
|
||||
* |??AAAAAA|AAAAAAAA|AA??????| < shadow
|
||||
* (1) (2) (3)
|
||||
*
|
||||
* First we align the start upwards and the end downwards, so that the
|
||||
* shadow of the region aligns with shadow page boundaries. In the
|
||||
* example, this gives us the shadow page (2). This is the shadow entirely
|
||||
* covered by this allocation.
|
||||
*
|
||||
* Then we have the tricky bits. We want to know if we can free the
|
||||
* partially covered shadow pages - (1) and (3) in the example. For this,
|
||||
* we are given the start and end of the free region that contains this
|
||||
* allocation. Extending our previous example, we could have:
|
||||
*
|
||||
* free_region_start free_region_end
|
||||
* | start end |
|
||||
* v v v v
|
||||
* |FFFFFFFF|FFFFFFFF|AAAAAAAA|AA....AA|AAAAAAAA|FFFFFFFF| < vmalloc
|
||||
* -------- -------- -------- -------- --------
|
||||
* | | | | |
|
||||
* | | | /-------/ |
|
||||
* \-------\|/------/ |/---------------/
|
||||
* ||| ||
|
||||
* |FFAAAAAA|AAAAAAAA|AAF?????| < shadow
|
||||
* (1) (2) (3)
|
||||
*
|
||||
* Once again, we align the start of the free region up, and the end of
|
||||
* the free region down so that the shadow is page aligned. So we can free
|
||||
* page (1) - we know no allocation currently uses anything in that page,
|
||||
* because all of it is in the vmalloc free region. But we cannot free
|
||||
* page (3), because we can't be sure that the rest of it is unused.
|
||||
*
|
||||
* We only consider pages that contain part of the original region for
|
||||
* freeing: we don't try to free other pages from the free region or we'd
|
||||
* end up trying to free huge chunks of virtual address space.
|
||||
*
|
||||
* Concurrency
|
||||
* -----------
|
||||
*
|
||||
* How do we know that we're not freeing a page that is simultaneously
|
||||
* being used for a fresh allocation in kasan_populate_vmalloc(_pte)?
|
||||
*
|
||||
* We _can_ have kasan_release_vmalloc and kasan_populate_vmalloc running
|
||||
* at the same time. While we run under free_vmap_area_lock, the population
|
||||
* code does not.
|
||||
*
|
||||
* free_vmap_area_lock instead operates to ensure that the larger range
|
||||
* [free_region_start, free_region_end) is safe: because __alloc_vmap_area and
|
||||
* the per-cpu region-finding algorithm both run under free_vmap_area_lock,
|
||||
* no space identified as free will become used while we are running. This
|
||||
* means that so long as we are careful with alignment and only free shadow
|
||||
* pages entirely covered by the free region, we will not run in to any
|
||||
* trouble - any simultaneous allocations will be for disjoint regions.
|
||||
*/
|
||||
void kasan_release_vmalloc(unsigned long start, unsigned long end,
|
||||
unsigned long free_region_start,
|
||||
unsigned long free_region_end)
|
||||
{
|
||||
void *shadow_start, *shadow_end;
|
||||
unsigned long region_start, region_end;
|
||||
|
||||
region_start = ALIGN(start, PAGE_SIZE * KASAN_SHADOW_SCALE_SIZE);
|
||||
region_end = ALIGN_DOWN(end, PAGE_SIZE * KASAN_SHADOW_SCALE_SIZE);
|
||||
|
||||
free_region_start = ALIGN(free_region_start,
|
||||
PAGE_SIZE * KASAN_SHADOW_SCALE_SIZE);
|
||||
|
||||
if (start != region_start &&
|
||||
free_region_start < region_start)
|
||||
region_start -= PAGE_SIZE * KASAN_SHADOW_SCALE_SIZE;
|
||||
|
||||
free_region_end = ALIGN_DOWN(free_region_end,
|
||||
PAGE_SIZE * KASAN_SHADOW_SCALE_SIZE);
|
||||
|
||||
if (end != region_end &&
|
||||
free_region_end > region_end)
|
||||
region_end += PAGE_SIZE * KASAN_SHADOW_SCALE_SIZE;
|
||||
|
||||
shadow_start = kasan_mem_to_shadow((void *)region_start);
|
||||
shadow_end = kasan_mem_to_shadow((void *)region_end);
|
||||
|
||||
if (shadow_end > shadow_start) {
|
||||
apply_to_page_range(&init_mm, (unsigned long)shadow_start,
|
||||
(unsigned long)(shadow_end - shadow_start),
|
||||
kasan_depopulate_vmalloc_pte, NULL);
|
||||
flush_tlb_kernel_range((unsigned long)shadow_start,
|
||||
(unsigned long)shadow_end);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -86,6 +86,9 @@ static const char *get_shadow_bug_type(struct kasan_access_info *info)
|
|||
case KASAN_ALLOCA_RIGHT:
|
||||
bug_type = "alloca-out-of-bounds";
|
||||
break;
|
||||
case KASAN_VMALLOC_INVALID:
|
||||
bug_type = "vmalloc-out-of-bounds";
|
||||
break;
|
||||
}
|
||||
|
||||
return bug_type;
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#endif
|
||||
|
||||
#define KASAN_GLOBAL_REDZONE 0xFA /* redzone for global variable */
|
||||
#define KASAN_VMALLOC_INVALID 0xF9 /* unallocated space in vmapped page */
|
||||
|
||||
/*
|
||||
* Stack redzone shadow values
|
||||
|
|
56
mm/vmalloc.c
56
mm/vmalloc.c
|
@ -683,7 +683,7 @@ insert_vmap_area_augment(struct vmap_area *va,
|
|||
* free area is inserted. If VA has been merged, it is
|
||||
* freed.
|
||||
*/
|
||||
static __always_inline void
|
||||
static __always_inline struct vmap_area *
|
||||
merge_or_add_vmap_area(struct vmap_area *va,
|
||||
struct rb_root *root, struct list_head *head)
|
||||
{
|
||||
|
@ -750,7 +750,10 @@ merge_or_add_vmap_area(struct vmap_area *va,
|
|||
|
||||
/* Free vmap_area object. */
|
||||
kmem_cache_free(vmap_area_cachep, va);
|
||||
return;
|
||||
|
||||
/* Point to the new merged area. */
|
||||
va = sibling;
|
||||
merged = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -759,6 +762,8 @@ merge_or_add_vmap_area(struct vmap_area *va,
|
|||
link_va(va, root, parent, link, head);
|
||||
augment_tree_propagate_from(va);
|
||||
}
|
||||
|
||||
return va;
|
||||
}
|
||||
|
||||
static __always_inline bool
|
||||
|
@ -1196,8 +1201,7 @@ static void free_vmap_area(struct vmap_area *va)
|
|||
* Insert/Merge it back to the free tree/list.
|
||||
*/
|
||||
spin_lock(&free_vmap_area_lock);
|
||||
merge_or_add_vmap_area(va,
|
||||
&free_vmap_area_root, &free_vmap_area_list);
|
||||
merge_or_add_vmap_area(va, &free_vmap_area_root, &free_vmap_area_list);
|
||||
spin_unlock(&free_vmap_area_lock);
|
||||
}
|
||||
|
||||
|
@ -1294,14 +1298,20 @@ static bool __purge_vmap_area_lazy(unsigned long start, unsigned long end)
|
|||
spin_lock(&free_vmap_area_lock);
|
||||
llist_for_each_entry_safe(va, n_va, valist, purge_list) {
|
||||
unsigned long nr = (va->va_end - va->va_start) >> PAGE_SHIFT;
|
||||
unsigned long orig_start = va->va_start;
|
||||
unsigned long orig_end = va->va_end;
|
||||
|
||||
/*
|
||||
* Finally insert or merge lazily-freed area. It is
|
||||
* detached and there is no need to "unlink" it from
|
||||
* anything.
|
||||
*/
|
||||
merge_or_add_vmap_area(va,
|
||||
&free_vmap_area_root, &free_vmap_area_list);
|
||||
va = merge_or_add_vmap_area(va, &free_vmap_area_root,
|
||||
&free_vmap_area_list);
|
||||
|
||||
if (is_vmalloc_or_module_addr((void *)orig_start))
|
||||
kasan_release_vmalloc(orig_start, orig_end,
|
||||
va->va_start, va->va_end);
|
||||
|
||||
atomic_long_sub(nr, &vmap_lazy_nr);
|
||||
|
||||
|
@ -2090,6 +2100,22 @@ static struct vm_struct *__get_vm_area_node(unsigned long size,
|
|||
|
||||
setup_vmalloc_vm(area, va, flags, caller);
|
||||
|
||||
/*
|
||||
* For KASAN, if we are in vmalloc space, we need to cover the shadow
|
||||
* area with real memory. If we come here through VM_ALLOC, this is
|
||||
* done by a higher level function that has access to the true size,
|
||||
* which might not be a full page.
|
||||
*
|
||||
* We assume module space comes via VM_ALLOC path.
|
||||
*/
|
||||
if (is_vmalloc_addr(area->addr) && !(area->flags & VM_ALLOC)) {
|
||||
if (kasan_populate_vmalloc(area->size, area)) {
|
||||
unmap_vmap_area(va);
|
||||
kfree(area);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return area;
|
||||
}
|
||||
|
||||
|
@ -2267,6 +2293,9 @@ static void __vunmap(const void *addr, int deallocate_pages)
|
|||
debug_check_no_locks_freed(area->addr, get_vm_area_size(area));
|
||||
debug_check_no_obj_freed(area->addr, get_vm_area_size(area));
|
||||
|
||||
if (area->flags & VM_KASAN)
|
||||
kasan_poison_vmalloc(area->addr, area->size);
|
||||
|
||||
vm_remove_mappings(area, deallocate_pages);
|
||||
|
||||
if (deallocate_pages) {
|
||||
|
@ -2519,6 +2548,11 @@ void *__vmalloc_node_range(unsigned long size, unsigned long align,
|
|||
if (!addr)
|
||||
return NULL;
|
||||
|
||||
if (is_vmalloc_or_module_addr(area->addr)) {
|
||||
if (kasan_populate_vmalloc(real_size, area))
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* In this function, newly allocated vm_struct has VM_UNINITIALIZED
|
||||
* flag. It means that vm_struct is not fully initialized.
|
||||
|
@ -3400,6 +3434,12 @@ struct vm_struct **pcpu_get_vm_areas(const unsigned long *offsets,
|
|||
}
|
||||
spin_unlock(&vmap_area_lock);
|
||||
|
||||
/* populate the shadow space outside of the lock */
|
||||
for (area = 0; area < nr_vms; area++) {
|
||||
/* assume success here */
|
||||
kasan_populate_vmalloc(sizes[area], vms[area]);
|
||||
}
|
||||
|
||||
kfree(vas);
|
||||
return vms;
|
||||
|
||||
|
@ -3411,8 +3451,8 @@ struct vm_struct **pcpu_get_vm_areas(const unsigned long *offsets,
|
|||
* and when pcpu_get_vm_areas() is success.
|
||||
*/
|
||||
while (area--) {
|
||||
merge_or_add_vmap_area(vas[area],
|
||||
&free_vmap_area_root, &free_vmap_area_list);
|
||||
merge_or_add_vmap_area(vas[area], &free_vmap_area_root,
|
||||
&free_vmap_area_list);
|
||||
vas[area] = NULL;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue