diff --git a/Documentation/guides/kasan.rst b/Documentation/guides/kasan.rst new file mode 100644 index 0000000000..3f3b1aeb04 --- /dev/null +++ b/Documentation/guides/kasan.rst @@ -0,0 +1,105 @@ +==================================== +The Kernel Address Sanitizer (KASAN) +==================================== + +Overview +-------- + +Kernel Address Sanitizer (KASAN) is a dynamic memory safety error detector +designed to find out-of-bounds and use-after-free bugs. + +The current version of NuttX has one modes: + +1. Generic KASAN + +Generic KASAN, enabled with CONFIG_MM_KASAN, is the mode intended for +debugging, similar to linux user level ASan. This mode is supported on many CPU +architectures, but it has significant performance and memory overheads. +The current NuttX Generic KASAN can support memory out of bounds detection +allocated by the default NuttX heap allocator,which depends on CONFIG_MM_DEFAULT_MANAGER +or CONFIG_MM_TLSF_MANAGER, and detection of out of bounds with global variables. + +Support +------- + +Architectures +~~~~~~~~~~~~~ + +Generic KASAN is supported on x86_64, arm, arm64, riscv, xtensa and so on. + +Usage +----- + +To enable Generic KASAN, configure the kernel with:: + CONFIG_MM_KASAN=y + CONFIG_MM_KASAN_ALL=y + +If you want to enable global variable out of bounds detection, +you can add configurations based on the above:: + CONFIG_MM_KASAN_GLOBAL=y + +Implementation details +---------------------- + +Compile with param -fsanitize=kernel-address, +Compile-time instrumentation is used to insert memory access checks. Compiler +inserts function calls (``__asan_load*(addr)``, ``__asan_store*(addr)``) before +each memory access of size 1, 2, 4, 8, or 16. These functions check whether +memory accesses are valid or not by checking corresponding shadow memory. + +It is slightly different from Linux. +On the one hand, in terms of the source of the shadow area; +NuttX's shadow area comes from the end of each heap. During heap initialization, +it is offset and a kasan region is shaped at the end. +Regions between multiple heaps are concatenated using a linked list. + +Secondly, in order to save more memory consumption, +the implementation of NuttX adopts a bitmap detection method; +For example, in the case of a 32-bit machine, +if the NuttX heap allocator allocates four bytes of memory to it, +the kasan module will allocate a shadow area of one bit per unit of +memory group on a four byte basis. If the shadow area is 0, +the memory group can be accessed, otherwise 1 is inaccessible + +Thirdly, the implementation of global variable out of bounds detection +for this NuttX is also different from Linux. +Due to the particularity of the shadow region, NuttX needs to construct kasan regions +separately for the data and bss segments where the global variable is located. +Before compiling, add the compile option '--param asan-globals=1'. +In this way, the compiler will store all global variable information in this special sections, +'.data..LASAN0', These two segments store information about all global variables +and can be parsed using the following structure:: + + struct kasan_global { + const void *beg; /* Address of the beginning of the global variable. */ + size_t size; /* Size of the global variable. */ + size_t size_with_redzone; /* Size of the variable + size of the redzone. 32 bytes aligned. */ + const void *name; + const void *module_name; /* Name of the module where the global variable is declared. */ + unsigned long has_dynamic_init; /* This is needed for C++. */ + + /* It will point to a location that stores the file row, + * column, and file name information of each global variable */ + + struct kasan_source_location *location; + char *odr_indicator; + }; + +In order to reduce the amount of data generated by the compiler occupying the already precious flash space. +NuttX's approach is to use multiple links to extract the global variable information in elf through scripts, +construct the region and shadow of the global variables according to the rules of kasan region, +form an array, and finally link it to the program. The program concatenates the array to kasan's region linked list. + +The data generated by the compiler will be placed in a non-existent memory block. +After the compilation is completed, this segment will be deleted +and will not be copied to the bin file of the final burned board. + +For developers +-------------- + +Ignoring accesses +~~~~~~~~~~~~~~~~~ + +If you want the module you are writing to not be inserted by the compiler, +you can add the option 'CFLAGS += -fno-sanitize=kernel-address' to a single module. +If it is a file, you can write it this way, special_file.o: CFLAGS = -fno-sanitize=kernel-address diff --git a/mm/kasan/CMakeLists.txt b/mm/kasan/CMakeLists.txt index 8bc0f885bb..8252112e34 100644 --- a/mm/kasan/CMakeLists.txt +++ b/mm/kasan/CMakeLists.txt @@ -19,13 +19,13 @@ # the License. # # ############################################################################## +set(SRCS hook.c) + if(CONFIG_MM_KASAN) - target_sources(mm PRIVATE kasan.c) - - target_compile_options(mm PRIVATE -fno-sanitize=kernel-address) - - if(NOT CONFIG_LTO_NONE) - target_compile_options(mm PRIVATE -fno-lto) - endif() - + list(APPEND SRCS generic.c) + set_source_files_properties(generic.c PROPERTIES COMPILE_FLAGS + -fno-sanitize=kernel-address) + set_source_files_properties(generic.c PROPERTIES COMPILE_FLAGS -fno-lto) endif() + +target_sources(mm PRIVATE ${SRCS}) diff --git a/mm/kasan/Make.defs b/mm/kasan/Make.defs index 50f5b96f4c..4b1b8b3d25 100644 --- a/mm/kasan/Make.defs +++ b/mm/kasan/Make.defs @@ -20,17 +20,19 @@ # ############################################################################ -ifeq ($(CONFIG_MM_KASAN),y) +CSRCS += hook.c -CSRCS += kasan.c +ifeq ($(CONFIG_MM_KASAN),y) +CSRCS += generic.c # Disable kernel-address in mm subsystem -ifeq ($(CONFIG_ARCH_TOOLCHAIN_GNU),y) - CFLAGS += -fno-sanitize=kernel-address - ifeq ($(CONFIG_LTO_NONE),n) - CFLAGS += -fno-lto - endif + ifeq ($(CONFIG_ARCH_TOOLCHAIN_GNU),y) + CFLAGS += -fno-sanitize=kernel-address + ifeq ($(CONFIG_LTO_NONE),n) + CFLAGS += -fno-lto + endif + endif # Add the core heap directory to the build @@ -38,4 +40,4 @@ endif DEPPATH += --dep-path kasan VPATH += :kasan -endif + diff --git a/mm/kasan/kasan.c b/mm/kasan/generic.c similarity index 50% rename from mm/kasan/kasan.c rename to mm/kasan/generic.c index 072a81fc9b..2916a091de 100644 --- a/mm/kasan/kasan.c +++ b/mm/kasan/generic.c @@ -1,5 +1,5 @@ /**************************************************************************** - * mm/kasan/kasan.c + * mm/kasan/generic.c * * SPDX-License-Identifier: Apache-2.0 * @@ -27,11 +27,7 @@ #include #include -#include -#include -#include #include -#include #include "kasan.h" @@ -64,7 +60,7 @@ #endif -#define KASAN_INIT_VALUE 0xDEADCAFE +#define KASAN_INIT_VALUE 0xdeadcafe /**************************************************************************** * Private Types @@ -78,12 +74,6 @@ struct kasan_region_s uintptr_t shadow[1]; }; -/**************************************************************************** - * Private Function Prototypes - ****************************************************************************/ - -static bool kasan_is_poisoned(FAR const void *addr, size_t size); - /**************************************************************************** * Private Data ****************************************************************************/ @@ -104,12 +94,13 @@ extern const unsigned char g_globals_region[]; * Private Functions ****************************************************************************/ -static inline FAR uintptr_t *kasan_find_mem(uintptr_t addr, size_t size, - unsigned int *bit) +static FAR uintptr_t *kasan_mem_to_shadow(FAR const void *ptr, size_t size, + unsigned int *bit) { FAR struct kasan_region_s *region; + uintptr_t addr = (uintptr_t)ptr; - if (size == 0) + if (size == 0 || g_region_init != KASAN_INIT_VALUE) { return NULL; } @@ -145,121 +136,6 @@ static inline FAR uintptr_t *kasan_find_mem(uintptr_t addr, size_t size, return NULL; } -static FAR uintptr_t *kasan_mem_to_shadow(FAR const void *ptr, size_t size, - unsigned int *bit) -{ - uintptr_t addr = (uintptr_t)ptr; - FAR uintptr_t *ret; - size_t mul; - size_t mod; - size_t i; - - if (g_region_init != KASAN_INIT_VALUE) - { - return NULL; - } - - if (size > KASAN_SHADOW_SCALE) - { - mul = size / KASAN_SHADOW_SCALE; - for (i = 0; i < mul; i++) - { - ret = kasan_find_mem(addr + i * KASAN_SHADOW_SCALE, - KASAN_SHADOW_SCALE, bit); - if (ret == NULL) - { - return ret; - } - } - - mod = size % KASAN_SHADOW_SCALE; - addr += mul * KASAN_SHADOW_SCALE; - size = mod; - } - - return kasan_find_mem(addr, size, bit); -} - -static void kasan_show_memory(FAR const uint8_t *addr, size_t size, - size_t dumpsize) -{ - FAR const uint8_t *start = (FAR const uint8_t *) - (((uintptr_t)addr) & ~0xf) - dumpsize; - FAR const uint8_t *end = start + 2 * dumpsize; - FAR const uint8_t *p = start; - char buffer[256]; - - _alert("Shadow bytes around the buggy address:\n"); - for (p = start; p < end; p += 16) - { - int ret = sprintf(buffer, " %p: ", p); - int i; - - for (i = 0; i < 16; i++) - { - if (kasan_is_poisoned(p + i, 1)) - { - if (p + i == addr) - { - ret += sprintf(buffer + ret, - "\b[\033[31m%02x\033[0m ", p[i]); - } - else if (p + i == addr + size - 1) - { - ret += sprintf(buffer + ret, "\033[31m%02x\033[0m]", p[i]); - } - else - { - ret += sprintf(buffer + ret, "\033[31m%02x\033[0m ", p[i]); - } - } - else - { - ret += sprintf(buffer + ret, "\033[37m%02x\033[0m ", p[i]); - } - } - - _alert("%s\n", buffer); - } -} - -static void kasan_report(FAR const void *addr, size_t size, - bool is_write, - FAR void *return_address) -{ - static int recursion; - irqstate_t flags; - - flags = enter_critical_section(); - - if (++recursion == 1) - { - _alert("kasan detected a %s access error, address at %p," - "size is %zu, return address: %p\n", - is_write ? "write" : "read", - addr, size, return_address); - - kasan_show_memory(addr, size, 80); -#ifndef CONFIG_MM_KASAN_DISABLE_PANIC - PANIC(); -#else - dump_stack(); -#endif - } - - --recursion; - leave_critical_section(flags); -} - -static bool kasan_is_poisoned(FAR const void *addr, size_t size) -{ - FAR uintptr_t *p; - unsigned int bit; - - p = kasan_mem_to_shadow(addr, size, &bit); - return p && ((*p >> bit) & 1); -} - static void kasan_set_poison(FAR const void *addr, size_t size, bool poisoned) { @@ -269,20 +145,17 @@ static void kasan_set_poison(FAR const void *addr, size_t size, uintptr_t mask; int flags; - if (size == 0) + p = kasan_mem_to_shadow(addr, size, &bit); + if (p == NULL) { return; } - flags = spin_lock_irqsave(&g_lock); - - p = kasan_find_mem((uintptr_t)addr, size, &bit); - DEBUGASSERT(p != NULL); - nbit = KASAN_BITS_PER_WORD - bit % KASAN_BITS_PER_WORD; mask = KASAN_FIRST_WORD_MASK(bit); - size /= KASAN_SHADOW_SCALE; + + flags = spin_lock_irqsave(&g_lock); while (size >= nbit) { if (poisoned) @@ -317,21 +190,18 @@ static void kasan_set_poison(FAR const void *addr, size_t size, spin_unlock_irqrestore(&g_lock, flags); } -static inline void kasan_check_report(FAR const void *addr, size_t size, - bool is_write, - FAR void *return_address) -{ - if (kasan_is_poisoned(addr, size)) - { - kasan_report(addr, size, is_write, return_address); - } -} - /**************************************************************************** * Public Functions ****************************************************************************/ -/* Exported functions called from other mm module */ +bool kasan_is_poisoned(FAR const void *addr, size_t size) +{ + FAR uintptr_t *p; + unsigned int bit; + + p = kasan_mem_to_shadow(addr, size, &bit); + return p && ((*p >> bit) & 1); +} void kasan_poison(FAR const void *addr, size_t size) { @@ -364,102 +234,3 @@ void kasan_init_early(void) { g_region_init = 0; } - -/* Exported functions called from the compiler generated code */ - -void __sanitizer_annotate_contiguous_container(FAR const void *beg, - FAR const void *end, - FAR const void *old_mid, - FAR const void *new_mid) -{ - /* Shut up compiler complaints */ -} - -void __asan_before_dynamic_init(FAR const void *module_name) -{ - /* Shut up compiler complaints */ -} - -void __asan_after_dynamic_init(void) -{ - /* Shut up compiler complaints */ -} - -void __asan_handle_no_return(void) -{ - /* Shut up compiler complaints */ -} - -void __asan_report_load_n_noabort(FAR void *addr, size_t size) -{ - kasan_report(addr, size, false, return_address(0)); -} - -void __asan_report_store_n_noabort(FAR void *addr, size_t size) -{ - kasan_report(addr, size, true, return_address(0)); -} - -void __asan_loadN_noabort(FAR void *addr, size_t size) -{ - kasan_check_report(addr, size, false, return_address(0)); -} - -void __asan_storeN_noabort(FAR void * addr, size_t size) -{ - kasan_check_report(addr, size, true, return_address(0)); -} - -void __asan_loadN(FAR void *addr, size_t size) -{ - kasan_check_report(addr, size, false, return_address(0)); -} - -void __asan_storeN(FAR void *addr, size_t size) -{ - kasan_check_report(addr, size, true, return_address(0)); -} - -#define DEFINE_ASAN_LOAD_STORE(size) \ - void __asan_report_load##size##_noabort(FAR void *addr) \ - { \ - kasan_report(addr, size, false, return_address(0)); \ - } \ - void __asan_report_store##size##_noabort(FAR void *addr) \ - { \ - kasan_report(addr, size, true, return_address(0)); \ - } \ - void __asan_load##size##_noabort(FAR void *addr) \ - { \ - kasan_check_report(addr, size, false, return_address(0)); \ - } \ - void __asan_store##size##_noabort(FAR void *addr) \ - { \ - kasan_check_report(addr, size, true, return_address(0)); \ - } \ - void __asan_load##size(FAR void *addr) \ - { \ - kasan_check_report(addr, size, false, return_address(0)); \ - } \ - void __asan_store##size(FAR void *addr) \ - { \ - kasan_check_report(addr, size, true, return_address(0)); \ - } - -DEFINE_ASAN_LOAD_STORE(1) -DEFINE_ASAN_LOAD_STORE(2) -DEFINE_ASAN_LOAD_STORE(4) -DEFINE_ASAN_LOAD_STORE(8) -DEFINE_ASAN_LOAD_STORE(16) - -#ifdef CONFIG_MM_KASAN_GLOBAL -void __asan_register_globals(void *ptr, ssize_t size) -{ - /* Shut up compiler complaints */ -} - -void __asan_unregister_globals(void *ptr, ssize_t size) -{ - /* Shut up compiler complaints */ -} -#endif diff --git a/mm/kasan/hook.c b/mm/kasan/hook.c new file mode 100644 index 0000000000..c8039402c9 --- /dev/null +++ b/mm/kasan/hook.c @@ -0,0 +1,215 @@ +/**************************************************************************** + * mm/kasan/hook.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include "kasan.h" + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void kasan_show_memory(FAR const uint8_t *addr, size_t size, + size_t dumpsize) +{ + FAR const uint8_t *start = (FAR const uint8_t *) + (((uintptr_t)addr) & ~0xf) - dumpsize; + FAR const uint8_t *end = start + 2 * dumpsize; + FAR const uint8_t *p = start; + char buffer[256]; + + _alert("Shadow bytes around the buggy address:\n"); + for (p = start; p < end; p += 16) + { + int ret = sprintf(buffer, " %p: ", p); + int i; + + for (i = 0; i < 16; i++) + { + if (kasan_is_poisoned(p + i, 1)) + { + if (p + i == addr) + { + ret += sprintf(buffer + ret, + "\b[\033[31m%02x\033[0m ", p[i]); + } + else if (p + i == addr + size - 1) + { + ret += sprintf(buffer + ret, "\033[31m%02x\033[0m]", p[i]); + } + else + { + ret += sprintf(buffer + ret, "\033[31m%02x\033[0m ", p[i]); + } + } + else + { + ret += sprintf(buffer + ret, "\033[37m%02x\033[0m ", p[i]); + } + } + + _alert("%s\n", buffer); + } +} + +static void kasan_report(FAR const void *addr, size_t size, + bool is_write, FAR void *return_address) +{ + static int recursion; + irqstate_t flags; + + flags = enter_critical_section(); + if (++recursion == 1) + { + _alert("kasan detected a %s access error, address at %p," + "size is %zu, return address: %p\n", + is_write ? "write" : "read", + addr, size, return_address); + + kasan_show_memory(addr, size, 80); +#ifndef CONFIG_MM_KASAN_DISABLE_PANIC + PANIC(); +#else + dump_stack(); +#endif + } + + --recursion; + leave_critical_section(flags); +} + +static inline void kasan_check_report(FAR const void *addr, size_t size, + bool is_write, + FAR void *return_address) +{ + if (kasan_is_poisoned(addr, size)) + { + kasan_report(addr, size, is_write, return_address); + } +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void __asan_before_dynamic_init(FAR const void *module_name) +{ + /* Shut up compiler complaints */ +} + +void __asan_after_dynamic_init(void) +{ + /* Shut up compiler complaints */ +} + +void __asan_handle_no_return(void) +{ + /* Shut up compiler complaints */ +} + +void __asan_register_globals(void *ptr, size_t size) +{ + /* Shut up compiler complaints */ +} + +void __asan_unregister_globals(void *ptr, size_t size) +{ + /* Shut up compiler complaints */ +} + +void __sanitizer_annotate_contiguous_container(FAR const void *beg, + FAR const void *end, + FAR const void *old_mid, + FAR const void *new_mid) +{ + /* Shut up compiler complaints */ +} + +void __asan_report_load_n_noabort(FAR void *addr, size_t size) +{ + kasan_report(addr, size, false, return_address(0)); +} + +void __asan_report_store_n_noabort(FAR void *addr, size_t size) +{ + kasan_report(addr, size, true, return_address(0)); +} + +void __asan_loadN_noabort(FAR void *addr, size_t size) +{ + kasan_check_report(addr, size, false, return_address(0)); +} + +void __asan_storeN_noabort(FAR void * addr, size_t size) +{ + kasan_check_report(addr, size, true, return_address(0)); +} + +void __asan_loadN(FAR void *addr, size_t size) +{ + kasan_check_report(addr, size, false, return_address(0)); +} + +void __asan_storeN(FAR void *addr, size_t size) +{ + kasan_check_report(addr, size, true, return_address(0)); +} + +#define DEFINE_ASAN_LOAD_STORE(size) \ + void __asan_report_load##size##_noabort(FAR void *addr) \ + { \ + kasan_report(addr, size, false, return_address(0)); \ + } \ + void __asan_report_store##size##_noabort(FAR void *addr) \ + { \ + kasan_report(addr, size, true, return_address(0)); \ + } \ + void __asan_load##size##_noabort(FAR void *addr) \ + { \ + kasan_check_report(addr, size, false, return_address(0)); \ + } \ + void __asan_store##size##_noabort(FAR void *addr) \ + { \ + kasan_check_report(addr, size, true, return_address(0)); \ + } \ + void __asan_load##size(FAR void *addr) \ + { \ + kasan_check_report(addr, size, false, return_address(0)); \ + } \ + void __asan_store##size(FAR void *addr) \ + { \ + kasan_check_report(addr, size, true, return_address(0)); \ + } + +DEFINE_ASAN_LOAD_STORE(1) +DEFINE_ASAN_LOAD_STORE(2) +DEFINE_ASAN_LOAD_STORE(4) +DEFINE_ASAN_LOAD_STORE(8) +DEFINE_ASAN_LOAD_STORE(16) diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h index 53baffb157..42e0d9d986 100644 --- a/mm/kasan/kasan.h +++ b/mm/kasan/kasan.h @@ -27,6 +27,7 @@ * Included Files ****************************************************************************/ +#include #include /**************************************************************************** @@ -34,11 +35,12 @@ ****************************************************************************/ #ifndef CONFIG_MM_KASAN +# define kasan_is_poisoned(addr, size) false # define kasan_poison(addr, size) # define kasan_unpoison(addr, size) # define kasan_register(addr, size) # define kasan_init_early() -#endif +#else /**************************************************************************** * Public Function Prototypes @@ -52,7 +54,22 @@ extern "C" #define EXTERN extern #endif -#ifdef CONFIG_MM_KASAN +/**************************************************************************** + * Name: kasan_is_poisoned + * + * Description: + * Check if the memory range is poisoned + * + * Input Parameters: + * addr - range start address + * size - range size + * + * Returned Value: + * true if the memory range is poisoned, false otherwise. + * + ****************************************************************************/ + +bool kasan_is_poisoned(FAR const void *addr, size_t size); /**************************************************************************** * Name: kasan_poison @@ -126,11 +143,11 @@ void kasan_register(FAR void *addr, FAR size_t *size); void kasan_init_early(void); -#endif /* CONFIG_MM_KASAN */ - #undef EXTERN #ifdef __cplusplus } #endif +#endif /* CONFIG_MM_KASAN */ + #endif /* __MM_KASAN_KASAN_H */