This is an automated email from the ASF dual-hosted git repository. xiaoxiang pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 8f541d2ef2c3cc1a23379eed73c2ede992303851 Author: wangmingrong1 <wangmingro...@xiaomi.com> AuthorDate: Mon Mar 10 12:50:07 2025 +0800 mte/kasan: Implementing KASAN memory protection for ARM64 hardware MTE 1. Add hw_tags.c, which will call arm64_mte to implement tagging of memory blocks by operating registers 2. It has been able to run normally on the default NX memory allocator, excluding mempool and tlsf 3. On more complex configurations, memory tests such as memstress can run normally without system crashes Signed-off-by: wangmingrong1 <wangmingro...@xiaomi.com> --- arch/Kconfig | 4 ++ arch/arm64/Kconfig | 1 + arch/arm64/src/common/arm64_arch.h | 14 ++++-- arch/arm64/src/common/arm64_mmu.h | 6 ++- arch/arm64/src/common/arm64_mte.c | 76 ++++++++++++++++++++++++++++-- arch/arm64/src/qemu/qemu_boot.c | 4 +- include/nuttx/arch.h | 59 ++++++++++++++++++++++++ include/nuttx/mm/kasan.h | 15 ++++++ mm/kasan/Kconfig | 16 +++++-- mm/kasan/generic.c | 5 ++ mm/kasan/hook.c | 7 +++ mm/kasan/hw_tags.c | 94 ++++++++++++++++++++++++++++++++++++++ mm/mm_heap/mm_lock.c | 29 ++++++++++-- mm/mm_heap/mm_malloc_size.c | 13 +++++- mm/mm_heap/mm_realloc.c | 5 +- 15 files changed, 326 insertions(+), 22 deletions(-) diff --git a/arch/Kconfig b/arch/Kconfig index 8ce7ca2edf..1cc404ece9 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -546,6 +546,10 @@ config ARCH_HAVE_DEBUG bool "Architecture have debug support" default n +config ARCH_HAVE_MEMTAG + bool "Architecture have memory tagging support" + default n + config ARCH_HAVE_PERF_EVENTS bool default n diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 913e446937..f3fb57f077 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -185,6 +185,7 @@ menu "ARMv8.5 architectural features" config ARM64_MTE bool "Memory Tagging Extension support" + select ARCH_HAVE_MEMTAG select ARM64_TBI default y diff --git a/arch/arm64/src/common/arm64_arch.h b/arch/arm64/src/common/arm64_arch.h index 11815ca892..4b426c1798 100644 --- a/arch/arm64/src/common/arm64_arch.h +++ b/arch/arm64/src/common/arm64_arch.h @@ -506,11 +506,15 @@ uint64_t arm64_get_mpid(int cpu); int arm64_get_cpuid(uint64_t mpid); #endif -#ifdef CONFIG_ARM64_MTE -void arm64_enable_mte(void); -#else -#define arm64_enable_mte() -#endif +/**************************************************************************** + * Name: arm64_mte_init + * + * Description: + * Initialize MTE settings and enable memory tagging + * + ****************************************************************************/ + +void arm64_mte_init(void); #endif /* __ASSEMBLY__ */ diff --git a/arch/arm64/src/common/arm64_mmu.h b/arch/arm64/src/common/arm64_mmu.h index 916b4c9de9..053f4f5423 100644 --- a/arch/arm64/src/common/arm64_mmu.h +++ b/arch/arm64/src/common/arm64_mmu.h @@ -170,8 +170,10 @@ * in the address range [59:55] = 0b00000 are unchecked accesses. */ -#define TCR_TCMA0 (1ULL << 57) -#define TCR_TCMA1 (1ULL << 58) +#define TCR_TCMA0 BIT(57) +#define TCR_TCMA1 BIT(58) +#define TCR_MTX0_SHIFT BIT(60) +#define TCR_MTX1_SHIFT BIT(61) #define TCR_PS_BITS_4GB 0x0ULL #define TCR_PS_BITS_64GB 0x1ULL diff --git a/arch/arm64/src/common/arm64_mte.c b/arch/arm64/src/common/arm64_mte.c index 6a2230f90f..be61945b1c 100644 --- a/arch/arm64/src/common/arm64_mte.c +++ b/arch/arm64/src/common/arm64_mte.c @@ -29,18 +29,24 @@ #include <stdio.h> #include "arm64_arch.h" +#include "arm64_mmu.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #define GCR_EL1_VAL 0x10001 +#define MTE_TAG_SHIFT 56 + +/* The alignment length of the MTE must be a multiple of sixteen */ + +#define MTE_MM_AILGN 16 /**************************************************************************** * Private Functions ****************************************************************************/ -static int arm64_mte_is_support(void) +static int mte_is_support(void) { int supported; __asm__ volatile ( @@ -57,11 +63,67 @@ static int arm64_mte_is_support(void) * Public Functions ****************************************************************************/ -void arm64_enable_mte(void) +uint8_t up_memtag_get_tag(const void *addr) +{ + return 0xf0 | (uint8_t)(((uint64_t)addr) >> MTE_TAG_SHIFT); +} + +uint8_t up_memtag_get_random_tag(const void *addr) +{ + asm("irg %0, %0" : "=r" (addr)); + + return up_memtag_get_tag(addr); +} + +void *up_memtag_set_tag(const void *addr, uint8_t tag) +{ + return (FAR void *) + ((((uint64_t)addr) & ~((uint64_t)0xff << MTE_TAG_SHIFT)) | + ((uint64_t)tag << MTE_TAG_SHIFT)); +} + +/* Set MTE state */ + +bool up_memtag_bypass(bool bypass) +{ + uint64_t val = read_sysreg(sctlr_el1); + bool state = !(val & SCTLR_TCF1_BIT); + + if (bypass) + { + val &= ~SCTLR_TCF1_BIT; + } + else + { + val |= SCTLR_TCF1_BIT; + } + + write_sysreg(val, sctlr_el1); + return state; +} + +/* Set memory tags for a given memory range */ + +void up_memtag_tag_mem(const void *addr, size_t size) +{ + size_t i; + + DEBUGASSERT((uintptr_t)addr % MTE_MM_AILGN == 0); + DEBUGASSERT(size % MTE_MM_AILGN == 0); + + for (i = 0; i < size; i += MTE_MM_AILGN) + { + asm("stg %0, [%0]" : : "r"(addr + i)); + } +} + +/* Initialize MTE settings and enable memory tagging */ + +void arm64_mte_init(void) { uint64_t val; - if (!arm64_mte_is_support()) + if (!mte_is_support()) { return; } @@ -78,6 +140,14 @@ void arm64_enable_mte(void) assert(!(read_sysreg(ttbr0_el1) & TTBR_CNP_BIT)); assert(!(read_sysreg(ttbr1_el1) & TTBR_CNP_BIT)); + /* Controls the default value for skipping high bytes */ + + val = read_sysreg(tcr_el1); + val |= TCR_TCMA1; + write_sysreg(val, tcr_el1); + + /* Enable the MTE function */ + val = read_sysreg(sctlr_el1); val |= SCTLR_ATA_BIT | SCTLR_TCF1_BIT; write_sysreg(val, sctlr_el1); diff --git a/arch/arm64/src/qemu/qemu_boot.c b/arch/arm64/src/qemu/qemu_boot.c index 4eb75f16e2..df19de57a4 100644 --- a/arch/arm64/src/qemu/qemu_boot.c +++ b/arch/arm64/src/qemu/qemu_boot.c @@ -161,7 +161,9 @@ void arm64_chip_boot(void) arm64_mmu_init(true); - arm64_enable_mte(); +#ifdef CONFIG_ARM64_MTE + arm64_mte_init(); +#endif #ifdef CONFIG_DEVICE_TREE fdt_register((const char *)0x40000000); diff --git a/include/nuttx/arch.h b/include/nuttx/arch.h index f442ba474e..f7b8ba0b1a 100644 --- a/include/nuttx/arch.h +++ b/include/nuttx/arch.h @@ -3067,6 +3067,65 @@ int up_get_legacy_irq(uint32_t devfn, uint8_t line, uint8_t pin); #endif +#ifdef CONFIG_ARCH_HAVE_SYSCALL + +/**************************************************************************** + * Name: up_assert + ****************************************************************************/ + +void up_assert(FAR const char *filename, int linenum, FAR const char *msg); +#endif + +#ifdef CONFIG_ARCH_HAVE_MEMTAG + +/**************************************************************************** + * Name: up_memtag_bypass + * + * Description: + * Set MTE state bypass or not + * + ****************************************************************************/ + +bool up_memtag_bypass(bool bypass); + +/**************************************************************************** + * Name: up_memtag_get_tag + ****************************************************************************/ + +uint8_t up_memtag_get_tag(const void *addr); + +/**************************************************************************** + * Name: up_memtag_get_random_tag + * + * Description: + * Get a random label based on the address through the mte register + * + ****************************************************************************/ + +uint8_t up_memtag_get_random_tag(const void *addr); + +/**************************************************************************** + * Name: up_memtag_set_tag + * + * Description: + * Get the address with label + * + ****************************************************************************/ + +void *up_memtag_set_tag(const void *addr, uint8_t tag); + +/**************************************************************************** + * Name: up_memtag_tag_mem + * + * Description: + * Set memory tags for a given memory range + * + ****************************************************************************/ + +void up_memtag_tag_mem(const void *addr, size_t size); + +#endif /* CONFIG_ARCH_HAVE_MEMTAG */ + #undef EXTERN #if defined(__cplusplus) } diff --git a/include/nuttx/mm/kasan.h b/include/nuttx/mm/kasan.h index c066c10863..4c595fbfd4 100644 --- a/include/nuttx/mm/kasan.h +++ b/include/nuttx/mm/kasan.h @@ -48,6 +48,7 @@ # define kasan_stop() # define kasan_debugpoint(t,a,s) 0 # define kasan_init_early() +# define kasan_bypass(state) (state) #else # define kasan_init_early() kasan_stop() @@ -180,7 +181,11 @@ uint8_t kasan_get_tag(FAR const void *addr); * ****************************************************************************/ +#ifdef CONFIG_MM_KASAN_INSTRUMENT void kasan_start(void); +#else +# define kasan_start() +#endif /**************************************************************************** * Name: kasan_stop @@ -198,7 +203,11 @@ void kasan_start(void); * ****************************************************************************/ +#ifdef CONFIG_MM_KASAN_INSTRUMENT void kasan_stop(void); +#else +# define kasan_stop() +#endif /**************************************************************************** * Name: kasan_debugpoint @@ -222,6 +231,12 @@ void kasan_stop(void); int kasan_debugpoint(int type, FAR void *addr, size_t size); +/**************************************************************************** + * Name: kasan_bypass + ****************************************************************************/ + +bool kasan_bypass(bool state); + #undef EXTERN #ifdef __cplusplus } diff --git a/mm/kasan/Kconfig b/mm/kasan/Kconfig index 075f1bf190..8520646a24 100644 --- a/mm/kasan/Kconfig +++ b/mm/kasan/Kconfig @@ -32,14 +32,19 @@ config MM_KASAN_GENERIC KASan generic mode that does not require hardware support at all config MM_KASAN_SW_TAGS - bool "KAsan SW tags" - select ARM64_TBI + bool "KAsan softtags tags" + select ARM64_TBI if ARCH_ARM64 select MM_KASAN_INSTRUMENT - depends on ARCH_ARM64 ---help--- KAsan based on software tags -endchoice # KAsan Mode +config MM_KASAN_HW_TAGS + bool "KAsan hardware tags" + select ARM64_MTE if ARCH_ARM64 + ---help--- + KAsan based on hardware tags + +endchoice config MM_KASAN_INSTRUMENT_ALL bool "Enable KASan for the entire image" @@ -52,6 +57,8 @@ config MM_KASAN_INSTRUMENT_ALL to check. Enabling this option will get image size increased and performance decreased significantly. +if MM_KASAN_INSTRUMENT + config MM_KASAN_REGIONS int "Kasan region count" default 8 @@ -126,4 +133,5 @@ config MM_KASAN_GLOBAL_ALIGN It is recommended to use 1, 2, 4, 8, 16, 32. The maximum value is 32. +endif # MM_KASAN_INSTRUMENT endif # MM_KASAN diff --git a/mm/kasan/generic.c b/mm/kasan/generic.c index e649be4bd7..1a736d28be 100644 --- a/mm/kasan/generic.c +++ b/mm/kasan/generic.c @@ -230,6 +230,11 @@ FAR void *kasan_unpoison(FAR const void *addr, size_t size) return (FAR void *)addr; } +bool kasan_bypass(bool state) +{ + return false; +} + void kasan_register(FAR void *addr, FAR size_t *size) { FAR struct kasan_region_s *region; diff --git a/mm/kasan/hook.c b/mm/kasan/hook.c index d4dc19c212..e67b2cee1c 100644 --- a/mm/kasan/hook.c +++ b/mm/kasan/hook.c @@ -42,6 +42,8 @@ # include "generic.c" #elif defined(CONFIG_MM_KASAN_SW_TAGS) # include "sw_tags.c" +#elif defined(CONFIG_MM_KASAN_HW_TAGS) +# include "hw_tags.c" #else # define kasan_is_poisoned(addr, size) false #endif @@ -96,6 +98,8 @@ #define KASAN_INIT_VALUE 0xcafe +#ifdef CONFIG_MM_KASAN_INSTRUMENT + /**************************************************************************** * Private Types ****************************************************************************/ @@ -405,3 +409,6 @@ DEFINE_ASAN_LOAD_STORE(2) DEFINE_ASAN_LOAD_STORE(4) DEFINE_ASAN_LOAD_STORE(8) DEFINE_ASAN_LOAD_STORE(16) + +#endif /* CONFIG_MM_KASAN_INSTRUMENT */ + diff --git a/mm/kasan/hw_tags.c b/mm/kasan/hw_tags.c new file mode 100644 index 0000000000..5b961cff07 --- /dev/null +++ b/mm/kasan/hw_tags.c @@ -0,0 +1,94 @@ +/**************************************************************************** + * mm/kasan/hw_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/arch.h> + +/**************************************************************************** + * Private Function + ****************************************************************************/ + +static FAR void * +kasan_poison_tag(FAR const void *addr, size_t size, uint8_t tag) +{ + FAR void *tag_addr; + + /* Label this address pointer */ + + tag_addr = up_memtag_set_tag(addr, tag); + + /* Add MTE hardware label to memory block */ + + up_memtag_tag_mem(tag_addr, size); + + return tag_addr; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +bool kasan_bypass(bool state) +{ + return up_memtag_bypass(state); +} + +FAR void *kasan_clear_tag(FAR const void *addr) +{ + return up_memtag_set_tag(addr, 0); +} + +void kasan_poison(FAR const void *addr, size_t size) +{ + uint8_t tag = up_memtag_get_random_tag(addr); + + kasan_poison_tag(addr, size, tag); +} + +uint8_t kasan_get_tag(FAR const void *addr) +{ + return up_memtag_get_tag(addr); +} + +FAR void *kasan_set_tag(FAR const void *addr, uint8_t tag) +{ + return up_memtag_set_tag(addr, tag); +} + +FAR void *kasan_unpoison(FAR const void *addr, size_t size) +{ + uint8_t tag = up_memtag_get_random_tag(addr); + + return kasan_poison_tag(addr, size, tag); +} + +void kasan_register(FAR void *addr, FAR size_t *size) +{ + uint8_t tag = up_memtag_get_random_tag(addr); + + kasan_poison_tag(addr, *size, tag); +} + +void kasan_unregister(FAR void *addr) +{ +} diff --git a/mm/mm_heap/mm_lock.c b/mm/mm_heap/mm_lock.c index eb9d8ddb18..5e0eabb4f5 100644 --- a/mm/mm_heap/mm_lock.c +++ b/mm/mm_heap/mm_lock.c @@ -32,6 +32,7 @@ #include <debug.h> #include <nuttx/arch.h> +#include <nuttx/mm/kasan.h> #include <nuttx/mm/mm.h> #include "mm_heap/mm.h" @@ -69,7 +70,16 @@ int mm_lock(FAR struct mm_heap_s *heap) * Or, touch the heap internal data directly. */ - return nxmutex_is_locked(&heap->mm_lock) ? -EAGAIN : 0; + if (nxmutex_is_locked(&heap->mm_lock)) + { + return -EAGAIN; + } + else + { + kasan_bypass(true); + return 0; + } + # else /* Can't take mutex in SMP interrupt handler */ @@ -94,7 +104,13 @@ int mm_lock(FAR struct mm_heap_s *heap) } else { - return nxmutex_lock(&heap->mm_lock); + int ret = nxmutex_lock(&heap->mm_lock); + if (ret >= 0) + { + kasan_bypass(true); + } + + return 0; } } @@ -111,10 +127,12 @@ void mm_unlock(FAR struct mm_heap_s *heap) #if defined(CONFIG_BUILD_FLAT) || defined(__KERNEL__) if (up_interrupt_context()) { + kasan_bypass(false); return; } #endif + kasan_bypass(false); DEBUGVERIFY(nxmutex_unlock(&heap->mm_lock)); } @@ -128,8 +146,12 @@ void mm_unlock(FAR struct mm_heap_s *heap) irqstate_t mm_lock_irq(FAR struct mm_heap_s *heap) { + irqstate_t flags = up_irq_save(); + UNUSED(heap); - return up_irq_save(); + kasan_bypass(true); + + return flags; } /**************************************************************************** @@ -143,5 +165,6 @@ irqstate_t mm_lock_irq(FAR struct mm_heap_s *heap) void mm_unlock_irq(FAR struct mm_heap_s *heap, irqstate_t state) { UNUSED(heap); + kasan_bypass(false); up_irq_restore(state); } diff --git a/mm/mm_heap/mm_malloc_size.c b/mm/mm_heap/mm_malloc_size.c index bb8ed7dcf5..895f11ee2b 100644 --- a/mm/mm_heap/mm_malloc_size.c +++ b/mm/mm_heap/mm_malloc_size.c @@ -29,6 +29,7 @@ #include <assert.h> #include <debug.h> +#include <nuttx/mm/kasan.h> #include <nuttx/mm/mm.h> #include "mm_heap/mm.h" @@ -40,12 +41,17 @@ size_t mm_malloc_size(FAR struct mm_heap_s *heap, FAR void *mem) { FAR struct mm_freenode_s *node; + ssize_t size; + bool flag; + + flag = kasan_bypass(true); #ifdef CONFIG_MM_HEAP_MEMPOOL if (heap->mm_mpool) { - ssize_t size = mempool_multiple_alloc_size(heap->mm_mpool, mem); + size = mempool_multiple_alloc_size(heap->mm_mpool, mem); if (size >= 0) { + kasan_bypass(flag); return size; } } @@ -66,5 +72,8 @@ size_t mm_malloc_size(FAR struct mm_heap_s *heap, FAR void *mem) DEBUGASSERT(MM_NODE_IS_ALLOC(node)); - return MM_SIZEOF_NODE(node) - MM_ALLOCNODE_OVERHEAD; + size = MM_SIZEOF_NODE(node) - MM_ALLOCNODE_OVERHEAD; + + kasan_bypass(flag); + return size; } diff --git a/mm/mm_heap/mm_realloc.c b/mm/mm_heap/mm_realloc.c index a91e2988ea..a6bc85bf0c 100644 --- a/mm/mm_heap/mm_realloc.c +++ b/mm/mm_heap/mm_realloc.c @@ -388,11 +388,12 @@ FAR void *mm_realloc(FAR struct mm_heap_s *heap, FAR void *oldmem, heap->mm_curused - newsize); sched_note_heap(NOTE_HEAP_ALLOC, heap, newmem, newsize, heap->mm_curused); + + size = MM_SIZEOF_NODE(oldnode); mm_unlock(heap); MM_ADD_BACKTRACE(heap, (FAR char *)newmem - MM_SIZEOF_ALLOCNODE); - newmem = kasan_unpoison(newmem, MM_SIZEOF_NODE(oldnode) - - MM_ALLOCNODE_OVERHEAD); + newmem = kasan_unpoison(newmem, size - MM_ALLOCNODE_OVERHEAD); oldmem = kasan_set_tag(oldmem, kasan_get_tag(newmem)); if (newmem != oldmem)