1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <fcntl.h>
4 #include <linux/bpf_insn.h>
5 #include <string.h>
6 #include <sys/mman.h>
7 #include <unistd.h>
8 
9 #include "bpf-foreign.h"
10 #include "load-fragment.h"
11 #include "manager.h"
12 #include "process-util.h"
13 #include "rlimit-util.h"
14 #include "rm-rf.h"
15 #include "service.h"
16 #include "tests.h"
17 #include "unit.h"
18 #include "virt.h"
19 
20 struct Test {
21         const char *option_name;
22         enum bpf_prog_type prog_type;
23         enum bpf_attach_type attach_type;
24         const char *bpffs_path;
25 };
26 
27 typedef struct Test Test;
28 
29 #define BPFFS_PATH(prog_suffix) ("/sys/fs/bpf/test-bpf-foreing-" # prog_suffix)
30 static const Test single_prog[] = {
31         {
32                 .option_name = "BPFProgram",
33                 .prog_type = BPF_PROG_TYPE_CGROUP_SKB,
34                 .attach_type = BPF_CGROUP_INET_INGRESS,
35                 .bpffs_path = BPFFS_PATH("trivial-skb"),
36         },
37 };
38 static const Test path_split_test[] = {
39         {
40                 .option_name = "BPFProgram",
41                 .prog_type = BPF_PROG_TYPE_CGROUP_SKB,
42                 .attach_type = BPF_CGROUP_INET_INGRESS,
43                 .bpffs_path = BPFFS_PATH("path:split:test"),
44         },
45 };
46 
47 static const Test same_prog_same_hook[] = {
48         {
49                 .option_name = "BPFProgram",
50                 .prog_type = BPF_PROG_TYPE_CGROUP_SOCK,
51                 .attach_type = BPF_CGROUP_INET_SOCK_CREATE,
52                 .bpffs_path = BPFFS_PATH("trivial-sock"),
53         },
54         {
55                 .option_name = "BPFProgram",
56                 .prog_type = BPF_PROG_TYPE_CGROUP_SOCK,
57                 .attach_type = BPF_CGROUP_INET_SOCK_CREATE,
58                 .bpffs_path = BPFFS_PATH("trivial-sock"),
59         }
60 };
61 
62 static const Test multi_prog_same_hook[] = {
63         {
64                 .option_name = "BPFProgram",
65                 .prog_type = BPF_PROG_TYPE_CGROUP_SOCK,
66                 .attach_type = BPF_CGROUP_INET_SOCK_CREATE,
67                 .bpffs_path = BPFFS_PATH("trivial-sock-0"),
68         },
69         {
70                 .option_name = "BPFProgram",
71                 .prog_type = BPF_PROG_TYPE_CGROUP_SOCK,
72                 .attach_type = BPF_CGROUP_INET_SOCK_CREATE,
73                 .bpffs_path = BPFFS_PATH("trivial-sock-1"),
74         }
75 };
76 
77 static const Test same_prog_multi_hook[] = {
78         {
79                 .option_name = "BPFProgram",
80                 .prog_type = BPF_PROG_TYPE_CGROUP_SKB,
81                 .attach_type = BPF_CGROUP_INET_INGRESS,
82                 .bpffs_path = BPFFS_PATH("trivial-skb"),
83         },
84         {
85                 .option_name = "BPFProgram",
86                 .prog_type = BPF_PROG_TYPE_CGROUP_SKB,
87                 .attach_type = BPF_CGROUP_INET_EGRESS,
88                 .bpffs_path = BPFFS_PATH("trivial-skb"),
89         }
90 };
91 
92 static const Test same_prog_multi_option_0[] = {
93         {
94                 .option_name = "BPFProgram",
95                 .prog_type = BPF_PROG_TYPE_CGROUP_SKB,
96                 .attach_type = BPF_CGROUP_INET_INGRESS,
97                 .bpffs_path = BPFFS_PATH("trivial-skb"),
98         },
99         {
100                 .option_name = "IPIngressFilterPath",
101                 .prog_type = BPF_PROG_TYPE_CGROUP_SKB,
102                 .attach_type = BPF_CGROUP_INET_INGRESS,
103                 .bpffs_path = BPFFS_PATH("trivial-skb"),
104         }
105 };
106 
107 static const Test same_prog_multi_option_1[] = {
108         {
109                 .option_name = "IPEgressFilterPath",
110                 .prog_type = BPF_PROG_TYPE_CGROUP_SKB,
111                 .attach_type = BPF_CGROUP_INET_EGRESS,
112                 .bpffs_path = BPFFS_PATH("trivial-skb"),
113         },
114         {
115                 .option_name = "BPFProgram",
116                 .prog_type = BPF_PROG_TYPE_CGROUP_SKB,
117                 .attach_type = BPF_CGROUP_INET_EGRESS,
118                 .bpffs_path = BPFFS_PATH("trivial-skb"),
119         }
120 };
121 #undef BPFFS_PATH
122 
bpf_foreign_test_to_string(enum bpf_attach_type attach_type,const char * bpffs_path,char ** ret_str)123 static int bpf_foreign_test_to_string(enum bpf_attach_type attach_type, const char *bpffs_path, char **ret_str) {
124         const char *s = NULL;
125 
126         assert_se(bpffs_path);
127         assert_se(ret_str);
128 
129         assert_se(s = bpf_cgroup_attach_type_to_string(attach_type));
130         assert_se(*ret_str = strjoin(s, ":", bpffs_path));
131 
132         return 0;
133 }
134 
unlink_paths_and_free(char ** paths)135 static char **unlink_paths_and_free(char **paths) {
136         STRV_FOREACH(i, paths)
137                 (void) unlink(*i);
138 
139         return strv_free(paths);
140 }
141 
142 DEFINE_TRIVIAL_CLEANUP_FUNC(char **, unlink_paths_and_free);
143 
pin_programs(Unit * u,CGroupContext * cc,const Test * test_suite,size_t test_suite_size,char *** paths_ret)144 static int pin_programs(Unit *u, CGroupContext *cc, const Test *test_suite, size_t test_suite_size, char ***paths_ret) {
145         _cleanup_(unlink_paths_and_freep) char **bpffs_paths = NULL;
146         static const struct bpf_insn trivial[] = {
147                 BPF_MOV64_IMM(BPF_REG_0, 0),
148                 BPF_EXIT_INSN()
149         };
150         char log_buf[0xffff];
151         int r;
152 
153         assert_se(paths_ret);
154 
155         for (size_t i = 0; i < test_suite_size; i++) {
156                 _cleanup_(bpf_program_freep) BPFProgram *prog = NULL;
157                 _cleanup_free_ char *str = NULL;
158 
159                 r = bpf_foreign_test_to_string(test_suite[i].attach_type, test_suite[i].bpffs_path, &str);
160                 if (r < 0)
161                         return log_error_errno(r, "Failed to convert program to string");
162 
163                 r = bpf_program_new(test_suite[i].prog_type, "sd_trivial", &prog);
164                 if (r < 0)
165                         return log_error_errno(r, "Failed to create program '%s'", str);
166 
167                 r = bpf_program_add_instructions(prog, trivial, ELEMENTSOF(trivial));
168                 if (r < 0)
169                         return log_error_errno(r, "Failed to add trivial instructions for '%s'", str);
170 
171                 r = bpf_program_load_kernel(prog, log_buf, ELEMENTSOF(log_buf));
172                 if (r < 0)
173                         return log_error_errno(r, "Failed to load BPF program '%s'", str);
174 
175                 if (strv_contains(bpffs_paths, test_suite[i].bpffs_path))
176                         continue;
177 
178                 r = strv_extend(&bpffs_paths, test_suite[i].bpffs_path);
179                 if (r < 0)
180                         return log_error_errno(r, "Failed to put path into a vector: %m");
181 
182                 r = bpf_program_pin(prog->kernel_fd, test_suite[i].bpffs_path);
183                 if (r < 0)
184                         return log_error_errno(r, "Failed to pin BPF program '%s'", str);
185         }
186 
187         *paths_ret = TAKE_PTR(bpffs_paths);
188         return 0;
189 }
190 
test_bpf_cgroup_programs(Manager * m,const char * unit_name,const Test * test_suite,size_t test_suite_size)191 static int test_bpf_cgroup_programs(Manager *m, const char *unit_name, const Test *test_suite, size_t test_suite_size) {
192         _cleanup_(unlink_paths_and_freep) char **bpffs_paths = NULL;
193         _cleanup_(unit_freep) Unit *u = NULL;
194         CGroupContext *cc = NULL;
195         int cld_code, r;
196 
197         assert_se(u = unit_new(m, sizeof(Service)));
198         assert_se(unit_add_name(u, unit_name) == 0);
199         assert_se(cc = unit_get_cgroup_context(u));
200 
201         r = pin_programs(u, cc, test_suite, test_suite_size, &bpffs_paths);
202         if (r < 0)
203                 return log_error_errno(r, "Failed to pin programs: %m");
204 
205         for (size_t i = 0; i < test_suite_size; i++) {
206                 if (streq(test_suite[i].option_name, "BPFProgram")) {
207                         _cleanup_free_ char *option = NULL;
208                         r = bpf_foreign_test_to_string(test_suite[i].attach_type, test_suite[i].bpffs_path, &option);
209                         if (r < 0)
210                                 return log_error_errno(r, "Failed to compose option string: %m");
211                         r = config_parse_bpf_foreign_program(
212                                         u->id, "filename", 1, "Service", 1, test_suite[i].option_name, 0, option, cc, u);
213 
214                         if (r < 0)
215                                 return log_error_errno(r, "Failed to parse option string '%s': %m", option);
216                 } else if (STR_IN_SET(test_suite[i].option_name, "IPIngressFilterPath", "IPEgressFilterPath")) {
217                         const char *option = test_suite[i].bpffs_path;
218                         void *paths = NULL;
219 
220                         if (streq(test_suite[i].option_name, "IPIngressFilterPath"))
221                                 paths = &cc->ip_filters_ingress;
222                         else
223                                 paths = &cc->ip_filters_egress;
224 
225                         r = config_parse_ip_filter_bpf_progs(
226                                         u->id, "filename", 1, "Service", 1, test_suite[i].option_name, 0, option, paths, u);
227                         if (r < 0)
228                                 return log_error_errno(r, "Failed to parse option string '%s': %m", option);
229                 }
230         }
231 
232         r = config_parse_exec(
233                         u->id,
234                         "filename",
235                         1,
236                         "Service",
237                         1,
238                         "ExecStart",
239                         SERVICE_EXEC_START,
240                         "-/bin/ping -c 5 127.0.0.1 -W 1",
241                         SERVICE(u)->exec_command,
242                         u);
243         if (r < 0)
244                 return log_error_errno(r, "Failed to parse ExecStart");
245 
246         SERVICE(u)->type = SERVICE_ONESHOT;
247         u->load_state = UNIT_LOADED;
248 
249         r = unit_start(u);
250         if (r < 0)
251                 return log_error_errno(r, "Unit start failed %m");
252 
253         while (!IN_SET(SERVICE(u)->state, SERVICE_DEAD, SERVICE_FAILED)) {
254                 r = sd_event_run(m->event, UINT64_MAX);
255                 if (r < 0)
256                         return log_error_errno(errno, "Event run failed %m");
257         }
258 
259         cld_code = SERVICE(u)->exec_command[SERVICE_EXEC_START]->exec_status.code;
260         if (cld_code != CLD_EXITED)
261                 return log_error_errno(SYNTHETIC_ERRNO(EBUSY),
262                                 "Child didn't exit normally, code='%s'", sigchld_code_to_string(cld_code));
263 
264         if (SERVICE(u)->state != SERVICE_DEAD)
265                 return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Service is not dead");
266 
267         return r;
268 }
269 
main(int argc,char * argv[])270 int main(int argc, char *argv[]) {
271         _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL;
272         _cleanup_(manager_freep) Manager *m = NULL;
273         _cleanup_free_ char *unit_dir = NULL;
274         struct rlimit rl;
275         int r;
276 
277         test_setup_logging(LOG_DEBUG);
278 
279         if (detect_container() > 0)
280                 return log_tests_skipped("test-bpf fails inside LXC and Docker containers: https://github.com/systemd/systemd/issues/9666");
281 
282         if (getuid() != 0)
283                 return log_tests_skipped("not running as root");
284 
285         assert_se(getrlimit(RLIMIT_MEMLOCK, &rl) >= 0);
286         rl.rlim_cur = rl.rlim_max = MAX(rl.rlim_max, CAN_MEMLOCK_SIZE);
287         (void) setrlimit_closest(RLIMIT_MEMLOCK, &rl);
288 
289         if (!can_memlock())
290                 return log_tests_skipped("Can't use mlock()");
291 
292         r = cg_all_unified();
293         if (r <= 0)
294                 return log_tests_skipped("Unified hierarchy is required");
295 
296         r = enter_cgroup_subroot(NULL);
297         if (r == -ENOMEDIUM)
298                 return log_tests_skipped("cgroupfs not available");
299 
300         assert_se(get_testdata_dir("units", &unit_dir) >= 0);
301         assert_se(set_unit_path(unit_dir) >= 0);
302         assert_se(runtime_dir = setup_fake_runtime_dir());
303 
304         assert_se(manager_new(LOOKUP_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m) >= 0);
305         assert_se(manager_startup(m, NULL, NULL, NULL) >= 0);
306 
307         assert_se(test_bpf_cgroup_programs(m,
308                                 "single_prog.service", single_prog, ELEMENTSOF(single_prog)) >= 0);
309         assert_se(test_bpf_cgroup_programs(m,
310                                 "multi_prog_same_hook.service",
311                                 multi_prog_same_hook, ELEMENTSOF(multi_prog_same_hook)) >= 0);
312         assert_se(test_bpf_cgroup_programs(m,
313                                 "same_prog_multi_hook.service",
314                                 same_prog_multi_hook, ELEMENTSOF(same_prog_multi_hook)) >= 0);
315         assert_se(test_bpf_cgroup_programs(m,
316                                 "same_prog_multi_option_0.service",
317                                 same_prog_multi_option_0, ELEMENTSOF(same_prog_multi_option_0)) >= 0);
318         assert_se(test_bpf_cgroup_programs(m,
319                                 "same_prog_multi_option_1.service",
320                                 same_prog_multi_option_1, ELEMENTSOF(same_prog_multi_option_1)) >= 0);
321         assert_se(test_bpf_cgroup_programs(m,
322                                 "same_prog_same_hook.service",
323                                 same_prog_same_hook,
324                                 ELEMENTSOF(same_prog_same_hook)) >= 0);
325         assert_se(test_bpf_cgroup_programs(m,
326                                 "path_split_test.service",
327                                 path_split_test,
328                                 ELEMENTSOF(path_split_test)) >= 0);
329         return 0;
330 }
331