kasan: more tag based mode fixes
Subsystems (4)
v2 cover kasan: more tag based mode fixes
Changes in v2:
- Add comments about kmemleak vs KASAN hooks order.
- Fix compilation error when CONFIG_SLUB_DEBUG is not defined.
Andrey Konovalov (5):
kasan: fix assigning tags twice
kasan, kmemleak: pass tagged pointers to kmemleak
kmemleak: account for tagged pointers when calculating pointer range kasan, slub: move kasan_poison_slab hook before page_address kasan, slub: fix conflicts with CONFIG_SLAB_FREELIST_HARDENED
mm/kasan/common.c | 29 +++++++++++++++++------------ mm/kmemleak.c | 10 +++++++--- mm/slab.h | 7 +++---- mm/slab_common.c | 3 ++- mm/slub.c | 43 +++++++++++++++++++++++++------------------ 5 files changed, 54 insertions(+), 38 deletions(-)
Signature · 1 line
2.20.1.791.gb4d0f1c61a-goog
PATCH v2 1/5 kasan: fix assigning tags twice
When an object is kmalloc()'ed, two hooks are called: kasan_slab_alloc() and kasan_kmalloc(). Right now we assign a tag twice, once in each of the hooks. Fix it by assigning a tag only in the former hook.
--- mm/kasan/common.c | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-)
mm/kasan/common.c +17 -12
@@ -361,10 +361,15 @@ void kasan_poison_object_data(struct kmem_cache *cache, void *object)* get different tags. */ static u8 assign_tag (struct kmem_cache *cache,const void *object, -bool init,bool krealloc) +bool init,bool keep_tag) { -/* Reuse the same tag for krealloc'ed objects. */ -if (krealloc) +/* +* 1. When an object is kmalloc()'ed, two hooks are called: +* kasan_slab_alloc() and kasan_kmalloc(). We assign the +* tag only in the first one. +* 2. We reuse the same tag for krealloc'ed objects. +*/ +if (keep_tag)return get_tag (object);/*
@@ -405,12 +410,6 @@ void * __must_check kasan_init_slab_obj(struct kmem_cache *cache,return (void *)object; } -void *__must_check kasan_slab_alloc (struct kmem_cache *cache,void *object, -gfp_t flags) -{ -return kasan_kmalloc (cache, object, cache->object_size, flags); -} -static inline bool shadow_invalid (u8 tag,s8 shadow_byte) {if (IS_ENABLED (CONFIG_KASAN_GENERIC ))
@@ -467,7 +466,7 @@ bool kasan_slab_free(struct kmem_cache *cache, void *object, unsigned long ip) }static void *__kasan_kmalloc (struct kmem_cache *cache,const void *object, -size_t size,gfp_t flags,bool krealloc) +size_t size,gfp_t flags,bool keep_tag) {unsigned long redzone_start;unsigned long redzone_end;
@@ -485,7 +484,7 @@ static void *__kasan_kmalloc(struct kmem_cache *cache, const void *object, KASAN_SHADOW_SCALE_SIZE);if (IS_ENABLED (CONFIG_KASAN_SW_TAGS )) - tag =assign_tag (cache, object, false, krealloc); + tag =assign_tag (cache, object, false, keep_tag);/* Tag is ignored in set_tag without CONFIG_KASAN_SW_TAGS */ kasan_unpoison_shadow (set_tag (object, tag), size);
@@ -498,10 +497,16 @@ static void *__kasan_kmalloc(struct kmem_cache *cache, const void *object,return set_tag (object, tag); } +void *__must_check kasan_slab_alloc (struct kmem_cache *cache,void *object, +gfp_t flags) +{ +return __kasan_kmalloc (cache, object, cache->object_size, flags, false); +} +void *__must_check kasan_kmalloc (struct kmem_cache *cache,const void *object,size_t size,gfp_t flags) { -return __kasan_kmalloc (cache, object, size, flags, false); +return __kasan_kmalloc (cache, object, size, flags, true); }EXPORT_SYMBOL (kasan_kmalloc);
Signature · 1 line
2.20.1.791.gb4d0f1c61a-goog
PATCH v2 2/5 kasan, kmemleak: pass tagged pointers to kmemleak
In reply to Andrey KonovalovRight now we call kmemleak hooks before assigning tags to pointers in KASAN hooks. As a result, when an objects gets allocated, kmemleak sees a differently tagged pointer, compared to the one it sees when the object gets freed. Fix it by calling KASAN hooks before kmemleak's ones.
--- mm/slab.h | 6 ++---- mm/slab_common.c | 2 +- mm/slub.c | 3 ++- 3 files changed, 5 insertions(+), 6 deletions(-)
mm/slab.h +2 -4
@@ -437,11 +437,9 @@ static inline void slab_post_alloc_hook(struct kmem_cache *s, gfp_t flags, flags &= gfp_allowed_mask;for (i =0 ; i < size; i++) { -void *object = p[i]; - -kmemleak_alloc_recursive (object, s->object_size,1 , + p[i] =kasan_slab_alloc (s, p[i], flags); +kmemleak_alloc_recursive (p[i], s->object_size,1 , s->flags, flags); - p[i] =kasan_slab_alloc (s, object, flags); }if (memcg_kmem_enabled ())
mm/slab_common.c +1 -1
@@ -1228,8 +1228,8 @@ void *kmalloc_order(size_t size, gfp_t flags, unsigned int order) flags |= __GFP_COMP; page =alloc_pages (flags, order); ret = page ?page_address (page) :NULL ; -kmemleak_alloc (ret, size,1 , flags); ret =kasan_kmalloc_large (ret, size, flags); +kmemleak_alloc (ret, size,1 , flags);return ret; }EXPORT_SYMBOL (kmalloc_order);
mm/slub.c +2 -1
@@ -1374,8 +1374,9 @@ static inline void dec_slabs_node(struct kmem_cache *s, int node,*/ static inline void *kmalloc_large_node_hook (void *ptr,size_t size,gfp_t flags) { + ptr =kasan_kmalloc_large (ptr, size, flags);kmemleak_alloc (ptr, size,1 , flags); -return kasan_kmalloc_large (ptr, size, flags); +return ptr; } static __always_inline void kfree_hook(void *x)
Signature · 1 line
2.20.1.791.gb4d0f1c61a-goog
PATCH v2 3/5 kmemleak: account for tagged pointers when calculating pointer range
In reply to Andrey Konovalovkmemleak keeps two global variables, min_addr and max_addr, which store the range of valid (encountered by kmemleak) pointer values, which it later uses to speed up pointer lookup when scanning blocks.
With tagged pointers this range will get bigger than it needs to be. This patch makes kmemleak untag pointers before saving them to min_addr and max_addr and when performing a lookup.
--- mm/kmemleak.c | 10 +++++++--- mm/slab.h | 1 + mm/slab_common.c | 1 + mm/slub.c | 1 + 4 files changed, 10 insertions(+), 3 deletions(-)
mm/kmemleak.c +7 -3
@@ -574,6 +574,7 @@ static struct kmemleak_object *create_object(unsigned long ptr, size_t size,unsigned long flags;struct kmemleak_object *object, *parent;struct rb_node **link, *rb_parent; +unsigned long untagged_ptr; object =kmem_cache_alloc (object_cache,gfp_kmemleak_mask (gfp));if (!object) {
@@ -619,8 +620,9 @@ static struct kmemleak_object *create_object(unsigned long ptr, size_t size,write_lock_irqsave (&kmemleak_lock, flags); - min_addr =min (min_addr, ptr); - max_addr =max (max_addr, ptr + size); + untagged_ptr = (unsigned long )kasan_reset_tag ((void *)ptr); + min_addr =min (min_addr, untagged_ptr); + max_addr =max (max_addr, untagged_ptr + size); link = &object_tree_root.rb_node; rb_parent =NULL ;while (*link) {
@@ -1333,6 +1335,7 @@ static void scan_block(void *_start, void *_end,unsigned long *start =PTR_ALIGN (_start,BYTES_PER_POINTER );unsigned long *end = _end - (BYTES_PER_POINTER -1 );unsigned long flags; +unsigned long untagged_ptr;read_lock_irqsave (&kmemleak_lock, flags);for (ptr = start; ptr < end; ptr++) {
@@ -1347,7 +1350,8 @@ static void scan_block(void *_start, void *_end, pointer = *ptr;kasan_enable_current (); -if (pointer < min_addr || pointer >= max_addr) + untagged_ptr = (unsigned long )kasan_reset_tag ((void *)pointer); +if (untagged_ptr < min_addr || untagged_ptr >= max_addr)continue ;/*
mm/slab.h +1 -0
@@ -438,6 +438,7 @@ static inline void slab_post_alloc_hook(struct kmem_cache *s, gfp_t flags, flags &= gfp_allowed_mask;for (i =0 ; i < size; i++) { p[i] =kasan_slab_alloc (s, p[i], flags); +/* As p[i] might get tagged, call kmemleak hook after KASAN. */ kmemleak_alloc_recursive (p[i], s->object_size,1 , s->flags, flags); }
mm/slab_common.c +1 -0
@@ -1229,6 +1229,7 @@ void *kmalloc_order(size_t size, gfp_t flags, unsigned int order) page =alloc_pages (flags, order); ret = page ?page_address (page) :NULL ; ret =kasan_kmalloc_large (ret, size, flags); +/* As ret might get tagged, call kmemleak hook after KASAN. */ kmemleak_alloc (ret, size,1 , flags);return ret; }
mm/slub.c +1 -0
@@ -1375,6 +1375,7 @@ static inline void dec_slabs_node(struct kmem_cache *s, int node,static inline void *kmalloc_large_node_hook (void *ptr,size_t size,gfp_t flags) { ptr =kasan_kmalloc_large (ptr, size, flags); +/* As ptr might get tagged, call kmemleak hook after KASAN. */ kmemleak_alloc (ptr, size,1 , flags);return ptr; }
Signature · 1 line
2.20.1.791.gb4d0f1c61a-goog
Re: [PATCH v2 3/5] kmemleak: account for tagged pointers when calculating pointer range
Quoted 9 lines · Andrey Konovalov
kmemleak keeps two global variables,
min_addrandmax_addr, which store the range of valid (encountered by kmemleak) pointer values, which it later uses to speed up pointer lookup when scanning blocks.With tagged pointers this range will get bigger than it needs to be. This patch makes kmemleak untag pointers before saving them to
min_addrandmax_addrand when performing a lookup.Signed-off-by Andrey Konovalov <[email protected]>
Re: [PATCH v2 3/5] kmemleak: account for tagged pointers when calculating pointer range
In reply to Andrey KonovalovQuoted 9 lines · Andrey Konovalov
kmemleak keeps two global variables,
min_addrandmax_addr, which store the range of valid (encountered by kmemleak) pointer values, which it later uses to speed up pointer lookup when scanning blocks.With tagged pointers this range will get bigger than it needs to be. This patch makes kmemleak untag pointers before saving them to
min_addrandmax_addrand when performing a lookup.Signed-off-by Andrey Konovalov <[email protected]>
I reviewed the old series. This patch also looks fine:
PATCH v2 4/5 kasan, slub: move kasan_poison_slab hook before page_address
In reply to Andrey KonovalovWith tag based KASAN page_address() looks at the page flags to see whether the resulting pointer needs to have a tag set. Since we don't want to set a tag when page_address() is called on SLAB pages, we call page_kasan_tag_reset() in kasan_poison_slab(). However in allocate_slab() page_address() is called before kasan_poison_slab(). Fix it by changing the order.
--- mm/slub.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-)
mm/slub.c +15 -4
@@ -1075,6 +1075,16 @@ static void setup_object_debug(struct kmem_cache *s, struct page *page,init_tracking (s, object); } +static void setup_page_debug (struct kmem_cache *s,void *addr,int order) +{ +if (!(s->flags &SLAB_POISON )) +return ; + +metadata_access_enable (); +memset (addr,POISON_INUSE ,PAGE_SIZE << order); +metadata_access_disable (); +} +static inline int alloc_consistency_checks (struct kmem_cache *s,struct page *page,void *object,unsigned long addr)
@@ -1330,6 +1340,8 @@ slab_flags_t kmem_cache_flags(unsigned int object_size,#else /* !CONFIG_SLUB_DEBUG */ static inline void setup_object_debug (struct kmem_cache *s,struct page *page,void *object) {} +static inline void setup_page_debug (struct kmem_cache *s, +void *addr,int order) {}static inline int alloc_debug_processing (struct kmem_cache *s,struct page *page,void *object,unsigned long addr) {return 0 ; }
@@ -1643,12 +1655,11 @@ static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node)if (page_is_pfmemalloc (page))SetPageSlabPfmemalloc (page); - start =page_address (page); +kasan_poison_slab (page); -if (unlikely (s->flags &SLAB_POISON )) -memset (start,POISON_INUSE ,PAGE_SIZE << order); + start =page_address (page); -kasan_poison_slab (page); +setup_page_debug (s, start, order); shuffle =shuffle_freelist (s, page);
Signature · 1 line
2.20.1.791.gb4d0f1c61a-goog
PATCH v2 5/5 kasan, slub: fix conflicts with CONFIG_SLAB_FREELIST_HARDENED
In reply to Andrey KonovalovCONFIG_SLAB_FREELIST_HARDENED hashes freelist pointer with the address of the object where the pointer gets stored. With tag based KASAN we don't account for that when building freelist, as we call set_freepointer() with the first argument untagged. This patch changes the code to properly propagate tags throughout the loop.
--- mm/slub.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-)
mm/slub.c +7 -13
@@ -303,11 +303,6 @@ static inline void set_freepointer(struct kmem_cache *s, void *object, void *fp) __p < (__addr ) + (__objects) * (__s)->size; \ __p += (__s)->size) -#define for_each_object_idx (__p, __idx, __s, __addr, __objects) \ - for (__p = fixup_red_left(__s, __addr), __idx = 1; \ - __idx <= __objects; \ - __p += (__s)->size, __idx++) -/* Determine object index from a given position */ static inline unsigned int slab_index (void *p,struct kmem_cache *s,void *addr) {
@@ -1664,17 +1659,16 @@ static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node) shuffle =shuffle_freelist (s, page);if (!shuffle) { -for_each_object_idx (p, idx, s, start, page->objects) { -if (likely (idx < page->objects)) { - next = p + s->size; - next =setup_object (s, page, next); -set_freepointer (s, p, next); - }else -set_freepointer (s, p,NULL ); - } start =fixup_red_left (s, start); start =setup_object (s, page, start); page->freelist = start; +for (idx =0 , p = start; idx < page->objects -1 ; idx++) { + next = p + s->size; + next =setup_object (s, page, next); +set_freepointer (s, p, next); + p = next; + } +set_freepointer (s, p,NULL ); } page->inuse = page->objects;
Signature · 1 line
2.20.1.791.gb4d0f1c61a-goog
Andrew Morton
maintainerQuoted 2 lines · On Wed, 13 Feb 2019 14:58:25 +0100 Andrey Konovalov <[email protected]>…
Changes in v2:
- Add comments about kmemleak vs KASAN hooks order.
I assume this refers to Vincenzo's review of "kasan, kmemleak: pass tagged pointers to kmemleak". But v2 of that patch is unchanged.
Quoted 1 line
- Fix compilation error when
CONFIG_SLUB_DEBUGis not defined.
Quoted 8 lines · Andrew Morton
Quoted 2 lines · On Wed, 13 Feb 2019 14:58:25 +0100 Andrey Konovalov <[email protected]>…
Changes in v2:
- Add comments about kmemleak vs KASAN hooks order.I assume this refers to Vincenzo's review of "kasan, kmemleak: pass tagged pointers to kmemleak". But v2 of that patch is unchanged.
I've accidentally squashed this change into commit #3 instead of #2 :(
Quoted 2 lines
Quoted 2 lines
- Fix compilation error when
CONFIG_SLUB_DEBUGis not defined.