* [PATCH v5 1/2] mm, kasan: improve double-free detection @ 2016-06-07 18:03 Kuthonuzo Luruo 2016-06-09 13:32 ` Alexander Potapenko 2016-06-09 17:00 ` Andrey Ryabinin 0 siblings, 2 replies; 10+ messages in thread From: Kuthonuzo Luruo @ 2016-06-07 18:03 UTC (permalink / raw) To: aryabinin, glider, dvyukov, cl, penberg, rientjes, iamjoonsoo.kim, akpm Cc: kasan-dev, linux-kernel, ynorov, kuthonuzo.luruo Currently, KASAN may fail to detect concurrent deallocations of the same object due to a race in kasan_slab_free(). This patch makes double-free detection more reliable by serializing access to KASAN object metadata. New functions kasan_meta_lock() and kasan_meta_unlock() are provided to lock/unlock per-object metadata. Double-free errors are now reported via kasan_report(). Per-object lock concept from suggestion/observations by Dmitry Vyukov. Testing: - Tested with a modified version of the 'slab_test' microbenchmark where allocs occur on CPU 0; then all other CPUs concurrently attempt to free the same object. - Tested with new double-free tests for 'test_kasan' in accompanying patch. Signed-off-by: Kuthonuzo Luruo <kuthonuzo.luruo@hpe.com> --- Changes in v5: - Removed redundant print of 'alloc_state' for pr_err in kasan_slab_free. Changes in v4: - Takes use of shadow memory in v3 further by storing lock bit in shadow byte for object header solving the issue of OOB nuking the header lock. Allocation state is also stored in the shadow header. - CAS loop using cmpxchg() byte similar to v2 is brought back. Unfortunately, standard lock primitives cannot be used since there aren't enough header/redzone shadow bytes for smaller objects. (A generic bit_spinlock() operating on a byte would have been sweet... :). - CAS loop with union magic is largely due to suggestions on patch v2 from Dmitry Vyukov. Thanks. - preempt enable/disable inside CAS loop to reduce contention is from review comment on v2 patch from Yury Norov. Thanks. Changes in v3: - simplified kasan_meta_lock()/unlock() to use generic bit spinlock apis; kasan_alloc_meta structure modified accordingly. - introduced a 'safety valve' for kasan_meta_lock() to prevent a kfree from getting stuck when a prior out-of-bounds write clobbers the object header. - removed potentially unsafe __builtin_return_address(1) from kasan_report() call per review comment from Yury Norov; callee now passed into kasan_slab_free(). --- include/linux/kasan.h | 7 ++- mm/kasan/kasan.c | 125 ++++++++++++++++++++++++++++++++++++++----------- mm/kasan/kasan.h | 24 +++++++++- mm/kasan/quarantine.c | 4 +- mm/kasan/report.c | 24 +++++++++- mm/slab.c | 3 +- mm/slub.c | 2 +- 7 files changed, 153 insertions(+), 36 deletions(-) diff --git a/include/linux/kasan.h b/include/linux/kasan.h index 611927f..3db974b 100644 --- a/include/linux/kasan.h +++ b/include/linux/kasan.h @@ -53,6 +53,7 @@ void kasan_cache_create(struct kmem_cache *cache, size_t *size, void kasan_cache_shrink(struct kmem_cache *cache); void kasan_cache_destroy(struct kmem_cache *cache); +void kasan_init_object(struct kmem_cache *cache, void *object); void kasan_poison_slab(struct page *page); void kasan_unpoison_object_data(struct kmem_cache *cache, void *object); void kasan_poison_object_data(struct kmem_cache *cache, void *object); @@ -65,7 +66,7 @@ void kasan_kmalloc(struct kmem_cache *s, const void *object, size_t size, void kasan_krealloc(const void *object, size_t new_size, gfp_t flags); void kasan_slab_alloc(struct kmem_cache *s, void *object, gfp_t flags); -bool kasan_slab_free(struct kmem_cache *s, void *object); +bool kasan_slab_free(struct kmem_cache *s, void *object, unsigned long caller); void kasan_poison_slab_free(struct kmem_cache *s, void *object); struct kasan_cache { @@ -94,6 +95,7 @@ static inline void kasan_cache_create(struct kmem_cache *cache, static inline void kasan_cache_shrink(struct kmem_cache *cache) {} static inline void kasan_cache_destroy(struct kmem_cache *cache) {} +static inline void kasan_init_object(struct kmem_cache *s, void *object) {} static inline void kasan_poison_slab(struct page *page) {} static inline void kasan_unpoison_object_data(struct kmem_cache *cache, void *object) {} @@ -110,7 +112,8 @@ static inline void kasan_krealloc(const void *object, size_t new_size, static inline void kasan_slab_alloc(struct kmem_cache *s, void *object, gfp_t flags) {} -static inline bool kasan_slab_free(struct kmem_cache *s, void *object) +static inline bool kasan_slab_free(struct kmem_cache *s, void *object, + unsigned long caller) { return false; } diff --git a/mm/kasan/kasan.c b/mm/kasan/kasan.c index 28439ac..daec64b 100644 --- a/mm/kasan/kasan.c +++ b/mm/kasan/kasan.c @@ -402,6 +402,23 @@ void kasan_cache_create(struct kmem_cache *cache, size_t *size, cache->object_size + optimal_redzone(cache->object_size))); } + +static inline union kasan_shadow_meta *get_shadow_meta( + struct kasan_alloc_meta *allocp) +{ + return (union kasan_shadow_meta *)kasan_mem_to_shadow((void *)allocp); +} + +void kasan_init_object(struct kmem_cache *cache, void *object) +{ + if (cache->flags & SLAB_KASAN) { + struct kasan_alloc_meta *allocp = get_alloc_info(cache, object); + union kasan_shadow_meta *shadow_meta = get_shadow_meta(allocp); + + __memset(allocp, 0, sizeof(*allocp)); + shadow_meta->data = KASAN_KMALLOC_META; + } +} #endif void kasan_cache_shrink(struct kmem_cache *cache) @@ -431,13 +448,6 @@ void kasan_poison_object_data(struct kmem_cache *cache, void *object) kasan_poison_shadow(object, round_up(cache->object_size, KASAN_SHADOW_SCALE_SIZE), KASAN_KMALLOC_REDZONE); -#ifdef CONFIG_SLAB - if (cache->flags & SLAB_KASAN) { - struct kasan_alloc_meta *alloc_info = - get_alloc_info(cache, object); - alloc_info->state = KASAN_STATE_INIT; - } -#endif } #ifdef CONFIG_SLAB @@ -501,6 +511,53 @@ struct kasan_free_meta *get_free_info(struct kmem_cache *cache, BUILD_BUG_ON(sizeof(struct kasan_free_meta) > 32); return (void *)object + cache->kasan_info.free_meta_offset; } + +u8 get_alloc_state(struct kasan_alloc_meta *alloc_info) +{ + return get_shadow_meta(alloc_info)->state; +} + +void set_alloc_state(struct kasan_alloc_meta *alloc_info, u8 state) +{ + get_shadow_meta(alloc_info)->state = state; +} + +/* + * Acquire per-object lock before accessing KASAN metadata. Lock bit is stored + * in header shadow memory to protect it from being flipped by out-of-bounds + * accesses on object. Standard lock primitives cannot be used since there + * aren't enough header shadow bytes. + */ +void kasan_meta_lock(struct kasan_alloc_meta *alloc_info) +{ + union kasan_shadow_meta *shadow_meta = get_shadow_meta(alloc_info); + union kasan_shadow_meta old, new; + + for (;;) { + old.data = READ_ONCE(shadow_meta->data); + if (old.lock) { + cpu_relax(); + continue; + } + new.data = old.data; + new.lock = 1; + preempt_disable(); + if (cmpxchg(&shadow_meta->data, old.data, new.data) == old.data) + break; + preempt_enable(); + } +} + +/* Release lock after a kasan_meta_lock(). */ +void kasan_meta_unlock(struct kasan_alloc_meta *alloc_info) +{ + union kasan_shadow_meta *shadow_meta = get_shadow_meta(alloc_info), new; + + new.data = READ_ONCE(shadow_meta->data); + new.lock = 0; + smp_store_release(&shadow_meta->data, new.data); + preempt_enable(); +} #endif void kasan_slab_alloc(struct kmem_cache *cache, void *object, gfp_t flags) @@ -520,35 +577,44 @@ void kasan_poison_slab_free(struct kmem_cache *cache, void *object) kasan_poison_shadow(object, rounded_up_size, KASAN_KMALLOC_FREE); } -bool kasan_slab_free(struct kmem_cache *cache, void *object) +bool kasan_slab_free(struct kmem_cache *cache, void *object, + unsigned long caller) { #ifdef CONFIG_SLAB + struct kasan_alloc_meta *alloc_info; + struct kasan_free_meta *free_info; + u8 alloc_state; + /* RCU slabs could be legally used after free within the RCU period */ if (unlikely(cache->flags & SLAB_DESTROY_BY_RCU)) return false; - if (likely(cache->flags & SLAB_KASAN)) { - struct kasan_alloc_meta *alloc_info = - get_alloc_info(cache, object); - struct kasan_free_meta *free_info = - get_free_info(cache, object); - - switch (alloc_info->state) { - case KASAN_STATE_ALLOC: - alloc_info->state = KASAN_STATE_QUARANTINE; - quarantine_put(free_info, cache); - set_track(&free_info->track, GFP_NOWAIT); - kasan_poison_slab_free(cache, object); - return true; + if (unlikely(!(cache->flags & SLAB_KASAN))) + return false; + + alloc_info = get_alloc_info(cache, object); + kasan_meta_lock(alloc_info); + alloc_state = get_alloc_state(alloc_info); + if (alloc_state == KASAN_STATE_ALLOC) { + free_info = get_free_info(cache, object); + quarantine_put(free_info, cache); + set_track(&free_info->track, GFP_NOWAIT); + kasan_poison_slab_free(cache, object); + set_alloc_state(alloc_info, KASAN_STATE_QUARANTINE); + kasan_meta_unlock(alloc_info); + return true; + } + switch (alloc_state) { case KASAN_STATE_QUARANTINE: case KASAN_STATE_FREE: - pr_err("Double free"); - dump_stack(); - break; + kasan_report((unsigned long)object, 0, false, caller); + kasan_meta_unlock(alloc_info); + return true; default: + pr_err("invalid allocation state!\n"); break; - } } + kasan_meta_unlock(alloc_info); return false; #else kasan_poison_slab_free(cache, object); @@ -580,10 +646,15 @@ void kasan_kmalloc(struct kmem_cache *cache, const void *object, size_t size, if (cache->flags & SLAB_KASAN) { struct kasan_alloc_meta *alloc_info = get_alloc_info(cache, object); + unsigned long flags; - alloc_info->state = KASAN_STATE_ALLOC; + local_irq_save(flags); + kasan_meta_lock(alloc_info); + set_alloc_state(alloc_info, KASAN_STATE_ALLOC); alloc_info->alloc_size = size; set_track(&alloc_info->track, flags); + kasan_meta_unlock(alloc_info); + local_irq_restore(flags); } #endif } @@ -636,7 +707,7 @@ void kasan_kfree(void *ptr) kasan_poison_shadow(ptr, PAGE_SIZE << compound_order(page), KASAN_FREE_PAGE); else - kasan_slab_free(page->slab_cache, ptr); + kasan_slab_free(page->slab_cache, ptr, _RET_IP_); } void kasan_kfree_large(const void *ptr) diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h index fb87923..76bdadd 100644 --- a/mm/kasan/kasan.h +++ b/mm/kasan/kasan.h @@ -12,6 +12,8 @@ #define KASAN_KMALLOC_REDZONE 0xFC /* redzone inside slub object */ #define KASAN_KMALLOC_FREE 0xFB /* object was freed (kmem_cache_free/kfree) */ #define KASAN_GLOBAL_REDZONE 0xFA /* redzone for global variable */ +#define KASAN_KMALLOC_META 0xE0 /* slab object header shadow; see union kasan_shadow_meta */ +#define KASAN_KMALLOC_META_MAX 0xEF /* end of header shadow code */ /* * Stack redzone shadow values @@ -73,10 +75,24 @@ struct kasan_track { depot_stack_handle_t stack; }; +/* + * Object header lock bit and allocation state are stored in shadow memory to + * prevent out-of-bounds accesses on object from overwriting them. + */ +union kasan_shadow_meta { + u8 data; + struct { + u8 lock : 1; /* lock bit */ + u8 state : 2; /* enum kasan_state */ + u8 unused : 1; + u8 poison : 4; /* poison constant; do not use */ + }; +}; + struct kasan_alloc_meta { + u32 alloc_size : 24; + u32 unused : 8; struct kasan_track track; - u32 state : 2; /* enum kasan_state */ - u32 alloc_size : 30; }; struct qlist_node { @@ -114,6 +130,10 @@ void kasan_report(unsigned long addr, size_t size, void quarantine_put(struct kasan_free_meta *info, struct kmem_cache *cache); void quarantine_reduce(void); void quarantine_remove_cache(struct kmem_cache *cache); +void kasan_meta_lock(struct kasan_alloc_meta *alloc_info); +void kasan_meta_unlock(struct kasan_alloc_meta *alloc_info); +u8 get_alloc_state(struct kasan_alloc_meta *alloc_info); +void set_alloc_state(struct kasan_alloc_meta *alloc_info, u8 state); #else static inline void quarantine_put(struct kasan_free_meta *info, struct kmem_cache *cache) { } diff --git a/mm/kasan/quarantine.c b/mm/kasan/quarantine.c index 4973505..ce9c913 100644 --- a/mm/kasan/quarantine.c +++ b/mm/kasan/quarantine.c @@ -148,7 +148,9 @@ static void qlink_free(struct qlist_node *qlink, struct kmem_cache *cache) unsigned long flags; local_irq_save(flags); - alloc_info->state = KASAN_STATE_FREE; + kasan_meta_lock(alloc_info); + set_alloc_state(alloc_info, KASAN_STATE_FREE); + kasan_meta_unlock(alloc_info); ___cache_free(cache, object, _THIS_IP_); local_irq_restore(flags); } diff --git a/mm/kasan/report.c b/mm/kasan/report.c index b3c122d..48c9c60 100644 --- a/mm/kasan/report.c +++ b/mm/kasan/report.c @@ -53,6 +53,17 @@ static void print_error_description(struct kasan_access_info *info) const char *bug_type = "unknown-crash"; u8 *shadow_addr; +#ifdef CONFIG_SLAB + if (!info->access_size) { + bug_type = "double-free"; + pr_err("BUG: KASAN: %s attempt in %pS on object at addr %p\n", + bug_type, (void *)info->ip, info->access_addr); + pr_err("%s by task %s/%d\n", bug_type, current->comm, + task_pid_nr(current)); + info->first_bad_addr = info->access_addr; + return; + } +#endif info->first_bad_addr = find_first_bad_addr(info->access_addr, info->access_size); @@ -75,6 +86,7 @@ static void print_error_description(struct kasan_access_info *info) break; case KASAN_PAGE_REDZONE: case KASAN_KMALLOC_REDZONE: + case KASAN_KMALLOC_META ... KASAN_KMALLOC_META_MAX: bug_type = "slab-out-of-bounds"; break; case KASAN_GLOBAL_REDZONE: @@ -131,7 +143,7 @@ static void print_track(struct kasan_track *track) } static void object_err(struct kmem_cache *cache, struct page *page, - void *object, char *unused_reason) + void *object, struct kasan_access_info *info) { struct kasan_alloc_meta *alloc_info = get_alloc_info(cache, object); struct kasan_free_meta *free_info; @@ -140,7 +152,9 @@ static void object_err(struct kmem_cache *cache, struct page *page, pr_err("Object at %p, in cache %s\n", object, cache->name); if (!(cache->flags & SLAB_KASAN)) return; - switch (alloc_info->state) { + if (info->access_size) + kasan_meta_lock(alloc_info); + switch (get_alloc_state(alloc_info)) { case KASAN_STATE_INIT: pr_err("Object not allocated yet\n"); break; @@ -161,6 +175,8 @@ static void object_err(struct kmem_cache *cache, struct page *page, print_track(&free_info->track); break; } + if (info->access_size) + kasan_meta_unlock(alloc_info); } #endif @@ -177,8 +193,12 @@ static void print_address_description(struct kasan_access_info *info) struct kmem_cache *cache = page->slab_cache; object = nearest_obj(cache, page, (void *)info->access_addr); +#ifdef CONFIG_SLAB + object_err(cache, page, object, info); +#else object_err(cache, page, object, "kasan: bad access detected"); +#endif return; } dump_page(page, "kasan: bad access detected"); diff --git a/mm/slab.c b/mm/slab.c index 763096a..b8c51a6 100644 --- a/mm/slab.c +++ b/mm/slab.c @@ -2611,6 +2611,7 @@ static void cache_init_objs(struct kmem_cache *cachep, cachep->ctor(objp); kasan_poison_object_data(cachep, objp); } + kasan_init_object(cachep, index_to_obj(cachep, page, i)); if (!shuffled) set_free_obj(page, i, i); @@ -3508,7 +3509,7 @@ static inline void __cache_free(struct kmem_cache *cachep, void *objp, unsigned long caller) { /* Put the object into the quarantine, don't touch it for now. */ - if (kasan_slab_free(cachep, objp)) + if (kasan_slab_free(cachep, objp, _RET_IP_)) return; ___cache_free(cachep, objp, caller); diff --git a/mm/slub.c b/mm/slub.c index 5beeeb2..f25c0c2 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -1344,7 +1344,7 @@ static inline void slab_free_hook(struct kmem_cache *s, void *x) if (!(s->flags & SLAB_DEBUG_OBJECTS)) debug_check_no_obj_freed(x, s->object_size); - kasan_slab_free(s, x); + kasan_slab_free(s, x, _RET_IP_); } static inline void slab_free_freelist_hook(struct kmem_cache *s, -- 1.7.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v5 1/2] mm, kasan: improve double-free detection 2016-06-07 18:03 [PATCH v5 1/2] mm, kasan: improve double-free detection Kuthonuzo Luruo @ 2016-06-09 13:32 ` Alexander Potapenko 2016-06-09 16:54 ` Luruo, Kuthonuzo 2016-06-09 17:00 ` Andrey Ryabinin 1 sibling, 1 reply; 10+ messages in thread From: Alexander Potapenko @ 2016-06-09 13:32 UTC (permalink / raw) To: Kuthonuzo Luruo Cc: Andrey Ryabinin, Dmitriy Vyukov, Christoph Lameter, penberg, David Rientjes, Joonsoo Kim, Andrew Morton, kasan-dev, LKML, ynorov On Tue, Jun 7, 2016 at 8:03 PM, Kuthonuzo Luruo <kuthonuzo.luruo@hpe.com> wrote: > Currently, KASAN may fail to detect concurrent deallocations of the same > object due to a race in kasan_slab_free(). This patch makes double-free > detection more reliable by serializing access to KASAN object metadata. > New functions kasan_meta_lock() and kasan_meta_unlock() are provided to > lock/unlock per-object metadata. Double-free errors are now reported via > kasan_report(). > > Per-object lock concept from suggestion/observations by Dmitry Vyukov. Note I've sent out a patch that enables stackdepot support in SLUB. I'll probably need to wait till you patch lands and add locking to SLUB as well. > Testing: > - Tested with a modified version of the 'slab_test' microbenchmark where > allocs occur on CPU 0; then all other CPUs concurrently attempt to free > the same object. > - Tested with new double-free tests for 'test_kasan' in accompanying patch. > > Signed-off-by: Kuthonuzo Luruo <kuthonuzo.luruo@hpe.com> > --- > > Changes in v5: > - Removed redundant print of 'alloc_state' for pr_err in kasan_slab_free. > > Changes in v4: > - Takes use of shadow memory in v3 further by storing lock bit in shadow > byte for object header solving the issue of OOB nuking the header lock. > Allocation state is also stored in the shadow header. > - CAS loop using cmpxchg() byte similar to v2 is brought back. > Unfortunately, standard lock primitives cannot be used since there aren't > enough header/redzone shadow bytes for smaller objects. (A generic > bit_spinlock() operating on a byte would have been sweet... :). > - CAS loop with union magic is largely due to suggestions on patch v2 from > Dmitry Vyukov. Thanks. > - preempt enable/disable inside CAS loop to reduce contention is from > review comment on v2 patch from Yury Norov. Thanks. > > Changes in v3: > - simplified kasan_meta_lock()/unlock() to use generic bit spinlock apis; > kasan_alloc_meta structure modified accordingly. > - introduced a 'safety valve' for kasan_meta_lock() to prevent a kfree from > getting stuck when a prior out-of-bounds write clobbers the object > header. > - removed potentially unsafe __builtin_return_address(1) from > kasan_report() call per review comment from Yury Norov; callee now passed > into kasan_slab_free(). > > --- > > include/linux/kasan.h | 7 ++- > mm/kasan/kasan.c | 125 ++++++++++++++++++++++++++++++++++++++----------- > mm/kasan/kasan.h | 24 +++++++++- > mm/kasan/quarantine.c | 4 +- > mm/kasan/report.c | 24 +++++++++- > mm/slab.c | 3 +- > mm/slub.c | 2 +- > 7 files changed, 153 insertions(+), 36 deletions(-) > > diff --git a/include/linux/kasan.h b/include/linux/kasan.h > index 611927f..3db974b 100644 > --- a/include/linux/kasan.h > +++ b/include/linux/kasan.h > @@ -53,6 +53,7 @@ void kasan_cache_create(struct kmem_cache *cache, size_t *size, > void kasan_cache_shrink(struct kmem_cache *cache); > void kasan_cache_destroy(struct kmem_cache *cache); > > +void kasan_init_object(struct kmem_cache *cache, void *object); > void kasan_poison_slab(struct page *page); > void kasan_unpoison_object_data(struct kmem_cache *cache, void *object); > void kasan_poison_object_data(struct kmem_cache *cache, void *object); > @@ -65,7 +66,7 @@ void kasan_kmalloc(struct kmem_cache *s, const void *object, size_t size, > void kasan_krealloc(const void *object, size_t new_size, gfp_t flags); > > void kasan_slab_alloc(struct kmem_cache *s, void *object, gfp_t flags); > -bool kasan_slab_free(struct kmem_cache *s, void *object); > +bool kasan_slab_free(struct kmem_cache *s, void *object, unsigned long caller); > void kasan_poison_slab_free(struct kmem_cache *s, void *object); > > struct kasan_cache { > @@ -94,6 +95,7 @@ static inline void kasan_cache_create(struct kmem_cache *cache, > static inline void kasan_cache_shrink(struct kmem_cache *cache) {} > static inline void kasan_cache_destroy(struct kmem_cache *cache) {} > > +static inline void kasan_init_object(struct kmem_cache *s, void *object) {} > static inline void kasan_poison_slab(struct page *page) {} > static inline void kasan_unpoison_object_data(struct kmem_cache *cache, > void *object) {} > @@ -110,7 +112,8 @@ static inline void kasan_krealloc(const void *object, size_t new_size, > > static inline void kasan_slab_alloc(struct kmem_cache *s, void *object, > gfp_t flags) {} > -static inline bool kasan_slab_free(struct kmem_cache *s, void *object) > +static inline bool kasan_slab_free(struct kmem_cache *s, void *object, > + unsigned long caller) > { > return false; > } > diff --git a/mm/kasan/kasan.c b/mm/kasan/kasan.c > index 28439ac..daec64b 100644 > --- a/mm/kasan/kasan.c > +++ b/mm/kasan/kasan.c > @@ -402,6 +402,23 @@ void kasan_cache_create(struct kmem_cache *cache, size_t *size, > cache->object_size + > optimal_redzone(cache->object_size))); > } > + > +static inline union kasan_shadow_meta *get_shadow_meta( > + struct kasan_alloc_meta *allocp) > +{ > + return (union kasan_shadow_meta *)kasan_mem_to_shadow((void *)allocp); > +} > + > +void kasan_init_object(struct kmem_cache *cache, void *object) > +{ > + if (cache->flags & SLAB_KASAN) { > + struct kasan_alloc_meta *allocp = get_alloc_info(cache, object); > + union kasan_shadow_meta *shadow_meta = get_shadow_meta(allocp); > + > + __memset(allocp, 0, sizeof(*allocp)); I think we need initialize the lock first, then lock it in order to touch *allocp. > + shadow_meta->data = KASAN_KMALLOC_META; Shouldn't this be a release store? > + } > +} > #endif > > void kasan_cache_shrink(struct kmem_cache *cache) > @@ -431,13 +448,6 @@ void kasan_poison_object_data(struct kmem_cache *cache, void *object) > kasan_poison_shadow(object, > round_up(cache->object_size, KASAN_SHADOW_SCALE_SIZE), > KASAN_KMALLOC_REDZONE); > -#ifdef CONFIG_SLAB > - if (cache->flags & SLAB_KASAN) { > - struct kasan_alloc_meta *alloc_info = > - get_alloc_info(cache, object); > - alloc_info->state = KASAN_STATE_INIT; > - } > -#endif > } > > #ifdef CONFIG_SLAB > @@ -501,6 +511,53 @@ struct kasan_free_meta *get_free_info(struct kmem_cache *cache, > BUILD_BUG_ON(sizeof(struct kasan_free_meta) > 32); > return (void *)object + cache->kasan_info.free_meta_offset; > } > + > +u8 get_alloc_state(struct kasan_alloc_meta *alloc_info) > +{ > + return get_shadow_meta(alloc_info)->state; > +} > + > +void set_alloc_state(struct kasan_alloc_meta *alloc_info, u8 state) > +{ > + get_shadow_meta(alloc_info)->state = state; > +} > + > +/* > + * Acquire per-object lock before accessing KASAN metadata. Lock bit is stored > + * in header shadow memory to protect it from being flipped by out-of-bounds > + * accesses on object. Standard lock primitives cannot be used since there > + * aren't enough header shadow bytes. > + */ > +void kasan_meta_lock(struct kasan_alloc_meta *alloc_info) > +{ > + union kasan_shadow_meta *shadow_meta = get_shadow_meta(alloc_info); > + union kasan_shadow_meta old, new; > + > + for (;;) { > + old.data = READ_ONCE(shadow_meta->data); > + if (old.lock) { > + cpu_relax(); > + continue; > + } > + new.data = old.data; > + new.lock = 1; > + preempt_disable(); > + if (cmpxchg(&shadow_meta->data, old.data, new.data) == old.data) > + break; > + preempt_enable(); > + } > +} > + > +/* Release lock after a kasan_meta_lock(). */ > +void kasan_meta_unlock(struct kasan_alloc_meta *alloc_info) > +{ > + union kasan_shadow_meta *shadow_meta = get_shadow_meta(alloc_info), new; > + > + new.data = READ_ONCE(shadow_meta->data); > + new.lock = 0; > + smp_store_release(&shadow_meta->data, new.data); > + preempt_enable(); > +} > #endif > > void kasan_slab_alloc(struct kmem_cache *cache, void *object, gfp_t flags) > @@ -520,35 +577,44 @@ void kasan_poison_slab_free(struct kmem_cache *cache, void *object) > kasan_poison_shadow(object, rounded_up_size, KASAN_KMALLOC_FREE); > } > > -bool kasan_slab_free(struct kmem_cache *cache, void *object) > +bool kasan_slab_free(struct kmem_cache *cache, void *object, > + unsigned long caller) > { > #ifdef CONFIG_SLAB > + struct kasan_alloc_meta *alloc_info; > + struct kasan_free_meta *free_info; > + u8 alloc_state; > + > /* RCU slabs could be legally used after free within the RCU period */ > if (unlikely(cache->flags & SLAB_DESTROY_BY_RCU)) > return false; > > - if (likely(cache->flags & SLAB_KASAN)) { > - struct kasan_alloc_meta *alloc_info = > - get_alloc_info(cache, object); > - struct kasan_free_meta *free_info = > - get_free_info(cache, object); > - > - switch (alloc_info->state) { > - case KASAN_STATE_ALLOC: > - alloc_info->state = KASAN_STATE_QUARANTINE; > - quarantine_put(free_info, cache); > - set_track(&free_info->track, GFP_NOWAIT); > - kasan_poison_slab_free(cache, object); > - return true; > + if (unlikely(!(cache->flags & SLAB_KASAN))) > + return false; > + > + alloc_info = get_alloc_info(cache, object); > + kasan_meta_lock(alloc_info); > + alloc_state = get_alloc_state(alloc_info); > + if (alloc_state == KASAN_STATE_ALLOC) { > + free_info = get_free_info(cache, object); > + quarantine_put(free_info, cache); > + set_track(&free_info->track, GFP_NOWAIT); > + kasan_poison_slab_free(cache, object); > + set_alloc_state(alloc_info, KASAN_STATE_QUARANTINE); > + kasan_meta_unlock(alloc_info); > + return true; > + } > + switch (alloc_state) { > case KASAN_STATE_QUARANTINE: > case KASAN_STATE_FREE: > - pr_err("Double free"); > - dump_stack(); > - break; > + kasan_report((unsigned long)object, 0, false, caller); > + kasan_meta_unlock(alloc_info); > + return true; > default: > + pr_err("invalid allocation state!\n"); I suggest you also print the object pointer here. > break; > - } > } > + kasan_meta_unlock(alloc_info); > return false; > #else > kasan_poison_slab_free(cache, object); > @@ -580,10 +646,15 @@ void kasan_kmalloc(struct kmem_cache *cache, const void *object, size_t size, > if (cache->flags & SLAB_KASAN) { > struct kasan_alloc_meta *alloc_info = > get_alloc_info(cache, object); > + unsigned long flags; > > - alloc_info->state = KASAN_STATE_ALLOC; > + local_irq_save(flags); > + kasan_meta_lock(alloc_info); > + set_alloc_state(alloc_info, KASAN_STATE_ALLOC); > alloc_info->alloc_size = size; > set_track(&alloc_info->track, flags); > + kasan_meta_unlock(alloc_info); > + local_irq_restore(flags); > } > #endif > } > @@ -636,7 +707,7 @@ void kasan_kfree(void *ptr) > kasan_poison_shadow(ptr, PAGE_SIZE << compound_order(page), > KASAN_FREE_PAGE); > else > - kasan_slab_free(page->slab_cache, ptr); > + kasan_slab_free(page->slab_cache, ptr, _RET_IP_); > } > > void kasan_kfree_large(const void *ptr) > diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h > index fb87923..76bdadd 100644 > --- a/mm/kasan/kasan.h > +++ b/mm/kasan/kasan.h > @@ -12,6 +12,8 @@ > #define KASAN_KMALLOC_REDZONE 0xFC /* redzone inside slub object */ > #define KASAN_KMALLOC_FREE 0xFB /* object was freed (kmem_cache_free/kfree) */ > #define KASAN_GLOBAL_REDZONE 0xFA /* redzone for global variable */ > +#define KASAN_KMALLOC_META 0xE0 /* slab object header shadow; see union kasan_shadow_meta */ > +#define KASAN_KMALLOC_META_MAX 0xEF /* end of header shadow code */ > > /* > * Stack redzone shadow values > @@ -73,10 +75,24 @@ struct kasan_track { > depot_stack_handle_t stack; > }; > > +/* > + * Object header lock bit and allocation state are stored in shadow memory to > + * prevent out-of-bounds accesses on object from overwriting them. > + */ > +union kasan_shadow_meta { > + u8 data; > + struct { > + u8 lock : 1; /* lock bit */ > + u8 state : 2; /* enum kasan_state */ > + u8 unused : 1; > + u8 poison : 4; /* poison constant; do not use */ > + }; > +}; > + > struct kasan_alloc_meta { > + u32 alloc_size : 24; Why reduce the alloc size? > + u32 unused : 8; > struct kasan_track track; > - u32 state : 2; /* enum kasan_state */ > - u32 alloc_size : 30; > }; > > struct qlist_node { > @@ -114,6 +130,10 @@ void kasan_report(unsigned long addr, size_t size, > void quarantine_put(struct kasan_free_meta *info, struct kmem_cache *cache); > void quarantine_reduce(void); > void quarantine_remove_cache(struct kmem_cache *cache); > +void kasan_meta_lock(struct kasan_alloc_meta *alloc_info); > +void kasan_meta_unlock(struct kasan_alloc_meta *alloc_info); > +u8 get_alloc_state(struct kasan_alloc_meta *alloc_info); > +void set_alloc_state(struct kasan_alloc_meta *alloc_info, u8 state); > #else > static inline void quarantine_put(struct kasan_free_meta *info, > struct kmem_cache *cache) { } > diff --git a/mm/kasan/quarantine.c b/mm/kasan/quarantine.c > index 4973505..ce9c913 100644 > --- a/mm/kasan/quarantine.c > +++ b/mm/kasan/quarantine.c > @@ -148,7 +148,9 @@ static void qlink_free(struct qlist_node *qlink, struct kmem_cache *cache) > unsigned long flags; > > local_irq_save(flags); > - alloc_info->state = KASAN_STATE_FREE; > + kasan_meta_lock(alloc_info); > + set_alloc_state(alloc_info, KASAN_STATE_FREE); > + kasan_meta_unlock(alloc_info); > ___cache_free(cache, object, _THIS_IP_); > local_irq_restore(flags); > } > diff --git a/mm/kasan/report.c b/mm/kasan/report.c > index b3c122d..48c9c60 100644 > --- a/mm/kasan/report.c > +++ b/mm/kasan/report.c > @@ -53,6 +53,17 @@ static void print_error_description(struct kasan_access_info *info) > const char *bug_type = "unknown-crash"; > u8 *shadow_addr; > > +#ifdef CONFIG_SLAB > + if (!info->access_size) { > + bug_type = "double-free"; > + pr_err("BUG: KASAN: %s attempt in %pS on object at addr %p\n", > + bug_type, (void *)info->ip, info->access_addr); > + pr_err("%s by task %s/%d\n", bug_type, current->comm, > + task_pid_nr(current)); > + info->first_bad_addr = info->access_addr; > + return; > + } > +#endif > info->first_bad_addr = find_first_bad_addr(info->access_addr, > info->access_size); > > @@ -75,6 +86,7 @@ static void print_error_description(struct kasan_access_info *info) > break; > case KASAN_PAGE_REDZONE: > case KASAN_KMALLOC_REDZONE: > + case KASAN_KMALLOC_META ... KASAN_KMALLOC_META_MAX: > bug_type = "slab-out-of-bounds"; > break; > case KASAN_GLOBAL_REDZONE: > @@ -131,7 +143,7 @@ static void print_track(struct kasan_track *track) > } > > static void object_err(struct kmem_cache *cache, struct page *page, > - void *object, char *unused_reason) > + void *object, struct kasan_access_info *info) > { > struct kasan_alloc_meta *alloc_info = get_alloc_info(cache, object); > struct kasan_free_meta *free_info; > @@ -140,7 +152,9 @@ static void object_err(struct kmem_cache *cache, struct page *page, > pr_err("Object at %p, in cache %s\n", object, cache->name); > if (!(cache->flags & SLAB_KASAN)) > return; > - switch (alloc_info->state) { > + if (info->access_size) > + kasan_meta_lock(alloc_info); In which case can info->access_size be zero? Guess even in that case we need to lock the metadata. > + switch (get_alloc_state(alloc_info)) { > case KASAN_STATE_INIT: > pr_err("Object not allocated yet\n"); > break; > @@ -161,6 +175,8 @@ static void object_err(struct kmem_cache *cache, struct page *page, > print_track(&free_info->track); > break; > } > + if (info->access_size) > + kasan_meta_unlock(alloc_info); > } > #endif > > @@ -177,8 +193,12 @@ static void print_address_description(struct kasan_access_info *info) > struct kmem_cache *cache = page->slab_cache; > object = nearest_obj(cache, page, > (void *)info->access_addr); > +#ifdef CONFIG_SLAB > + object_err(cache, page, object, info); > +#else > object_err(cache, page, object, > "kasan: bad access detected"); > +#endif > return; > } > dump_page(page, "kasan: bad access detected"); > diff --git a/mm/slab.c b/mm/slab.c > index 763096a..b8c51a6 100644 > --- a/mm/slab.c > +++ b/mm/slab.c > @@ -2611,6 +2611,7 @@ static void cache_init_objs(struct kmem_cache *cachep, > cachep->ctor(objp); > kasan_poison_object_data(cachep, objp); > } > + kasan_init_object(cachep, index_to_obj(cachep, page, i)); > > if (!shuffled) > set_free_obj(page, i, i); > @@ -3508,7 +3509,7 @@ static inline void __cache_free(struct kmem_cache *cachep, void *objp, > unsigned long caller) > { > /* Put the object into the quarantine, don't touch it for now. */ > - if (kasan_slab_free(cachep, objp)) > + if (kasan_slab_free(cachep, objp, _RET_IP_)) > return; > > ___cache_free(cachep, objp, caller); > diff --git a/mm/slub.c b/mm/slub.c > index 5beeeb2..f25c0c2 100644 > --- a/mm/slub.c > +++ b/mm/slub.c > @@ -1344,7 +1344,7 @@ static inline void slab_free_hook(struct kmem_cache *s, void *x) > if (!(s->flags & SLAB_DEBUG_OBJECTS)) > debug_check_no_obj_freed(x, s->object_size); > > - kasan_slab_free(s, x); > + kasan_slab_free(s, x, _RET_IP_); > } > > static inline void slab_free_freelist_hook(struct kmem_cache *s, > -- > 1.7.1 > -- Alexander Potapenko Software Engineer Google Germany GmbH Erika-Mann-Straße, 33 80636 München Geschäftsführer: Matthew Scott Sucherman, Paul Terence Manicle Registergericht und -nummer: Hamburg, HRB 86891 Sitz der Gesellschaft: Hamburg ^ permalink raw reply [flat|nested] 10+ messages in thread
* RE: [PATCH v5 1/2] mm, kasan: improve double-free detection 2016-06-09 13:32 ` Alexander Potapenko @ 2016-06-09 16:54 ` Luruo, Kuthonuzo 0 siblings, 0 replies; 10+ messages in thread From: Luruo, Kuthonuzo @ 2016-06-09 16:54 UTC (permalink / raw) To: Alexander Potapenko Cc: Andrey Ryabinin, Dmitriy Vyukov, Christoph Lameter, penberg, David Rientjes, Joonsoo Kim, Andrew Morton, kasan-dev, LKML, ynorov > > Currently, KASAN may fail to detect concurrent deallocations of the same > > object due to a race in kasan_slab_free(). This patch makes double-free > > detection more reliable by serializing access to KASAN object metadata. > > New functions kasan_meta_lock() and kasan_meta_unlock() are provided to > > lock/unlock per-object metadata. Double-free errors are now reported via > > kasan_report(). > > > > Per-object lock concept from suggestion/observations by Dmitry Vyukov. > Note I've sent out a patch that enables stackdepot support in SLUB. > I'll probably need to wait till you patch lands and add locking to SLUB as well. My patch can wait; It can be rebased and resent for consideration by maintainers/reviewers after your patch has been reviewed. > > +void kasan_init_object(struct kmem_cache *cache, void *object) > > +{ > > + if (cache->flags & SLAB_KASAN) { > > + struct kasan_alloc_meta *allocp = get_alloc_info(cache, object); > > + union kasan_shadow_meta *shadow_meta = > get_shadow_meta(allocp); > > + > > + __memset(allocp, 0, sizeof(*allocp)); > I think we need initialize the lock first, then lock it in order to > touch *allocp. > > + shadow_meta->data = KASAN_KMALLOC_META; > Shouldn't this be a release store? Object at this point is being initialized by SLAB off a newly allocated page/slab. Concurrent access to the embryonic object is unlikely/not expected. I don't think locking here is of any benefit... > > default: > > + pr_err("invalid allocation state!\n"); > I suggest you also print the object pointer here. ok. > > + > > struct kasan_alloc_meta { > > + u32 alloc_size : 24; > Why reduce the alloc size? Thought to save some bits while still accounting for max object size instrumented by KASAN. But now, with the spectre of OOB writes, header real estate suddenly seems a lot less valuable ;-). A reset to u32 won’t be inappropriate. > > if (!(cache->flags & SLAB_KASAN)) > > return; > > - switch (alloc_info->state) { > > + if (info->access_size) > > + kasan_meta_lock(alloc_info); > In which case can info->access_size be zero? Guess even in that case > we need to lock the metadata. For a double-free error, access size is zero and lock is already held. Thank you very much for reviewing the patch! Kuthonuzo ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v5 1/2] mm, kasan: improve double-free detection 2016-06-07 18:03 [PATCH v5 1/2] mm, kasan: improve double-free detection Kuthonuzo Luruo 2016-06-09 13:32 ` Alexander Potapenko @ 2016-06-09 17:00 ` Andrey Ryabinin 2016-06-10 17:03 ` Andrey Ryabinin 1 sibling, 1 reply; 10+ messages in thread From: Andrey Ryabinin @ 2016-06-09 17:00 UTC (permalink / raw) To: Kuthonuzo Luruo, glider, dvyukov, cl, penberg, rientjes, iamjoonsoo.kim, akpm Cc: kasan-dev, linux-kernel, ynorov On 06/07/2016 09:03 PM, Kuthonuzo Luruo wrote: Next time, when/if you send patch series, send patches in one thread, i.e. patches should be replies to the cover letter. Your patches are not linked together, which makes them harder to track. > Currently, KASAN may fail to detect concurrent deallocations of the same > object due to a race in kasan_slab_free(). This patch makes double-free > detection more reliable by serializing access to KASAN object metadata. > New functions kasan_meta_lock() and kasan_meta_unlock() are provided to > lock/unlock per-object metadata. Double-free errors are now reported via > kasan_report(). > > Per-object lock concept from suggestion/observations by Dmitry Vyukov. > So, I still don't like this, this too way hacky and complex. I have some thoughts about how to make this lockless and robust enough. I'll try to sort this out tomorrow. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v5 1/2] mm, kasan: improve double-free detection 2016-06-09 17:00 ` Andrey Ryabinin @ 2016-06-10 17:03 ` Andrey Ryabinin 2016-06-10 17:09 ` Dmitry Vyukov ` (2 more replies) 0 siblings, 3 replies; 10+ messages in thread From: Andrey Ryabinin @ 2016-06-10 17:03 UTC (permalink / raw) To: Kuthonuzo Luruo, glider, dvyukov, cl, penberg, rientjes, iamjoonsoo.kim, akpm Cc: kasan-dev, linux-kernel, ynorov On 06/09/2016 08:00 PM, Andrey Ryabinin wrote: > On 06/07/2016 09:03 PM, Kuthonuzo Luruo wrote: > > Next time, when/if you send patch series, send patches in one thread, i.e. patches should be replies to the cover letter. > Your patches are not linked together, which makes them harder to track. > > >> Currently, KASAN may fail to detect concurrent deallocations of the same >> object due to a race in kasan_slab_free(). This patch makes double-free >> detection more reliable by serializing access to KASAN object metadata. >> New functions kasan_meta_lock() and kasan_meta_unlock() are provided to >> lock/unlock per-object metadata. Double-free errors are now reported via >> kasan_report(). >> >> Per-object lock concept from suggestion/observations by Dmitry Vyukov. >> > > > So, I still don't like this, this too way hacky and complex. > I have some thoughts about how to make this lockless and robust enough. > I'll try to sort this out tomorrow. > So, I something like this should work. Tested very briefly. diff --git a/include/linux/kasan.h b/include/linux/kasan.h index ac4b3c4..8691142 100644 --- a/include/linux/kasan.h +++ b/include/linux/kasan.h @@ -75,6 +75,8 @@ struct kasan_cache { int kasan_module_alloc(void *addr, size_t size); void kasan_free_shadow(const struct vm_struct *vm); +void kasan_init_slab_obj(struct kmem_cache *cache, const void *object); + size_t ksize(const void *); static inline void kasan_unpoison_slab(const void *ptr) { ksize(ptr); } @@ -102,6 +104,9 @@ static inline void kasan_unpoison_object_data(struct kmem_cache *cache, static inline void kasan_poison_object_data(struct kmem_cache *cache, void *object) {} +static inline void kasan_init_slab_obj(struct kmem_cache *cache, + const void *object) { } + static inline void kasan_kmalloc_large(void *ptr, size_t size, gfp_t flags) {} static inline void kasan_kfree_large(const void *ptr) {} static inline void kasan_poison_kfree(void *ptr) {} diff --git a/mm/kasan/kasan.c b/mm/kasan/kasan.c index 6845f92..ab0fded 100644 --- a/mm/kasan/kasan.c +++ b/mm/kasan/kasan.c @@ -388,11 +388,9 @@ void kasan_cache_create(struct kmem_cache *cache, size_t *size, *size += sizeof(struct kasan_alloc_meta); /* Add free meta. */ - if (cache->flags & SLAB_DESTROY_BY_RCU || cache->ctor || - cache->object_size < sizeof(struct kasan_free_meta)) { - cache->kasan_info.free_meta_offset = *size; - *size += sizeof(struct kasan_free_meta); - } + cache->kasan_info.free_meta_offset = *size; + *size += sizeof(struct kasan_free_meta); + redzone_adjust = optimal_redzone(cache->object_size) - (*size - cache->object_size); if (redzone_adjust > 0) @@ -431,13 +429,6 @@ void kasan_poison_object_data(struct kmem_cache *cache, void *object) kasan_poison_shadow(object, round_up(cache->object_size, KASAN_SHADOW_SCALE_SIZE), KASAN_KMALLOC_REDZONE); -#ifdef CONFIG_SLAB - if (cache->flags & SLAB_KASAN) { - struct kasan_alloc_meta *alloc_info = - get_alloc_info(cache, object); - alloc_info->state = KASAN_STATE_INIT; - } -#endif } #ifdef CONFIG_SLAB @@ -501,6 +492,20 @@ struct kasan_free_meta *get_free_info(struct kmem_cache *cache, BUILD_BUG_ON(sizeof(struct kasan_free_meta) > 32); return (void *)object + cache->kasan_info.free_meta_offset; } + +void kasan_init_slab_obj(struct kmem_cache *cache, const void *object) +{ + struct kasan_alloc_meta *alloc_info; + struct kasan_free_meta *free_info; + + if (!(cache->flags & SLAB_KASAN)) + return; + + alloc_info = get_alloc_info(cache, object); + free_info = get_free_info(cache, object); + __memset(alloc_info, 0, sizeof(*alloc_info)); + __memset(free_info, 0, sizeof(*free_info)); +} #endif void kasan_slab_alloc(struct kmem_cache *cache, void *object, gfp_t flags) @@ -523,37 +528,47 @@ static void kasan_poison_slab_free(struct kmem_cache *cache, void *object) bool kasan_slab_free(struct kmem_cache *cache, void *object) { #ifdef CONFIG_SLAB + struct kasan_free_meta *free_info = get_free_info(cache, object); + struct kasan_track new_free_stack, old_free_stack; + s8 old_shadow; + /* RCU slabs could be legally used after free within the RCU period */ if (unlikely(cache->flags & SLAB_DESTROY_BY_RCU)) return false; - if (likely(cache->flags & SLAB_KASAN)) { - struct kasan_alloc_meta *alloc_info = - get_alloc_info(cache, object); - struct kasan_free_meta *free_info = - get_free_info(cache, object); - - switch (alloc_info->state) { - case KASAN_STATE_ALLOC: - alloc_info->state = KASAN_STATE_QUARANTINE; - quarantine_put(free_info, cache); - set_track(&free_info->track, GFP_NOWAIT); - kasan_poison_slab_free(cache, object); - return true; - case KASAN_STATE_QUARANTINE: - case KASAN_STATE_FREE: - pr_err("Double free"); - dump_stack(); - break; - default: - break; - } + if (unlikely(!(cache->flags & SLAB_KASAN))) + return false; + + set_track(&new_free_stack, GFP_NOWAIT); + old_free_stack = xchg(&free_info->track, new_free_stack); + old_shadow = xchg((s8 *)kasan_mem_to_shadow(object), + KASAN_KMALLOC_FREE); + + if (old_shadow < 0 || old_shadow >= KASAN_SHADOW_SCALE_SIZE) { + struct kasan_track free_stack; + + /* Paired with xchg() above */ + free_stack = smp_load_acquire(&free_info->track); + + /* + * We didn't raced with another instance of kasan_slab_free() + * so the previous free stack supposed to be in old_free_stack. + * Otherwise, free_stack will contain stack trace of another + * kfree() call. + */ + if (free_stack.id == new_free_stack.id) + free_stack = old_free_stack; + + kasan_report_double_free(cache, object, + free_stack, old_shadow); + return false; } - return false; -#else kasan_poison_slab_free(cache, object); - return false; + return true; + #endif + kasan_poison_slab_free(cache, object); + return false; } void kasan_kmalloc(struct kmem_cache *cache, const void *object, size_t size, @@ -581,7 +596,6 @@ void kasan_kmalloc(struct kmem_cache *cache, const void *object, size_t size, struct kasan_alloc_meta *alloc_info = get_alloc_info(cache, object); - alloc_info->state = KASAN_STATE_ALLOC; alloc_info->alloc_size = size; set_track(&alloc_info->track, flags); } diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h index fb87923..9b46d2e 100644 --- a/mm/kasan/kasan.h +++ b/mm/kasan/kasan.h @@ -59,24 +59,21 @@ struct kasan_global { * Structures to keep alloc and free tracks * */ -enum kasan_state { - KASAN_STATE_INIT, - KASAN_STATE_ALLOC, - KASAN_STATE_QUARANTINE, - KASAN_STATE_FREE -}; - #define KASAN_STACK_DEPTH 64 struct kasan_track { - u32 pid; - depot_stack_handle_t stack; +union { + struct { + u32 pid; + depot_stack_handle_t stack; + }; + u64 id; +}; }; struct kasan_alloc_meta { struct kasan_track track; - u32 state : 2; /* enum kasan_state */ - u32 alloc_size : 30; + u32 alloc_size; }; struct qlist_node { @@ -109,6 +106,9 @@ static inline bool kasan_report_enabled(void) void kasan_report(unsigned long addr, size_t size, bool is_write, unsigned long ip); +void kasan_report_double_free(struct kmem_cache *cache, void *object, + struct kasan_track free_stack, s8 shadow); + #ifdef CONFIG_SLAB void quarantine_put(struct kasan_free_meta *info, struct kmem_cache *cache); diff --git a/mm/kasan/quarantine.c b/mm/kasan/quarantine.c index 4973505..3ec039c 100644 --- a/mm/kasan/quarantine.c +++ b/mm/kasan/quarantine.c @@ -144,11 +144,9 @@ static void *qlink_to_object(struct qlist_node *qlink, struct kmem_cache *cache) static void qlink_free(struct qlist_node *qlink, struct kmem_cache *cache) { void *object = qlink_to_object(qlink, cache); - struct kasan_alloc_meta *alloc_info = get_alloc_info(cache, object); unsigned long flags; local_irq_save(flags); - alloc_info->state = KASAN_STATE_FREE; ___cache_free(cache, object, _THIS_IP_); local_irq_restore(flags); } diff --git a/mm/kasan/report.c b/mm/kasan/report.c index b3c122d..a0f4519 100644 --- a/mm/kasan/report.c +++ b/mm/kasan/report.c @@ -140,28 +140,13 @@ static void object_err(struct kmem_cache *cache, struct page *page, pr_err("Object at %p, in cache %s\n", object, cache->name); if (!(cache->flags & SLAB_KASAN)) return; - switch (alloc_info->state) { - case KASAN_STATE_INIT: - pr_err("Object not allocated yet\n"); - break; - case KASAN_STATE_ALLOC: - pr_err("Object allocated with size %u bytes.\n", - alloc_info->alloc_size); - pr_err("Allocation:\n"); - print_track(&alloc_info->track); - break; - case KASAN_STATE_FREE: - case KASAN_STATE_QUARANTINE: - pr_err("Object freed, allocated with size %u bytes\n", - alloc_info->alloc_size); - free_info = get_free_info(cache, object); - pr_err("Allocation:\n"); - print_track(&alloc_info->track); - pr_err("Deallocation:\n"); - print_track(&free_info->track); - break; - } + free_info = get_free_info(cache, object); + pr_err("Allocation:\n"); + print_track(&alloc_info->track); + pr_err("Deallocation:\n"); + print_track(&free_info->track); } + #endif static void print_address_description(struct kasan_access_info *info) @@ -245,17 +230,31 @@ static void print_shadow_for_address(const void *addr) static DEFINE_SPINLOCK(report_lock); -static void kasan_report_error(struct kasan_access_info *info) +static void kasan_start_report(unsigned long *flags) { - unsigned long flags; - const char *bug_type; - /* * Make sure we don't end up in loop. */ kasan_disable_current(); - spin_lock_irqsave(&report_lock, flags); + spin_lock_irqsave(&report_lock, *flags); pr_err("==================================================================\n"); +} + +static void kasan_end_report(unsigned long *flags) +{ + pr_err("==================================================================\n"); + add_taint(TAINT_BAD_PAGE, LOCKDEP_NOW_UNRELIABLE); + spin_unlock_irqrestore(&report_lock, *flags); + kasan_enable_current(); +} + +static void kasan_report_error(struct kasan_access_info *info) +{ + unsigned long flags; + const char *bug_type; + + kasan_start_report(&flags); + if (info->access_addr < kasan_shadow_to_mem((void *)KASAN_SHADOW_START)) { if ((unsigned long)info->access_addr < PAGE_SIZE) @@ -276,10 +275,29 @@ static void kasan_report_error(struct kasan_access_info *info) print_address_description(info); print_shadow_for_address(info->first_bad_addr); } - pr_err("==================================================================\n"); - add_taint(TAINT_BAD_PAGE, LOCKDEP_NOW_UNRELIABLE); - spin_unlock_irqrestore(&report_lock, flags); - kasan_enable_current(); + + kasan_end_report(&flags); +} + +void kasan_report_double_free(struct kmem_cache *cache, void *object, + struct kasan_track free_stack, s8 shadow) +{ + unsigned long flags; + + kasan_start_report(&flags); + + pr_err("BUG: Double free or corrupt pointer\n"); + pr_err("Unexpected shadow byte: 0x%hhX\n", shadow); + + dump_stack(); + pr_err("Object at %p, in cache %s\n", object, cache->name); + get_alloc_info(cache, object); + pr_err("Allocation:\n"); + print_track(&get_alloc_info(cache, object)->track); + pr_err("Deallocation:\n"); + print_track(&free_stack); + + kasan_end_report(&flags); } void kasan_report(unsigned long addr, size_t size, diff --git a/mm/slab.c b/mm/slab.c index 763096a..65c942b 100644 --- a/mm/slab.c +++ b/mm/slab.c @@ -2604,9 +2604,11 @@ static void cache_init_objs(struct kmem_cache *cachep, } for (i = 0; i < cachep->num; i++) { + objp = index_to_obj(cachep, page, i); + kasan_init_slab_obj(cachep, objp); + /* constructor could break poison info */ if (DEBUG == 0 && cachep->ctor) { - objp = index_to_obj(cachep, page, i); kasan_unpoison_object_data(cachep, objp); cachep->ctor(objp); kasan_poison_object_data(cachep, objp); ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v5 1/2] mm, kasan: improve double-free detection 2016-06-10 17:03 ` Andrey Ryabinin @ 2016-06-10 17:09 ` Dmitry Vyukov 2016-06-15 16:18 ` Andrey Ryabinin 2016-06-13 11:56 ` Alexander Potapenko 2016-06-14 6:46 ` Luruo, Kuthonuzo 2 siblings, 1 reply; 10+ messages in thread From: Dmitry Vyukov @ 2016-06-10 17:09 UTC (permalink / raw) To: Andrey Ryabinin Cc: Kuthonuzo Luruo, Alexander Potapenko, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, kasan-dev, LKML, Yury Norov On Fri, Jun 10, 2016 at 7:03 PM, Andrey Ryabinin <aryabinin@virtuozzo.com> wrote: > > > On 06/09/2016 08:00 PM, Andrey Ryabinin wrote: >> On 06/07/2016 09:03 PM, Kuthonuzo Luruo wrote: >> >> Next time, when/if you send patch series, send patches in one thread, i.e. patches should be replies to the cover letter. >> Your patches are not linked together, which makes them harder to track. >> >> >>> Currently, KASAN may fail to detect concurrent deallocations of the same >>> object due to a race in kasan_slab_free(). This patch makes double-free >>> detection more reliable by serializing access to KASAN object metadata. >>> New functions kasan_meta_lock() and kasan_meta_unlock() are provided to >>> lock/unlock per-object metadata. Double-free errors are now reported via >>> kasan_report(). >>> >>> Per-object lock concept from suggestion/observations by Dmitry Vyukov. >>> >> >> >> So, I still don't like this, this too way hacky and complex. >> I have some thoughts about how to make this lockless and robust enough. >> I'll try to sort this out tomorrow. >> > > > So, I something like this should work. > Tested very briefly. > > diff --git a/include/linux/kasan.h b/include/linux/kasan.h > index ac4b3c4..8691142 100644 > --- a/include/linux/kasan.h > +++ b/include/linux/kasan.h > @@ -75,6 +75,8 @@ struct kasan_cache { > int kasan_module_alloc(void *addr, size_t size); > void kasan_free_shadow(const struct vm_struct *vm); > > +void kasan_init_slab_obj(struct kmem_cache *cache, const void *object); > + > size_t ksize(const void *); > static inline void kasan_unpoison_slab(const void *ptr) { ksize(ptr); } > > @@ -102,6 +104,9 @@ static inline void kasan_unpoison_object_data(struct kmem_cache *cache, > static inline void kasan_poison_object_data(struct kmem_cache *cache, > void *object) {} > > +static inline void kasan_init_slab_obj(struct kmem_cache *cache, > + const void *object) { } > + > static inline void kasan_kmalloc_large(void *ptr, size_t size, gfp_t flags) {} > static inline void kasan_kfree_large(const void *ptr) {} > static inline void kasan_poison_kfree(void *ptr) {} > diff --git a/mm/kasan/kasan.c b/mm/kasan/kasan.c > index 6845f92..ab0fded 100644 > --- a/mm/kasan/kasan.c > +++ b/mm/kasan/kasan.c > @@ -388,11 +388,9 @@ void kasan_cache_create(struct kmem_cache *cache, size_t *size, > *size += sizeof(struct kasan_alloc_meta); > > /* Add free meta. */ > - if (cache->flags & SLAB_DESTROY_BY_RCU || cache->ctor || > - cache->object_size < sizeof(struct kasan_free_meta)) { > - cache->kasan_info.free_meta_offset = *size; > - *size += sizeof(struct kasan_free_meta); > - } > + cache->kasan_info.free_meta_offset = *size; > + *size += sizeof(struct kasan_free_meta); > + Why?! Please don't worsen runtime characteristics of KASAN. We run real systems with it. Most objects are small. This can lead to significant memory consumption. > redzone_adjust = optimal_redzone(cache->object_size) - > (*size - cache->object_size); > if (redzone_adjust > 0) > @@ -431,13 +429,6 @@ void kasan_poison_object_data(struct kmem_cache *cache, void *object) > kasan_poison_shadow(object, > round_up(cache->object_size, KASAN_SHADOW_SCALE_SIZE), > KASAN_KMALLOC_REDZONE); > -#ifdef CONFIG_SLAB > - if (cache->flags & SLAB_KASAN) { > - struct kasan_alloc_meta *alloc_info = > - get_alloc_info(cache, object); > - alloc_info->state = KASAN_STATE_INIT; > - } > -#endif > } > > #ifdef CONFIG_SLAB > @@ -501,6 +492,20 @@ struct kasan_free_meta *get_free_info(struct kmem_cache *cache, > BUILD_BUG_ON(sizeof(struct kasan_free_meta) > 32); > return (void *)object + cache->kasan_info.free_meta_offset; > } > + > +void kasan_init_slab_obj(struct kmem_cache *cache, const void *object) > +{ > + struct kasan_alloc_meta *alloc_info; > + struct kasan_free_meta *free_info; > + > + if (!(cache->flags & SLAB_KASAN)) > + return; > + > + alloc_info = get_alloc_info(cache, object); > + free_info = get_free_info(cache, object); > + __memset(alloc_info, 0, sizeof(*alloc_info)); > + __memset(free_info, 0, sizeof(*free_info)); > +} > #endif > > void kasan_slab_alloc(struct kmem_cache *cache, void *object, gfp_t flags) > @@ -523,37 +528,47 @@ static void kasan_poison_slab_free(struct kmem_cache *cache, void *object) > bool kasan_slab_free(struct kmem_cache *cache, void *object) > { > #ifdef CONFIG_SLAB > + struct kasan_free_meta *free_info = get_free_info(cache, object); > + struct kasan_track new_free_stack, old_free_stack; > + s8 old_shadow; > + > /* RCU slabs could be legally used after free within the RCU period */ > if (unlikely(cache->flags & SLAB_DESTROY_BY_RCU)) > return false; > > - if (likely(cache->flags & SLAB_KASAN)) { > - struct kasan_alloc_meta *alloc_info = > - get_alloc_info(cache, object); > - struct kasan_free_meta *free_info = > - get_free_info(cache, object); > - > - switch (alloc_info->state) { > - case KASAN_STATE_ALLOC: > - alloc_info->state = KASAN_STATE_QUARANTINE; > - quarantine_put(free_info, cache); > - set_track(&free_info->track, GFP_NOWAIT); > - kasan_poison_slab_free(cache, object); > - return true; > - case KASAN_STATE_QUARANTINE: > - case KASAN_STATE_FREE: > - pr_err("Double free"); > - dump_stack(); > - break; > - default: > - break; > - } > + if (unlikely(!(cache->flags & SLAB_KASAN))) > + return false; > + > + set_track(&new_free_stack, GFP_NOWAIT); > + old_free_stack = xchg(&free_info->track, new_free_stack); > + old_shadow = xchg((s8 *)kasan_mem_to_shadow(object), > + KASAN_KMALLOC_FREE); > + > + if (old_shadow < 0 || old_shadow >= KASAN_SHADOW_SCALE_SIZE) { > + struct kasan_track free_stack; > + > + /* Paired with xchg() above */ > + free_stack = smp_load_acquire(&free_info->track); > + > + /* > + * We didn't raced with another instance of kasan_slab_free() > + * so the previous free stack supposed to be in old_free_stack. > + * Otherwise, free_stack will contain stack trace of another > + * kfree() call. > + */ > + if (free_stack.id == new_free_stack.id) > + free_stack = old_free_stack; > + > + kasan_report_double_free(cache, object, > + free_stack, old_shadow); > + return false; > } > - return false; > -#else > kasan_poison_slab_free(cache, object); > - return false; > + return true; > + > #endif > + kasan_poison_slab_free(cache, object); > + return false; > } > > void kasan_kmalloc(struct kmem_cache *cache, const void *object, size_t size, > @@ -581,7 +596,6 @@ void kasan_kmalloc(struct kmem_cache *cache, const void *object, size_t size, > struct kasan_alloc_meta *alloc_info = > get_alloc_info(cache, object); > > - alloc_info->state = KASAN_STATE_ALLOC; > alloc_info->alloc_size = size; > set_track(&alloc_info->track, flags); > } > diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h > index fb87923..9b46d2e 100644 > --- a/mm/kasan/kasan.h > +++ b/mm/kasan/kasan.h > @@ -59,24 +59,21 @@ struct kasan_global { > * Structures to keep alloc and free tracks * > */ > > -enum kasan_state { > - KASAN_STATE_INIT, > - KASAN_STATE_ALLOC, > - KASAN_STATE_QUARANTINE, > - KASAN_STATE_FREE > -}; > - > #define KASAN_STACK_DEPTH 64 > > struct kasan_track { > - u32 pid; > - depot_stack_handle_t stack; > +union { > + struct { > + u32 pid; > + depot_stack_handle_t stack; > + }; > + u64 id; > +}; > }; > > struct kasan_alloc_meta { > struct kasan_track track; > - u32 state : 2; /* enum kasan_state */ > - u32 alloc_size : 30; > + u32 alloc_size; > }; > > struct qlist_node { > @@ -109,6 +106,9 @@ static inline bool kasan_report_enabled(void) > > void kasan_report(unsigned long addr, size_t size, > bool is_write, unsigned long ip); > +void kasan_report_double_free(struct kmem_cache *cache, void *object, > + struct kasan_track free_stack, s8 shadow); > + > > #ifdef CONFIG_SLAB > void quarantine_put(struct kasan_free_meta *info, struct kmem_cache *cache); > diff --git a/mm/kasan/quarantine.c b/mm/kasan/quarantine.c > index 4973505..3ec039c 100644 > --- a/mm/kasan/quarantine.c > +++ b/mm/kasan/quarantine.c > @@ -144,11 +144,9 @@ static void *qlink_to_object(struct qlist_node *qlink, struct kmem_cache *cache) > static void qlink_free(struct qlist_node *qlink, struct kmem_cache *cache) > { > void *object = qlink_to_object(qlink, cache); > - struct kasan_alloc_meta *alloc_info = get_alloc_info(cache, object); > unsigned long flags; > > local_irq_save(flags); > - alloc_info->state = KASAN_STATE_FREE; > ___cache_free(cache, object, _THIS_IP_); > local_irq_restore(flags); > } > diff --git a/mm/kasan/report.c b/mm/kasan/report.c > index b3c122d..a0f4519 100644 > --- a/mm/kasan/report.c > +++ b/mm/kasan/report.c > @@ -140,28 +140,13 @@ static void object_err(struct kmem_cache *cache, struct page *page, > pr_err("Object at %p, in cache %s\n", object, cache->name); > if (!(cache->flags & SLAB_KASAN)) > return; > - switch (alloc_info->state) { > - case KASAN_STATE_INIT: > - pr_err("Object not allocated yet\n"); > - break; > - case KASAN_STATE_ALLOC: > - pr_err("Object allocated with size %u bytes.\n", > - alloc_info->alloc_size); > - pr_err("Allocation:\n"); > - print_track(&alloc_info->track); > - break; > - case KASAN_STATE_FREE: > - case KASAN_STATE_QUARANTINE: > - pr_err("Object freed, allocated with size %u bytes\n", > - alloc_info->alloc_size); > - free_info = get_free_info(cache, object); > - pr_err("Allocation:\n"); > - print_track(&alloc_info->track); > - pr_err("Deallocation:\n"); > - print_track(&free_info->track); > - break; > - } > + free_info = get_free_info(cache, object); > + pr_err("Allocation:\n"); > + print_track(&alloc_info->track); > + pr_err("Deallocation:\n"); > + print_track(&free_info->track); > } > + > #endif > > static void print_address_description(struct kasan_access_info *info) > @@ -245,17 +230,31 @@ static void print_shadow_for_address(const void *addr) > > static DEFINE_SPINLOCK(report_lock); > > -static void kasan_report_error(struct kasan_access_info *info) > +static void kasan_start_report(unsigned long *flags) > { > - unsigned long flags; > - const char *bug_type; > - > /* > * Make sure we don't end up in loop. > */ > kasan_disable_current(); > - spin_lock_irqsave(&report_lock, flags); > + spin_lock_irqsave(&report_lock, *flags); > pr_err("==================================================================\n"); > +} > + > +static void kasan_end_report(unsigned long *flags) > +{ > + pr_err("==================================================================\n"); > + add_taint(TAINT_BAD_PAGE, LOCKDEP_NOW_UNRELIABLE); > + spin_unlock_irqrestore(&report_lock, *flags); > + kasan_enable_current(); > +} > + > +static void kasan_report_error(struct kasan_access_info *info) > +{ > + unsigned long flags; > + const char *bug_type; > + > + kasan_start_report(&flags); > + > if (info->access_addr < > kasan_shadow_to_mem((void *)KASAN_SHADOW_START)) { > if ((unsigned long)info->access_addr < PAGE_SIZE) > @@ -276,10 +275,29 @@ static void kasan_report_error(struct kasan_access_info *info) > print_address_description(info); > print_shadow_for_address(info->first_bad_addr); > } > - pr_err("==================================================================\n"); > - add_taint(TAINT_BAD_PAGE, LOCKDEP_NOW_UNRELIABLE); > - spin_unlock_irqrestore(&report_lock, flags); > - kasan_enable_current(); > + > + kasan_end_report(&flags); > +} > + > +void kasan_report_double_free(struct kmem_cache *cache, void *object, > + struct kasan_track free_stack, s8 shadow) > +{ > + unsigned long flags; > + > + kasan_start_report(&flags); > + > + pr_err("BUG: Double free or corrupt pointer\n"); > + pr_err("Unexpected shadow byte: 0x%hhX\n", shadow); > + > + dump_stack(); > + pr_err("Object at %p, in cache %s\n", object, cache->name); > + get_alloc_info(cache, object); > + pr_err("Allocation:\n"); > + print_track(&get_alloc_info(cache, object)->track); > + pr_err("Deallocation:\n"); > + print_track(&free_stack); > + > + kasan_end_report(&flags); > } > > void kasan_report(unsigned long addr, size_t size, > diff --git a/mm/slab.c b/mm/slab.c > index 763096a..65c942b 100644 > --- a/mm/slab.c > +++ b/mm/slab.c > @@ -2604,9 +2604,11 @@ static void cache_init_objs(struct kmem_cache *cachep, > } > > for (i = 0; i < cachep->num; i++) { > + objp = index_to_obj(cachep, page, i); > + kasan_init_slab_obj(cachep, objp); > + > /* constructor could break poison info */ > if (DEBUG == 0 && cachep->ctor) { > - objp = index_to_obj(cachep, page, i); > kasan_unpoison_object_data(cachep, objp); > cachep->ctor(objp); > kasan_poison_object_data(cachep, objp); ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v5 1/2] mm, kasan: improve double-free detection 2016-06-10 17:09 ` Dmitry Vyukov @ 2016-06-15 16:18 ` Andrey Ryabinin 0 siblings, 0 replies; 10+ messages in thread From: Andrey Ryabinin @ 2016-06-15 16:18 UTC (permalink / raw) To: Dmitry Vyukov Cc: Kuthonuzo Luruo, Alexander Potapenko, Christoph Lameter, Pekka Enberg, David Rientjes, Joonsoo Kim, Andrew Morton, kasan-dev, LKML, Yury Norov On 06/10/2016 08:09 PM, Dmitry Vyukov wrote: > On Fri, Jun 10, 2016 at 7:03 PM, Andrey Ryabinin > <aryabinin@virtuozzo.com> wrote: >> >> >> On 06/09/2016 08:00 PM, Andrey Ryabinin wrote: >>> On 06/07/2016 09:03 PM, Kuthonuzo Luruo wrote: >>> >>> Next time, when/if you send patch series, send patches in one thread, i.e. patches should be replies to the cover letter. >>> Your patches are not linked together, which makes them harder to track. >>> >>> >>>> Currently, KASAN may fail to detect concurrent deallocations of the same >>>> object due to a race in kasan_slab_free(). This patch makes double-free >>>> detection more reliable by serializing access to KASAN object metadata. >>>> New functions kasan_meta_lock() and kasan_meta_unlock() are provided to >>>> lock/unlock per-object metadata. Double-free errors are now reported via >>>> kasan_report(). >>>> >>>> Per-object lock concept from suggestion/observations by Dmitry Vyukov. >>>> >>> >>> >>> So, I still don't like this, this too way hacky and complex. >>> I have some thoughts about how to make this lockless and robust enough. >>> I'll try to sort this out tomorrow. >>> >> >> >> So, I something like this should work. >> Tested very briefly. >> >> diff --git a/include/linux/kasan.h b/include/linux/kasan.h >> index ac4b3c4..8691142 100644 >> --- a/include/linux/kasan.h >> +++ b/include/linux/kasan.h >> @@ -75,6 +75,8 @@ struct kasan_cache { >> int kasan_module_alloc(void *addr, size_t size); >> void kasan_free_shadow(const struct vm_struct *vm); >> >> +void kasan_init_slab_obj(struct kmem_cache *cache, const void *object); >> + >> size_t ksize(const void *); >> static inline void kasan_unpoison_slab(const void *ptr) { ksize(ptr); } >> >> @@ -102,6 +104,9 @@ static inline void kasan_unpoison_object_data(struct kmem_cache *cache, >> static inline void kasan_poison_object_data(struct kmem_cache *cache, >> void *object) {} >> >> +static inline void kasan_init_slab_obj(struct kmem_cache *cache, >> + const void *object) { } >> + >> static inline void kasan_kmalloc_large(void *ptr, size_t size, gfp_t flags) {} >> static inline void kasan_kfree_large(const void *ptr) {} >> static inline void kasan_poison_kfree(void *ptr) {} >> diff --git a/mm/kasan/kasan.c b/mm/kasan/kasan.c >> index 6845f92..ab0fded 100644 >> --- a/mm/kasan/kasan.c >> +++ b/mm/kasan/kasan.c >> @@ -388,11 +388,9 @@ void kasan_cache_create(struct kmem_cache *cache, size_t *size, >> *size += sizeof(struct kasan_alloc_meta); >> >> /* Add free meta. */ >> - if (cache->flags & SLAB_DESTROY_BY_RCU || cache->ctor || >> - cache->object_size < sizeof(struct kasan_free_meta)) { >> - cache->kasan_info.free_meta_offset = *size; >> - *size += sizeof(struct kasan_free_meta); >> - } >> + cache->kasan_info.free_meta_offset = *size; >> + *size += sizeof(struct kasan_free_meta); >> + > > > Why?! > Please don't worsen runtime characteristics of KASAN. We run real > systems with it. > Most objects are small. This can lead to significant memory consumption. > Yeah, this is a temp hack actually, because I didn't finish this part yet. Basically, I want to make free stack always available (i.e. always save it in redzone), because the is always better to have more information. Also this makes bug report code a bit easier. Of course, increasing memory usage is not what we want, so my plan is to make this: - remove alloc_size, because we already now object size. I mean cache->object_size. For kmalloc()'ed objects object_size is rounded up size, but exact size of allocation usually is not valuable information (Personally, I can't remember it being useful). - Unify allocation stack and free stack and keep them both in redzone. This is exactly 16-bytes, so this won't increase memory usage. So only quarantine pointer may be stored in freed object. Proposed changes will actually decrease memory usage, because 8-byte objects will occupy less space. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v5 1/2] mm, kasan: improve double-free detection 2016-06-10 17:03 ` Andrey Ryabinin 2016-06-10 17:09 ` Dmitry Vyukov @ 2016-06-13 11:56 ` Alexander Potapenko 2016-06-14 6:46 ` Luruo, Kuthonuzo 2 siblings, 0 replies; 10+ messages in thread From: Alexander Potapenko @ 2016-06-13 11:56 UTC (permalink / raw) To: Andrey Ryabinin Cc: Kuthonuzo Luruo, Dmitriy Vyukov, Christoph Lameter, penberg, David Rientjes, Joonsoo Kim, Andrew Morton, kasan-dev, LKML, ynorov On Fri, Jun 10, 2016 at 7:03 PM, Andrey Ryabinin <aryabinin@virtuozzo.com> wrote: > > > On 06/09/2016 08:00 PM, Andrey Ryabinin wrote: >> On 06/07/2016 09:03 PM, Kuthonuzo Luruo wrote: >> >> Next time, when/if you send patch series, send patches in one thread, i.e. patches should be replies to the cover letter. >> Your patches are not linked together, which makes them harder to track. >> >> >>> Currently, KASAN may fail to detect concurrent deallocations of the same >>> object due to a race in kasan_slab_free(). This patch makes double-free >>> detection more reliable by serializing access to KASAN object metadata. >>> New functions kasan_meta_lock() and kasan_meta_unlock() are provided to >>> lock/unlock per-object metadata. Double-free errors are now reported via >>> kasan_report(). >>> >>> Per-object lock concept from suggestion/observations by Dmitry Vyukov. >>> >> >> >> So, I still don't like this, this too way hacky and complex. >> I have some thoughts about how to make this lockless and robust enough. >> I'll try to sort this out tomorrow. >> > > > So, I something like this should work. > Tested very briefly. > > diff --git a/include/linux/kasan.h b/include/linux/kasan.h > index ac4b3c4..8691142 100644 > --- a/include/linux/kasan.h > +++ b/include/linux/kasan.h > @@ -75,6 +75,8 @@ struct kasan_cache { > int kasan_module_alloc(void *addr, size_t size); > void kasan_free_shadow(const struct vm_struct *vm); > > +void kasan_init_slab_obj(struct kmem_cache *cache, const void *object); > + > size_t ksize(const void *); > static inline void kasan_unpoison_slab(const void *ptr) { ksize(ptr); } > > @@ -102,6 +104,9 @@ static inline void kasan_unpoison_object_data(struct kmem_cache *cache, > static inline void kasan_poison_object_data(struct kmem_cache *cache, > void *object) {} > > +static inline void kasan_init_slab_obj(struct kmem_cache *cache, > + const void *object) { } > + > static inline void kasan_kmalloc_large(void *ptr, size_t size, gfp_t flags) {} > static inline void kasan_kfree_large(const void *ptr) {} > static inline void kasan_poison_kfree(void *ptr) {} > diff --git a/mm/kasan/kasan.c b/mm/kasan/kasan.c > index 6845f92..ab0fded 100644 > --- a/mm/kasan/kasan.c > +++ b/mm/kasan/kasan.c > @@ -388,11 +388,9 @@ void kasan_cache_create(struct kmem_cache *cache, size_t *size, > *size += sizeof(struct kasan_alloc_meta); > > /* Add free meta. */ > - if (cache->flags & SLAB_DESTROY_BY_RCU || cache->ctor || > - cache->object_size < sizeof(struct kasan_free_meta)) { > - cache->kasan_info.free_meta_offset = *size; > - *size += sizeof(struct kasan_free_meta); > - } > + cache->kasan_info.free_meta_offset = *size; > + *size += sizeof(struct kasan_free_meta); > + > redzone_adjust = optimal_redzone(cache->object_size) - > (*size - cache->object_size); > if (redzone_adjust > 0) > @@ -431,13 +429,6 @@ void kasan_poison_object_data(struct kmem_cache *cache, void *object) > kasan_poison_shadow(object, > round_up(cache->object_size, KASAN_SHADOW_SCALE_SIZE), > KASAN_KMALLOC_REDZONE); > -#ifdef CONFIG_SLAB > - if (cache->flags & SLAB_KASAN) { > - struct kasan_alloc_meta *alloc_info = > - get_alloc_info(cache, object); > - alloc_info->state = KASAN_STATE_INIT; > - } > -#endif > } > > #ifdef CONFIG_SLAB > @@ -501,6 +492,20 @@ struct kasan_free_meta *get_free_info(struct kmem_cache *cache, > BUILD_BUG_ON(sizeof(struct kasan_free_meta) > 32); > return (void *)object + cache->kasan_info.free_meta_offset; > } > + > +void kasan_init_slab_obj(struct kmem_cache *cache, const void *object) > +{ > + struct kasan_alloc_meta *alloc_info; > + struct kasan_free_meta *free_info; > + > + if (!(cache->flags & SLAB_KASAN)) > + return; > + > + alloc_info = get_alloc_info(cache, object); > + free_info = get_free_info(cache, object); > + __memset(alloc_info, 0, sizeof(*alloc_info)); > + __memset(free_info, 0, sizeof(*free_info)); > +} > #endif > > void kasan_slab_alloc(struct kmem_cache *cache, void *object, gfp_t flags) > @@ -523,37 +528,47 @@ static void kasan_poison_slab_free(struct kmem_cache *cache, void *object) > bool kasan_slab_free(struct kmem_cache *cache, void *object) > { > #ifdef CONFIG_SLAB > + struct kasan_free_meta *free_info = get_free_info(cache, object); > + struct kasan_track new_free_stack, old_free_stack; > + s8 old_shadow; > + > /* RCU slabs could be legally used after free within the RCU period */ > if (unlikely(cache->flags & SLAB_DESTROY_BY_RCU)) > return false; > > - if (likely(cache->flags & SLAB_KASAN)) { > - struct kasan_alloc_meta *alloc_info = > - get_alloc_info(cache, object); > - struct kasan_free_meta *free_info = > - get_free_info(cache, object); > - > - switch (alloc_info->state) { > - case KASAN_STATE_ALLOC: > - alloc_info->state = KASAN_STATE_QUARANTINE; > - quarantine_put(free_info, cache); > - set_track(&free_info->track, GFP_NOWAIT); > - kasan_poison_slab_free(cache, object); > - return true; > - case KASAN_STATE_QUARANTINE: > - case KASAN_STATE_FREE: > - pr_err("Double free"); > - dump_stack(); > - break; > - default: > - break; > - } > + if (unlikely(!(cache->flags & SLAB_KASAN))) > + return false; > + > + set_track(&new_free_stack, GFP_NOWAIT); > + old_free_stack = xchg(&free_info->track, new_free_stack); > + old_shadow = xchg((s8 *)kasan_mem_to_shadow(object), > + KASAN_KMALLOC_FREE); Indeed, is the use of two xchg operations better than what Kuthonuzo suggests? On a related note, I wonder whether false sharing becomes a problem when we perform atomic operations on the shadow (or spin on it, like in the original suggestion). > + > + if (old_shadow < 0 || old_shadow >= KASAN_SHADOW_SCALE_SIZE) { > + struct kasan_track free_stack; > + > + /* Paired with xchg() above */ > + free_stack = smp_load_acquire(&free_info->track); > + > + /* > + * We didn't raced with another instance of kasan_slab_free() > + * so the previous free stack supposed to be in old_free_stack. > + * Otherwise, free_stack will contain stack trace of another > + * kfree() call. > + */ > + if (free_stack.id == new_free_stack.id) > + free_stack = old_free_stack; > + > + kasan_report_double_free(cache, object, > + free_stack, old_shadow); > + return false; > } > - return false; > -#else > kasan_poison_slab_free(cache, object); > - return false; > + return true; > + > #endif > + kasan_poison_slab_free(cache, object); > + return false; > } > > void kasan_kmalloc(struct kmem_cache *cache, const void *object, size_t size, > @@ -581,7 +596,6 @@ void kasan_kmalloc(struct kmem_cache *cache, const void *object, size_t size, > struct kasan_alloc_meta *alloc_info = > get_alloc_info(cache, object); > > - alloc_info->state = KASAN_STATE_ALLOC; > alloc_info->alloc_size = size; > set_track(&alloc_info->track, flags); > } > diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h > index fb87923..9b46d2e 100644 > --- a/mm/kasan/kasan.h > +++ b/mm/kasan/kasan.h > @@ -59,24 +59,21 @@ struct kasan_global { > * Structures to keep alloc and free tracks * > */ > > -enum kasan_state { > - KASAN_STATE_INIT, > - KASAN_STATE_ALLOC, > - KASAN_STATE_QUARANTINE, > - KASAN_STATE_FREE > -}; > - > #define KASAN_STACK_DEPTH 64 > > struct kasan_track { > - u32 pid; > - depot_stack_handle_t stack; > +union { > + struct { > + u32 pid; > + depot_stack_handle_t stack; > + }; > + u64 id; > +}; > }; > > struct kasan_alloc_meta { > struct kasan_track track; > - u32 state : 2; /* enum kasan_state */ > - u32 alloc_size : 30; > + u32 alloc_size; > }; > > struct qlist_node { > @@ -109,6 +106,9 @@ static inline bool kasan_report_enabled(void) > > void kasan_report(unsigned long addr, size_t size, > bool is_write, unsigned long ip); > +void kasan_report_double_free(struct kmem_cache *cache, void *object, > + struct kasan_track free_stack, s8 shadow); > + > > #ifdef CONFIG_SLAB > void quarantine_put(struct kasan_free_meta *info, struct kmem_cache *cache); > diff --git a/mm/kasan/quarantine.c b/mm/kasan/quarantine.c > index 4973505..3ec039c 100644 > --- a/mm/kasan/quarantine.c > +++ b/mm/kasan/quarantine.c > @@ -144,11 +144,9 @@ static void *qlink_to_object(struct qlist_node *qlink, struct kmem_cache *cache) > static void qlink_free(struct qlist_node *qlink, struct kmem_cache *cache) > { > void *object = qlink_to_object(qlink, cache); > - struct kasan_alloc_meta *alloc_info = get_alloc_info(cache, object); > unsigned long flags; > > local_irq_save(flags); > - alloc_info->state = KASAN_STATE_FREE; > ___cache_free(cache, object, _THIS_IP_); > local_irq_restore(flags); > } > diff --git a/mm/kasan/report.c b/mm/kasan/report.c > index b3c122d..a0f4519 100644 > --- a/mm/kasan/report.c > +++ b/mm/kasan/report.c > @@ -140,28 +140,13 @@ static void object_err(struct kmem_cache *cache, struct page *page, > pr_err("Object at %p, in cache %s\n", object, cache->name); > if (!(cache->flags & SLAB_KASAN)) > return; > - switch (alloc_info->state) { > - case KASAN_STATE_INIT: > - pr_err("Object not allocated yet\n"); > - break; > - case KASAN_STATE_ALLOC: > - pr_err("Object allocated with size %u bytes.\n", > - alloc_info->alloc_size); > - pr_err("Allocation:\n"); > - print_track(&alloc_info->track); > - break; > - case KASAN_STATE_FREE: > - case KASAN_STATE_QUARANTINE: > - pr_err("Object freed, allocated with size %u bytes\n", > - alloc_info->alloc_size); > - free_info = get_free_info(cache, object); > - pr_err("Allocation:\n"); > - print_track(&alloc_info->track); > - pr_err("Deallocation:\n"); > - print_track(&free_info->track); > - break; > - } > + free_info = get_free_info(cache, object); > + pr_err("Allocation:\n"); > + print_track(&alloc_info->track); > + pr_err("Deallocation:\n"); > + print_track(&free_info->track); > } > + > #endif > > static void print_address_description(struct kasan_access_info *info) > @@ -245,17 +230,31 @@ static void print_shadow_for_address(const void *addr) > > static DEFINE_SPINLOCK(report_lock); > > -static void kasan_report_error(struct kasan_access_info *info) > +static void kasan_start_report(unsigned long *flags) > { > - unsigned long flags; > - const char *bug_type; > - > /* > * Make sure we don't end up in loop. > */ > kasan_disable_current(); > - spin_lock_irqsave(&report_lock, flags); > + spin_lock_irqsave(&report_lock, *flags); > pr_err("==================================================================\n"); > +} > + > +static void kasan_end_report(unsigned long *flags) > +{ > + pr_err("==================================================================\n"); > + add_taint(TAINT_BAD_PAGE, LOCKDEP_NOW_UNRELIABLE); > + spin_unlock_irqrestore(&report_lock, *flags); > + kasan_enable_current(); > +} > + > +static void kasan_report_error(struct kasan_access_info *info) > +{ > + unsigned long flags; > + const char *bug_type; > + > + kasan_start_report(&flags); > + > if (info->access_addr < > kasan_shadow_to_mem((void *)KASAN_SHADOW_START)) { > if ((unsigned long)info->access_addr < PAGE_SIZE) > @@ -276,10 +275,29 @@ static void kasan_report_error(struct kasan_access_info *info) > print_address_description(info); > print_shadow_for_address(info->first_bad_addr); > } > - pr_err("==================================================================\n"); > - add_taint(TAINT_BAD_PAGE, LOCKDEP_NOW_UNRELIABLE); > - spin_unlock_irqrestore(&report_lock, flags); > - kasan_enable_current(); > + > + kasan_end_report(&flags); > +} > + > +void kasan_report_double_free(struct kmem_cache *cache, void *object, > + struct kasan_track free_stack, s8 shadow) > +{ > + unsigned long flags; > + > + kasan_start_report(&flags); > + > + pr_err("BUG: Double free or corrupt pointer\n"); > + pr_err("Unexpected shadow byte: 0x%hhX\n", shadow); > + > + dump_stack(); > + pr_err("Object at %p, in cache %s\n", object, cache->name); > + get_alloc_info(cache, object); > + pr_err("Allocation:\n"); > + print_track(&get_alloc_info(cache, object)->track); > + pr_err("Deallocation:\n"); > + print_track(&free_stack); > + > + kasan_end_report(&flags); > } > > void kasan_report(unsigned long addr, size_t size, > diff --git a/mm/slab.c b/mm/slab.c > index 763096a..65c942b 100644 > --- a/mm/slab.c > +++ b/mm/slab.c > @@ -2604,9 +2604,11 @@ static void cache_init_objs(struct kmem_cache *cachep, > } > > for (i = 0; i < cachep->num; i++) { > + objp = index_to_obj(cachep, page, i); > + kasan_init_slab_obj(cachep, objp); > + > /* constructor could break poison info */ > if (DEBUG == 0 && cachep->ctor) { > - objp = index_to_obj(cachep, page, i); > kasan_unpoison_object_data(cachep, objp); > cachep->ctor(objp); > kasan_poison_object_data(cachep, objp); -- Alexander Potapenko Software Engineer Google Germany GmbH Erika-Mann-Straße, 33 80636 München Geschäftsführer: Matthew Scott Sucherman, Paul Terence Manicle Registergericht und -nummer: Hamburg, HRB 86891 Sitz der Gesellschaft: Hamburg ^ permalink raw reply [flat|nested] 10+ messages in thread
* RE: [PATCH v5 1/2] mm, kasan: improve double-free detection 2016-06-10 17:03 ` Andrey Ryabinin 2016-06-10 17:09 ` Dmitry Vyukov 2016-06-13 11:56 ` Alexander Potapenko @ 2016-06-14 6:46 ` Luruo, Kuthonuzo 2016-06-15 16:38 ` Andrey Ryabinin 2 siblings, 1 reply; 10+ messages in thread From: Luruo, Kuthonuzo @ 2016-06-14 6:46 UTC (permalink / raw) To: Andrey Ryabinin Cc: glider, dvyukov, cl, penberg, rientjes, iamjoonsoo.kim, akpm, kasan-dev, linux-kernel, ynorov > > Next time, when/if you send patch series, send patches in one thread, i.e. > > patches should be replies to the cover letter. > > Your patches are not linked together, which makes them harder to track. Thanks for the tip; but doesn't this conflict with the advice in https://www.kernel.org/doc/Documentation/SubmittingPatches, specifically the use of the "summary phrase"... > > > > > >> Currently, KASAN may fail to detect concurrent deallocations of the same > >> object due to a race in kasan_slab_free(). This patch makes double-free > >> detection more reliable by serializing access to KASAN object metadata. > >> New functions kasan_meta_lock() and kasan_meta_unlock() are provided to > >> lock/unlock per-object metadata. Double-free errors are now reported via > >> kasan_report(). > >> > >> Per-object lock concept from suggestion/observations by Dmitry Vyukov. > >> > > > > > > So, I still don't like this, this too way hacky and complex. I don't think patch is particularly complex; but respect your judgment. > > I have some thoughts about how to make this lockless and robust enough. > > I'll try to sort this out tomorrow. > > > > > So, I something like this should work. > Tested very briefly. > > diff --git a/include/linux/kasan.h b/include/linux/kasan.h > index ac4b3c4..8691142 100644 > --- a/include/linux/kasan.h > +++ b/include/linux/kasan.h > @@ -75,6 +75,8 @@ struct kasan_cache { > int kasan_module_alloc(void *addr, size_t size); > void kasan_free_shadow(const struct vm_struct *vm); > > +void kasan_init_slab_obj(struct kmem_cache *cache, const void *object); > + > size_t ksize(const void *); > static inline void kasan_unpoison_slab(const void *ptr) { ksize(ptr); } > > @@ -102,6 +104,9 @@ static inline void kasan_unpoison_object_data(struct > kmem_cache *cache, > static inline void kasan_poison_object_data(struct kmem_cache *cache, > void *object) {} > > +static inline void kasan_init_slab_obj(struct kmem_cache *cache, > + const void *object) { } > + > static inline void kasan_kmalloc_large(void *ptr, size_t size, gfp_t flags) {} > static inline void kasan_kfree_large(const void *ptr) {} > static inline void kasan_poison_kfree(void *ptr) {} > diff --git a/mm/kasan/kasan.c b/mm/kasan/kasan.c > index 6845f92..ab0fded 100644 > --- a/mm/kasan/kasan.c > +++ b/mm/kasan/kasan.c > @@ -388,11 +388,9 @@ void kasan_cache_create(struct kmem_cache *cache, > size_t *size, > *size += sizeof(struct kasan_alloc_meta); > > /* Add free meta. */ > - if (cache->flags & SLAB_DESTROY_BY_RCU || cache->ctor || > - cache->object_size < sizeof(struct kasan_free_meta)) { > - cache->kasan_info.free_meta_offset = *size; > - *size += sizeof(struct kasan_free_meta); > - } > + cache->kasan_info.free_meta_offset = *size; > + *size += sizeof(struct kasan_free_meta); > + > redzone_adjust = optimal_redzone(cache->object_size) - > (*size - cache->object_size); > if (redzone_adjust > 0) > @@ -431,13 +429,6 @@ void kasan_poison_object_data(struct kmem_cache > *cache, void *object) > kasan_poison_shadow(object, > round_up(cache->object_size, > KASAN_SHADOW_SCALE_SIZE), > KASAN_KMALLOC_REDZONE); > -#ifdef CONFIG_SLAB > - if (cache->flags & SLAB_KASAN) { > - struct kasan_alloc_meta *alloc_info = > - get_alloc_info(cache, object); > - alloc_info->state = KASAN_STATE_INIT; > - } > -#endif > } > > #ifdef CONFIG_SLAB > @@ -501,6 +492,20 @@ struct kasan_free_meta *get_free_info(struct > kmem_cache *cache, > BUILD_BUG_ON(sizeof(struct kasan_free_meta) > 32); > return (void *)object + cache->kasan_info.free_meta_offset; > } > + > +void kasan_init_slab_obj(struct kmem_cache *cache, const void *object) > +{ > + struct kasan_alloc_meta *alloc_info; > + struct kasan_free_meta *free_info; > + > + if (!(cache->flags & SLAB_KASAN)) > + return; > + > + alloc_info = get_alloc_info(cache, object); > + free_info = get_free_info(cache, object); > + __memset(alloc_info, 0, sizeof(*alloc_info)); > + __memset(free_info, 0, sizeof(*free_info)); > +} > #endif > > void kasan_slab_alloc(struct kmem_cache *cache, void *object, gfp_t flags) > @@ -523,37 +528,47 @@ static void kasan_poison_slab_free(struct > kmem_cache *cache, void *object) > bool kasan_slab_free(struct kmem_cache *cache, void *object) > { > #ifdef CONFIG_SLAB > + struct kasan_free_meta *free_info = get_free_info(cache, object); > + struct kasan_track new_free_stack, old_free_stack; > + s8 old_shadow; > + > /* RCU slabs could be legally used after free within the RCU period */ > if (unlikely(cache->flags & SLAB_DESTROY_BY_RCU)) > return false; > > - if (likely(cache->flags & SLAB_KASAN)) { > - struct kasan_alloc_meta *alloc_info = > - get_alloc_info(cache, object); > - struct kasan_free_meta *free_info = > - get_free_info(cache, object); > - > - switch (alloc_info->state) { > - case KASAN_STATE_ALLOC: > - alloc_info->state = KASAN_STATE_QUARANTINE; > - quarantine_put(free_info, cache); > - set_track(&free_info->track, GFP_NOWAIT); > - kasan_poison_slab_free(cache, object); > - return true; > - case KASAN_STATE_QUARANTINE: > - case KASAN_STATE_FREE: > - pr_err("Double free"); > - dump_stack(); > - break; > - default: > - break; > - } > + if (unlikely(!(cache->flags & SLAB_KASAN))) > + return false; > + > + set_track(&new_free_stack, GFP_NOWAIT); > + old_free_stack = xchg(&free_info->track, new_free_stack); > + old_shadow = xchg((s8 *)kasan_mem_to_shadow(object), > + KASAN_KMALLOC_FREE); > + > + if (old_shadow < 0 || old_shadow >= KASAN_SHADOW_SCALE_SIZE) { > + struct kasan_track free_stack; > + > + /* Paired with xchg() above */ > + free_stack = smp_load_acquire(&free_info->track); > + > + /* > + * We didn't raced with another instance of kasan_slab_free() > + * so the previous free stack supposed to be in old_free_stack. > + * Otherwise, free_stack will contain stack trace of another > + * kfree() call. > + */ > + if (free_stack.id == new_free_stack.id) > + free_stack = old_free_stack; > + > + kasan_report_double_free(cache, object, > + free_stack, old_shadow); > + return false; > } > - return false; > -#else > kasan_poison_slab_free(cache, object); > - return false; > + return true; > + > #endif > + kasan_poison_slab_free(cache, object); > + return false; > } > > void kasan_kmalloc(struct kmem_cache *cache, const void *object, size_t size, > @@ -581,7 +596,6 @@ void kasan_kmalloc(struct kmem_cache *cache, const > void *object, size_t size, > struct kasan_alloc_meta *alloc_info = > get_alloc_info(cache, object); > > - alloc_info->state = KASAN_STATE_ALLOC; > alloc_info->alloc_size = size; > set_track(&alloc_info->track, flags); > } > diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h > index fb87923..9b46d2e 100644 > --- a/mm/kasan/kasan.h > +++ b/mm/kasan/kasan.h > @@ -59,24 +59,21 @@ struct kasan_global { > * Structures to keep alloc and free tracks * > */ > > -enum kasan_state { > - KASAN_STATE_INIT, > - KASAN_STATE_ALLOC, > - KASAN_STATE_QUARANTINE, > - KASAN_STATE_FREE > -}; > - > #define KASAN_STACK_DEPTH 64 > > struct kasan_track { > - u32 pid; > - depot_stack_handle_t stack; > +union { > + struct { > + u32 pid; > + depot_stack_handle_t stack; > + }; > + u64 id; > +}; > }; > > struct kasan_alloc_meta { > struct kasan_track track; > - u32 state : 2; /* enum kasan_state */ > - u32 alloc_size : 30; > + u32 alloc_size; > }; > > struct qlist_node { > @@ -109,6 +106,9 @@ static inline bool kasan_report_enabled(void) > > void kasan_report(unsigned long addr, size_t size, > bool is_write, unsigned long ip); > +void kasan_report_double_free(struct kmem_cache *cache, void *object, > + struct kasan_track free_stack, s8 shadow); > + > > #ifdef CONFIG_SLAB > void quarantine_put(struct kasan_free_meta *info, struct kmem_cache > *cache); > diff --git a/mm/kasan/quarantine.c b/mm/kasan/quarantine.c > index 4973505..3ec039c 100644 > --- a/mm/kasan/quarantine.c > +++ b/mm/kasan/quarantine.c > @@ -144,11 +144,9 @@ static void *qlink_to_object(struct qlist_node *qlink, > struct kmem_cache *cache) > static void qlink_free(struct qlist_node *qlink, struct kmem_cache *cache) > { > void *object = qlink_to_object(qlink, cache); > - struct kasan_alloc_meta *alloc_info = get_alloc_info(cache, object); > unsigned long flags; > > local_irq_save(flags); > - alloc_info->state = KASAN_STATE_FREE; > ___cache_free(cache, object, _THIS_IP_); > local_irq_restore(flags); > } > diff --git a/mm/kasan/report.c b/mm/kasan/report.c > index b3c122d..a0f4519 100644 > --- a/mm/kasan/report.c > +++ b/mm/kasan/report.c > @@ -140,28 +140,13 @@ static void object_err(struct kmem_cache *cache, > struct page *page, > pr_err("Object at %p, in cache %s\n", object, cache->name); > if (!(cache->flags & SLAB_KASAN)) > return; > - switch (alloc_info->state) { > - case KASAN_STATE_INIT: > - pr_err("Object not allocated yet\n"); > - break; > - case KASAN_STATE_ALLOC: > - pr_err("Object allocated with size %u bytes.\n", > - alloc_info->alloc_size); > - pr_err("Allocation:\n"); > - print_track(&alloc_info->track); > - break; > - case KASAN_STATE_FREE: > - case KASAN_STATE_QUARANTINE: > - pr_err("Object freed, allocated with size %u bytes\n", > - alloc_info->alloc_size); > - free_info = get_free_info(cache, object); > - pr_err("Allocation:\n"); > - print_track(&alloc_info->track); > - pr_err("Deallocation:\n"); > - print_track(&free_info->track); > - break; > - } > + free_info = get_free_info(cache, object); > + pr_err("Allocation:\n"); > + print_track(&alloc_info->track); > + pr_err("Deallocation:\n"); > + print_track(&free_info->track); > } > + > #endif > > static void print_address_description(struct kasan_access_info *info) > @@ -245,17 +230,31 @@ static void print_shadow_for_address(const void > *addr) > > static DEFINE_SPINLOCK(report_lock); > > -static void kasan_report_error(struct kasan_access_info *info) > +static void kasan_start_report(unsigned long *flags) > { > - unsigned long flags; > - const char *bug_type; > - > /* > * Make sure we don't end up in loop. > */ > kasan_disable_current(); > - spin_lock_irqsave(&report_lock, flags); > + spin_lock_irqsave(&report_lock, *flags); > > pr_err("==================================================== > ==============\n"); > +} > + > +static void kasan_end_report(unsigned long *flags) > +{ > + > pr_err("==================================================== > ==============\n"); > + add_taint(TAINT_BAD_PAGE, LOCKDEP_NOW_UNRELIABLE); > + spin_unlock_irqrestore(&report_lock, *flags); > + kasan_enable_current(); > +} > + > +static void kasan_report_error(struct kasan_access_info *info) > +{ > + unsigned long flags; > + const char *bug_type; > + > + kasan_start_report(&flags); > + > if (info->access_addr < > kasan_shadow_to_mem((void > *)KASAN_SHADOW_START)) { > if ((unsigned long)info->access_addr < PAGE_SIZE) > @@ -276,10 +275,29 @@ static void kasan_report_error(struct > kasan_access_info *info) > print_address_description(info); > print_shadow_for_address(info->first_bad_addr); > } > - > pr_err("==================================================== > ==============\n"); > - add_taint(TAINT_BAD_PAGE, LOCKDEP_NOW_UNRELIABLE); > - spin_unlock_irqrestore(&report_lock, flags); > - kasan_enable_current(); > + > + kasan_end_report(&flags); > +} > + > +void kasan_report_double_free(struct kmem_cache *cache, void *object, > + struct kasan_track free_stack, s8 shadow) > +{ > + unsigned long flags; > + > + kasan_start_report(&flags); > + > + pr_err("BUG: Double free or corrupt pointer\n"); > + pr_err("Unexpected shadow byte: 0x%hhX\n", shadow); > + > + dump_stack(); > + pr_err("Object at %p, in cache %s\n", object, cache->name); > + get_alloc_info(cache, object); > + pr_err("Allocation:\n"); > + print_track(&get_alloc_info(cache, object)->track); > + pr_err("Deallocation:\n"); > + print_track(&free_stack); > + > + kasan_end_report(&flags); > } > > void kasan_report(unsigned long addr, size_t size, > diff --git a/mm/slab.c b/mm/slab.c > index 763096a..65c942b 100644 > --- a/mm/slab.c > +++ b/mm/slab.c > @@ -2604,9 +2604,11 @@ static void cache_init_objs(struct kmem_cache > *cachep, > } > > for (i = 0; i < cachep->num; i++) { > + objp = index_to_obj(cachep, page, i); > + kasan_init_slab_obj(cachep, objp); > + > /* constructor could break poison info */ > if (DEBUG == 0 && cachep->ctor) { > - objp = index_to_obj(cachep, page, i); > kasan_unpoison_object_data(cachep, objp); > cachep->ctor(objp); > kasan_poison_object_data(cachep, objp); Nice hack & novel approach. It does have the flaw that subsequent error reports for object will have a bogus deallocation track. Kuthonuzo ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v5 1/2] mm, kasan: improve double-free detection 2016-06-14 6:46 ` Luruo, Kuthonuzo @ 2016-06-15 16:38 ` Andrey Ryabinin 0 siblings, 0 replies; 10+ messages in thread From: Andrey Ryabinin @ 2016-06-15 16:38 UTC (permalink / raw) To: Luruo, Kuthonuzo Cc: glider, dvyukov, cl, penberg, rientjes, iamjoonsoo.kim, akpm, kasan-dev, linux-kernel, ynorov On 06/14/2016 09:46 AM, Luruo, Kuthonuzo wrote: >>> Next time, when/if you send patch series, send patches in one thread, i.e. >>> patches should be replies to the cover letter. >>> Your patches are not linked together, which makes them harder to track. > > Thanks for the tip; but doesn't this conflict with the advice in > https://www.kernel.org/doc/Documentation/SubmittingPatches, specifically the > use of the "summary phrase"... > No, it doesn't. 'summary phrase' is about "Subject:" line. My point is about setting 'In-Reaply-To' which section 15) of SubmittingPatches For example, this one is good case: http://thread.gmane.org/gmane.linux.kernel/2244577 All patches are replies to the cover letter, so they are in one thread. And this is yours: http://thread.gmane.org/gmane.linux.kernel/2237786 http://thread.gmane.org/gmane.linux.kernel/2237787 ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2016-06-15 16:37 UTC | newest] Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2016-06-07 18:03 [PATCH v5 1/2] mm, kasan: improve double-free detection Kuthonuzo Luruo 2016-06-09 13:32 ` Alexander Potapenko 2016-06-09 16:54 ` Luruo, Kuthonuzo 2016-06-09 17:00 ` Andrey Ryabinin 2016-06-10 17:03 ` Andrey Ryabinin 2016-06-10 17:09 ` Dmitry Vyukov 2016-06-15 16:18 ` Andrey Ryabinin 2016-06-13 11:56 ` Alexander Potapenko 2016-06-14 6:46 ` Luruo, Kuthonuzo 2016-06-15 16:38 ` Andrey Ryabinin
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).