mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Ryan Roberts <ryan.roberts@arm.com>
To: Dev Jain <dev.jain@arm.com>,
	akpm@linux-foundation.org, vbabka@kernel.org, harry@kernel.org,
	ryabinin.a.a@gmail.com
Cc: surenb@google.com, mhocko@suse.com, jackmanb@google.com,
	hannes@cmpxchg.org, ziy@nvidia.com, hao.li@linux.dev,
	cl@gentwo.org, rientjes@google.com, roman.gushchin@linux.dev,
	linux-mm@kvack.org, linux-kernel@vger.kernel.org,
	glider@google.com, andreyknvl@gmail.com, dvyukov@google.com,
	vincenzo.frascino@arm.com, kasan-dev@googlegroups.com,
	anshuman.khandual@arm.com, catalin.marinas@arm.com
Subject: Re: [PATCH 2/3] kasan: avoid re-poisoning tag-based kmalloc redzones
Date: Tue, 19 May 2026 09:53:53 +0100	[thread overview]
Message-ID: <2609d691-5e70-4baa-8e58-99f5ea708c6a@arm.com> (raw)
In-Reply-To: <14da7ffe-1332-45d0-86d6-e9d9ae0a4fe0@arm.com>

On 19/05/2026 09:15, Dev Jain wrote:
> 
> 
> On 13/05/26 4:27 pm, Dev Jain wrote:
>> When we allocate object from slab, kasan will unpoison the entire object.
>> In case of allocation from kmalloc caches, the actual allocation size
>> request can be less than the size of the kmalloc cache. kasan poisons
>> the bytes following allocation size up till object size to catch OOB.
>>
>> We can do this operation in one shot: while unpoisoning the object upon
>> allocation, only unpoison up till allocation size bytes, so that the
>> bytes following that up till object size remain poisoned.
>>
>> Currently when we free an object into the slab, we use KASAN_SLAB_FREE for
>> poisoning, and use KASAN_SLAB_REDZONE for poisoning the tail end. For
>> tag-based kasan, these two are equal, as opposed to generic kasan. So we
>> make this optimization only for tag-based kasan.
>>
>> Signed-off-by: Dev Jain <dev.jain@arm.com>
>> ---
> 
> Sashiko: https://sashiko.dev/#/patchset/20260513105734.3380544-1-dev.jain@arm.com
> 
> So I'll explain what Sashiko is saying with examples. If I am mis-interpreting
> then please correct me!
> 
> Problem 1:
> 
> p = kzalloc(96, GFP_KERNEL);                 // kmalloc-128
> p = krealloc(p, 120, GFP_KERNEL | __GFP_ZERO);
> 
> Before my patch, kzalloc zeroed bytes 0..127, krealloc exposes bytes 96..119
> which are already zero from kzalloc.
> 
> After my patch, kzalloc zeroes only 0..95, krealloc may think old
> size is 128, so it does not zero 96..119, 96..119 may contain stale data.
> 
> Why is this a problem? 

Intuitively, I would say it's a problem if you're leaking when you previously
were not.

> Surely caller of krealloc shouldn't be
> relying on a previous zeroing. krealloc documentation explicitly says that
> GFP_ZERO is ignored.

What would happen if you did:

p = kzalloc(96, GFP_KERNEL);                 // kmalloc-128
p = krealloc(p, 129, GFP_KERNEL | __GFP_ZERO);

Presumably the krealloc() would have to move the whole allocation to the next
biggest bin? Would p[128] be zeroed? If so, then I'd say your change is problematic.

> 
> 
> Problem 2 for SW tags:
> 
> p = kzalloc(97, GFP_KERNEL);   // kmalloc-128, granule = 16
> 
> Before patch:
> 
> KASAN unpoisons 0..111
> memset zeroes 0..127
> p[100] will not be caught by KASAN, but reads zero
> 
> After patch:
> 
> KASAN unpoisons 0..111
> memset zeroes only 0..96
> p[100] will not be caught by KASAN and reads stale data
> 
> Again why is this a problem?

Because you have defeated KASAN and are leaking data? I think you'd want to zero
at least to the end of the KASAN granule?

Thanks,
Ryan


> 
>>  include/linux/kasan.h | 17 +++++++++----
>>  mm/kasan/common.c     | 55 +++++++++++++++++++++++++++++++++----------
>>  mm/slub.c             | 11 +++++----
>>  3 files changed, 61 insertions(+), 22 deletions(-)
>>
>> diff --git a/include/linux/kasan.h b/include/linux/kasan.h
>> index bf233bde68c7..fd7c1f5f9fd6 100644
>> --- a/include/linux/kasan.h
>> +++ b/include/linux/kasan.h
>> @@ -102,6 +102,12 @@ static inline bool kasan_has_integrated_init(void)
>>  	return kasan_hw_tags_enabled();
>>  }
>>  
>> +static inline bool kasan_has_tag_based_kmalloc_redzones(void)
>> +{
>> +	return kasan_enabled() &&
>> +	       (IS_ENABLED(CONFIG_KASAN_SW_TAGS) || kasan_hw_tags_enabled());
>> +}
>> +
>>  #ifdef CONFIG_KASAN
>>  void __kasan_unpoison_range(const void *addr, size_t size);
>>  static __always_inline void kasan_unpoison_range(const void *addr, size_t size)
>> @@ -244,13 +250,14 @@ static __always_inline void kasan_kfree_large(void *ptr)
>>  		__kasan_kfree_large(ptr, _RET_IP_);
>>  }
>>  
>> -void * __must_check __kasan_slab_alloc(struct kmem_cache *s,
>> -				       void *object, gfp_t flags, bool init);
>> +void * __must_check __kasan_slab_alloc(struct kmem_cache *s, void *object,
>> +				       size_t size, gfp_t flags, bool init);
>>  static __always_inline void * __must_check kasan_slab_alloc(
>> -		struct kmem_cache *s, void *object, gfp_t flags, bool init)
>> +		struct kmem_cache *s, void *object, size_t size,
>> +		gfp_t flags, bool init)
>>  {
>>  	if (kasan_enabled())
>> -		return __kasan_slab_alloc(s, object, flags, init);
>> +		return __kasan_slab_alloc(s, object, size, flags, init);
>>  	return object;
>>  }
>>  
>> @@ -437,7 +444,7 @@ static inline bool kasan_slab_free(struct kmem_cache *s, void *object,
>>  }
>>  static inline void kasan_kfree_large(void *ptr) {}
>>  static inline void *kasan_slab_alloc(struct kmem_cache *s, void *object,
>> -				   gfp_t flags, bool init)
>> +				   size_t size, gfp_t flags, bool init)
>>  {
>>  	return object;
>>  }
>> diff --git a/mm/kasan/common.c b/mm/kasan/common.c
>> index b7d05c2a6d93..9a4db9c21aaf 100644
>> --- a/mm/kasan/common.c
>> +++ b/mm/kasan/common.c
>> @@ -326,14 +326,25 @@ void __kasan_kfree_large(void *ptr, unsigned long ip)
>>  	/* The object will be poisoned by kasan_poison_pages(). */
>>  }
>>  
>> +static inline size_t slab_unpoison_size(struct kmem_cache *cache, size_t size)
>> +{
>> +	if (kasan_has_tag_based_kmalloc_redzones() && is_kmalloc_cache(cache))
>> +		return min_t(size_t, size, cache->object_size);
>> +
>> +	return cache->object_size;
>> +}
>> +
>>  static inline void unpoison_slab_object(struct kmem_cache *cache, void *object,
>> -					gfp_t flags, bool init)
>> +					size_t size, gfp_t flags, bool init)
>>  {
>>  	/*
>> -	 * Unpoison the whole object. For kmalloc() allocations,
>> -	 * poison_kmalloc_redzone() will do precise poisoning.
>> +	 * For tag-based modes, kmalloc redzones all use the same invalid tag.
>> +	 * Keep the tail poisoned and only unpoison the requested allocation
>> +	 * size. Generic KASAN keeps distinct shadow values for free objects and
>> +	 * redzones, so it still unpoisons the whole object and later poisons
>> +	 * the precise redzone.
>>  	 */
>> -	kasan_unpoison(object, cache->object_size, init);
>> +	kasan_unpoison(object, slab_unpoison_size(cache, size), init);
>>  
>>  	/* Save alloc info (if possible) for non-kmalloc() allocations. */
>>  	if (kasan_stack_collection_enabled() && !is_kmalloc_cache(cache))
>> @@ -341,7 +352,8 @@ static inline void unpoison_slab_object(struct kmem_cache *cache, void *object,
>>  }
>>  
>>  void * __must_check __kasan_slab_alloc(struct kmem_cache *cache,
>> -					void *object, gfp_t flags, bool init)
>> +					void *object, size_t size,
>> +					gfp_t flags, bool init)
>>  {
>>  	u8 tag;
>>  	void *tagged_object;
>> @@ -363,11 +375,18 @@ void * __must_check __kasan_slab_alloc(struct kmem_cache *cache,
>>  	tagged_object = set_tag(object, tag);
>>  
>>  	/* Unpoison the object and save alloc info for non-kmalloc() allocations. */
>> -	unpoison_slab_object(cache, tagged_object, flags, init);
>> +	unpoison_slab_object(cache, tagged_object, size, flags, init);
>>  
>>  	return tagged_object;
>>  }
>>  
>> +static inline void save_kmalloc_alloc_info(struct kmem_cache *cache,
>> +					   void *object, gfp_t flags)
>> +{
>> +	if (kasan_stack_collection_enabled() && is_kmalloc_cache(cache))
>> +		kasan_save_alloc_info(cache, object, flags);
>> +}
>> +
>>  static inline void poison_kmalloc_redzone(struct kmem_cache *cache,
>>  				const void *object, size_t size, gfp_t flags)
>>  {
>> @@ -394,8 +413,7 @@ static inline void poison_kmalloc_redzone(struct kmem_cache *cache,
>>  	 * Save alloc info (if possible) for kmalloc() allocations.
>>  	 * This also rewrites the alloc info when called from kasan_krealloc().
>>  	 */
>> -	if (kasan_stack_collection_enabled() && is_kmalloc_cache(cache))
>> -		kasan_save_alloc_info(cache, (void *)object, flags);
>> +	save_kmalloc_alloc_info(cache, (void *)object, flags);
>>  
>>  }
>>  
>> @@ -411,8 +429,14 @@ void * __must_check __kasan_kmalloc(struct kmem_cache *cache, const void *object
>>  	if (is_kfence_address(object))
>>  		return (void *)object;
>>  
>> -	/* The object has already been unpoisoned by kasan_slab_alloc(). */
>> -	poison_kmalloc_redzone(cache, object, size, flags);
>> +	/*
>> +	 * For tag-based modes, the object has already been precisely
>> +	 * unpoisoned by kasan_slab_alloc(). The tail remains poisoned.
>> +	 */
>> +	if (kasan_has_tag_based_kmalloc_redzones())
>> +		save_kmalloc_alloc_info(cache, (void *)object, flags);
>> +	else
>> +		poison_kmalloc_redzone(cache, object, size, flags);
>>  
>>  	/* Keep the tag that was set by kasan_slab_alloc(). */
>>  	return (void *)object;
>> @@ -561,11 +585,16 @@ void __kasan_mempool_unpoison_object(void *ptr, size_t size, unsigned long ip)
>>  		return;
>>  
>>  	/* Unpoison the object and save alloc info for non-kmalloc() allocations. */
>> -	unpoison_slab_object(slab->slab_cache, ptr, flags, false);
>> +	unpoison_slab_object(slab->slab_cache, ptr, size, flags, false);
>>  
>>  	/* Poison the redzone and save alloc info for kmalloc() allocations. */
>> -	if (is_kmalloc_cache(slab->slab_cache))
>> -		poison_kmalloc_redzone(slab->slab_cache, ptr, size, flags);
>> +	if (is_kmalloc_cache(slab->slab_cache)) {
>> +		if (kasan_has_tag_based_kmalloc_redzones())
>> +			save_kmalloc_alloc_info(slab->slab_cache, ptr, flags);
>> +		else
>> +			poison_kmalloc_redzone(slab->slab_cache, ptr, size,
>> +					       flags);
>> +	}
>>  }
>>  
>>  bool __kasan_check_byte(const void *address, unsigned long ip)
>> diff --git a/mm/slub.c b/mm/slub.c
>> index da3520769d1f..15144b2e078c 100644
>> --- a/mm/slub.c
>> +++ b/mm/slub.c
>> @@ -4550,8 +4550,9 @@ bool slab_post_alloc_hook(struct kmem_cache *s, struct list_lru *lru,
>>  	 * replacement of current poisoning under certain debug option, and
>>  	 * won't break other sanity checks.
>>  	 */
>> -	if (kmem_cache_debug_flags(s, SLAB_STORE_USER | SLAB_RED_ZONE) &&
>> -	    (s->flags & SLAB_KMALLOC))
>> +	if ((s->flags & SLAB_KMALLOC) &&
>> +	    (kmem_cache_debug_flags(s, SLAB_STORE_USER | SLAB_RED_ZONE) ||
>> +	     kasan_has_tag_based_kmalloc_redzones()))
>>  		zero_size = orig_size;
>>  
>>  	/*
>> @@ -4573,7 +4574,8 @@ bool slab_post_alloc_hook(struct kmem_cache *s, struct list_lru *lru,
>>  	 * As p[i] might get tagged, memset and kmemleak hook come after KASAN.
>>  	 */
>>  	for (i = 0; i < size; i++) {
>> -		p[i] = kasan_slab_alloc(s, p[i], init_flags, kasan_init);
>> +		p[i] = kasan_slab_alloc(s, p[i], orig_size, init_flags,
>> +					kasan_init);
>>  		if (p[i] && init && (!kasan_init ||
>>  				     !kasan_has_integrated_init()))
>>  			memset(p[i], 0, zero_size);
>> @@ -7615,7 +7617,8 @@ static void early_kmem_cache_node_alloc(int node)
>>  #ifdef CONFIG_SLUB_DEBUG
>>  	init_object(kmem_cache_node, n, SLUB_RED_ACTIVE);
>>  #endif
>> -	n = kasan_slab_alloc(kmem_cache_node, n, GFP_KERNEL, false);
>> +	n = kasan_slab_alloc(kmem_cache_node, n, kmem_cache_node->object_size,
>> +			     GFP_KERNEL, false);
>>  	slab->freelist = get_freepointer(kmem_cache_node, n);
>>  	slab->inuse = 1;
>>  	kmem_cache_node->per_node[node].node = n;
> 


  reply	other threads:[~2026-05-19  8:53 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-13 10:57 [PATCH 0/3] kasan: hw_tags: some micro-optimizations Dev Jain
2026-05-13 10:57 ` [PATCH 1/3] mm/slub: hw_tags: skip page-allocator unpoisoning on slab allocation Dev Jain
2026-05-14 12:11   ` Ryan Roberts
2026-05-15  7:04     ` Dev Jain
2026-05-13 10:57 ` [PATCH 2/3] kasan: avoid re-poisoning tag-based kmalloc redzones Dev Jain
2026-05-19  8:15   ` Dev Jain
2026-05-19  8:53     ` Ryan Roberts [this message]
2026-05-20  7:02       ` Dev Jain
2026-05-20  8:33         ` Dev Jain
2026-05-20  4:32     ` Dev Jain
2026-05-13 10:57 ` [PATCH 3/3] vmalloc: hw_tags: optimize vmalloc redzoning Dev Jain
2026-05-19  7:35   ` Dev Jain
2026-05-14  9:56 ` [PATCH 0/3] kasan: hw_tags: some micro-optimizations Harry Yoo (Oracle)
2026-05-14 10:22   ` Dev Jain

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=2609d691-5e70-4baa-8e58-99f5ea708c6a@arm.com \
    --to=ryan.roberts@arm.com \
    --cc=akpm@linux-foundation.org \
    --cc=andreyknvl@gmail.com \
    --cc=anshuman.khandual@arm.com \
    --cc=catalin.marinas@arm.com \
    --cc=cl@gentwo.org \
    --cc=dev.jain@arm.com \
    --cc=dvyukov@google.com \
    --cc=glider@google.com \
    --cc=hannes@cmpxchg.org \
    --cc=hao.li@linux.dev \
    --cc=harry@kernel.org \
    --cc=jackmanb@google.com \
    --cc=kasan-dev@googlegroups.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=mhocko@suse.com \
    --cc=rientjes@google.com \
    --cc=roman.gushchin@linux.dev \
    --cc=ryabinin.a.a@gmail.com \
    --cc=surenb@google.com \
    --cc=vbabka@kernel.org \
    --cc=vincenzo.frascino@arm.com \
    --cc=ziy@nvidia.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®