1 /*
2 * Copyright (c) Facebook, Inc.
3 * Licensed under the Apache License, Version 2.0 (the "License")
4 *
5 * Usage:
6 * $ ./KModRetExample
7 * opened file: /bin/true
8 * security_file_open() is called 1 times, expecting 1
9 *
10 * Kfunc modify_ret support is only available at kernel version 5.6 and later.
11 * This example only works for x64. Currently, only the kernel functions can
12 * be attached with BPF_MODIFY_RETURN:
13 * - Whitelisted for error injection by checking within_error_injection_list.
14 * Similar discussions happened for the bpf_override_return helper.
15 * - The LSM security hooks (kernel global function with prefix "security_").
16 */
17
18 #include <fstream>
19 #include <iostream>
20 #include <iomanip>
21 #include <string>
22
23 #include <error.h>
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <fcntl.h>
27
28 #include "bcc_version.h"
29 #include "BPF.h"
30
31 const std::string BPF_PROGRAM = R"(
32 #include <linux/fs.h>
33 #include <asm/errno.h>
34
35 BPF_ARRAY(target_pid, u32, 1);
36 static bool match_target_pid()
37 {
38 int key = 0, *val, tpid, cpid;
39
40 val = target_pid.lookup(&key);
41 if (!val)
42 return false;
43
44 tpid = *val;
45 cpid = bpf_get_current_pid_tgid() >> 32;
46 if (tpid == 0 || tpid != cpid)
47 return false;
48 return true;
49 }
50
51 struct fname_buf {
52 char buf[16];
53 };
54 BPF_ARRAY(fname_table, struct fname_buf, 1);
55
56 KMOD_RET(__x64_sys_openat, struct pt_regs *regs, int ret)
57 {
58 if (!match_target_pid())
59 return 0;
60
61 // openat syscall arguments:
62 // int dfd, const char __user * filename, int flags, umode_t mode
63 char *filename = (char *)PT_REGS_PARM2_SYSCALL(regs);
64
65 int key = 0;
66 struct fname_buf *val;
67 val = fname_table.lookup(&key);
68 if (!val)
69 return false;
70
71 if (bpf_copy_from_user(val, sizeof(*val), filename) < 0)
72 return 0;
73
74 /* match target_pid, return -EINVAL. */
75 return -EINVAL;
76 }
77
78 BPF_ARRAY(count, u32, 1);
79 KMOD_RET(security_file_open, struct file *file, int ret)
80 {
81 if (!match_target_pid())
82 return 0;
83
84 int key = 0, *val;
85 val = count.lookup(&key);
86 if (!val)
87 return 0;
88
89 /* no modification, kernel func continues to execute after this. */
90 lock_xadd(val, 1);
91 return 0;
92 }
93 )";
94
95 struct fname_buf {
96 char buf[16];
97 };
98
modify_return(ebpf::BPF & bpf)99 static int modify_return(ebpf::BPF &bpf) {
100 int prog_fd;
101 auto res = bpf.load_func("kmod_ret____x64_sys_openat",
102 BPF_PROG_TYPE_TRACING, prog_fd, BPF_F_SLEEPABLE);
103 if (!res.ok()) {
104 std::cerr << res.msg() << std::endl;
105 return 1;
106 }
107
108 int attach_fd = bpf_attach_kfunc(prog_fd);
109 if (attach_fd < 0) {
110 std::cerr << "bpf_attach_kfunc failed: " << attach_fd << std::endl;
111 return 1;
112 }
113
114 int ret = open("/bin/true", O_RDONLY);
115 if (ret >= 0 || errno != EINVAL) {
116 close(attach_fd);
117 std::cerr << "incorrect open result" << std::endl;
118 return 1;
119 }
120
121 auto fname_table = bpf.get_array_table<struct fname_buf>("fname_table");
122 uint32_t key = 0;
123 struct fname_buf val;
124 res = fname_table.get_value(key, val);
125 if (!res.ok()) {
126 close(attach_fd);
127 std::cerr << res.msg() << std::endl;
128 return 1;
129 }
130 std::cout << "opened file: " << val.buf << std::endl;
131
132 // detach the kfunc.
133 close(attach_fd);
134 return 0;
135 }
136
not_modify_return(ebpf::BPF & bpf)137 static int not_modify_return(ebpf::BPF &bpf) {
138 int prog_fd;
139 auto res = bpf.load_func("kmod_ret__security_file_open",
140 BPF_PROG_TYPE_TRACING, prog_fd);
141 if (!res.ok()) {
142 std::cerr << res.msg() << std::endl;
143 return 1;
144 }
145
146 int attach_fd = bpf_attach_kfunc(prog_fd);
147 if (attach_fd < 0) {
148 std::cerr << "bpf_attach_kfunc failed: " << attach_fd << std::endl;
149 return 1;
150 }
151
152 int ret = open("/bin/true", O_RDONLY);
153 if (ret < 0) {
154 close(attach_fd);
155 std::cerr << "incorrect open result" << std::endl;
156 return 1;
157 }
158
159 auto count_table = bpf.get_array_table<uint32_t>("count");
160 uint32_t key = 0, val = 0;
161 res = count_table.get_value(key, val);
162 if (!res.ok()) {
163 close(attach_fd);
164 std::cerr << res.msg() << std::endl;
165 return 1;
166 }
167
168 close(attach_fd);
169 std::cout << "security_file_open() is called " << val << " times, expecting 1\n";
170 return 0;
171 }
172
main()173 int main() {
174 ebpf::BPF bpf;
175 auto res = bpf.init(BPF_PROGRAM);
176 if (!res.ok()) {
177 std::cerr << res.msg() << std::endl;
178 return 1;
179 }
180
181 uint32_t key = 0, val = getpid();
182 auto pid_table = bpf.get_array_table<uint32_t>("target_pid");
183 res = pid_table.update_value(key, val);
184 if (!res.ok()) {
185 std::cerr << res.msg() << std::endl;
186 return 1;
187 }
188
189 if (modify_return(bpf))
190 return 1;
191
192 if (not_modify_return(bpf))
193 return 1;
194
195 return 0;
196 }
197