mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-12 13:55:32 +00:00
![Andrey Konovalov](/assets/img/avatar_default.png)
A tag-based KASAN shadow memory cell contains a memory tag, that corresponds to the tag in the top byte of the pointer, that points to that memory. The native top byte value of kernel pointers is 0xff, so with tag-based KASAN we need to initialize shadow memory to 0xff. [cai@lca.pw: arm64: skip kmemleak for KASAN again\ Link: http://lkml.kernel.org/r/20181226020550.63712-1-cai@lca.pw Link: http://lkml.kernel.org/r/5cc1b789aad7c99cf4f3ec5b328b147ad53edb40.1544099024.git.andreyknvl@google.com Signed-off-by: Andrey Konovalov <andreyknvl@google.com> Reviewed-by: Andrey Ryabinin <aryabinin@virtuozzo.com> Reviewed-by: Dmitry Vyukov <dvyukov@google.com> Cc: Christoph Lameter <cl@linux.com> Cc: Mark Rutland <mark.rutland@arm.com> Cc: Will Deacon <will.deacon@arm.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
604 lines
15 KiB
C
604 lines
15 KiB
C
/*
|
|
* This file contains common generic and tag-based KASAN code.
|
|
*
|
|
* Copyright (c) 2014 Samsung Electronics Co., Ltd.
|
|
* Author: Andrey Ryabinin <ryabinin.a.a@gmail.com>
|
|
*
|
|
* Some code borrowed from https://github.com/xairy/kasan-prototype by
|
|
* Andrey Konovalov <andreyknvl@gmail.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
*/
|
|
|
|
#include <linux/export.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kasan.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/kmemleak.h>
|
|
#include <linux/linkage.h>
|
|
#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/sched/task_stack.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/stacktrace.h>
|
|
#include <linux/string.h>
|
|
#include <linux/types.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/bug.h>
|
|
|
|
#include "kasan.h"
|
|
#include "../slab.h"
|
|
|
|
static inline int in_irqentry_text(unsigned long ptr)
|
|
{
|
|
return (ptr >= (unsigned long)&__irqentry_text_start &&
|
|
ptr < (unsigned long)&__irqentry_text_end) ||
|
|
(ptr >= (unsigned long)&__softirqentry_text_start &&
|
|
ptr < (unsigned long)&__softirqentry_text_end);
|
|
}
|
|
|
|
static inline void filter_irq_stacks(struct stack_trace *trace)
|
|
{
|
|
int i;
|
|
|
|
if (!trace->nr_entries)
|
|
return;
|
|
for (i = 0; i < trace->nr_entries; i++)
|
|
if (in_irqentry_text(trace->entries[i])) {
|
|
/* Include the irqentry function into the stack. */
|
|
trace->nr_entries = i + 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static inline depot_stack_handle_t save_stack(gfp_t flags)
|
|
{
|
|
unsigned long entries[KASAN_STACK_DEPTH];
|
|
struct stack_trace trace = {
|
|
.nr_entries = 0,
|
|
.entries = entries,
|
|
.max_entries = KASAN_STACK_DEPTH,
|
|
.skip = 0
|
|
};
|
|
|
|
save_stack_trace(&trace);
|
|
filter_irq_stacks(&trace);
|
|
if (trace.nr_entries != 0 &&
|
|
trace.entries[trace.nr_entries-1] == ULONG_MAX)
|
|
trace.nr_entries--;
|
|
|
|
return depot_save_stack(&trace, flags);
|
|
}
|
|
|
|
static inline void set_track(struct kasan_track *track, gfp_t flags)
|
|
{
|
|
track->pid = current->pid;
|
|
track->stack = save_stack(flags);
|
|
}
|
|
|
|
void kasan_enable_current(void)
|
|
{
|
|
current->kasan_depth++;
|
|
}
|
|
|
|
void kasan_disable_current(void)
|
|
{
|
|
current->kasan_depth--;
|
|
}
|
|
|
|
void kasan_check_read(const volatile void *p, unsigned int size)
|
|
{
|
|
check_memory_region((unsigned long)p, size, false, _RET_IP_);
|
|
}
|
|
EXPORT_SYMBOL(kasan_check_read);
|
|
|
|
void kasan_check_write(const volatile void *p, unsigned int size)
|
|
{
|
|
check_memory_region((unsigned long)p, size, true, _RET_IP_);
|
|
}
|
|
EXPORT_SYMBOL(kasan_check_write);
|
|
|
|
#undef memset
|
|
void *memset(void *addr, int c, size_t len)
|
|
{
|
|
check_memory_region((unsigned long)addr, len, true, _RET_IP_);
|
|
|
|
return __memset(addr, c, len);
|
|
}
|
|
|
|
#undef memmove
|
|
void *memmove(void *dest, const void *src, size_t len)
|
|
{
|
|
check_memory_region((unsigned long)src, len, false, _RET_IP_);
|
|
check_memory_region((unsigned long)dest, len, true, _RET_IP_);
|
|
|
|
return __memmove(dest, src, len);
|
|
}
|
|
|
|
#undef memcpy
|
|
void *memcpy(void *dest, const void *src, size_t len)
|
|
{
|
|
check_memory_region((unsigned long)src, len, false, _RET_IP_);
|
|
check_memory_region((unsigned long)dest, len, true, _RET_IP_);
|
|
|
|
return __memcpy(dest, src, len);
|
|
}
|
|
|
|
/*
|
|
* Poisons the shadow memory for 'size' bytes starting from 'addr'.
|
|
* Memory addresses should be aligned to KASAN_SHADOW_SCALE_SIZE.
|
|
*/
|
|
void kasan_poison_shadow(const void *address, size_t size, u8 value)
|
|
{
|
|
void *shadow_start, *shadow_end;
|
|
|
|
shadow_start = kasan_mem_to_shadow(address);
|
|
shadow_end = kasan_mem_to_shadow(address + size);
|
|
|
|
__memset(shadow_start, value, shadow_end - shadow_start);
|
|
}
|
|
|
|
void kasan_unpoison_shadow(const void *address, size_t size)
|
|
{
|
|
kasan_poison_shadow(address, size, 0);
|
|
|
|
if (size & KASAN_SHADOW_MASK) {
|
|
u8 *shadow = (u8 *)kasan_mem_to_shadow(address + size);
|
|
*shadow = size & KASAN_SHADOW_MASK;
|
|
}
|
|
}
|
|
|
|
static void __kasan_unpoison_stack(struct task_struct *task, const void *sp)
|
|
{
|
|
void *base = task_stack_page(task);
|
|
size_t size = sp - base;
|
|
|
|
kasan_unpoison_shadow(base, size);
|
|
}
|
|
|
|
/* Unpoison the entire stack for a task. */
|
|
void kasan_unpoison_task_stack(struct task_struct *task)
|
|
{
|
|
__kasan_unpoison_stack(task, task_stack_page(task) + THREAD_SIZE);
|
|
}
|
|
|
|
/* Unpoison the stack for the current task beyond a watermark sp value. */
|
|
asmlinkage void kasan_unpoison_task_stack_below(const void *watermark)
|
|
{
|
|
/*
|
|
* Calculate the task stack base address. Avoid using 'current'
|
|
* because this function is called by early resume code which hasn't
|
|
* yet set up the percpu register (%gs).
|
|
*/
|
|
void *base = (void *)((unsigned long)watermark & ~(THREAD_SIZE - 1));
|
|
|
|
kasan_unpoison_shadow(base, watermark - base);
|
|
}
|
|
|
|
/*
|
|
* Clear all poison for the region between the current SP and a provided
|
|
* watermark value, as is sometimes required prior to hand-crafted asm function
|
|
* returns in the middle of functions.
|
|
*/
|
|
void kasan_unpoison_stack_above_sp_to(const void *watermark)
|
|
{
|
|
const void *sp = __builtin_frame_address(0);
|
|
size_t size = watermark - sp;
|
|
|
|
if (WARN_ON(sp > watermark))
|
|
return;
|
|
kasan_unpoison_shadow(sp, size);
|
|
}
|
|
|
|
void kasan_alloc_pages(struct page *page, unsigned int order)
|
|
{
|
|
if (likely(!PageHighMem(page)))
|
|
kasan_unpoison_shadow(page_address(page), PAGE_SIZE << order);
|
|
}
|
|
|
|
void kasan_free_pages(struct page *page, unsigned int order)
|
|
{
|
|
if (likely(!PageHighMem(page)))
|
|
kasan_poison_shadow(page_address(page),
|
|
PAGE_SIZE << order,
|
|
KASAN_FREE_PAGE);
|
|
}
|
|
|
|
/*
|
|
* Adaptive redzone policy taken from the userspace AddressSanitizer runtime.
|
|
* For larger allocations larger redzones are used.
|
|
*/
|
|
static inline unsigned int optimal_redzone(unsigned int object_size)
|
|
{
|
|
return
|
|
object_size <= 64 - 16 ? 16 :
|
|
object_size <= 128 - 32 ? 32 :
|
|
object_size <= 512 - 64 ? 64 :
|
|
object_size <= 4096 - 128 ? 128 :
|
|
object_size <= (1 << 14) - 256 ? 256 :
|
|
object_size <= (1 << 15) - 512 ? 512 :
|
|
object_size <= (1 << 16) - 1024 ? 1024 : 2048;
|
|
}
|
|
|
|
void kasan_cache_create(struct kmem_cache *cache, unsigned int *size,
|
|
slab_flags_t *flags)
|
|
{
|
|
unsigned int orig_size = *size;
|
|
int redzone_adjust;
|
|
|
|
/* Add alloc meta. */
|
|
cache->kasan_info.alloc_meta_offset = *size;
|
|
*size += sizeof(struct kasan_alloc_meta);
|
|
|
|
/* Add free meta. */
|
|
if (cache->flags & SLAB_TYPESAFE_BY_RCU || cache->ctor ||
|
|
cache->object_size < sizeof(struct kasan_free_meta)) {
|
|
cache->kasan_info.free_meta_offset = *size;
|
|
*size += sizeof(struct kasan_free_meta);
|
|
}
|
|
redzone_adjust = optimal_redzone(cache->object_size) -
|
|
(*size - cache->object_size);
|
|
|
|
if (redzone_adjust > 0)
|
|
*size += redzone_adjust;
|
|
|
|
*size = min_t(unsigned int, KMALLOC_MAX_SIZE,
|
|
max(*size, cache->object_size +
|
|
optimal_redzone(cache->object_size)));
|
|
|
|
/*
|
|
* If the metadata doesn't fit, don't enable KASAN at all.
|
|
*/
|
|
if (*size <= cache->kasan_info.alloc_meta_offset ||
|
|
*size <= cache->kasan_info.free_meta_offset) {
|
|
cache->kasan_info.alloc_meta_offset = 0;
|
|
cache->kasan_info.free_meta_offset = 0;
|
|
*size = orig_size;
|
|
return;
|
|
}
|
|
|
|
*flags |= SLAB_KASAN;
|
|
}
|
|
|
|
size_t kasan_metadata_size(struct kmem_cache *cache)
|
|
{
|
|
return (cache->kasan_info.alloc_meta_offset ?
|
|
sizeof(struct kasan_alloc_meta) : 0) +
|
|
(cache->kasan_info.free_meta_offset ?
|
|
sizeof(struct kasan_free_meta) : 0);
|
|
}
|
|
|
|
struct kasan_alloc_meta *get_alloc_info(struct kmem_cache *cache,
|
|
const void *object)
|
|
{
|
|
BUILD_BUG_ON(sizeof(struct kasan_alloc_meta) > 32);
|
|
return (void *)object + cache->kasan_info.alloc_meta_offset;
|
|
}
|
|
|
|
struct kasan_free_meta *get_free_info(struct kmem_cache *cache,
|
|
const void *object)
|
|
{
|
|
BUILD_BUG_ON(sizeof(struct kasan_free_meta) > 32);
|
|
return (void *)object + cache->kasan_info.free_meta_offset;
|
|
}
|
|
|
|
void kasan_poison_slab(struct page *page)
|
|
{
|
|
kasan_poison_shadow(page_address(page),
|
|
PAGE_SIZE << compound_order(page),
|
|
KASAN_KMALLOC_REDZONE);
|
|
}
|
|
|
|
void kasan_unpoison_object_data(struct kmem_cache *cache, void *object)
|
|
{
|
|
kasan_unpoison_shadow(object, cache->object_size);
|
|
}
|
|
|
|
void kasan_poison_object_data(struct kmem_cache *cache, void *object)
|
|
{
|
|
kasan_poison_shadow(object,
|
|
round_up(cache->object_size, KASAN_SHADOW_SCALE_SIZE),
|
|
KASAN_KMALLOC_REDZONE);
|
|
}
|
|
|
|
void *kasan_init_slab_obj(struct kmem_cache *cache, const void *object)
|
|
{
|
|
struct kasan_alloc_meta *alloc_info;
|
|
|
|
if (!(cache->flags & SLAB_KASAN))
|
|
return (void *)object;
|
|
|
|
alloc_info = get_alloc_info(cache, object);
|
|
__memset(alloc_info, 0, sizeof(*alloc_info));
|
|
|
|
return (void *)object;
|
|
}
|
|
|
|
void *kasan_slab_alloc(struct kmem_cache *cache, void *object, gfp_t flags)
|
|
{
|
|
return kasan_kmalloc(cache, object, cache->object_size, flags);
|
|
}
|
|
|
|
static bool __kasan_slab_free(struct kmem_cache *cache, void *object,
|
|
unsigned long ip, bool quarantine)
|
|
{
|
|
s8 shadow_byte;
|
|
unsigned long rounded_up_size;
|
|
|
|
if (unlikely(nearest_obj(cache, virt_to_head_page(object), object) !=
|
|
object)) {
|
|
kasan_report_invalid_free(object, ip);
|
|
return true;
|
|
}
|
|
|
|
/* RCU slabs could be legally used after free within the RCU period */
|
|
if (unlikely(cache->flags & SLAB_TYPESAFE_BY_RCU))
|
|
return false;
|
|
|
|
shadow_byte = READ_ONCE(*(s8 *)kasan_mem_to_shadow(object));
|
|
if (shadow_byte < 0 || shadow_byte >= KASAN_SHADOW_SCALE_SIZE) {
|
|
kasan_report_invalid_free(object, ip);
|
|
return true;
|
|
}
|
|
|
|
rounded_up_size = round_up(cache->object_size, KASAN_SHADOW_SCALE_SIZE);
|
|
kasan_poison_shadow(object, rounded_up_size, KASAN_KMALLOC_FREE);
|
|
|
|
if (!quarantine || unlikely(!(cache->flags & SLAB_KASAN)))
|
|
return false;
|
|
|
|
set_track(&get_alloc_info(cache, object)->free_track, GFP_NOWAIT);
|
|
quarantine_put(get_free_info(cache, object), cache);
|
|
return true;
|
|
}
|
|
|
|
bool kasan_slab_free(struct kmem_cache *cache, void *object, unsigned long ip)
|
|
{
|
|
return __kasan_slab_free(cache, object, ip, true);
|
|
}
|
|
|
|
void *kasan_kmalloc(struct kmem_cache *cache, const void *object, size_t size,
|
|
gfp_t flags)
|
|
{
|
|
unsigned long redzone_start;
|
|
unsigned long redzone_end;
|
|
|
|
if (gfpflags_allow_blocking(flags))
|
|
quarantine_reduce();
|
|
|
|
if (unlikely(object == NULL))
|
|
return NULL;
|
|
|
|
redzone_start = round_up((unsigned long)(object + size),
|
|
KASAN_SHADOW_SCALE_SIZE);
|
|
redzone_end = round_up((unsigned long)object + cache->object_size,
|
|
KASAN_SHADOW_SCALE_SIZE);
|
|
|
|
kasan_unpoison_shadow(object, size);
|
|
kasan_poison_shadow((void *)redzone_start, redzone_end - redzone_start,
|
|
KASAN_KMALLOC_REDZONE);
|
|
|
|
if (cache->flags & SLAB_KASAN)
|
|
set_track(&get_alloc_info(cache, object)->alloc_track, flags);
|
|
|
|
return (void *)object;
|
|
}
|
|
EXPORT_SYMBOL(kasan_kmalloc);
|
|
|
|
void *kasan_kmalloc_large(const void *ptr, size_t size, gfp_t flags)
|
|
{
|
|
struct page *page;
|
|
unsigned long redzone_start;
|
|
unsigned long redzone_end;
|
|
|
|
if (gfpflags_allow_blocking(flags))
|
|
quarantine_reduce();
|
|
|
|
if (unlikely(ptr == NULL))
|
|
return NULL;
|
|
|
|
page = virt_to_page(ptr);
|
|
redzone_start = round_up((unsigned long)(ptr + size),
|
|
KASAN_SHADOW_SCALE_SIZE);
|
|
redzone_end = (unsigned long)ptr + (PAGE_SIZE << compound_order(page));
|
|
|
|
kasan_unpoison_shadow(ptr, size);
|
|
kasan_poison_shadow((void *)redzone_start, redzone_end - redzone_start,
|
|
KASAN_PAGE_REDZONE);
|
|
|
|
return (void *)ptr;
|
|
}
|
|
|
|
void *kasan_krealloc(const void *object, size_t size, gfp_t flags)
|
|
{
|
|
struct page *page;
|
|
|
|
if (unlikely(object == ZERO_SIZE_PTR))
|
|
return (void *)object;
|
|
|
|
page = virt_to_head_page(object);
|
|
|
|
if (unlikely(!PageSlab(page)))
|
|
return kasan_kmalloc_large(object, size, flags);
|
|
else
|
|
return kasan_kmalloc(page->slab_cache, object, size, flags);
|
|
}
|
|
|
|
void kasan_poison_kfree(void *ptr, unsigned long ip)
|
|
{
|
|
struct page *page;
|
|
|
|
page = virt_to_head_page(ptr);
|
|
|
|
if (unlikely(!PageSlab(page))) {
|
|
if (ptr != page_address(page)) {
|
|
kasan_report_invalid_free(ptr, ip);
|
|
return;
|
|
}
|
|
kasan_poison_shadow(ptr, PAGE_SIZE << compound_order(page),
|
|
KASAN_FREE_PAGE);
|
|
} else {
|
|
__kasan_slab_free(page->slab_cache, ptr, ip, false);
|
|
}
|
|
}
|
|
|
|
void kasan_kfree_large(void *ptr, unsigned long ip)
|
|
{
|
|
if (ptr != page_address(virt_to_head_page(ptr)))
|
|
kasan_report_invalid_free(ptr, ip);
|
|
/* The object will be poisoned by page_alloc. */
|
|
}
|
|
|
|
int kasan_module_alloc(void *addr, size_t size)
|
|
{
|
|
void *ret;
|
|
size_t scaled_size;
|
|
size_t shadow_size;
|
|
unsigned long shadow_start;
|
|
|
|
shadow_start = (unsigned long)kasan_mem_to_shadow(addr);
|
|
scaled_size = (size + KASAN_SHADOW_MASK) >> KASAN_SHADOW_SCALE_SHIFT;
|
|
shadow_size = round_up(scaled_size, 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,
|
|
PAGE_KERNEL, VM_NO_GUARD, NUMA_NO_NODE,
|
|
__builtin_return_address(0));
|
|
|
|
if (ret) {
|
|
__memset(ret, KASAN_SHADOW_INIT, shadow_size);
|
|
find_vm_area(addr)->flags |= VM_KASAN;
|
|
kmemleak_ignore(ret);
|
|
return 0;
|
|
}
|
|
|
|
return -ENOMEM;
|
|
}
|
|
|
|
void kasan_free_shadow(const struct vm_struct *vm)
|
|
{
|
|
if (vm->flags & VM_KASAN)
|
|
vfree(kasan_mem_to_shadow(vm->addr));
|
|
}
|
|
|
|
#ifdef CONFIG_MEMORY_HOTPLUG
|
|
static bool shadow_mapped(unsigned long addr)
|
|
{
|
|
pgd_t *pgd = pgd_offset_k(addr);
|
|
p4d_t *p4d;
|
|
pud_t *pud;
|
|
pmd_t *pmd;
|
|
pte_t *pte;
|
|
|
|
if (pgd_none(*pgd))
|
|
return false;
|
|
p4d = p4d_offset(pgd, addr);
|
|
if (p4d_none(*p4d))
|
|
return false;
|
|
pud = pud_offset(p4d, addr);
|
|
if (pud_none(*pud))
|
|
return false;
|
|
|
|
/*
|
|
* We can't use pud_large() or pud_huge(), the first one is
|
|
* arch-specific, the last one depends on HUGETLB_PAGE. So let's abuse
|
|
* pud_bad(), if pud is bad then it's bad because it's huge.
|
|
*/
|
|
if (pud_bad(*pud))
|
|
return true;
|
|
pmd = pmd_offset(pud, addr);
|
|
if (pmd_none(*pmd))
|
|
return false;
|
|
|
|
if (pmd_bad(*pmd))
|
|
return true;
|
|
pte = pte_offset_kernel(pmd, addr);
|
|
return !pte_none(*pte);
|
|
}
|
|
|
|
static int __meminit kasan_mem_notifier(struct notifier_block *nb,
|
|
unsigned long action, void *data)
|
|
{
|
|
struct memory_notify *mem_data = data;
|
|
unsigned long nr_shadow_pages, start_kaddr, shadow_start;
|
|
unsigned long shadow_end, shadow_size;
|
|
|
|
nr_shadow_pages = mem_data->nr_pages >> KASAN_SHADOW_SCALE_SHIFT;
|
|
start_kaddr = (unsigned long)pfn_to_kaddr(mem_data->start_pfn);
|
|
shadow_start = (unsigned long)kasan_mem_to_shadow((void *)start_kaddr);
|
|
shadow_size = nr_shadow_pages << PAGE_SHIFT;
|
|
shadow_end = shadow_start + shadow_size;
|
|
|
|
if (WARN_ON(mem_data->nr_pages % KASAN_SHADOW_SCALE_SIZE) ||
|
|
WARN_ON(start_kaddr % (KASAN_SHADOW_SCALE_SIZE << PAGE_SHIFT)))
|
|
return NOTIFY_BAD;
|
|
|
|
switch (action) {
|
|
case MEM_GOING_ONLINE: {
|
|
void *ret;
|
|
|
|
/*
|
|
* If shadow is mapped already than it must have been mapped
|
|
* during the boot. This could happen if we onlining previously
|
|
* offlined memory.
|
|
*/
|
|
if (shadow_mapped(shadow_start))
|
|
return NOTIFY_OK;
|
|
|
|
ret = __vmalloc_node_range(shadow_size, PAGE_SIZE, shadow_start,
|
|
shadow_end, GFP_KERNEL,
|
|
PAGE_KERNEL, VM_NO_GUARD,
|
|
pfn_to_nid(mem_data->start_pfn),
|
|
__builtin_return_address(0));
|
|
if (!ret)
|
|
return NOTIFY_BAD;
|
|
|
|
kmemleak_ignore(ret);
|
|
return NOTIFY_OK;
|
|
}
|
|
case MEM_CANCEL_ONLINE:
|
|
case MEM_OFFLINE: {
|
|
struct vm_struct *vm;
|
|
|
|
/*
|
|
* shadow_start was either mapped during boot by kasan_init()
|
|
* or during memory online by __vmalloc_node_range().
|
|
* In the latter case we can use vfree() to free shadow.
|
|
* Non-NULL result of the find_vm_area() will tell us if
|
|
* that was the second case.
|
|
*
|
|
* Currently it's not possible to free shadow mapped
|
|
* during boot by kasan_init(). It's because the code
|
|
* to do that hasn't been written yet. So we'll just
|
|
* leak the memory.
|
|
*/
|
|
vm = find_vm_area((void *)shadow_start);
|
|
if (vm)
|
|
vfree((void *)shadow_start);
|
|
}
|
|
}
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static int __init kasan_memhotplug_init(void)
|
|
{
|
|
hotplug_memory_notifier(kasan_mem_notifier, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
core_initcall(kasan_memhotplug_init);
|
|
#endif
|