diff options
author | Andrey Ryabinin <aryabinin@virtuozzo.com> | 2016-08-02 17:02:55 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2016-08-02 17:31:41 -0400 |
commit | 7e088978933ee186533355ae03a9dc1de99cf6c7 (patch) | |
tree | 009af8de8842183482dc3619f429bceafd8e65bb /mm/kasan | |
parent | b3cbd9bf77cd1888114dbee1653e79aa23fd4068 (diff) |
kasan: improve double-free reports
Currently we just dump stack in case of double free bug.
Let's dump all info about the object that we have.
[aryabinin@virtuozzo.com: change double free message per Alexander]
Link: http://lkml.kernel.org/r/1470153654-30160-1-git-send-email-aryabinin@virtuozzo.com
Link: http://lkml.kernel.org/r/1470062715-14077-6-git-send-email-aryabinin@virtuozzo.com
Signed-off-by: Andrey Ryabinin <aryabinin@virtuozzo.com>
Cc: Alexander Potapenko <glider@google.com>
Cc: Dmitry Vyukov <dvyukov@google.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'mm/kasan')
-rw-r--r-- | mm/kasan/kasan.c | 3 | ||||
-rw-r--r-- | mm/kasan/kasan.h | 2 | ||||
-rw-r--r-- | mm/kasan/report.c | 54 |
3 files changed, 41 insertions, 18 deletions
diff --git a/mm/kasan/kasan.c b/mm/kasan/kasan.c index 92750e3b0083..88af13c00d3c 100644 --- a/mm/kasan/kasan.c +++ b/mm/kasan/kasan.c | |||
@@ -543,8 +543,7 @@ bool kasan_slab_free(struct kmem_cache *cache, void *object) | |||
543 | 543 | ||
544 | shadow_byte = READ_ONCE(*(s8 *)kasan_mem_to_shadow(object)); | 544 | shadow_byte = READ_ONCE(*(s8 *)kasan_mem_to_shadow(object)); |
545 | if (shadow_byte < 0 || shadow_byte >= KASAN_SHADOW_SCALE_SIZE) { | 545 | if (shadow_byte < 0 || shadow_byte >= KASAN_SHADOW_SCALE_SIZE) { |
546 | pr_err("Double free"); | 546 | kasan_report_double_free(cache, object, shadow_byte); |
547 | dump_stack(); | ||
548 | return true; | 547 | return true; |
549 | } | 548 | } |
550 | 549 | ||
diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h index 9b7b31e25fd2..e5c2181fee6f 100644 --- a/mm/kasan/kasan.h +++ b/mm/kasan/kasan.h | |||
@@ -99,6 +99,8 @@ static inline bool kasan_report_enabled(void) | |||
99 | 99 | ||
100 | void kasan_report(unsigned long addr, size_t size, | 100 | void kasan_report(unsigned long addr, size_t size, |
101 | bool is_write, unsigned long ip); | 101 | bool is_write, unsigned long ip); |
102 | void kasan_report_double_free(struct kmem_cache *cache, void *object, | ||
103 | s8 shadow); | ||
102 | 104 | ||
103 | #if defined(CONFIG_SLAB) || defined(CONFIG_SLUB) | 105 | #if defined(CONFIG_SLAB) || defined(CONFIG_SLUB) |
104 | void quarantine_put(struct kasan_free_meta *info, struct kmem_cache *cache); | 106 | 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 f437398b685a..24c1211fe9d5 100644 --- a/mm/kasan/report.c +++ b/mm/kasan/report.c | |||
@@ -116,6 +116,26 @@ static inline bool init_task_stack_addr(const void *addr) | |||
116 | sizeof(init_thread_union.stack)); | 116 | sizeof(init_thread_union.stack)); |
117 | } | 117 | } |
118 | 118 | ||
119 | static DEFINE_SPINLOCK(report_lock); | ||
120 | |||
121 | static void kasan_start_report(unsigned long *flags) | ||
122 | { | ||
123 | /* | ||
124 | * Make sure we don't end up in loop. | ||
125 | */ | ||
126 | kasan_disable_current(); | ||
127 | spin_lock_irqsave(&report_lock, *flags); | ||
128 | pr_err("==================================================================\n"); | ||
129 | } | ||
130 | |||
131 | static void kasan_end_report(unsigned long *flags) | ||
132 | { | ||
133 | pr_err("==================================================================\n"); | ||
134 | add_taint(TAINT_BAD_PAGE, LOCKDEP_NOW_UNRELIABLE); | ||
135 | spin_unlock_irqrestore(&report_lock, *flags); | ||
136 | kasan_enable_current(); | ||
137 | } | ||
138 | |||
119 | static void print_track(struct kasan_track *track) | 139 | static void print_track(struct kasan_track *track) |
120 | { | 140 | { |
121 | pr_err("PID = %u\n", track->pid); | 141 | pr_err("PID = %u\n", track->pid); |
@@ -129,8 +149,7 @@ static void print_track(struct kasan_track *track) | |||
129 | } | 149 | } |
130 | } | 150 | } |
131 | 151 | ||
132 | static void kasan_object_err(struct kmem_cache *cache, struct page *page, | 152 | static void kasan_object_err(struct kmem_cache *cache, void *object) |
133 | void *object, char *unused_reason) | ||
134 | { | 153 | { |
135 | struct kasan_alloc_meta *alloc_info = get_alloc_info(cache, object); | 154 | struct kasan_alloc_meta *alloc_info = get_alloc_info(cache, object); |
136 | 155 | ||
@@ -147,6 +166,18 @@ static void kasan_object_err(struct kmem_cache *cache, struct page *page, | |||
147 | print_track(&alloc_info->free_track); | 166 | print_track(&alloc_info->free_track); |
148 | } | 167 | } |
149 | 168 | ||
169 | void kasan_report_double_free(struct kmem_cache *cache, void *object, | ||
170 | s8 shadow) | ||
171 | { | ||
172 | unsigned long flags; | ||
173 | |||
174 | kasan_start_report(&flags); | ||
175 | pr_err("BUG: Double free or freeing an invalid pointer\n"); | ||
176 | pr_err("Unexpected shadow byte: 0x%hhX\n", shadow); | ||
177 | kasan_object_err(cache, object); | ||
178 | kasan_end_report(&flags); | ||
179 | } | ||
180 | |||
150 | static void print_address_description(struct kasan_access_info *info) | 181 | static void print_address_description(struct kasan_access_info *info) |
151 | { | 182 | { |
152 | const void *addr = info->access_addr; | 183 | const void *addr = info->access_addr; |
@@ -160,8 +191,7 @@ static void print_address_description(struct kasan_access_info *info) | |||
160 | struct kmem_cache *cache = page->slab_cache; | 191 | struct kmem_cache *cache = page->slab_cache; |
161 | object = nearest_obj(cache, page, | 192 | object = nearest_obj(cache, page, |
162 | (void *)info->access_addr); | 193 | (void *)info->access_addr); |
163 | kasan_object_err(cache, page, object, | 194 | kasan_object_err(cache, object); |
164 | "kasan: bad access detected"); | ||
165 | return; | 195 | return; |
166 | } | 196 | } |
167 | dump_page(page, "kasan: bad access detected"); | 197 | dump_page(page, "kasan: bad access detected"); |
@@ -226,19 +256,13 @@ static void print_shadow_for_address(const void *addr) | |||
226 | } | 256 | } |
227 | } | 257 | } |
228 | 258 | ||
229 | static DEFINE_SPINLOCK(report_lock); | ||
230 | |||
231 | static void kasan_report_error(struct kasan_access_info *info) | 259 | static void kasan_report_error(struct kasan_access_info *info) |
232 | { | 260 | { |
233 | unsigned long flags; | 261 | unsigned long flags; |
234 | const char *bug_type; | 262 | const char *bug_type; |
235 | 263 | ||
236 | /* | 264 | kasan_start_report(&flags); |
237 | * Make sure we don't end up in loop. | 265 | |
238 | */ | ||
239 | kasan_disable_current(); | ||
240 | spin_lock_irqsave(&report_lock, flags); | ||
241 | pr_err("==================================================================\n"); | ||
242 | if (info->access_addr < | 266 | if (info->access_addr < |
243 | kasan_shadow_to_mem((void *)KASAN_SHADOW_START)) { | 267 | kasan_shadow_to_mem((void *)KASAN_SHADOW_START)) { |
244 | if ((unsigned long)info->access_addr < PAGE_SIZE) | 268 | if ((unsigned long)info->access_addr < PAGE_SIZE) |
@@ -259,10 +283,8 @@ static void kasan_report_error(struct kasan_access_info *info) | |||
259 | print_address_description(info); | 283 | print_address_description(info); |
260 | print_shadow_for_address(info->first_bad_addr); | 284 | print_shadow_for_address(info->first_bad_addr); |
261 | } | 285 | } |
262 | pr_err("==================================================================\n"); | 286 | |
263 | add_taint(TAINT_BAD_PAGE, LOCKDEP_NOW_UNRELIABLE); | 287 | kasan_end_report(&flags); |
264 | spin_unlock_irqrestore(&report_lock, flags); | ||
265 | kasan_enable_current(); | ||
266 | } | 288 | } |
267 | 289 | ||
268 | void kasan_report(unsigned long addr, size_t size, | 290 | void kasan_report(unsigned long addr, size_t size, |