aboutsummaryrefslogtreecommitdiffstats
path: root/mm/kasan/report.c
diff options
context:
space:
mode:
authorAndrey Ryabinin <a.ryabinin@samsung.com>2015-02-13 17:39:17 -0500
committerLinus Torvalds <torvalds@linux-foundation.org>2015-02-14 00:21:40 -0500
commit0b24becc810dc3be6e3f94103a866f214c282394 (patch)
treead4ca40742a383f4b9e7385cc7f2506eee1aa24b /mm/kasan/report.c
parentcb4188ac8e5779f66b9f55888ac2c75b391cde44 (diff)
kasan: add kernel address sanitizer infrastructure
Kernel Address sanitizer (KASan) is a dynamic memory error detector. It provides fast and comprehensive solution for finding use-after-free and out-of-bounds bugs. KASAN uses compile-time instrumentation for checking every memory access, therefore GCC > v4.9.2 required. v4.9.2 almost works, but has issues with putting symbol aliases into the wrong section, which breaks kasan instrumentation of globals. This patch only adds infrastructure for kernel address sanitizer. It's not available for use yet. The idea and some code was borrowed from [1]. Basic idea: The main idea of KASAN is to use shadow memory to record whether each byte of memory is safe to access or not, and use compiler's instrumentation to check the shadow memory on each memory access. Address sanitizer uses 1/8 of the memory addressable in kernel for shadow memory and uses direct mapping with a scale and offset to translate a memory address to its corresponding shadow address. Here is function to translate address to corresponding shadow address: unsigned long kasan_mem_to_shadow(unsigned long addr) { return (addr >> KASAN_SHADOW_SCALE_SHIFT) + KASAN_SHADOW_OFFSET; } where KASAN_SHADOW_SCALE_SHIFT = 3. So for every 8 bytes there is one corresponding byte of shadow memory. The following encoding used for each shadow byte: 0 means that all 8 bytes of the corresponding memory region are valid for access; k (1 <= k <= 7) means that the first k bytes are valid for access, and other (8 - k) bytes are not; Any negative value indicates that the entire 8-bytes are inaccessible. Different negative values used to distinguish between different kinds of inaccessible memory (redzones, freed memory) (see mm/kasan/kasan.h). To be able to detect accesses to bad memory we need a special compiler. Such compiler inserts a specific function calls (__asan_load*(addr), __asan_store*(addr)) before each memory access of size 1, 2, 4, 8 or 16. These functions check whether memory region is valid to access or not by checking corresponding shadow memory. If access is not valid an error printed. Historical background of the address sanitizer from Dmitry Vyukov: "We've developed the set of tools, AddressSanitizer (Asan), ThreadSanitizer and MemorySanitizer, for user space. We actively use them for testing inside of Google (continuous testing, fuzzing, running prod services). To date the tools have found more than 10'000 scary bugs in Chromium, Google internal codebase and various open-source projects (Firefox, OpenSSL, gcc, clang, ffmpeg, MySQL and lots of others): [2] [3] [4]. The tools are part of both gcc and clang compilers. We have not yet done massive testing under the Kernel AddressSanitizer (it's kind of chicken and egg problem, you need it to be upstream to start applying it extensively). To date it has found about 50 bugs. Bugs that we've found in upstream kernel are listed in [5]. We've also found ~20 bugs in out internal version of the kernel. Also people from Samsung and Oracle have found some. [...] As others noted, the main feature of AddressSanitizer is its performance due to inline compiler instrumentation and simple linear shadow memory. User-space Asan has ~2x slowdown on computational programs and ~2x memory consumption increase. Taking into account that kernel usually consumes only small fraction of CPU and memory when running real user-space programs, I would expect that kernel Asan will have ~10-30% slowdown and similar memory consumption increase (when we finish all tuning). I agree that Asan can well replace kmemcheck. We have plans to start working on Kernel MemorySanitizer that finds uses of unitialized memory. Asan+Msan will provide feature-parity with kmemcheck. As others noted, Asan will unlikely replace debug slab and pagealloc that can be enabled at runtime. Asan uses compiler instrumentation, so even if it is disabled, it still incurs visible overheads. Asan technology is easily portable to other architectures. Compiler instrumentation is fully portable. Runtime has some arch-dependent parts like shadow mapping and atomic operation interception. They are relatively easy to port." Comparison with other debugging features: ======================================== KMEMCHECK: - KASan can do almost everything that kmemcheck can. KASan uses compile-time instrumentation, which makes it significantly faster than kmemcheck. The only advantage of kmemcheck over KASan is detection of uninitialized memory reads. Some brief performance testing showed that kasan could be x500-x600 times faster than kmemcheck: $ netperf -l 30 MIGRATED TCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to localhost (127.0.0.1) port 0 AF_INET Recv Send Send Socket Socket Message Elapsed Size Size Size Time Throughput bytes bytes bytes secs. 10^6bits/sec no debug: 87380 16384 16384 30.00 41624.72 kasan inline: 87380 16384 16384 30.00 12870.54 kasan outline: 87380 16384 16384 30.00 10586.39 kmemcheck: 87380 16384 16384 30.03 20.23 - Also kmemcheck couldn't work on several CPUs. It always sets number of CPUs to 1. KASan doesn't have such limitation. DEBUG_PAGEALLOC: - KASan is slower than DEBUG_PAGEALLOC, but KASan works on sub-page granularity level, so it able to find more bugs. SLUB_DEBUG (poisoning, redzones): - SLUB_DEBUG has lower overhead than KASan. - SLUB_DEBUG in most cases are not able to detect bad reads, KASan able to detect both reads and writes. - In some cases (e.g. redzone overwritten) SLUB_DEBUG detect bugs only on allocation/freeing of object. KASan catch bugs right before it will happen, so we always know exact place of first bad read/write. [1] https://code.google.com/p/address-sanitizer/wiki/AddressSanitizerForKernel [2] https://code.google.com/p/address-sanitizer/wiki/FoundBugs [3] https://code.google.com/p/thread-sanitizer/wiki/FoundBugs [4] https://code.google.com/p/memory-sanitizer/wiki/FoundBugs [5] https://code.google.com/p/address-sanitizer/wiki/AddressSanitizerForKernel#Trophies Based on work by Andrey Konovalov. Signed-off-by: Andrey Ryabinin <a.ryabinin@samsung.com> Acked-by: Michal Marek <mmarek@suse.cz> Signed-off-by: Andrey Konovalov <adech.fo@gmail.com> Cc: Dmitry Vyukov <dvyukov@google.com> Cc: Konstantin Serebryany <kcc@google.com> Cc: Dmitry Chernenkov <dmitryc@google.com> Cc: Yuri Gribov <tetra2005@gmail.com> Cc: Konstantin Khlebnikov <koct9i@gmail.com> Cc: Sasha Levin <sasha.levin@oracle.com> Cc: Christoph Lameter <cl@linux.com> Cc: Joonsoo Kim <iamjoonsoo.kim@lge.com> Cc: Dave Hansen <dave.hansen@intel.com> Cc: Andi Kleen <andi@firstfloor.org> Cc: Ingo Molnar <mingo@elte.hu> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: "H. Peter Anvin" <hpa@zytor.com> Cc: Christoph Lameter <cl@linux.com> Cc: Pekka Enberg <penberg@kernel.org> Cc: David Rientjes <rientjes@google.com> Cc: Stephen Rothwell <sfr@canb.auug.org.au> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'mm/kasan/report.c')
-rw-r--r--mm/kasan/report.c209
1 files changed, 209 insertions, 0 deletions
diff --git a/mm/kasan/report.c b/mm/kasan/report.c
new file mode 100644
index 000000000000..5835d69563f5
--- /dev/null
+++ b/mm/kasan/report.c
@@ -0,0 +1,209 @@
1/*
2 * This file contains error reporting code.
3 *
4 * Copyright (c) 2014 Samsung Electronics Co., Ltd.
5 * Author: Andrey Ryabinin <a.ryabinin@samsung.com>
6 *
7 * Some of code borrowed from https://github.com/xairy/linux by
8 * Andrey Konovalov <adech.fo@gmail.com>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2 as
12 * published by the Free Software Foundation.
13 *
14 */
15
16#include <linux/kernel.h>
17#include <linux/mm.h>
18#include <linux/printk.h>
19#include <linux/sched.h>
20#include <linux/slab.h>
21#include <linux/stacktrace.h>
22#include <linux/string.h>
23#include <linux/types.h>
24#include <linux/kasan.h>
25
26#include "kasan.h"
27
28/* Shadow layout customization. */
29#define SHADOW_BYTES_PER_BLOCK 1
30#define SHADOW_BLOCKS_PER_ROW 16
31#define SHADOW_BYTES_PER_ROW (SHADOW_BLOCKS_PER_ROW * SHADOW_BYTES_PER_BLOCK)
32#define SHADOW_ROWS_AROUND_ADDR 2
33
34static const void *find_first_bad_addr(const void *addr, size_t size)
35{
36 u8 shadow_val = *(u8 *)kasan_mem_to_shadow(addr);
37 const void *first_bad_addr = addr;
38
39 while (!shadow_val && first_bad_addr < addr + size) {
40 first_bad_addr += KASAN_SHADOW_SCALE_SIZE;
41 shadow_val = *(u8 *)kasan_mem_to_shadow(first_bad_addr);
42 }
43 return first_bad_addr;
44}
45
46static void print_error_description(struct kasan_access_info *info)
47{
48 const char *bug_type = "unknown crash";
49 u8 shadow_val;
50
51 info->first_bad_addr = find_first_bad_addr(info->access_addr,
52 info->access_size);
53
54 shadow_val = *(u8 *)kasan_mem_to_shadow(info->first_bad_addr);
55
56 switch (shadow_val) {
57 case 0 ... KASAN_SHADOW_SCALE_SIZE - 1:
58 bug_type = "out of bounds access";
59 break;
60 }
61
62 pr_err("BUG: KASan: %s in %pS at addr %p\n",
63 bug_type, (void *)info->ip,
64 info->access_addr);
65 pr_err("%s of size %zu by task %s/%d\n",
66 info->is_write ? "Write" : "Read",
67 info->access_size, current->comm, task_pid_nr(current));
68}
69
70static void print_address_description(struct kasan_access_info *info)
71{
72 dump_stack();
73}
74
75static bool row_is_guilty(const void *row, const void *guilty)
76{
77 return (row <= guilty) && (guilty < row + SHADOW_BYTES_PER_ROW);
78}
79
80static int shadow_pointer_offset(const void *row, const void *shadow)
81{
82 /* The length of ">ff00ff00ff00ff00: " is
83 * 3 + (BITS_PER_LONG/8)*2 chars.
84 */
85 return 3 + (BITS_PER_LONG/8)*2 + (shadow - row)*2 +
86 (shadow - row) / SHADOW_BYTES_PER_BLOCK + 1;
87}
88
89static void print_shadow_for_address(const void *addr)
90{
91 int i;
92 const void *shadow = kasan_mem_to_shadow(addr);
93 const void *shadow_row;
94
95 shadow_row = (void *)round_down((unsigned long)shadow,
96 SHADOW_BYTES_PER_ROW)
97 - SHADOW_ROWS_AROUND_ADDR * SHADOW_BYTES_PER_ROW;
98
99 pr_err("Memory state around the buggy address:\n");
100
101 for (i = -SHADOW_ROWS_AROUND_ADDR; i <= SHADOW_ROWS_AROUND_ADDR; i++) {
102 const void *kaddr = kasan_shadow_to_mem(shadow_row);
103 char buffer[4 + (BITS_PER_LONG/8)*2];
104
105 snprintf(buffer, sizeof(buffer),
106 (i == 0) ? ">%p: " : " %p: ", kaddr);
107
108 kasan_disable_current();
109 print_hex_dump(KERN_ERR, buffer,
110 DUMP_PREFIX_NONE, SHADOW_BYTES_PER_ROW, 1,
111 shadow_row, SHADOW_BYTES_PER_ROW, 0);
112 kasan_enable_current();
113
114 if (row_is_guilty(shadow_row, shadow))
115 pr_err("%*c\n",
116 shadow_pointer_offset(shadow_row, shadow),
117 '^');
118
119 shadow_row += SHADOW_BYTES_PER_ROW;
120 }
121}
122
123static DEFINE_SPINLOCK(report_lock);
124
125void kasan_report_error(struct kasan_access_info *info)
126{
127 unsigned long flags;
128
129 spin_lock_irqsave(&report_lock, flags);
130 pr_err("================================="
131 "=================================\n");
132 print_error_description(info);
133 print_address_description(info);
134 print_shadow_for_address(info->first_bad_addr);
135 pr_err("================================="
136 "=================================\n");
137 spin_unlock_irqrestore(&report_lock, flags);
138}
139
140void kasan_report_user_access(struct kasan_access_info *info)
141{
142 unsigned long flags;
143
144 spin_lock_irqsave(&report_lock, flags);
145 pr_err("================================="
146 "=================================\n");
147 pr_err("BUG: KASan: user-memory-access on address %p\n",
148 info->access_addr);
149 pr_err("%s of size %zu by task %s/%d\n",
150 info->is_write ? "Write" : "Read",
151 info->access_size, current->comm, task_pid_nr(current));
152 dump_stack();
153 pr_err("================================="
154 "=================================\n");
155 spin_unlock_irqrestore(&report_lock, flags);
156}
157
158void kasan_report(unsigned long addr, size_t size,
159 bool is_write, unsigned long ip)
160{
161 struct kasan_access_info info;
162
163 if (likely(!kasan_enabled()))
164 return;
165
166 info.access_addr = (void *)addr;
167 info.access_size = size;
168 info.is_write = is_write;
169 info.ip = ip;
170 kasan_report_error(&info);
171}
172
173
174#define DEFINE_ASAN_REPORT_LOAD(size) \
175void __asan_report_load##size##_noabort(unsigned long addr) \
176{ \
177 kasan_report(addr, size, false, _RET_IP_); \
178} \
179EXPORT_SYMBOL(__asan_report_load##size##_noabort)
180
181#define DEFINE_ASAN_REPORT_STORE(size) \
182void __asan_report_store##size##_noabort(unsigned long addr) \
183{ \
184 kasan_report(addr, size, true, _RET_IP_); \
185} \
186EXPORT_SYMBOL(__asan_report_store##size##_noabort)
187
188DEFINE_ASAN_REPORT_LOAD(1);
189DEFINE_ASAN_REPORT_LOAD(2);
190DEFINE_ASAN_REPORT_LOAD(4);
191DEFINE_ASAN_REPORT_LOAD(8);
192DEFINE_ASAN_REPORT_LOAD(16);
193DEFINE_ASAN_REPORT_STORE(1);
194DEFINE_ASAN_REPORT_STORE(2);
195DEFINE_ASAN_REPORT_STORE(4);
196DEFINE_ASAN_REPORT_STORE(8);
197DEFINE_ASAN_REPORT_STORE(16);
198
199void __asan_report_load_n_noabort(unsigned long addr, size_t size)
200{
201 kasan_report(addr, size, false, _RET_IP_);
202}
203EXPORT_SYMBOL(__asan_report_load_n_noabort);
204
205void __asan_report_store_n_noabort(unsigned long addr, size_t size)
206{
207 kasan_report(addr, size, true, _RET_IP_);
208}
209EXPORT_SYMBOL(__asan_report_store_n_noabort);