Re: [PATCH v6 03/18] khwasan: add CONFIG_KASAN_GENERIC and CONFIG_KASAN_HW

From: Dmitry Vyukov
Date: Wed Sep 12 2018 - 10:51:45 EST


On Wed, Sep 12, 2018 at 4:47 PM, Dmitry Vyukov <dvyukov@xxxxxxxxxx> wrote:
> On Wed, Aug 29, 2018 at 1:35 PM, Andrey Konovalov <andreyknvl@xxxxxxxxxx> wrote:
>> This commit splits the current CONFIG_KASAN config option into two:
>> 1. CONFIG_KASAN_GENERIC, that enables the generic software-only KASAN
>> version (the one that exists now);
>> 2. CONFIG_KASAN_HW, that enables KHWASAN.
>>
>> With CONFIG_KASAN_HW enabled, compiler options are changed to instrument
>> kernel files wiht -fsantize=hwaddress (except the ones for which
>> KASAN_SANITIZE := n is set).
>>
>> Both CONFIG_KASAN_GENERIC and CONFIG_KASAN_HW support both
>> CONFIG_KASAN_INLINE and CONFIG_KASAN_OUTLINE instrumentation modes.
>>
>> This commit also adds empty placeholder (for now) implementation of
>> KHWASAN specific hooks inserted by the compiler and adjusts common hooks
>> implementation to compile correctly with each of the config options.
>>
>> Signed-off-by: Andrey Konovalov <andreyknvl@xxxxxxxxxx>
>> ---
>> arch/arm64/Kconfig | 1 +
>> include/linux/compiler-clang.h | 3 +-
>> include/linux/compiler-gcc.h | 4 ++
>> include/linux/compiler.h | 3 +-
>> include/linux/kasan.h | 16 +++++--
>> lib/Kconfig.kasan | 77 ++++++++++++++++++++++++++--------
>> mm/kasan/Makefile | 6 ++-
>> mm/kasan/kasan.h | 3 +-
>> mm/kasan/khwasan.c | 75 +++++++++++++++++++++++++++++++++
>> mm/slub.c | 2 +-
>> scripts/Makefile.kasan | 27 +++++++++++-
>> 11 files changed, 188 insertions(+), 29 deletions(-)
>> create mode 100644 mm/kasan/khwasan.c
>>
>> diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
>> index 29e75b47becd..991564148f54 100644
>> --- a/arch/arm64/Kconfig
>> +++ b/arch/arm64/Kconfig
>> @@ -105,6 +105,7 @@ config ARM64
>> select HAVE_ARCH_HUGE_VMAP
>> select HAVE_ARCH_JUMP_LABEL
>> select HAVE_ARCH_KASAN if !(ARM64_16K_PAGES && ARM64_VA_BITS_48)
>> + select HAVE_ARCH_KASAN_HW if !(ARM64_16K_PAGES && ARM64_VA_BITS_48)
>> select HAVE_ARCH_KGDB
>> select HAVE_ARCH_MMAP_RND_BITS
>> select HAVE_ARCH_MMAP_RND_COMPAT_BITS if COMPAT
>> diff --git a/include/linux/compiler-clang.h b/include/linux/compiler-clang.h
>> index b1ce500fe8b3..2c258a9d4c67 100644
>> --- a/include/linux/compiler-clang.h
>> +++ b/include/linux/compiler-clang.h
>> @@ -17,11 +17,12 @@
>> #define KASAN_ABI_VERSION 5
>>
>> /* emulate gcc's __SANITIZE_ADDRESS__ flag */
>> -#if __has_feature(address_sanitizer)
>> +#if __has_feature(address_sanitizer) || __has_feature(hwaddress_sanitizer)
>> #define __SANITIZE_ADDRESS__
>> #endif
>>
>> #define __no_sanitize_address __attribute__((no_sanitize("address")))
>> +#define __no_sanitize_hwaddress __attribute__((no_sanitize("hwaddress")))
>
> It seems that it would be better to have just 1 attribute for both types.
> Currently __no_sanitize_address is used just in a single place. But if
> it ever used more, people will need to always spell both which looks
> unnecessary, or, worse will only fix asan but forget about khwasan.
>
> If we do just:
>
> #define __no_sanitize_address __attribute__((no_sanitize("address",
> "hwaddress")))
>
> Then we don't need any changes in compiler-gcc.h nor in compiler.h,
> and no chance or forgetting one of them.
>
>> /*
>> * Not all versions of clang implement the the type-generic versions
>> diff --git a/include/linux/compiler-gcc.h b/include/linux/compiler-gcc.h
>> index 763bbad1e258..a186b55c8c4c 100644
>> --- a/include/linux/compiler-gcc.h
>> +++ b/include/linux/compiler-gcc.h
>> @@ -227,6 +227,10 @@
>> #define __no_sanitize_address
>> #endif
>>
>> +#if !defined(__no_sanitize_hwaddress)
>> +#define __no_sanitize_hwaddress /* gcc doesn't support KHWASAN */
>> +#endif
>> +
>> /*
>> * Turn individual warnings and errors on and off locally, depending
>> * on version.
>> diff --git a/include/linux/compiler.h b/include/linux/compiler.h
>> index 681d866efb1e..3f2ba192d57d 100644
>> --- a/include/linux/compiler.h
>> +++ b/include/linux/compiler.h
>> @@ -195,7 +195,8 @@ void __read_once_size(const volatile void *p, void *res, int size)
>> * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67368
>> * '__maybe_unused' allows us to avoid defined-but-not-used warnings.
>> */
>> -# define __no_kasan_or_inline __no_sanitize_address __maybe_unused
>> +# define __no_kasan_or_inline __no_sanitize_address __no_sanitize_hwaddress \
>> + __maybe_unused
>> #else
>> # define __no_kasan_or_inline __always_inline
>> #endif
>> diff --git a/include/linux/kasan.h b/include/linux/kasan.h
>> index 54d577ad2181..beb56a26fe9b 100644
>> --- a/include/linux/kasan.h
>> +++ b/include/linux/kasan.h
>> @@ -45,8 +45,6 @@ void kasan_free_pages(struct page *page, unsigned int order);
>>
>> void kasan_cache_create(struct kmem_cache *cache, unsigned int *size,
>> slab_flags_t *flags);
>> -void kasan_cache_shrink(struct kmem_cache *cache);
>> -void kasan_cache_shutdown(struct kmem_cache *cache);
>>
>> void kasan_poison_slab(struct page *page);
>> void kasan_unpoison_object_data(struct kmem_cache *cache, void *object);
>> @@ -97,8 +95,6 @@ static inline void kasan_free_pages(struct page *page, unsigned int order) {}
>> static inline void kasan_cache_create(struct kmem_cache *cache,
>> unsigned int *size,
>> slab_flags_t *flags) {}
>> -static inline void kasan_cache_shrink(struct kmem_cache *cache) {}
>> -static inline void kasan_cache_shutdown(struct kmem_cache *cache) {}
>>
>> static inline void kasan_poison_slab(struct page *page) {}
>> static inline void kasan_unpoison_object_data(struct kmem_cache *cache,
>> @@ -152,4 +148,16 @@ static inline size_t kasan_metadata_size(struct kmem_cache *cache) { return 0; }
>>
>> #endif /* CONFIG_KASAN */
>>
>> +#ifdef CONFIG_KASAN_GENERIC
>> +
>> +void kasan_cache_shrink(struct kmem_cache *cache);
>> +void kasan_cache_shutdown(struct kmem_cache *cache);
>> +
>> +#else /* CONFIG_KASAN_GENERIC */
>> +
>> +static inline void kasan_cache_shrink(struct kmem_cache *cache) {}
>> +static inline void kasan_cache_shutdown(struct kmem_cache *cache) {}
>> +
>> +#endif /* CONFIG_KASAN_GENERIC */
>> +
>> #endif /* LINUX_KASAN_H */
>> diff --git a/lib/Kconfig.kasan b/lib/Kconfig.kasan
>> index befb127507c0..5a22629f30e7 100644
>> --- a/lib/Kconfig.kasan
>> +++ b/lib/Kconfig.kasan
>> @@ -1,34 +1,75 @@
>> config HAVE_ARCH_KASAN
>> bool
>>
>> +config HAVE_ARCH_KASAN_HW
>> + bool
>> +
>> if HAVE_ARCH_KASAN
>>
>> config KASAN
>> - bool "KASan: runtime memory debugger"
>> + bool "KASAN: runtime memory debugger"
>> + help
>> + Enables KASAN (KernelAddressSANitizer) - runtime memory debugger,
>> + designed to find out-of-bounds accesses and use-after-free bugs.
>
> Perhaps also give link to Documentation/dev-tools/kasan.rst while we are here.
>
>> +
>> +choice
>> + prompt "KASAN mode"
>> + depends on KASAN
>> + default KASAN_GENERIC
>> + help
>> + KASAN has two modes: KASAN (a classic version, similar to userspace
>
> In these few sentences we call the old mode with 3 different terms:
> "generic", "classic" and "KASAN" :)
> This is somewhat confusing. Let's call it "generic" throughout (here
> and in the docs patch). "Generic" as in "supported on multiple arch
> and not-dependent on hardware features". "Classic" makes sense for
> people who knew KASAN before, but for future readers in won't make
> sense.
>
>
>> + ASan, enabled with CONFIG_KASAN_GENERIC) and KHWASAN (a version
>> + based on pointer tagging, only for arm64, similar to userspace
>> + HWASan, enabled with CONFIG_KASAN_HW).
>> +
>> +config KASAN_GENERIC
>> + bool "KASAN: the generic mode"
>> depends on (SLUB && SYSFS) || (SLAB && !DEBUG_SLAB)
>> select SLUB_DEBUG if SLUB
>> select CONSTRUCTORS
>> select STACKDEPOT
>> help
>> - Enables kernel address sanitizer - runtime memory debugger,
>> - designed to find out-of-bounds accesses and use-after-free bugs.
>> - This is strictly a debugging feature and it requires a gcc version
>> - of 4.9.2 or later. Detection of out of bounds accesses to stack or
>> - global variables requires gcc 5.0 or later.
>> - This feature consumes about 1/8 of available memory and brings about
>> - ~x3 performance slowdown.
>> + Enables the generic mode of KASAN.
>> + This is strictly a debugging feature and it requires a GCC version
>> + of 4.9.2 or later. Detection of out-of-bounds accesses to stack or
>> + global variables requires GCC 5.0 or later.
>> + This mode consumes about 1/8 of available memory at kernel start
>> + and introduces an overhead of ~x1.5 for the rest of the allocations.
>> + The performance slowdown is ~x3.
>> For better error detection enable CONFIG_STACKTRACE.
>> - Currently CONFIG_KASAN doesn't work with CONFIG_DEBUG_SLAB
>> + Currently CONFIG_KASAN_GENERIC doesn't work with CONFIG_DEBUG_SLAB
>> (the resulting kernel does not boot).
>>
>> +if HAVE_ARCH_KASAN_HW
>
> This choice looks somewhat weird on non-arm64. It's kinda a choice
> menu, but one can't really choose anything. Should we put the whole
> choice under HAVE_ARCH_KASAN_HW, and just select KASAN_GENERIC
> otherwise? I don't know what't the practice here. Andrey R?
>
>> +config KASAN_HW
>> + bool "KHWASAN: the hardware assisted mode"

Do we need a hyphen here? hardware-assisted?

>> + depends on (SLUB && SYSFS) || (SLAB && !DEBUG_SLAB)
>> + select SLUB_DEBUG if SLUB
>> + select CONSTRUCTORS
>> + select STACKDEPOT
>> + help
>> + Enabled KHWASAN (KASAN mode based on pointer tagging).
>> + This mode requires Top Byte Ignore support by the CPU and therefore
>> + only supported for arm64.
>> + This feature requires clang revision 330044 or later.
>> + This mode consumes about 1/16 of available memory at kernel start
>> + and introduces an overhead of ~20% for the rest of the allocations.
>> + For better error detection enable CONFIG_STACKTRACE.
>> + Currently CONFIG_KASAN_HW doesn't work with CONFIG_DEBUG_SLAB
>> + (the resulting kernel does not boot).
>> +
>> +endif
>> +
>> +endchoice
>> +
>> config KASAN_EXTRA
>> - bool "KAsan: extra checks"
>> - depends on KASAN && DEBUG_KERNEL && !COMPILE_TEST
>> + bool "KASAN: extra checks"
>> + depends on KASAN_GENERIC && DEBUG_KERNEL && !COMPILE_TEST
>> help
>> - This enables further checks in the kernel address sanitizer, for now
>> - it only includes the address-use-after-scope check that can lead
>> - to excessive kernel stack usage, frame size warnings and longer
>> - compile time.
>> + This enables further checks in KASAN, for now it only includes the
>> + address-use-after-scope check that can lead to excessive kernel
>> + stack usage, frame size warnings and longer compile time.
>> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81715 has more
>>
>>
>> @@ -53,16 +94,16 @@ config KASAN_INLINE
>> memory accesses. This is faster than outline (in some workloads
>> it gives about x2 boost over outline instrumentation), but
>> make kernel's .text size much bigger.
>> - This requires a gcc version of 5.0 or later.
>> + For CONFIG_KASAN_GENERIC this requires GCC 5.0 or later.
>>
>> endchoice
>>
>> config TEST_KASAN
>> - tristate "Module for testing kasan for bug detection"
>> + tristate "Module for testing KASAN for bug detection"
>> depends on m && KASAN
>> help
>> This is a test module doing various nasty things like
>> out of bounds accesses, use after free. It is useful for testing
>> - kernel debugging features like kernel address sanitizer.
>> + kernel debugging features like KASAN.
>>
>> endif
>> diff --git a/mm/kasan/Makefile b/mm/kasan/Makefile
>> index a6df14bffb6b..14955add96d3 100644
>> --- a/mm/kasan/Makefile
>> +++ b/mm/kasan/Makefile
>> @@ -2,6 +2,7 @@
>> KASAN_SANITIZE := n
>> UBSAN_SANITIZE_common.o := n
>> UBSAN_SANITIZE_kasan.o := n
>> +UBSAN_SANITIZE_khwasan.o := n
>> KCOV_INSTRUMENT := n
>>
>> CFLAGS_REMOVE_kasan.o = -pg
>> @@ -10,5 +11,8 @@ CFLAGS_REMOVE_kasan.o = -pg
>>
>> CFLAGS_common.o := $(call cc-option, -fno-conserve-stack -fno-stack-protector)
>> CFLAGS_kasan.o := $(call cc-option, -fno-conserve-stack -fno-stack-protector)
>> +CFLAGS_khwasan.o := $(call cc-option, -fno-conserve-stack -fno-stack-protector)
>>
>> -obj-y := common.o kasan.o report.o kasan_init.o quarantine.o
>> +obj-$(CONFIG_KASAN) := common.o kasan_init.o report.o
>> +obj-$(CONFIG_KASAN_GENERIC) += kasan.o quarantine.o
>> +obj-$(CONFIG_KASAN_HW) += khwasan.o
>> diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h
>> index 659463800f10..19b950eaccff 100644
>> --- a/mm/kasan/kasan.h
>> +++ b/mm/kasan/kasan.h
>> @@ -114,7 +114,8 @@ void kasan_report(unsigned long addr, size_t size,
>> bool is_write, unsigned long ip);
>> void kasan_report_invalid_free(void *object, unsigned long ip);
>>
>> -#if defined(CONFIG_SLAB) || defined(CONFIG_SLUB)
>> +#if defined(CONFIG_KASAN_GENERIC) && \
>> + (defined(CONFIG_SLAB) || defined(CONFIG_SLUB))
>> void quarantine_put(struct kasan_free_meta *info, struct kmem_cache *cache);
>> void quarantine_reduce(void);
>> void quarantine_remove_cache(struct kmem_cache *cache);
>> diff --git a/mm/kasan/khwasan.c b/mm/kasan/khwasan.c
>> new file mode 100644
>> index 000000000000..e2c3a7f7fd1f
>> --- /dev/null
>> +++ b/mm/kasan/khwasan.c
>> @@ -0,0 +1,75 @@
>> +/*
>> + * This file contains core KHWASAN code.
>> + *
>> + * Copyright (c) 2018 Google, Inc.
>> + * Author: Andrey Konovalov <andreyknvl@xxxxxxxxxx>
>> + *
>> + * 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.
>> + *
>> + */
>> +
>> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>> +#define DISABLE_BRANCH_PROFILING
>> +
>> +#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/random.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"
>> +
>> +void check_memory_region(unsigned long addr, size_t size, bool write,
>> + unsigned long ret_ip)
>> +{
>> +}
>> +
>> +#define DEFINE_HWASAN_LOAD_STORE(size) \
>> + void __hwasan_load##size##_noabort(unsigned long addr) \
>> + { \
>> + } \
>> + EXPORT_SYMBOL(__hwasan_load##size##_noabort); \
>> + void __hwasan_store##size##_noabort(unsigned long addr) \
>> + { \
>> + } \
>> + EXPORT_SYMBOL(__hwasan_store##size##_noabort)
>> +
>> +DEFINE_HWASAN_LOAD_STORE(1);
>> +DEFINE_HWASAN_LOAD_STORE(2);
>> +DEFINE_HWASAN_LOAD_STORE(4);
>> +DEFINE_HWASAN_LOAD_STORE(8);
>> +DEFINE_HWASAN_LOAD_STORE(16);
>> +
>> +void __hwasan_loadN_noabort(unsigned long addr, unsigned long size)
>> +{
>> +}
>> +EXPORT_SYMBOL(__hwasan_loadN_noabort);
>> +
>> +void __hwasan_storeN_noabort(unsigned long addr, unsigned long size)
>> +{
>> +}
>> +EXPORT_SYMBOL(__hwasan_storeN_noabort);
>> +
>> +void __hwasan_tag_memory(unsigned long addr, u8 tag, unsigned long size)
>> +{
>> +}
>> +EXPORT_SYMBOL(__hwasan_tag_memory);
>> diff --git a/mm/slub.c b/mm/slub.c
>> index 30b9bf777bab..4206e1b616e7 100644
>> --- a/mm/slub.c
>> +++ b/mm/slub.c
>> @@ -2955,7 +2955,7 @@ static __always_inline void slab_free(struct kmem_cache *s, struct page *page,
>> do_slab_free(s, page, head, tail, cnt, addr);
>> }
>>
>> -#ifdef CONFIG_KASAN
>> +#ifdef CONFIG_KASAN_GENERIC
>> void ___cache_free(struct kmem_cache *cache, void *x, unsigned long addr)
>> {
>> do_slab_free(cache, virt_to_head_page(x), x, NULL, 1, addr);
>> diff --git a/scripts/Makefile.kasan b/scripts/Makefile.kasan
>> index 69552a39951d..49c6e056c697 100644
>> --- a/scripts/Makefile.kasan
>> +++ b/scripts/Makefile.kasan
>> @@ -1,5 +1,5 @@
>> # SPDX-License-Identifier: GPL-2.0
>> -ifdef CONFIG_KASAN
>> +ifdef CONFIG_KASAN_GENERIC
>> ifdef CONFIG_KASAN_INLINE
>> call_threshold := 10000
>> else
>> @@ -42,6 +42,29 @@ ifdef CONFIG_KASAN_EXTRA
>> CFLAGS_KASAN += $(call cc-option, -fsanitize-address-use-after-scope)
>> endif
>>
>> -CFLAGS_KASAN_NOSANITIZE := -fno-builtin
>> +endif
>> +
>> +ifdef CONFIG_KASAN_HW
>> +
>> +ifdef CONFIG_KASAN_INLINE
>> + instrumentation_flags := -mllvm -hwasan-mapping-offset=$(KASAN_SHADOW_OFFSET)
>> +else
>> + instrumentation_flags := -mllvm -hwasan-instrument-with-calls=1
>> +endif
>>
>> +CFLAGS_KASAN := -fsanitize=kernel-hwaddress \
>> + -mllvm -hwasan-instrument-stack=0 \
>> + $(instrumentation_flags)
>> +
>> +ifeq ($(call cc-option, $(CFLAGS_KASAN) -Werror),)
>> + ifneq ($(CONFIG_COMPILE_TEST),y)
>> + $(warning Cannot use CONFIG_KASAN_HW: \
>> + -fsanitize=hwaddress is not supported by compiler)
>> + endif
>> +endif
>> +
>> +endif
>> +
>> +ifdef CONFIG_KASAN
>> +CFLAGS_KASAN_NOSANITIZE := -fno-builtin
>> endif
>> --
>> 2.19.0.rc0.228.g281dcd1b4d0-goog
>>