kasan: Implementation of Kasan based on software tags.

Currently, only aarch64 is supported

Signed-off-by: wangmingrong <wangmingrong@xiaomi.com>
This commit is contained in:
wangmingrong 2024-03-14 16:40:52 +08:00 committed by Xiang Xiao
parent 9c6bed4b00
commit ae3facda53
17 changed files with 394 additions and 51 deletions

View File

@ -19,6 +19,7 @@ Guides
customapps.rst
citests.rst
zerolatencyinterrupts.rst
kasan.rst
nestedinterrupts.rst
cortexmhardfaults.rst
coredump.rst

View File

@ -8,17 +8,23 @@ 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:
The current version of NuttX has two modes:
1. Generic KASAN
2. Software Tag-Based KASAN
Generic KASAN, enabled with CONFIG_MM_KASAN, is the mode intended for
Generic KASAN, enabled with CONFIG_MM_KASAN_GENERIC, 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.
Software Tag-Based KASAN or SW_TAGS KASAN, enabled with CONFIG_MM_KASAN_SW_TAGS,
can be used for both debugging, This mode is only supported for arm64,
but its moderate memory overhead allows using it for testing on
memory-restricted devices with real workloads.
Support
-------
@ -27,20 +33,33 @@ Architectures
Generic KASAN is supported on x86_64, arm, arm64, riscv, xtensa and so on.
Software Tag-Based KASAN modes are supported only on arm64.
Usage
-----
To enable Generic KASAN, configure the kernel with::
CONFIG_MM_KASAN=y
CONFIG_MM_KASAN_ALL=y
CONFIG_MM_KASAN_GENERIC=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
To enable Software Tag-Based KASAN, configure the kernel with::
CONFIG_MM_KASAN=y
CONFIG_MM_KASAN_ALL=y
CONFIG_MM_KASAN_SW_TAGS=y
Implementation details
----------------------
Generic KASAN:
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
@ -94,6 +113,26 @@ 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.
Software Tag-Based KASAN:
Software Tag-Based KASAN uses a software memory tagging approach to checking
access validity. It is currently only implemented for the arm64 architecture.
Software Tag-Based KASAN uses the Top Byte Ignore (TBI) feature of arm64 CPUs
to store a pointer tag in the top byte of kernel pointers. It uses shadow memory
to store memory tags associated with each heap allocated memory cell (therefore, it
dedicates 1/8 th of the kernel memory for shadow memory).
On each memory allocation, Software Tag-Based KASAN generates a random tag, tags
the allocated memory with this tag, and embeds the same tag into the returned
pointer.
Software Tag-Based KASAN uses compile-time instrumentation to insert checks
before each memory access. These checks make sure that the tag of the memory
that is being accessed is equal to the tag of the pointer that is used to access
this memory. In case of a tag mismatch, Software Tag-Based KASAN prints a bug
report.
For developers
--------------
@ -102,4 +141,6 @@ 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
If it is a file, you can write it this way,
special_file.o: CFLAGS = -fno-sanitize=kernel-address
or : special_file.o: CFLAGS = -fno-sanitize=kernel-hwaddress

View File

@ -79,7 +79,11 @@ ifeq ($(CONFIG_MM_UBSAN_TRAP_ON_ERROR),y)
endif
ifeq ($(CONFIG_MM_KASAN_ALL),y)
ARCHOPTIMIZATION += -fsanitize=kernel-address
ifeq ($(CONFIG_MM_KASAN_GENERIC),y)
ARCHOPTIMIZATION += -fsanitize=kernel-address
else ifeq ($(CONFIG_MM_KASAN_SW_TAGS),y)
ARCHOPTIMIZATION += -fsanitize=kernel-hwaddress
endif
endif
ifeq ($(CONFIG_MM_KASAN_GLOBAL),y)

View File

@ -153,6 +153,12 @@
#define TCR_PS_BITS TCR_PS_BITS_4GB
#endif
#ifdef CONFIG_MM_KASAN_SW_TAGS
#define TCR_KASAN_SW_FLAGS (TCR_TBI0 | TCR_TBI1 | TCR_ASID_8)
#else
#define TCR_KASAN_SW_FLAGS 0
#endif
/****************************************************************************
* Private Data
****************************************************************************/
@ -254,7 +260,8 @@ static uint64_t get_tcr(int el)
* inner shareable
*/
tcr |= TCR_TG0_4K | TCR_SHARED_INNER | TCR_ORGN_WBWA | TCR_IRGN_WBWA;
tcr |= TCR_TG0_4K | TCR_SHARED_INNER | TCR_ORGN_WBWA |
TCR_IRGN_WBWA | TCR_KASAN_SW_FLAGS;
return tcr;
}

View File

@ -149,6 +149,12 @@
#define TCR_TG0_16K (2ULL << 14)
#define TCR_EPD1_DISABLE (1ULL << 23)
#define TCR_AS_SHIFT 36U
#define TCR_ASID_8 (0ULL << TCR_AS_SHIFT)
#define TCR_ASID_16 (1ULL << TCR_AS_SHIFT)
#define TCR_TBI0 (1ULL << 37)
#define TCR_TBI1 (1ULL << 38)
#define TCR_PS_BITS_4GB 0x0ULL
#define TCR_PS_BITS_64GB 0x1ULL
#define TCR_PS_BITS_1TB 0x2ULL

View File

@ -37,9 +37,10 @@
#ifndef CONFIG_MM_KASAN
# define kasan_is_poisoned(addr, size) false
# define kasan_poison(addr, size)
# define kasan_unpoison(addr, size)
# define kasan_unpoison(addr, size) addr
# define kasan_register(addr, size)
# define kasan_init_early()
# define kasan_reset_tag(addr) addr
#else
/****************************************************************************
@ -99,11 +100,11 @@ void kasan_poison(FAR const void *addr, size_t size);
* size - range size
*
* Returned Value:
* None.
* Return tagged address
*
****************************************************************************/
void kasan_unpoison(FAR const void *addr, size_t size);
FAR void *kasan_unpoison(FAR const void *addr, size_t size);
/****************************************************************************
* Name: kasan_register
@ -143,6 +144,19 @@ void kasan_register(FAR void *addr, FAR size_t *size);
void kasan_init_early(void);
/****************************************************************************
* Name: kasan_reset_tag
*
* Input Parameters:
* addr - The address of the memory to reset the tag.
*
* Returned Value:
* Unlabeled address
*
****************************************************************************/
FAR void *kasan_reset_tag(FAR const void *addr);
#undef EXTERN
#ifdef __cplusplus
}

View File

@ -258,6 +258,25 @@ config MM_KASAN
bugs in native code. After turn on this option, Please
add -fsanitize=kernel-address to CFLAGS/CXXFLAGS too.
if MM_KASAN
choice
prompt "KAsan Mode"
default MM_KASAN_GENERIC
config MM_KASAN_GENERIC
bool "KAsan generic mode"
---help---
KASan generic mode that does not require hardware support at all
config MM_KASAN_SW_TAGS
bool "KAsan SW tags"
depends on ARCH_ARM64
---help---
KAsan based on software tags
endchoice
config MM_KASAN_ALL
bool "Enable KASan for the entire image"
depends on MM_KASAN
@ -288,7 +307,7 @@ config MM_KASAN_DISABLE_WRITES_CHECK
config MM_KASAN_GLOBAL
bool "Enable global data check"
depends on MM_KASAN
depends on MM_KASAN_GENERIC
default n
---help---
This option enables KASan global data check.

View File

@ -23,16 +23,24 @@
CSRCS += hook.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_MM_KASAN_GENERIC),y)
CSRCS += generic.c
CFLAGS += -fno-sanitize=kernel-address
endif
ifeq ($(CONFIG_MM_KASAN_SW_TAGS),y)
CSRCS += sw_tags.c
CFLAGS += -fno-sanitize=kernel-hwaddress
endif
ifeq ($(CONFIG_LTO_NONE),n)
CFLAGS += -fno-lto
endif
endif
endif
# Add the core heap directory to the build

View File

@ -139,10 +139,10 @@ static void kasan_set_poison(FAR const void *addr, size_t size,
bool poisoned)
{
FAR uintptr_t *p;
irqstate_t flags;
unsigned int bit;
unsigned int nbit;
uintptr_t mask;
int flags;
p = kasan_mem_to_shadow(addr, size, &bit);
if (p == NULL)
@ -193,6 +193,11 @@ static void kasan_set_poison(FAR const void *addr, size_t size,
* Public Functions
****************************************************************************/
FAR void *kasan_reset_tag(FAR const void *addr)
{
return (FAR void *)addr;
}
bool kasan_is_poisoned(FAR const void *addr, size_t size)
{
FAR uintptr_t *p;
@ -207,9 +212,10 @@ void kasan_poison(FAR const void *addr, size_t size)
kasan_set_poison(addr, size, true);
}
void kasan_unpoison(FAR const void *addr, size_t size)
FAR void *kasan_unpoison(FAR const void *addr, size_t size)
{
kasan_set_poison(addr, size, false);
return (FAR void *)addr;
}
void kasan_register(FAR void *addr, FAR size_t *size)

View File

@ -31,6 +31,46 @@
#include <stdint.h>
#include <stdio.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#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 DEFINE_HWASAN_LOAD_STORE(size) \
void __hwasan_load##size##_noabort(void *addr) \
{ \
kasan_check_report(addr, size, false, return_address(0)); \
} \
void __hwasan_store##size##_noabort(void *addr) \
{ \
kasan_check_report(addr, size, true, return_address(0)); \
}
/****************************************************************************
* Private Functions
****************************************************************************/
@ -181,34 +221,28 @@ 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)); \
}
/* Generic KASan will instrument the following functions */
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)
/* Soft tags KASan will instrument the following functions */
void __hwasan_loadN_noabort(FAR void *addr, size_t size)
{
kasan_check_report(addr, size, false, return_address(0));
}
void __hwasan_storeN_noabort(FAR void *addr, size_t size)
{
kasan_check_report(addr, size, true, return_address(0));
}
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)

199
mm/kasan/sw_tags.c Normal file
View File

@ -0,0 +1,199 @@
/****************************************************************************
* mm/kasan/sw_tags.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/mm/kasan.h>
#include <nuttx/spinlock.h>
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define KASAN_TAG_SHIFT 56
#define kasan_get_tag(addr) \
((uint8_t)((uint64_t)(addr) >> KASAN_TAG_SHIFT))
#define kasan_set_tag(addr, tag) \
(FAR void *)((((uint64_t)(addr)) & ~((uint64_t)0xff << KASAN_TAG_SHIFT)) | \
(((uint64_t)(tag)) << KASAN_TAG_SHIFT))
#define kasan_random_tag() (rand() % ((1 << (64 - KASAN_TAG_SHIFT)) - 1))
#define KASAN_SHADOW_SCALE (sizeof(uintptr_t))
#define KASAN_SHADOW_SIZE(size) \
((size) + KASAN_SHADOW_SCALE - 1) / KASAN_SHADOW_SCALE
#define KASAN_REGION_SIZE(size) \
(sizeof(struct kasan_region_s) + KASAN_SHADOW_SIZE(size))
#define KASAN_INIT_VALUE 0xdeadcafe
/****************************************************************************
* Private Types
****************************************************************************/
struct kasan_region_s
{
FAR struct kasan_region_s *next;
uintptr_t begin;
uintptr_t end;
uint8_t shadow[1];
};
/****************************************************************************
* Private Data
****************************************************************************/
static spinlock_t g_lock;
static FAR struct kasan_region_s *g_region;
static uint32_t g_region_init;
/****************************************************************************
* Private Functions
****************************************************************************/
static FAR uint8_t *kasan_mem_to_shadow(FAR const void *ptr, size_t size)
{
FAR struct kasan_region_s *region;
uintptr_t addr;
addr = (uintptr_t)kasan_reset_tag(ptr);
if (size == 0 || g_region_init != KASAN_INIT_VALUE)
{
return NULL;
}
for (region = g_region; region != NULL; region = region->next)
{
if (addr >= region->begin && addr < region->end)
{
DEBUGASSERT(addr + size <= region->end);
addr -= region->begin;
return &region->shadow[addr / KASAN_SHADOW_SCALE];
}
}
return NULL;
}
static void kasan_set_poison(FAR const void *addr,
size_t size, uint8_t value)
{
irqstate_t flags;
FAR uint8_t *p;
addr = kasan_reset_tag(addr);
p = kasan_mem_to_shadow(addr, size);
if (p == NULL)
{
return;
}
size = KASAN_SHADOW_SIZE(size);
flags = spin_lock_irqsave(&g_lock);
while (size--)
{
p[size] = value;
}
spin_unlock_irqrestore(&g_lock, flags);
}
/****************************************************************************
* Public Functions
****************************************************************************/
FAR void *kasan_reset_tag(FAR const void *addr)
{
return (FAR void *)
(((uint64_t)(addr)) & ~((uint64_t)0xff << KASAN_TAG_SHIFT));
}
bool kasan_is_poisoned(FAR const void *addr, size_t size)
{
FAR uint8_t *p;
uint8_t tag;
tag = kasan_get_tag(addr);
p = kasan_mem_to_shadow(addr, size);
if (p == NULL)
{
return false;
}
size = KASAN_SHADOW_SIZE(size);
while (size--)
{
if (p[size] != tag)
{
return true;
}
}
return false;
}
void kasan_poison(FAR const void *addr, size_t size)
{
kasan_set_poison(addr, size, 0xff);
}
FAR void *kasan_unpoison(FAR const void *addr, size_t size)
{
uint8_t tag = kasan_random_tag();
kasan_set_poison(addr, size, tag);
return kasan_set_tag(addr, tag);
}
void kasan_register(FAR void *addr, FAR size_t *size)
{
FAR struct kasan_region_s *region;
irqstate_t flags;
region = (FAR struct kasan_region_s *)
((FAR char *)addr + *size - KASAN_REGION_SIZE(*size));
region->begin = (uintptr_t)addr;
region->end = region->begin + *size;
flags = spin_lock_irqsave(&g_lock);
region->next = g_region;
g_region = region;
spin_unlock_irqrestore(&g_lock, flags);
g_region_init = KASAN_INIT_VALUE;
kasan_poison(addr, *size);
*size -= KASAN_REGION_SIZE(*size);
}
void kasan_init_early(void)
{
g_region_init = 0;
}

View File

@ -406,7 +406,7 @@ retry:
pool->nalloc++;
spin_unlock_irqrestore(&pool->lock, flags);
kasan_unpoison(blk, pool->blocksize);
blk = kasan_unpoison(blk, pool->blocksize);
#ifdef CONFIG_MM_FILL_ALLOCATIONS
memset(blk, MM_ALLOC_MAGIC, pool->blocksize);
#endif
@ -648,7 +648,7 @@ int mempool_deinit(FAR struct mempool_s *pool)
{
blk = (FAR sq_entry_t *)((FAR char *)blk - count * blocksize);
kasan_unpoison(blk, count * blocksize + sizeof(sq_entry_t));
blk = kasan_unpoison(blk, count * blocksize + sizeof(sq_entry_t));
pool->free(pool, blk);
if (pool->expandsize >= blocksize + sizeof(sq_entry_t))
{
@ -658,8 +658,8 @@ int mempool_deinit(FAR struct mempool_s *pool)
if (pool->ibase)
{
kasan_unpoison(pool->ibase,
pool->interruptsize / blocksize * blocksize);
pool->ibase = kasan_unpoison(pool->ibase,
pool->interruptsize / blocksize * blocksize);
pool->free(pool, pool->ibase);
}

View File

@ -30,6 +30,7 @@
#include <debug.h>
#include <nuttx/mm/mm.h>
#include <nuttx/mm/kasan.h>
#include "mm_heap/mm.h"
@ -56,6 +57,7 @@
bool mm_heapmember(FAR struct mm_heap_s *heap, FAR void *mem)
{
mem = kasan_reset_tag(mem);
#if CONFIG_MM_REGIONS > 1
int i;

View File

@ -324,7 +324,7 @@ FAR void *mm_malloc(FAR struct mm_heap_s *heap, size_t size)
if (ret)
{
MM_ADD_BACKTRACE(heap, node);
kasan_unpoison(ret, mm_malloc_size(heap, ret));
ret = kasan_unpoison(ret, mm_malloc_size(heap, ret));
#ifdef CONFIG_MM_FILL_ALLOCATIONS
memset(ret, MM_ALLOC_MAGIC, alignsize - MM_ALLOCNODE_OVERHEAD);
#endif

View File

@ -277,8 +277,10 @@ FAR void *mm_memalign(FAR struct mm_heap_s *heap, size_t alignment,
MM_ADD_BACKTRACE(heap, node);
kasan_unpoison((FAR void *)alignedchunk,
mm_malloc_size(heap, (FAR void *)alignedchunk));
alignedchunk = (uintptr_t)kasan_unpoison
((FAR const void *)alignedchunk,
mm_malloc_size(heap,
(FAR void *)alignedchunk));
DEBUGASSERT(alignedchunk % alignment == 0);
return (FAR void *)alignedchunk;

View File

@ -385,8 +385,8 @@ FAR void *mm_realloc(FAR struct mm_heap_s *heap, FAR void *oldmem,
mm_unlock(heap);
MM_ADD_BACKTRACE(heap, (FAR char *)newmem - MM_SIZEOF_ALLOCNODE);
kasan_unpoison(newmem, mm_malloc_size(heap, newmem));
if (newmem != oldmem)
newmem = kasan_unpoison(newmem, mm_malloc_size(heap, newmem));
if (kasan_reset_tag(newmem) != kasan_reset_tag(oldmem))
{
/* Now we have to move the user contents 'down' in memory. memcpy
* should be safe for this.

View File

@ -1183,7 +1183,7 @@ FAR void *mm_malloc(FAR struct mm_heap_s *heap, size_t size)
memdump_backtrace(heap, buf);
#endif
kasan_unpoison(ret, mm_malloc_size(heap, ret));
ret = kasan_unpoison(ret, mm_malloc_size(heap, ret));
#ifdef CONFIG_MM_FILL_ALLOCATIONS
memset(ret, 0xaa, nodesize);
@ -1260,7 +1260,7 @@ FAR void *mm_memalign(FAR struct mm_heap_s *heap, size_t alignment,
memdump_backtrace(heap, buf);
#endif
kasan_unpoison(ret, mm_malloc_size(heap, ret));
ret = kasan_unpoison(ret, mm_malloc_size(heap, ret));
}
#if CONFIG_MM_FREE_DELAYCOUNT_MAX > 0