Re: [PATCH] mm, kasan: switch SLUB to stackdepot, enable memory quarantine for SLUB

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, Jun 9, 2016 at 8:22 PM, Alexander Potapenko <glider@xxxxxxxxxx> wrote:
> On Thu, Jun 9, 2016 at 6:45 PM, Andrey Ryabinin <aryabinin@xxxxxxxxxxxxx> wrote:
>>
>>
>> On 06/08/2016 09:40 PM, Alexander Potapenko wrote:
>>> For KASAN builds:
>>>  - switch SLUB allocator to using stackdepot instead of storing the
>>>    allocation/deallocation stacks in the objects;
>>>  - define SLAB_RED_ZONE, SLAB_POISON, SLAB_STORE_USER to zero,
>>>    effectively disabling these debug features, as they're redundant in
>>>    the presence of KASAN;
>>
>> Instead of having duplicated functionality, I think it might be better to switch SLAB_STORE_USER to stackdepot instead.
>> Because now, we have two piles of code which do basically the same thing, but
>> do differently.
> Fine, I'll try that out.
>>>  - refactor the slab freelist hook, put freed memory into the quarantine.
>>>
>>
>> What you did with slab_freelist_hook() is not refactoring, it's an obfuscation.
> Whatever you call it.
> The problem is that if a list of heterogeneous objects is passed into
> slab_free_freelist_hook(), some of them may end up in the quarantine,
> while others will not.
> Therefore we need to filter that list and remove the objects that
> don't need to be freed from it.
>>
>>>  }
>>>
>>> -#ifdef CONFIG_SLAB
>>>  /*
>>>   * Adaptive redzone policy taken from the userspace AddressSanitizer runtime.
>>>   * For larger allocations larger redzones are used.
>>> @@ -372,17 +371,21 @@ static size_t optimal_redzone(size_t object_size)
>>>  void kasan_cache_create(struct kmem_cache *cache, size_t *size,
>>>                       unsigned long *flags)
>>>  {
>>> -     int redzone_adjust;
>>> -     /* Make sure the adjusted size is still less than
>>> -      * KMALLOC_MAX_CACHE_SIZE.
>>> -      * TODO: this check is only useful for SLAB, but not SLUB. We'll need
>>> -      * to skip it for SLUB when it starts using kasan_cache_create().
>>> +     int redzone_adjust, orig_size = *size;
>>> +
>>> +#ifdef CONFIG_SLAB
>>> +     /*
>>> +      * Make sure the adjusted size is still less than
>>> +      * KMALLOC_MAX_CACHE_SIZE, i.e. we don't use the page allocator.
>>>        */
>>> +
>>>       if (*size > KMALLOC_MAX_CACHE_SIZE -
>>
>> This is wrong. You probably wanted KMALLOC_MAX_SIZE here.
> Yeah, sonds right.
>> However, we should get rid of SLAB_KASAN altogether. It's absolutely useless, and only complicates
>> the code. And if we don't fit in KMALLOC_MAX_SIZE, just don't create cache.
> Thanks, I'll look into this. Looks like you are right, once we remove
> this check every existing cache will have SLAB_KASAN set.
> It's handy for debugging, but not really needed.
Turns out we cannot just skip cache creation if we don't fit in
KMALLOC_MAX_SIZE, because SLAB, among others, creates kmalloc-4194304.
So we must keep an attribute that denotes that this is a KASAN-enabled
slab (i.e. the SLAB_KASAN flag), or set alloc_meta_offset and
free_meta_offset to -1, which isn't so convenient.
I suggest we stick with SLAB_KASAN.
>>
>>>           sizeof(struct kasan_alloc_meta) -
>>>           sizeof(struct kasan_free_meta))
>>>               return;
>>> +#endif
>>>       *flags |= SLAB_KASAN;
>>> +
>>>       /* Add alloc meta. */
>>>       cache->kasan_info.alloc_meta_offset = *size;
>>>       *size += sizeof(struct kasan_alloc_meta);
>>> @@ -392,17 +395,37 @@ void kasan_cache_create(struct kmem_cache *cache, size_t *size,
>>>           cache->object_size < sizeof(struct kasan_free_meta)) {
>>>               cache->kasan_info.free_meta_offset = *size;
>>>               *size += sizeof(struct kasan_free_meta);
>>> +     } else {
>>> +             cache->kasan_info.free_meta_offset = 0;
>>>       }
>>>       redzone_adjust = optimal_redzone(cache->object_size) -
>>>               (*size - cache->object_size);
>>> +
>>>       if (redzone_adjust > 0)
>>>               *size += redzone_adjust;
>>> +
>>> +#ifdef CONFIG_SLAB
>>>       *size = min(KMALLOC_MAX_CACHE_SIZE,
>>>                   max(*size,
>>>                       cache->object_size +
>>>                       optimal_redzone(cache->object_size)));
>>> -}
>>> +     /*
>>> +      * If the metadata doesn't fit, disable KASAN at all.
>>> +      */
>>> +     if (*size <= cache->kasan_info.alloc_meta_offset ||
>>> +                     *size <= cache->kasan_info.free_meta_offset) {
>>> +             *flags &= ~SLAB_KASAN;
>>> +             *size = orig_size;
>>> +             cache->kasan_info.alloc_meta_offset = -1;
>>> +             cache->kasan_info.free_meta_offset = -1;
>>> +     }
>>> +#else
>>> +     *size = max(*size,
>>> +                     cache->object_size +
>>> +                     optimal_redzone(cache->object_size));
>>> +
>>>  #endif
>>> +}
>>>
>>>  void kasan_cache_shrink(struct kmem_cache *cache)
>>>  {
>>> @@ -431,16 +454,14 @@ 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;
>>> +             if (alloc_info)
>>
>> If I read the code right alloc_info can be NULL only if SLAB_KASAN is set.
> This has been left over from tracking down some nasty bugs, but, yes,
> we can assume alloc_info and free_info are always valid.
>>
>>> +                     alloc_info->state = KASAN_STATE_INIT;
>>>       }
>>> -#endif
>>>  }
>>>
>>> -#ifdef CONFIG_SLAB
>>>  static inline int in_irqentry_text(unsigned long ptr)
>>>  {
>>>       return (ptr >= (unsigned long)&__irqentry_text_start &&
>>> @@ -492,6 +513,8 @@ struct kasan_alloc_meta *get_alloc_info(struct kmem_cache *cache,
>>>                                       const void *object)
>>>  {
>>>       BUILD_BUG_ON(sizeof(struct kasan_alloc_meta) > 32);
>>> +     if (cache->kasan_info.alloc_meta_offset == -1)
>>> +             return NULL;
>>
>> What's the point of this ? This should be always false.
> Agreed, will remove this (and other similar cases).
>>>       return (void *)object + cache->kasan_info.alloc_meta_offset;
>>>  }
>>>
>>> @@ -499,9 +522,10 @@ struct kasan_free_meta *get_free_info(struct kmem_cache *cache,
>>>                                     const void *object)
>>>  {
>>>       BUILD_BUG_ON(sizeof(struct kasan_free_meta) > 32);
>>> +     if (cache->kasan_info.free_meta_offset == -1)
>>> +             return NULL;
>>>       return (void *)object + cache->kasan_info.free_meta_offset;
>>>  }
>>> -#endif
>>>
>>>  void kasan_slab_alloc(struct kmem_cache *cache, void *object, gfp_t flags)
>>>  {
>>> @@ -522,7 +546,6 @@ void kasan_poison_slab_free(struct kmem_cache *cache, void *object)
>>>
>>>  bool kasan_slab_free(struct kmem_cache *cache, void *object)
>>>  {
>>> -#ifdef CONFIG_SLAB
>>>       /* RCU slabs could be legally used after free within the RCU period */
>>>       if (unlikely(cache->flags & SLAB_DESTROY_BY_RCU))
>>>               return false;
>>> @@ -532,7 +555,10 @@ bool kasan_slab_free(struct kmem_cache *cache, void *object)
>>>                       get_alloc_info(cache, object);
>>>               struct kasan_free_meta *free_info =
>>>                       get_free_info(cache, object);
>>> -
>>> +             WARN_ON(!alloc_info);
>>> +             WARN_ON(!free_info);
>>> +             if (!alloc_info || !free_info)
>>> +                     return;
>>
>> Again, never possible.
>>
>>
>>>               switch (alloc_info->state) {
>>>               case KASAN_STATE_ALLOC:
>>>                       alloc_info->state = KASAN_STATE_QUARANTINE;
>>> @@ -550,10 +576,6 @@ bool kasan_slab_free(struct kmem_cache *cache, void *object)
>>>               }
>>>       }
>>>       return false;
>>> -#else
>>> -     kasan_poison_slab_free(cache, object);
>>> -     return false;
>>> -#endif
>>>  }
>>>
>>>  void kasan_kmalloc(struct kmem_cache *cache, const void *object, size_t size,
>>> @@ -568,24 +590,29 @@ void kasan_kmalloc(struct kmem_cache *cache, const void *object, size_t size,
>>>       if (unlikely(object == NULL))
>>>               return;
>>>
>>> +     if (!(cache->flags & SLAB_KASAN))
>>> +             return;
>>> +
>>>       redzone_start = round_up((unsigned long)(object + size),
>>>                               KASAN_SHADOW_SCALE_SIZE);
>>>       redzone_end = round_up((unsigned long)object + cache->object_size,
>>>                               KASAN_SHADOW_SCALE_SIZE);
>>>
>>>       kasan_unpoison_shadow(object, size);
>>> +     WARN_ON(redzone_start > redzone_end);
>>> +     if (redzone_start > redzone_end)
>>
>> How that's can happen?
> This was possible because of incorrect ksize implementation, should be
> now ok. Removed.
>>> +             return;
>>>       kasan_poison_shadow((void *)redzone_start, redzone_end - redzone_start,
>>>               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_ALLOC;
>>> -             alloc_info->alloc_size = size;
>>> -             set_track(&alloc_info->track, flags);
>>> +             if (alloc_info) {
>>
>> And again...
>>
>>
>>> +                     alloc_info->state = KASAN_STATE_ALLOC;
>>> +                     alloc_info->alloc_size = size;
>>> +                     set_track(&alloc_info->track, flags);
>>> +             }
>>>       }
>>> -#endif
>>>  }
>>>  EXPORT_SYMBOL(kasan_kmalloc);
>>>
>>
>>
>> [..]
>>
>>> diff --git a/mm/slab.h b/mm/slab.h
>>> index dedb1a9..fde1fea 100644
>>> --- a/mm/slab.h
>>> +++ b/mm/slab.h
>>> @@ -366,6 +366,10 @@ static inline size_t slab_ksize(const struct kmem_cache *s)
>>>       if (s->flags & (SLAB_RED_ZONE | SLAB_POISON))
>>>               return s->object_size;
>>>  # endif
>>> +# ifdef CONFIG_KASAN
>>
>> Gush, you love ifdefs, don't you? Hint: it's redundant here.
>>
>>> +     if (s->flags & SLAB_KASAN)
>>> +             return s->object_size;
>>> +# endif
>>>       /*
>>>        * If we have the need to store the freelist pointer
>> ...
>
>
>
> --
> 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



-- 
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

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@xxxxxxxxx.  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href



[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Bugtraq]     [Linux]     [Linux OMAP]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]