From: Joonsoo Kim <iamjoonsoo.kim@xxxxxxx> Mark/unmark the shadow of the objects that is allocated before the vchecker is enabled/disabled. It is necessary to fully debug the system. Since there is no synchronization way to prevent slab object free, we cannot synchronously mark/unmark the shadow of the allocated object. Therefore, with this patch, it would be possible to overwrite KASAN_KMALLOC_FREE shadow value to KASAN_VCHECKER_GRAYZONE/0 and UAF check in KASAN would be missed. However, it is okay since it happens rarely and we would decide to use this feature as a last resort. We can solve this race problem if another shadow memory is introduced. It will be considered after the usefulness of the feature is justified. Signed-off-by: Joonsoo Kim <iamjoonsoo.kim@xxxxxxx> --- include/linux/slab.h | 6 ++++++ mm/kasan/vchecker.c | 27 +++++++++++++++++++++++++++ mm/kasan/vchecker.h | 7 +++++++ mm/slab.c | 31 +++++++++++++++++++++++++++++++ mm/slab.h | 4 ++-- mm/slub.c | 36 ++++++++++++++++++++++++++++++++++-- 6 files changed, 107 insertions(+), 4 deletions(-) diff --git a/include/linux/slab.h b/include/linux/slab.h index 47e70e6..f6efbbe 100644 --- a/include/linux/slab.h +++ b/include/linux/slab.h @@ -108,6 +108,12 @@ #define SLAB_KASAN 0 #endif +#ifdef CONFIG_VCHECKER +#define SLAB_VCHECKER 0x10000000UL +#else +#define SLAB_VCHECKER 0x00000000UL +#endif + /* The following flags affect the page allocator grouping pages by mobility */ /* Objects are reclaimable */ #define SLAB_RECLAIM_ACCOUNT ((slab_flags_t __force)0x00020000U) diff --git a/mm/kasan/vchecker.c b/mm/kasan/vchecker.c index 0ac031c..0b8a1e7 100644 --- a/mm/kasan/vchecker.c +++ b/mm/kasan/vchecker.c @@ -109,6 +109,12 @@ static int remove_cbs(struct kmem_cache *s, struct vchecker_type *t) return 0; } +void vchecker_cache_create(struct kmem_cache *s, + size_t *size, slab_flags_t *flags) +{ + *flags |= SLAB_VCHECKER; +} + void vchecker_kmalloc(struct kmem_cache *s, const void *object, size_t size) { struct vchecker *checker; @@ -130,6 +136,26 @@ void vchecker_kmalloc(struct kmem_cache *s, const void *object, size_t size) rcu_read_unlock(); } +void vchecker_enable_obj(struct kmem_cache *s, const void *object, + size_t size, bool enable) +{ + struct vchecker *checker; + struct vchecker_cb *cb; + s8 shadow_val = READ_ONCE(*(s8 *)kasan_mem_to_shadow(object)); + s8 mark = enable ? KASAN_VCHECKER_GRAYZONE : 0; + + /* It would be freed object. We don't need to mark it */ + if (shadow_val < 0 && (u8)shadow_val != KASAN_VCHECKER_GRAYZONE) + return; + + checker = s->vchecker_cache.checker; + list_for_each_entry(cb, &checker->cb_list, list) { + kasan_poison_shadow(object + cb->begin, + round_up(cb->end - cb->begin, + KASAN_SHADOW_SCALE_SIZE), mark); + } +} + static void vchecker_report(unsigned long addr, size_t size, bool write, unsigned long ret_ip, struct kmem_cache *s, struct vchecker_cb *cb, void *object) @@ -380,6 +406,7 @@ static ssize_t enable_write(struct file *filp, const char __user *ubuf, * left that accesses checker's cb list if vchecker is disabled. */ synchronize_sched(); + vchecker_enable_cache(s, enable); mutex_unlock(&vchecker_meta); return cnt; diff --git a/mm/kasan/vchecker.h b/mm/kasan/vchecker.h index 77ba07d..aa22e8d 100644 --- a/mm/kasan/vchecker.h +++ b/mm/kasan/vchecker.h @@ -16,6 +16,11 @@ bool vchecker_check(unsigned long addr, size_t size, bool write, unsigned long ret_ip); int init_vchecker(struct kmem_cache *s); void fini_vchecker(struct kmem_cache *s); +void vchecker_cache_create(struct kmem_cache *s, size_t *size, + slab_flags_t *flags); +void vchecker_enable_cache(struct kmem_cache *s, bool enable); +void vchecker_enable_obj(struct kmem_cache *s, const void *object, + size_t size, bool enable); #else static inline void vchecker_kmalloc(struct kmem_cache *s, @@ -24,6 +29,8 @@ static inline bool vchecker_check(unsigned long addr, size_t size, bool write, unsigned long ret_ip) { return false; } static inline int init_vchecker(struct kmem_cache *s) { return 0; } static inline void fini_vchecker(struct kmem_cache *s) { } +static inline void vchecker_cache_create(struct kmem_cache *s, + size_t *size, slab_flags_t *flags) {} #endif diff --git a/mm/slab.c b/mm/slab.c index 78ea436..ba45c15 100644 --- a/mm/slab.c +++ b/mm/slab.c @@ -2551,6 +2551,37 @@ static inline bool shuffle_freelist(struct kmem_cache *cachep, } #endif /* CONFIG_SLAB_FREELIST_RANDOM */ +#ifdef CONFIG_VCHECKER +static void __vchecker_enable_cache(struct kmem_cache *s, + struct page *page, bool enable) +{ + int i; + void *p; + + for (i = 0; i < s->num; i++) { + p = index_to_obj(s, page, i); + vchecker_enable_obj(s, p, s->object_size, enable); + } +} + +void vchecker_enable_cache(struct kmem_cache *s, bool enable) +{ + int node; + struct kmem_cache_node *n; + struct page *page; + unsigned long flags; + + for_each_kmem_cache_node(s, node, n) { + spin_lock_irqsave(&n->list_lock, flags); + list_for_each_entry(page, &n->slabs_partial, lru) + __vchecker_enable_cache(s, page, enable); + list_for_each_entry(page, &n->slabs_full, lru) + __vchecker_enable_cache(s, page, enable); + spin_unlock_irqrestore(&n->list_lock, flags); + } +} +#endif + static void cache_init_objs(struct kmem_cache *cachep, struct page *page) { diff --git a/mm/slab.h b/mm/slab.h index d054da8..c1cf486 100644 --- a/mm/slab.h +++ b/mm/slab.h @@ -136,10 +136,10 @@ static inline slab_flags_t kmem_cache_flags(unsigned long object_size, SLAB_TYPESAFE_BY_RCU | SLAB_DEBUG_OBJECTS ) #if defined(CONFIG_DEBUG_SLAB) -#define SLAB_DEBUG_FLAGS (SLAB_RED_ZONE | SLAB_POISON | SLAB_STORE_USER) +#define SLAB_DEBUG_FLAGS (SLAB_RED_ZONE | SLAB_POISON | SLAB_STORE_USER | SLAB_VCHECKER) #elif defined(CONFIG_SLUB_DEBUG) #define SLAB_DEBUG_FLAGS (SLAB_RED_ZONE | SLAB_POISON | SLAB_STORE_USER | \ - SLAB_TRACE | SLAB_CONSISTENCY_CHECKS) + SLAB_TRACE | SLAB_CONSISTENCY_CHECKS | SLAB_VCHECKER) #else #define SLAB_DEBUG_FLAGS (0) #endif diff --git a/mm/slub.c b/mm/slub.c index bb8c949..67364cb 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -1026,7 +1026,7 @@ static void trace(struct kmem_cache *s, struct page *page, void *object, static void add_full(struct kmem_cache *s, struct kmem_cache_node *n, struct page *page) { - if (!(s->flags & SLAB_STORE_USER)) + if (!(s->flags & (SLAB_STORE_USER | SLAB_VCHECKER))) return; lockdep_assert_held(&n->list_lock); @@ -1035,7 +1035,7 @@ static void add_full(struct kmem_cache *s, static void remove_full(struct kmem_cache *s, struct kmem_cache_node *n, struct page *page) { - if (!(s->flags & SLAB_STORE_USER)) + if (!(s->flags & (SLAB_STORE_USER | SLAB_VCHECKER))) return; lockdep_assert_held(&n->list_lock); @@ -1555,6 +1555,38 @@ static inline bool shuffle_freelist(struct kmem_cache *s, struct page *page) } #endif /* CONFIG_SLAB_FREELIST_RANDOM */ +#ifdef CONFIG_VCHECKER +static void __vchecker_enable_cache(struct kmem_cache *s, + struct page *page, bool enable) +{ + void *p; + void *addr = page_address(page); + + if (!page->inuse) + return; + + for_each_object(p, s, addr, page->objects) + vchecker_enable_obj(s, p, s->object_size, enable); +} + +void vchecker_enable_cache(struct kmem_cache *s, bool enable) +{ + int node; + struct kmem_cache_node *n; + struct page *page; + unsigned long flags; + + for_each_kmem_cache_node(s, node, n) { + spin_lock_irqsave(&n->list_lock, flags); + list_for_each_entry(page, &n->partial, lru) + __vchecker_enable_cache(s, page, enable); + list_for_each_entry(page, &n->full, lru) + __vchecker_enable_cache(s, page, enable); + spin_unlock_irqrestore(&n->list_lock, flags); + } +} +#endif + static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node) { struct page *page; -- 2.7.4 -- 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=mailto:"dont@xxxxxxxxx"> email@xxxxxxxxx </a>