kasan: HW_TAGS tests support and fixes
Subsystems (8)
v3 cover kasan: HW_TAGS tests support and fixes
This patchset adds support for running KASAN-KUnit tests with the hardware tag-based mode and also contains a few fixes.
Changes v2->v3:
- Don't call kmalloc(0) when generating random size.
- Use ARRAY_SIZE() in kmem_cache_bulk_alloc() test.
- Print error message when tests are being ran with kasan.mode=off.
- Move _RET_IP_ to inline wrappers for kasan annotations.
Andrey Konovalov (15):
kasan: prefix global functions with kasan_
kasan: clarify HW_TAGS impact on TBI
kasan: clean up comments in tests
kasan: add macros to simplify checking test constraints
kasan: add match-all tag tests
kasan, arm64: allow using KUnit tests with HW_TAGS mode
kasan: rename CONFIG_TEST_KASAN_MODULE
kasan: add compiler barriers to KUNIT_EXPECT_KASAN_FAIL
kasan: adapt kmalloc_uaf2 test to HW_TAGS mode
kasan: fix memory corruption in kasan_bitops_tags test
kasan: move _RET_IP_ to inline wrappers
kasan: fix bug detection via ksize for HW_TAGS mode
kasan: add proper page allocator tests
kasan: add a test for kmem_cache_alloc/free_bulk
kasan: don't run tests when KASAN is not enabled
Documentation/dev-tools/kasan.rst | 24 +- arch/arm64/include/asm/memory.h | 1 + arch/arm64/include/asm/mte-kasan.h | 12 + arch/arm64/kernel/mte.c | 12 + arch/arm64/mm/fault.c | 16 +- include/linux/kasan-checks.h | 6 + include/linux/kasan.h | 37 ++- lib/Kconfig.kasan | 6 +- lib/Makefile | 2 +- lib/test_kasan.c | 424 +++++++++++++++++++++-------- lib/test_kasan_module.c | 5 +- mm/kasan/common.c | 56 ++-- mm/kasan/generic.c | 38 +-- mm/kasan/kasan.h | 69 +++-- mm/kasan/quarantine.c | 22 +- mm/kasan/report.c | 15 +- mm/kasan/report_generic.c | 8 +- mm/kasan/report_hw_tags.c | 8 +- mm/kasan/report_sw_tags.c | 8 +- mm/kasan/shadow.c | 26 +- mm/kasan/sw_tags.c | 20 +- mm/mempool.c | 2 +- mm/slab.c | 2 +- mm/slab_common.c | 16 +- mm/slub.c | 4 +- tools/objtool/check.c | 2 +- 26 files changed, 559 insertions(+), 282 deletions(-)
Signature · 1 line
2.30.0.284.gd98b1dd5eaa7-goog
PATCH v3 1/15 kasan: prefix global functions with kasan_
There's a number of internal KASAN functions that are used across multiple source code files and therefore aren't marked as static inline. To avoid littering the kernel function names list with generic function names, prefix all such KASAN functions with kasan_.
As a part of this change:
- Rename internal (un)poison_range() to kasan_(un)poison() (no _range) to avoid name collision with a public kasan_unpoison_range().
- Rename check_memory_region() to kasan_check_range(), as it's a more fitting name.
--- mm/kasan/common.c | 47 +++++++++++++++++++------------------- mm/kasan/generic.c | 36 ++++++++++++++--------------- mm/kasan/kasan.h | 48 +++++++++++++++++++-------------------- mm/kasan/quarantine.c | 22 +++++++++--------- mm/kasan/report.c | 13 ++++++----- mm/kasan/report_generic.c | 8 +++---- mm/kasan/report_hw_tags.c | 8 +++---- mm/kasan/report_sw_tags.c | 8 +++---- mm/kasan/shadow.c | 26 ++++++++++----------- mm/kasan/sw_tags.c | 16 ++++++------- tools/objtool/check.c | 2 +- 11 files changed, 117 insertions(+), 117 deletions(-)
mm/kasan/common.c +23 -24
@@ -60,7 +60,7 @@ void kasan_disable_current(void)void __kasan_unpoison_range (const void *address,size_t size) { -unpoison_range (address, size); +kasan_unpoison (address, size); }#if CONFIG_KASAN_STACK
@@ -69,7 +69,7 @@ void kasan_unpoison_task_stack(struct task_struct *task) {void *base =task_stack_page (task); -unpoison_range (base,THREAD_SIZE ); +kasan_unpoison (base,THREAD_SIZE ); }/* Unpoison the stack for the current task beyond a watermark sp value. */
@@ -82,7 +82,7 @@ asmlinkage void kasan_unpoison_task_stack_below(const void *watermark)*/ void *base = (void *)((unsigned long )watermark & ~(THREAD_SIZE -1 )); - unpoison_range(base, watermark - base); + kasan_unpoison(base, watermark - base); }#endif /* CONFIG_KASAN_STACK */
@@ -105,18 +105,17 @@ void __kasan_alloc_pages(struct page *page, unsigned int order)if (unlikely (PageHighMem (page)))return ; - tag =random_tag (); + tag =kasan_random_tag ();for (i =0 ; i < (1 << order); i++)page_kasan_tag_set (page + i, tag); -unpoison_range (page_address (page),PAGE_SIZE << order); +kasan_unpoison (page_address (page),PAGE_SIZE << order); }void __kasan_free_pages (struct page *page,unsigned int order) {if (likely (!PageHighMem (page))) -poison_range (page_address (page), -PAGE_SIZE << order, -KASAN_FREE_PAGE ); +kasan_poison (page_address (page),PAGE_SIZE << order, +KASAN_FREE_PAGE ); }/*
@@ -246,18 +245,18 @@ void __kasan_poison_slab(struct page *page)for (i =0 ; i <compound_nr (page); i++)page_kasan_tag_reset (page + i); -poison_range (page_address (page),page_size (page), +kasan_poison (page_address (page),page_size (page),KASAN_KMALLOC_REDZONE ); }void __kasan_unpoison_object_data (struct kmem_cache *cache,void *object) { -unpoison_range (object, cache->object_size); +kasan_unpoison (object, cache->object_size); }void __kasan_poison_object_data (struct kmem_cache *cache,void *object) { -poison_range (object, cache->object_size,KASAN_KMALLOC_REDZONE ); +kasan_poison (object, cache->object_size,KASAN_KMALLOC_REDZONE ); }/*
@@ -294,7 +293,7 @@ static u8 assign_tag(struct kmem_cache *cache, const void *object,* set, assign a tag when the object is being allocated (init == false). */ if (!cache->ctor && !(cache->flags &SLAB_TYPESAFE_BY_RCU )) -return init ?KASAN_TAG_KERNEL :random_tag (); +return init ?KASAN_TAG_KERNEL :kasan_random_tag ();/* For caches that either have a constructor or SLAB_TYPESAFE_BY_RCU: */ #ifdef CONFIG_SLAB
@@ -305,7 +304,7 @@ static u8 assign_tag(struct kmem_cache *cache, const void *object,* For SLUB assign a random tag during slab creation, otherwise reuse * the already assigned tag. */ -return init ?random_tag () :get_tag (object); +return init ?kasan_random_tag () :get_tag (object);#endif }
@@ -346,12 +345,12 @@ static bool ____kasan_slab_free(struct kmem_cache *cache, void *object,if (unlikely (cache->flags &SLAB_TYPESAFE_BY_RCU ))return false; -if (check_invalid_free (tagged_object)) { +if (kasan_check_invalid_free (tagged_object)) {kasan_report_invalid_free (tagged_object, ip);return true; } -poison_range (object, cache->object_size,KASAN_KMALLOC_FREE ); +kasan_poison (object, cache->object_size,KASAN_KMALLOC_FREE );if (!kasan_stack_collection_enabled ())return false;
@@ -361,7 +360,7 @@ static bool ____kasan_slab_free(struct kmem_cache *cache, void *object,kasan_set_free_info (cache, object, tag); -return quarantine_put (cache, object); +return kasan_quarantine_put (cache, object); }bool __kasan_slab_free (struct kmem_cache *cache,void *object,unsigned long ip)
@@ -386,7 +385,7 @@ void __kasan_slab_free_mempool(void *ptr, unsigned long ip)kasan_report_invalid_free (ptr, ip);return ; } -poison_range (ptr,page_size (page),KASAN_FREE_PAGE ); +kasan_poison (ptr,page_size (page),KASAN_FREE_PAGE ); }else {____kasan_slab_free (page->slab_cache, ptr, ip, false); }
@@ -409,7 +408,7 @@ static void *____kasan_kmalloc(struct kmem_cache *cache, const void *object,u8 tag;if (gfpflags_allow_blocking (flags)) -quarantine_reduce (); +kasan_quarantine_reduce ();if (unlikely (object ==NULL ))return NULL ;
@@ -421,9 +420,9 @@ static void *____kasan_kmalloc(struct kmem_cache *cache, const void *object, tag =assign_tag (cache, object, false, keep_tag);/* Tag is ignored in set_tag without CONFIG_KASAN_SW/HW_TAGS */ -unpoison_range (set_tag (object, tag), size); -poison_range ((void *)redzone_start, redzone_end - redzone_start, -KASAN_KMALLOC_REDZONE ); +kasan_unpoison (set_tag (object, tag), size); +kasan_poison ((void *)redzone_start, redzone_end - redzone_start, +KASAN_KMALLOC_REDZONE );if (kasan_stack_collection_enabled ())set_alloc_info (cache, (void *)object, flags);
@@ -452,7 +451,7 @@ void * __must_check __kasan_kmalloc_large(const void *ptr, size_t size,unsigned long redzone_end;if (gfpflags_allow_blocking (flags)) -quarantine_reduce (); +kasan_quarantine_reduce ();if (unlikely (ptr ==NULL ))return NULL ;
@@ -462,8 +461,8 @@ void * __must_check __kasan_kmalloc_large(const void *ptr, size_t size, KASAN_GRANULE_SIZE); redzone_end = (unsigned long )ptr +page_size (page); -unpoison_range (ptr, size); -poison_range ((void *)redzone_start, redzone_end - redzone_start, +kasan_unpoison (ptr, size); +kasan_poison ((void *)redzone_start, redzone_end - redzone_start,KASAN_PAGE_REDZONE );return (void *)ptr;
mm/kasan/generic.c +18 -18
@@ -158,7 +158,7 @@ static __always_inline bool memory_is_poisoned(unsigned long addr, size_t size)return memory_is_poisoned_n (addr, size); } -static __always_inline bool check_memory_region_inline(unsigned long addr, +static __always_inline bool check_region_inline(unsigned long addr,size_t size,bool write,unsigned long ret_ip) {
@@ -179,13 +179,13 @@ static __always_inline bool check_memory_region_inline(unsigned long addr,return !kasan_report (addr, size, write, ret_ip); } -bool check_memory_region (unsigned long addr,size_t size,bool write, -unsigned long ret_ip) +bool kasan_check_range (unsigned long addr,size_t size,bool write, +unsigned long ret_ip) { -return check_memory_region_inline (addr, size, write, ret_ip); +return check_region_inline (addr, size, write, ret_ip); } -bool check_invalid_free (void *addr) +bool kasan_check_invalid_free (void *addr) {s8 shadow_byte =READ_ONCE (*(s8 *)kasan_mem_to_shadow (addr));
@@ -194,22 +194,22 @@ bool check_invalid_free(void *addr)void kasan_cache_shrink (struct kmem_cache *cache) { -quarantine_remove_cache (cache); +kasan_quarantine_remove_cache (cache); }void kasan_cache_shutdown (struct kmem_cache *cache) {if (!__kmem_cache_empty (cache)) -quarantine_remove_cache (cache); +kasan_quarantine_remove_cache (cache); }static void register_global (struct kasan_global *global) {size_t aligned_size =round_up (global->size,KASAN_GRANULE_SIZE ); -unpoison_range (global->beg, global->size); +kasan_unpoison (global->beg, global->size); -poison_range (global->beg + aligned_size, +kasan_poison (global->beg + aligned_size, global->size_with_redzone - aligned_size,KASAN_GLOBAL_REDZONE ); }
@@ -231,7 +231,7 @@ EXPORT_SYMBOL(__asan_unregister_globals);#define DEFINE_ASAN_LOAD_STORE (size) \ void __asan_load##size(unsigned long addr) \ { \ - check_memory_region_inline(addr, size, false, _RET_IP_);\ + check_region_inline(addr, size, false, _RET_IP_); \ } \ EXPORT_SYMBOL(__asan_load##size); \ __alias(__asan_load##size) \
@@ -239,7 +239,7 @@ EXPORT_SYMBOL(__asan_unregister_globals); EXPORT_SYMBOL(__asan_load##size##_noabort); \ void __asan_store##size(unsigned long addr) \ { \ - check_memory_region_inline(addr, size, true, _RET_IP_); \ + check_region_inline(addr, size, true, _RET_IP_); \ } \ EXPORT_SYMBOL(__asan_store##size); \ __alias(__asan_store##size) \
@@ -254,7 +254,7 @@ DEFINE_ASAN_LOAD_STORE(16);void __asan_loadN (unsigned long addr,size_t size) { -check_memory_region (addr, size, false, _RET_IP_); +kasan_check_range (addr, size, false, _RET_IP_); }EXPORT_SYMBOL (__asan_loadN);
@@ -264,7 +264,7 @@ EXPORT_SYMBOL(__asan_loadN_noabort);void __asan_storeN (unsigned long addr,size_t size) { -check_memory_region (addr, size, true, _RET_IP_); +kasan_check_range (addr, size, true, _RET_IP_); }EXPORT_SYMBOL (__asan_storeN);
@@ -290,11 +290,11 @@ void __asan_alloca_poison(unsigned long addr, size_t size)WARN_ON (!IS_ALIGNED (addr,KASAN_ALLOCA_REDZONE_SIZE )); -unpoison_range ((const void *)(addr + rounded_down_size), - size - rounded_down_size); -poison_range (left_redzone,KASAN_ALLOCA_REDZONE_SIZE , +kasan_unpoison ((const void *)(addr + rounded_down_size), + size - rounded_down_size); +kasan_poison (left_redzone,KASAN_ALLOCA_REDZONE_SIZE ,KASAN_ALLOCA_LEFT ); -poison_range (right_redzone, padding_size +KASAN_ALLOCA_REDZONE_SIZE , +kasan_poison (right_redzone, padding_size +KASAN_ALLOCA_REDZONE_SIZE ,KASAN_ALLOCA_RIGHT ); }EXPORT_SYMBOL (__asan_alloca_poison);
@@ -305,7 +305,7 @@ void __asan_allocas_unpoison(const void *stack_top, const void *stack_bottom)if (unlikely (!stack_top || stack_top > stack_bottom))return ; -unpoison_range (stack_top, stack_bottom - stack_top); +kasan_unpoison (stack_top, stack_bottom - stack_top); }EXPORT_SYMBOL (__asan_allocas_unpoison);
mm/kasan/kasan.h +24 -24
107 lines not on this page · raw message
mm/kasan/quarantine.c +11 -11
@@ -168,7 +168,7 @@ static void qlist_free_all(struct qlist_head *q, struct kmem_cache *cache)qlist_init (q); } -bool quarantine_put (struct kmem_cache *cache,void *object) +bool kasan_quarantine_put (struct kmem_cache *cache,void *object) {unsigned long flags;struct qlist_head *q;
@@ -184,11 +184,11 @@ bool quarantine_put(struct kmem_cache *cache, void *object)/* * Note: irq must be disabled until after we move the batch to the -* global quarantine. Otherwise quarantine_remove_cache() can miss -* some objects belonging to the cache if they are in our local temp -* list. quarantine_remove_cache() executes on_each_cpu() at the -* beginning which ensures that it either sees the objects in per-cpu -* lists or in the global quarantine. +* global quarantine. Otherwise kasan_quarantine_remove_cache() can +* miss some objects belonging to the cache if they are in our local +* temp list. kasan_quarantine_remove_cache() executes on_each_cpu() +* at the beginning which ensures that it either sees the objects in +* per-cpu lists or in the global quarantine. */ local_irq_save (flags);
@@ -222,7 +222,7 @@ bool quarantine_put(struct kmem_cache *cache, void *object)return true; } -void quarantine_reduce (void ) +void kasan_quarantine_reduce (void ) {size_t total_size, new_quarantine_size, percpu_quarantines;unsigned long flags;
@@ -234,7 +234,7 @@ void quarantine_reduce(void)return ;/* -* srcu critical section ensures that quarantine_remove_cache() +* srcu critical section ensures that kasan_quarantine_remove_cache() * will not miss objects belonging to the cache while they are in our * local to_free list. srcu is chosen because (1) it gives us private * grace period domain that does not interfere with anything else,
@@ -309,15 +309,15 @@ static void per_cpu_remove_cache(void *arg) }/* Free all quarantined objects belonging to cache. */ -void quarantine_remove_cache (struct kmem_cache *cache) +void kasan_quarantine_remove_cache (struct kmem_cache *cache) {unsigned long flags, i;struct qlist_head to_free =QLIST_INIT ;/* * Must be careful to not miss any objects that are being moved from -* per-cpu list to the global quarantine in quarantine_put(), -* nor objects being freed in quarantine_reduce(). on_each_cpu() +* per-cpu list to the global quarantine in kasan_quarantine_put(), +* nor objects being freed in kasan_quarantine_reduce(). on_each_cpu() * achieves the first goal, while synchronize_srcu() achieves the * second. */
mm/kasan/report.c +7 -6
@@ -61,7 +61,7 @@ __setup("kasan_multi_shot", kasan_set_multi_shot);static void print_error_description (struct kasan_access_info *info) {pr_err ("BUG: KASAN: %s in %pS\n" , -get_bug_type (info), (void *)info->ip); +kasan_get_bug_type (info), (void *)info->ip);if (info->access_size) pr_err("%s of size %zu at addr %px by task %s/%d\n", info->is_write ? "Write" : "Read", info->access_size,
@@ -247,7 +247,7 @@ static void print_address_description(void *addr, u8 tag)dump_page (page,"kasan: bad access detected" ); } -print_address_stack_frame (addr); +kasan_print_address_stack_frame (addr); }static bool meta_row_is_guilty (const void *row,const void *addr)
@@ -293,7 +293,7 @@ static void print_memory_metadata(const void *addr)* function, because generic functions may try to * access kasan mapping for the passed address. */ -metadata_fetch_row (&metadata[0 ], row); +kasan_metadata_fetch_row (&metadata[0 ], row); print_hex_dump(KERN_ERR, buffer, DUMP_PREFIX_NONE, META_BYTES_PER_ROW, 1,
@@ -350,7 +350,7 @@ void kasan_report_invalid_free(void *object, unsigned long ip)start_report (&flags);pr_err ("BUG: KASAN: double-free or invalid-free in %pS\n" , (void *)ip); -print_tags (tag, object); +kasan_print_tags (tag, object);pr_err ("\n" );print_address_description (object, tag);pr_err ("\n" );
@@ -378,7 +378,8 @@ static void __kasan_report(unsigned long addr, size_t size, bool is_write, info.access_addr = tagged_addr;if (addr_has_metadata (untagged_addr)) - info.first_bad_addr =find_first_bad_addr (tagged_addr, size); + info.first_bad_addr = +kasan_find_first_bad_addr (tagged_addr, size);else info.first_bad_addr = untagged_addr; info.access_size = size;
@@ -389,7 +390,7 @@ static void __kasan_report(unsigned long addr, size_t size, bool is_write,print_error_description (&info);if (addr_has_metadata (untagged_addr)) -print_tags (get_tag (tagged_addr), info.first_bad_addr); +kasan_print_tags (get_tag (tagged_addr), info.first_bad_addr);pr_err ("\n" );if (addr_has_metadata (untagged_addr)) {
mm/kasan/report_generic.c +4 -4
@@ -30,7 +30,7 @@#include "kasan.h" #include "../slab.h" -void *find_first_bad_addr (void *addr,size_t size) +void *kasan_find_first_bad_addr (void *addr,size_t size) {void *p = addr;
@@ -105,7 +105,7 @@ static const char *get_wild_bug_type(struct kasan_access_info *info)return bug_type; } -const char *get_bug_type (struct kasan_access_info *info) +const char *kasan_get_bug_type (struct kasan_access_info *info) {/* * If access_size is a negative number, then it has reason to be
@@ -123,7 +123,7 @@ const char *get_bug_type(struct kasan_access_info *info)return get_wild_bug_type (info); } -void metadata_fetch_row (char *buffer,void *row) +void kasan_metadata_fetch_row (char *buffer,void *row) {memcpy (buffer,kasan_mem_to_shadow (row),META_BYTES_PER_ROW ); }
@@ -263,7 +263,7 @@ static bool __must_check get_address_stack_frame_info(const void *addr,return true; } -void print_address_stack_frame (const void *addr) +void kasan_print_address_stack_frame (const void *addr) {unsigned long offset;const char *frame_descr;
mm/kasan/report_hw_tags.c +4 -4
@@ -15,17 +15,17 @@#include "kasan.h" -const char *get_bug_type (struct kasan_access_info *info) +const char *kasan_get_bug_type (struct kasan_access_info *info) {return "invalid-access" ; } -void *find_first_bad_addr (void *addr,size_t size) +void *kasan_find_first_bad_addr (void *addr,size_t size) {return kasan_reset_tag (addr); } -void metadata_fetch_row (char *buffer,void *row) +void kasan_metadata_fetch_row (char *buffer,void *row) {int i;
@@ -33,7 +33,7 @@ void metadata_fetch_row(char *buffer, void *row) buffer[i] =hw_get_mem_tag (row + i *KASAN_GRANULE_SIZE ); } -void print_tags (u8 addr_tag,const void *addr) +void kasan_print_tags (u8 addr_tag,const void *addr) {u8 memory_tag =hw_get_mem_tag ((void *)addr);
mm/kasan/report_sw_tags.c +4 -4
@@ -29,7 +29,7 @@#include "kasan.h" #include "../slab.h" -const char *get_bug_type (struct kasan_access_info *info) +const char *kasan_get_bug_type (struct kasan_access_info *info) {#ifdef CONFIG_KASAN_SW_TAGS_IDENTIFY struct kasan_alloc_meta *alloc_meta;
@@ -72,7 +72,7 @@ const char *get_bug_type(struct kasan_access_info *info)return "invalid-access" ; } -void *find_first_bad_addr (void *addr,size_t size) +void *kasan_find_first_bad_addr (void *addr,size_t size) {u8 tag =get_tag (addr);void *p =kasan_reset_tag (addr);
@@ -83,12 +83,12 @@ void *find_first_bad_addr(void *addr, size_t size)return p; } -void metadata_fetch_row (char *buffer,void *row) +void kasan_metadata_fetch_row (char *buffer,void *row) {memcpy (buffer,kasan_mem_to_shadow (row),META_BYTES_PER_ROW ); } -void print_tags (u8 addr_tag,const void *addr) +void kasan_print_tags (u8 addr_tag,const void *addr) {u8 *shadow = (u8 *)kasan_mem_to_shadow (addr);
mm/kasan/shadow.c +13 -13
@@ -27,20 +27,20 @@bool __kasan_check_read (const volatile void *p,unsigned int size) { -return check_memory_region ((unsigned long )p, size, false, _RET_IP_); +return kasan_check_range ((unsigned long )p, size, false, _RET_IP_); }EXPORT_SYMBOL (__kasan_check_read);bool __kasan_check_write (const volatile void *p,unsigned int size) { -return check_memory_region ((unsigned long )p, size, true, _RET_IP_); +return kasan_check_range ((unsigned long )p, size, true, _RET_IP_); }EXPORT_SYMBOL (__kasan_check_write);#undef memsetvoid *memset (void *addr,int c,size_t len) { -if (!check_memory_region ((unsigned long )addr, len, true, _RET_IP_)) +if (!kasan_check_range ((unsigned long )addr, len, true, _RET_IP_))return NULL ;return __memset (addr, c, len);
@@ -50,8 +50,8 @@ void *memset(void *addr, int c, size_t len)#undef memmovevoid *memmove (void *dest,const void *src,size_t len) { -if (!check_memory_region ((unsigned long )src, len, false, _RET_IP_) || - !check_memory_region ((unsigned long )dest, len, true, _RET_IP_)) +if (!kasan_check_range ((unsigned long )src, len, false, _RET_IP_) || + !kasan_check_range ((unsigned long )dest, len, true, _RET_IP_))return NULL ;return __memmove (dest, src, len);
@@ -61,8 +61,8 @@ void *memmove(void *dest, const void *src, size_t len)#undef memcpyvoid *memcpy (void *dest,const void *src,size_t len) { -if (!check_memory_region ((unsigned long )src, len, false, _RET_IP_) || - !check_memory_region ((unsigned long )dest, len, true, _RET_IP_)) +if (!kasan_check_range ((unsigned long )src, len, false, _RET_IP_) || + !kasan_check_range ((unsigned long )dest, len, true, _RET_IP_))return NULL ;return __memcpy (dest, src, len);
@@ -72,7 +72,7 @@ void *memcpy(void *dest, const void *src, size_t len)* Poisons the shadow memory for 'size' bytes starting from 'addr'. * Memory addresses should be aligned to KASAN_GRANULE_SIZE. */ -void poison_range (const void *address,size_t size,u8 value) +void kasan_poison (const void *address,size_t size,u8 value) {void *shadow_start, *shadow_end;
@@ -90,7 +90,7 @@ void poison_range(const void *address, size_t size, u8 value)__memset (shadow_start, value, shadow_end - shadow_start); } -void unpoison_range (const void *address,size_t size) +void kasan_unpoison (const void *address,size_t size) {u8 tag =get_tag (address);
@@ -101,7 +101,7 @@ void unpoison_range(const void *address, size_t size)*/ address =kasan_reset_tag (address); -poison_range (address, size, tag); +kasan_poison (address, size, tag);if (size &KASAN_GRANULE_MASK ) {u8 *shadow = (u8 *)kasan_mem_to_shadow (address + size);
@@ -286,7 +286,7 @@ int kasan_populate_vmalloc(unsigned long addr, unsigned long size)* // vmalloc() allocates memory * // let a = area->addr * // we reach kasan_populate_vmalloc -* // and call unpoison_range: +* // and call kasan_unpoison: * STORE shadow(a), unpoison_val * ... * STORE shadow(a+99), unpoison_val x = LOAD p
@@ -321,7 +321,7 @@ void kasan_poison_vmalloc(const void *start, unsigned long size)return ; size =round_up (size,KASAN_GRANULE_SIZE ); -poison_range (start, size,KASAN_VMALLOC_INVALID ); +kasan_poison (start, size,KASAN_VMALLOC_INVALID ); }void kasan_unpoison_vmalloc (const void *start,unsigned long size)
@@ -329,7 +329,7 @@ void kasan_unpoison_vmalloc(const void *start, unsigned long size)if (!is_vmalloc_or_module_addr (start))return ; -unpoison_range (start, size); +kasan_unpoison (start, size); } static int kasan_depopulate_vmalloc_pte(pte_t *ptep, unsigned long addr,
mm/kasan/sw_tags.c +8 -8
@@ -57,7 +57,7 @@ void __init kasan_init_sw_tags(void)* sequence has in fact positive effect, since interrupts that randomly skew * PRNG at unpredictable points do only good. */ -u8 random_tag (void ) +u8 kasan_random_tag (void ) {u32 state =this_cpu_read (prng_state);
@@ -67,7 +67,7 @@ u8 random_tag(void)return (u8)(state % (KASAN_TAG_MAX +1 )); } -bool check_memory_region (unsigned long addr,size_t size,bool write, +bool kasan_check_range (unsigned long addr,size_t size,bool write,unsigned long ret_ip) {u8 tag;
@@ -118,7 +118,7 @@ bool check_memory_region(unsigned long addr, size_t size, bool write,return true; } -bool check_invalid_free (void *addr) +bool kasan_check_invalid_free (void *addr) {u8 tag =get_tag (addr);u8 shadow_byte =READ_ONCE (*(u8 *)kasan_mem_to_shadow (kasan_reset_tag (addr)));
@@ -130,12 +130,12 @@ bool check_invalid_free(void *addr)#define DEFINE_HWASAN_LOAD_STORE (size) \ void __hwasan_load##size##_noabort(unsigned long addr) \ { \ - check_memory_region(addr, size, false, _RET_IP_); \ + kasan_check_range(addr, size, false, _RET_IP_); \ } \ EXPORT_SYMBOL(__hwasan_load##size##_noabort); \ void __hwasan_store##size##_noabort(unsigned long addr) \ { \ - check_memory_region(addr, size, true, _RET_IP_); \ + kasan_check_range(addr, size, true, _RET_IP_); \ } \ EXPORT_SYMBOL(__hwasan_store##size##_noabort)
@@ -147,19 +147,19 @@ DEFINE_HWASAN_LOAD_STORE(16);void __hwasan_loadN_noabort (unsigned long addr,unsigned long size) { -check_memory_region (addr, size, false, _RET_IP_); +kasan_check_range (addr, size, false, _RET_IP_); }EXPORT_SYMBOL (__hwasan_loadN_noabort);void __hwasan_storeN_noabort (unsigned long addr,unsigned long size) { -check_memory_region (addr, size, true, _RET_IP_); +kasan_check_range (addr, size, true, _RET_IP_); }EXPORT_SYMBOL (__hwasan_storeN_noabort);void __hwasan_tag_memory (unsigned long addr,u8 tag,unsigned long size) { -poison_range ((void *)addr, size, tag); +kasan_poison ((void *)addr, size, tag); }EXPORT_SYMBOL (__hwasan_tag_memory);
tools/objtool/check.c +1 -1
@@ -576,7 +576,7 @@ static void add_ignores(struct objtool_file *file)static const char *uaccess_safe_builtin[] = {/* KASAN */ "kasan_report" , -"check_memory_region" , +"kasan_check_range" ,/* KASAN out-of-line */ "__asan_loadN_noabort" ,"__asan_load1_noabort" ,
Signature · 1 line
2.30.0.284.gd98b1dd5eaa7-goog
PATCH v3 2/15 kasan: clarify HW_TAGS impact on TBI
In reply to Andrey KonovalovMention in the documentation that enabling CONFIG_KASAN_HW_TAGS always results in in-kernel TBI (Top Byte Ignore) being enabled.
Also do a few minor documentation cleanups.
--- Documentation/dev-tools/kasan.rst | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-)
Documentation/dev-tools/kasan.rst +11 -5
@@ -147,15 +147,14 @@ negative values to distinguish between different kinds of inaccessible memory like redzones or freed memory (see mm/kasan/kasan.h). In the report above the arrows point to the shadow byte 03, which means that -the accessed address is partially accessible. - -For tag-based KASAN this last report section shows the memory tags around the -accessed address (see `Implementation details`_ section). +the accessed address is partially accessible. For tag-based KASAN modes this +last report section shows the memory tags around the accessed address +(see the `Implementation details`_ section). Boot parameters ~~~~~~~~~~~~~~~ -Hardware tag-based KASAN mode (see the section about different mode below) is +Hardware tag-based KASAN mode (see the section about various modes below) is intended for use in production as a security mitigation. Therefore it supports boot parameters that allow to disable KASAN competely or otherwise control particular KASAN features.
@@ -305,6 +304,13 @@ reserved to tag freed memory regions. Hardware tag-based KASAN currently only supports tagging of kmem_cache_alloc/kmalloc and page_alloc memory. +If the hardware doesn't support MTE (pre ARMv8.5), hardware tag-based KASAN +won't be enabled. In this case all boot parameters are ignored. + +Note, that enabling CONFIG_KASAN_HW_TAGS always results in in-kernel TBI being +enabled. Even when kasan.mode=off is provided, or when the hardware doesn't +support MTE (but supports TBI). + What memory accesses are sanitised by KASAN? --------------------------------------------
Signature · 1 line
2.30.0.284.gd98b1dd5eaa7-goog
PATCH v3 3/15 kasan: clean up comments in tests
In reply to Andrey KonovalovClarify and update comments in KASAN tests.
--- lib/test_kasan.c | 59 +++++++++++++++++++++++++---------------- lib/test_kasan_module.c | 5 ++-- 2 files changed, 39 insertions(+), 25 deletions(-)
lib/test_kasan.c +36 -23
@@ -28,10 +28,9 @@#define OOB_TAG_OFF (IS_ENABLED(CONFIG_KASAN_GENERIC) ? 0 : KASAN_GRANULE_SIZE)/* -* We assign some test results to these globals to make sure the tests -* are not eliminated as dead code. +* Some tests use these global variables to store return values from function +* calls that could otherwise be eliminated by the compiler as dead code. */ -void *kasan_ptr_result;int kasan_int_result;
@@ -39,14 +38,13 @@ static struct kunit_resource resource;static struct kunit_kasan_expectation fail_data;static bool multishot; +/* +* Temporarily enable multi-shot mode. Otherwise, KASAN would only report the +* first detected bug and panic the kernel if panic_on_warn is enabled. +*/ static int kasan_test_init (struct kunit *test) { -/* -* Temporarily enable multi-shot mode and set panic_on_warn=0. -* Otherwise, we'd only get a report for the first case. -*/ multishot =kasan_save_enable_multi_shot (); -return 0 ; }
@@ -56,12 +54,12 @@ static void kasan_test_exit(struct kunit *test) }/** -* KUNIT_EXPECT_KASAN_FAIL() - Causes a test failure when the expression does -* not cause a KASAN error. This uses a KUnit resource named "kasan_data." Do -* Do not use this name for a KUnit resource outside here. -* +* KUNIT_EXPECT_KASAN_FAIL() - check that the executed expression produces a +* KASAN report; causes a test failure otherwise. This relies on a KUnit +* resource named "kasan_data". Do not use this name for KUnit resources +* outside of KASAN tests. */ -#define KUNIT_EXPECT_KASAN_FAIL (test, condition) do { \ +#define KUNIT_EXPECT_KASAN_FAIL (test, expression) do { \ fail_data.report_expected = true; \ fail_data.report_found = false; \ kunit_add_named_resource(test, \
@@ -69,7 +67,7 @@ static void kasan_test_exit(struct kunit *test)NULL , \ &resource, \ "kasan_data", &fail_data); \ - condition; \ + expression; \KUNIT_EXPECT_EQ (test, \ fail_data.report_expected, \ fail_data.report_found); \
@@ -121,7 +119,8 @@ static void kmalloc_pagealloc_oob_right(struct kunit *test)return ; } -/* Allocate a chunk that does not fit into a SLUB cache to trigger +/* +* Allocate a chunk that does not fit into a SLUB cache to trigger * the page allocator fallback. */ ptr =kmalloc (size,GFP_KERNEL );
@@ -168,7 +167,9 @@ static void kmalloc_large_oob_right(struct kunit *test) {char *ptr;size_t size =KMALLOC_MAX_CACHE_SIZE -256 ; -/* Allocate a chunk that is large enough, but still fits into a slab + +/* +* Allocate a chunk that is large enough, but still fits into a slab * and does not trigger the page allocator fallback in SLUB. */ ptr =kmalloc (size,GFP_KERNEL );
@@ -469,10 +470,13 @@ static void ksize_unpoisons_memory(struct kunit *test) ptr =kmalloc (size,GFP_KERNEL );KUNIT_ASSERT_NOT_ERR_OR_NULL (test, ptr); real_size =ksize (ptr); -/* This access doesn't trigger an error. */ + +/* This access shouldn't trigger a KASAN report. */ ptr[size] ='x' ; -/* This one does. */ + +/* This one must. */ KUNIT_EXPECT_KASAN_FAIL (test, ptr[real_size] ='y' ); +kfree (ptr); }
@@ -568,7 +572,7 @@ static void kmem_cache_invalid_free(struct kunit *test)return ; } -/* Trigger invalid free, the object doesn't get freed */ +/* Trigger invalid free, the object doesn't get freed. */ KUNIT_EXPECT_KASAN_FAIL (test,kmem_cache_free (cache, p +1 ));/*
@@ -585,7 +589,10 @@ static void kasan_memchr(struct kunit *test)char *ptr;size_t size =24 ; -/* See https://bugzilla.kernel.org/show_bug.cgi?id=206337 */ +/* +* str* functions are not instrumented with CONFIG_AMD_MEM_ENCRYPT. +* See https://bugzilla.kernel.org/show_bug.cgi?id=206337 for details. +*/ if (IS_ENABLED (CONFIG_AMD_MEM_ENCRYPT )) {kunit_info (test,"str* functions are not instrumented with CONFIG_AMD_MEM_ENCRYPT" );
@@ -610,7 +617,10 @@ static void kasan_memcmp(struct kunit *test)size_t size =24 ;int arr[9 ]; -/* See https://bugzilla.kernel.org/show_bug.cgi?id=206337 */ +/* +* str* functions are not instrumented with CONFIG_AMD_MEM_ENCRYPT. +* See https://bugzilla.kernel.org/show_bug.cgi?id=206337 for details. +*/ if (IS_ENABLED (CONFIG_AMD_MEM_ENCRYPT )) {kunit_info (test,"str* functions are not instrumented with CONFIG_AMD_MEM_ENCRYPT" );
@@ -634,7 +644,10 @@ static void kasan_strings(struct kunit *test)char *ptr;size_t size =24 ; -/* See https://bugzilla.kernel.org/show_bug.cgi?id=206337 */ +/* +* str* functions are not instrumented with CONFIG_AMD_MEM_ENCRYPT. +* See https://bugzilla.kernel.org/show_bug.cgi?id=206337 for details. +*/ if (IS_ENABLED (CONFIG_AMD_MEM_ENCRYPT )) {kunit_info (test,"str* functions are not instrumented with CONFIG_AMD_MEM_ENCRYPT" );
@@ -706,7 +719,7 @@ static void kasan_bitops_generic(struct kunit *test) }/* -* Allocate 1 more byte, which causes kzalloc to round up to 16-bytes; +* Allocate 1 more byte, which causes kzalloc to round up to 16 bytes; * this way we do not actually corrupt other memory. */ bits =kzalloc (sizeof (*bits) +1 ,GFP_KERNEL );
lib/test_kasan_module.c +3 -2
@@ -123,8 +123,9 @@ static noinline void __init kasan_workqueue_uaf(void)static int __init test_kasan_module_init (void ) {/* -* Temporarily enable multi-shot mode. Otherwise, we'd only get a -* report for the first case. +* Temporarily enable multi-shot mode. Otherwise, KASAN would only +* report the first detected bug and panic the kernel if panic_on_warn +* is enabled. */ bool multishot =kasan_save_enable_multi_shot ();
Signature · 1 line
2.30.0.284.gd98b1dd5eaa7-goog
Re: [PATCH v3 03/15] kasan: clean up comments in tests
Quoted 6 lines · Andrey Konovalov
Clarify and update comments in KASAN tests.
PATCH v3 4/15 kasan: add macros to simplify checking test constraints
In reply to Andrey KonovalovSome KASAN tests require specific kernel configs to be enabled. Instead of copy-pasting the checks for these configs add a few helper macros and use them.
--- lib/test_kasan.c | 101 +++++++++++++++-------------------------------- 1 file changed, 31 insertions(+), 70 deletions(-)
lib/test_kasan.c +31 -70
@@ -73,6 +73,20 @@ static void kasan_test_exit(struct kunit *test) fail_data.report_found); \ } while (0) +#define KASAN_TEST_NEEDS_CONFIG_ON (test, config) do { \ + if (!IS_ENABLED(config)) { \ + kunit_info((test), "skipping, " #config " required"); \ + return; \ + } \ +} while (0) + +#define KASAN_TEST_NEEDS_CONFIG_OFF (test, config) do { \ + if (IS_ENABLED(config)) { \ + kunit_info((test), "skipping, " #config " enabled"); \ + return; \ + } \ +} while (0) +static void kmalloc_oob_right (struct kunit *test) {char *ptr;
@@ -114,10 +128,7 @@ static void kmalloc_pagealloc_oob_right(struct kunit *test)char *ptr;size_t size =KMALLOC_MAX_CACHE_SIZE +10 ; -if (!IS_ENABLED (CONFIG_SLUB )) { -kunit_info (test,"CONFIG_SLUB is not enabled." ); -return ; - } +KASAN_TEST_NEEDS_CONFIG_ON (test,CONFIG_SLUB );/* * Allocate a chunk that does not fit into a SLUB cache to trigger
@@ -135,10 +146,7 @@ static void kmalloc_pagealloc_uaf(struct kunit *test)char *ptr;size_t size =KMALLOC_MAX_CACHE_SIZE +10 ; -if (!IS_ENABLED (CONFIG_SLUB )) { -kunit_info (test,"CONFIG_SLUB is not enabled." ); -return ; - } +KASAN_TEST_NEEDS_CONFIG_ON (test,CONFIG_SLUB ); ptr =kmalloc (size,GFP_KERNEL );KUNIT_ASSERT_NOT_ERR_OR_NULL (test, ptr);
@@ -152,10 +160,7 @@ static void kmalloc_pagealloc_invalid_free(struct kunit *test)char *ptr;size_t size =KMALLOC_MAX_CACHE_SIZE +10 ; -if (!IS_ENABLED (CONFIG_SLUB )) { -kunit_info (test,"CONFIG_SLUB is not enabled." ); -return ; - } +KASAN_TEST_NEEDS_CONFIG_ON (test,CONFIG_SLUB ); ptr =kmalloc (size,GFP_KERNEL );KUNIT_ASSERT_NOT_ERR_OR_NULL (test, ptr);
@@ -218,10 +223,7 @@ static void kmalloc_oob_16(struct kunit *test) } *ptr1, *ptr2;/* This test is specifically crafted for the generic mode. */ -if (!IS_ENABLED (CONFIG_KASAN_GENERIC )) { -kunit_info (test,"CONFIG_KASAN_GENERIC required\n" ); -return ; - } +KASAN_TEST_NEEDS_CONFIG_ON (test,CONFIG_KASAN_GENERIC ); ptr1 =kmalloc (sizeof (*ptr1) -3 ,GFP_KERNEL );KUNIT_ASSERT_NOT_ERR_OR_NULL (test, ptr1);
@@ -454,10 +456,7 @@ static void kasan_global_oob(struct kunit *test)char *p = &global_array[ARRAY_SIZE (global_array) + i];/* Only generic mode instruments globals. */ -if (!IS_ENABLED (CONFIG_KASAN_GENERIC )) { -kunit_info (test,"CONFIG_KASAN_GENERIC required" ); -return ; - } +KASAN_TEST_NEEDS_CONFIG_ON (test,CONFIG_KASAN_GENERIC );KUNIT_EXPECT_KASAN_FAIL (test, *(volatile char *)p); }
@@ -486,10 +485,7 @@ static void kasan_stack_oob(struct kunit *test)volatile int i =OOB_TAG_OFF ;char *p = &stack_array[ARRAY_SIZE (stack_array) + i]; -if (!IS_ENABLED (CONFIG_KASAN_STACK )) { -kunit_info (test,"CONFIG_KASAN_STACK is not enabled" ); -return ; - } +KASAN_TEST_NEEDS_CONFIG_ON (test,CONFIG_KASAN_STACK );KUNIT_EXPECT_KASAN_FAIL (test, *(volatile char *)p); }
@@ -501,15 +497,8 @@ static void kasan_alloca_oob_left(struct kunit *test)char *p = alloca_array -1 ;/* Only generic mode instruments dynamic allocas. */ -if (!IS_ENABLED (CONFIG_KASAN_GENERIC )) { -kunit_info (test,"CONFIG_KASAN_GENERIC required" ); -return ; - } - -if (!IS_ENABLED (CONFIG_KASAN_STACK )) { -kunit_info (test,"CONFIG_KASAN_STACK is not enabled" ); -return ; - } +KASAN_TEST_NEEDS_CONFIG_ON (test,CONFIG_KASAN_GENERIC ); +KASAN_TEST_NEEDS_CONFIG_ON (test,CONFIG_KASAN_STACK );KUNIT_EXPECT_KASAN_FAIL (test, *(volatile char *)p); }
@@ -521,15 +510,8 @@ static void kasan_alloca_oob_right(struct kunit *test)char *p = alloca_array + i;/* Only generic mode instruments dynamic allocas. */ -if (!IS_ENABLED (CONFIG_KASAN_GENERIC )) { -kunit_info (test,"CONFIG_KASAN_GENERIC required" ); -return ; - } - -if (!IS_ENABLED (CONFIG_KASAN_STACK )) { -kunit_info (test,"CONFIG_KASAN_STACK is not enabled" ); -return ; - } +KASAN_TEST_NEEDS_CONFIG_ON (test,CONFIG_KASAN_GENERIC ); +KASAN_TEST_NEEDS_CONFIG_ON (test,CONFIG_KASAN_STACK );KUNIT_EXPECT_KASAN_FAIL (test, *(volatile char *)p); }
@@ -593,11 +575,7 @@ static void kasan_memchr(struct kunit *test)* str* functions are not instrumented with CONFIG_AMD_MEM_ENCRYPT. * See https://bugzilla.kernel.org/show_bug.cgi?id=206337 for details. */ -if (IS_ENABLED (CONFIG_AMD_MEM_ENCRYPT )) { -kunit_info (test, -"str* functions are not instrumented with CONFIG_AMD_MEM_ENCRYPT" ); -return ; - } +KASAN_TEST_NEEDS_CONFIG_OFF (test,CONFIG_AMD_MEM_ENCRYPT );if (OOB_TAG_OFF ) size =round_up (size,OOB_TAG_OFF );
@@ -621,11 +599,7 @@ static void kasan_memcmp(struct kunit *test)* str* functions are not instrumented with CONFIG_AMD_MEM_ENCRYPT. * See https://bugzilla.kernel.org/show_bug.cgi?id=206337 for details. */ -if (IS_ENABLED (CONFIG_AMD_MEM_ENCRYPT )) { -kunit_info (test, -"str* functions are not instrumented with CONFIG_AMD_MEM_ENCRYPT" ); -return ; - } +KASAN_TEST_NEEDS_CONFIG_OFF (test,CONFIG_AMD_MEM_ENCRYPT );if (OOB_TAG_OFF ) size =round_up (size,OOB_TAG_OFF );
@@ -648,11 +622,7 @@ static void kasan_strings(struct kunit *test)* str* functions are not instrumented with CONFIG_AMD_MEM_ENCRYPT. * See https://bugzilla.kernel.org/show_bug.cgi?id=206337 for details. */ -if (IS_ENABLED (CONFIG_AMD_MEM_ENCRYPT )) { -kunit_info (test, -"str* functions are not instrumented with CONFIG_AMD_MEM_ENCRYPT" ); -return ; - } +KASAN_TEST_NEEDS_CONFIG_OFF (test,CONFIG_AMD_MEM_ENCRYPT ); ptr =kmalloc (size,GFP_KERNEL | __GFP_ZERO);KUNIT_ASSERT_NOT_ERR_OR_NULL (test, ptr);
@@ -713,10 +683,7 @@ static void kasan_bitops_generic(struct kunit *test)long *bits;/* This test is specifically crafted for the generic mode. */ -if (!IS_ENABLED (CONFIG_KASAN_GENERIC )) { -kunit_info (test,"CONFIG_KASAN_GENERIC required\n" ); -return ; - } +KASAN_TEST_NEEDS_CONFIG_ON (test,CONFIG_KASAN_GENERIC );/* * Allocate 1 more byte, which causes kzalloc to round up to 16 bytes;
@@ -744,11 +711,8 @@ static void kasan_bitops_tags(struct kunit *test) {long *bits; -/* This test is specifically crafted for the tag-based mode. */ -if (IS_ENABLED (CONFIG_KASAN_GENERIC )) { -kunit_info (test,"CONFIG_KASAN_SW_TAGS required\n" ); -return ; - } +/* This test is specifically crafted for tag-based modes. */ +KASAN_TEST_NEEDS_CONFIG_OFF (test,CONFIG_KASAN_GENERIC );/* Allocation size will be rounded to up granule size, which is 16. */ bits =kzalloc (sizeof (*bits),GFP_KERNEL );
@@ -777,10 +741,7 @@ static void vmalloc_oob(struct kunit *test) {void *area; -if (!IS_ENABLED (CONFIG_KASAN_VMALLOC )) { -kunit_info (test,"CONFIG_KASAN_VMALLOC is not enabled." ); -return ; - } +KASAN_TEST_NEEDS_CONFIG_ON (test,CONFIG_KASAN_VMALLOC );/* * We have to be careful not to hit the guard page.
Signature · 1 line
2.30.0.284.gd98b1dd5eaa7-goog
PATCH v3 5/15 kasan: add match-all tag tests
In reply to Andrey KonovalovAdd 3 new tests for tag-based KASAN modes:
1. Check that match-all pointer tag is not assigned randomly.
2. Check that 0xff works as a match-all pointer tag.
3. Check that there are no match-all memory tags.
Note, that test #3 causes a significant number (255) of KASAN reports to be printed during execution for the SW_TAGS mode.
--- lib/test_kasan.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++++ mm/kasan/kasan.h | 6 ++++ 2 files changed, 98 insertions(+)
lib/test_kasan.c +92 -0
@@ -13,6 +13,7 @@#include <linux/mman.h> #include <linux/module.h> #include <linux/printk.h> +#include <linux/random.h> #include <linux/slab.h> #include <linux/string.h> #include <linux/uaccess.h>
@@ -754,6 +755,94 @@ static void vmalloc_oob(struct kunit *test)vfree (area); } +/* +* Check that the assigned pointer tag falls within the [KASAN_TAG_MIN, +* KASAN_TAG_KERNEL) range (note: excluding the match-all tag) for tag-based +* modes. +*/ +static void match_all_not_assigned (struct kunit *test) +{ +char *ptr; +struct page *pages; +int i, size, order; + +KASAN_TEST_NEEDS_CONFIG_OFF (test,CONFIG_KASAN_GENERIC ); + +for (i =0 ; i <256 ; i++) { + size = (get_random_int () %1024 ) +1 ; + ptr =kmalloc (size,GFP_KERNEL ); +KUNIT_ASSERT_NOT_ERR_OR_NULL (test, ptr); +KUNIT_EXPECT_GE (test, (u8 )get_tag (ptr), (u8 )KASAN_TAG_MIN ); +KUNIT_EXPECT_LT (test, (u8 )get_tag (ptr), (u8 )KASAN_TAG_KERNEL ); +kfree (ptr); + } + +for (i =0 ; i <256 ; i++) { + order = (get_random_int () %4 ) +1 ; + pages =alloc_pages (GFP_KERNEL , order); + ptr =page_address (pages); +KUNIT_ASSERT_NOT_ERR_OR_NULL (test, ptr); +KUNIT_EXPECT_GE (test, (u8 )get_tag (ptr), (u8 )KASAN_TAG_MIN ); +KUNIT_EXPECT_LT (test, (u8 )get_tag (ptr), (u8 )KASAN_TAG_KERNEL ); +free_pages ((unsigned long )ptr, order); + } +} + +/* Check that 0xff works as a match-all pointer tag for tag-based modes. */ +static void match_all_ptr_tag (struct kunit *test) +{ +char *ptr; +u8 tag; + +KASAN_TEST_NEEDS_CONFIG_OFF (test,CONFIG_KASAN_GENERIC ); + + ptr =kmalloc (128 ,GFP_KERNEL ); +KUNIT_ASSERT_NOT_ERR_OR_NULL (test, ptr); + +/* Backup the assigned tag. */ + tag =get_tag (ptr); +KUNIT_EXPECT_NE (test, tag, (u8 )KASAN_TAG_KERNEL ); + +/* Reset the tag to 0xff.*/ + ptr =set_tag (ptr,KASAN_TAG_KERNEL ); + +/* This access shouldn't trigger a KASAN report. */ + *ptr =0 ; + +/* Recover the pointer tag and free. */ + ptr =set_tag (ptr, tag); +kfree (ptr); +} + +/* Check that there are no match-all memory tags for tag-based modes. */ +static void match_all_mem_tag (struct kunit *test) +{ +char *ptr; +int tag; + +KASAN_TEST_NEEDS_CONFIG_OFF (test,CONFIG_KASAN_GENERIC ); + + ptr =kmalloc (128 ,GFP_KERNEL ); +KUNIT_ASSERT_NOT_ERR_OR_NULL (test, ptr); +KUNIT_EXPECT_NE (test, (u8 )get_tag (ptr), (u8 )KASAN_TAG_KERNEL ); + +/* For each possible tag value not matching the pointer tag. */ +for (tag =KASAN_TAG_MIN ; tag <=KASAN_TAG_KERNEL ; tag++) { +if (tag ==get_tag (ptr)) +continue ; + +/* Mark the first memory granule with the chosen memory tag. */ +kasan_poison (ptr,KASAN_GRANULE_SIZE , (u8 )tag); + +/* This access must cause a KASAN report. */ +KUNIT_EXPECT_KASAN_FAIL (test, *ptr =0 ); + } + +/* Recover the memory tag and free. */ +kasan_poison (ptr,KASAN_GRANULE_SIZE ,get_tag (ptr)); +kfree (ptr); +} +static struct kunit_case kasan_kunit_test_cases[] = {KUNIT_CASE (kmalloc_oob_right),KUNIT_CASE (kmalloc_oob_left),
@@ -793,6 +882,9 @@ static struct kunit_case kasan_kunit_test_cases[] = {KUNIT_CASE (kasan_bitops_tags),KUNIT_CASE (kmalloc_double_kzfree),KUNIT_CASE (vmalloc_oob), +KUNIT_CASE (match_all_not_assigned), +KUNIT_CASE (match_all_ptr_tag), + KUNIT_CASE(match_all_mem_tag), {} };
mm/kasan/kasan.h +6 -0
@@ -36,6 +36,12 @@ extern bool kasan_flag_panic __ro_after_init;#define KASAN_TAG_INVALID 0xFE/* inaccessible memory tag */ #define KASAN_TAG_MAX 0xFD/* maximum value for random tags */ +#ifdef CONFIG_KASAN_HW_TAGS +#define KASAN_TAG_MIN 0xF0/* mimimum value for random tags */ +#else +#define KASAN_TAG_MIN 0x00/* mimimum value for random tags */ +#endif +#ifdef CONFIG_KASAN_GENERIC #define KASAN_FREE_PAGE 0xFF/* page was freed */ #define KASAN_PAGE_REDZONE 0xFE/* redzone for kmalloc_large allocations */
Signature · 1 line
2.30.0.284.gd98b1dd5eaa7-goog
Re: [PATCH v3 05/15] kasan: add match-all tag tests
Quoted 7 lines · Andrey Konovalov
Add 3 new tests for tag-based KASAN modes:
1. Check that match-all pointer tag is not assigned randomly.
2. Check that0xffworks as a match-all pointer tag.
3. Check that there are no match-all memory tags.
Quoted 6 lines
Note, that test #3 causes a significant number (255) of KASAN reports to be printed during execution for the
SW_TAGSmode.
PATCH v3 6/15 kasan, arm64: allow using KUnit tests with HW_TAGS mode
In reply to Andrey KonovalovOn a high level, this patch allows running KUnit KASAN tests with the hardware tag-based KASAN mode.
Internally, this change reenables tag checking at the end of each KASAN test that triggers a tag fault and leads to tag checking being disabled.
With this patch KASAN tests are still failing for the hardware tag-based mode; fixes come in the next few patches.
--- arch/arm64/include/asm/memory.h | 1 + arch/arm64/include/asm/mte-kasan.h | 12 +++++++++ arch/arm64/kernel/mte.c | 12 +++++++++ arch/arm64/mm/fault.c | 16 +++++++----- lib/Kconfig.kasan | 4 +-- lib/test_kasan.c | 42 +++++++++++++++++++++--------- mm/kasan/kasan.h | 9 +++++++ 7 files changed, 75 insertions(+), 21 deletions(-)
arch/arm64/include/asm/memory.h +1 -0
@@ -232,6 +232,7 @@ static inline const void *__tag_set(const void *addr, u8 tag)#ifdef CONFIG_KASAN_HW_TAGS #define arch_enable_tagging () mte_enable_kernel() +#define arch_set_tagging_report_once (state) mte_set_report_once(state)#define arch_init_tags (max_tag) mte_init_tags(max_tag)#define arch_get_random_tag () mte_get_random_tag()#define arch_get_mem_tag (addr) mte_get_mem_tag(addr)
arch/arm64/include/asm/mte-kasan.h +12 -0
@@ -32,6 +32,9 @@ void *mte_set_mem_tag_range(void *addr, size_t size, u8 tag);void mte_enable_kernel (void );void mte_init_tags (u64 max_tag); +void mte_set_report_once (bool state); +bool mte_report_once (void ); +#else /* CONFIG_ARM64_MTE */ static inline u8 mte_get_ptr_tag (void *ptr)
@@ -60,6 +63,15 @@ static inline void mte_init_tags(u64 max_tag) { } +static inline void mte_set_report_once (bool state) +{ +} + +static inline bool mte_report_once (void ) +{ +return false; +} +#endif /* CONFIG_ARM64_MTE */ #endif /* __ASSEMBLY__ */
arch/arm64/kernel/mte.c +12 -0
@@ -25,6 +25,8 @@u64 gcr_kernel_excl__ro_after_init ; +static bool report_fault_once = true; +static void mte_sync_page_tags (struct page *page,pte_t *ptep,bool check_swap) {pte_t old_pte =READ_ONCE (*ptep);
@@ -158,6 +160,16 @@ void mte_enable_kernel(void)isb (); } +void mte_set_report_once (bool state) +{ +WRITE_ONCE (report_fault_once, state); +} + +bool mte_report_once (void ) +{ +return READ_ONCE (report_fault_once); +} +static void update_sctlr_el1_tcf0 (u64 tcf0) {/* ISB required for the kernel uaccess routines */
arch/arm64/mm/fault.c +10 -6
@@ -302,7 +302,14 @@ static void die_kernel_fault(const char *msg, unsigned long addr,static void report_tag_fault (unsigned long addr,unsigned int esr,struct pt_regs *regs) { -bool is_write = ((esr & ESR_ELx_WNR) >> ESR_ELx_WNR_SHIFT) !=0 ; +static bool reported; +bool is_write; + +if (READ_ONCE (reported)) +return ; + +if (mte_report_once ()) +WRITE_ONCE (reported, true);/* The format of KASAN tags is 0xF<x>. */ addr |= (0xF0UL <<MTE_TAG_SHIFT );
@@ -310,6 +317,7 @@ static void report_tag_fault(unsigned long addr, unsigned int esr,* SAS bits aren't set for all faults reported in EL1, so we can't * find out access size. */ + is_write = ((esr & ESR_ELx_WNR) >> ESR_ELx_WNR_SHIFT) !=0 ; kasan_report(addr, 0, is_write, regs->pc); }#else
@@ -321,12 +329,8 @@ static inline void report_tag_fault(unsigned long addr, unsigned int esr,static void do_tag_recovery (unsigned long addr,unsigned int esr,struct pt_regs *regs) { -static bool reported; -if (!READ_ONCE (reported)) { -report_tag_fault (addr, esr, regs); -WRITE_ONCE (reported, true); - } +report_tag_fault (addr, esr, regs);/* * Disable MTE Tag Checking on the local CPU for the current EL.
lib/Kconfig.kasan +2 -2
@@ -190,11 +190,11 @@ config KASAN_KUNIT_TEST kernel debugging features like KASAN. For more information on KUnit and unit tests in general, please refer - to the KUnit documentation in Documentation/dev-tools/kunit + to the KUnit documentation in Documentation/dev-tools/kunit. config TEST_KASAN_MODULE tristate "KUnit-incompatible tests of KASAN bug detection capabilities" - depends on m && KASAN + depends on m && KASAN && !KASAN_HW_TAGS help This is a part of the KASAN test suite that is incompatible with KUnit. Currently includes tests that do bad copy_from/to_user
lib/test_kasan.c +29 -13
64 lines not on this page · raw message
mm/kasan/kasan.h +9 -0
@@ -280,6 +280,9 @@ static inline const void *arch_kasan_set_tag(const void *addr, u8 tag)#ifndef arch_init_tags#define arch_init_tags (max_tag)#endif +#ifndef arch_set_tagging_report_once +#define arch_set_tagging_report_once (state) +#endif #ifndef arch_get_random_tag#define arch_get_random_tag () (0xFF)#endif
@@ -292,10 +295,16 @@ static inline const void *arch_kasan_set_tag(const void *addr, u8 tag)#define hw_enable_tagging () arch_enable_tagging()#define hw_init_tags (max_tag) arch_init_tags(max_tag) +#define hw_set_tagging_report_once (state) arch_set_tagging_report_once(state)#define hw_get_random_tag () arch_get_random_tag()#define hw_get_mem_tag (addr) arch_get_mem_tag(addr)#define hw_set_mem_tag_range (addr, size, tag) arch_set_mem_tag_range((addr), (size), (tag)) +#else /* CONFIG_KASAN_HW_TAGS */ + +#define hw_enable_tagging () +#define hw_set_tagging_report_once (state) +#endif /* CONFIG_KASAN_HW_TAGS */ #ifdef CONFIG_KASAN_SW_TAGS
Signature · 1 line
2.30.0.284.gd98b1dd5eaa7-goog
PATCH v3 7/15 kasan: rename CONFIG_TEST_KASAN_MODULE
In reply to Andrey KonovalovRename CONFIG_TEST_KASAN_MODULE to CONFIG_KASAN_MODULE_TEST.
This naming is more consistent with the existing CONFIG_KASAN_KUNIT_TEST.
--- Documentation/dev-tools/kasan.rst | 8 ++++---- lib/Kconfig.kasan | 2 +- lib/Makefile | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-)
Documentation/dev-tools/kasan.rst +4 -4
@@ -374,17 +374,17 @@ unmapped. This will require changes in arch-specific code. This allows ``VMAP_STACK`` support on x86, and can simplify support of architectures that do not have a fixed module region. -CONFIG_KASAN_KUNIT_TEST & CONFIG_TEST_KASAN_MODULE --------------------------------------------------- +CONFIG_KASAN_KUNIT_TEST and CONFIG_KASAN_MODULE_TEST +---------------------------------------------------- -KASAN tests consist on two parts: +KASAN tests consist of two parts: 1. Tests that are integrated with the KUnit Test Framework. Enabled with ``CONFIG_KASAN_KUNIT_TEST``. These tests can be run and partially verified automatically in a few different ways, see the instructions below. 2. Tests that are currently incompatible with KUnit. Enabled with -``CONFIG_TEST_KASAN_MODULE`` and can only be run as a module. These tests can +``CONFIG_KASAN_MODULE_TEST`` and can only be run as a module. These tests can only be verified manually, by loading the kernel module and inspecting the kernel log for KASAN reports.
lib/Kconfig.kasan +1 -1
@@ -192,7 +192,7 @@ config KASAN_KUNIT_TEST For more information on KUnit and unit tests in general, please refer to the KUnit documentation in Documentation/dev-tools/kunit. -config TEST_KASAN_MODULE +config KASAN_MODULE_TEST tristate "KUnit-incompatible tests of KASAN bug detection capabilities" depends on m && KASAN && !KASAN_HW_TAGS help
lib/Makefile +1 -1
@@ -68,7 +68,7 @@ obj-$(CONFIG_TEST_IDA) += test_ida.o obj-$(CONFIG_KASAN_KUNIT_TEST) += test_kasan.o CFLAGS_test_kasan.o += -fno-builtin CFLAGS_test_kasan.o += $(call cc-disable-warning, vla) -obj-$(CONFIG_TEST_KASAN_MODULE) += test_kasan_module.o +obj-$(CONFIG_KASAN_MODULE_TEST) += test_kasan_module.o CFLAGS_test_kasan_module.o += -fno-builtin obj-$(CONFIG_TEST_UBSAN) += test_ubsan.o CFLAGS_test_ubsan.o += $(call cc-disable-warning, vla)
Signature · 1 line
2.30.0.284.gd98b1dd5eaa7-goog
PATCH v3 8/15 kasan: add compiler barriers to KUNIT_EXPECT_KASAN_FAIL
In reply to Andrey KonovalovIt might not be obvious to the compiler that the expression must be executed between writing and reading to fail_data. In this case, the compiler might reorder or optimize away some of the accesses, and the tests will fail.
Add compiler barriers around the expression in KUNIT_EXPECT_KASAN_FAIL and use READ/WRITE_ONCE() for accessing fail_data fields.
--- lib/test_kasan.c | 17 ++++++++++++----- mm/kasan/report.c | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-)
lib/test_kasan.c +12 -5
@@ -68,23 +68,30 @@ static void kasan_test_exit(struct kunit *test)* normally auto-disabled. When this happens, this test handler reenables * tag checking. As tag checking can be only disabled or enabled per CPU, this * handler disables migration (preemption). +* +* Since the compiler doesn't see that the expression can change the fail_data +* fields, it can reorder or optimize away the accesses to those fields. +* Use READ/WRITE_ONCE() for the accesses and compiler barriers around the +* expression to prevent that. */ #define KUNIT_EXPECT_KASAN_FAIL (test, expression) do { \ if (IS_ENABLED(CONFIG_KASAN_HW_TAGS)) \ migrate_disable(); \ - fail_data.report_expected = true; \ - fail_data.report_found = false; \ + WRITE_ONCE(fail_data.report_expected, true); \ + WRITE_ONCE(fail_data.report_found, false); \ kunit_add_named_resource(test, \ NULL, \ NULL, \ &resource, \ "kasan_data", &fail_data); \ + barrier(); \ expression; \ + barrier(); \ KUNIT_EXPECT_EQ(test, \ - fail_data.report_expected, \ - fail_data.report_found); \ + READ_ONCE(fail_data.report_expected), \ + READ_ONCE(fail_data.report_found)); \ if (IS_ENABLED(CONFIG_KASAN_HW_TAGS)) { \ - if (fail_data.report_found) \ + if (READ_ONCE(fail_data.report_found)) \ hw_enable_tagging(); \ migrate_enable(); \ } \
mm/kasan/report.c +1 -1
@@ -331,7 +331,7 @@ static void kasan_update_kunit_status(struct kunit *cur_test) } kasan_data = (struct kunit_kasan_expectation *)resource->data; - kasan_data->report_found = true; +WRITE_ONCE (kasan_data->report_found, true);kunit_put_resource (resource); }#endif /* IS_ENABLED(CONFIG_KUNIT) */
Signature · 1 line
2.30.0.284.gd98b1dd5eaa7-goog
Re: [PATCH v3 08/15] kasan: add compiler barriers to KUNIT_EXPECT_KASAN_FAIL
Quoted 6 lines · Andrey Konovalov
It might not be obvious to the compiler that the expression must be executed between writing and reading to
fail_data. In this case, the compiler might reorder or optimize away some of the accesses, and the tests will fail.
Quoted 6 lines
Add compiler barriers around the expression in
KUNIT_EXPECT_KASAN_FAILand use READ/WRITE_ONCE() for accessingfail_datafields.
PATCH v3 9/15 kasan: adapt kmalloc_uaf2 test to HW_TAGS mode
In reply to Andrey KonovalovIn the kmalloc_uaf2() test, the pointers to the two allocated memory blocks might happen to be the same, and the test will fail. With the software tag-based mode, the probability of the that is 1/254, so it's hard to observe the failure. For the hardware tag-based mode though, the probablity is 1/14, which is quite noticable.
Allow up to 16 attempts at generating different tags for the tag-based modes.
--- lib/test_kasan.c | 11 +++++++++++ 1 file changed, 11 insertions(+)
lib/test_kasan.c +11 -0
@@ -382,7 +382,9 @@ static void kmalloc_uaf2(struct kunit *test) {char *ptr1, *ptr2;size_t size =43 ; +int counter =0 ; +again: ptr1 =kmalloc (size,GFP_KERNEL );KUNIT_ASSERT_NOT_ERR_OR_NULL (test, ptr1);
@@ -391,6 +393,15 @@ static void kmalloc_uaf2(struct kunit *test) ptr2 =kmalloc (size,GFP_KERNEL );KUNIT_ASSERT_NOT_ERR_OR_NULL (test, ptr2); +/* +* For tag-based KASAN ptr1 and ptr2 tags might happen to be the same. +* Allow up to 16 attempts at generating different tags. +*/ +if (!IS_ENABLED (CONFIG_KASAN_GENERIC ) && ptr1 == ptr2 && counter++ <16 ) { +kfree (ptr2); + goto again; + } +KUNIT_EXPECT_KASAN_FAIL (test, ptr1[40 ] ='x' );KUNIT_EXPECT_PTR_NE (test, ptr1, ptr2);
Signature · 1 line
2.30.0.284.gd98b1dd5eaa7-goog
PATCH v3 10/15 kasan: fix memory corruption in kasan_bitops_tags test
In reply to Andrey KonovalovSince the hardware tag-based KASAN mode might not have a redzone that comes after an allocated object (when kasan.mode=prod is enabled), the kasan_bitops_tags() test ends up corrupting the next object in memory.
Change the test so it always accesses the redzone that lies within the allocated object's boundaries.
--- lib/test_kasan.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-)
lib/test_kasan.c +5 -5
@@ -749,13 +749,13 @@ static void kasan_bitops_tags(struct kunit *test)/* This test is specifically crafted for tag-based modes. */ KASAN_TEST_NEEDS_CONFIG_OFF (test,CONFIG_KASAN_GENERIC ); -/* Allocation size will be rounded to up granule size, which is 16. */ - bits =kzalloc (sizeof (*bits),GFP_KERNEL ); +/* kmalloc-64 cache will be used and the last 16 bytes will be the redzone. */ + bits =kzalloc (48 ,GFP_KERNEL );KUNIT_ASSERT_NOT_ERR_OR_NULL (test, bits); -/* Do the accesses past the 16 allocated bytes. */ -kasan_bitops_modify (test,BITS_PER_LONG , &bits[1 ]); -kasan_bitops_test_and_modify (test,BITS_PER_LONG +BITS_PER_BYTE , &bits[1 ]); +/* Do the accesses past the 48 allocated bytes, but within the redone. */ +kasan_bitops_modify (test,BITS_PER_LONG , (void *)bits +48 ); +kasan_bitops_test_and_modify (test,BITS_PER_LONG +BITS_PER_BYTE , (void *)bits +48 );kfree (bits); }
Signature · 1 line
2.30.0.284.gd98b1dd5eaa7-goog
PATCH v3 11/15 kasan: move _RET_IP_ to inline wrappers
In reply to Andrey KonovalovGeneric mm functions that call KASAN annotations that might report a bug pass _RET_IP_ to them as an argument. This allows KASAN to include the name of the function that called the mm function in its report's header.
Now that KASAN has inline wrappers for all of its annotations, move _RET_IP_ to those wrappers to simplify annotation call sites.
--- include/linux/kasan.h | 20 +++++++++----------- mm/mempool.c | 2 +- mm/slab.c | 2 +- mm/slub.c | 4 ++-- 4 files changed, 13 insertions(+), 15 deletions(-)
include/linux/kasan.h +9 -11
@@ -181,19 +181,18 @@ static __always_inline void * __must_check kasan_init_slab_obj( }bool __kasan_slab_free (struct kmem_cache *s,void *object,unsigned long ip); -static __always_inline bool kasan_slab_free(struct kmem_cache *s, void *object, -unsigned long ip) +static __always_inline bool kasan_slab_free(struct kmem_cache *s, void *object) {if (kasan_enabled ()) -return __kasan_slab_free (s, object, ip); +return __kasan_slab_free (s, object, _RET_IP_);return false; }void __kasan_slab_free_mempool (void *ptr,unsigned long ip); -static __always_inline void kasan_slab_free_mempool(void *ptr, unsigned long ip) +static __always_inline void kasan_slab_free_mempool(void *ptr) {if (kasan_enabled ()) -__kasan_slab_free_mempool (ptr, ip); +__kasan_slab_free_mempool (ptr, _RET_IP_); } void * __must_check __kasan_slab_alloc(struct kmem_cache *s,
@@ -237,10 +236,10 @@ static __always_inline void * __must_check kasan_krealloc(const void *object, }void __kasan_kfree_large (void *ptr,unsigned long ip); -static __always_inline void kasan_kfree_large(void *ptr, unsigned long ip) +static __always_inline void kasan_kfree_large(void *ptr) {if (kasan_enabled ()) -__kasan_kfree_large (ptr, ip); +__kasan_kfree_large (ptr, _RET_IP_); }bool kasan_save_enable_multi_shot (void );
@@ -273,12 +272,11 @@ static inline void *kasan_init_slab_obj(struct kmem_cache *cache, {return (void *)object; } -static inline bool kasan_slab_free (struct kmem_cache *s,void *object, -unsigned long ip) +static inline bool kasan_slab_free (struct kmem_cache *s,void *object) {return false; } -static inline void kasan_slab_free_mempool (void *ptr,unsigned long ip) {} +static inline void kasan_slab_free_mempool (void *ptr) {}static inline void *kasan_slab_alloc (struct kmem_cache *s,void *object,gfp_t flags) {
@@ -298,7 +296,7 @@ static inline void *kasan_krealloc(const void *object, size_t new_size, {return (void *)object; } -static inline void kasan_kfree_large (void *ptr,unsigned long ip) {} +static inline void kasan_kfree_large (void *ptr) {}#endif /* CONFIG_KASAN */
mm/mempool.c +1 -1
@@ -104,7 +104,7 @@ static inline void poison_element(mempool_t *pool, void *element) static __always_inline void kasan_poison_element(mempool_t *pool, void *element) {if (pool->alloc == mempool_alloc_slab || pool->alloc == mempool_kmalloc) -kasan_slab_free_mempool (element, _RET_IP_); +kasan_slab_free_mempool (element);else if (pool->alloc == mempool_alloc_pages)kasan_free_pages (element, (unsigned long )pool->pool_data); }
mm/slab.c +1 -1
@@ -3421,7 +3421,7 @@ static __always_inline void __cache_free(struct kmem_cache *cachep, void *objp,memset (objp,0 , cachep->object_size);/* Put the object into the quarantine, don't touch it for now. */ -if (kasan_slab_free (cachep, objp, _RET_IP_)) +if (kasan_slab_free (cachep, objp))return ;/* Use KCSAN to help debug racy use-after-free. */
mm/slub.c +2 -2
@@ -1514,7 +1514,7 @@ static inline void *kmalloc_large_node_hook(void *ptr, size_t size, gfp_t flags) static __always_inline void kfree_hook(void *x) {kmemleak_free (x); -kasan_kfree_large (x, _RET_IP_); +kasan_kfree_large (x); } static __always_inline bool slab_free_hook(struct kmem_cache *s, void *x)
@@ -1544,7 +1544,7 @@ static __always_inline bool slab_free_hook(struct kmem_cache *s, void *x) KCSAN_ACCESS_WRITE | KCSAN_ACCESS_ASSERT);/* KASAN might put x into memory quarantine, delaying its reuse */ -return kasan_slab_free (s, x, _RET_IP_); +return kasan_slab_free (s, x); } static inline bool slab_free_freelist_hook(struct kmem_cache *s,
Signature · 1 line
2.30.0.284.gd98b1dd5eaa7-goog
Re: [PATCH v3 11/15] kasan: move _RET_IP_ to inline wrappers
Quoted 9 lines · Andrey Konovalov
Generic mm functions that call KASAN annotations that might report a bug pass _RET_IP_ to them as an argument. This allows KASAN to include the name of the function that called the mm function in its report's header.
Now that KASAN has inline wrappers for all of its annotations, move _RET_IP_ to those wrappers to simplify annotation call sites.
Much nicer!
Quoted 123 lines
--- include/linux/kasan.h | 20 +++++++++----------- mm/mempool.c | 2 +- mm/slab.c | 2 +- mm/slub.c | 4 ++-- 4 files changed, 13 insertions(+), 15 deletions(-)include/linux/kasan.h +9
-11@@ -181,19 +181,18 @@ static __always_inline void * __must_check kasan_init_slab_obj( }bool __kasan_slab_free (struct kmem_cache *s,void *object,unsigned long ip); -static __always_inline bool kasan_slab_free(struct kmem_cache *s, void *object, -unsigned long ip) +static __always_inline bool kasan_slab_free(struct kmem_cache *s, void *object) {if (kasan_enabled ()) -return __kasan_slab_free (s, object, ip); +return __kasan_slab_free (s, object, _RET_IP_);return false; }void __kasan_slab_free_mempool (void *ptr,unsigned long ip); -static __always_inline void kasan_slab_free_mempool(void *ptr, unsigned long ip) +static __always_inline void kasan_slab_free_mempool(void *ptr) {if (kasan_enabled ()) -__kasan_slab_free_mempool (ptr, ip); +__kasan_slab_free_mempool (ptr, _RET_IP_); } void * __must_check __kasan_slab_alloc(struct kmem_cache *s,@@ -237,10 +236,10 @@ static __always_inline void * __must_check kasan_krealloc(const void *object, }void __kasan_kfree_large (void *ptr,unsigned long ip); -static __always_inline void kasan_kfree_large(void *ptr, unsigned long ip) +static __always_inline void kasan_kfree_large(void *ptr) {if (kasan_enabled ()) -__kasan_kfree_large (ptr, ip); +__kasan_kfree_large (ptr, _RET_IP_); }bool kasan_save_enable_multi_shot (void );@@ -273,12 +272,11 @@ static inline void *kasan_init_slab_obj(struct kmem_cache *cache, {return (void *)object; } -static inline bool kasan_slab_free (struct kmem_cache *s,void *object, -unsigned long ip) +static inline bool kasan_slab_free (struct kmem_cache *s,void *object) {return false; } -static inline void kasan_slab_free_mempool (void *ptr,unsigned long ip) {} +static inline void kasan_slab_free_mempool (void *ptr) {}static inline void *kasan_slab_alloc (struct kmem_cache *s,void *object,gfp_t flags) {@@ -298,7 +296,7 @@ static inline void *kasan_krealloc(const void *object, size_t new_size, {return (void *)object; } -static inline void kasan_kfree_large (void *ptr,unsigned long ip) {} +static inline void kasan_kfree_large (void *ptr) {}#endif /* CONFIG_KASAN */ mm/mempool.c +1
-1@@ -104,7 +104,7 @@ static inline void poison_element(mempool_t *pool, void *element) static __always_inline void kasan_poison_element(mempool_t *pool, void *element) {if (pool->alloc == mempool_alloc_slab || pool->alloc == mempool_kmalloc) -kasan_slab_free_mempool (element, _RET_IP_); +kasan_slab_free_mempool (element);else if (pool->alloc == mempool_alloc_pages)kasan_free_pages (element, (unsigned long )pool->pool_data); }mm/slab.c +1
-1@@ -3421,7 +3421,7 @@ static __always_inline void __cache_free(struct kmem_cache *cachep, void *objp,memset (objp,0 , cachep->object_size);/* Put the object into the quarantine, don't touch it for now. */ -if (kasan_slab_free (cachep, objp, _RET_IP_)) +if (kasan_slab_free (cachep, objp))return ;/* Use KCSAN to help debug racy use-after-free. */ mm/slub.c +2
-2@@ -1514,7 +1514,7 @@ static inline void *kmalloc_large_node_hook(void *ptr, size_t size, gfp_t flags) static __always_inline void kfree_hook(void *x) {kmemleak_free (x); -kasan_kfree_large (x, _RET_IP_); +kasan_kfree_large (x); } static __always_inline bool slab_free_hook(struct kmem_cache *s, void *x)@@ -1544,7 +1544,7 @@ static __always_inline bool slab_free_hook(struct kmem_cache *s, void *x) KCSAN_ACCESS_WRITE | KCSAN_ACCESS_ASSERT);/* KASAN might put x into memory quarantine, delaying its reuse */ -return kasan_slab_free (s, x, _RET_IP_); +return kasan_slab_free (s, x); } static inline bool slab_free_freelist_hook(struct kmem_cache *s,Signature · 1 line
2.30.0.284.gd98b1dd5eaa7-goog
Re: [PATCH v3 11/15] kasan: move _RET_IP_ to inline wrappers
Quoted 9 lines · Marco Elver
Quoted 7 lines · Andrey Konovalov
Generic mm functions that call KASAN annotations that might report a bug pass _RET_IP_ to them as an argument. This allows KASAN to include the name of the function that called the mm function in its report's header.
Now that KASAN has inline wrappers for all of its annotations, move _RET_IP_ to those wrappers to simplify annotation call sites.
Quoted 6 lines
Quoted 2 lines
Much nicer!
Reviewed-by Marco Elver <[email protected]>
PATCH v3 12/15 kasan: fix bug detection via ksize for HW_TAGS mode
In reply to Andrey KonovalovThe currently existing kasan_check_read/write() annotations are intended to be used for kernel modules that have KASAN compiler instrumentation disabled. Thus, they are only relevant for the software KASAN modes that rely on compiler instrumentation.
However there's another use case for these annotations: ksize() checks that the object passed to it is indeed accessible before unpoisoning the whole object. This is currently done via __kasan_check_read(), which is compiled away for the hardware tag-based mode that doesn't rely on compiler instrumentation. This leads to KASAN missing detecting some memory corruptions.
Provide another annotation called kasan_check_byte() that is available for all KASAN modes. As the implementation rename and reuse kasan_check_invalid_free(). Use this new annotation in ksize(). To avoid having ksize() as the top frame in the reported stack trace pass _RET_IP_ to __kasan_check_byte().
Also add a new ksize_uaf() test that checks that a use-after-free is detected via ksize() itself, and via plain accesses that happen later.
--- include/linux/kasan-checks.h | 6 ++++++ include/linux/kasan.h | 17 +++++++++++++++++ lib/test_kasan.c | 20 ++++++++++++++++++++ mm/kasan/common.c | 11 ++++++++++- mm/kasan/generic.c | 4 ++-- mm/kasan/kasan.h | 10 +++++----- mm/kasan/sw_tags.c | 6 +++--- mm/slab_common.c | 16 +++++++++------- 8 files changed, 72 insertions(+), 18 deletions(-)
include/linux/kasan-checks.h +6 -0
@@ -4,6 +4,12 @@#include <linux/types.h> +/* +* The annotations present in this file are only relevant for the software +* KASAN modes that rely on compiler instrumentation, and will be optimized +* away for the hardware tag-based KASAN mode. Use kasan_check_byte() instead. +*/ +/* * __kasan_check_*: Always available when KASAN is enabled. This may be used * even in compilation units that selectively disable KASAN, but must use KASAN
include/linux/kasan.h +17 -0
@@ -242,6 +242,19 @@ static __always_inline void kasan_kfree_large(void *ptr)__kasan_kfree_large (ptr, _RET_IP_); } +/* +* Unlike kasan_check_read/write(), kasan_check_byte() is performed even for +* the hardware tag-based mode that doesn't rely on compiler instrumentation. +*/ +bool __kasan_check_byte (const void *addr,unsigned long ip); +static __always_inline bool kasan_check_byte(const void *addr) +{ +if (kasan_enabled ()) +return __kasan_check_byte (addr, _RET_IP_); +return true; +} + +bool kasan_save_enable_multi_shot (void );void kasan_restore_multi_shot (bool enabled);
@@ -297,6 +310,10 @@ static inline void *kasan_krealloc(const void *object, size_t new_size, return (void *)object; }static inline void kasan_kfree_large (void *ptr) {} +static inline bool kasan_check_byte (const void *address) +{ +return true; +}#endif /* CONFIG_KASAN */
lib/test_kasan.c +20 -0
@@ -496,6 +496,7 @@ static void kasan_global_oob(struct kunit *test)KUNIT_EXPECT_KASAN_FAIL (test, *(volatile char *)p); } +/* Check that ksize() makes the whole object accessible. */ static void ksize_unpoisons_memory (struct kunit *test) {char *ptr;
@@ -514,6 +515,24 @@ static void ksize_unpoisons_memory(struct kunit *test)kfree (ptr); } +/* +* Check that a use-after-free is detected by ksize() and via normal accesses +* after it. +*/ +static void ksize_uaf (struct kunit *test) +{ +char *ptr; +int size =128 -KASAN_GRANULE_SIZE ; + + ptr =kmalloc (size,GFP_KERNEL ); +KUNIT_ASSERT_NOT_ERR_OR_NULL (test, ptr); +kfree (ptr); + +KUNIT_EXPECT_KASAN_FAIL (test,ksize (ptr)); +KUNIT_EXPECT_KASAN_FAIL (test, kasan_int_result = *ptr); +KUNIT_EXPECT_KASAN_FAIL (test, kasan_int_result = *(ptr + size)); +} +static void kasan_stack_oob (struct kunit *test) {char stack_array[10 ];
@@ -907,6 +926,7 @@ static struct kunit_case kasan_kunit_test_cases[] = {KUNIT_CASE (kasan_alloca_oob_left),KUNIT_CASE (kasan_alloca_oob_right),KUNIT_CASE (ksize_unpoisons_memory), +KUNIT_CASE (ksize_uaf),KUNIT_CASE (kmem_cache_double_free),KUNIT_CASE (kmem_cache_invalid_free), KUNIT_CASE(kasan_memchr),
mm/kasan/common.c +10 -1
@@ -345,7 +345,7 @@ static bool ____kasan_slab_free(struct kmem_cache *cache, void *object,if (unlikely (cache->flags &SLAB_TYPESAFE_BY_RCU ))return false; -if (kasan_check_invalid_free (tagged_object)) { +if (!kasan_byte_accessible (tagged_object)) {kasan_report_invalid_free (tagged_object, ip);return true; }
@@ -490,3 +490,12 @@ void __kasan_kfree_large(void *ptr, unsigned long ip)kasan_report_invalid_free (ptr, ip);/* The object will be poisoned by kasan_free_pages(). */ } + +bool __kasan_check_byte (const void *address,unsigned long ip) +{ +if (!kasan_byte_accessible (address)) { +kasan_report ((unsigned long )address,1 , false, ip); +return false; + } +return true; +}
mm/kasan/generic.c +2 -2
@@ -185,11 +185,11 @@ bool kasan_check_range(unsigned long addr, size_t size, bool write,return check_region_inline (addr, size, write, ret_ip); } -bool kasan_check_invalid_free (void *addr) +bool kasan_byte_accessible (const void *addr) {s8 shadow_byte =READ_ONCE (*(s8 *)kasan_mem_to_shadow (addr)); -return shadow_byte <0 || shadow_byte >=KASAN_GRANULE_SIZE ; +return shadow_byte >=0 && shadow_byte <KASAN_GRANULE_SIZE ; }void kasan_cache_shrink (struct kmem_cache *cache)
mm/kasan/kasan.h +5 -5
@@ -329,20 +329,20 @@ static inline void kasan_unpoison(const void *address, size_t size) round_up(size, KASAN_GRANULE_SIZE), get_tag(address)); } -static inline bool kasan_check_invalid_free (void *addr) +static inline bool kasan_byte_accessible (const void *addr) {u8 ptr_tag =get_tag (addr); -u8 mem_tag =hw_get_mem_tag (addr); +u8 mem_tag =hw_get_mem_tag ((void *)addr); -return (mem_tag ==KASAN_TAG_INVALID ) || - (ptr_tag !=KASAN_TAG_KERNEL && ptr_tag != mem_tag); +return (mem_tag !=KASAN_TAG_INVALID ) && + (ptr_tag ==KASAN_TAG_KERNEL || ptr_tag == mem_tag); }#else /* CONFIG_KASAN_HW_TAGS */ void kasan_poison (const void *address,size_t size,u8 value);void kasan_unpoison (const void *address,size_t size); -bool kasan_check_invalid_free (void *addr); +bool kasan_byte_accessible (const void *addr);#endif /* CONFIG_KASAN_HW_TAGS */
mm/kasan/sw_tags.c +3 -3
@@ -118,13 +118,13 @@ bool kasan_check_range(unsigned long addr, size_t size, bool write,return true; } -bool kasan_check_invalid_free (void *addr) +bool kasan_byte_accessible (const void *addr) {u8 tag =get_tag (addr);u8 shadow_byte =READ_ONCE (*(u8 *)kasan_mem_to_shadow (kasan_reset_tag (addr))); -return (shadow_byte ==KASAN_TAG_INVALID ) || - (tag !=KASAN_TAG_KERNEL && tag != shadow_byte); +return (shadow_byte !=KASAN_TAG_INVALID ) && + (tag ==KASAN_TAG_KERNEL || tag == shadow_byte); }#define DEFINE_HWASAN_LOAD_STORE (size) \
mm/slab_common.c +9 -7
@@ -1157,19 +1157,21 @@ size_t ksize(const void *objp)size_t size;/* -* We need to check that the pointed to object is valid, and only then -* unpoison the shadow memory below. We use __kasan_check_read(), to -* generate a more useful report at the time ksize() is called (rather -* than later where behaviour is undefined due to potential -* use-after-free or double-free). +* We need to first check that the pointer to the object is valid, and +* only then unpoison the memory. The report printed from ksize() is +* more useful, then when it's printed later when the behaviour could +* be undefined due to a potential use-after-free or double-free. * -* If the pointed to memory is invalid we return 0, to avoid users of +* We use kasan_check_byte(), which is supported for the hardware +* tag-based KASAN mode, unlike kasan_check_read/write(). +* +* If the pointed to memory is invalid, we return 0 to avoid users of * ksize() writing to and potentially corrupting the memory region. * * We want to perform the check before __ksize(), to avoid potentially * crashing in __ksize() due to accessing invalid metadata. */ -if (unlikely (ZERO_OR_NULL_PTR (objp)) || !__kasan_check_read (objp,1 )) +if (unlikely (ZERO_OR_NULL_PTR (objp)) || !kasan_check_byte (objp))return 0 ; size =__ksize (objp);
Signature · 1 line
2.30.0.284.gd98b1dd5eaa7-goog
Re: [PATCH v3 12/15] kasan: fix bug detection via ksize for HW_TAGS mode
Quoted 18 lines · Andrey Konovalov
The currently existing kasan_check_read/write() annotations are intended to be used for kernel modules that have KASAN compiler instrumentation disabled. Thus, they are only relevant for the software KASAN modes that rely on compiler instrumentation.
However there's another use case for these annotations:
ksize()checks that the object passed to it is indeed accessible before unpoisoning the whole object. This is currently done via__kasan_check_read(), which is compiled away for the hardware tag-based mode that doesn't rely on compiler instrumentation. This leads to KASAN missing detecting some memory corruptions.Provide another annotation called
kasan_check_byte()that is available for all KASAN modes. As the implementation rename and reusekasan_check_invalid_free(). Use this new annotation inksize(). To avoid havingksize()as the top frame in the reported stack trace pass _RET_IP_ to__kasan_check_byte().
Quoted 5 lines
Also add a new
ksize_uaf()test that checks that a use-after-free is detected viaksize()itself, and via plain accesses that happen later.
Quoted 239 lines
--- include/linux/kasan-checks.h | 6 ++++++ include/linux/kasan.h | 17 +++++++++++++++++ lib/test_kasan.c | 20 ++++++++++++++++++++ mm/kasan/common.c | 11 ++++++++++- mm/kasan/generic.c | 4 ++-- mm/kasan/kasan.h | 10 +++++----- mm/kasan/sw_tags.c | 6 +++--- mm/slab_common.c | 16 +++++++++------- 8 files changed, 72 insertions(+), 18 deletions(-)include/linux/kasan-checks.h +6
-0@@ -4,6 +4,12 @@#include <linux/types.h> +/* +* The annotations present in this file are only relevant for the software +* KASAN modes that rely on compiler instrumentation, and will be optimized +* away for the hardware tag-based KASAN mode. Use kasan_check_byte() instead. +*/ +/* * __kasan_check_*: Always available when KASAN is enabled. This may be used * even in compilation units that selectively disable KASAN, but must use KASAN include/linux/kasan.h +17
-0@@ -242,6 +242,19 @@ static __always_inline void kasan_kfree_large(void *ptr)__kasan_kfree_large (ptr, _RET_IP_); } +/* +* Unlike kasan_check_read/write(), kasan_check_byte() is performed even for +* the hardware tag-based mode that doesn't rely on compiler instrumentation. +*/ +bool __kasan_check_byte (const void *addr,unsigned long ip); +static __always_inline bool kasan_check_byte(const void *addr) +{ +if (kasan_enabled ()) +return __kasan_check_byte (addr, _RET_IP_); +return true; +} + +bool kasan_save_enable_multi_shot (void );void kasan_restore_multi_shot (bool enabled);@@ -297,6 +310,10 @@ static inline void *kasan_krealloc(const void *object, size_t new_size, return (void *)object; }static inline void kasan_kfree_large (void *ptr) {} +static inline bool kasan_check_byte (const void *address) +{ +return true; +}#endif /* CONFIG_KASAN */ lib/test_kasan.c +20
-0@@ -496,6 +496,7 @@ static void kasan_global_oob(struct kunit *test)KUNIT_EXPECT_KASAN_FAIL (test, *(volatile char *)p); } +/* Check that ksize() makes the whole object accessible. */ static void ksize_unpoisons_memory (struct kunit *test) {char *ptr;@@ -514,6 +515,24 @@ static void ksize_unpoisons_memory(struct kunit *test)kfree (ptr); } +/* +* Check that a use-after-free is detected by ksize() and via normal accesses +* after it. +*/ +static void ksize_uaf (struct kunit *test) +{ +char *ptr; +int size =128 -KASAN_GRANULE_SIZE ; + + ptr =kmalloc (size,GFP_KERNEL ); +KUNIT_ASSERT_NOT_ERR_OR_NULL (test, ptr); +kfree (ptr); + +KUNIT_EXPECT_KASAN_FAIL (test,ksize (ptr)); +KUNIT_EXPECT_KASAN_FAIL (test, kasan_int_result = *ptr); +KUNIT_EXPECT_KASAN_FAIL (test, kasan_int_result = *(ptr + size)); +} +static void kasan_stack_oob (struct kunit *test) {char stack_array[10 ];@@ -907,6 +926,7 @@ static struct kunit_case kasan_kunit_test_cases[] = {KUNIT_CASE (kasan_alloca_oob_left),KUNIT_CASE (kasan_alloca_oob_right),KUNIT_CASE (ksize_unpoisons_memory), +KUNIT_CASE (ksize_uaf),KUNIT_CASE (kmem_cache_double_free),KUNIT_CASE (kmem_cache_invalid_free), KUNIT_CASE(kasan_memchr),mm/kasan/common.c +10
-1@@ -345,7 +345,7 @@ static bool ____kasan_slab_free(struct kmem_cache *cache, void *object,if (unlikely (cache->flags &SLAB_TYPESAFE_BY_RCU ))return false; -if (kasan_check_invalid_free (tagged_object)) { +if (!kasan_byte_accessible (tagged_object)) {kasan_report_invalid_free (tagged_object, ip);return true; }@@ -490,3 +490,12 @@ void __kasan_kfree_large(void *ptr, unsigned long ip)kasan_report_invalid_free (ptr, ip);/* The object will be poisoned by kasan_free_pages(). */ } + +bool __kasan_check_byte (const void *address,unsigned long ip) +{ +if (!kasan_byte_accessible (address)) { +kasan_report ((unsigned long )address,1 , false, ip); +return false; + } +return true; +}mm/kasan/generic.c +2
-2@@ -185,11 +185,11 @@ bool kasan_check_range(unsigned long addr, size_t size, bool write,return check_region_inline (addr, size, write, ret_ip); } -bool kasan_check_invalid_free (void *addr) +bool kasan_byte_accessible (const void *addr) {s8 shadow_byte =READ_ONCE (*(s8 *)kasan_mem_to_shadow (addr)); -return shadow_byte <0 || shadow_byte >=KASAN_GRANULE_SIZE ; +return shadow_byte >=0 && shadow_byte <KASAN_GRANULE_SIZE ; }void kasan_cache_shrink (struct kmem_cache *cache)mm/kasan/kasan.h +5
-5@@ -329,20 +329,20 @@ static inline void kasan_unpoison(const void *address, size_t size) round_up(size, KASAN_GRANULE_SIZE), get_tag(address)); } -static inline bool kasan_check_invalid_free (void *addr) +static inline bool kasan_byte_accessible (const void *addr) {u8 ptr_tag =get_tag (addr); -u8 mem_tag =hw_get_mem_tag (addr); +u8 mem_tag =hw_get_mem_tag ((void *)addr); -return (mem_tag ==KASAN_TAG_INVALID ) || - (ptr_tag !=KASAN_TAG_KERNEL && ptr_tag != mem_tag); +return (mem_tag !=KASAN_TAG_INVALID ) && + (ptr_tag ==KASAN_TAG_KERNEL || ptr_tag == mem_tag); }#else /* CONFIG_KASAN_HW_TAGS */ void kasan_poison (const void *address,size_t size,u8 value);void kasan_unpoison (const void *address,size_t size); -bool kasan_check_invalid_free (void *addr); +bool kasan_byte_accessible (const void *addr);#endif /* CONFIG_KASAN_HW_TAGS */ mm/kasan/sw_tags.c +3
-3@@ -118,13 +118,13 @@ bool kasan_check_range(unsigned long addr, size_t size, bool write,return true; } -bool kasan_check_invalid_free (void *addr) +bool kasan_byte_accessible (const void *addr) {u8 tag =get_tag (addr);u8 shadow_byte =READ_ONCE (*(u8 *)kasan_mem_to_shadow (kasan_reset_tag (addr))); -return (shadow_byte ==KASAN_TAG_INVALID ) || - (tag !=KASAN_TAG_KERNEL && tag != shadow_byte); +return (shadow_byte !=KASAN_TAG_INVALID ) && + (tag ==KASAN_TAG_KERNEL || tag == shadow_byte); }#define DEFINE_HWASAN_LOAD_STORE (size) \mm/slab_common.c +9
-7@@ -1157,19 +1157,21 @@ size_t ksize(const void *objp)size_t size;/* -* We need to check that the pointed to object is valid, and only then -* unpoison the shadow memory below. We use __kasan_check_read(), to -* generate a more useful report at the time ksize() is called (rather -* than later where behaviour is undefined due to potential -* use-after-free or double-free). +* We need to first check that the pointer to the object is valid, and +* only then unpoison the memory. The report printed from ksize() is +* more useful, then when it's printed later when the behaviour could +* be undefined due to a potential use-after-free or double-free. * -* If the pointed to memory is invalid we return 0, to avoid users of +* We use kasan_check_byte(), which is supported for the hardware +* tag-based KASAN mode, unlike kasan_check_read/write(). +* +* If the pointed to memory is invalid, we return 0 to avoid users of * ksize() writing to and potentially corrupting the memory region. * * We want to perform the check before __ksize(), to avoid potentially * crashing in __ksize() due to accessing invalid metadata. */ -if (unlikely (ZERO_OR_NULL_PTR (objp)) || !__kasan_check_read (objp,1 )) +if (unlikely (ZERO_OR_NULL_PTR (objp)) || !kasan_check_byte (objp))return 0 ; size =__ksize (objp);Signature · 1 line
2.30.0.284.gd98b1dd5eaa7-goog
Re: [PATCH v3 12/15] kasan: fix bug detection via ksize for HW_TAGS mode
Quoted 20 lines · Marco Elver
Quoted 18 lines · Andrey Konovalov
The currently existing kasan_check_read/write() annotations are intended to be used for kernel modules that have KASAN compiler instrumentation disabled. Thus, they are only relevant for the software KASAN modes that rely on compiler instrumentation.
However there's another use case for these annotations:
ksize()checks that the object passed to it is indeed accessible before unpoisoning the whole object. This is currently done via__kasan_check_read(), which is compiled away for the hardware tag-based mode that doesn't rely on compiler instrumentation. This leads to KASAN missing detecting some memory corruptions.Provide another annotation called
kasan_check_byte()that is available for all KASAN modes. As the implementation rename and reusekasan_check_invalid_free(). Use this new annotation inksize(). To avoid havingksize()as the top frame in the reported stack trace pass _RET_IP_ to__kasan_check_byte().
Quoted 7 lines
Quoted 5 lines
Also add a new
ksize_uaf()test that checks that a use-after-free is detected viaksize()itself, and via plain accesses that happen later.Reviewed-by Marco Elver <[email protected]>
PATCH v3 13/15 kasan: add proper page allocator tests
In reply to Andrey KonovalovThe currently existing page allocator tests rely on kmalloc fallback with large sizes that is only present for SLUB. Add proper tests that use alloc/free_pages().
--- lib/test_kasan.c | 51 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 5 deletions(-)
lib/test_kasan.c +46 -5
@@ -147,6 +147,12 @@ static void kmalloc_node_oob_right(struct kunit *test)kfree (ptr); } +/* +* These kmalloc_pagealloc_* tests try allocating a memory chunk that doesn't +* fit into a slab cache and therefore is allocated via the page allocator +* fallback. Since this kind of fallback is only implemented for SLUB, these +* tests are limited to that allocator. +*/ static void kmalloc_pagealloc_oob_right (struct kunit *test) {char *ptr;
@@ -154,14 +160,11 @@ static void kmalloc_pagealloc_oob_right(struct kunit *test)KASAN_TEST_NEEDS_CONFIG_ON (test,CONFIG_SLUB ); -/* -* Allocate a chunk that does not fit into a SLUB cache to trigger -* the page allocator fallback. -*/ ptr =kmalloc (size,GFP_KERNEL );KUNIT_ASSERT_NOT_ERR_OR_NULL (test, ptr);KUNIT_EXPECT_KASAN_FAIL (test, ptr[size +OOB_TAG_OFF ] =0 ); +kfree (ptr); }
@@ -174,8 +177,8 @@ static void kmalloc_pagealloc_uaf(struct kunit *test) ptr =kmalloc (size,GFP_KERNEL );KUNIT_ASSERT_NOT_ERR_OR_NULL (test, ptr); -kfree (ptr); +KUNIT_EXPECT_KASAN_FAIL (test, ptr[0 ] =0 ); }
@@ -192,6 +195,42 @@ static void kmalloc_pagealloc_invalid_free(struct kunit *test)KUNIT_EXPECT_KASAN_FAIL (test,kfree (ptr +1 )); } +static void pagealloc_oob_right (struct kunit *test) +{ +char *ptr; +struct page *pages; +size_t order =4 ; +size_t size = (1UL << (PAGE_SHIFT + order)); + +/* +* With generic KASAN page allocations have no redzones, thus +* out-of-bounds detection is not guaranteed. +* See https://bugzilla.kernel.org/show_bug.cgi?id=210503. +*/ +KASAN_TEST_NEEDS_CONFIG_OFF (test,CONFIG_KASAN_GENERIC ); + + pages =alloc_pages (GFP_KERNEL , order); + ptr =page_address (pages); +KUNIT_ASSERT_NOT_ERR_OR_NULL (test, ptr); + +KUNIT_EXPECT_KASAN_FAIL (test, ptr[size] =0 ); +free_pages ((unsigned long )ptr, order); +} + +static void pagealloc_uaf (struct kunit *test) +{ +char *ptr; +struct page *pages; +size_t order =4 ; + + pages =alloc_pages (GFP_KERNEL , order); + ptr =page_address (pages); +KUNIT_ASSERT_NOT_ERR_OR_NULL (test, ptr); +free_pages ((unsigned long )ptr, order); + +KUNIT_EXPECT_KASAN_FAIL (test, ptr[0 ] =0 ); +} +static void kmalloc_large_oob_right (struct kunit *test) {char *ptr;
@@ -903,6 +942,8 @@ static struct kunit_case kasan_kunit_test_cases[] = {KUNIT_CASE (kmalloc_pagealloc_oob_right),KUNIT_CASE (kmalloc_pagealloc_uaf),KUNIT_CASE (kmalloc_pagealloc_invalid_free), +KUNIT_CASE (pagealloc_oob_right), +KUNIT_CASE (pagealloc_uaf),KUNIT_CASE (kmalloc_large_oob_right),KUNIT_CASE (kmalloc_oob_krealloc_more), KUNIT_CASE(kmalloc_oob_krealloc_less),
Signature · 1 line
2.30.0.284.gd98b1dd5eaa7-goog
PATCH v3 14/15 kasan: add a test for kmem_cache_alloc/free_bulk
In reply to Andrey KonovalovAdd a test for kmem_cache_alloc/free_bulk to make sure there are no false-positives when these functions are used.
--- lib/test_kasan.c | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-)
lib/test_kasan.c +33 -5
@@ -479,10 +479,11 @@ static void kmem_cache_oob(struct kunit *test) {char *p;size_t size =200 ; -struct kmem_cache *cache =kmem_cache_create ("test_cache" , - size,0 , -0 ,NULL ); +struct kmem_cache *cache; + + cache =kmem_cache_create ("test_cache" , size,0 ,0 ,NULL );KUNIT_ASSERT_NOT_ERR_OR_NULL (test, cache); + p =kmem_cache_alloc (cache,GFP_KERNEL );if (!p) {kunit_err (test,"Allocation failed: %s\n" , __func__);
@@ -491,11 +492,12 @@ static void kmem_cache_oob(struct kunit *test) }KUNIT_EXPECT_KASAN_FAIL (test, *p = p[size +OOB_TAG_OFF ]); +kmem_cache_free (cache, p);kmem_cache_destroy (cache); } -static void memcg_accounted_kmem_cache (struct kunit *test) +static void kmem_cache_accounted (struct kunit *test) {int i;char *p;
@@ -522,6 +524,31 @@ static void memcg_accounted_kmem_cache(struct kunit *test)kmem_cache_destroy (cache); } +static void kmem_cache_bulk (struct kunit *test) +{ +struct kmem_cache *cache; +size_t size =200 ; +char *p[10 ]; +bool ret; +int i; + + cache =kmem_cache_create ("test_cache" , size,0 ,0 ,NULL ); +KUNIT_ASSERT_NOT_ERR_OR_NULL (test, cache); + + ret =kmem_cache_alloc_bulk (cache,GFP_KERNEL ,ARRAY_SIZE (p), (void **)&p); +if (!ret) { +kunit_err (test,"Allocation failed: %s\n" , __func__); +kmem_cache_destroy (cache); +return ; + } + +for (i =0 ; i <ARRAY_SIZE (p); i++) + p[i][0 ] = p[i][size -1 ] =42 ; + +kmem_cache_free_bulk (cache,ARRAY_SIZE (p), (void **)&p); +kmem_cache_destroy (cache); +} +static char global_array[10 ];static void kasan_global_oob (struct kunit *test)
@@ -961,7 +988,8 @@ static struct kunit_case kasan_kunit_test_cases[] = {KUNIT_CASE (kfree_via_page),KUNIT_CASE (kfree_via_phys),KUNIT_CASE (kmem_cache_oob), -KUNIT_CASE (memcg_accounted_kmem_cache), +KUNIT_CASE (kmem_cache_accounted), +KUNIT_CASE (kmem_cache_bulk),KUNIT_CASE (kasan_global_oob),KUNIT_CASE (kasan_stack_oob), KUNIT_CASE(kasan_alloca_oob_left),
Signature · 1 line
2.30.0.284.gd98b1dd5eaa7-goog
Re: [PATCH v3 14/15] kasan: add a test for kmem_cache_alloc/free_bulk
Quoted 5 lines · Andrey Konovalov
Add a test for kmem_cache_alloc/free_bulk to make sure there are no false-positives when these functions are used.
Quoted 82 lines
--- lib/test_kasan.c | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-)lib/test_kasan.c +33
-5@@ -479,10 +479,11 @@ static void kmem_cache_oob(struct kunit *test) {char *p;size_t size =200 ; -struct kmem_cache *cache =kmem_cache_create ("test_cache" , - size,0 , -0 ,NULL ); +struct kmem_cache *cache; + + cache =kmem_cache_create ("test_cache" , size,0 ,0 ,NULL );KUNIT_ASSERT_NOT_ERR_OR_NULL (test, cache); + p =kmem_cache_alloc (cache,GFP_KERNEL );if (!p) {kunit_err (test,"Allocation failed: %s\n" , __func__);@@ -491,11 +492,12 @@ static void kmem_cache_oob(struct kunit *test) }KUNIT_EXPECT_KASAN_FAIL (test, *p = p[size +OOB_TAG_OFF ]); +kmem_cache_free (cache, p);kmem_cache_destroy (cache); } -static void memcg_accounted_kmem_cache (struct kunit *test) +static void kmem_cache_accounted (struct kunit *test) {int i;char *p;@@ -522,6 +524,31 @@ static void memcg_accounted_kmem_cache(struct kunit *test)kmem_cache_destroy (cache); } +static void kmem_cache_bulk (struct kunit *test) +{ +struct kmem_cache *cache; +size_t size =200 ; +char *p[10 ]; +bool ret; +int i; + + cache =kmem_cache_create ("test_cache" , size,0 ,0 ,NULL ); +KUNIT_ASSERT_NOT_ERR_OR_NULL (test, cache); + + ret =kmem_cache_alloc_bulk (cache,GFP_KERNEL ,ARRAY_SIZE (p), (void **)&p); +if (!ret) { +kunit_err (test,"Allocation failed: %s\n" , __func__); +kmem_cache_destroy (cache); +return ; + } + +for (i =0 ; i <ARRAY_SIZE (p); i++) + p[i][0 ] = p[i][size -1 ] =42 ; + +kmem_cache_free_bulk (cache,ARRAY_SIZE (p), (void **)&p); +kmem_cache_destroy (cache); +} +static char global_array[10 ];static void kasan_global_oob (struct kunit *test)@@ -961,7 +988,8 @@ static struct kunit_case kasan_kunit_test_cases[] = {KUNIT_CASE (kfree_via_page),KUNIT_CASE (kfree_via_phys),KUNIT_CASE (kmem_cache_oob), -KUNIT_CASE (memcg_accounted_kmem_cache), +KUNIT_CASE (kmem_cache_accounted), +KUNIT_CASE (kmem_cache_bulk),KUNIT_CASE (kasan_global_oob),KUNIT_CASE (kasan_stack_oob), KUNIT_CASE(kasan_alloca_oob_left),Signature · 1 line
2.30.0.284.gd98b1dd5eaa7-goog
Re: [PATCH v3 14/15] kasan: add a test for kmem_cache_alloc/free_bulk
Quoted 9 lines · Marco Elver
Quoted 5 lines · Andrey Konovalov
Add a test for kmem_cache_alloc/free_bulk to make sure there are no false-positives when these functions are used.
Reviewed-by Marco Elver <[email protected]>
(see a nit below)
Quoted 1 line
Quoted 1 line
+ cache =
kmem_cache_create("test_cache", size, 0, 0, NULL);
Looks like there's a tab between "test_cache" and size, please double-check.
Re: [PATCH v3 14/15] kasan: add a test for kmem_cache_alloc/free_bulk
Quoted 10 lines · Alexander Potapenko
Quoted 8 lines · Marco Elver
Quoted 5 lines · Andrey Konovalov
Add a test for kmem_cache_alloc/free_bulk to make sure there are no false-positives when these functions are used.
Quoted 7 lines
Quoted 1 line
Reviewed-by Marco Elver <[email protected]>Reviewed-by Alexander Potapenko <[email protected]>(see a nit below)
Quoted 1 line
Quoted 1 line
+ cache =
kmem_cache_create("test_cache", size, 0, 0, NULL);Looks like there's a tab between "
test_cache" and size, please double-check.
Indeed, thanks for noticing! Will fix.
PATCH v3 15/15 kasan: don't run tests when KASAN is not enabled
In reply to Andrey KonovalovDon't run KASAN tests when it's disabled with kasan.mode=off to avoid corrupting kernel memory.
--- lib/test_kasan.c | 5 +++++ 1 file changed, 5 insertions(+)
lib/test_kasan.c +5 -0
@@ -47,6 +47,11 @@ static bool multishot;*/ static int kasan_test_init (struct kunit *test) { +if (!kasan_enabled ()) { +kunit_err (test,"can't run KASAN tests with KASAN disabled" ); +return -1 ; + } + multishot =kasan_save_enable_multi_shot ();hw_set_tagging_report_once (false);return 0 ;
Signature · 1 line
2.30.0.284.gd98b1dd5eaa7-goog
Re: [PATCH v3 15/15] kasan: don't run tests when KASAN is not enabled
Quoted 5 lines · Andrey Konovalov
Don't run KASAN tests when it's disabled with kasan.mode=off to avoid corrupting kernel memory.
Quoted 23 lines
--- lib/test_kasan.c | 5 +++++ 1 file changed, 5 insertions(+)lib/test_kasan.c +5
-0@@ -47,6 +47,11 @@ static bool multishot;*/ static int kasan_test_init (struct kunit *test) { +if (!kasan_enabled ()) { +kunit_err (test,"can't run KASAN tests with KASAN disabled" ); +return -1 ; + } + multishot =kasan_save_enable_multi_shot ();hw_set_tagging_report_once (false);return 0 ;Signature · 1 line
2.30.0.284.gd98b1dd5eaa7-goog
Re: [PATCH v3 15/15] kasan: don't run tests when KASAN is not enabled
Quoted 9 lines · Marco Elver
Quoted 5 lines · Andrey Konovalov
Don't run KASAN tests when it's disabled with kasan.mode=off to avoid corrupting kernel memory.
Reviewed-by Marco Elver <[email protected]>