linux/kernel/fail_function.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * fail_function.c: Function-based error injection
   4 */
   5#include <linux/error-injection.h>
   6#include <linux/debugfs.h>
   7#include <linux/fault-inject.h>
   8#include <linux/kallsyms.h>
   9#include <linux/kprobes.h>
  10#include <linux/module.h>
  11#include <linux/mutex.h>
  12#include <linux/slab.h>
  13#include <linux/uaccess.h>
  14
  15static int fei_kprobe_handler(struct kprobe *kp, struct pt_regs *regs);
  16
  17static void fei_post_handler(struct kprobe *kp, struct pt_regs *regs,
  18                             unsigned long flags)
  19{
  20        /*
  21         * A dummy post handler is required to prohibit optimizing, because
  22         * jump optimization does not support execution path overriding.
  23         */
  24}
  25
  26struct fei_attr {
  27        struct list_head list;
  28        struct kprobe kp;
  29        unsigned long retval;
  30};
  31static DEFINE_MUTEX(fei_lock);
  32static LIST_HEAD(fei_attr_list);
  33static DECLARE_FAULT_ATTR(fei_fault_attr);
  34static struct dentry *fei_debugfs_dir;
  35
  36static unsigned long adjust_error_retval(unsigned long addr, unsigned long retv)
  37{
  38        switch (get_injectable_error_type(addr)) {
  39        case EI_ETYPE_NULL:
  40                if (retv != 0)
  41                        return 0;
  42                break;
  43        case EI_ETYPE_ERRNO:
  44                if (retv < (unsigned long)-MAX_ERRNO)
  45                        return (unsigned long)-EINVAL;
  46                break;
  47        case EI_ETYPE_ERRNO_NULL:
  48                if (retv != 0 && retv < (unsigned long)-MAX_ERRNO)
  49                        return (unsigned long)-EINVAL;
  50                break;
  51        }
  52
  53        return retv;
  54}
  55
  56static struct fei_attr *fei_attr_new(const char *sym, unsigned long addr)
  57{
  58        struct fei_attr *attr;
  59
  60        attr = kzalloc(sizeof(*attr), GFP_KERNEL);
  61        if (attr) {
  62                attr->kp.symbol_name = kstrdup(sym, GFP_KERNEL);
  63                if (!attr->kp.symbol_name) {
  64                        kfree(attr);
  65                        return NULL;
  66                }
  67                attr->kp.pre_handler = fei_kprobe_handler;
  68                attr->kp.post_handler = fei_post_handler;
  69                attr->retval = adjust_error_retval(addr, 0);
  70                INIT_LIST_HEAD(&attr->list);
  71        }
  72        return attr;
  73}
  74
  75static void fei_attr_free(struct fei_attr *attr)
  76{
  77        if (attr) {
  78                kfree(attr->kp.symbol_name);
  79                kfree(attr);
  80        }
  81}
  82
  83static struct fei_attr *fei_attr_lookup(const char *sym)
  84{
  85        struct fei_attr *attr;
  86
  87        list_for_each_entry(attr, &fei_attr_list, list) {
  88                if (!strcmp(attr->kp.symbol_name, sym))
  89                        return attr;
  90        }
  91
  92        return NULL;
  93}
  94
  95static bool fei_attr_is_valid(struct fei_attr *_attr)
  96{
  97        struct fei_attr *attr;
  98
  99        list_for_each_entry(attr, &fei_attr_list, list) {
 100                if (attr == _attr)
 101                        return true;
 102        }
 103
 104        return false;
 105}
 106
 107static int fei_retval_set(void *data, u64 val)
 108{
 109        struct fei_attr *attr = data;
 110        unsigned long retv = (unsigned long)val;
 111        int err = 0;
 112
 113        mutex_lock(&fei_lock);
 114        /*
 115         * Since this operation can be done after retval file is removed,
 116         * It is safer to check the attr is still valid before accessing
 117         * its member.
 118         */
 119        if (!fei_attr_is_valid(attr)) {
 120                err = -ENOENT;
 121                goto out;
 122        }
 123
 124        if (attr->kp.addr) {
 125                if (adjust_error_retval((unsigned long)attr->kp.addr,
 126                                        val) != retv)
 127                        err = -EINVAL;
 128        }
 129        if (!err)
 130                attr->retval = val;
 131out:
 132        mutex_unlock(&fei_lock);
 133
 134        return err;
 135}
 136
 137static int fei_retval_get(void *data, u64 *val)
 138{
 139        struct fei_attr *attr = data;
 140        int err = 0;
 141
 142        mutex_lock(&fei_lock);
 143        /* Here we also validate @attr to ensure it still exists. */
 144        if (!fei_attr_is_valid(attr))
 145                err = -ENOENT;
 146        else
 147                *val = attr->retval;
 148        mutex_unlock(&fei_lock);
 149
 150        return err;
 151}
 152DEFINE_DEBUGFS_ATTRIBUTE(fei_retval_ops, fei_retval_get, fei_retval_set,
 153                         "%llx\n");
 154
 155static void fei_debugfs_add_attr(struct fei_attr *attr)
 156{
 157        struct dentry *dir;
 158
 159        dir = debugfs_create_dir(attr->kp.symbol_name, fei_debugfs_dir);
 160
 161        debugfs_create_file("retval", 0600, dir, attr, &fei_retval_ops);
 162}
 163
 164static void fei_debugfs_remove_attr(struct fei_attr *attr)
 165{
 166        struct dentry *dir;
 167
 168        dir = debugfs_lookup(attr->kp.symbol_name, fei_debugfs_dir);
 169        debugfs_remove_recursive(dir);
 170}
 171
 172static int fei_kprobe_handler(struct kprobe *kp, struct pt_regs *regs)
 173{
 174        struct fei_attr *attr = container_of(kp, struct fei_attr, kp);
 175
 176        if (should_fail(&fei_fault_attr, 1)) {
 177                regs_set_return_value(regs, attr->retval);
 178                override_function_with_return(regs);
 179                return 1;
 180        }
 181
 182        return 0;
 183}
 184NOKPROBE_SYMBOL(fei_kprobe_handler)
 185
 186static void *fei_seq_start(struct seq_file *m, loff_t *pos)
 187{
 188        mutex_lock(&fei_lock);
 189        return seq_list_start(&fei_attr_list, *pos);
 190}
 191
 192static void fei_seq_stop(struct seq_file *m, void *v)
 193{
 194        mutex_unlock(&fei_lock);
 195}
 196
 197static void *fei_seq_next(struct seq_file *m, void *v, loff_t *pos)
 198{
 199        return seq_list_next(v, &fei_attr_list, pos);
 200}
 201
 202static int fei_seq_show(struct seq_file *m, void *v)
 203{
 204        struct fei_attr *attr = list_entry(v, struct fei_attr, list);
 205
 206        seq_printf(m, "%ps\n", attr->kp.addr);
 207        return 0;
 208}
 209
 210static const struct seq_operations fei_seq_ops = {
 211        .start  = fei_seq_start,
 212        .next   = fei_seq_next,
 213        .stop   = fei_seq_stop,
 214        .show   = fei_seq_show,
 215};
 216
 217static int fei_open(struct inode *inode, struct file *file)
 218{
 219        return seq_open(file, &fei_seq_ops);
 220}
 221
 222static void fei_attr_remove(struct fei_attr *attr)
 223{
 224        fei_debugfs_remove_attr(attr);
 225        unregister_kprobe(&attr->kp);
 226        list_del(&attr->list);
 227        fei_attr_free(attr);
 228}
 229
 230static void fei_attr_remove_all(void)
 231{
 232        struct fei_attr *attr, *n;
 233
 234        list_for_each_entry_safe(attr, n, &fei_attr_list, list) {
 235                fei_attr_remove(attr);
 236        }
 237}
 238
 239static ssize_t fei_write(struct file *file, const char __user *buffer,
 240                         size_t count, loff_t *ppos)
 241{
 242        struct fei_attr *attr;
 243        unsigned long addr;
 244        char *buf, *sym;
 245        int ret;
 246
 247        /* cut off if it is too long */
 248        if (count > KSYM_NAME_LEN)
 249                count = KSYM_NAME_LEN;
 250        buf = kmalloc(count + 1, GFP_KERNEL);
 251        if (!buf)
 252                return -ENOMEM;
 253
 254        if (copy_from_user(buf, buffer, count)) {
 255                ret = -EFAULT;
 256                goto out;
 257        }
 258        buf[count] = '\0';
 259        sym = strstrip(buf);
 260
 261        mutex_lock(&fei_lock);
 262
 263        /* Writing just spaces will remove all injection points */
 264        if (sym[0] == '\0') {
 265                fei_attr_remove_all();
 266                ret = count;
 267                goto out;
 268        }
 269        /* Writing !function will remove one injection point */
 270        if (sym[0] == '!') {
 271                attr = fei_attr_lookup(sym + 1);
 272                if (!attr) {
 273                        ret = -ENOENT;
 274                        goto out;
 275                }
 276                fei_attr_remove(attr);
 277                ret = count;
 278                goto out;
 279        }
 280
 281        addr = kallsyms_lookup_name(sym);
 282        if (!addr) {
 283                ret = -EINVAL;
 284                goto out;
 285        }
 286        if (!within_error_injection_list(addr)) {
 287                ret = -ERANGE;
 288                goto out;
 289        }
 290        if (fei_attr_lookup(sym)) {
 291                ret = -EBUSY;
 292                goto out;
 293        }
 294        attr = fei_attr_new(sym, addr);
 295        if (!attr) {
 296                ret = -ENOMEM;
 297                goto out;
 298        }
 299
 300        ret = register_kprobe(&attr->kp);
 301        if (!ret)
 302                fei_debugfs_add_attr(attr);
 303        if (ret < 0)
 304                fei_attr_remove(attr);
 305        else {
 306                list_add_tail(&attr->list, &fei_attr_list);
 307                ret = count;
 308        }
 309out:
 310        kfree(buf);
 311        mutex_unlock(&fei_lock);
 312        return ret;
 313}
 314
 315static const struct file_operations fei_ops = {
 316        .open =         fei_open,
 317        .read =         seq_read,
 318        .write =        fei_write,
 319        .llseek =       seq_lseek,
 320        .release =      seq_release,
 321};
 322
 323static int __init fei_debugfs_init(void)
 324{
 325        struct dentry *dir;
 326
 327        dir = fault_create_debugfs_attr("fail_function", NULL,
 328                                        &fei_fault_attr);
 329        if (IS_ERR(dir))
 330                return PTR_ERR(dir);
 331
 332        /* injectable attribute is just a symlink of error_inject/list */
 333        debugfs_create_symlink("injectable", dir, "../error_injection/list");
 334
 335        debugfs_create_file("inject", 0600, dir, NULL, &fei_ops);
 336
 337        fei_debugfs_dir = dir;
 338
 339        return 0;
 340}
 341
 342late_initcall(fei_debugfs_init);
 343