xref: /linux-6.15/kernel/kcsan/debugfs.c (revision b86f7c9f)
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(&current->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