mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-05 02:19:51 +00:00
![Alexander Potapenko](/assets/img/avatar_default.png)
Quarantine isolates freed objects in a separate queue. The objects are returned to the allocator later, which helps to detect use-after-free errors. When the object is freed, its state changes from KASAN_STATE_ALLOC to KASAN_STATE_QUARANTINE. The object is poisoned and put into quarantine instead of being returned to the allocator, therefore every subsequent access to that object triggers a KASAN error, and the error handler is able to say where the object has been allocated and deallocated. When it's time for the object to leave quarantine, its state becomes KASAN_STATE_FREE and it's returned to the allocator. From now on the allocator may reuse it for another allocation. Before that happens, it's still possible to detect a use-after free on that object (it retains the allocation/deallocation stacks). When the allocator reuses this object, the shadow is unpoisoned and old allocation/deallocation stacks are wiped. Therefore a use of this object, even an incorrect one, won't trigger ASan warning. Without the quarantine, it's not guaranteed that the objects aren't reused immediately, that's why the probability of catching a use-after-free is lower than with quarantine in place. Quarantine isolates freed objects in a separate queue. The objects are returned to the allocator later, which helps to detect use-after-free errors. Freed objects are first added to per-cpu quarantine queues. When a cache is destroyed or memory shrinking is requested, the objects are moved into the global quarantine queue. Whenever a kmalloc call allows memory reclaiming, the oldest objects are popped out of the global queue until the total size of objects in quarantine is less than 3/4 of the maximum quarantine size (which is a fraction of installed physical memory). As long as an object remains in the quarantine, KASAN is able to report accesses to it, so the chance of reporting a use-after-free is increased. Once the object leaves quarantine, the allocator may reuse it, in which case the object is unpoisoned and KASAN can't detect incorrect accesses to it. Right now quarantine support is only enabled in SLAB allocator. Unification of KASAN features in SLAB and SLUB will be done later. This patch is based on the "mm: kasan: quarantine" patch originally prepared by Dmitry Chernenkov. A number of improvements have been suggested by Andrey Ryabinin. [glider@google.com: v9] Link: http://lkml.kernel.org/r/1462987130-144092-1-git-send-email-glider@google.com Signed-off-by: Alexander Potapenko <glider@google.com> Cc: Christoph Lameter <cl@linux.com> Cc: Pekka Enberg <penberg@kernel.org> Cc: David Rientjes <rientjes@google.com> Cc: Joonsoo Kim <iamjoonsoo.kim@lge.com> Cc: Andrey Konovalov <adech.fo@gmail.com> Cc: Dmitry Vyukov <dvyukov@google.com> Cc: Andrey Ryabinin <ryabinin.a.a@gmail.com> Cc: Steven Rostedt <rostedt@goodmis.org> Cc: Konstantin Serebryany <kcc@google.com> Cc: Dmitry Chernenkov <dmitryc@google.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
124 lines
3.9 KiB
C
124 lines
3.9 KiB
C
#ifndef _LINUX_KASAN_H
|
|
#define _LINUX_KASAN_H
|
|
|
|
#include <linux/sched.h>
|
|
#include <linux/types.h>
|
|
|
|
struct kmem_cache;
|
|
struct page;
|
|
struct vm_struct;
|
|
|
|
#ifdef CONFIG_KASAN
|
|
|
|
#define KASAN_SHADOW_SCALE_SHIFT 3
|
|
|
|
#include <asm/kasan.h>
|
|
#include <asm/pgtable.h>
|
|
|
|
extern unsigned char kasan_zero_page[PAGE_SIZE];
|
|
extern pte_t kasan_zero_pte[PTRS_PER_PTE];
|
|
extern pmd_t kasan_zero_pmd[PTRS_PER_PMD];
|
|
extern pud_t kasan_zero_pud[PTRS_PER_PUD];
|
|
|
|
void kasan_populate_zero_shadow(const void *shadow_start,
|
|
const void *shadow_end);
|
|
|
|
static inline void *kasan_mem_to_shadow(const void *addr)
|
|
{
|
|
return (void *)((unsigned long)addr >> KASAN_SHADOW_SCALE_SHIFT)
|
|
+ KASAN_SHADOW_OFFSET;
|
|
}
|
|
|
|
/* Enable reporting bugs after kasan_disable_current() */
|
|
static inline void kasan_enable_current(void)
|
|
{
|
|
current->kasan_depth++;
|
|
}
|
|
|
|
/* Disable reporting bugs for current task */
|
|
static inline void kasan_disable_current(void)
|
|
{
|
|
current->kasan_depth--;
|
|
}
|
|
|
|
void kasan_unpoison_shadow(const void *address, size_t size);
|
|
|
|
void kasan_unpoison_task_stack(struct task_struct *task);
|
|
|
|
void kasan_alloc_pages(struct page *page, unsigned int order);
|
|
void kasan_free_pages(struct page *page, unsigned int order);
|
|
|
|
void kasan_cache_create(struct kmem_cache *cache, size_t *size,
|
|
unsigned long *flags);
|
|
void kasan_cache_shrink(struct kmem_cache *cache);
|
|
void kasan_cache_destroy(struct kmem_cache *cache);
|
|
|
|
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);
|
|
|
|
void kasan_kmalloc_large(const void *ptr, size_t size, gfp_t flags);
|
|
void kasan_kfree_large(const void *ptr);
|
|
void kasan_kfree(void *ptr);
|
|
void kasan_kmalloc(struct kmem_cache *s, const void *object, size_t size,
|
|
gfp_t flags);
|
|
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);
|
|
void kasan_poison_slab_free(struct kmem_cache *s, void *object);
|
|
|
|
struct kasan_cache {
|
|
int alloc_meta_offset;
|
|
int free_meta_offset;
|
|
};
|
|
|
|
int kasan_module_alloc(void *addr, size_t size);
|
|
void kasan_free_shadow(const struct vm_struct *vm);
|
|
|
|
#else /* CONFIG_KASAN */
|
|
|
|
static inline void kasan_unpoison_shadow(const void *address, size_t size) {}
|
|
|
|
static inline void kasan_unpoison_task_stack(struct task_struct *task) {}
|
|
|
|
static inline void kasan_enable_current(void) {}
|
|
static inline void kasan_disable_current(void) {}
|
|
|
|
static inline void kasan_alloc_pages(struct page *page, unsigned int order) {}
|
|
static inline void kasan_free_pages(struct page *page, unsigned int order) {}
|
|
|
|
static inline void kasan_cache_create(struct kmem_cache *cache,
|
|
size_t *size,
|
|
unsigned long *flags) {}
|
|
static inline void kasan_cache_shrink(struct kmem_cache *cache) {}
|
|
static inline void kasan_cache_destroy(struct kmem_cache *cache) {}
|
|
|
|
static inline void kasan_poison_slab(struct page *page) {}
|
|
static inline void kasan_unpoison_object_data(struct kmem_cache *cache,
|
|
void *object) {}
|
|
static inline void kasan_poison_object_data(struct kmem_cache *cache,
|
|
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_kfree(void *ptr) {}
|
|
static inline void kasan_kmalloc(struct kmem_cache *s, const void *object,
|
|
size_t size, gfp_t flags) {}
|
|
static inline void kasan_krealloc(const void *object, size_t new_size,
|
|
gfp_t flags) {}
|
|
|
|
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)
|
|
{
|
|
return false;
|
|
}
|
|
static inline void kasan_poison_slab_free(struct kmem_cache *s, void *object) {}
|
|
|
|
static inline int kasan_module_alloc(void *addr, size_t size) { return 0; }
|
|
static inline void kasan_free_shadow(const struct vm_struct *vm) {}
|
|
|
|
#endif /* CONFIG_KASAN */
|
|
|
|
#endif /* LINUX_KASAN_H */
|