diff --git a/mm/Kconfig b/mm/Kconfig index 8c5e772432..8746a201dc 100644 --- a/mm/Kconfig +++ b/mm/Kconfig @@ -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" diff --git a/mm/Makefile b/mm/Makefile index 03980293af..323a8ed33c 100644 --- a/mm/Makefile +++ b/mm/Makefile @@ -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 diff --git a/mm/kasan/Make.defs b/mm/kasan/Make.defs new file mode 100644 index 0000000000..ab67a9c2f2 --- /dev/null +++ b/mm/kasan/Make.defs @@ -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 diff --git a/mm/kasan/kasan.c b/mm/kasan/kasan.c new file mode 100644 index 0000000000..90c324e7cd --- /dev/null +++ b/mm/kasan/kasan.c @@ -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 + +#include +#include +#include +#include + +#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 ®ion->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); +} diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h new file mode 100644 index 0000000000..c6fd07ccc6 --- /dev/null +++ b/mm/kasan/kasan.h @@ -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 + +/**************************************************************************** + * 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 */ diff --git a/mm/mm_heap/mm_free.c b/mm/mm_heap/mm_free.c index c3545cf9b4..c788649f4f 100644 --- a/mm/mm_heap/mm_free.c +++ b/mm/mm_heap/mm_free.c @@ -31,6 +31,7 @@ #include #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. diff --git a/mm/mm_heap/mm_initialize.c b/mm/mm_heap/mm_initialize.c index 9156111654..4e2a21b658 100644 --- a/mm/mm_heap/mm_initialize.c +++ b/mm/mm_heap/mm_initialize.c @@ -31,6 +31,7 @@ #include #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 diff --git a/mm/mm_heap/mm_malloc.c b/mm/mm_heap/mm_malloc.c index 598f553637..a1262f53fc 100644 --- a/mm/mm_heap/mm_malloc.c +++ b/mm/mm_heap/mm_malloc.c @@ -32,6 +32,7 @@ #include #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 diff --git a/mm/mm_heap/mm_memalign.c b/mm/mm_heap/mm_memalign.c index 9d1bdd6347..1fcd387c24 100644 --- a/mm/mm_heap/mm_memalign.c +++ b/mm/mm_heap/mm_memalign.c @@ -29,6 +29,7 @@ #include #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; } diff --git a/mm/mm_heap/mm_realloc.c b/mm/mm_heap/mm_realloc.c index 897201c53d..1842572b19 100644 --- a/mm/mm_heap/mm_realloc.c +++ b/mm/mm_heap/mm_realloc.c @@ -32,6 +32,7 @@ #include #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; } diff --git a/mm/umm_heap/umm_initialize.c b/mm/umm_heap/umm_initialize.c index ca0fcc6034..1eb726e669 100644 --- a/mm/umm_heap/umm_initialize.c +++ b/mm/umm_heap/umm_initialize.c @@ -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 diff --git a/tools/nxstyle.c b/tools/nxstyle.c index ae0c2883e4..f2b44fb8e8 100644 --- a/tools/nxstyle.c +++ b/tools/nxstyle.c @@ -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 };