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