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 "ctx.h"
7 :
8 : #include <errno.h>
9 : #include <fcntl.h>
10 : #include <stdbool.h>
11 : #include <stdlib.h>
12 : #include <unistd.h>
13 :
14 : #include <bpfilter/bpf.h>
15 : #include <bpfilter/btf.h>
16 : #include <bpfilter/chain.h>
17 : #include <bpfilter/dump.h>
18 : #include <bpfilter/front.h>
19 : #include <bpfilter/helper.h>
20 : #include <bpfilter/hook.h>
21 : #include <bpfilter/io.h>
22 : #include <bpfilter/list.h>
23 : #include <bpfilter/logger.h>
24 : #include <bpfilter/ns.h>
25 : #include <bpfilter/pack.h>
26 :
27 : #include "cgen/cgen.h"
28 : #include "cgen/elfstub.h"
29 : #include "opts.h"
30 :
31 : #define _free_bf_ctx_ __attribute__((cleanup(_bf_ctx_free)))
32 :
33 : /**
34 : * @struct bf_ctx
35 : *
36 : * bpfilter working context. Only one context is used during the daemon's
37 : * lifetime.
38 : */
39 : struct bf_ctx
40 : {
41 : /// Namespaces the daemon was started in.
42 : struct bf_ns ns;
43 :
44 : /// BPF token file descriptor
45 : int token_fd;
46 :
47 : bf_list cgens;
48 :
49 : struct bf_elfstub *stubs[_BF_ELFSTUB_MAX];
50 : };
51 :
52 : static void _bf_ctx_free(struct bf_ctx **ctx);
53 :
54 : /// Global daemon context. Hidden in this translation unit.
55 : static struct bf_ctx *_bf_global_ctx = NULL;
56 :
57 0 : static int _bf_ctx_gen_token(void)
58 : {
59 0 : _cleanup_close_ int mnt_fd = -1;
60 0 : _cleanup_close_ int bpffs_fd = -1;
61 0 : _cleanup_close_ int token_fd = -1;
62 :
63 0 : mnt_fd = open(bf_opts_bpffs_path(), O_DIRECTORY);
64 0 : if (mnt_fd < 0)
65 0 : return bf_err_r(errno, "failed to open '%s'", bf_opts_bpffs_path());
66 :
67 0 : bpffs_fd = openat(mnt_fd, ".", 0, O_RDWR);
68 0 : if (bpffs_fd < 0)
69 0 : return bf_err_r(errno, "failed to get bpffs FD from '%s'",
70 : bf_opts_bpffs_path());
71 :
72 0 : token_fd = bf_bpf_token_create(bpffs_fd);
73 0 : if (token_fd < 0) {
74 0 : return bf_err_r(token_fd, "failed to create BPF token for '%s'",
75 : bf_opts_bpffs_path());
76 : }
77 :
78 0 : return TAKE_FD(token_fd);
79 : }
80 :
81 : /**
82 : * Create and initialize a new context.
83 : *
84 : * On failure, @p ctx is left unchanged.
85 : *
86 : * @param ctx New context to create. Can't be NULL.
87 : * @return 0 on success, negative errno value on failure.
88 : */
89 5 : static int _bf_ctx_new(struct bf_ctx **ctx)
90 : {
91 4 : _free_bf_ctx_ struct bf_ctx *_ctx = NULL;
92 : int r;
93 :
94 5 : bf_assert(ctx);
95 :
96 4 : _ctx = calloc(1, sizeof(*_ctx));
97 4 : if (!_ctx)
98 : return -ENOMEM;
99 :
100 4 : r = bf_ns_init(&_ctx->ns, getpid());
101 4 : if (r)
102 0 : return bf_err_r(r, "failed to initialise current bf_ns");
103 :
104 4 : _ctx->token_fd = -1;
105 4 : if (bf_opts_with_bpf_token()) {
106 0 : _cleanup_close_ int token_fd = -1;
107 :
108 0 : r = bf_btf_kernel_has_token();
109 0 : if (r == -ENOENT) {
110 0 : bf_err(
111 : "--with-bpf-token requested, but this kernel doesn't support BPF token");
112 0 : return r;
113 : }
114 0 : if (r)
115 0 : return bf_err_r(r, "failed to check for BPF token support");
116 :
117 0 : token_fd = _bf_ctx_gen_token();
118 0 : if (token_fd < 0)
119 0 : return bf_err_r(token_fd, "failed to generate a BPF token");
120 :
121 0 : _ctx->token_fd = TAKE_FD(token_fd);
122 : }
123 :
124 4 : _ctx->cgens = bf_list_default(bf_cgen_free, bf_cgen_pack);
125 :
126 20 : for (enum bf_elfstub_id id = 0; id < _BF_ELFSTUB_MAX; ++id) {
127 16 : r = bf_elfstub_new(&_ctx->stubs[id], id);
128 16 : if (r)
129 0 : return bf_err_r(r, "failed to create ELF stub ID %u", id);
130 : }
131 :
132 4 : *ctx = TAKE_PTR(_ctx);
133 :
134 4 : return 0;
135 : }
136 :
137 : /**
138 : * @brief Allocate and initialize a new context from serialized data.
139 : *
140 : * @param ctx Context object to allocate and initialize from the serialized
141 : * data. The caller will own the object. On failure, `*ctx` is
142 : * unchanged. Can't be NULL.
143 : * @param node Node containing the serialized matcher.
144 : * @return 0 on success, or a negative errno value on failure.
145 : */
146 0 : static int _bf_ctx_new_from_pack(struct bf_ctx **ctx, bf_rpack_node_t node)
147 : {
148 0 : _free_bf_ctx_ struct bf_ctx *_ctx = NULL;
149 : bf_rpack_node_t child, array_node;
150 : int r;
151 :
152 0 : bf_assert(ctx);
153 :
154 0 : r = _bf_ctx_new(&_ctx);
155 0 : if (r < 0)
156 : return r;
157 :
158 0 : r = bf_rpack_kv_array(node, "cgens", &child);
159 0 : if (r)
160 : return r;
161 0 : bf_rpack_array_foreach (child, array_node) {
162 0 : _free_bf_cgen_ struct bf_cgen *cgen = NULL;
163 :
164 0 : r = bf_list_emplace(&_ctx->cgens, bf_cgen_new_from_pack, cgen,
165 : array_node);
166 : if (r)
167 : return r;
168 : }
169 :
170 0 : *ctx = TAKE_PTR(_ctx);
171 :
172 0 : return 0;
173 : }
174 :
175 : /**
176 : * Free a context.
177 : *
178 : * If @p ctx points to a NULL pointer, this function does nothing. Once
179 : * the function returns, @p ctx points to a NULL pointer.
180 : *
181 : * @param ctx Context to free. Can't be NULL.
182 : */
183 11 : static void _bf_ctx_free(struct bf_ctx **ctx)
184 : {
185 11 : bf_assert(ctx);
186 :
187 10 : if (!*ctx)
188 : return;
189 :
190 4 : bf_ns_clean(&(*ctx)->ns);
191 4 : closep(&(*ctx)->token_fd);
192 4 : bf_list_clean(&(*ctx)->cgens);
193 :
194 20 : for (enum bf_elfstub_id id = 0; id < _BF_ELFSTUB_MAX; ++id)
195 16 : bf_elfstub_free(&(*ctx)->stubs[id]);
196 :
197 : freep((void *)ctx);
198 : }
199 :
200 : /**
201 : * See @ref bf_ctx_dump for details.
202 : */
203 0 : static void _bf_ctx_dump(const struct bf_ctx *ctx, prefix_t *prefix)
204 : {
205 0 : DUMP(prefix, "struct bf_ctx at %p", ctx);
206 :
207 0 : bf_dump_prefix_push(prefix);
208 :
209 : // Namespaces
210 0 : DUMP(prefix, "ns: struct bf_ns")
211 0 : bf_dump_prefix_push(prefix);
212 :
213 0 : DUMP(prefix, "net: struct bf_ns_info");
214 0 : bf_dump_prefix_push(prefix);
215 0 : DUMP(prefix, "fd: %d", ctx->ns.net.fd);
216 0 : DUMP(bf_dump_prefix_last(prefix), "inode: %u", ctx->ns.net.inode);
217 0 : bf_dump_prefix_pop(prefix);
218 :
219 0 : DUMP(bf_dump_prefix_last(prefix), "mnt: struct bf_ns_info");
220 0 : bf_dump_prefix_push(prefix);
221 0 : DUMP(prefix, "fd: %d", ctx->ns.mnt.fd);
222 0 : DUMP(bf_dump_prefix_last(prefix), "inode: %u", ctx->ns.mnt.inode);
223 0 : bf_dump_prefix_pop(prefix);
224 :
225 0 : bf_dump_prefix_pop(prefix);
226 :
227 0 : DUMP(prefix, "token_fd: %d", ctx->token_fd);
228 :
229 : // Codegens
230 0 : DUMP(bf_dump_prefix_last(prefix), "cgens: bf_list<struct bf_cgen>[%lu]",
231 : bf_list_size(&ctx->cgens));
232 0 : bf_dump_prefix_push(prefix);
233 0 : bf_list_foreach (&ctx->cgens, cgen_node) {
234 0 : struct bf_cgen *cgen = bf_list_node_get_data(cgen_node);
235 :
236 0 : if (bf_list_is_tail(&ctx->cgens, cgen_node))
237 0 : bf_dump_prefix_last(prefix);
238 :
239 0 : bf_cgen_dump(cgen, prefix);
240 : }
241 0 : bf_dump_prefix_pop(prefix);
242 :
243 0 : bf_dump_prefix_pop(prefix);
244 0 : }
245 :
246 : /**
247 : * @brief Serialize a context.
248 : *
249 : * The context is serialized as:
250 : * @code
251 : * {
252 : * "cgens": [
253 : * { bf_codegen },
254 : * // ...
255 : * ]
256 : * }
257 : * @endcode
258 : *
259 : * @param ctx Context to serialize. Can't be NULL.
260 : * @param pack `bf_wpack_t` object to serialize the context into. Can't be NULL.
261 : * @return 0 on success, or a negative error value on failure.
262 : */
263 0 : static int _bf_ctx_pack(const struct bf_ctx *ctx, bf_wpack_t *pack)
264 : {
265 0 : bf_assert(ctx);
266 0 : bf_assert(pack);
267 :
268 0 : bf_wpack_kv_list(pack, "cgens", &ctx->cgens);
269 :
270 0 : return bf_wpack_is_valid(pack) ? 0 : -EINVAL;
271 : }
272 :
273 : /**
274 : * See @ref bf_ctx_get_cgen for details.
275 : */
276 6 : static struct bf_cgen *_bf_ctx_get_cgen(const struct bf_ctx *ctx,
277 : const char *name)
278 : {
279 6 : bf_assert(ctx && name);
280 :
281 16 : bf_list_foreach (&ctx->cgens, cgen_node) {
282 6 : struct bf_cgen *cgen = bf_list_node_get_data(cgen_node);
283 :
284 6 : if (bf_streq(cgen->chain->name, name))
285 : return cgen;
286 : }
287 :
288 : return NULL;
289 : }
290 :
291 : /**
292 : * See @ref bf_ctx_get_cgens_for_front for details.
293 : */
294 0 : static int _bf_ctx_get_cgens_for_front(const struct bf_ctx *ctx, bf_list *cgens,
295 : enum bf_front front)
296 : {
297 0 : _clean_bf_list_ bf_list _cgens =
298 0 : bf_list_default(cgens->ops.free, cgens->ops.pack);
299 : int r;
300 :
301 0 : bf_assert(ctx && cgens);
302 :
303 0 : bf_list_foreach (&ctx->cgens, cgen_node) {
304 0 : struct bf_cgen *cgen = bf_list_node_get_data(cgen_node);
305 :
306 0 : if (cgen->front != front)
307 0 : continue;
308 :
309 0 : r = bf_list_add_tail(&_cgens, cgen);
310 0 : if (r)
311 0 : return bf_err_r(r, "failed to insert codegen into list");
312 : }
313 :
314 0 : *cgens = bf_list_move(_cgens);
315 :
316 0 : return 0;
317 : }
318 :
319 : /**
320 : * See @ref bf_ctx_set_cgen for details.
321 : */
322 3 : static int _bf_ctx_set_cgen(struct bf_ctx *ctx, struct bf_cgen *cgen)
323 : {
324 3 : bf_assert(ctx && cgen);
325 :
326 3 : if (_bf_ctx_get_cgen(ctx, cgen->chain->name))
327 1 : return bf_err_r(-EEXIST, "codegen already exists in context");
328 :
329 2 : return bf_list_add_tail(&ctx->cgens, cgen);
330 : }
331 :
332 0 : static int _bf_ctx_delete_cgen(struct bf_ctx *ctx, struct bf_cgen *cgen,
333 : bool unload)
334 : {
335 0 : bf_list_foreach (&ctx->cgens, cgen_node) {
336 0 : struct bf_cgen *_cgen = bf_list_node_get_data(cgen_node);
337 :
338 0 : if (_cgen != cgen)
339 : continue;
340 :
341 0 : if (unload)
342 0 : bf_cgen_unload(_cgen);
343 :
344 0 : bf_list_delete(&ctx->cgens, cgen_node);
345 :
346 0 : return 0;
347 : }
348 :
349 : return -ENOENT;
350 : }
351 :
352 0 : int bf_ctx_setup(void)
353 : {
354 0 : _free_bf_ctx_ struct bf_ctx *_ctx = NULL;
355 : int r;
356 :
357 0 : bf_assert(!_ctx);
358 :
359 0 : r = _bf_ctx_new(&_ctx);
360 0 : if (r)
361 0 : return bf_err_r(r, "failed to create new context");
362 :
363 0 : _bf_global_ctx = TAKE_PTR(_ctx);
364 :
365 0 : return 0;
366 : }
367 :
368 0 : void bf_ctx_teardown(bool clear)
369 : {
370 0 : if (clear) {
371 0 : bf_list_foreach (&_bf_global_ctx->cgens, cgen_node)
372 0 : bf_cgen_unload(bf_list_node_get_data(cgen_node));
373 : }
374 :
375 0 : _bf_ctx_free(&_bf_global_ctx);
376 0 : }
377 :
378 0 : int bf_ctx_save(bf_wpack_t *pack)
379 : {
380 : int r;
381 :
382 0 : bf_assert(pack);
383 :
384 0 : r = _bf_ctx_pack(_bf_global_ctx, pack);
385 0 : if (r)
386 0 : return bf_err_r(r, "failed to serialize context");
387 :
388 : return 0;
389 : }
390 :
391 0 : int bf_ctx_load(bf_rpack_node_t node)
392 : {
393 0 : _free_bf_ctx_ struct bf_ctx *ctx = NULL;
394 : int r;
395 :
396 0 : r = _bf_ctx_new_from_pack(&ctx, node);
397 0 : if (r)
398 0 : return bf_err_r(r, "failed to deserialize context");
399 :
400 0 : _bf_global_ctx = TAKE_PTR(ctx);
401 :
402 0 : return 0;
403 : }
404 :
405 0 : static void _bf_ctx_flush(struct bf_ctx *ctx, enum bf_front front)
406 : {
407 0 : bf_assert(ctx);
408 :
409 0 : bf_list_foreach (&ctx->cgens, cgen_node) {
410 0 : struct bf_cgen *cgen = bf_list_node_get_data(cgen_node);
411 :
412 0 : if (cgen->front != front)
413 0 : continue;
414 :
415 0 : bf_cgen_unload(cgen);
416 0 : bf_list_delete(&ctx->cgens, cgen_node);
417 : }
418 0 : }
419 :
420 0 : void bf_ctx_flush(enum bf_front front)
421 : {
422 0 : _bf_ctx_flush(_bf_global_ctx, front);
423 0 : }
424 :
425 0 : bool bf_ctx_is_empty(void)
426 : {
427 0 : return bf_list_is_empty(&_bf_global_ctx->cgens);
428 : }
429 :
430 0 : void bf_ctx_dump(prefix_t *prefix)
431 : {
432 0 : _bf_ctx_dump(_bf_global_ctx, prefix);
433 0 : }
434 :
435 0 : struct bf_cgen *bf_ctx_get_cgen(const char *name)
436 : {
437 0 : return _bf_ctx_get_cgen(_bf_global_ctx, name);
438 : }
439 :
440 0 : int bf_ctx_get_cgens_for_front(bf_list *cgens, enum bf_front front)
441 : {
442 0 : return _bf_ctx_get_cgens_for_front(_bf_global_ctx, cgens, front);
443 : }
444 :
445 0 : int bf_ctx_set_cgen(struct bf_cgen *cgen)
446 : {
447 0 : return _bf_ctx_set_cgen(_bf_global_ctx, cgen);
448 : }
449 :
450 0 : int bf_ctx_delete_cgen(struct bf_cgen *cgen, bool unload)
451 : {
452 0 : return _bf_ctx_delete_cgen(_bf_global_ctx, cgen, unload);
453 : }
454 :
455 0 : struct bf_ns *bf_ctx_get_ns(void)
456 : {
457 0 : return &_bf_global_ctx->ns;
458 : }
459 :
460 0 : int bf_ctx_token(void)
461 : {
462 0 : return _bf_global_ctx->token_fd;
463 : }
464 :
465 0 : int bf_ctx_get_pindir_fd(void)
466 : {
467 0 : _cleanup_close_ int bpffs_fd = -1;
468 0 : _cleanup_close_ int pindir_fd = -1;
469 :
470 0 : bpffs_fd = bf_opendir(bf_opts_bpffs_path());
471 0 : if (bpffs_fd < 0) {
472 0 : return bf_err_r(bpffs_fd, "failed to open bpffs at %s",
473 : bf_opts_bpffs_path());
474 : }
475 :
476 0 : pindir_fd = bf_opendir_at(bpffs_fd, "bpfilter", true);
477 0 : if (pindir_fd < 0) {
478 0 : return bf_err_r(pindir_fd, "failed to open pin directory %s/bpfilter",
479 : bf_opts_bpffs_path());
480 : }
481 :
482 0 : return TAKE_FD(pindir_fd);
483 : }
484 :
485 0 : int bf_ctx_rm_pindir(void)
486 : {
487 0 : _cleanup_close_ int bpffs_fd = -1;
488 : int r;
489 :
490 0 : bpffs_fd = bf_opendir(bf_opts_bpffs_path());
491 0 : if (bpffs_fd < 0) {
492 0 : return bf_err_r(bpffs_fd, "failed to open bpffs at %s",
493 : bf_opts_bpffs_path());
494 : }
495 :
496 0 : r = bf_rmdir_at(bpffs_fd, "bpfilter", false);
497 0 : if (r < 0 && r != -ENOTEMPTY && r != -ENOENT)
498 0 : return bf_err_r(r, "failed to remove bpfilter bpffs directory");
499 :
500 : return 0;
501 : }
502 :
503 0 : const struct bf_elfstub *bf_ctx_get_elfstub(enum bf_elfstub_id id)
504 : {
505 0 : return _bf_global_ctx->stubs[id];
506 : }
|