
[ Upstream commit aa1ef4d7b3f67f7f17aa4aa34f5ec513c7e4db6c ] Kernel allocator code accesses metadata for slab objects, that may lie out-of-bounds of the object itself, or be accessed when an object is freed. Such accesses trigger tag faults and lead to false-positive reports with hardware tag-based KASAN. Software KASAN modes disable instrumentation for allocator code via KASAN_SANITIZE Makefile macro, and rely on kasan_enable/disable_current() annotations which are used to ignore KASAN reports. With hardware tag-based KASAN neither of those options are available, as it doesn't use compiler instrumetation, no tag faults are ignored, and MTE is disabled after the first one. Instead, reset tags when accessing metadata (currently only for SLUB). Link: https://lkml.kernel.org/r/a0f3cefbc49f34c843b664110842de4db28179d0.1606161801.git.andreyknvl@google.com Signed-off-by: Andrey Konovalov <andreyknvl@google.com> Signed-off-by: Vincenzo Frascino <vincenzo.frascino@arm.com> Acked-by: Marco Elver <elver@google.com> Reviewed-by: Alexander Potapenko <glider@google.com> Tested-by: Vincenzo Frascino <vincenzo.frascino@arm.com> Cc: Andrey Ryabinin <aryabinin@virtuozzo.com> Cc: Branislav Rankov <Branislav.Rankov@arm.com> Cc: Catalin Marinas <catalin.marinas@arm.com> Cc: Dmitry Vyukov <dvyukov@google.com> Cc: Evgenii Stepanov <eugenis@google.com> Cc: Kevin Brodsky <kevin.brodsky@arm.com> Cc: Vasily Gorbik <gor@linux.ibm.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> Bug: 172318110 Signed-off-by: Andrey Konovalov <andreyknvl@google.com> Change-Id: I9e465a2b11b96938d2dc4d45d31a15b1c6c1d129
145 lines
3.1 KiB
C
145 lines
3.1 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include <linux/kernel.h>
|
|
#include <linux/string.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/highmem.h>
|
|
#include <linux/page_ext.h>
|
|
#include <linux/poison.h>
|
|
#include <linux/ratelimit.h>
|
|
#include <linux/kasan.h>
|
|
|
|
static DEFINE_STATIC_KEY_FALSE_RO(want_page_poisoning);
|
|
|
|
static int __init early_page_poison_param(char *buf)
|
|
{
|
|
int ret;
|
|
bool tmp;
|
|
|
|
ret = strtobool(buf, &tmp);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (tmp)
|
|
static_branch_enable(&want_page_poisoning);
|
|
else
|
|
static_branch_disable(&want_page_poisoning);
|
|
|
|
return 0;
|
|
}
|
|
early_param("page_poison", early_page_poison_param);
|
|
|
|
/**
|
|
* page_poisoning_enabled - check if page poisoning is enabled
|
|
*
|
|
* Return true if page poisoning is enabled, or false if not.
|
|
*/
|
|
bool page_poisoning_enabled(void)
|
|
{
|
|
/*
|
|
* Assumes that debug_pagealloc_enabled is set before
|
|
* memblock_free_all.
|
|
* Page poisoning is debug page alloc for some arches. If
|
|
* either of those options are enabled, enable poisoning.
|
|
*/
|
|
return (static_branch_unlikely(&want_page_poisoning) ||
|
|
(!IS_ENABLED(CONFIG_ARCH_SUPPORTS_DEBUG_PAGEALLOC) &&
|
|
debug_pagealloc_enabled()));
|
|
}
|
|
EXPORT_SYMBOL_GPL(page_poisoning_enabled);
|
|
|
|
static void poison_page(struct page *page)
|
|
{
|
|
void *addr = kmap_atomic(page);
|
|
|
|
/* KASAN still think the page is in-use, so skip it. */
|
|
kasan_disable_current();
|
|
memset(kasan_reset_tag(addr), PAGE_POISON, PAGE_SIZE);
|
|
kasan_enable_current();
|
|
kunmap_atomic(addr);
|
|
}
|
|
|
|
static void poison_pages(struct page *page, int n)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < n; i++)
|
|
poison_page(page + i);
|
|
}
|
|
|
|
static bool single_bit_flip(unsigned char a, unsigned char b)
|
|
{
|
|
unsigned char error = a ^ b;
|
|
|
|
return error && !(error & (error - 1));
|
|
}
|
|
|
|
static void check_poison_mem(unsigned char *mem, size_t bytes)
|
|
{
|
|
static DEFINE_RATELIMIT_STATE(ratelimit, 5 * HZ, 10);
|
|
unsigned char *start;
|
|
unsigned char *end;
|
|
|
|
if (IS_ENABLED(CONFIG_PAGE_POISONING_NO_SANITY))
|
|
return;
|
|
|
|
start = memchr_inv(mem, PAGE_POISON, bytes);
|
|
if (!start)
|
|
return;
|
|
|
|
for (end = mem + bytes - 1; end > start; end--) {
|
|
if (*end != PAGE_POISON)
|
|
break;
|
|
}
|
|
|
|
if (!__ratelimit(&ratelimit))
|
|
return;
|
|
else if (start == end && single_bit_flip(*start, PAGE_POISON))
|
|
pr_err("pagealloc: single bit error\n");
|
|
else
|
|
pr_err("pagealloc: memory corruption\n");
|
|
|
|
print_hex_dump(KERN_ERR, "", DUMP_PREFIX_ADDRESS, 16, 1, start,
|
|
end - start + 1, 1);
|
|
dump_stack();
|
|
}
|
|
|
|
static void unpoison_page(struct page *page)
|
|
{
|
|
void *addr;
|
|
|
|
addr = kmap_atomic(page);
|
|
/*
|
|
* Page poisoning when enabled poisons each and every page
|
|
* that is freed to buddy. Thus no extra check is done to
|
|
* see if a page was poisoned.
|
|
*/
|
|
check_poison_mem(addr, PAGE_SIZE);
|
|
kunmap_atomic(addr);
|
|
}
|
|
|
|
static void unpoison_pages(struct page *page, int n)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < n; i++)
|
|
unpoison_page(page + i);
|
|
}
|
|
|
|
void kernel_poison_pages(struct page *page, int numpages, int enable)
|
|
{
|
|
if (!page_poisoning_enabled())
|
|
return;
|
|
|
|
if (enable)
|
|
unpoison_pages(page, numpages);
|
|
else
|
|
poison_pages(page, numpages);
|
|
}
|
|
|
|
#ifndef CONFIG_ARCH_SUPPORTS_DEBUG_PAGEALLOC
|
|
void __kernel_map_pages(struct page *page, int numpages, int enable)
|
|
{
|
|
/* This function does nothing, all work is done via poison pages */
|
|
}
|
|
#endif
|