kcsan: Add option for verbose reporting
[linux-block.git] / kernel / kcsan / debugfs.c
CommitLineData
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 */
21static 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 */
27static 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};
40static DEFINE_SPINLOCK(report_filterlist_lock);
41
42static 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
60void kcsan_counter_inc(enum kcsan_counter_id id)
61{
62 atomic_long_inc(&counters[id]);
63}
64
65void 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 75static 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 */
102static long test_dummy;
703b3215 103static long test_flags;
a3120135
ME
104static 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(&current->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
144static 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
152bool 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
178out:
179 spin_unlock_irqrestore(&report_filterlist_lock, flags);
180 return ret;
181}
182
183static 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. */
193static 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
237out:
238 spin_unlock_irqrestore(&report_filterlist_lock, flags);
5cbaefe9 239
dfd402a4
ME
240 return ret;
241}
242
243static 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
266static int debugfs_open(struct inode *inode, struct file *file)
267{
268 return single_open(file, show_info, NULL);
269}
270
5cbaefe9
IM
271static ssize_t
272debugfs_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
315static 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
323void __init kcsan_debugfs_init(void)
324{
325 debugfs_create_file("kcsan", 0644, NULL, NULL, &debugfs_ops);
326}