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> 9 #include <linux/seq_file.h> 10 #include <linux/slab.h> 11 #include <linux/sort.h> 12 #include <linux/string.h> 13 #include <linux/uaccess.h> 14 15 #include "kcsan.h" 16 17 /* 18 * Statistics counters. 19 */ 20 static atomic_long_t counters[KCSAN_COUNTER_COUNT]; 21 22 /* 23 * Addresses for filtering functions from reporting. This list can be used as a 24 * whitelist or blacklist. 25 */ 26 static struct { 27 unsigned long *addrs; /* array of addresses */ 28 size_t size; /* current size */ 29 int used; /* number of elements used */ 30 bool sorted; /* if elements are sorted */ 31 bool whitelist; /* if list is a blacklist or whitelist */ 32 } report_filterlist = { 33 .addrs = NULL, 34 .size = 8, /* small initial size */ 35 .used = 0, 36 .sorted = false, 37 .whitelist = false, /* default is blacklist */ 38 }; 39 static DEFINE_SPINLOCK(report_filterlist_lock); 40 41 static const char *counter_to_name(enum kcsan_counter_id id) 42 { 43 switch (id) { 44 case KCSAN_COUNTER_USED_WATCHPOINTS: return "used_watchpoints"; 45 case KCSAN_COUNTER_SETUP_WATCHPOINTS: return "setup_watchpoints"; 46 case KCSAN_COUNTER_DATA_RACES: return "data_races"; 47 case KCSAN_COUNTER_ASSERT_FAILURES: return "assert_failures"; 48 case KCSAN_COUNTER_NO_CAPACITY: return "no_capacity"; 49 case KCSAN_COUNTER_REPORT_RACES: return "report_races"; 50 case KCSAN_COUNTER_RACES_UNKNOWN_ORIGIN: return "races_unknown_origin"; 51 case KCSAN_COUNTER_UNENCODABLE_ACCESSES: return "unencodable_accesses"; 52 case KCSAN_COUNTER_ENCODING_FALSE_POSITIVES: return "encoding_false_positives"; 53 case KCSAN_COUNTER_COUNT: 54 BUG(); 55 } 56 return NULL; 57 } 58 59 void kcsan_counter_inc(enum kcsan_counter_id id) 60 { 61 atomic_long_inc(&counters[id]); 62 } 63 64 void kcsan_counter_dec(enum kcsan_counter_id id) 65 { 66 atomic_long_dec(&counters[id]); 67 } 68 69 /* 70 * The microbenchmark allows benchmarking KCSAN core runtime only. To run 71 * multiple threads, pipe 'microbench=<iters>' from multiple tasks into the 72 * debugfs file. 73 */ 74 static void microbenchmark(unsigned long iters) 75 { 76 cycles_t cycles; 77 78 pr_info("KCSAN: %s begin | iters: %lu\n", __func__, iters); 79 80 cycles = get_cycles(); 81 while (iters--) { 82 /* 83 * We can run this benchmark from multiple tasks; this address 84 * calculation increases likelyhood of some accesses overlapping 85 * (they still won't conflict because all are reads). 86 */ 87 unsigned long addr = 88 iters % (CONFIG_KCSAN_NUM_WATCHPOINTS * PAGE_SIZE); 89 __kcsan_check_read((void *)addr, sizeof(long)); 90 } 91 cycles = get_cycles() - cycles; 92 93 pr_info("KCSAN: %s end | cycles: %llu\n", __func__, cycles); 94 } 95 96 static int cmp_filterlist_addrs(const void *rhs, const void *lhs) 97 { 98 const unsigned long a = *(const unsigned long *)rhs; 99 const unsigned long b = *(const unsigned long *)lhs; 100 101 return a < b ? -1 : a == b ? 0 : 1; 102 } 103 104 bool kcsan_skip_report_debugfs(unsigned long func_addr) 105 { 106 unsigned long symbolsize, offset; 107 unsigned long flags; 108 bool ret = false; 109 110 if (!kallsyms_lookup_size_offset(func_addr, &symbolsize, &offset)) 111 return false; 112 func_addr -= offset; /* Get function start */ 113 114 spin_lock_irqsave(&report_filterlist_lock, flags); 115 if (report_filterlist.used == 0) 116 goto out; 117 118 /* Sort array if it is unsorted, and then do a binary search. */ 119 if (!report_filterlist.sorted) { 120 sort(report_filterlist.addrs, report_filterlist.used, 121 sizeof(unsigned long), cmp_filterlist_addrs, NULL); 122 report_filterlist.sorted = true; 123 } 124 ret = !!bsearch(&func_addr, report_filterlist.addrs, 125 report_filterlist.used, sizeof(unsigned long), 126 cmp_filterlist_addrs); 127 if (report_filterlist.whitelist) 128 ret = !ret; 129 130 out: 131 spin_unlock_irqrestore(&report_filterlist_lock, flags); 132 return ret; 133 } 134 135 static void set_report_filterlist_whitelist(bool whitelist) 136 { 137 unsigned long flags; 138 139 spin_lock_irqsave(&report_filterlist_lock, flags); 140 report_filterlist.whitelist = whitelist; 141 spin_unlock_irqrestore(&report_filterlist_lock, flags); 142 } 143 144 /* Returns 0 on success, error-code otherwise. */ 145 static ssize_t insert_report_filterlist(const char *func) 146 { 147 unsigned long flags; 148 unsigned long addr = kallsyms_lookup_name(func); 149 ssize_t ret = 0; 150 151 if (!addr) { 152 pr_err("KCSAN: could not find function: '%s'\n", func); 153 return -ENOENT; 154 } 155 156 spin_lock_irqsave(&report_filterlist_lock, flags); 157 158 if (report_filterlist.addrs == NULL) { 159 /* initial allocation */ 160 report_filterlist.addrs = 161 kmalloc_array(report_filterlist.size, 162 sizeof(unsigned long), GFP_KERNEL); 163 if (report_filterlist.addrs == NULL) { 164 ret = -ENOMEM; 165 goto out; 166 } 167 } else if (report_filterlist.used == report_filterlist.size) { 168 /* resize filterlist */ 169 size_t new_size = report_filterlist.size * 2; 170 unsigned long *new_addrs = 171 krealloc(report_filterlist.addrs, 172 new_size * sizeof(unsigned long), GFP_KERNEL); 173 174 if (new_addrs == NULL) { 175 /* leave filterlist itself untouched */ 176 ret = -ENOMEM; 177 goto out; 178 } 179 180 report_filterlist.size = new_size; 181 report_filterlist.addrs = new_addrs; 182 } 183 184 /* Note: deduplicating should be done in userspace. */ 185 report_filterlist.addrs[report_filterlist.used++] = 186 kallsyms_lookup_name(func); 187 report_filterlist.sorted = false; 188 189 out: 190 spin_unlock_irqrestore(&report_filterlist_lock, flags); 191 192 return ret; 193 } 194 195 static int show_info(struct seq_file *file, void *v) 196 { 197 int i; 198 unsigned long flags; 199 200 /* show stats */ 201 seq_printf(file, "enabled: %i\n", READ_ONCE(kcsan_enabled)); 202 for (i = 0; i < KCSAN_COUNTER_COUNT; ++i) 203 seq_printf(file, "%s: %ld\n", counter_to_name(i), 204 atomic_long_read(&counters[i])); 205 206 /* show filter functions, and filter type */ 207 spin_lock_irqsave(&report_filterlist_lock, flags); 208 seq_printf(file, "\n%s functions: %s\n", 209 report_filterlist.whitelist ? "whitelisted" : "blacklisted", 210 report_filterlist.used == 0 ? "none" : ""); 211 for (i = 0; i < report_filterlist.used; ++i) 212 seq_printf(file, " %ps\n", (void *)report_filterlist.addrs[i]); 213 spin_unlock_irqrestore(&report_filterlist_lock, flags); 214 215 return 0; 216 } 217 218 static int debugfs_open(struct inode *inode, struct file *file) 219 { 220 return single_open(file, show_info, NULL); 221 } 222 223 static ssize_t 224 debugfs_write(struct file *file, const char __user *buf, size_t count, loff_t *off) 225 { 226 char kbuf[KSYM_NAME_LEN]; 227 char *arg; 228 int read_len = count < (sizeof(kbuf) - 1) ? count : (sizeof(kbuf) - 1); 229 230 if (copy_from_user(kbuf, buf, read_len)) 231 return -EFAULT; 232 kbuf[read_len] = '\0'; 233 arg = strstrip(kbuf); 234 235 if (!strcmp(arg, "on")) { 236 WRITE_ONCE(kcsan_enabled, true); 237 } else if (!strcmp(arg, "off")) { 238 WRITE_ONCE(kcsan_enabled, false); 239 } else if (!strncmp(arg, "microbench=", sizeof("microbench=") - 1)) { 240 unsigned long iters; 241 242 if (kstrtoul(&arg[sizeof("microbench=") - 1], 0, &iters)) 243 return -EINVAL; 244 microbenchmark(iters); 245 } else if (!strcmp(arg, "whitelist")) { 246 set_report_filterlist_whitelist(true); 247 } else if (!strcmp(arg, "blacklist")) { 248 set_report_filterlist_whitelist(false); 249 } else if (arg[0] == '!') { 250 ssize_t ret = insert_report_filterlist(&arg[1]); 251 252 if (ret < 0) 253 return ret; 254 } else { 255 return -EINVAL; 256 } 257 258 return count; 259 } 260 261 static const struct file_operations debugfs_ops = 262 { 263 .read = seq_read, 264 .open = debugfs_open, 265 .write = debugfs_write, 266 .release = single_release 267 }; 268 269 void __init kcsan_debugfs_init(void) 270 { 271 debugfs_create_file("kcsan", 0644, NULL, NULL, &debugfs_ops); 272 } 273