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