-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathinject-error.py
122 lines (105 loc) · 3.26 KB
/
inject-error.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
from bcc import BPF
from time import sleep
from subprocess import Popen
import argparse
import sys
bpf_text = """
#include <uapi/linux/ptrace.h>
#include <linux/bio.h>
BPF_HASH(fail_pids, u64);
int trigger_function(struct pt_regs *ctx)
{
u64 pid = bpf_get_current_pid_tgid();
u64 zero = 0;
u64 *val;
val = fail_pids.lookup_or_init(&pid, &zero);
lock_xadd(val, 1);
return 0;
}
int trigger_function_ret(struct pt_regs *ctx)
{
u64 pid = bpf_get_current_pid_tgid();
u64 *val;
val = fail_pids.lookup(&pid);
if (!val)
return 0;
lock_xadd(val, -1);
return 0;
}
int override_function(struct pt_regs *ctx)
{
u64 pid = bpf_get_current_pid_tgid();
u64 *val;
val = fail_pids.lookup(&pid);
if (!val)
return 0;
if (*val != FAIL_CNT)
return 0;
bpf_trace_printk("overrding something\\n");
unsigned long rc = RCVAL;
bpf_override_return(ctx, rc);
return 0;
}
"""
parser = argparse.ArgumentParser()
parser.add_argument("-o", "--override", help="The function to override")
parser.add_argument("-r", "--retval", type=int, help="The return value to use")
parser.add_argument("-t", "--trigger", action='append',
help="The function that must be called to trigger the error injection")
parser.add_argument("-d", "--delay", type=int,
help="The delay to wait before injecting the error")
parser.add_argument("-T", "--timeout", type=int,
help="Timeout after error injection has been loaded to wait on the task")
parser.add_argument("COMMAND", nargs='+', help="The command to run")
args = parser.parse_args()
retval = -12
if not args.override:
print("Must specify an override function")
sys.exit(1)
if not args.trigger:
print("Must specify a function as the trigger function")
sys.exit(1)
if args.retval:
retval = args.retval
bpf_text = bpf_text.replace("RCVAL", str(retval))
bpf_text = bpf_text.replace("FAIL_CNT", str(len(args.trigger)))
print("Running command")
p = Popen(args.COMMAND)
if args.delay:
print("Sleeping for {} seconds".format(args.delay))
sleep(args.delay)
print("Loading error injection")
b = BPF(text=bpf_text)
# Load the kretprobe first, because we want the delete guy to be in place before
# the add guy is in place, otherwise we could error out pids that are no longer
# in our path and cause unfortunate things to happen.
for t in args.trigger:
b.attach_kretprobe(event=t, fn_name="trigger_function_ret")
for t in args.trigger:
b.attach_kprobe(event=t, fn_name="trigger_function")
b.attach_kprobe(event=args.override, fn_name="override_function")
print("Dropping caches")
f = open("/proc/sys/vm/drop_caches", "w")
f.write("3")
f.close()
print("Waiting for the command to exit")
while p.poll() is None:
if args.timeout:
sleep(args.timeout)
if p.poll() is None:
print("Killing the task, it didn't die")
f = open("nofail.txt", "a")
f.write(args.trigger + "\n")
f.close()
p.kill()
p.wait()
break
p.wait()
# We have to remove in this order otherwise we could end up with a half
# populated hasmap and overrding legitimate things.
b.detach_kprobe(args.override)
for t in args.trigger:
b.detach_kprobe(t)
for t in args.trigger:
b.detach_kretprobe(t)
print("Exiting")