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