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