Commit | Line | Data |
---|---|---|
dfd402a4 ME |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | ||
3 | #include <linux/atomic.h> | |
4 | #include <linux/bsearch.h> | |
5 | #include <linux/bug.h> | |
6 | #include <linux/debugfs.h> | |
7 | #include <linux/init.h> | |
8 | #include <linux/kallsyms.h> | |
a3120135 | 9 | #include <linux/sched.h> |
dfd402a4 ME |
10 | #include <linux/seq_file.h> |
11 | #include <linux/slab.h> | |
12 | #include <linux/sort.h> | |
13 | #include <linux/string.h> | |
14 | #include <linux/uaccess.h> | |
15 | ||
16 | #include "kcsan.h" | |
17 | ||
18 | /* | |
19 | * Statistics counters. | |
20 | */ | |
21 | static atomic_long_t counters[KCSAN_COUNTER_COUNT]; | |
22 | ||
23 | /* | |
24 | * Addresses for filtering functions from reporting. This list can be used as a | |
25 | * whitelist or blacklist. | |
26 | */ | |
27 | static struct { | |
5cbaefe9 IM |
28 | unsigned long *addrs; /* array of addresses */ |
29 | size_t size; /* current size */ | |
30 | int used; /* number of elements used */ | |
31 | bool sorted; /* if elements are sorted */ | |
32 | bool whitelist; /* if list is a blacklist or whitelist */ | |
dfd402a4 | 33 | } report_filterlist = { |
5cbaefe9 IM |
34 | .addrs = NULL, |
35 | .size = 8, /* small initial size */ | |
36 | .used = 0, | |
37 | .sorted = false, | |
38 | .whitelist = false, /* default is blacklist */ | |
dfd402a4 ME |
39 | }; |
40 | static DEFINE_SPINLOCK(report_filterlist_lock); | |
41 | ||
42 | static const char *counter_to_name(enum kcsan_counter_id id) | |
43 | { | |
44 | switch (id) { | |
5cbaefe9 IM |
45 | case KCSAN_COUNTER_USED_WATCHPOINTS: return "used_watchpoints"; |
46 | case KCSAN_COUNTER_SETUP_WATCHPOINTS: return "setup_watchpoints"; | |
47 | case KCSAN_COUNTER_DATA_RACES: return "data_races"; | |
d591ec3d | 48 | case KCSAN_COUNTER_ASSERT_FAILURES: return "assert_failures"; |
5cbaefe9 IM |
49 | case KCSAN_COUNTER_NO_CAPACITY: return "no_capacity"; |
50 | case KCSAN_COUNTER_REPORT_RACES: return "report_races"; | |
51 | case KCSAN_COUNTER_RACES_UNKNOWN_ORIGIN: return "races_unknown_origin"; | |
52 | case KCSAN_COUNTER_UNENCODABLE_ACCESSES: return "unencodable_accesses"; | |
53 | case KCSAN_COUNTER_ENCODING_FALSE_POSITIVES: return "encoding_false_positives"; | |
dfd402a4 ME |
54 | case KCSAN_COUNTER_COUNT: |
55 | BUG(); | |
56 | } | |
57 | return NULL; | |
58 | } | |
59 | ||
60 | void kcsan_counter_inc(enum kcsan_counter_id id) | |
61 | { | |
62 | atomic_long_inc(&counters[id]); | |
63 | } | |
64 | ||
65 | void kcsan_counter_dec(enum kcsan_counter_id id) | |
66 | { | |
67 | atomic_long_dec(&counters[id]); | |
68 | } | |
69 | ||
70 | /* | |
71 | * The microbenchmark allows benchmarking KCSAN core runtime only. To run | |
72 | * multiple threads, pipe 'microbench=<iters>' from multiple tasks into the | |
a3120135 | 73 | * debugfs file. This will not generate any conflicts, and tests fast-path only. |
dfd402a4 | 74 | */ |
a3120135 | 75 | static noinline void microbenchmark(unsigned long iters) |
dfd402a4 ME |
76 | { |
77 | cycles_t cycles; | |
78 | ||
79 | pr_info("KCSAN: %s begin | iters: %lu\n", __func__, iters); | |
80 | ||
81 | cycles = get_cycles(); | |
82 | while (iters--) { | |
83 | /* | |
84 | * We can run this benchmark from multiple tasks; this address | |
a3120135 ME |
85 | * calculation increases likelyhood of some accesses |
86 | * overlapping. Make the access type an atomic read, to never | |
87 | * set up watchpoints and test the fast-path only. | |
dfd402a4 ME |
88 | */ |
89 | unsigned long addr = | |
90 | iters % (CONFIG_KCSAN_NUM_WATCHPOINTS * PAGE_SIZE); | |
a3120135 | 91 | __kcsan_check_access((void *)addr, sizeof(long), KCSAN_ACCESS_ATOMIC); |
dfd402a4 ME |
92 | } |
93 | cycles = get_cycles() - cycles; | |
94 | ||
95 | pr_info("KCSAN: %s end | cycles: %llu\n", __func__, cycles); | |
96 | } | |
97 | ||
a3120135 ME |
98 | /* |
99 | * Simple test to create conflicting accesses. Write 'test=<iters>' to KCSAN's | |
100 | * debugfs file from multiple tasks to generate real conflicts and show reports. | |
101 | */ | |
102 | static long test_dummy; | |
703b3215 | 103 | static long test_flags; |
a3120135 ME |
104 | static noinline void test_thread(unsigned long iters) |
105 | { | |
703b3215 | 106 | const long CHANGE_BITS = 0xff00ff00ff00ff00L; |
a3120135 ME |
107 | const struct kcsan_ctx ctx_save = current->kcsan_ctx; |
108 | cycles_t cycles; | |
109 | ||
110 | /* We may have been called from an atomic region; reset context. */ | |
111 | memset(¤t->kcsan_ctx, 0, sizeof(current->kcsan_ctx)); | |
112 | ||
113 | pr_info("KCSAN: %s begin | iters: %lu\n", __func__, iters); | |
703b3215 | 114 | pr_info("test_dummy@%px, test_flags@%px\n", &test_dummy, &test_flags); |
a3120135 ME |
115 | |
116 | cycles = get_cycles(); | |
117 | while (iters--) { | |
703b3215 | 118 | /* These all should generate reports. */ |
a3120135 | 119 | __kcsan_check_read(&test_dummy, sizeof(test_dummy)); |
a3120135 ME |
120 | ASSERT_EXCLUSIVE_WRITER(test_dummy); |
121 | ASSERT_EXCLUSIVE_ACCESS(test_dummy); | |
122 | ||
703b3215 ME |
123 | ASSERT_EXCLUSIVE_BITS(test_flags, ~CHANGE_BITS); /* no report */ |
124 | __kcsan_check_read(&test_flags, sizeof(test_flags)); /* no report */ | |
125 | ||
126 | ASSERT_EXCLUSIVE_BITS(test_flags, CHANGE_BITS); /* report */ | |
127 | __kcsan_check_read(&test_flags, sizeof(test_flags)); /* no report */ | |
128 | ||
a3120135 ME |
129 | /* not actually instrumented */ |
130 | WRITE_ONCE(test_dummy, iters); /* to observe value-change */ | |
703b3215 ME |
131 | __kcsan_check_write(&test_dummy, sizeof(test_dummy)); |
132 | ||
133 | test_flags ^= CHANGE_BITS; /* generate value-change */ | |
134 | __kcsan_check_write(&test_flags, sizeof(test_flags)); | |
a3120135 ME |
135 | } |
136 | cycles = get_cycles() - cycles; | |
137 | ||
138 | pr_info("KCSAN: %s end | cycles: %llu\n", __func__, cycles); | |
139 | ||
140 | /* restore context */ | |
141 | current->kcsan_ctx = ctx_save; | |
142 | } | |
143 | ||
dfd402a4 ME |
144 | static int cmp_filterlist_addrs(const void *rhs, const void *lhs) |
145 | { | |
146 | const unsigned long a = *(const unsigned long *)rhs; | |
147 | const unsigned long b = *(const unsigned long *)lhs; | |
148 | ||
149 | return a < b ? -1 : a == b ? 0 : 1; | |
150 | } | |
151 | ||
152 | bool kcsan_skip_report_debugfs(unsigned long func_addr) | |
153 | { | |
154 | unsigned long symbolsize, offset; | |
155 | unsigned long flags; | |
156 | bool ret = false; | |
157 | ||
158 | if (!kallsyms_lookup_size_offset(func_addr, &symbolsize, &offset)) | |
159 | return false; | |
5cbaefe9 | 160 | func_addr -= offset; /* Get function start */ |
dfd402a4 ME |
161 | |
162 | spin_lock_irqsave(&report_filterlist_lock, flags); | |
163 | if (report_filterlist.used == 0) | |
164 | goto out; | |
165 | ||
166 | /* Sort array if it is unsorted, and then do a binary search. */ | |
167 | if (!report_filterlist.sorted) { | |
168 | sort(report_filterlist.addrs, report_filterlist.used, | |
169 | sizeof(unsigned long), cmp_filterlist_addrs, NULL); | |
170 | report_filterlist.sorted = true; | |
171 | } | |
172 | ret = !!bsearch(&func_addr, report_filterlist.addrs, | |
173 | report_filterlist.used, sizeof(unsigned long), | |
174 | cmp_filterlist_addrs); | |
175 | if (report_filterlist.whitelist) | |
176 | ret = !ret; | |
177 | ||
178 | out: | |
179 | spin_unlock_irqrestore(&report_filterlist_lock, flags); | |
180 | return ret; | |
181 | } | |
182 | ||
183 | static void set_report_filterlist_whitelist(bool whitelist) | |
184 | { | |
185 | unsigned long flags; | |
186 | ||
187 | spin_lock_irqsave(&report_filterlist_lock, flags); | |
188 | report_filterlist.whitelist = whitelist; | |
189 | spin_unlock_irqrestore(&report_filterlist_lock, flags); | |
190 | } | |
191 | ||
192 | /* Returns 0 on success, error-code otherwise. */ | |
193 | static ssize_t insert_report_filterlist(const char *func) | |
194 | { | |
195 | unsigned long flags; | |
196 | unsigned long addr = kallsyms_lookup_name(func); | |
197 | ssize_t ret = 0; | |
198 | ||
199 | if (!addr) { | |
200 | pr_err("KCSAN: could not find function: '%s'\n", func); | |
201 | return -ENOENT; | |
202 | } | |
203 | ||
204 | spin_lock_irqsave(&report_filterlist_lock, flags); | |
205 | ||
206 | if (report_filterlist.addrs == NULL) { | |
207 | /* initial allocation */ | |
208 | report_filterlist.addrs = | |
209 | kmalloc_array(report_filterlist.size, | |
210 | sizeof(unsigned long), GFP_KERNEL); | |
211 | if (report_filterlist.addrs == NULL) { | |
212 | ret = -ENOMEM; | |
213 | goto out; | |
214 | } | |
215 | } else if (report_filterlist.used == report_filterlist.size) { | |
216 | /* resize filterlist */ | |
217 | size_t new_size = report_filterlist.size * 2; | |
218 | unsigned long *new_addrs = | |
219 | krealloc(report_filterlist.addrs, | |
220 | new_size * sizeof(unsigned long), GFP_KERNEL); | |
221 | ||
222 | if (new_addrs == NULL) { | |
223 | /* leave filterlist itself untouched */ | |
224 | ret = -ENOMEM; | |
225 | goto out; | |
226 | } | |
227 | ||
228 | report_filterlist.size = new_size; | |
229 | report_filterlist.addrs = new_addrs; | |
230 | } | |
231 | ||
232 | /* Note: deduplicating should be done in userspace. */ | |
233 | report_filterlist.addrs[report_filterlist.used++] = | |
234 | kallsyms_lookup_name(func); | |
235 | report_filterlist.sorted = false; | |
236 | ||
237 | out: | |
238 | spin_unlock_irqrestore(&report_filterlist_lock, flags); | |
5cbaefe9 | 239 | |
dfd402a4 ME |
240 | return ret; |
241 | } | |
242 | ||
243 | static int show_info(struct seq_file *file, void *v) | |
244 | { | |
245 | int i; | |
246 | unsigned long flags; | |
247 | ||
248 | /* show stats */ | |
249 | seq_printf(file, "enabled: %i\n", READ_ONCE(kcsan_enabled)); | |
250 | for (i = 0; i < KCSAN_COUNTER_COUNT; ++i) | |
251 | seq_printf(file, "%s: %ld\n", counter_to_name(i), | |
252 | atomic_long_read(&counters[i])); | |
253 | ||
254 | /* show filter functions, and filter type */ | |
255 | spin_lock_irqsave(&report_filterlist_lock, flags); | |
256 | seq_printf(file, "\n%s functions: %s\n", | |
257 | report_filterlist.whitelist ? "whitelisted" : "blacklisted", | |
258 | report_filterlist.used == 0 ? "none" : ""); | |
259 | for (i = 0; i < report_filterlist.used; ++i) | |
260 | seq_printf(file, " %ps\n", (void *)report_filterlist.addrs[i]); | |
261 | spin_unlock_irqrestore(&report_filterlist_lock, flags); | |
262 | ||
263 | return 0; | |
264 | } | |
265 | ||
266 | static int debugfs_open(struct inode *inode, struct file *file) | |
267 | { | |
268 | return single_open(file, show_info, NULL); | |
269 | } | |
270 | ||
5cbaefe9 IM |
271 | static ssize_t |
272 | debugfs_write(struct file *file, const char __user *buf, size_t count, loff_t *off) | |
dfd402a4 ME |
273 | { |
274 | char kbuf[KSYM_NAME_LEN]; | |
275 | char *arg; | |
276 | int read_len = count < (sizeof(kbuf) - 1) ? count : (sizeof(kbuf) - 1); | |
277 | ||
278 | if (copy_from_user(kbuf, buf, read_len)) | |
279 | return -EFAULT; | |
280 | kbuf[read_len] = '\0'; | |
281 | arg = strstrip(kbuf); | |
282 | ||
283 | if (!strcmp(arg, "on")) { | |
284 | WRITE_ONCE(kcsan_enabled, true); | |
285 | } else if (!strcmp(arg, "off")) { | |
286 | WRITE_ONCE(kcsan_enabled, false); | |
287 | } else if (!strncmp(arg, "microbench=", sizeof("microbench=") - 1)) { | |
288 | unsigned long iters; | |
289 | ||
290 | if (kstrtoul(&arg[sizeof("microbench=") - 1], 0, &iters)) | |
291 | return -EINVAL; | |
292 | microbenchmark(iters); | |
a3120135 ME |
293 | } else if (!strncmp(arg, "test=", sizeof("test=") - 1)) { |
294 | unsigned long iters; | |
295 | ||
296 | if (kstrtoul(&arg[sizeof("test=") - 1], 0, &iters)) | |
297 | return -EINVAL; | |
298 | test_thread(iters); | |
dfd402a4 ME |
299 | } else if (!strcmp(arg, "whitelist")) { |
300 | set_report_filterlist_whitelist(true); | |
301 | } else if (!strcmp(arg, "blacklist")) { | |
302 | set_report_filterlist_whitelist(false); | |
303 | } else if (arg[0] == '!') { | |
304 | ssize_t ret = insert_report_filterlist(&arg[1]); | |
305 | ||
306 | if (ret < 0) | |
307 | return ret; | |
308 | } else { | |
309 | return -EINVAL; | |
310 | } | |
311 | ||
312 | return count; | |
313 | } | |
314 | ||
5cbaefe9 IM |
315 | static const struct file_operations debugfs_ops = |
316 | { | |
317 | .read = seq_read, | |
318 | .open = debugfs_open, | |
319 | .write = debugfs_write, | |
320 | .release = single_release | |
321 | }; | |
dfd402a4 ME |
322 | |
323 | void __init kcsan_debugfs_init(void) | |
324 | { | |
325 | debugfs_create_file("kcsan", 0644, NULL, NULL, &debugfs_ops); | |
326 | } |