Branch data Line data Source code
1 : : /* SPDX-License-Identifier: GPL-2.0-only */
2 : : /*
3 : : * Copyright (c) 2023 Meta Platforms, Inc. and affiliates.
4 : : */
5 : :
6 : : #include <dirent.h>
7 : : #include <errno.h>
8 : : #include <fcntl.h>
9 : : #include <stdbool.h>
10 : : #include <stdlib.h>
11 : : #include <sys/file.h>
12 : : #include <unistd.h>
13 : :
14 : : #include <bpfilter/bpf.h>
15 : : #include <bpfilter/btf.h>
16 : : #include <bpfilter/chain.h>
17 : : #include <bpfilter/core/list.h>
18 : : #include <bpfilter/ctx.h>
19 : : #include <bpfilter/dump.h>
20 : : #include <bpfilter/elfstub.h>
21 : : #include <bpfilter/helper.h>
22 : : #include <bpfilter/hook.h>
23 : : #include <bpfilter/io.h>
24 : : #include <bpfilter/logger.h>
25 : : #include <bpfilter/pack.h>
26 : :
27 : : #include "cgen/cgen.h"
28 : :
29 : : #define _free_bf_ctx_ __attribute__((cleanup(_bf_ctx_free)))
30 : :
31 : : /**
32 : : * @struct bf_ctx
33 : : *
34 : : * bpfilter working context. Only one context is used during the library's
35 : : * lifetime.
36 : : */
37 : : struct bf_ctx
38 : : {
39 : : /// BPF token file descriptor
40 : : int token_fd;
41 : :
42 : : int lock_fd;
43 : :
44 : : bf_list cgens;
45 : :
46 : : struct bf_elfstub *stubs[_BF_ELFSTUB_MAX];
47 : :
48 : : /// Pass a token to BPF system calls, obtained from bpffs.
49 : : bool with_bpf_token;
50 : :
51 : : /// Path to the bpffs to pin the BPF objects into.
52 : : const char *bpffs_path;
53 : :
54 : : /// Verbose flags.
55 : : uint16_t verbose;
56 : : };
57 : :
58 : : static void _bf_ctx_free(struct bf_ctx **ctx);
59 : :
60 : : /// Global runtime context. Hidden in this translation unit.
61 : : static struct bf_ctx *_bf_global_ctx = NULL;
62 : :
63 : 0 : static int _bf_ctx_gen_token(const char *bpffs_path)
64 : : {
65 : 0 : _cleanup_close_ int mnt_fd = -1;
66 : 0 : _cleanup_close_ int bpffs_fd = -1;
67 : 0 : _cleanup_close_ int token_fd = -1;
68 : :
69 : 0 : mnt_fd = open(bpffs_path, O_DIRECTORY);
70 [ # # ]: 0 : if (mnt_fd < 0)
71 [ # # ]: 0 : return bf_err_r(errno, "failed to open '%s'", bpffs_path);
72 : :
73 : 0 : bpffs_fd = openat(mnt_fd, ".", 0, O_RDWR);
74 [ # # ]: 0 : if (bpffs_fd < 0)
75 [ # # ]: 0 : return bf_err_r(errno, "failed to get bpffs FD from '%s'", bpffs_path);
76 : :
77 : 0 : token_fd = bf_bpf_token_create(bpffs_fd);
78 [ # # ]: 0 : if (token_fd < 0) {
79 [ # # ]: 0 : return bf_err_r(token_fd, "failed to create BPF token for '%s'",
80 : : bpffs_path);
81 : : }
82 : :
83 : 0 : return TAKE_FD(token_fd);
84 : : }
85 : :
86 : : /**
87 : : * Create and initialize a new context.
88 : : *
89 : : * On failure, @p ctx is left unchanged.
90 : : *
91 : : * @param ctx New context to create. Can't be NULL.
92 : : * @param with_bpf_token If true, create a BPF token from bpffs.
93 : : * @param bpffs_path Path to the bpffs mountpoint. Can't be NULL.
94 : : * @param verbose Bitmask of verbose flags.
95 : : * @return 0 on success, negative errno value on failure.
96 : : */
97 : 341 : static int _bf_ctx_new(struct bf_ctx **ctx, bool with_bpf_token,
98 : : const char *bpffs_path, uint16_t verbose)
99 : : {
100 : 341 : _free_bf_ctx_ struct bf_ctx *_ctx = NULL;
101 : : int r;
102 : :
103 : : assert(ctx);
104 : : assert(bpffs_path);
105 : :
106 : 341 : _ctx = calloc(1, sizeof(*_ctx));
107 [ + - ]: 341 : if (!_ctx)
108 : : return -ENOMEM;
109 : :
110 : 341 : r = bf_btf_setup();
111 [ - + ]: 341 : if (r)
112 [ # # ]: 0 : return bf_err_r(r, "failed to load vmlinux BTF");
113 : :
114 : 341 : _ctx->with_bpf_token = with_bpf_token;
115 : 341 : _ctx->bpffs_path = bpffs_path;
116 : 341 : _ctx->verbose = verbose;
117 : 341 : _ctx->lock_fd = -1;
118 : :
119 : 341 : _ctx->token_fd = -1;
120 [ - + ]: 341 : if (_ctx->with_bpf_token) {
121 : 0 : _cleanup_close_ int token_fd = -1;
122 : :
123 : 0 : r = bf_btf_kernel_has_token();
124 [ # # ]: 0 : if (r == -ENOENT) {
125 [ # # ]: 0 : bf_err(
126 : : "--with-bpf-token requested, but this kernel doesn't support BPF token");
127 : 0 : return r;
128 : : }
129 [ # # ]: 0 : if (r)
130 [ # # ]: 0 : return bf_err_r(r, "failed to check for BPF token support");
131 : :
132 : 0 : token_fd = _bf_ctx_gen_token(_ctx->bpffs_path);
133 [ # # ]: 0 : if (token_fd < 0)
134 [ # # ]: 0 : return bf_err_r(token_fd, "failed to generate a BPF token");
135 : :
136 : 0 : _ctx->token_fd = TAKE_FD(token_fd);
137 : : }
138 : :
139 : 341 : _ctx->cgens = bf_list_default(bf_cgen_free, bf_cgen_pack);
140 : :
141 [ + + ]: 2046 : for (enum bf_elfstub_id id = 0; id < _BF_ELFSTUB_MAX; ++id) {
142 : 1705 : r = bf_elfstub_new(&_ctx->stubs[id], id);
143 [ - + ]: 1705 : if (r)
144 [ # # ]: 0 : return bf_err_r(r, "failed to create ELF stub ID %u", id);
145 : : }
146 : :
147 : 341 : *ctx = TAKE_PTR(_ctx);
148 : :
149 : 341 : return 0;
150 : : }
151 : :
152 : : /**
153 : : * Free a context.
154 : : *
155 : : * If @p ctx points to a NULL pointer, this function does nothing. Once
156 : : * the function returns, @p ctx points to a NULL pointer.
157 : : *
158 : : * @param ctx Context to free. Can't be NULL.
159 : : */
160 : 682 : static void _bf_ctx_free(struct bf_ctx **ctx)
161 : : {
162 : : assert(ctx);
163 : :
164 [ + + ]: 682 : if (!*ctx)
165 : : return;
166 : :
167 : 341 : closep(&(*ctx)->token_fd);
168 : 341 : closep(&(*ctx)->lock_fd);
169 : 341 : bf_list_clean(&(*ctx)->cgens);
170 : :
171 [ + + ]: 2046 : for (enum bf_elfstub_id id = 0; id < _BF_ELFSTUB_MAX; ++id)
172 : 1705 : bf_elfstub_free(&(*ctx)->stubs[id]);
173 : :
174 : 341 : bf_btf_teardown();
175 : :
176 : : freep((void *)ctx);
177 : : }
178 : :
179 : : /**
180 : : * See @ref bf_ctx_dump for details.
181 : : */
182 : 0 : static void _bf_ctx_dump(const struct bf_ctx *ctx, prefix_t *prefix)
183 : : {
184 [ # # ]: 0 : DUMP(prefix, "struct bf_ctx at %p", ctx);
185 : :
186 : 0 : bf_dump_prefix_push(prefix);
187 : :
188 [ # # ]: 0 : DUMP(prefix, "token_fd: %d", ctx->token_fd);
189 : :
190 : : // Codegens
191 [ # # ]: 0 : DUMP(bf_dump_prefix_last(prefix), "cgens: bf_list<struct bf_cgen>[%lu]",
192 : : bf_list_size(&ctx->cgens));
193 : 0 : bf_dump_prefix_push(prefix);
194 [ # # # # : 0 : bf_list_foreach (&ctx->cgens, cgen_node) {
# # ]
195 : : struct bf_cgen *cgen = bf_list_node_get_data(cgen_node);
196 : :
197 [ # # ]: 0 : if (bf_list_is_tail(&ctx->cgens, cgen_node))
198 : 0 : bf_dump_prefix_last(prefix);
199 : :
200 : 0 : bf_cgen_dump(cgen, prefix);
201 : : }
202 : 0 : bf_dump_prefix_pop(prefix);
203 : :
204 : 0 : bf_dump_prefix_pop(prefix);
205 : 0 : }
206 : :
207 : : /**
208 : : * See @ref bf_ctx_get_cgen for details.
209 : : */
210 : 6121 : static struct bf_cgen *_bf_ctx_get_cgen(const struct bf_ctx *ctx,
211 : : const char *name)
212 : : {
213 : : assert(ctx);
214 : : assert(name);
215 : :
216 [ + + + + : 12642 : bf_list_foreach (&ctx->cgens, cgen_node) {
+ + ]
217 : : struct bf_cgen *cgen = bf_list_node_get_data(cgen_node);
218 : :
219 [ + + ]: 4251 : if (bf_streq(cgen->chain->name, name))
220 : : return cgen;
221 : : }
222 : :
223 : : return NULL;
224 : : }
225 : :
226 : : /**
227 : : * See @ref bf_ctx_get_cgens for details.
228 : : */
229 : 4 : static int _bf_ctx_get_cgens(const struct bf_ctx *ctx, bf_list *cgens)
230 : : {
231 : 4 : _clean_bf_list_ bf_list _cgens = bf_list_default(NULL, NULL);
232 : : int r;
233 : :
234 : : assert(ctx);
235 : : assert(cgens);
236 : :
237 [ + - + + : 18 : bf_list_foreach (&ctx->cgens, cgen_node) {
+ + ]
238 : 5 : r = bf_list_add_tail(&_cgens, bf_list_node_get_data(cgen_node));
239 [ + - ]: 5 : if (r)
240 : : return r;
241 : : }
242 : :
243 : 4 : *cgens = bf_list_move(_cgens);
244 : :
245 : 4 : return 0;
246 : : }
247 : :
248 : : /**
249 : : * See @ref bf_ctx_set_cgen for details.
250 : : */
251 : 1171 : static int _bf_ctx_set_cgen(struct bf_ctx *ctx, struct bf_cgen *cgen)
252 : : {
253 : : assert(ctx);
254 : : assert(cgen);
255 : :
256 [ - + ]: 1171 : if (_bf_ctx_get_cgen(ctx, cgen->chain->name))
257 [ # # ]: 0 : return bf_err_r(-EEXIST, "codegen already exists in context");
258 : :
259 : 1171 : return bf_list_add_tail(&ctx->cgens, cgen);
260 : : }
261 : :
262 : 262 : static int _bf_ctx_delete_cgen(struct bf_ctx *ctx, struct bf_cgen *cgen,
263 : : bool unload)
264 : : {
265 [ + - + - ]: 574 : bf_list_foreach (&ctx->cgens, cgen_node) {
266 : : struct bf_cgen *_cgen = bf_list_node_get_data(cgen_node);
267 : :
268 [ + + - + ]: 287 : if (_cgen != cgen)
269 : : continue;
270 : :
271 [ + - ]: 262 : if (unload)
272 : 262 : bf_cgen_unload(_cgen);
273 : :
274 : 262 : bf_list_delete(&ctx->cgens, cgen_node);
275 : :
276 : 262 : return 0;
277 : : }
278 : :
279 : : return -ENOENT;
280 : : }
281 : :
282 : : static void _bf_free_dir(DIR **dir)
283 : : {
284 [ - + ]: 341 : if (!*dir)
285 : : return;
286 : :
287 : 341 : closedir(*dir);
288 : : *dir = NULL;
289 : : }
290 : :
291 : : #define _free_dir_ __attribute__((__cleanup__(_bf_free_dir)))
292 : :
293 : : /**
294 : : * @brief Discover and restore chains from bpffs context maps.
295 : : *
296 : : * Iterates subdirectories under `{bpffs}/bpfilter/`, deserializing each
297 : : * chain's `bf_ctx` context map into a `bf_cgen` and adding it to the
298 : : * global context. The global context must already be initialized via
299 : : * `bf_ctx_setup` before calling this function.
300 : : *
301 : : * @return 0 on success, or a negative errno value on failure.
302 : : */
303 : 341 : static int _bf_ctx_discover(void)
304 : : {
305 : 341 : _cleanup_close_ int bpffs_fd = -1;
306 : 341 : _cleanup_close_ int pindir_fd = -1;
307 : : _free_dir_ DIR *dir = NULL;
308 : : struct dirent *entry;
309 : : int iter_fd;
310 : : int r;
311 : :
312 : 341 : bpffs_fd = bf_opendir(_bf_global_ctx->bpffs_path);
313 [ - + ]: 341 : if (bpffs_fd < 0) {
314 [ # # ]: 0 : return bf_err_r(bpffs_fd, "failed to open bpffs at %s",
315 : : _bf_global_ctx->bpffs_path);
316 : : }
317 : :
318 : 341 : pindir_fd = bf_opendir_at(bpffs_fd, "bpfilter", false);
319 [ - + ]: 341 : if (pindir_fd < 0) {
320 [ # # ]: 0 : if (pindir_fd == -ENOENT) {
321 [ # # ]: 0 : bf_info("no bpfilter pin directory found, nothing to discover");
322 : : return 0;
323 : : }
324 [ # # ]: 0 : return bf_err_r(pindir_fd, "failed to open pin directory");
325 : : }
326 : :
327 : : /* fdopendir() takes ownership of the fd: dup so pindir_fd remains valid
328 : : * for openat() calls inside the loop. */
329 : 341 : iter_fd = dup(pindir_fd);
330 [ - + ]: 341 : if (iter_fd < 0)
331 [ # # ]: 0 : return bf_err_r(-errno, "failed to dup pin directory fd");
332 : :
333 : 341 : dir = fdopendir(iter_fd);
334 [ - + ]: 341 : if (!dir) {
335 : 0 : r = -errno;
336 : 0 : close(iter_fd);
337 [ # # ]: 0 : return bf_err_r(r, "failed to open pin directory for iteration");
338 : : }
339 : :
340 : : for (;;) {
341 : 1342 : _free_bf_cgen_ struct bf_cgen *cgen = NULL;
342 : 1342 : _cleanup_close_ int chain_fd = -1;
343 : :
344 : 1342 : errno = 0;
345 : 1342 : entry = readdir(dir);
346 [ + + ]: 1342 : if (!entry)
347 : : break;
348 : :
349 [ + + + + ]: 1001 : if (bf_streq(entry->d_name, ".") || bf_streq(entry->d_name, ".."))
350 : 682 : continue;
351 : :
352 [ - + ]: 319 : if (entry->d_type != DT_DIR)
353 : 0 : continue;
354 : :
355 : 319 : chain_fd = openat(pindir_fd, entry->d_name, O_DIRECTORY);
356 [ - + ]: 319 : if (chain_fd < 0) {
357 [ # # ]: 0 : bf_warn("failed to open chain directory '%s', skipping",
358 : : entry->d_name);
359 : 0 : continue;
360 : : }
361 : :
362 : 319 : r = bf_cgen_new_from_dir_fd(&cgen, chain_fd);
363 [ - + ]: 319 : if (r) {
364 [ # # ]: 0 : bf_warn("failed to restore chain '%s', skipping", entry->d_name);
365 : 0 : continue;
366 : : }
367 : :
368 [ + - ]: 319 : bf_info("discovered chain '%s'", entry->d_name);
369 : :
370 : 319 : r = bf_list_push(&_bf_global_ctx->cgens, (void **)&cgen);
371 [ - + ]: 319 : if (r) {
372 [ # # ]: 0 : bf_warn("failed to add restored chain '%s' to context, skipping",
373 : : entry->d_name);
374 : : }
375 : : }
376 : :
377 [ - + ]: 341 : if (errno)
378 [ # # ]: 0 : return bf_err_r(-errno, "failed to read pin directory");
379 : :
380 : : return 0;
381 : : }
382 : :
383 : 341 : int bf_ctx_setup(bool with_bpf_token, const char *bpffs_path, uint16_t verbose)
384 : : {
385 : 341 : _cleanup_close_ int pindir_fd = -1;
386 : : int r;
387 : :
388 : 341 : r = _bf_ctx_new(&_bf_global_ctx, with_bpf_token, bpffs_path, verbose);
389 [ - + ]: 341 : if (r)
390 [ # # ]: 0 : return bf_err_r(r, "failed to create new context");
391 : :
392 : 341 : pindir_fd = bf_ctx_get_pindir_fd();
393 [ - + ]: 341 : if (pindir_fd < 0) {
394 : 0 : _bf_ctx_free(&_bf_global_ctx);
395 [ # # ]: 0 : return bf_err_r(pindir_fd, "failed to get pin directory FD");
396 : : }
397 : :
398 : 341 : r = flock(pindir_fd, LOCK_EX | LOCK_NB);
399 [ - + ]: 341 : if (r) {
400 : 0 : _bf_ctx_free(&_bf_global_ctx);
401 [ # # ]: 0 : return bf_err_r(-errno, "failed to lock pin directory");
402 : : }
403 : :
404 : 341 : r = _bf_ctx_discover();
405 [ - + ]: 341 : if (r) {
406 : 0 : _bf_ctx_free(&_bf_global_ctx);
407 [ # # ]: 0 : return bf_err_r(r, "failed to discover chains");
408 : : }
409 : :
410 : 341 : _bf_global_ctx->lock_fd = TAKE_FD(pindir_fd);
411 : :
412 : 341 : return 0;
413 : : }
414 : :
415 : 341 : void bf_ctx_teardown(void)
416 : : {
417 : 341 : _bf_ctx_free(&_bf_global_ctx);
418 : 341 : }
419 : :
420 : 1690 : static void _bf_ctx_flush(struct bf_ctx *ctx)
421 : : {
422 : : assert(ctx);
423 : :
424 [ + + + + : 5158 : bf_list_foreach (&ctx->cgens, cgen_node) {
+ + ]
425 : : struct bf_cgen *cgen = bf_list_node_get_data(cgen_node);
426 : :
427 : 889 : bf_cgen_unload(cgen);
428 : 889 : bf_list_delete(&ctx->cgens, cgen_node);
429 : : }
430 : 1690 : }
431 : :
432 : 1690 : void bf_ctx_flush(void)
433 : : {
434 [ + - ]: 1690 : if (!_bf_global_ctx)
435 : : return;
436 : :
437 : 1690 : _bf_ctx_flush(_bf_global_ctx);
438 : : }
439 : :
440 : 0 : void bf_ctx_dump(prefix_t *prefix)
441 : : {
442 [ # # ]: 0 : if (!_bf_global_ctx)
443 : : return;
444 : :
445 : 0 : _bf_ctx_dump(_bf_global_ctx, prefix);
446 : : }
447 : :
448 : 4950 : struct bf_cgen *bf_ctx_get_cgen(const char *name)
449 : : {
450 [ + - ]: 4950 : if (!_bf_global_ctx)
451 : : return NULL;
452 : :
453 : 4950 : return _bf_ctx_get_cgen(_bf_global_ctx, name);
454 : : }
455 : :
456 : 4 : int bf_ctx_get_cgens(bf_list *cgens)
457 : : {
458 [ - + ]: 4 : if (!_bf_global_ctx)
459 [ # # ]: 0 : return bf_err_r(-EINVAL, "context is not initialized");
460 : :
461 : 4 : return _bf_ctx_get_cgens(_bf_global_ctx, cgens);
462 : : }
463 : :
464 : 1171 : int bf_ctx_set_cgen(struct bf_cgen *cgen)
465 : : {
466 [ - + ]: 1171 : if (!_bf_global_ctx)
467 [ # # ]: 0 : return bf_err_r(-EINVAL, "context is not initialized");
468 : :
469 : 1171 : return _bf_ctx_set_cgen(_bf_global_ctx, cgen);
470 : : }
471 : :
472 : 262 : int bf_ctx_delete_cgen(struct bf_cgen *cgen, bool unload)
473 : : {
474 [ - + ]: 262 : if (!_bf_global_ctx)
475 [ # # ]: 0 : return bf_err_r(-EINVAL, "context is not initialized");
476 : :
477 : 262 : return _bf_ctx_delete_cgen(_bf_global_ctx, cgen, unload);
478 : : }
479 : :
480 : 6265 : int bf_ctx_token(void)
481 : : {
482 [ + - ]: 6265 : if (!_bf_global_ctx)
483 : : return -1;
484 : :
485 : 6265 : return _bf_global_ctx->token_fd;
486 : : }
487 : :
488 : 4510 : int bf_ctx_get_pindir_fd(void)
489 : : {
490 : 4510 : _cleanup_close_ int bpffs_fd = -1;
491 : 4510 : _cleanup_close_ int pindir_fd = -1;
492 : :
493 [ - + ]: 4510 : if (!_bf_global_ctx)
494 [ # # ]: 0 : return bf_err_r(-EINVAL, "context is not initialized");
495 : :
496 : 4510 : bpffs_fd = bf_opendir(_bf_global_ctx->bpffs_path);
497 [ - + ]: 4510 : if (bpffs_fd < 0) {
498 [ # # ]: 0 : return bf_err_r(bpffs_fd, "failed to open bpffs at %s",
499 : : _bf_global_ctx->bpffs_path);
500 : : }
501 : :
502 : 4510 : pindir_fd = bf_opendir_at(bpffs_fd, "bpfilter", true);
503 [ - + ]: 4510 : if (pindir_fd < 0) {
504 [ # # ]: 0 : return bf_err_r(pindir_fd, "failed to open pin directory %s/bpfilter",
505 : : _bf_global_ctx->bpffs_path);
506 : : }
507 : :
508 : 4510 : return TAKE_FD(pindir_fd);
509 : : }
510 : :
511 : 2363 : const struct bf_elfstub *bf_ctx_get_elfstub(enum bf_elfstub_id id)
512 : : {
513 [ + - ]: 2363 : if (!_bf_global_ctx)
514 : : return NULL;
515 : :
516 : 2363 : return _bf_global_ctx->stubs[id];
517 : : }
518 : :
519 : 6200 : bool bf_ctx_is_verbose(enum bf_verbose opt)
520 : : {
521 [ + - ]: 6200 : if (!_bf_global_ctx)
522 : : return false;
523 : :
524 : 6200 : return _bf_global_ctx->verbose & BF_FLAG(opt);
525 : : }
|