Commit fdde5f3b authored by Alexei Starovoitov's avatar Alexei Starovoitov

Merge branch 'error-injection'

Masami Hiramatsu says:

====================
Here are the 5th version of patches to moving error injection
table from kprobes. This version fixes a bug and update
fail-function to support multiple function error injection.

Here is the previous version:

https://patchwork.ozlabs.org/cover/858663/

Changes in v5:
 - [3/5] Fix a bug that within_error_injection returns false always.
 - [5/5] Update to support multiple function error injection.

Thank you,
====================
Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parents daaf24c6 4b1a29a7
...@@ -30,6 +30,12 @@ o fail_mmc_request ...@@ -30,6 +30,12 @@ o fail_mmc_request
injects MMC data errors on devices permitted by setting injects MMC data errors on devices permitted by setting
debugfs entries under /sys/kernel/debug/mmc0/fail_mmc_request debugfs entries under /sys/kernel/debug/mmc0/fail_mmc_request
o fail_function
injects error return on specific functions, which are marked by
ALLOW_ERROR_INJECTION() macro, by setting debugfs entries
under /sys/kernel/debug/fail_function. No boot option supported.
Configure fault-injection capabilities behavior Configure fault-injection capabilities behavior
----------------------------------------------- -----------------------------------------------
...@@ -123,6 +129,29 @@ configuration of fault-injection capabilities. ...@@ -123,6 +129,29 @@ configuration of fault-injection capabilities.
default is 'N', setting it to 'Y' will disable failure injections default is 'N', setting it to 'Y' will disable failure injections
when dealing with private (address space) futexes. when dealing with private (address space) futexes.
- /sys/kernel/debug/fail_function/inject:
Format: { 'function-name' | '!function-name' | '' }
specifies the target function of error injection by name.
If the function name leads '!' prefix, given function is
removed from injection list. If nothing specified ('')
injection list is cleared.
- /sys/kernel/debug/fail_function/injectable:
(read only) shows error injectable functions and what type of
error values can be specified. The error type will be one of
below;
- NULL: retval must be 0.
- ERRNO: retval must be -1 to -MAX_ERRNO (-4096).
- ERR_NULL: retval must be 0 or -1 to -MAX_ERRNO (-4096).
- /sys/kernel/debug/fail_function/<functiuon-name>/retval:
specifies the "error" return value to inject to the given
function for given function. This will be created when
user specifies new injection entry.
o Boot option o Boot option
In order to inject faults while debugfs is not available (early boot time), In order to inject faults while debugfs is not available (early boot time),
...@@ -268,6 +297,45 @@ trap "echo 0 > /sys/kernel/debug/$FAILTYPE/probability" SIGINT SIGTERM EXIT ...@@ -268,6 +297,45 @@ trap "echo 0 > /sys/kernel/debug/$FAILTYPE/probability" SIGINT SIGTERM EXIT
echo "Injecting errors into the module $module... (interrupt to stop)" echo "Injecting errors into the module $module... (interrupt to stop)"
sleep 1000000 sleep 1000000
------------------------------------------------------------------------------
o Inject open_ctree error while btrfs mount
#!/bin/bash
rm -f testfile.img
dd if=/dev/zero of=testfile.img bs=1M seek=1000 count=1
DEVICE=$(losetup --show -f testfile.img)
mkfs.btrfs -f $DEVICE
mkdir -p tmpmnt
FAILTYPE=fail_function
FAILFUNC=open_ctree
echo $FAILFUNC > /sys/kernel/debug/$FAILTYPE/inject
echo -12 > /sys/kernel/debug/$FAILTYPE/$FAILFUNC/retval
echo N > /sys/kernel/debug/$FAILTYPE/task-filter
echo 100 > /sys/kernel/debug/$FAILTYPE/probability
echo 0 > /sys/kernel/debug/$FAILTYPE/interval
echo -1 > /sys/kernel/debug/$FAILTYPE/times
echo 0 > /sys/kernel/debug/$FAILTYPE/space
echo 1 > /sys/kernel/debug/$FAILTYPE/verbose
mount -t btrfs $DEVICE tmpmnt
if [ $? -ne 0 ]
then
echo "SUCCESS!"
else
echo "FAILED!"
umount tmpmnt
fi
echo > /sys/kernel/debug/$FAILTYPE/inject
rmdir tmpmnt
losetup -d $DEVICE
rm testfile.img
Tool to run command with failslab or fail_page_alloc Tool to run command with failslab or fail_page_alloc
---------------------------------------------------- ----------------------------------------------------
In order to make it easier to accomplish the tasks mentioned above, we can use In order to make it easier to accomplish the tasks mentioned above, we can use
......
...@@ -196,7 +196,7 @@ config HAVE_OPTPROBES ...@@ -196,7 +196,7 @@ config HAVE_OPTPROBES
config HAVE_KPROBES_ON_FTRACE config HAVE_KPROBES_ON_FTRACE
bool bool
config HAVE_KPROBE_OVERRIDE config HAVE_FUNCTION_ERROR_INJECTION
bool bool
config HAVE_NMI config HAVE_NMI
......
...@@ -154,7 +154,7 @@ config X86 ...@@ -154,7 +154,7 @@ config X86
select HAVE_KERNEL_XZ select HAVE_KERNEL_XZ
select HAVE_KPROBES select HAVE_KPROBES
select HAVE_KPROBES_ON_FTRACE select HAVE_KPROBES_ON_FTRACE
select HAVE_KPROBE_OVERRIDE select HAVE_FUNCTION_ERROR_INJECTION
select HAVE_KRETPROBES select HAVE_KRETPROBES
select HAVE_KVM select HAVE_KVM
select HAVE_LIVEPATCH if X86_64 select HAVE_LIVEPATCH if X86_64
......
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _ASM_ERROR_INJECTION_H
#define _ASM_ERROR_INJECTION_H
#include <linux/compiler.h>
#include <linux/linkage.h>
#include <asm/ptrace.h>
#include <asm-generic/error-injection.h>
asmlinkage void just_return_func(void);
void override_function_with_return(struct pt_regs *regs);
#endif /* _ASM_ERROR_INJECTION_H */
...@@ -67,9 +67,7 @@ extern const int kretprobe_blacklist_size; ...@@ -67,9 +67,7 @@ extern const int kretprobe_blacklist_size;
void arch_remove_kprobe(struct kprobe *p); void arch_remove_kprobe(struct kprobe *p);
asmlinkage void kretprobe_trampoline(void); asmlinkage void kretprobe_trampoline(void);
#ifdef CONFIG_KPROBES_ON_FTRACE extern void arch_kprobe_override_function(struct pt_regs *regs);
extern void arch_ftrace_kprobe_override_function(struct pt_regs *regs);
#endif
/* Architecture specific copy of original instruction*/ /* Architecture specific copy of original instruction*/
struct arch_specific_insn { struct arch_specific_insn {
......
...@@ -97,17 +97,3 @@ int arch_prepare_kprobe_ftrace(struct kprobe *p) ...@@ -97,17 +97,3 @@ int arch_prepare_kprobe_ftrace(struct kprobe *p)
p->ainsn.boostable = false; p->ainsn.boostable = false;
return 0; return 0;
} }
asmlinkage void override_func(void);
asm(
".type override_func, @function\n"
"override_func:\n"
" ret\n"
".size override_func, .-override_func\n"
);
void arch_ftrace_kprobe_override_function(struct pt_regs *regs)
{
regs->ip = (unsigned long)&override_func;
}
NOKPROBE_SYMBOL(arch_ftrace_kprobe_override_function);
...@@ -26,6 +26,7 @@ lib-y += memcpy_$(BITS).o ...@@ -26,6 +26,7 @@ lib-y += memcpy_$(BITS).o
lib-$(CONFIG_RWSEM_XCHGADD_ALGORITHM) += rwsem.o lib-$(CONFIG_RWSEM_XCHGADD_ALGORITHM) += rwsem.o
lib-$(CONFIG_INSTRUCTION_DECODER) += insn.o inat.o insn-eval.o lib-$(CONFIG_INSTRUCTION_DECODER) += insn.o inat.o insn-eval.o
lib-$(CONFIG_RANDOMIZE_BASE) += kaslr.o lib-$(CONFIG_RANDOMIZE_BASE) += kaslr.o
lib-$(CONFIG_FUNCTION_ERROR_INJECTION) += error-inject.o
obj-y += msr.o msr-reg.o msr-reg-export.o hweight.o obj-y += msr.o msr-reg.o msr-reg-export.o hweight.o
......
// SPDX-License-Identifier: GPL-2.0
#include <linux/error-injection.h>
#include <linux/kprobes.h>
asmlinkage void just_return_func(void);
asm(
".type just_return_func, @function\n"
"just_return_func:\n"
" ret\n"
".size just_return_func, .-just_return_func\n"
);
void override_function_with_return(struct pt_regs *regs)
{
regs->ip = (unsigned long)&just_return_func;
}
NOKPROBE_SYMBOL(override_function_with_return);
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
#include <linux/ratelimit.h> #include <linux/ratelimit.h>
#include <linux/uuid.h> #include <linux/uuid.h>
#include <linux/semaphore.h> #include <linux/semaphore.h>
#include <linux/bpf.h> #include <linux/error-injection.h>
#include <asm/unaligned.h> #include <asm/unaligned.h>
#include "ctree.h" #include "ctree.h"
#include "disk-io.h" #include "disk-io.h"
...@@ -3124,7 +3124,7 @@ int open_ctree(struct super_block *sb, ...@@ -3124,7 +3124,7 @@ int open_ctree(struct super_block *sb,
goto fail_block_groups; goto fail_block_groups;
goto retry_root_backup; goto retry_root_backup;
} }
BPF_ALLOW_ERROR_INJECTION(open_ctree); ALLOW_ERROR_INJECTION(open_ctree, ERRNO);
static void btrfs_end_buffer_write_sync(struct buffer_head *bh, int uptodate) static void btrfs_end_buffer_write_sync(struct buffer_head *bh, int uptodate)
{ {
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/math64.h> #include <linux/math64.h>
#include <linux/ratelimit.h> #include <linux/ratelimit.h>
#include <linux/bpf.h> #include <linux/error-injection.h>
#include "ctree.h" #include "ctree.h"
#include "free-space-cache.h" #include "free-space-cache.h"
#include "transaction.h" #include "transaction.h"
...@@ -333,7 +333,7 @@ static int io_ctl_init(struct btrfs_io_ctl *io_ctl, struct inode *inode, ...@@ -333,7 +333,7 @@ static int io_ctl_init(struct btrfs_io_ctl *io_ctl, struct inode *inode,
return 0; return 0;
} }
BPF_ALLOW_ERROR_INJECTION(io_ctl_init); ALLOW_ERROR_INJECTION(io_ctl_init, ERRNO);
static void io_ctl_free(struct btrfs_io_ctl *io_ctl) static void io_ctl_free(struct btrfs_io_ctl *io_ctl)
{ {
......
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _ASM_GENERIC_ERROR_INJECTION_H
#define _ASM_GENERIC_ERROR_INJECTION_H
#if defined(__KERNEL__) && !defined(__ASSEMBLY__)
enum {
EI_ETYPE_NONE, /* Dummy value for undefined case */
EI_ETYPE_NULL, /* Return NULL if failure */
EI_ETYPE_ERRNO, /* Return -ERRNO if failure */
EI_ETYPE_ERRNO_NULL, /* Return -ERRNO or NULL if failure */
};
struct error_injection_entry {
unsigned long addr;
int etype;
};
#ifdef CONFIG_FUNCTION_ERROR_INJECTION
/*
* Whitelist ganerating macro. Specify functions which can be
* error-injectable using this macro.
*/
#define ALLOW_ERROR_INJECTION(fname, _etype) \
static struct error_injection_entry __used \
__attribute__((__section__("_error_injection_whitelist"))) \
_eil_addr_##fname = { \
.addr = (unsigned long)fname, \
.etype = EI_ETYPE_##_etype, \
};
#else
#define ALLOW_ERROR_INJECTION(fname, _etype)
#endif
#endif
#endif /* _ASM_GENERIC_ERROR_INJECTION_H */
...@@ -136,13 +136,13 @@ ...@@ -136,13 +136,13 @@
#define KPROBE_BLACKLIST() #define KPROBE_BLACKLIST()
#endif #endif
#ifdef CONFIG_BPF_KPROBE_OVERRIDE #ifdef CONFIG_FUNCTION_ERROR_INJECTION
#define ERROR_INJECT_LIST() . = ALIGN(8); \ #define ERROR_INJECT_WHITELIST() STRUCT_ALIGN(); \
VMLINUX_SYMBOL(__start_kprobe_error_inject_list) = .; \ VMLINUX_SYMBOL(__start_error_injection_whitelist) = .;\
KEEP(*(_kprobe_error_inject_list)) \ KEEP(*(_error_injection_whitelist)) \
VMLINUX_SYMBOL(__stop_kprobe_error_inject_list) = .; VMLINUX_SYMBOL(__stop_error_injection_whitelist) = .;
#else #else
#define ERROR_INJECT_LIST() #define ERROR_INJECT_WHITELIST()
#endif #endif
#ifdef CONFIG_EVENT_TRACING #ifdef CONFIG_EVENT_TRACING
...@@ -573,7 +573,7 @@ ...@@ -573,7 +573,7 @@
FTRACE_EVENTS() \ FTRACE_EVENTS() \
TRACE_SYSCALLS() \ TRACE_SYSCALLS() \
KPROBE_BLACKLIST() \ KPROBE_BLACKLIST() \
ERROR_INJECT_LIST() \ ERROR_INJECT_WHITELIST() \
MEM_DISCARD(init.rodata) \ MEM_DISCARD(init.rodata) \
CLK_OF_TABLES() \ CLK_OF_TABLES() \
RESERVEDMEM_OF_TABLES() \ RESERVEDMEM_OF_TABLES() \
......
...@@ -613,15 +613,4 @@ extern const struct bpf_func_proto bpf_sock_map_update_proto; ...@@ -613,15 +613,4 @@ extern const struct bpf_func_proto bpf_sock_map_update_proto;
void bpf_user_rnd_init_once(void); void bpf_user_rnd_init_once(void);
u64 bpf_user_rnd_u32(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5); u64 bpf_user_rnd_u32(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5);
#if defined(__KERNEL__) && !defined(__ASSEMBLY__)
#ifdef CONFIG_BPF_KPROBE_OVERRIDE
#define BPF_ALLOW_ERROR_INJECTION(fname) \
static unsigned long __used \
__attribute__((__section__("_kprobe_error_inject_list"))) \
_eil_addr_##fname = (unsigned long)fname;
#else
#define BPF_ALLOW_ERROR_INJECTION(fname)
#endif
#endif
#endif /* _LINUX_BPF_H */ #endif /* _LINUX_BPF_H */
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _LINUX_ERROR_INJECTION_H
#define _LINUX_ERROR_INJECTION_H
#ifdef CONFIG_FUNCTION_ERROR_INJECTION
#include <asm/error-injection.h>
extern bool within_error_injection_list(unsigned long addr);
extern int get_injectable_error_type(unsigned long addr);
#else /* !CONFIG_FUNCTION_ERROR_INJECTION */
#include <asm-generic/error-injection.h>
static inline bool within_error_injection_list(unsigned long addr)
{
return false;
}
static inline int get_injectable_error_type(unsigned long addr)
{
return EI_ETYPE_NONE;
}
#endif
#endif /* _LINUX_ERROR_INJECTION_H */
...@@ -271,7 +271,6 @@ extern bool arch_kprobe_on_func_entry(unsigned long offset); ...@@ -271,7 +271,6 @@ extern bool arch_kprobe_on_func_entry(unsigned long offset);
extern bool kprobe_on_func_entry(kprobe_opcode_t *addr, const char *sym, unsigned long offset); extern bool kprobe_on_func_entry(kprobe_opcode_t *addr, const char *sym, unsigned long offset);
extern bool within_kprobe_blacklist(unsigned long addr); extern bool within_kprobe_blacklist(unsigned long addr);
extern bool within_kprobe_error_injection_list(unsigned long addr);
struct kprobe_insn_cache { struct kprobe_insn_cache {
struct mutex mutex; struct mutex mutex;
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include <linux/jump_label.h> #include <linux/jump_label.h>
#include <linux/export.h> #include <linux/export.h>
#include <linux/rbtree_latch.h> #include <linux/rbtree_latch.h>
#include <linux/error-injection.h>
#include <linux/percpu.h> #include <linux/percpu.h>
#include <asm/module.h> #include <asm/module.h>
...@@ -476,9 +477,9 @@ struct module { ...@@ -476,9 +477,9 @@ struct module {
unsigned int num_ctors; unsigned int num_ctors;
#endif #endif
#ifdef CONFIG_BPF_KPROBE_OVERRIDE #ifdef CONFIG_FUNCTION_ERROR_INJECTION
unsigned int num_kprobe_ei_funcs; struct error_injection_entry *ei_funcs;
unsigned long *kprobe_ei_funcs; unsigned int num_ei_funcs;
#endif #endif
} ____cacheline_aligned __randomize_layout; } ____cacheline_aligned __randomize_layout;
#ifndef MODULE_ARCH_INIT #ifndef MODULE_ARCH_INIT
......
...@@ -81,6 +81,7 @@ obj-$(CONFIG_AUDIT_TREE) += audit_tree.o ...@@ -81,6 +81,7 @@ obj-$(CONFIG_AUDIT_TREE) += audit_tree.o
obj-$(CONFIG_GCOV_KERNEL) += gcov/ obj-$(CONFIG_GCOV_KERNEL) += gcov/
obj-$(CONFIG_KCOV) += kcov.o obj-$(CONFIG_KCOV) += kcov.o
obj-$(CONFIG_KPROBES) += kprobes.o obj-$(CONFIG_KPROBES) += kprobes.o
obj-$(CONFIG_FAIL_FUNCTION) += fail_function.o
obj-$(CONFIG_KGDB) += debug/ obj-$(CONFIG_KGDB) += debug/
obj-$(CONFIG_DETECT_HUNG_TASK) += hung_task.o obj-$(CONFIG_DETECT_HUNG_TASK) += hung_task.o
obj-$(CONFIG_LOCKUP_DETECTOR) += watchdog.o obj-$(CONFIG_LOCKUP_DETECTOR) += watchdog.o
......
// SPDX-License-Identifier: GPL-2.0
/*
* fail_function.c: Function-based error injection
*/
#include <linux/error-injection.h>
#include <linux/debugfs.h>
#include <linux/fault-inject.h>
#include <linux/kallsyms.h>
#include <linux/kprobes.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
static int fei_kprobe_handler(struct kprobe *kp, struct pt_regs *regs);
struct fei_attr {
struct list_head list;
struct kprobe kp;
unsigned long retval;
};
static DEFINE_MUTEX(fei_lock);
static LIST_HEAD(fei_attr_list);
static DECLARE_FAULT_ATTR(fei_fault_attr);
static struct dentry *fei_debugfs_dir;
static unsigned long adjust_error_retval(unsigned long addr, unsigned long retv)
{
switch (get_injectable_error_type(addr)) {
case EI_ETYPE_NULL:
if (retv != 0)
return 0;
break;
case EI_ETYPE_ERRNO:
if (retv < (unsigned long)-MAX_ERRNO)
return (unsigned long)-EINVAL;
break;
case EI_ETYPE_ERRNO_NULL:
if (retv != 0 && retv < (unsigned long)-MAX_ERRNO)
return (unsigned long)-EINVAL;
break;
}
return retv;
}
static struct fei_attr *fei_attr_new(const char *sym, unsigned long addr)
{
struct fei_attr *attr;
attr = kzalloc(sizeof(*attr), GFP_KERNEL);
if (attr) {
attr->kp.symbol_name = kstrdup(sym, GFP_KERNEL);
if (!attr->kp.symbol_name) {
kfree(attr);
return NULL;
}
attr->kp.pre_handler = fei_kprobe_handler;
attr->retval = adjust_error_retval(addr, 0);
INIT_LIST_HEAD(&attr->list);
}
return attr;
}
static void fei_attr_free(struct fei_attr *attr)
{
if (attr) {
kfree(attr->kp.symbol_name);
kfree(attr);
}
}
static struct fei_attr *fei_attr_lookup(const char *sym)
{
struct fei_attr *attr;
list_for_each_entry(attr, &fei_attr_list, list) {
if (!strcmp(attr->kp.symbol_name, sym))
return attr;
}
return NULL;
}
static bool fei_attr_is_valid(struct fei_attr *_attr)
{
struct fei_attr *attr;
list_for_each_entry(attr, &fei_attr_list, list) {
if (attr == _attr)
return true;
}
return false;
}
static int fei_retval_set(void *data, u64 val)
{
struct fei_attr *attr = data;
unsigned long retv = (unsigned long)val;
int err = 0;
mutex_lock(&fei_lock);
/*
* Since this operation can be done after retval file is removed,
* It is safer to check the attr is still valid before accessing
* its member.
*/
if (!fei_attr_is_valid(attr)) {
err = -ENOENT;
goto out;
}
if (attr->kp.addr) {
if (adjust_error_retval((unsigned long)attr->kp.addr,
val) != retv)
err = -EINVAL;
}
if (!err)
attr->retval = val;
out:
mutex_unlock(&fei_lock);
return err;
}
static int fei_retval_get(void *data, u64 *val)
{
struct fei_attr *attr = data;
int err = 0;
mutex_lock(&fei_lock);
/* Here we also validate @attr to ensure it still exists. */
if (!fei_attr_is_valid(attr))
err = -ENOENT;
else
*val = attr->retval;
mutex_unlock(&fei_lock);
return err;
}
DEFINE_DEBUGFS_ATTRIBUTE(fei_retval_ops, fei_retval_get, fei_retval_set,
"%llx\n");
static int fei_debugfs_add_attr(struct fei_attr *attr)
{
struct dentry *dir;
dir = debugfs_create_dir(attr->kp.symbol_name, fei_debugfs_dir);
if (!dir)
return -ENOMEM;
if (!debugfs_create_file("retval", 0600, dir, attr, &fei_retval_ops)) {
debugfs_remove_recursive(dir);
return -ENOMEM;
}
return 0;
}
static void fei_debugfs_remove_attr(struct fei_attr *attr)
{
struct dentry *dir;
dir = debugfs_lookup(attr->kp.symbol_name, fei_debugfs_dir);
if (dir)
debugfs_remove_recursive(dir);
}
static int fei_kprobe_handler(struct kprobe *kp, struct pt_regs *regs)
{
struct fei_attr *attr = container_of(kp, struct fei_attr, kp);
if (should_fail(&fei_fault_attr, 1)) {
regs_set_return_value(regs, attr->retval);
override_function_with_return(regs);
/* Kprobe specific fixup */
reset_current_kprobe();
preempt_enable_no_resched();
return 1;
}
return 0;
}
NOKPROBE_SYMBOL(fei_kprobe_handler)
static void *fei_seq_start(struct seq_file *m, loff_t *pos)
{
mutex_lock(&fei_lock);
return seq_list_start(&fei_attr_list, *pos);
}
static void fei_seq_stop(struct seq_file *m, void *v)
{
mutex_unlock(&fei_lock);
}
static void *fei_seq_next(struct seq_file *m, void *v, loff_t *pos)
{
return seq_list_next(v, &fei_attr_list, pos);
}
static int fei_seq_show(struct seq_file *m, void *v)
{
struct fei_attr *attr = list_entry(v, struct fei_attr, list);
seq_printf(m, "%pf\n", attr->kp.addr);
return 0;
}
static const struct seq_operations fei_seq_ops = {
.start = fei_seq_start,
.next = fei_seq_next,
.stop = fei_seq_stop,
.show = fei_seq_show,
};
static int fei_open(struct inode *inode, struct file *file)
{
return seq_open(file, &fei_seq_ops);
}
static void fei_attr_remove(struct fei_attr *attr)
{
fei_debugfs_remove_attr(attr);
unregister_kprobe(&attr->kp);
list_del(&attr->list);
fei_attr_free(attr);
}
static void fei_attr_remove_all(void)
{
struct fei_attr *attr, *n;
list_for_each_entry_safe(attr, n, &fei_attr_list, list) {
fei_attr_remove(attr);
}
}
static ssize_t fei_write(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
struct fei_attr *attr;
unsigned long addr;
char *buf, *sym;
int ret;
/* cut off if it is too long */
if (count > KSYM_NAME_LEN)
count = KSYM_NAME_LEN;
buf = kmalloc(sizeof(char) * (count + 1), GFP_KERNEL);
if (!buf)
return -ENOMEM;
if (copy_from_user(buf, buffer, count)) {
ret = -EFAULT;
goto out;
}
buf[count] = '\0';
sym = strstrip(buf);
mutex_lock(&fei_lock);
/* Writing just spaces will remove all injection points */
if (sym[0] == '\0') {
fei_attr_remove_all();
ret = count;
goto out;
}
/* Writing !function will remove one injection point */
if (sym[0] == '!') {
attr = fei_attr_lookup(sym + 1);
if (!attr) {
ret = -ENOENT;
goto out;
}
fei_attr_remove(attr);
ret = count;
goto out;
}
addr = kallsyms_lookup_name(sym);
if (!addr) {
ret = -EINVAL;
goto out;
}
if (!within_error_injection_list(addr)) {
ret = -ERANGE;
goto out;
}
if (fei_attr_lookup(sym)) {
ret = -EBUSY;
goto out;
}
attr = fei_attr_new(sym, addr);
if (!attr) {
ret = -ENOMEM;
goto out;
}
ret = register_kprobe(&attr->kp);
if (!ret)
ret = fei_debugfs_add_attr(attr);
if (ret < 0)
fei_attr_remove(attr);
else {
list_add_tail(&attr->list, &fei_attr_list);
ret = count;
}
out:
kfree(buf);
mutex_unlock(&fei_lock);
return ret;
}
static const struct file_operations fei_ops = {
.open = fei_open,
.read = seq_read,
.write = fei_write,
.llseek = seq_lseek,
.release = seq_release,
};
static int __init fei_debugfs_init(void)
{
struct dentry *dir;
dir = fault_create_debugfs_attr("fail_function", NULL,
&fei_fault_attr);
if (IS_ERR(dir))
return PTR_ERR(dir);
/* injectable attribute is just a symlink of error_inject/list */
if (!debugfs_create_symlink("injectable", dir,
"../error_injection/list"))
goto error;
if (!debugfs_create_file("inject", 0600, dir, NULL, &fei_ops))
goto error;
fei_debugfs_dir = dir;
return 0;
error:
debugfs_remove_recursive(dir);
return -ENOMEM;
}
late_initcall(fei_debugfs_init);
...@@ -83,16 +83,6 @@ static raw_spinlock_t *kretprobe_table_lock_ptr(unsigned long hash) ...@@ -83,16 +83,6 @@ static raw_spinlock_t *kretprobe_table_lock_ptr(unsigned long hash)
return &(kretprobe_table_locks[hash].lock); return &(kretprobe_table_locks[hash].lock);
} }
/* List of symbols that can be overriden for error injection. */
static LIST_HEAD(kprobe_error_injection_list);
static DEFINE_MUTEX(kprobe_ei_mutex);
struct kprobe_ei_entry {
struct list_head list;
unsigned long start_addr;
unsigned long end_addr;
void *priv;
};
/* Blacklist -- list of struct kprobe_blacklist_entry */ /* Blacklist -- list of struct kprobe_blacklist_entry */
static LIST_HEAD(kprobe_blacklist); static LIST_HEAD(kprobe_blacklist);
...@@ -1404,17 +1394,6 @@ bool within_kprobe_blacklist(unsigned long addr) ...@@ -1404,17 +1394,6 @@ bool within_kprobe_blacklist(unsigned long addr)
return false; return false;
} }
bool within_kprobe_error_injection_list(unsigned long addr)
{
struct kprobe_ei_entry *ent;
list_for_each_entry(ent, &kprobe_error_injection_list, list) {
if (addr >= ent->start_addr && addr < ent->end_addr)
return true;
}
return false;
}
/* /*
* If we have a symbol_name argument, look it up and add the offset field * If we have a symbol_name argument, look it up and add the offset field
* to it. This way, we can specify a relative address to a symbol. * to it. This way, we can specify a relative address to a symbol.
...@@ -2189,86 +2168,6 @@ static int __init populate_kprobe_blacklist(unsigned long *start, ...@@ -2189,86 +2168,6 @@ static int __init populate_kprobe_blacklist(unsigned long *start,
return 0; return 0;
} }
#ifdef CONFIG_BPF_KPROBE_OVERRIDE
/* Markers of the _kprobe_error_inject_list section */
extern unsigned long __start_kprobe_error_inject_list[];
extern unsigned long __stop_kprobe_error_inject_list[];
/*
* Lookup and populate the kprobe_error_injection_list.
*
* For safety reasons we only allow certain functions to be overriden with
* bpf_error_injection, so we need to populate the list of the symbols that have
* been marked as safe for overriding.
*/
static void populate_kprobe_error_injection_list(unsigned long *start,
unsigned long *end,
void *priv)
{
unsigned long *iter;
struct kprobe_ei_entry *ent;
unsigned long entry, offset = 0, size = 0;
mutex_lock(&kprobe_ei_mutex);
for (iter = start; iter < end; iter++) {
entry = arch_deref_entry_point((void *)*iter);
if (!kernel_text_address(entry) ||
!kallsyms_lookup_size_offset(entry, &size, &offset)) {
pr_err("Failed to find error inject entry at %p\n",
(void *)entry);
continue;
}
ent = kmalloc(sizeof(*ent), GFP_KERNEL);
if (!ent)
break;
ent->start_addr = entry;
ent->end_addr = entry + size;
ent->priv = priv;
INIT_LIST_HEAD(&ent->list);
list_add_tail(&ent->list, &kprobe_error_injection_list);
}
mutex_unlock(&kprobe_ei_mutex);
}
static void __init populate_kernel_kprobe_ei_list(void)
{
populate_kprobe_error_injection_list(__start_kprobe_error_inject_list,
__stop_kprobe_error_inject_list,
NULL);
}
static void module_load_kprobe_ei_list(struct module *mod)
{
if (!mod->num_kprobe_ei_funcs)
return;
populate_kprobe_error_injection_list(mod->kprobe_ei_funcs,
mod->kprobe_ei_funcs +
mod->num_kprobe_ei_funcs, mod);
}
static void module_unload_kprobe_ei_list(struct module *mod)
{
struct kprobe_ei_entry *ent, *n;
if (!mod->num_kprobe_ei_funcs)
return;
mutex_lock(&kprobe_ei_mutex);
list_for_each_entry_safe(ent, n, &kprobe_error_injection_list, list) {
if (ent->priv == mod) {
list_del_init(&ent->list);
kfree(ent);
}
}
mutex_unlock(&kprobe_ei_mutex);
}
#else
static inline void __init populate_kernel_kprobe_ei_list(void) {}
static inline void module_load_kprobe_ei_list(struct module *m) {}
static inline void module_unload_kprobe_ei_list(struct module *m) {}
#endif
/* Module notifier call back, checking kprobes on the module */ /* Module notifier call back, checking kprobes on the module */
static int kprobes_module_callback(struct notifier_block *nb, static int kprobes_module_callback(struct notifier_block *nb,
unsigned long val, void *data) unsigned long val, void *data)
...@@ -2279,11 +2178,6 @@ static int kprobes_module_callback(struct notifier_block *nb, ...@@ -2279,11 +2178,6 @@ static int kprobes_module_callback(struct notifier_block *nb,
unsigned int i; unsigned int i;
int checkcore = (val == MODULE_STATE_GOING); int checkcore = (val == MODULE_STATE_GOING);
if (val == MODULE_STATE_COMING)
module_load_kprobe_ei_list(mod);
else if (val == MODULE_STATE_GOING)
module_unload_kprobe_ei_list(mod);
if (val != MODULE_STATE_GOING && val != MODULE_STATE_LIVE) if (val != MODULE_STATE_GOING && val != MODULE_STATE_LIVE)
return NOTIFY_DONE; return NOTIFY_DONE;
...@@ -2346,8 +2240,6 @@ static int __init init_kprobes(void) ...@@ -2346,8 +2240,6 @@ static int __init init_kprobes(void)
pr_err("Please take care of using kprobes.\n"); pr_err("Please take care of using kprobes.\n");
} }
populate_kernel_kprobe_ei_list();
if (kretprobe_blacklist_size) { if (kretprobe_blacklist_size) {
/* lookup the function address from its name */ /* lookup the function address from its name */
for (i = 0; kretprobe_blacklist[i].name != NULL; i++) { for (i = 0; kretprobe_blacklist[i].name != NULL; i++) {
...@@ -2515,56 +2407,6 @@ static const struct file_operations debugfs_kprobe_blacklist_ops = { ...@@ -2515,56 +2407,6 @@ static const struct file_operations debugfs_kprobe_blacklist_ops = {
.release = seq_release, .release = seq_release,
}; };
/*
* kprobes/error_injection_list -- shows which functions can be overriden for
* error injection.
* */
static void *kprobe_ei_seq_start(struct seq_file *m, loff_t *pos)
{
mutex_lock(&kprobe_ei_mutex);
return seq_list_start(&kprobe_error_injection_list, *pos);
}
static void kprobe_ei_seq_stop(struct seq_file *m, void *v)
{
mutex_unlock(&kprobe_ei_mutex);
}
static void *kprobe_ei_seq_next(struct seq_file *m, void *v, loff_t *pos)
{
return seq_list_next(v, &kprobe_error_injection_list, pos);
}
static int kprobe_ei_seq_show(struct seq_file *m, void *v)
{
char buffer[KSYM_SYMBOL_LEN];
struct kprobe_ei_entry *ent =
list_entry(v, struct kprobe_ei_entry, list);
sprint_symbol(buffer, ent->start_addr);
seq_printf(m, "%s\n", buffer);
return 0;
}
static const struct seq_operations kprobe_ei_seq_ops = {
.start = kprobe_ei_seq_start,
.next = kprobe_ei_seq_next,
.stop = kprobe_ei_seq_stop,
.show = kprobe_ei_seq_show,
};
static int kprobe_ei_open(struct inode *inode, struct file *filp)
{
return seq_open(filp, &kprobe_ei_seq_ops);
}
static const struct file_operations debugfs_kprobe_ei_ops = {
.open = kprobe_ei_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
static void arm_all_kprobes(void) static void arm_all_kprobes(void)
{ {
struct hlist_head *head; struct hlist_head *head;
...@@ -2706,11 +2548,6 @@ static int __init debugfs_kprobe_init(void) ...@@ -2706,11 +2548,6 @@ static int __init debugfs_kprobe_init(void)
if (!file) if (!file)
goto error; goto error;
file = debugfs_create_file("error_injection_list", 0444, dir, NULL,
&debugfs_kprobe_ei_ops);
if (!file)
goto error;
return 0; return 0;
error: error:
......
...@@ -3118,10 +3118,10 @@ static int find_module_sections(struct module *mod, struct load_info *info) ...@@ -3118,10 +3118,10 @@ static int find_module_sections(struct module *mod, struct load_info *info)
sizeof(*mod->ftrace_callsites), sizeof(*mod->ftrace_callsites),
&mod->num_ftrace_callsites); &mod->num_ftrace_callsites);
#endif #endif
#ifdef CONFIG_BPF_KPROBE_OVERRIDE #ifdef CONFIG_FUNCTION_ERROR_INJECTION
mod->kprobe_ei_funcs = section_objs(info, "_kprobe_error_inject_list", mod->ei_funcs = section_objs(info, "_error_injection_whitelist",
sizeof(*mod->kprobe_ei_funcs), sizeof(*mod->ei_funcs),
&mod->num_kprobe_ei_funcs); &mod->num_ei_funcs);
#endif #endif
mod->extable = section_objs(info, "__ex_table", mod->extable = section_objs(info, "__ex_table",
sizeof(*mod->extable), &mod->num_exentries); sizeof(*mod->extable), &mod->num_exentries);
......
...@@ -533,9 +533,7 @@ config FUNCTION_PROFILER ...@@ -533,9 +533,7 @@ config FUNCTION_PROFILER
config BPF_KPROBE_OVERRIDE config BPF_KPROBE_OVERRIDE
bool "Enable BPF programs to override a kprobed function" bool "Enable BPF programs to override a kprobed function"
depends on BPF_EVENTS depends on BPF_EVENTS
depends on KPROBES_ON_FTRACE depends on FUNCTION_ERROR_INJECTION
depends on HAVE_KPROBE_OVERRIDE
depends on DYNAMIC_FTRACE_WITH_REGS
default n default n
help help
Allows BPF to override the execution of a probed function and Allows BPF to override the execution of a probed function and
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
#include <linux/uaccess.h> #include <linux/uaccess.h>
#include <linux/ctype.h> #include <linux/ctype.h>
#include <linux/kprobes.h> #include <linux/kprobes.h>
#include <asm/kprobes.h> #include <linux/error-injection.h>
#include "trace_probe.h" #include "trace_probe.h"
#include "trace.h" #include "trace.h"
...@@ -83,9 +83,8 @@ EXPORT_SYMBOL_GPL(trace_call_bpf); ...@@ -83,9 +83,8 @@ EXPORT_SYMBOL_GPL(trace_call_bpf);
#ifdef CONFIG_BPF_KPROBE_OVERRIDE #ifdef CONFIG_BPF_KPROBE_OVERRIDE
BPF_CALL_2(bpf_override_return, struct pt_regs *, regs, unsigned long, rc) BPF_CALL_2(bpf_override_return, struct pt_regs *, regs, unsigned long, rc)
{ {
__this_cpu_write(bpf_kprobe_override, 1);
regs_set_return_value(regs, rc); regs_set_return_value(regs, rc);
arch_ftrace_kprobe_override_function(regs); override_function_with_return(regs);
return 0; return 0;
} }
...@@ -800,11 +799,11 @@ int perf_event_attach_bpf_prog(struct perf_event *event, ...@@ -800,11 +799,11 @@ int perf_event_attach_bpf_prog(struct perf_event *event,
int ret = -EEXIST; int ret = -EEXIST;
/* /*
* Kprobe override only works for ftrace based kprobes, and only if they * Kprobe override only works if they are on the function entry,
* are on the opt-in list. * and only if they are on the opt-in list.
*/ */
if (prog->kprobe_override && if (prog->kprobe_override &&
(!trace_kprobe_ftrace(event->tp_event) || (!trace_kprobe_on_func_entry(event->tp_event) ||
!trace_kprobe_error_injectable(event->tp_event))) !trace_kprobe_error_injectable(event->tp_event)))
return -EINVAL; return -EINVAL;
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include <linux/module.h> #include <linux/module.h>
#include <linux/uaccess.h> #include <linux/uaccess.h>
#include <linux/rculist.h> #include <linux/rculist.h>
#include <linux/error-injection.h>
#include "trace_probe.h" #include "trace_probe.h"
...@@ -42,8 +43,6 @@ struct trace_kprobe { ...@@ -42,8 +43,6 @@ struct trace_kprobe {
(offsetof(struct trace_kprobe, tp.args) + \ (offsetof(struct trace_kprobe, tp.args) + \
(sizeof(struct probe_arg) * (n))) (sizeof(struct probe_arg) * (n)))
DEFINE_PER_CPU(int, bpf_kprobe_override);
static nokprobe_inline bool trace_kprobe_is_return(struct trace_kprobe *tk) static nokprobe_inline bool trace_kprobe_is_return(struct trace_kprobe *tk)
{ {
return tk->rp.handler != NULL; return tk->rp.handler != NULL;
...@@ -88,13 +87,16 @@ static nokprobe_inline unsigned long trace_kprobe_nhit(struct trace_kprobe *tk) ...@@ -88,13 +87,16 @@ static nokprobe_inline unsigned long trace_kprobe_nhit(struct trace_kprobe *tk)
return nhit; return nhit;
} }
int trace_kprobe_ftrace(struct trace_event_call *call) bool trace_kprobe_on_func_entry(struct trace_event_call *call)
{ {
struct trace_kprobe *tk = (struct trace_kprobe *)call->data; struct trace_kprobe *tk = (struct trace_kprobe *)call->data;
return kprobe_ftrace(&tk->rp.kp);
return kprobe_on_func_entry(tk->rp.kp.addr,
tk->rp.kp.addr ? NULL : tk->rp.kp.symbol_name,
tk->rp.kp.addr ? 0 : tk->rp.kp.offset);
} }
int trace_kprobe_error_injectable(struct trace_event_call *call) bool trace_kprobe_error_injectable(struct trace_event_call *call)
{ {
struct trace_kprobe *tk = (struct trace_kprobe *)call->data; struct trace_kprobe *tk = (struct trace_kprobe *)call->data;
unsigned long addr; unsigned long addr;
...@@ -106,7 +108,7 @@ int trace_kprobe_error_injectable(struct trace_event_call *call) ...@@ -106,7 +108,7 @@ int trace_kprobe_error_injectable(struct trace_event_call *call)
} else { } else {
addr = (unsigned long)tk->rp.kp.addr; addr = (unsigned long)tk->rp.kp.addr;
} }
return within_kprobe_error_injection_list(addr); return within_error_injection_list(addr);
} }
static int register_kprobe_event(struct trace_kprobe *tk); static int register_kprobe_event(struct trace_kprobe *tk);
...@@ -1202,6 +1204,7 @@ kprobe_perf_func(struct trace_kprobe *tk, struct pt_regs *regs) ...@@ -1202,6 +1204,7 @@ kprobe_perf_func(struct trace_kprobe *tk, struct pt_regs *regs)
int rctx; int rctx;
if (bpf_prog_array_valid(call)) { if (bpf_prog_array_valid(call)) {
unsigned long orig_ip = instruction_pointer(regs);
int ret; int ret;
ret = trace_call_bpf(call, regs); ret = trace_call_bpf(call, regs);
...@@ -1209,12 +1212,13 @@ kprobe_perf_func(struct trace_kprobe *tk, struct pt_regs *regs) ...@@ -1209,12 +1212,13 @@ kprobe_perf_func(struct trace_kprobe *tk, struct pt_regs *regs)
/* /*
* We need to check and see if we modified the pc of the * We need to check and see if we modified the pc of the
* pt_regs, and if so clear the kprobe and return 1 so that we * pt_regs, and if so clear the kprobe and return 1 so that we
* don't do the instruction skipping. Also reset our state so * don't do the single stepping.
* we are clean the next pass through. * The ftrace kprobe handler leaves it up to us to re-enable
* preemption here before returning if we've modified the ip.
*/ */
if (__this_cpu_read(bpf_kprobe_override)) { if (orig_ip != instruction_pointer(regs)) {
__this_cpu_write(bpf_kprobe_override, 0);
reset_current_kprobe(); reset_current_kprobe();
preempt_enable_no_resched();
return 1; return 1;
} }
if (!ret) if (!ret)
...@@ -1322,15 +1326,8 @@ static int kprobe_dispatcher(struct kprobe *kp, struct pt_regs *regs) ...@@ -1322,15 +1326,8 @@ static int kprobe_dispatcher(struct kprobe *kp, struct pt_regs *regs)
if (tk->tp.flags & TP_FLAG_TRACE) if (tk->tp.flags & TP_FLAG_TRACE)
kprobe_trace_func(tk, regs); kprobe_trace_func(tk, regs);
#ifdef CONFIG_PERF_EVENTS #ifdef CONFIG_PERF_EVENTS
if (tk->tp.flags & TP_FLAG_PROFILE) { if (tk->tp.flags & TP_FLAG_PROFILE)
ret = kprobe_perf_func(tk, regs); ret = kprobe_perf_func(tk, regs);
/*
* The ftrace kprobe handler leaves it up to us to re-enable
* preemption here before returning if we've modified the ip.
*/
if (ret)
preempt_enable_no_resched();
}
#endif #endif
return ret; return ret;
} }
......
...@@ -252,8 +252,8 @@ struct symbol_cache; ...@@ -252,8 +252,8 @@ struct symbol_cache;
unsigned long update_symbol_cache(struct symbol_cache *sc); unsigned long update_symbol_cache(struct symbol_cache *sc);
void free_symbol_cache(struct symbol_cache *sc); void free_symbol_cache(struct symbol_cache *sc);
struct symbol_cache *alloc_symbol_cache(const char *sym, long offset); struct symbol_cache *alloc_symbol_cache(const char *sym, long offset);
int trace_kprobe_ftrace(struct trace_event_call *call); bool trace_kprobe_on_func_entry(struct trace_event_call *call);
int trace_kprobe_error_injectable(struct trace_event_call *call); bool trace_kprobe_error_injectable(struct trace_event_call *call);
#else #else
/* uprobes do not support symbol fetch methods */ /* uprobes do not support symbol fetch methods */
#define fetch_symbol_u8 NULL #define fetch_symbol_u8 NULL
...@@ -280,14 +280,14 @@ alloc_symbol_cache(const char *sym, long offset) ...@@ -280,14 +280,14 @@ alloc_symbol_cache(const char *sym, long offset)
return NULL; return NULL;
} }
static inline int trace_kprobe_ftrace(struct trace_event_call *call) static inline bool trace_kprobe_on_func_entry(struct trace_event_call *call)
{ {
return 0; return false;
} }
static inline int trace_kprobe_error_injectable(struct trace_event_call *call) static inline bool trace_kprobe_error_injectable(struct trace_event_call *call)
{ {
return 0; return false;
} }
#endif /* CONFIG_KPROBE_EVENTS */ #endif /* CONFIG_KPROBE_EVENTS */
......
...@@ -1500,6 +1500,10 @@ config FAULT_INJECTION ...@@ -1500,6 +1500,10 @@ config FAULT_INJECTION
Provide fault-injection framework. Provide fault-injection framework.
For more details, see Documentation/fault-injection/. For more details, see Documentation/fault-injection/.
config FUNCTION_ERROR_INJECTION
def_bool y
depends on HAVE_FUNCTION_ERROR_INJECTION && KPROBES
config FAILSLAB config FAILSLAB
bool "Fault-injection capability for kmalloc" bool "Fault-injection capability for kmalloc"
depends on FAULT_INJECTION depends on FAULT_INJECTION
...@@ -1547,6 +1551,16 @@ config FAIL_FUTEX ...@@ -1547,6 +1551,16 @@ config FAIL_FUTEX
help help
Provide fault-injection capability for futexes. Provide fault-injection capability for futexes.
config FAIL_FUNCTION
bool "Fault-injection capability for functions"
depends on FAULT_INJECTION_DEBUG_FS && FUNCTION_ERROR_INJECTION
help
Provide function-based fault-injection capability.
This will allow you to override a specific function with a return
with given return value. As a result, function caller will see
an error value and have to handle it. This is useful to test the
error handling in various subsystems.
config FAULT_INJECTION_DEBUG_FS config FAULT_INJECTION_DEBUG_FS
bool "Debugfs entries for fault-injection capabilities" bool "Debugfs entries for fault-injection capabilities"
depends on FAULT_INJECTION && SYSFS && DEBUG_FS depends on FAULT_INJECTION && SYSFS && DEBUG_FS
......
...@@ -149,6 +149,7 @@ obj-$(CONFIG_NETDEV_NOTIFIER_ERROR_INJECT) += netdev-notifier-error-inject.o ...@@ -149,6 +149,7 @@ obj-$(CONFIG_NETDEV_NOTIFIER_ERROR_INJECT) += netdev-notifier-error-inject.o
obj-$(CONFIG_MEMORY_NOTIFIER_ERROR_INJECT) += memory-notifier-error-inject.o obj-$(CONFIG_MEMORY_NOTIFIER_ERROR_INJECT) += memory-notifier-error-inject.o
obj-$(CONFIG_OF_RECONFIG_NOTIFIER_ERROR_INJECT) += \ obj-$(CONFIG_OF_RECONFIG_NOTIFIER_ERROR_INJECT) += \
of-reconfig-notifier-error-inject.o of-reconfig-notifier-error-inject.o
obj-$(CONFIG_FUNCTION_ERROR_INJECTION) += error-inject.o
lib-$(CONFIG_GENERIC_BUG) += bug.o lib-$(CONFIG_GENERIC_BUG) += bug.o
......
// SPDX-License-Identifier: GPL-2.0
// error-inject.c: Function-level error injection table
#include <linux/error-injection.h>
#include <linux/debugfs.h>
#include <linux/kallsyms.h>
#include <linux/kprobes.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/list.h>
#include <linux/slab.h>
/* Whitelist of symbols that can be overridden for error injection. */
static LIST_HEAD(error_injection_list);
static DEFINE_MUTEX(ei_mutex);
struct ei_entry {
struct list_head list;
unsigned long start_addr;
unsigned long end_addr;
int etype;
void *priv;
};
bool within_error_injection_list(unsigned long addr)
{
struct ei_entry *ent;
bool ret = false;
mutex_lock(&ei_mutex);
list_for_each_entry(ent, &error_injection_list, list) {
if (addr >= ent->start_addr && addr < ent->end_addr) {
ret = true;
break;
}
}
mutex_unlock(&ei_mutex);
return ret;
}
int get_injectable_error_type(unsigned long addr)
{
struct ei_entry *ent;
list_for_each_entry(ent, &error_injection_list, list) {
if (addr >= ent->start_addr && addr < ent->end_addr)
return ent->etype;
}
return EI_ETYPE_NONE;
}
/*
* Lookup and populate the error_injection_list.
*
* For safety reasons we only allow certain functions to be overridden with
* bpf_error_injection, so we need to populate the list of the symbols that have
* been marked as safe for overriding.
*/
static void populate_error_injection_list(struct error_injection_entry *start,
struct error_injection_entry *end,
void *priv)
{
struct error_injection_entry *iter;
struct ei_entry *ent;
unsigned long entry, offset = 0, size = 0;
mutex_lock(&ei_mutex);
for (iter = start; iter < end; iter++) {
entry = arch_deref_entry_point((void *)iter->addr);
if (!kernel_text_address(entry) ||
!kallsyms_lookup_size_offset(entry, &size, &offset)) {
pr_err("Failed to find error inject entry at %p\n",
(void *)entry);
continue;
}
ent = kmalloc(sizeof(*ent), GFP_KERNEL);
if (!ent)
break;
ent->start_addr = entry;
ent->end_addr = entry + size;
ent->etype = iter->etype;
ent->priv = priv;
INIT_LIST_HEAD(&ent->list);
list_add_tail(&ent->list, &error_injection_list);
}
mutex_unlock(&ei_mutex);
}
/* Markers of the _error_inject_whitelist section */
extern struct error_injection_entry __start_error_injection_whitelist[];
extern struct error_injection_entry __stop_error_injection_whitelist[];
static void __init populate_kernel_ei_list(void)
{
populate_error_injection_list(__start_error_injection_whitelist,
__stop_error_injection_whitelist,
NULL);
}
#ifdef CONFIG_MODULES
static void module_load_ei_list(struct module *mod)
{
if (!mod->num_ei_funcs)
return;
populate_error_injection_list(mod->ei_funcs,
mod->ei_funcs + mod->num_ei_funcs, mod);
}
static void module_unload_ei_list(struct module *mod)
{
struct ei_entry *ent, *n;
if (!mod->num_ei_funcs)
return;
mutex_lock(&ei_mutex);
list_for_each_entry_safe(ent, n, &error_injection_list, list) {
if (ent->priv == mod) {
list_del_init(&ent->list);
kfree(ent);
}
}
mutex_unlock(&ei_mutex);
}
/* Module notifier call back, checking error injection table on the module */
static int ei_module_callback(struct notifier_block *nb,
unsigned long val, void *data)
{
struct module *mod = data;
if (val == MODULE_STATE_COMING)
module_load_ei_list(mod);
else if (val == MODULE_STATE_GOING)
module_unload_ei_list(mod);
return NOTIFY_DONE;
}
static struct notifier_block ei_module_nb = {
.notifier_call = ei_module_callback,
.priority = 0
};
static __init int module_ei_init(void)
{
return register_module_notifier(&ei_module_nb);
}
#else /* !CONFIG_MODULES */
#define module_ei_init() (0)
#endif
/*
* error_injection/whitelist -- shows which functions can be overridden for
* error injection.
*/
static void *ei_seq_start(struct seq_file *m, loff_t *pos)
{
mutex_lock(&ei_mutex);
return seq_list_start(&error_injection_list, *pos);
}
static void ei_seq_stop(struct seq_file *m, void *v)
{
mutex_unlock(&ei_mutex);
}
static void *ei_seq_next(struct seq_file *m, void *v, loff_t *pos)
{
return seq_list_next(v, &error_injection_list, pos);
}
static const char *error_type_string(int etype)
{
switch (etype) {
case EI_ETYPE_NULL:
return "NULL";
case EI_ETYPE_ERRNO:
return "ERRNO";
case EI_ETYPE_ERRNO_NULL:
return "ERRNO_NULL";
default:
return "(unknown)";
}
}
static int ei_seq_show(struct seq_file *m, void *v)
{
struct ei_entry *ent = list_entry(v, struct ei_entry, list);
seq_printf(m, "%pf\t%s\n", (void *)ent->start_addr,
error_type_string(ent->etype));
return 0;
}
static const struct seq_operations ei_seq_ops = {
.start = ei_seq_start,
.next = ei_seq_next,
.stop = ei_seq_stop,
.show = ei_seq_show,
};
static int ei_open(struct inode *inode, struct file *filp)
{
return seq_open(filp, &ei_seq_ops);
}
static const struct file_operations debugfs_ei_ops = {
.open = ei_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
static int __init ei_debugfs_init(void)
{
struct dentry *dir, *file;
dir = debugfs_create_dir("error_injection", NULL);
if (!dir)
return -ENOMEM;
file = debugfs_create_file("list", 0444, dir, NULL, &debugfs_ei_ops);
if (!file) {
debugfs_remove(dir);
return -ENOMEM;
}
return 0;
}
static int __init init_error_injection(void)
{
populate_kernel_ei_list();
if (!module_ei_init())
ei_debugfs_init();
return 0;
}
late_initcall(init_error_injection);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment