On 8/5/25 4:26 PM, Sabyrzhan Tasbolatov wrote:
> Introduce CONFIG_ARCH_DEFER_KASAN to identify architectures that need
> to defer KASAN initialization until shadow memory is properly set up,
> and unify the static key infrastructure across all KASAN modes.
>
> Some architectures (like PowerPC with radix MMU) need to set up their
> shadow memory mappings before KASAN can be safely enabled, while others
> (like s390, x86, arm) can enable KASAN much earlier or even from the
> beginning.
>
> Historically, the runtime static key kasan_flag_enabled existed only for
> CONFIG_KASAN_HW_TAGS mode. Generic and SW_TAGS modes either relied on
> architecture-specific kasan_arch_is_ready() implementations or evaluated
> KASAN checks unconditionally, leading to code duplication.
>
> Closes: https://bugzilla.kernel.org/show_bug.cgi?id=217049
> Signed-off-by: Sabyrzhan Tasbolatov <snovit...@gmail.com>
> ---
> Changes in v4:
> - Fixed HW_TAGS static key functionality (was broken in v3)
I don't think it fixed. Before you patch kasan_enabled() esentially
worked like this:
if (IS_ENABLED(CONFIG_KASAN_HW_TAGS))
return static_branch_likely(&kasan_flag_enabled);
else
return IS_ENABLED(CONFIG_KASAN);
Now it's just IS_ENABLED(CONFIG_KASAN);
And there are bunch of kasan_enabled() calls left whose behavior changed for
no reason.
> - Merged configuration and implementation for atomicity
> ---
> include/linux/kasan-enabled.h | 36 +++++++++++++++++++++++-------
> include/linux/kasan.h | 42 +++++++++++++++++++++++++++--------
> lib/Kconfig.kasan | 8 +++++++
> mm/kasan/common.c | 18 ++++++++++-----
> mm/kasan/generic.c | 23 +++++++++++--------
> mm/kasan/hw_tags.c | 9 +-------
> mm/kasan/kasan.h | 36 +++++++++++++++++++++---------
> mm/kasan/shadow.c | 32 ++++++--------------------
> mm/kasan/sw_tags.c | 4 +++-
> mm/kasan/tags.c | 2 +-
> 10 files changed, 133 insertions(+), 77 deletions(-)
>
> diff --git a/include/linux/kasan-enabled.h b/include/linux/kasan-enabled.h
> index 6f612d69ea0..52a3909f032 100644
> --- a/include/linux/kasan-enabled.h
> +++ b/include/linux/kasan-enabled.h
> @@ -4,32 +4,52 @@
>
> #include <linux/static_key.h>
>
> -#ifdef CONFIG_KASAN_HW_TAGS
> +/* Controls whether KASAN is enabled at all (compile-time check). */
> +static __always_inline bool kasan_enabled(void)
> +{
> + return IS_ENABLED(CONFIG_KASAN);
> +}
>
> +#if defined(CONFIG_ARCH_DEFER_KASAN) || defined(CONFIG_KASAN_HW_TAGS)
> +/*
> + * Global runtime flag for KASAN modes that need runtime control.
> + * Used by ARCH_DEFER_KASAN architectures and HW_TAGS mode.
> + */
> DECLARE_STATIC_KEY_FALSE(kasan_flag_enabled);
>
> -static __always_inline bool kasan_enabled(void)
> +/*
> + * Runtime control for shadow memory initialization or HW_TAGS mode.
> + * Uses static key for architectures that need deferred KASAN or HW_TAGS.
> + */
> +static __always_inline bool kasan_shadow_initialized(void)
Don't rename it, just leave as is - kasan_enabled().
It's better name, shorter and you don't need to convert call sites, so
there is less chance of mistakes due to unchanged kasan_enabled() ->
kasan_shadow_initialized().
> {
> return static_branch_likely(&kasan_flag_enabled);
> }
>
> -static inline bool kasan_hw_tags_enabled(void)
> +static inline void kasan_enable(void)
> +{
> + static_branch_enable(&kasan_flag_enabled);
> +}
> +#else
> +/* For architectures that can enable KASAN early, use compile-time check. */
> +static __always_inline bool kasan_shadow_initialized(void)
> {
> return kasan_enabled();
> }
>
...
>
> void kasan_populate_early_vm_area_shadow(void *start, unsigned long size);
> -int kasan_populate_vmalloc(unsigned long addr, unsigned long size);
> -void kasan_release_vmalloc(unsigned long start, unsigned long end,
> +
> +int __kasan_populate_vmalloc(unsigned long addr, unsigned long size);
> +static inline int kasan_populate_vmalloc(unsigned long addr, unsigned long
> size)
> +{
> + if (!kasan_shadow_initialized())
> + return 0;
What's the point of moving these checks to header?
Leave it in C, it's easier to grep and navigate code this way.
> + return __kasan_populate_vmalloc(addr, size);
> +}
> +
> +void __kasan_release_vmalloc(unsigned long start, unsigned long end,
> unsigned long free_region_start,
> unsigned long free_region_end,
> unsigned long flags);
> +static inline void kasan_release_vmalloc(unsigned long start,
> + unsigned long end,
> + unsigned long free_region_start,
> + unsigned long free_region_end,
> + unsigned long flags)
> +{
> + if (kasan_shadow_initialized())
> + __kasan_release_vmalloc(start, end, free_region_start,
> + free_region_end, flags);
> +}
>
...> @@ -250,7 +259,7 @@ static inline void poison_slab_object(struct
kmem_cache *cache, void *object,
> bool __kasan_slab_pre_free(struct kmem_cache *cache, void *object,
> unsigned long ip)
> {
> - if (!kasan_arch_is_ready() || is_kfence_address(object))
> + if (is_kfence_address(object))
> return false;
> return check_slab_allocation(cache, object, ip);
> }
> @@ -258,7 +267,7 @@ bool __kasan_slab_pre_free(struct kmem_cache *cache, void
> *object,
> bool __kasan_slab_free(struct kmem_cache *cache, void *object, bool init,
> bool still_accessible)
> {
> - if (!kasan_arch_is_ready() || is_kfence_address(object))
> + if (is_kfence_address(object))
> return false;
>
> poison_slab_object(cache, object, init, still_accessible);
> @@ -282,9 +291,6 @@ bool __kasan_slab_free(struct kmem_cache *cache, void
> *object, bool init,
>
> static inline bool check_page_allocation(void *ptr, unsigned long ip)
> {
> - if (!kasan_arch_is_ready())
> - return false;
> -
Well, you can't do this yet, because no arch using ARCH_DEFER_KASAN yet, so
this breaks
bisectability.
Leave it, and remove with separate patch only when there are no users left.
> if (ptr != page_address(virt_to_head_page(ptr))) {
> kasan_report_invalid_free(ptr, ip, KASAN_REPORT_INVALID_FREE);
> return true;
> @@ -511,7 +517,7 @@ bool __kasan_mempool_poison_object(void *ptr, unsigned
> long ip)
> return true;
> }
>
> - if (is_kfence_address(ptr) || !kasan_arch_is_ready())
> + if (is_kfence_address(ptr))
> return true;
>
> slab = folio_slab(folio);