mm: Support the different kasan implementation

Split kasan.c to incorporate label based kasan:
hook.c: Implement compiler instrumentation function
generic.c: Implementation software kasan algorithm

Signed-off-by: wangmingrong <wangmingrong1@xiaomi.com>
This commit is contained in:
Xiang Xiao 2024-03-10 16:29:23 +08:00 committed by Xiang Xiao
parent 70b9f63985
commit 47ffb9019f
6 changed files with 377 additions and 267 deletions

View File

@ -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

View File

@ -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})

View File

@ -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

View File

@ -1,5 +1,5 @@
/****************************************************************************
* mm/kasan/kasan.c
* mm/kasan/generic.c
*
* SPDX-License-Identifier: Apache-2.0
*
@ -27,11 +27,7 @@
#include <nuttx/spinlock.h>
#include <assert.h>
#include <debug.h>
#include <execinfo.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#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

215
mm/kasan/hook.c Normal file
View File

@ -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 <nuttx/irq.h>
#include <assert.h>
#include <debug.h>
#include <execinfo.h>
#include <stdint.h>
#include <stdio.h>
#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)

View File

@ -27,6 +27,7 @@
* Included Files
****************************************************************************/
#include <stdbool.h>
#include <stddef.h>
/****************************************************************************
@ -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 */