1dfd402a4SMarco Elver // SPDX-License-Identifier: GPL-2.0
2bd0ccc4aSMarco Elver /*
3bd0ccc4aSMarco Elver * KCSAN debugfs interface.
4bd0ccc4aSMarco Elver *
5bd0ccc4aSMarco Elver * Copyright (C) 2019, Google LLC.
6bd0ccc4aSMarco Elver */
7dfd402a4SMarco Elver
8178a1877SMarco Elver #define pr_fmt(fmt) "kcsan: " fmt
9178a1877SMarco Elver
10dfd402a4SMarco Elver #include <linux/atomic.h>
11dfd402a4SMarco Elver #include <linux/bsearch.h>
12dfd402a4SMarco Elver #include <linux/bug.h>
13dfd402a4SMarco Elver #include <linux/debugfs.h>
14dfd402a4SMarco Elver #include <linux/init.h>
15dfd402a4SMarco Elver #include <linux/kallsyms.h>
16a3120135SMarco Elver #include <linux/sched.h>
17dfd402a4SMarco Elver #include <linux/seq_file.h>
18dfd402a4SMarco Elver #include <linux/slab.h>
19dfd402a4SMarco Elver #include <linux/sort.h>
20dfd402a4SMarco Elver #include <linux/string.h>
21dfd402a4SMarco Elver #include <linux/uaccess.h>
22dfd402a4SMarco Elver
23dfd402a4SMarco Elver #include "kcsan.h"
24dfd402a4SMarco Elver
252e986b81SMarco Elver atomic_long_t kcsan_counters[KCSAN_COUNTER_COUNT];
2669b2c81bSMarco Elver static const char *const counter_names[] = {
2769b2c81bSMarco Elver [KCSAN_COUNTER_USED_WATCHPOINTS] = "used_watchpoints",
2869b2c81bSMarco Elver [KCSAN_COUNTER_SETUP_WATCHPOINTS] = "setup_watchpoints",
2969b2c81bSMarco Elver [KCSAN_COUNTER_DATA_RACES] = "data_races",
3069b2c81bSMarco Elver [KCSAN_COUNTER_ASSERT_FAILURES] = "assert_failures",
3169b2c81bSMarco Elver [KCSAN_COUNTER_NO_CAPACITY] = "no_capacity",
3269b2c81bSMarco Elver [KCSAN_COUNTER_REPORT_RACES] = "report_races",
3369b2c81bSMarco Elver [KCSAN_COUNTER_RACES_UNKNOWN_ORIGIN] = "races_unknown_origin",
3469b2c81bSMarco Elver [KCSAN_COUNTER_UNENCODABLE_ACCESSES] = "unencodable_accesses",
3569b2c81bSMarco Elver [KCSAN_COUNTER_ENCODING_FALSE_POSITIVES] = "encoding_false_positives",
3669b2c81bSMarco Elver };
3769b2c81bSMarco Elver static_assert(ARRAY_SIZE(counter_names) == KCSAN_COUNTER_COUNT);
38dfd402a4SMarco Elver
39dfd402a4SMarco Elver /*
40dfd402a4SMarco Elver * Addresses for filtering functions from reporting. This list can be used as a
41dfd402a4SMarco Elver * whitelist or blacklist.
42dfd402a4SMarco Elver */
43dfd402a4SMarco Elver static struct {
44dfd402a4SMarco Elver unsigned long *addrs; /* array of addresses */
45dfd402a4SMarco Elver size_t size; /* current size */
46dfd402a4SMarco Elver int used; /* number of elements used */
47dfd402a4SMarco Elver bool sorted; /* if elements are sorted */
48dfd402a4SMarco Elver bool whitelist; /* if list is a blacklist or whitelist */
4959458fa4SMarco Elver } report_filterlist;
5059458fa4SMarco Elver static DEFINE_RAW_SPINLOCK(report_filterlist_lock);
51dfd402a4SMarco Elver
52dfd402a4SMarco Elver /*
53dfd402a4SMarco Elver * The microbenchmark allows benchmarking KCSAN core runtime only. To run
54dfd402a4SMarco Elver * multiple threads, pipe 'microbench=<iters>' from multiple tasks into the
55a3120135SMarco Elver * debugfs file. This will not generate any conflicts, and tests fast-path only.
56dfd402a4SMarco Elver */
microbenchmark(unsigned long iters)57a3120135SMarco Elver static noinline void microbenchmark(unsigned long iters)
58dfd402a4SMarco Elver {
5944656d3dSMarco Elver const struct kcsan_ctx ctx_save = current->kcsan_ctx;
6044656d3dSMarco Elver const bool was_enabled = READ_ONCE(kcsan_enabled);
6109b1b134SHeiko Carstens u64 cycles;
62dfd402a4SMarco Elver
6344656d3dSMarco Elver /* We may have been called from an atomic region; reset context. */
6444656d3dSMarco Elver memset(¤t->kcsan_ctx, 0, sizeof(current->kcsan_ctx));
6544656d3dSMarco Elver /*
6644656d3dSMarco Elver * Disable to benchmark fast-path for all accesses, and (expected
6744656d3dSMarco Elver * negligible) call into slow-path, but never set up watchpoints.
6844656d3dSMarco Elver */
6944656d3dSMarco Elver WRITE_ONCE(kcsan_enabled, false);
7044656d3dSMarco Elver
71178a1877SMarco Elver pr_info("%s begin | iters: %lu\n", __func__, iters);
72dfd402a4SMarco Elver
73dfd402a4SMarco Elver cycles = get_cycles();
74dfd402a4SMarco Elver while (iters--) {
7544656d3dSMarco Elver unsigned long addr = iters & ((PAGE_SIZE << 8) - 1);
7644656d3dSMarco Elver int type = !(iters & 0x7f) ? KCSAN_ACCESS_ATOMIC :
7744656d3dSMarco Elver (!(iters & 0xf) ? KCSAN_ACCESS_WRITE : 0);
7844656d3dSMarco Elver __kcsan_check_access((void *)addr, sizeof(long), type);
79dfd402a4SMarco Elver }
80dfd402a4SMarco Elver cycles = get_cycles() - cycles;
81dfd402a4SMarco Elver
82178a1877SMarco Elver pr_info("%s end | cycles: %llu\n", __func__, cycles);
8344656d3dSMarco Elver
8444656d3dSMarco Elver WRITE_ONCE(kcsan_enabled, was_enabled);
8544656d3dSMarco Elver /* restore context */
8644656d3dSMarco Elver current->kcsan_ctx = ctx_save;
87dfd402a4SMarco Elver }
88dfd402a4SMarco Elver
cmp_filterlist_addrs(const void * rhs,const void * lhs)89dfd402a4SMarco Elver static int cmp_filterlist_addrs(const void *rhs, const void *lhs)
90dfd402a4SMarco Elver {
91dfd402a4SMarco Elver const unsigned long a = *(const unsigned long *)rhs;
92dfd402a4SMarco Elver const unsigned long b = *(const unsigned long *)lhs;
93dfd402a4SMarco Elver
94dfd402a4SMarco Elver return a < b ? -1 : a == b ? 0 : 1;
95dfd402a4SMarco Elver }
96dfd402a4SMarco Elver
kcsan_skip_report_debugfs(unsigned long func_addr)97dfd402a4SMarco Elver bool kcsan_skip_report_debugfs(unsigned long func_addr)
98dfd402a4SMarco Elver {
99dfd402a4SMarco Elver unsigned long symbolsize, offset;
100dfd402a4SMarco Elver unsigned long flags;
101dfd402a4SMarco Elver bool ret = false;
102dfd402a4SMarco Elver
103dfd402a4SMarco Elver if (!kallsyms_lookup_size_offset(func_addr, &symbolsize, &offset))
104dfd402a4SMarco Elver return false;
1055cbaefe9SIngo Molnar func_addr -= offset; /* Get function start */
106dfd402a4SMarco Elver
10759458fa4SMarco Elver raw_spin_lock_irqsave(&report_filterlist_lock, flags);
108dfd402a4SMarco Elver if (report_filterlist.used == 0)
109dfd402a4SMarco Elver goto out;
110dfd402a4SMarco Elver
111dfd402a4SMarco Elver /* Sort array if it is unsorted, and then do a binary search. */
112dfd402a4SMarco Elver if (!report_filterlist.sorted) {
113dfd402a4SMarco Elver sort(report_filterlist.addrs, report_filterlist.used,
114dfd402a4SMarco Elver sizeof(unsigned long), cmp_filterlist_addrs, NULL);
115dfd402a4SMarco Elver report_filterlist.sorted = true;
116dfd402a4SMarco Elver }
117dfd402a4SMarco Elver ret = !!bsearch(&func_addr, report_filterlist.addrs,
118dfd402a4SMarco Elver report_filterlist.used, sizeof(unsigned long),
119dfd402a4SMarco Elver cmp_filterlist_addrs);
120dfd402a4SMarco Elver if (report_filterlist.whitelist)
121dfd402a4SMarco Elver ret = !ret;
122dfd402a4SMarco Elver
123dfd402a4SMarco Elver out:
12459458fa4SMarco Elver raw_spin_unlock_irqrestore(&report_filterlist_lock, flags);
125dfd402a4SMarco Elver return ret;
126dfd402a4SMarco Elver }
127dfd402a4SMarco Elver
set_report_filterlist_whitelist(bool whitelist)128dfd402a4SMarco Elver static void set_report_filterlist_whitelist(bool whitelist)
129dfd402a4SMarco Elver {
130dfd402a4SMarco Elver unsigned long flags;
131dfd402a4SMarco Elver
13259458fa4SMarco Elver raw_spin_lock_irqsave(&report_filterlist_lock, flags);
133dfd402a4SMarco Elver report_filterlist.whitelist = whitelist;
13459458fa4SMarco Elver raw_spin_unlock_irqrestore(&report_filterlist_lock, flags);
135dfd402a4SMarco Elver }
136dfd402a4SMarco Elver
137dfd402a4SMarco Elver /* Returns 0 on success, error-code otherwise. */
insert_report_filterlist(const char * func)138dfd402a4SMarco Elver static ssize_t insert_report_filterlist(const char *func)
139dfd402a4SMarco Elver {
140dfd402a4SMarco Elver unsigned long flags;
141dfd402a4SMarco Elver unsigned long addr = kallsyms_lookup_name(func);
14259458fa4SMarco Elver unsigned long *delay_free = NULL;
14359458fa4SMarco Elver unsigned long *new_addrs = NULL;
14459458fa4SMarco Elver size_t new_size = 0;
145dfd402a4SMarco Elver ssize_t ret = 0;
146dfd402a4SMarco Elver
147dfd402a4SMarco Elver if (!addr) {
148178a1877SMarco Elver pr_err("could not find function: '%s'\n", func);
149dfd402a4SMarco Elver return -ENOENT;
150dfd402a4SMarco Elver }
151dfd402a4SMarco Elver
15259458fa4SMarco Elver retry_alloc:
15359458fa4SMarco Elver /*
15459458fa4SMarco Elver * Check if we need an allocation, and re-validate under the lock. Since
15559458fa4SMarco Elver * the report_filterlist_lock is a raw, cannot allocate under the lock.
15659458fa4SMarco Elver */
15759458fa4SMarco Elver if (data_race(report_filterlist.used == report_filterlist.size)) {
15859458fa4SMarco Elver new_size = (report_filterlist.size ?: 4) * 2;
15959458fa4SMarco Elver delay_free = new_addrs = kmalloc_array(new_size, sizeof(unsigned long), GFP_KERNEL);
16059458fa4SMarco Elver if (!new_addrs)
16159458fa4SMarco Elver return -ENOMEM;
162dfd402a4SMarco Elver }
163dfd402a4SMarco Elver
16459458fa4SMarco Elver raw_spin_lock_irqsave(&report_filterlist_lock, flags);
16559458fa4SMarco Elver if (report_filterlist.used == report_filterlist.size) {
16659458fa4SMarco Elver /* Check we pre-allocated enough, and retry if not. */
16759458fa4SMarco Elver if (report_filterlist.used >= new_size) {
16859458fa4SMarco Elver raw_spin_unlock_irqrestore(&report_filterlist_lock, flags);
16959458fa4SMarco Elver kfree(new_addrs); /* kfree(NULL) is safe */
17059458fa4SMarco Elver delay_free = new_addrs = NULL;
17159458fa4SMarco Elver goto retry_alloc;
17259458fa4SMarco Elver }
17359458fa4SMarco Elver
17459458fa4SMarco Elver if (report_filterlist.used)
17559458fa4SMarco Elver memcpy(new_addrs, report_filterlist.addrs, report_filterlist.used * sizeof(unsigned long));
17659458fa4SMarco Elver delay_free = report_filterlist.addrs; /* free the old list */
17759458fa4SMarco Elver report_filterlist.addrs = new_addrs; /* switch to the new list */
178dfd402a4SMarco Elver report_filterlist.size = new_size;
179dfd402a4SMarco Elver }
180dfd402a4SMarco Elver
181dfd402a4SMarco Elver /* Note: deduplicating should be done in userspace. */
182*b86f7c9fSRan Xiaokai report_filterlist.addrs[report_filterlist.used++] = addr;
183dfd402a4SMarco Elver report_filterlist.sorted = false;
184dfd402a4SMarco Elver
18559458fa4SMarco Elver raw_spin_unlock_irqrestore(&report_filterlist_lock, flags);
1865cbaefe9SIngo Molnar
18759458fa4SMarco Elver kfree(delay_free);
188dfd402a4SMarco Elver return ret;
189dfd402a4SMarco Elver }
190dfd402a4SMarco Elver
show_info(struct seq_file * file,void * v)191dfd402a4SMarco Elver static int show_info(struct seq_file *file, void *v)
192dfd402a4SMarco Elver {
193dfd402a4SMarco Elver int i;
194dfd402a4SMarco Elver unsigned long flags;
195dfd402a4SMarco Elver
196dfd402a4SMarco Elver /* show stats */
197dfd402a4SMarco Elver seq_printf(file, "enabled: %i\n", READ_ONCE(kcsan_enabled));
1982e986b81SMarco Elver for (i = 0; i < KCSAN_COUNTER_COUNT; ++i) {
1992e986b81SMarco Elver seq_printf(file, "%s: %ld\n", counter_names[i],
2002e986b81SMarco Elver atomic_long_read(&kcsan_counters[i]));
2012e986b81SMarco Elver }
202dfd402a4SMarco Elver
203dfd402a4SMarco Elver /* show filter functions, and filter type */
20459458fa4SMarco Elver raw_spin_lock_irqsave(&report_filterlist_lock, flags);
205dfd402a4SMarco Elver seq_printf(file, "\n%s functions: %s\n",
206dfd402a4SMarco Elver report_filterlist.whitelist ? "whitelisted" : "blacklisted",
207dfd402a4SMarco Elver report_filterlist.used == 0 ? "none" : "");
208dfd402a4SMarco Elver for (i = 0; i < report_filterlist.used; ++i)
209dfd402a4SMarco Elver seq_printf(file, " %ps\n", (void *)report_filterlist.addrs[i]);
21059458fa4SMarco Elver raw_spin_unlock_irqrestore(&report_filterlist_lock, flags);
211dfd402a4SMarco Elver
212dfd402a4SMarco Elver return 0;
213dfd402a4SMarco Elver }
214dfd402a4SMarco Elver
debugfs_open(struct inode * inode,struct file * file)215dfd402a4SMarco Elver static int debugfs_open(struct inode *inode, struct file *file)
216dfd402a4SMarco Elver {
217dfd402a4SMarco Elver return single_open(file, show_info, NULL);
218dfd402a4SMarco Elver }
219dfd402a4SMarco Elver
2205cbaefe9SIngo Molnar static ssize_t
debugfs_write(struct file * file,const char __user * buf,size_t count,loff_t * off)2215cbaefe9SIngo Molnar debugfs_write(struct file *file, const char __user *buf, size_t count, loff_t *off)
222dfd402a4SMarco Elver {
223dfd402a4SMarco Elver char kbuf[KSYM_NAME_LEN];
224dfd402a4SMarco Elver char *arg;
22543d631bfSThorsten Blum const size_t read_len = min(count, sizeof(kbuf) - 1);
226dfd402a4SMarco Elver
227dfd402a4SMarco Elver if (copy_from_user(kbuf, buf, read_len))
228dfd402a4SMarco Elver return -EFAULT;
229dfd402a4SMarco Elver kbuf[read_len] = '\0';
230dfd402a4SMarco Elver arg = strstrip(kbuf);
231dfd402a4SMarco Elver
232dfd402a4SMarco Elver if (!strcmp(arg, "on")) {
233dfd402a4SMarco Elver WRITE_ONCE(kcsan_enabled, true);
234dfd402a4SMarco Elver } else if (!strcmp(arg, "off")) {
235dfd402a4SMarco Elver WRITE_ONCE(kcsan_enabled, false);
236a4e74fa5SMarco Elver } else if (str_has_prefix(arg, "microbench=")) {
237dfd402a4SMarco Elver unsigned long iters;
238dfd402a4SMarco Elver
239a4e74fa5SMarco Elver if (kstrtoul(&arg[strlen("microbench=")], 0, &iters))
240dfd402a4SMarco Elver return -EINVAL;
241dfd402a4SMarco Elver microbenchmark(iters);
242dfd402a4SMarco Elver } else if (!strcmp(arg, "whitelist")) {
243dfd402a4SMarco Elver set_report_filterlist_whitelist(true);
244dfd402a4SMarco Elver } else if (!strcmp(arg, "blacklist")) {
245dfd402a4SMarco Elver set_report_filterlist_whitelist(false);
246dfd402a4SMarco Elver } else if (arg[0] == '!') {
247dfd402a4SMarco Elver ssize_t ret = insert_report_filterlist(&arg[1]);
248dfd402a4SMarco Elver
249dfd402a4SMarco Elver if (ret < 0)
250dfd402a4SMarco Elver return ret;
251dfd402a4SMarco Elver } else {
252dfd402a4SMarco Elver return -EINVAL;
253dfd402a4SMarco Elver }
254dfd402a4SMarco Elver
255dfd402a4SMarco Elver return count;
256dfd402a4SMarco Elver }
257dfd402a4SMarco Elver
2585cbaefe9SIngo Molnar static const struct file_operations debugfs_ops =
2595cbaefe9SIngo Molnar {
2605cbaefe9SIngo Molnar .read = seq_read,
261dfd402a4SMarco Elver .open = debugfs_open,
262dfd402a4SMarco Elver .write = debugfs_write,
2635cbaefe9SIngo Molnar .release = single_release
2645cbaefe9SIngo Molnar };
265dfd402a4SMarco Elver
kcsan_debugfs_init(void)266976aac5fSArnd Bergmann static int __init kcsan_debugfs_init(void)
267dfd402a4SMarco Elver {
268dfd402a4SMarco Elver debugfs_create_file("kcsan", 0644, NULL, NULL, &debugfs_ops);
269976aac5fSArnd Bergmann return 0;
270dfd402a4SMarco Elver }
271e36299efSMarco Elver
272e36299efSMarco Elver late_initcall(kcsan_debugfs_init);
273