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 int 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        if (!dir)
 161                return -ENOMEM;
 162
 163        if (!debugfs_create_file("retval", 0600, dir, attr, &fei_retval_ops)) {
 164                debugfs_remove_recursive(dir);
 165                return -ENOMEM;
 166        }
 167
 168        return 0;
 169}
 170
 171static void fei_debugfs_remove_attr(struct fei_attr *attr)
 172{
 173        struct dentry *dir;
 174
 175        dir = debugfs_lookup(attr->kp.symbol_name, fei_debugfs_dir);
 176        if (dir)
 177                debugfs_remove_recursive(dir);
 178}
 179
 180static int fei_kprobe_handler(struct kprobe *kp, struct pt_regs *regs)
 181{
 182        struct fei_attr *attr = container_of(kp, struct fei_attr, kp);
 183
 184        if (should_fail(&fei_fault_attr, 1)) {
 185                regs_set_return_value(regs, attr->retval);
 186                override_function_with_return(regs);
 187                return 1;
 188        }
 189
 190        return 0;
 191}
 192NOKPROBE_SYMBOL(fei_kprobe_handler)
 193
 194static void *fei_seq_start(struct seq_file *m, loff_t *pos)
 195{
 196        mutex_lock(&fei_lock);
 197        return seq_list_start(&fei_attr_list, *pos);
 198}
 199
 200static void fei_seq_stop(struct seq_file *m, void *v)
 201{
 202        mutex_unlock(&fei_lock);
 203}
 204
 205static void *fei_seq_next(struct seq_file *m, void *v, loff_t *pos)
 206{
 207        return seq_list_next(v, &fei_attr_list, pos);
 208}
 209
 210static int fei_seq_show(struct seq_file *m, void *v)
 211{
 212        struct fei_attr *attr = list_entry(v, struct fei_attr, list);
 213
 214        seq_printf(m, "%pf\n", attr->kp.addr);
 215        return 0;
 216}
 217
 218static const struct seq_operations fei_seq_ops = {
 219        .start  = fei_seq_start,
 220        .next   = fei_seq_next,
 221        .stop   = fei_seq_stop,
 222        .show   = fei_seq_show,
 223};
 224
 225static int fei_open(struct inode *inode, struct file *file)
 226{
 227        return seq_open(file, &fei_seq_ops);
 228}
 229
 230static void fei_attr_remove(struct fei_attr *attr)
 231{
 232        fei_debugfs_remove_attr(attr);
 233        unregister_kprobe(&attr->kp);
 234        list_del(&attr->list);
 235        fei_attr_free(attr);
 236}
 237
 238static void fei_attr_remove_all(void)
 239{
 240        struct fei_attr *attr, *n;
 241
 242        list_for_each_entry_safe(attr, n, &fei_attr_list, list) {
 243                fei_attr_remove(attr);
 244        }
 245}
 246
 247static ssize_t fei_write(struct file *file, const char __user *buffer,
 248                         size_t count, loff_t *ppos)
 249{
 250        struct fei_attr *attr;
 251        unsigned long addr;
 252        char *buf, *sym;
 253        int ret;
 254
 255        /* cut off if it is too long */
 256        if (count > KSYM_NAME_LEN)
 257                count = KSYM_NAME_LEN;
 258        buf = kmalloc(count + 1, GFP_KERNEL);
 259        if (!buf)
 260                return -ENOMEM;
 261
 262        if (copy_from_user(buf, buffer, count)) {
 263                ret = -EFAULT;
 264                goto out;
 265        }
 266        buf[count] = '\0';
 267        sym = strstrip(buf);
 268
 269        mutex_lock(&fei_lock);
 270
 271        /* Writing just spaces will remove all injection points */
 272        if (sym[0] == '\0') {
 273                fei_attr_remove_all();
 274                ret = count;
 275                goto out;
 276        }
 277        /* Writing !function will remove one injection point */
 278        if (sym[0] == '!') {
 279                attr = fei_attr_lookup(sym + 1);
 280                if (!attr) {
 281                        ret = -ENOENT;
 282                        goto out;
 283                }
 284                fei_attr_remove(attr);
 285                ret = count;
 286                goto out;
 287        }
 288
 289        addr = kallsyms_lookup_name(sym);
 290        if (!addr) {
 291                ret = -EINVAL;
 292                goto out;
 293        }
 294        if (!within_error_injection_list(addr)) {
 295                ret = -ERANGE;
 296                goto out;
 297        }
 298        if (fei_attr_lookup(sym)) {
 299                ret = -EBUSY;
 300                goto out;
 301        }
 302        attr = fei_attr_new(sym, addr);
 303        if (!attr) {
 304                ret = -ENOMEM;
 305                goto out;
 306        }
 307
 308        ret = register_kprobe(&attr->kp);
 309        if (!ret)
 310                ret = fei_debugfs_add_attr(attr);
 311        if (ret < 0)
 312                fei_attr_remove(attr);
 313        else {
 314                list_add_tail(&attr->list, &fei_attr_list);
 315                ret = count;
 316        }
 317out:
 318        kfree(buf);
 319        mutex_unlock(&fei_lock);
 320        return ret;
 321}
 322
 323static const struct file_operations fei_ops = {
 324        .open =         fei_open,
 325        .read =         seq_read,
 326        .write =        fei_write,
 327        .llseek =       seq_lseek,
 328        .release =      seq_release,
 329};
 330
 331static int __init fei_debugfs_init(void)
 332{
 333        struct dentry *dir;
 334
 335        dir = fault_create_debugfs_attr("fail_function", NULL,
 336                                        &fei_fault_attr);
 337        if (IS_ERR(dir))
 338                return PTR_ERR(dir);
 339
 340        /* injectable attribute is just a symlink of error_inject/list */
 341        if (!debugfs_create_symlink("injectable", dir,
 342                                    "../error_injection/list"))
 343                goto error;
 344
 345        if (!debugfs_create_file("inject", 0600, dir, NULL, &fei_ops))
 346                goto error;
 347
 348        fei_debugfs_dir = dir;
 349
 350        return 0;
 351error:
 352        debugfs_remove_recursive(dir);
 353        return -ENOMEM;
 354}
 355
 356late_initcall(fei_debugfs_init);
 357