xref: /openbmc/linux/kernel/fail_function.c (revision 10c1d542)
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);
350