mm: Support the kernel address sanitizer

Signed-off-by: chenwei23 <chenwei23@xiaomi.com>
This commit is contained in:
chenwei23 2021-10-09 15:22:28 +08:00 committed by Gustavo Henrique Nihei
parent 5cb6b042aa
commit 39cdd99d77
12 changed files with 625 additions and 20 deletions

View File

@ -178,4 +178,12 @@ config MM_CIRCBUF
---help---
Build in support for the circular buffer management.
config MM_KASAN
bool "Kernel Address Sanitizer"
default n
---help---
KASan is a fast compiler-based tool for detecting memory
bugs in native code. After turn on this option, Please
add -fsanitize=kernel-address to CFLAGS/CXXFLAGS too.
source "mm/iob/Kconfig"

View File

@ -29,6 +29,7 @@ include mm_gran/Make.defs
include shm/Make.defs
include iob/Make.defs
include circbuf/Make.defs
include kasan/Make.defs
BINDIR ?= bin

34
mm/kasan/Make.defs Normal file
View File

@ -0,0 +1,34 @@
############################################################################
# mm/kasan/Make.defs
#
# 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.
#
############################################################################
ifeq ($(CONFIG_MM_KASAN),y)
CSRCS += kasan.c
# Disable kernel-address in mm subsystem
CFLAGS += -fno-sanitize=kernel-address
# Add the core heap directory to the build
DEPPATH += --dep-path kasan
VPATH += :kasan
endif

409
mm/kasan/kasan.c Normal file
View File

@ -0,0 +1,409 @@
/****************************************************************************
* mm/kasan/kasan.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/semaphore.h>
#include <assert.h>
#include <debug.h>
#include <stdbool.h>
#include <stdint.h>
#include "kasan.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define KASAN_BYTES_PER_WORD (sizeof(uintptr_t))
#define KASAN_BITS_PER_WORD (KASAN_BYTES_PER_WORD * 8)
#define KASAN_FIRST_WORD_MASK(start) \
(UINTPTR_MAX << ((start) & (KASAN_BITS_PER_WORD - 1)))
#define KASAN_LAST_WORD_MASK(end) \
(UINTPTR_MAX >> (-(end) & (KASAN_BITS_PER_WORD - 1)))
#define KASAN_SHADOW_SCALE (sizeof(uintptr_t))
#define KASAN_SHADOW_SIZE(size) \
(KASAN_BYTES_PER_WORD * ((size) / KASAN_SHADOW_SCALE / KASAN_BITS_PER_WORD))
#define KASAN_REGION_SIZE(size) \
(sizeof(struct kasan_region_s) + KASAN_SHADOW_SIZE(size))
/****************************************************************************
* Private Types
****************************************************************************/
struct kasan_region_s
{
FAR struct kasan_region_s *next;
uintptr_t begin;
uintptr_t end;
uintptr_t shadow[1];
};
/****************************************************************************
* Private Data
****************************************************************************/
static sem_t g_lock = SEM_INITIALIZER(1);
static FAR struct kasan_region_s *g_region;
/****************************************************************************
* Private Functions
****************************************************************************/
static FAR uintptr_t *kasan_mem_to_shadow(uintptr_t addr, size_t size,
unsigned int *bit)
{
FAR struct kasan_region_s *region;
for (region = g_region; region != NULL; region = region->next)
{
if (addr >= region->begin && addr < region->end)
{
DEBUGASSERT(addr + size <= region->end);
addr -= region->begin;
addr /= KASAN_SHADOW_SCALE;
*bit = addr % KASAN_BITS_PER_WORD;
return &region->shadow[addr / KASAN_BITS_PER_WORD];
}
}
return NULL;
}
static void kasan_report(uintptr_t addr, size_t size, bool is_write)
{
static int recursion;
if (++recursion == 1)
{
_alert("kasan detected a %s access error, address at %0#"PRIxPTR
", size is %zu\n", is_write ? "write" : "read", addr, size);
PANIC();
}
--recursion;
}
static bool kasan_is_poisoned(uintptr_t addr, size_t size)
{
FAR uintptr_t *p;
unsigned int bit;
p = kasan_mem_to_shadow(addr + size - 1, 1, &bit);
return p && ((*p >> bit) & 1);
}
static void kasan_set_poison(uintptr_t addr, size_t size, bool poisoned)
{
FAR uintptr_t *p;
unsigned int bit;
unsigned int nbit;
uintptr_t mask;
p = kasan_mem_to_shadow(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;
while (size >= nbit)
{
if (poisoned)
{
*p++ |= mask;
}
else
{
*p++ &= ~mask;
}
bit += nbit;
size -= nbit;
nbit = KASAN_BITS_PER_WORD;
mask = UINTPTR_MAX;
}
if (size)
{
mask &= KASAN_LAST_WORD_MASK(bit + size);
if (poisoned)
{
*p |= mask;
}
else
{
*p &= ~mask;
}
}
}
/****************************************************************************
* Public Functions
****************************************************************************/
/* Exported functions called from other mm module */
void kasan_poison(FAR const void *addr, size_t size)
{
kasan_set_poison((uintptr_t)addr, size, true);
}
void kasan_unpoison(FAR const void *addr, size_t size)
{
kasan_set_poison((uintptr_t)addr, size, false);
}
void kasan_register(FAR void *addr, FAR size_t *size)
{
FAR struct kasan_region_s *region;
region = (FAR struct kasan_region_s *)
((FAR char *)addr + *size - KASAN_REGION_SIZE(*size));
region->begin = (uintptr_t)addr;
region->end = region->begin + *size;
_SEM_WAIT(&g_lock);
region->next = g_region;
g_region = region;
_SEM_POST(&g_lock);
kasan_poison(addr, *size);
*size -= KASAN_REGION_SIZE(*size);
}
/* 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 char *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(uintptr_t addr, size_t size)
{
kasan_report(addr, size, false);
}
void __asan_report_store_n_noabort(uintptr_t addr, size_t size)
{
kasan_report(addr, size, true);
}
void __asan_report_load16_noabort(uintptr_t addr)
{
__asan_report_load_n_noabort(addr, 16);
}
void __asan_report_store16_noabort(uintptr_t addr)
{
__asan_report_store_n_noabort(addr, 16);
}
void __asan_report_load8_noabort(uintptr_t addr)
{
__asan_report_load_n_noabort(addr, 8);
}
void __asan_report_store8_noabort(uintptr_t addr)
{
__asan_report_store_n_noabort(addr, 8);
}
void __asan_report_load4_noabort(uintptr_t addr)
{
__asan_report_load_n_noabort(addr, 4);
}
void __asan_report_store4_noabort(uintptr_t addr)
{
__asan_report_store_n_noabort(addr, 4);
}
void __asan_report_load2_noabort(uintptr_t addr)
{
__asan_report_load_n_noabort(addr, 2);
}
void __asan_report_store2_noabort(uintptr_t addr)
{
__asan_report_store_n_noabort(addr, 2);
}
void __asan_report_load1_noabort(uintptr_t addr)
{
__asan_report_load_n_noabort(addr, 1);
}
void __asan_report_store1_noabort(uintptr_t addr)
{
__asan_report_store_n_noabort(addr, 1);
}
void __asan_loadN_noabort(uintptr_t addr, size_t size)
{
if (kasan_is_poisoned(addr, size))
{
kasan_report(addr, size, false);
}
}
void __asan_storeN_noabort(uintptr_t addr, size_t size)
{
if (kasan_is_poisoned(addr, size))
{
kasan_report(addr, size, true);
}
}
void __asan_load16_noabort(uintptr_t addr)
{
__asan_loadN_noabort(addr, 16);
}
void __asan_store16_noabort(uintptr_t addr)
{
__asan_storeN_noabort(addr, 16);
}
void __asan_load8_noabort(uintptr_t addr)
{
__asan_loadN_noabort(addr, 8);
}
void __asan_store8_noabort(uintptr_t addr)
{
__asan_storeN_noabort(addr, 8);
}
void __asan_load4_noabort(uintptr_t addr)
{
__asan_loadN_noabort(addr, 4);
}
void __asan_store4_noabort(uintptr_t addr)
{
__asan_storeN_noabort(addr, 4);
}
void __asan_load2_noabort(uintptr_t addr)
{
__asan_loadN_noabort(addr, 2);
}
void __asan_store2_noabort(uintptr_t addr)
{
__asan_storeN_noabort(addr, 2);
}
void __asan_load1_noabort(uintptr_t addr)
{
__asan_loadN_noabort(addr, 1);
}
void __asan_store1_noabort(uintptr_t addr)
{
__asan_storeN_noabort(addr, 1);
}
void __asan_loadN(uintptr_t addr, size_t size)
{
__asan_loadN_noabort(addr, size);
}
void __asan_storeN(uintptr_t addr, size_t size)
{
__asan_storeN_noabort(addr, size);
}
void __asan_load16(uintptr_t addr)
{
__asan_load16_noabort(addr);
}
void __asan_store16(uintptr_t addr)
{
__asan_store16_noabort(addr);
}
void __asan_load8(uintptr_t addr)
{
__asan_load8_noabort(addr);
}
void __asan_store8(uintptr_t addr)
{
__asan_store8_noabort(addr);
}
void __asan_load4(uintptr_t addr)
{
__asan_load4_noabort(addr);
}
void __asan_store4(uintptr_t addr)
{
__asan_store4_noabort(addr);
}
void __asan_load2(uintptr_t addr)
{
__asan_load2_noabort(addr);
}
void __asan_store2(uintptr_t addr)
{
__asan_store2_noabort(addr);
}
void __asan_load1(uintptr_t addr)
{
__asan_load1_noabort(addr);
}
void __asan_store1(uintptr_t addr)
{
__asan_store1_noabort(addr);
}

115
mm/kasan/kasan.h Normal file
View File

@ -0,0 +1,115 @@
/****************************************************************************
* mm/kasan/kasan.h
*
* 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.
*
****************************************************************************/
#ifndef __MM_KASAN_KASAN_H
#define __MM_KASAN_KASAN_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <stddef.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#ifndef CONFIG_MM_KASAN
# define kasan_poison(addr, size)
# define kasan_unpoison(addr, size)
# define kasan_register(addr, size)
#endif
/****************************************************************************
* Public Function Prototypes
****************************************************************************/
#ifdef __cplusplus
#define EXTERN extern "C"
extern "C"
{
#else
#define EXTERN extern
#endif
#ifdef CONFIG_MM_KASAN
/****************************************************************************
* Name: kasan_poison
*
* Description:
* Mark the memory range as inaccessible
*
* Input Parameters:
* addr - range start address
* size - range size
*
* Returned Value:
* None.
*
****************************************************************************/
void kasan_poison(FAR const void *addr, size_t size);
/****************************************************************************
* Name: kasan_unpoison
*
* Description:
* Mark the memory range as accessible
*
* Input Parameters:
* addr - range start address
* size - range size
*
* Returned Value:
* None.
*
****************************************************************************/
void kasan_unpoison(FAR const void *addr, size_t size);
/****************************************************************************
* Name: kasan_register
*
* Description:
* Monitor the memory range for invalid access check
*
* Input Parameters:
* addr - range start address
* size - range size
*
* Returned Value:
* None.
*
* Note:
* The size is shrinked for the shadow region
*
****************************************************************************/
void kasan_register(FAR void *addr, FAR size_t *size);
#endif /* CONFIG_MM_KASAN */
#undef EXTERN
#ifdef __cplusplus
}
#endif
#endif /* __MM_KASAN_KASAN_H */

View File

@ -31,6 +31,7 @@
#include <nuttx/mm/mm.h>
#include "mm_heap/mm.h"
#include "kasan/kasan.h"
/****************************************************************************
* Private Functions
@ -83,8 +84,12 @@ void mm_free(FAR struct mm_heap_s *heap, FAR void *mem)
return;
}
kasan_poison(mem, mm_malloc_size(mem));
if (mm_takesemaphore(heap) == false)
{
kasan_unpoison(mem, mm_malloc_size(mem));
/* We are in IDLE task & can't get sem, or meet -ESRCH return,
* which means we are in situations during context switching(See
* mm_takesemaphore() & getpid()). Then add to the delay list.

View File

@ -31,6 +31,7 @@
#include <nuttx/mm/mm.h>
#include "mm_heap/mm.h"
#include "kasan/kasan.h"
/****************************************************************************
* Public Functions
@ -86,6 +87,10 @@ void mm_addregion(FAR struct mm_heap_s *heap, FAR void *heapstart,
DEBUGASSERT(heapsize <= MMSIZE_MAX + 1);
#endif
/* Register to KASan for access check */
kasan_register(heapstart, &heapsize);
DEBUGVERIFY(mm_takesemaphore(heap));
/* Adjust the provided heap start and size so that they are both aligned

View File

@ -32,6 +32,7 @@
#include <nuttx/mm/mm.h>
#include "mm_heap/mm.h"
#include "kasan/kasan.h"
/****************************************************************************
* Pre-processor Definitions
@ -228,25 +229,20 @@ FAR void *mm_malloc(FAR struct mm_heap_s *heap, size_t size)
DEBUGASSERT(ret == NULL || mm_heapmember(heap, ret));
mm_givesemaphore(heap);
#ifdef CONFIG_MM_FILL_ALLOCATIONS
if (ret)
{
memset(ret, 0xaa, alignsize - SIZEOF_MM_ALLOCNODE);
}
kasan_unpoison(ret, mm_malloc_size(ret));
#ifdef CONFIG_MM_FILL_ALLOCATIONS
memset(ret, 0xaa, alignsize - SIZEOF_MM_ALLOCNODE);
#endif
/* If CONFIG_DEBUG_MM is defined, then output the result of the allocation
* to the SYSLOG.
*/
#ifdef CONFIG_DEBUG_MM
if (!ret)
{
mwarn("WARNING: Allocation failed, size %zu\n", alignsize);
minfo("Allocated %p, size %zu\n", ret, alignsize);
#endif
}
#ifdef CONFIG_DEBUG_MM
else
{
minfo("Allocated %p, size %zu\n", ret, alignsize);
mwarn("WARNING: Allocation failed, size %zu\n", alignsize);
}
#endif

View File

@ -29,6 +29,7 @@
#include <nuttx/mm/mm.h>
#include "mm_heap/mm.h"
#include "kasan/kasan.h"
/****************************************************************************
* Public Functions
@ -111,6 +112,8 @@ FAR void *mm_memalign(FAR struct mm_heap_s *heap, size_t alignment,
return NULL;
}
kasan_poison((FAR void *)rawchunk, mm_malloc_size((FAR void *)rawchunk));
/* We need to hold the MM semaphore while we muck with the chunks and
* nodelist.
*/
@ -219,5 +222,9 @@ FAR void *mm_memalign(FAR struct mm_heap_s *heap, size_t alignment,
}
mm_givesemaphore(heap);
kasan_unpoison((FAR void *)alignedchunk,
mm_malloc_size((FAR void *)alignedchunk));
return (FAR void *)alignedchunk;
}

View File

@ -32,6 +32,7 @@
#include <nuttx/mm/mm.h>
#include "mm_heap/mm.h"
#include "kasan/kasan.h"
/****************************************************************************
* Public Functions
@ -123,6 +124,8 @@ FAR void *mm_realloc(FAR struct mm_heap_s *heap, FAR void *oldmem,
if (newsize < oldsize)
{
mm_shrinkchunk(heap, oldnode, newsize);
kasan_poison((FAR char *)oldnode + oldnode->size,
oldsize - oldnode->size);
}
/* Then return the original address */
@ -264,12 +267,7 @@ FAR void *mm_realloc(FAR struct mm_heap_s *heap, FAR void *oldmem,
(next->preceding & MM_ALLOC_BIT);
}
/* Now we have to move the user contents 'down' in memory. memcpy
* should be safe for this.
*/
newmem = (FAR void *)((FAR char *)newnode + SIZEOF_MM_ALLOCNODE);
memcpy(newmem, oldmem, oldsize - SIZEOF_MM_ALLOCNODE);
/* Now we want to return newnode */
@ -335,6 +333,17 @@ FAR void *mm_realloc(FAR struct mm_heap_s *heap, FAR void *oldmem,
}
mm_givesemaphore(heap);
kasan_unpoison(newmem, mm_malloc_size(newmem));
if (newmem != oldmem)
{
/* Now we have to move the user contents 'down' in memory. memcpy
* should be safe for this.
*/
memcpy(newmem, oldmem, oldsize - SIZEOF_MM_ALLOCNODE);
}
return newmem;
}

View File

@ -102,6 +102,7 @@ void umm_initialize(FAR void *heap_start, size_t heap_size)
void umm_try_initialize(void)
{
uintptr_t allocbase;
size_t npages = 1;
/* Return if the user heap is already initialized. */
@ -110,16 +111,22 @@ void umm_try_initialize(void)
return;
}
/* Allocate one page. If we provide a zero brkaddr to pgalloc(),
#ifdef CONFIG_MM_KASAN
/* we have to commit all memory for the shadow region */
npages = CONFIG_ARCH_HEAP_NPAGES;
#endif
/* If we provide a zero brkaddr to pgalloc(),
* it will create the first block in the correct virtual address
* space and return the start address of that block.
*/
allocbase = pgalloc(0, 1);
allocbase = pgalloc(0, npages);
DEBUGASSERT(allocbase != 0);
/* Let umm_initialize do the real work. */
umm_initialize((FAR void *)allocbase, CONFIG_MM_PGSIZE);
umm_initialize((FAR void *)allocbase, npages * CONFIG_MM_PGSIZE);
}
#endif

View File

@ -276,6 +276,15 @@ static const char *g_white_list[] =
"SETATTR3resok",
"FS3args",
/* Ref:
* mm/kasan/kasan.c
*/
"__asan_loadN",
"__asan_storeN",
"__asan_loadN_noabort",
"__asan_storeN_noabort",
NULL
};