Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * KCSAN debugfs interface.
0004  *
0005  * Copyright (C) 2019, Google LLC.
0006  */
0007 
0008 #define pr_fmt(fmt) "kcsan: " fmt
0009 
0010 #include <linux/atomic.h>
0011 #include <linux/bsearch.h>
0012 #include <linux/bug.h>
0013 #include <linux/debugfs.h>
0014 #include <linux/init.h>
0015 #include <linux/kallsyms.h>
0016 #include <linux/sched.h>
0017 #include <linux/seq_file.h>
0018 #include <linux/slab.h>
0019 #include <linux/sort.h>
0020 #include <linux/string.h>
0021 #include <linux/uaccess.h>
0022 
0023 #include "kcsan.h"
0024 
0025 atomic_long_t kcsan_counters[KCSAN_COUNTER_COUNT];
0026 static const char *const counter_names[] = {
0027     [KCSAN_COUNTER_USED_WATCHPOINTS]        = "used_watchpoints",
0028     [KCSAN_COUNTER_SETUP_WATCHPOINTS]       = "setup_watchpoints",
0029     [KCSAN_COUNTER_DATA_RACES]          = "data_races",
0030     [KCSAN_COUNTER_ASSERT_FAILURES]         = "assert_failures",
0031     [KCSAN_COUNTER_NO_CAPACITY]         = "no_capacity",
0032     [KCSAN_COUNTER_REPORT_RACES]            = "report_races",
0033     [KCSAN_COUNTER_RACES_UNKNOWN_ORIGIN]        = "races_unknown_origin",
0034     [KCSAN_COUNTER_UNENCODABLE_ACCESSES]        = "unencodable_accesses",
0035     [KCSAN_COUNTER_ENCODING_FALSE_POSITIVES]    = "encoding_false_positives",
0036 };
0037 static_assert(ARRAY_SIZE(counter_names) == KCSAN_COUNTER_COUNT);
0038 
0039 /*
0040  * Addresses for filtering functions from reporting. This list can be used as a
0041  * whitelist or blacklist.
0042  */
0043 static struct {
0044     unsigned long   *addrs;     /* array of addresses */
0045     size_t      size;       /* current size */
0046     int     used;       /* number of elements used */
0047     bool        sorted;     /* if elements are sorted */
0048     bool        whitelist;  /* if list is a blacklist or whitelist */
0049 } report_filterlist = {
0050     .addrs      = NULL,
0051     .size       = 8,        /* small initial size */
0052     .used       = 0,
0053     .sorted     = false,
0054     .whitelist  = false,    /* default is blacklist */
0055 };
0056 static DEFINE_SPINLOCK(report_filterlist_lock);
0057 
0058 /*
0059  * The microbenchmark allows benchmarking KCSAN core runtime only. To run
0060  * multiple threads, pipe 'microbench=<iters>' from multiple tasks into the
0061  * debugfs file. This will not generate any conflicts, and tests fast-path only.
0062  */
0063 static noinline void microbenchmark(unsigned long iters)
0064 {
0065     const struct kcsan_ctx ctx_save = current->kcsan_ctx;
0066     const bool was_enabled = READ_ONCE(kcsan_enabled);
0067     u64 cycles;
0068 
0069     /* We may have been called from an atomic region; reset context. */
0070     memset(&current->kcsan_ctx, 0, sizeof(current->kcsan_ctx));
0071     /*
0072      * Disable to benchmark fast-path for all accesses, and (expected
0073      * negligible) call into slow-path, but never set up watchpoints.
0074      */
0075     WRITE_ONCE(kcsan_enabled, false);
0076 
0077     pr_info("%s begin | iters: %lu\n", __func__, iters);
0078 
0079     cycles = get_cycles();
0080     while (iters--) {
0081         unsigned long addr = iters & ((PAGE_SIZE << 8) - 1);
0082         int type = !(iters & 0x7f) ? KCSAN_ACCESS_ATOMIC :
0083                 (!(iters & 0xf) ? KCSAN_ACCESS_WRITE : 0);
0084         __kcsan_check_access((void *)addr, sizeof(long), type);
0085     }
0086     cycles = get_cycles() - cycles;
0087 
0088     pr_info("%s end   | cycles: %llu\n", __func__, cycles);
0089 
0090     WRITE_ONCE(kcsan_enabled, was_enabled);
0091     /* restore context */
0092     current->kcsan_ctx = ctx_save;
0093 }
0094 
0095 static int cmp_filterlist_addrs(const void *rhs, const void *lhs)
0096 {
0097     const unsigned long a = *(const unsigned long *)rhs;
0098     const unsigned long b = *(const unsigned long *)lhs;
0099 
0100     return a < b ? -1 : a == b ? 0 : 1;
0101 }
0102 
0103 bool kcsan_skip_report_debugfs(unsigned long func_addr)
0104 {
0105     unsigned long symbolsize, offset;
0106     unsigned long flags;
0107     bool ret = false;
0108 
0109     if (!kallsyms_lookup_size_offset(func_addr, &symbolsize, &offset))
0110         return false;
0111     func_addr -= offset; /* Get function start */
0112 
0113     spin_lock_irqsave(&report_filterlist_lock, flags);
0114     if (report_filterlist.used == 0)
0115         goto out;
0116 
0117     /* Sort array if it is unsorted, and then do a binary search. */
0118     if (!report_filterlist.sorted) {
0119         sort(report_filterlist.addrs, report_filterlist.used,
0120              sizeof(unsigned long), cmp_filterlist_addrs, NULL);
0121         report_filterlist.sorted = true;
0122     }
0123     ret = !!bsearch(&func_addr, report_filterlist.addrs,
0124             report_filterlist.used, sizeof(unsigned long),
0125             cmp_filterlist_addrs);
0126     if (report_filterlist.whitelist)
0127         ret = !ret;
0128 
0129 out:
0130     spin_unlock_irqrestore(&report_filterlist_lock, flags);
0131     return ret;
0132 }
0133 
0134 static void set_report_filterlist_whitelist(bool whitelist)
0135 {
0136     unsigned long flags;
0137 
0138     spin_lock_irqsave(&report_filterlist_lock, flags);
0139     report_filterlist.whitelist = whitelist;
0140     spin_unlock_irqrestore(&report_filterlist_lock, flags);
0141 }
0142 
0143 /* Returns 0 on success, error-code otherwise. */
0144 static ssize_t insert_report_filterlist(const char *func)
0145 {
0146     unsigned long flags;
0147     unsigned long addr = kallsyms_lookup_name(func);
0148     ssize_t ret = 0;
0149 
0150     if (!addr) {
0151         pr_err("could not find function: '%s'\n", func);
0152         return -ENOENT;
0153     }
0154 
0155     spin_lock_irqsave(&report_filterlist_lock, flags);
0156 
0157     if (report_filterlist.addrs == NULL) {
0158         /* initial allocation */
0159         report_filterlist.addrs =
0160             kmalloc_array(report_filterlist.size,
0161                       sizeof(unsigned long), GFP_ATOMIC);
0162         if (report_filterlist.addrs == NULL) {
0163             ret = -ENOMEM;
0164             goto out;
0165         }
0166     } else if (report_filterlist.used == report_filterlist.size) {
0167         /* resize filterlist */
0168         size_t new_size = report_filterlist.size * 2;
0169         unsigned long *new_addrs =
0170             krealloc(report_filterlist.addrs,
0171                  new_size * sizeof(unsigned long), GFP_ATOMIC);
0172 
0173         if (new_addrs == NULL) {
0174             /* leave filterlist itself untouched */
0175             ret = -ENOMEM;
0176             goto out;
0177         }
0178 
0179         report_filterlist.size = new_size;
0180         report_filterlist.addrs = new_addrs;
0181     }
0182 
0183     /* Note: deduplicating should be done in userspace. */
0184     report_filterlist.addrs[report_filterlist.used++] =
0185         kallsyms_lookup_name(func);
0186     report_filterlist.sorted = false;
0187 
0188 out:
0189     spin_unlock_irqrestore(&report_filterlist_lock, flags);
0190 
0191     return ret;
0192 }
0193 
0194 static int show_info(struct seq_file *file, void *v)
0195 {
0196     int i;
0197     unsigned long flags;
0198 
0199     /* show stats */
0200     seq_printf(file, "enabled: %i\n", READ_ONCE(kcsan_enabled));
0201     for (i = 0; i < KCSAN_COUNTER_COUNT; ++i) {
0202         seq_printf(file, "%s: %ld\n", counter_names[i],
0203                atomic_long_read(&kcsan_counters[i]));
0204     }
0205 
0206     /* show filter functions, and filter type */
0207     spin_lock_irqsave(&report_filterlist_lock, flags);
0208     seq_printf(file, "\n%s functions: %s\n",
0209            report_filterlist.whitelist ? "whitelisted" : "blacklisted",
0210            report_filterlist.used == 0 ? "none" : "");
0211     for (i = 0; i < report_filterlist.used; ++i)
0212         seq_printf(file, " %ps\n", (void *)report_filterlist.addrs[i]);
0213     spin_unlock_irqrestore(&report_filterlist_lock, flags);
0214 
0215     return 0;
0216 }
0217 
0218 static int debugfs_open(struct inode *inode, struct file *file)
0219 {
0220     return single_open(file, show_info, NULL);
0221 }
0222 
0223 static ssize_t
0224 debugfs_write(struct file *file, const char __user *buf, size_t count, loff_t *off)
0225 {
0226     char kbuf[KSYM_NAME_LEN];
0227     char *arg;
0228     int read_len = count < (sizeof(kbuf) - 1) ? count : (sizeof(kbuf) - 1);
0229 
0230     if (copy_from_user(kbuf, buf, read_len))
0231         return -EFAULT;
0232     kbuf[read_len] = '\0';
0233     arg = strstrip(kbuf);
0234 
0235     if (!strcmp(arg, "on")) {
0236         WRITE_ONCE(kcsan_enabled, true);
0237     } else if (!strcmp(arg, "off")) {
0238         WRITE_ONCE(kcsan_enabled, false);
0239     } else if (str_has_prefix(arg, "microbench=")) {
0240         unsigned long iters;
0241 
0242         if (kstrtoul(&arg[strlen("microbench=")], 0, &iters))
0243             return -EINVAL;
0244         microbenchmark(iters);
0245     } else if (!strcmp(arg, "whitelist")) {
0246         set_report_filterlist_whitelist(true);
0247     } else if (!strcmp(arg, "blacklist")) {
0248         set_report_filterlist_whitelist(false);
0249     } else if (arg[0] == '!') {
0250         ssize_t ret = insert_report_filterlist(&arg[1]);
0251 
0252         if (ret < 0)
0253             return ret;
0254     } else {
0255         return -EINVAL;
0256     }
0257 
0258     return count;
0259 }
0260 
0261 static const struct file_operations debugfs_ops =
0262 {
0263     .read    = seq_read,
0264     .open    = debugfs_open,
0265     .write   = debugfs_write,
0266     .release = single_release
0267 };
0268 
0269 static int __init kcsan_debugfs_init(void)
0270 {
0271     debugfs_create_file("kcsan", 0644, NULL, NULL, &debugfs_ops);
0272     return 0;
0273 }
0274 
0275 late_initcall(kcsan_debugfs_init);