14b1a29a7SMasami Hiramatsu // SPDX-License-Identifier: GPL-2.0 24b1a29a7SMasami Hiramatsu /* 34b1a29a7SMasami Hiramatsu * fail_function.c: Function-based error injection 44b1a29a7SMasami Hiramatsu */ 54b1a29a7SMasami Hiramatsu #include <linux/error-injection.h> 64b1a29a7SMasami Hiramatsu #include <linux/debugfs.h> 74b1a29a7SMasami Hiramatsu #include <linux/fault-inject.h> 84b1a29a7SMasami Hiramatsu #include <linux/kallsyms.h> 94b1a29a7SMasami Hiramatsu #include <linux/kprobes.h> 104b1a29a7SMasami Hiramatsu #include <linux/module.h> 114b1a29a7SMasami Hiramatsu #include <linux/mutex.h> 124b1a29a7SMasami Hiramatsu #include <linux/slab.h> 134b1a29a7SMasami Hiramatsu #include <linux/uaccess.h> 144b1a29a7SMasami Hiramatsu 154b1a29a7SMasami Hiramatsu static int fei_kprobe_handler(struct kprobe *kp, struct pt_regs *regs); 164b1a29a7SMasami Hiramatsu 17b6b76dd6SMasami Hiramatsu static void fei_post_handler(struct kprobe *kp, struct pt_regs *regs, 18b6b76dd6SMasami Hiramatsu unsigned long flags) 19b6b76dd6SMasami Hiramatsu { 20b6b76dd6SMasami Hiramatsu /* 21b6b76dd6SMasami Hiramatsu * A dummy post handler is required to prohibit optimizing, because 22b6b76dd6SMasami Hiramatsu * jump optimization does not support execution path overriding. 23b6b76dd6SMasami Hiramatsu */ 24b6b76dd6SMasami Hiramatsu } 25b6b76dd6SMasami Hiramatsu 264b1a29a7SMasami Hiramatsu struct fei_attr { 274b1a29a7SMasami Hiramatsu struct list_head list; 284b1a29a7SMasami Hiramatsu struct kprobe kp; 294b1a29a7SMasami Hiramatsu unsigned long retval; 304b1a29a7SMasami Hiramatsu }; 314b1a29a7SMasami Hiramatsu static DEFINE_MUTEX(fei_lock); 324b1a29a7SMasami Hiramatsu static LIST_HEAD(fei_attr_list); 334b1a29a7SMasami Hiramatsu static DECLARE_FAULT_ATTR(fei_fault_attr); 344b1a29a7SMasami Hiramatsu static struct dentry *fei_debugfs_dir; 354b1a29a7SMasami Hiramatsu 364b1a29a7SMasami Hiramatsu static unsigned long adjust_error_retval(unsigned long addr, unsigned long retv) 374b1a29a7SMasami Hiramatsu { 384b1a29a7SMasami Hiramatsu switch (get_injectable_error_type(addr)) { 394b1a29a7SMasami Hiramatsu case EI_ETYPE_NULL: 404b1a29a7SMasami Hiramatsu return 0; 414b1a29a7SMasami Hiramatsu case EI_ETYPE_ERRNO: 424b1a29a7SMasami Hiramatsu if (retv < (unsigned long)-MAX_ERRNO) 434b1a29a7SMasami Hiramatsu return (unsigned long)-EINVAL; 444b1a29a7SMasami Hiramatsu break; 454b1a29a7SMasami Hiramatsu case EI_ETYPE_ERRNO_NULL: 464b1a29a7SMasami Hiramatsu if (retv != 0 && retv < (unsigned long)-MAX_ERRNO) 474b1a29a7SMasami Hiramatsu return (unsigned long)-EINVAL; 484b1a29a7SMasami Hiramatsu break; 49*537cd894SBarnabás Pőcze case EI_ETYPE_TRUE: 50*537cd894SBarnabás Pőcze return 1; 514b1a29a7SMasami Hiramatsu } 524b1a29a7SMasami Hiramatsu 534b1a29a7SMasami Hiramatsu return retv; 544b1a29a7SMasami Hiramatsu } 554b1a29a7SMasami Hiramatsu 564b1a29a7SMasami Hiramatsu static struct fei_attr *fei_attr_new(const char *sym, unsigned long addr) 574b1a29a7SMasami Hiramatsu { 584b1a29a7SMasami Hiramatsu struct fei_attr *attr; 594b1a29a7SMasami Hiramatsu 604b1a29a7SMasami Hiramatsu attr = kzalloc(sizeof(*attr), GFP_KERNEL); 614b1a29a7SMasami Hiramatsu if (attr) { 624b1a29a7SMasami Hiramatsu attr->kp.symbol_name = kstrdup(sym, GFP_KERNEL); 634b1a29a7SMasami Hiramatsu if (!attr->kp.symbol_name) { 644b1a29a7SMasami Hiramatsu kfree(attr); 654b1a29a7SMasami Hiramatsu return NULL; 664b1a29a7SMasami Hiramatsu } 674b1a29a7SMasami Hiramatsu attr->kp.pre_handler = fei_kprobe_handler; 68b6b76dd6SMasami Hiramatsu attr->kp.post_handler = fei_post_handler; 694b1a29a7SMasami Hiramatsu attr->retval = adjust_error_retval(addr, 0); 704b1a29a7SMasami Hiramatsu INIT_LIST_HEAD(&attr->list); 714b1a29a7SMasami Hiramatsu } 724b1a29a7SMasami Hiramatsu return attr; 734b1a29a7SMasami Hiramatsu } 744b1a29a7SMasami Hiramatsu 754b1a29a7SMasami Hiramatsu static void fei_attr_free(struct fei_attr *attr) 764b1a29a7SMasami Hiramatsu { 774b1a29a7SMasami Hiramatsu if (attr) { 784b1a29a7SMasami Hiramatsu kfree(attr->kp.symbol_name); 794b1a29a7SMasami Hiramatsu kfree(attr); 804b1a29a7SMasami Hiramatsu } 814b1a29a7SMasami Hiramatsu } 824b1a29a7SMasami Hiramatsu 834b1a29a7SMasami Hiramatsu static struct fei_attr *fei_attr_lookup(const char *sym) 844b1a29a7SMasami Hiramatsu { 854b1a29a7SMasami Hiramatsu struct fei_attr *attr; 864b1a29a7SMasami Hiramatsu 874b1a29a7SMasami Hiramatsu list_for_each_entry(attr, &fei_attr_list, list) { 884b1a29a7SMasami Hiramatsu if (!strcmp(attr->kp.symbol_name, sym)) 894b1a29a7SMasami Hiramatsu return attr; 904b1a29a7SMasami Hiramatsu } 914b1a29a7SMasami Hiramatsu 924b1a29a7SMasami Hiramatsu return NULL; 934b1a29a7SMasami Hiramatsu } 944b1a29a7SMasami Hiramatsu 954b1a29a7SMasami Hiramatsu static bool fei_attr_is_valid(struct fei_attr *_attr) 964b1a29a7SMasami Hiramatsu { 974b1a29a7SMasami Hiramatsu struct fei_attr *attr; 984b1a29a7SMasami Hiramatsu 994b1a29a7SMasami Hiramatsu list_for_each_entry(attr, &fei_attr_list, list) { 1004b1a29a7SMasami Hiramatsu if (attr == _attr) 1014b1a29a7SMasami Hiramatsu return true; 1024b1a29a7SMasami Hiramatsu } 1034b1a29a7SMasami Hiramatsu 1044b1a29a7SMasami Hiramatsu return false; 1054b1a29a7SMasami Hiramatsu } 1064b1a29a7SMasami Hiramatsu 1074b1a29a7SMasami Hiramatsu static int fei_retval_set(void *data, u64 val) 1084b1a29a7SMasami Hiramatsu { 1094b1a29a7SMasami Hiramatsu struct fei_attr *attr = data; 1104b1a29a7SMasami Hiramatsu unsigned long retv = (unsigned long)val; 1114b1a29a7SMasami Hiramatsu int err = 0; 1124b1a29a7SMasami Hiramatsu 1134b1a29a7SMasami Hiramatsu mutex_lock(&fei_lock); 1144b1a29a7SMasami Hiramatsu /* 1154b1a29a7SMasami Hiramatsu * Since this operation can be done after retval file is removed, 1164b1a29a7SMasami Hiramatsu * It is safer to check the attr is still valid before accessing 1174b1a29a7SMasami Hiramatsu * its member. 1184b1a29a7SMasami Hiramatsu */ 1194b1a29a7SMasami Hiramatsu if (!fei_attr_is_valid(attr)) { 1204b1a29a7SMasami Hiramatsu err = -ENOENT; 1214b1a29a7SMasami Hiramatsu goto out; 1224b1a29a7SMasami Hiramatsu } 1234b1a29a7SMasami Hiramatsu 1244b1a29a7SMasami Hiramatsu if (attr->kp.addr) { 1254b1a29a7SMasami Hiramatsu if (adjust_error_retval((unsigned long)attr->kp.addr, 1264b1a29a7SMasami Hiramatsu val) != retv) 1274b1a29a7SMasami Hiramatsu err = -EINVAL; 1284b1a29a7SMasami Hiramatsu } 1294b1a29a7SMasami Hiramatsu if (!err) 1304b1a29a7SMasami Hiramatsu attr->retval = val; 1314b1a29a7SMasami Hiramatsu out: 1324b1a29a7SMasami Hiramatsu mutex_unlock(&fei_lock); 1334b1a29a7SMasami Hiramatsu 1344b1a29a7SMasami Hiramatsu return err; 1354b1a29a7SMasami Hiramatsu } 1364b1a29a7SMasami Hiramatsu 1374b1a29a7SMasami Hiramatsu static int fei_retval_get(void *data, u64 *val) 1384b1a29a7SMasami Hiramatsu { 1394b1a29a7SMasami Hiramatsu struct fei_attr *attr = data; 1404b1a29a7SMasami Hiramatsu int err = 0; 1414b1a29a7SMasami Hiramatsu 1424b1a29a7SMasami Hiramatsu mutex_lock(&fei_lock); 1434b1a29a7SMasami Hiramatsu /* Here we also validate @attr to ensure it still exists. */ 1444b1a29a7SMasami Hiramatsu if (!fei_attr_is_valid(attr)) 1454b1a29a7SMasami Hiramatsu err = -ENOENT; 1464b1a29a7SMasami Hiramatsu else 1474b1a29a7SMasami Hiramatsu *val = attr->retval; 1484b1a29a7SMasami Hiramatsu mutex_unlock(&fei_lock); 1494b1a29a7SMasami Hiramatsu 1504b1a29a7SMasami Hiramatsu return err; 1514b1a29a7SMasami Hiramatsu } 1524b1a29a7SMasami Hiramatsu DEFINE_DEBUGFS_ATTRIBUTE(fei_retval_ops, fei_retval_get, fei_retval_set, 1534b1a29a7SMasami Hiramatsu "%llx\n"); 1544b1a29a7SMasami Hiramatsu 1554aa3b1f6SGreg Kroah-Hartman static void fei_debugfs_add_attr(struct fei_attr *attr) 1564b1a29a7SMasami Hiramatsu { 1574b1a29a7SMasami Hiramatsu struct dentry *dir; 1584b1a29a7SMasami Hiramatsu 1594b1a29a7SMasami Hiramatsu dir = debugfs_create_dir(attr->kp.symbol_name, fei_debugfs_dir); 1604b1a29a7SMasami Hiramatsu 1614aa3b1f6SGreg Kroah-Hartman debugfs_create_file("retval", 0600, dir, attr, &fei_retval_ops); 1624b1a29a7SMasami Hiramatsu } 1634b1a29a7SMasami Hiramatsu 1644b1a29a7SMasami Hiramatsu static void fei_debugfs_remove_attr(struct fei_attr *attr) 1654b1a29a7SMasami Hiramatsu { 1664b1a29a7SMasami Hiramatsu struct dentry *dir; 1674b1a29a7SMasami Hiramatsu 1684b1a29a7SMasami Hiramatsu dir = debugfs_lookup(attr->kp.symbol_name, fei_debugfs_dir); 1694b1a29a7SMasami Hiramatsu debugfs_remove_recursive(dir); 1704b1a29a7SMasami Hiramatsu } 1714b1a29a7SMasami Hiramatsu 1724b1a29a7SMasami Hiramatsu static int fei_kprobe_handler(struct kprobe *kp, struct pt_regs *regs) 1734b1a29a7SMasami Hiramatsu { 1744b1a29a7SMasami Hiramatsu struct fei_attr *attr = container_of(kp, struct fei_attr, kp); 1754b1a29a7SMasami Hiramatsu 1764b1a29a7SMasami Hiramatsu if (should_fail(&fei_fault_attr, 1)) { 1774b1a29a7SMasami Hiramatsu regs_set_return_value(regs, attr->retval); 1784b1a29a7SMasami Hiramatsu override_function_with_return(regs); 1794b1a29a7SMasami Hiramatsu return 1; 1804b1a29a7SMasami Hiramatsu } 1814b1a29a7SMasami Hiramatsu 1824b1a29a7SMasami Hiramatsu return 0; 1834b1a29a7SMasami Hiramatsu } 1844b1a29a7SMasami Hiramatsu NOKPROBE_SYMBOL(fei_kprobe_handler) 1854b1a29a7SMasami Hiramatsu 1864b1a29a7SMasami Hiramatsu static void *fei_seq_start(struct seq_file *m, loff_t *pos) 1874b1a29a7SMasami Hiramatsu { 1884b1a29a7SMasami Hiramatsu mutex_lock(&fei_lock); 1894b1a29a7SMasami Hiramatsu return seq_list_start(&fei_attr_list, *pos); 1904b1a29a7SMasami Hiramatsu } 1914b1a29a7SMasami Hiramatsu 1924b1a29a7SMasami Hiramatsu static void fei_seq_stop(struct seq_file *m, void *v) 1934b1a29a7SMasami Hiramatsu { 1944b1a29a7SMasami Hiramatsu mutex_unlock(&fei_lock); 1954b1a29a7SMasami Hiramatsu } 1964b1a29a7SMasami Hiramatsu 1974b1a29a7SMasami Hiramatsu static void *fei_seq_next(struct seq_file *m, void *v, loff_t *pos) 1984b1a29a7SMasami Hiramatsu { 1994b1a29a7SMasami Hiramatsu return seq_list_next(v, &fei_attr_list, pos); 2004b1a29a7SMasami Hiramatsu } 2014b1a29a7SMasami Hiramatsu 2024b1a29a7SMasami Hiramatsu static int fei_seq_show(struct seq_file *m, void *v) 2034b1a29a7SMasami Hiramatsu { 2044b1a29a7SMasami Hiramatsu struct fei_attr *attr = list_entry(v, struct fei_attr, list); 2054b1a29a7SMasami Hiramatsu 206d75f773cSSakari Ailus seq_printf(m, "%ps\n", attr->kp.addr); 2074b1a29a7SMasami Hiramatsu return 0; 2084b1a29a7SMasami Hiramatsu } 2094b1a29a7SMasami Hiramatsu 2104b1a29a7SMasami Hiramatsu static const struct seq_operations fei_seq_ops = { 2114b1a29a7SMasami Hiramatsu .start = fei_seq_start, 2124b1a29a7SMasami Hiramatsu .next = fei_seq_next, 2134b1a29a7SMasami Hiramatsu .stop = fei_seq_stop, 2144b1a29a7SMasami Hiramatsu .show = fei_seq_show, 2154b1a29a7SMasami Hiramatsu }; 2164b1a29a7SMasami Hiramatsu 2174b1a29a7SMasami Hiramatsu static int fei_open(struct inode *inode, struct file *file) 2184b1a29a7SMasami Hiramatsu { 2194b1a29a7SMasami Hiramatsu return seq_open(file, &fei_seq_ops); 2204b1a29a7SMasami Hiramatsu } 2214b1a29a7SMasami Hiramatsu 2224b1a29a7SMasami Hiramatsu static void fei_attr_remove(struct fei_attr *attr) 2234b1a29a7SMasami Hiramatsu { 2244b1a29a7SMasami Hiramatsu fei_debugfs_remove_attr(attr); 2254b1a29a7SMasami Hiramatsu unregister_kprobe(&attr->kp); 2264b1a29a7SMasami Hiramatsu list_del(&attr->list); 2274b1a29a7SMasami Hiramatsu fei_attr_free(attr); 2284b1a29a7SMasami Hiramatsu } 2294b1a29a7SMasami Hiramatsu 2304b1a29a7SMasami Hiramatsu static void fei_attr_remove_all(void) 2314b1a29a7SMasami Hiramatsu { 2324b1a29a7SMasami Hiramatsu struct fei_attr *attr, *n; 2334b1a29a7SMasami Hiramatsu 2344b1a29a7SMasami Hiramatsu list_for_each_entry_safe(attr, n, &fei_attr_list, list) { 2354b1a29a7SMasami Hiramatsu fei_attr_remove(attr); 2364b1a29a7SMasami Hiramatsu } 2374b1a29a7SMasami Hiramatsu } 2384b1a29a7SMasami Hiramatsu 2394b1a29a7SMasami Hiramatsu static ssize_t fei_write(struct file *file, const char __user *buffer, 2404b1a29a7SMasami Hiramatsu size_t count, loff_t *ppos) 2414b1a29a7SMasami Hiramatsu { 2424b1a29a7SMasami Hiramatsu struct fei_attr *attr; 2434b1a29a7SMasami Hiramatsu unsigned long addr; 2444b1a29a7SMasami Hiramatsu char *buf, *sym; 2454b1a29a7SMasami Hiramatsu int ret; 2464b1a29a7SMasami Hiramatsu 2474b1a29a7SMasami Hiramatsu /* cut off if it is too long */ 2484b1a29a7SMasami Hiramatsu if (count > KSYM_NAME_LEN) 2494b1a29a7SMasami Hiramatsu count = KSYM_NAME_LEN; 2506da2ec56SKees Cook buf = kmalloc(count + 1, GFP_KERNEL); 2514b1a29a7SMasami Hiramatsu if (!buf) 2524b1a29a7SMasami Hiramatsu return -ENOMEM; 2534b1a29a7SMasami Hiramatsu 2544b1a29a7SMasami Hiramatsu if (copy_from_user(buf, buffer, count)) { 2554b1a29a7SMasami Hiramatsu ret = -EFAULT; 2562801a5daSLuo Meng goto out_free; 2574b1a29a7SMasami Hiramatsu } 2584b1a29a7SMasami Hiramatsu buf[count] = '\0'; 2594b1a29a7SMasami Hiramatsu sym = strstrip(buf); 2604b1a29a7SMasami Hiramatsu 2614b1a29a7SMasami Hiramatsu mutex_lock(&fei_lock); 2624b1a29a7SMasami Hiramatsu 2634b1a29a7SMasami Hiramatsu /* Writing just spaces will remove all injection points */ 2644b1a29a7SMasami Hiramatsu if (sym[0] == '\0') { 2654b1a29a7SMasami Hiramatsu fei_attr_remove_all(); 2664b1a29a7SMasami Hiramatsu ret = count; 2674b1a29a7SMasami Hiramatsu goto out; 2684b1a29a7SMasami Hiramatsu } 2694b1a29a7SMasami Hiramatsu /* Writing !function will remove one injection point */ 2704b1a29a7SMasami Hiramatsu if (sym[0] == '!') { 2714b1a29a7SMasami Hiramatsu attr = fei_attr_lookup(sym + 1); 2724b1a29a7SMasami Hiramatsu if (!attr) { 2734b1a29a7SMasami Hiramatsu ret = -ENOENT; 2744b1a29a7SMasami Hiramatsu goto out; 2754b1a29a7SMasami Hiramatsu } 2764b1a29a7SMasami Hiramatsu fei_attr_remove(attr); 2774b1a29a7SMasami Hiramatsu ret = count; 2784b1a29a7SMasami Hiramatsu goto out; 2794b1a29a7SMasami Hiramatsu } 2804b1a29a7SMasami Hiramatsu 2814b1a29a7SMasami Hiramatsu addr = kallsyms_lookup_name(sym); 2824b1a29a7SMasami Hiramatsu if (!addr) { 2834b1a29a7SMasami Hiramatsu ret = -EINVAL; 2844b1a29a7SMasami Hiramatsu goto out; 2854b1a29a7SMasami Hiramatsu } 2864b1a29a7SMasami Hiramatsu if (!within_error_injection_list(addr)) { 2874b1a29a7SMasami Hiramatsu ret = -ERANGE; 2884b1a29a7SMasami Hiramatsu goto out; 2894b1a29a7SMasami Hiramatsu } 2904b1a29a7SMasami Hiramatsu if (fei_attr_lookup(sym)) { 2914b1a29a7SMasami Hiramatsu ret = -EBUSY; 2924b1a29a7SMasami Hiramatsu goto out; 2934b1a29a7SMasami Hiramatsu } 2944b1a29a7SMasami Hiramatsu attr = fei_attr_new(sym, addr); 2954b1a29a7SMasami Hiramatsu if (!attr) { 2964b1a29a7SMasami Hiramatsu ret = -ENOMEM; 2974b1a29a7SMasami Hiramatsu goto out; 2984b1a29a7SMasami Hiramatsu } 2994b1a29a7SMasami Hiramatsu 3004b1a29a7SMasami Hiramatsu ret = register_kprobe(&attr->kp); 3014b1a29a7SMasami Hiramatsu if (!ret) 3024aa3b1f6SGreg Kroah-Hartman fei_debugfs_add_attr(attr); 3034b1a29a7SMasami Hiramatsu if (ret < 0) 3044b1a29a7SMasami Hiramatsu fei_attr_remove(attr); 3054b1a29a7SMasami Hiramatsu else { 3064b1a29a7SMasami Hiramatsu list_add_tail(&attr->list, &fei_attr_list); 3074b1a29a7SMasami Hiramatsu ret = count; 3084b1a29a7SMasami Hiramatsu } 3094b1a29a7SMasami Hiramatsu out: 3104b1a29a7SMasami Hiramatsu mutex_unlock(&fei_lock); 3112801a5daSLuo Meng out_free: 3122801a5daSLuo Meng kfree(buf); 3134b1a29a7SMasami Hiramatsu return ret; 3144b1a29a7SMasami Hiramatsu } 3154b1a29a7SMasami Hiramatsu 3164b1a29a7SMasami Hiramatsu static const struct file_operations fei_ops = { 3174b1a29a7SMasami Hiramatsu .open = fei_open, 3184b1a29a7SMasami Hiramatsu .read = seq_read, 3194b1a29a7SMasami Hiramatsu .write = fei_write, 3204b1a29a7SMasami Hiramatsu .llseek = seq_lseek, 3214b1a29a7SMasami Hiramatsu .release = seq_release, 3224b1a29a7SMasami Hiramatsu }; 3234b1a29a7SMasami Hiramatsu 3244b1a29a7SMasami Hiramatsu static int __init fei_debugfs_init(void) 3254b1a29a7SMasami Hiramatsu { 3264b1a29a7SMasami Hiramatsu struct dentry *dir; 3274b1a29a7SMasami Hiramatsu 3284b1a29a7SMasami Hiramatsu dir = fault_create_debugfs_attr("fail_function", NULL, 3294b1a29a7SMasami Hiramatsu &fei_fault_attr); 3304b1a29a7SMasami Hiramatsu if (IS_ERR(dir)) 3314b1a29a7SMasami Hiramatsu return PTR_ERR(dir); 3324b1a29a7SMasami Hiramatsu 3334b1a29a7SMasami Hiramatsu /* injectable attribute is just a symlink of error_inject/list */ 3344aa3b1f6SGreg Kroah-Hartman debugfs_create_symlink("injectable", dir, "../error_injection/list"); 3354b1a29a7SMasami Hiramatsu 3364aa3b1f6SGreg Kroah-Hartman debugfs_create_file("inject", 0600, dir, NULL, &fei_ops); 3374b1a29a7SMasami Hiramatsu 3384b1a29a7SMasami Hiramatsu fei_debugfs_dir = dir; 3394b1a29a7SMasami Hiramatsu 3404b1a29a7SMasami Hiramatsu return 0; 3414b1a29a7SMasami Hiramatsu } 3424b1a29a7SMasami Hiramatsu 3434b1a29a7SMasami Hiramatsu late_initcall(fei_debugfs_init); 344