diff options
author | Dmitry Vyukov <dvyukov@google.com> | 2018-02-06 18:36:23 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2018-02-06 21:32:42 -0500 |
commit | 47adccce3e8a31d315f47183ab1185862b2fc5d4 (patch) | |
tree | aaf982ef67484b702b42440b459cfd648e58dc4e | |
parent | d321599cf6b861beefe92327476b617435c7fc4a (diff) |
kasan: detect invalid frees for large objects
Patch series "kasan: detect invalid frees".
KASAN detects double-frees, but does not detect invalid-frees (when a
pointer into a middle of heap object is passed to free). We recently had
a very unpleasant case in crypto code which freed an inner object inside
of a heap allocation. This left unnoticed during free, but totally
corrupted heap and later lead to a bunch of random crashes all over kernel
code.
Detect invalid frees.
This patch (of 5):
Detect frees of pointers into middle of large heap objects.
I dropped const from kasan_kfree_large() because it starts propagating
through a bunch of functions in kasan_report.c, slab/slub nearest_obj(),
all of their local variables, fixup_red_left(), etc.
Link: http://lkml.kernel.org/r/1b45b4fe1d20fc0de1329aab674c1dd973fee723.1514378558.git.dvyukov@google.com
Signed-off-by: Dmitry Vyukov <dvyukov@google.com>
Cc: Andrey Ryabinin <aryabinin@virtuozzo.com>a
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | include/linux/kasan.h | 4 | ||||
-rw-r--r-- | lib/test_kasan.c | 33 | ||||
-rw-r--r-- | mm/kasan/kasan.c | 12 | ||||
-rw-r--r-- | mm/kasan/kasan.h | 3 | ||||
-rw-r--r-- | mm/kasan/report.c | 3 | ||||
-rw-r--r-- | mm/slub.c | 4 |
6 files changed, 44 insertions, 15 deletions
diff --git a/include/linux/kasan.h b/include/linux/kasan.h index e3eb834c9a35..fc9e642533f8 100644 --- a/include/linux/kasan.h +++ b/include/linux/kasan.h | |||
@@ -56,7 +56,7 @@ void kasan_poison_object_data(struct kmem_cache *cache, void *object); | |||
56 | void kasan_init_slab_obj(struct kmem_cache *cache, const void *object); | 56 | void kasan_init_slab_obj(struct kmem_cache *cache, const void *object); |
57 | 57 | ||
58 | void kasan_kmalloc_large(const void *ptr, size_t size, gfp_t flags); | 58 | void kasan_kmalloc_large(const void *ptr, size_t size, gfp_t flags); |
59 | void kasan_kfree_large(const void *ptr); | 59 | void kasan_kfree_large(void *ptr); |
60 | void kasan_poison_kfree(void *ptr); | 60 | void kasan_poison_kfree(void *ptr); |
61 | void kasan_kmalloc(struct kmem_cache *s, const void *object, size_t size, | 61 | void kasan_kmalloc(struct kmem_cache *s, const void *object, size_t size, |
62 | gfp_t flags); | 62 | gfp_t flags); |
@@ -108,7 +108,7 @@ static inline void kasan_init_slab_obj(struct kmem_cache *cache, | |||
108 | const void *object) {} | 108 | const void *object) {} |
109 | 109 | ||
110 | static inline void kasan_kmalloc_large(void *ptr, size_t size, gfp_t flags) {} | 110 | static inline void kasan_kmalloc_large(void *ptr, size_t size, gfp_t flags) {} |
111 | static inline void kasan_kfree_large(const void *ptr) {} | 111 | static inline void kasan_kfree_large(void *ptr) {} |
112 | static inline void kasan_poison_kfree(void *ptr) {} | 112 | static inline void kasan_poison_kfree(void *ptr) {} |
113 | static inline void kasan_kmalloc(struct kmem_cache *s, const void *object, | 113 | static inline void kasan_kmalloc(struct kmem_cache *s, const void *object, |
114 | size_t size, gfp_t flags) {} | 114 | size_t size, gfp_t flags) {} |
diff --git a/lib/test_kasan.c b/lib/test_kasan.c index 2724f86c4cef..e9c5d765be66 100644 --- a/lib/test_kasan.c +++ b/lib/test_kasan.c | |||
@@ -94,6 +94,37 @@ static noinline void __init kmalloc_pagealloc_oob_right(void) | |||
94 | ptr[size] = 0; | 94 | ptr[size] = 0; |
95 | kfree(ptr); | 95 | kfree(ptr); |
96 | } | 96 | } |
97 | |||
98 | static noinline void __init kmalloc_pagealloc_uaf(void) | ||
99 | { | ||
100 | char *ptr; | ||
101 | size_t size = KMALLOC_MAX_CACHE_SIZE + 10; | ||
102 | |||
103 | pr_info("kmalloc pagealloc allocation: use-after-free\n"); | ||
104 | ptr = kmalloc(size, GFP_KERNEL); | ||
105 | if (!ptr) { | ||
106 | pr_err("Allocation failed\n"); | ||
107 | return; | ||
108 | } | ||
109 | |||
110 | kfree(ptr); | ||
111 | ptr[0] = 0; | ||
112 | } | ||
113 | |||
114 | static noinline void __init kmalloc_pagealloc_invalid_free(void) | ||
115 | { | ||
116 | char *ptr; | ||
117 | size_t size = KMALLOC_MAX_CACHE_SIZE + 10; | ||
118 | |||
119 | pr_info("kmalloc pagealloc allocation: invalid-free\n"); | ||
120 | ptr = kmalloc(size, GFP_KERNEL); | ||
121 | if (!ptr) { | ||
122 | pr_err("Allocation failed\n"); | ||
123 | return; | ||
124 | } | ||
125 | |||
126 | kfree(ptr + 1); | ||
127 | } | ||
97 | #endif | 128 | #endif |
98 | 129 | ||
99 | static noinline void __init kmalloc_large_oob_right(void) | 130 | static noinline void __init kmalloc_large_oob_right(void) |
@@ -505,6 +536,8 @@ static int __init kmalloc_tests_init(void) | |||
505 | kmalloc_node_oob_right(); | 536 | kmalloc_node_oob_right(); |
506 | #ifdef CONFIG_SLUB | 537 | #ifdef CONFIG_SLUB |
507 | kmalloc_pagealloc_oob_right(); | 538 | kmalloc_pagealloc_oob_right(); |
539 | kmalloc_pagealloc_uaf(); | ||
540 | kmalloc_pagealloc_invalid_free(); | ||
508 | #endif | 541 | #endif |
509 | kmalloc_large_oob_right(); | 542 | kmalloc_large_oob_right(); |
510 | kmalloc_oob_krealloc_more(); | 543 | kmalloc_oob_krealloc_more(); |
diff --git a/mm/kasan/kasan.c b/mm/kasan/kasan.c index 8aaee42fcfab..ecb64fda79e6 100644 --- a/mm/kasan/kasan.c +++ b/mm/kasan/kasan.c | |||
@@ -511,8 +511,7 @@ bool kasan_slab_free(struct kmem_cache *cache, void *object) | |||
511 | 511 | ||
512 | shadow_byte = READ_ONCE(*(s8 *)kasan_mem_to_shadow(object)); | 512 | shadow_byte = READ_ONCE(*(s8 *)kasan_mem_to_shadow(object)); |
513 | if (shadow_byte < 0 || shadow_byte >= KASAN_SHADOW_SCALE_SIZE) { | 513 | if (shadow_byte < 0 || shadow_byte >= KASAN_SHADOW_SCALE_SIZE) { |
514 | kasan_report_double_free(cache, object, | 514 | kasan_report_invalid_free(object, __builtin_return_address(1)); |
515 | __builtin_return_address(1)); | ||
516 | return true; | 515 | return true; |
517 | } | 516 | } |
518 | 517 | ||
@@ -602,12 +601,11 @@ void kasan_poison_kfree(void *ptr) | |||
602 | kasan_poison_slab_free(page->slab_cache, ptr); | 601 | kasan_poison_slab_free(page->slab_cache, ptr); |
603 | } | 602 | } |
604 | 603 | ||
605 | void kasan_kfree_large(const void *ptr) | 604 | void kasan_kfree_large(void *ptr) |
606 | { | 605 | { |
607 | struct page *page = virt_to_page(ptr); | 606 | if (ptr != page_address(virt_to_head_page(ptr))) |
608 | 607 | kasan_report_invalid_free(ptr, __builtin_return_address(1)); | |
609 | kasan_poison_shadow(ptr, PAGE_SIZE << compound_order(page), | 608 | /* The object will be poisoned by page_alloc. */ |
610 | KASAN_FREE_PAGE); | ||
611 | } | 609 | } |
612 | 610 | ||
613 | int kasan_module_alloc(void *addr, size_t size) | 611 | int kasan_module_alloc(void *addr, size_t size) |
diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h index 9a768dd71c51..bf353a18c908 100644 --- a/mm/kasan/kasan.h +++ b/mm/kasan/kasan.h | |||
@@ -107,8 +107,7 @@ static inline const void *kasan_shadow_to_mem(const void *shadow_addr) | |||
107 | 107 | ||
108 | void kasan_report(unsigned long addr, size_t size, | 108 | void kasan_report(unsigned long addr, size_t size, |
109 | bool is_write, unsigned long ip); | 109 | bool is_write, unsigned long ip); |
110 | void kasan_report_double_free(struct kmem_cache *cache, void *object, | 110 | void kasan_report_invalid_free(void *object, void *ip); |
111 | void *ip); | ||
112 | 111 | ||
113 | #if defined(CONFIG_SLAB) || defined(CONFIG_SLUB) | 112 | #if defined(CONFIG_SLAB) || defined(CONFIG_SLUB) |
114 | void quarantine_put(struct kasan_free_meta *info, struct kmem_cache *cache); | 113 | void quarantine_put(struct kasan_free_meta *info, struct kmem_cache *cache); |
diff --git a/mm/kasan/report.c b/mm/kasan/report.c index eff12e040498..55916ad21722 100644 --- a/mm/kasan/report.c +++ b/mm/kasan/report.c | |||
@@ -326,8 +326,7 @@ static void print_shadow_for_address(const void *addr) | |||
326 | } | 326 | } |
327 | } | 327 | } |
328 | 328 | ||
329 | void kasan_report_double_free(struct kmem_cache *cache, void *object, | 329 | void kasan_report_invalid_free(void *object, void *ip) |
330 | void *ip) | ||
331 | { | 330 | { |
332 | unsigned long flags; | 331 | unsigned long flags; |
333 | 332 | ||
@@ -1356,7 +1356,7 @@ static inline void kmalloc_large_node_hook(void *ptr, size_t size, gfp_t flags) | |||
1356 | kasan_kmalloc_large(ptr, size, flags); | 1356 | kasan_kmalloc_large(ptr, size, flags); |
1357 | } | 1357 | } |
1358 | 1358 | ||
1359 | static inline void kfree_hook(const void *x) | 1359 | static inline void kfree_hook(void *x) |
1360 | { | 1360 | { |
1361 | kmemleak_free(x); | 1361 | kmemleak_free(x); |
1362 | kasan_kfree_large(x); | 1362 | kasan_kfree_large(x); |
@@ -3910,7 +3910,7 @@ void kfree(const void *x) | |||
3910 | page = virt_to_head_page(x); | 3910 | page = virt_to_head_page(x); |
3911 | if (unlikely(!PageSlab(page))) { | 3911 | if (unlikely(!PageSlab(page))) { |
3912 | BUG_ON(!PageCompound(page)); | 3912 | BUG_ON(!PageCompound(page)); |
3913 | kfree_hook(x); | 3913 | kfree_hook(object); |
3914 | __free_pages(page, compound_order(page)); | 3914 | __free_pages(page, compound_order(page)); |
3915 | return; | 3915 | return; |
3916 | } | 3916 | } |