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 : : #include "core/lock.h"
29 : :
30 : : #define _free_bf_ctx_ __attribute__((cleanup(_bf_ctx_free)))
31 : :
32 : : /**
33 : : * @struct bf_ctx
34 : : *
35 : : * bpfilter working context. Only one context is used during the library's
36 : : * lifetime.
37 : : */
38 : : struct bf_ctx
39 : : {
40 : : /// BPF token file descriptor
41 : : int token_fd;
42 : :
43 : : struct bf_elfstub *stubs[_BF_ELFSTUB_MAX];
44 : :
45 : : /// Pass a token to BPF system calls, obtained from bpffs.
46 : : bool with_bpf_token;
47 : :
48 : : /// Path to the bpffs to pin the BPF objects into.
49 : : const char *bpffs_path;
50 : :
51 : : /// Verbose flags.
52 : : uint16_t verbose;
53 : : };
54 : :
55 : : static void _bf_ctx_free(struct bf_ctx **ctx);
56 : :
57 : : /// Global runtime context. Hidden in this translation unit.
58 : : static struct bf_ctx *_bf_global_ctx = NULL;
59 : :
60 : 0 : static int _bf_ctx_gen_token(const char *bpffs_path)
61 : : {
62 : 0 : _cleanup_close_ int mnt_fd = -1;
63 : 0 : _cleanup_close_ int bpffs_fd = -1;
64 : 0 : _cleanup_close_ int token_fd = -1;
65 : :
66 : 0 : mnt_fd = open(bpffs_path, O_DIRECTORY);
67 [ # # ]: 0 : if (mnt_fd < 0)
68 [ # # ]: 0 : return bf_err_r(errno, "failed to open '%s'", bpffs_path);
69 : :
70 : 0 : bpffs_fd = openat(mnt_fd, ".", 0, O_RDWR);
71 [ # # ]: 0 : if (bpffs_fd < 0)
72 [ # # ]: 0 : return bf_err_r(errno, "failed to get bpffs FD from '%s'", bpffs_path);
73 : :
74 : 0 : token_fd = bf_bpf_token_create(bpffs_fd);
75 [ # # ]: 0 : if (token_fd < 0) {
76 [ # # ]: 0 : return bf_err_r(token_fd, "failed to create BPF token for '%s'",
77 : : bpffs_path);
78 : : }
79 : :
80 : 0 : return TAKE_FD(token_fd);
81 : : }
82 : :
83 : : /**
84 : : * Create and initialize a new context.
85 : : *
86 : : * On failure, @p ctx is left unchanged.
87 : : *
88 : : * @param ctx New context to create. Can't be NULL.
89 : : * @param with_bpf_token If true, create a BPF token from bpffs.
90 : : * @param bpffs_path Path to the bpffs mountpoint. Can't be NULL.
91 : : * @param verbose Bitmask of verbose flags.
92 : : * @return 0 on success, negative errno value on failure.
93 : : */
94 : 368 : static int _bf_ctx_new(struct bf_ctx **ctx, bool with_bpf_token,
95 : : const char *bpffs_path, uint16_t verbose)
96 : : {
97 : 368 : _free_bf_ctx_ struct bf_ctx *_ctx = NULL;
98 : : int r;
99 : :
100 : : assert(ctx);
101 : : assert(bpffs_path);
102 : :
103 : 368 : _ctx = calloc(1, sizeof(*_ctx));
104 [ + - ]: 368 : if (!_ctx)
105 : : return -ENOMEM;
106 : :
107 : 368 : _ctx->token_fd = -1;
108 : :
109 : 368 : r = bf_btf_setup();
110 [ - + ]: 368 : if (r)
111 [ # # ]: 0 : return bf_err_r(r, "failed to load vmlinux BTF");
112 : :
113 : 368 : _ctx->with_bpf_token = with_bpf_token;
114 : 368 : _ctx->verbose = verbose;
115 : :
116 : 368 : _ctx->bpffs_path = strdup(bpffs_path);
117 [ + - ]: 368 : if (!_ctx->bpffs_path)
118 : : return -ENOMEM;
119 : :
120 [ - + ]: 368 : 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 : : 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 [ + + ]: 2576 : for (enum bf_elfstub_id id = 0; id < _BF_ELFSTUB_MAX; ++id) {
140 : 2208 : r = bf_elfstub_new(&_ctx->stubs[id], id);
141 [ - + ]: 2208 : if (r)
142 [ # # ]: 0 : return bf_err_r(r, "failed to create ELF stub ID %u", id);
143 : : }
144 : :
145 : 368 : *ctx = TAKE_PTR(_ctx);
146 : :
147 : 368 : return 0;
148 : : }
149 : :
150 : : /**
151 : : * Free a context.
152 : : *
153 : : * If @p ctx points to a NULL pointer, this function does nothing. Once
154 : : * the function returns, @p ctx points to a NULL pointer.
155 : : *
156 : : * @param ctx Context to free. Can't be NULL.
157 : : */
158 : 736 : static void _bf_ctx_free(struct bf_ctx **ctx)
159 : : {
160 : : assert(ctx);
161 : :
162 [ + + ]: 736 : if (!*ctx)
163 : : return;
164 : :
165 : 368 : closep(&(*ctx)->token_fd);
166 : 368 : BF_FREEP(&(*ctx)->bpffs_path);
167 : :
168 [ + + ]: 2576 : for (enum bf_elfstub_id id = 0; id < _BF_ELFSTUB_MAX; ++id)
169 : 2208 : bf_elfstub_free(&(*ctx)->stubs[id]);
170 : :
171 : 368 : bf_btf_teardown();
172 : :
173 : 368 : BF_FREEP(ctx);
174 : : }
175 : :
176 : : /**
177 : : * See @ref bf_ctx_dump for details.
178 : : */
179 : 0 : static void _bf_ctx_dump(const struct bf_ctx *ctx, prefix_t *prefix)
180 : : {
181 [ # # ]: 0 : DUMP(prefix, "struct bf_ctx at %p", ctx);
182 : :
183 : 0 : bf_dump_prefix_push(prefix);
184 : :
185 [ # # ]: 0 : DUMP(prefix, "token_fd: %d", ctx->token_fd);
186 : :
187 : 0 : bf_dump_prefix_pop(prefix);
188 : 0 : }
189 : :
190 : : static void _bf_free_dir(DIR **dir)
191 : : {
192 [ + - ]: 1155 : if (!*dir)
193 : : return;
194 : :
195 : 1523 : closedir(*dir);
196 : : *dir = NULL;
197 : : }
198 : :
199 : : #define _free_dir_ __attribute__((__cleanup__(_bf_free_dir)))
200 : :
201 : : /**
202 : : * @brief Sweep leftover staging directories from a previous run.
203 : : *
204 : : * `core/lock.c` creates uniquely-named `.staging.*` directories while
205 : : * publishing new chain dirs via `renameat2(RENAME_NOREPLACE)`. If a
206 : : * process crashes between the `mkdirat` and the `renameat2`, the staging
207 : : * dir is orphaned.
208 : : *
209 : : * This function walks the pindir under `BF_LOCK_WRITE` and removes any
210 : : * `.staging.*` entry whose `flock(LOCK_EX | LOCK_NB)` succeeds (meaning
211 : : * nobody currently owns it). Live staging dirs are left alone.
212 : : *
213 : : * Runs once from `bf_ctx_setup()`. Non-fatal: a failure to sweep only
214 : : * leaves garbage behind, it does not compromise correctness.
215 : : */
216 : 368 : static void _bf_ctx_sweep_staging(void)
217 : : {
218 : 368 : _clean_bf_lock_ struct bf_lock lock = bf_lock_default();
219 : : _free_dir_ DIR *dir = NULL;
220 : : struct dirent *entry;
221 : : int iter_fd;
222 : : int r;
223 : :
224 : 368 : r = bf_lock_init(&lock, BF_LOCK_WRITE);
225 [ - + ]: 368 : if (r) {
226 [ # # ]: 0 : bf_warn_r(r, "failed to lock pindir for staging sweep, skipping");
227 : : return;
228 : : }
229 : :
230 : 368 : iter_fd = dup(lock.pindir_fd);
231 [ - + ]: 368 : if (iter_fd < 0) {
232 [ # # ]: 0 : bf_warn_r(-errno, "failed to dup pindir fd for staging sweep");
233 : : return;
234 : : }
235 : :
236 : 368 : dir = fdopendir(iter_fd);
237 [ - + ]: 368 : if (!dir) {
238 : 0 : close(iter_fd);
239 [ # # ]: 0 : bf_warn_r(-errno, "failed to fdopendir pindir for staging sweep");
240 : : return;
241 : : }
242 : :
243 [ + + ]: 1450 : while ((entry = readdir(dir))) {
244 : 1082 : _cleanup_close_ int stage_fd = -1;
245 : :
246 [ + - ]: 1082 : if (!bf_strneq(entry->d_name, BF_LOCK_STAGING_PREFIX,
247 : : sizeof(BF_LOCK_STAGING_PREFIX) - 1))
248 : 1082 : continue;
249 : :
250 [ # # ]: 0 : if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN)
251 : 0 : continue;
252 : :
253 : 0 : stage_fd = openat(lock.pindir_fd, entry->d_name, O_DIRECTORY);
254 [ # # ]: 0 : if (stage_fd < 0)
255 : 0 : continue;
256 : :
257 : : /* LOCK_NB: if the staging dir is still live, skip it. */
258 [ # # ]: 0 : if (flock(stage_fd, LOCK_EX | LOCK_NB) < 0)
259 : 0 : continue;
260 : :
261 [ # # ]: 0 : if (bf_rmdir_at(lock.pindir_fd, entry->d_name, true)) {
262 [ # # ]: 0 : bf_warn("failed to sweep orphan staging dir '%s'", entry->d_name);
263 : : } else {
264 [ # # ]: 0 : bf_dbg("removed left-over staging directory '%s'", entry->d_name);
265 : : }
266 : : }
267 : : }
268 : :
269 : 369 : int bf_ctx_setup(bool with_bpf_token, const char *bpffs_path, uint16_t verbose)
270 : : {
271 : : int r;
272 : :
273 [ + + ]: 369 : if (_bf_global_ctx)
274 [ + - ]: 1 : return bf_err_r(-EBUSY, "context is already initialized");
275 : :
276 : 368 : r = _bf_ctx_new(&_bf_global_ctx, with_bpf_token, bpffs_path, verbose);
277 [ - + ]: 368 : if (r)
278 [ # # ]: 0 : return bf_err_r(r, "failed to create new context");
279 : :
280 : : /* Reclaim any orphan staging directory left by a previous crash. */
281 : 368 : _bf_ctx_sweep_staging();
282 : :
283 : 368 : return 0;
284 : : }
285 : :
286 : 368 : void bf_ctx_teardown(void)
287 : : {
288 : 368 : _bf_ctx_free(&_bf_global_ctx);
289 : 368 : }
290 : :
291 : 3 : bool bf_ctx_is_setup(void)
292 : : {
293 : 3 : return _bf_global_ctx != NULL;
294 : : }
295 : :
296 : 0 : void bf_ctx_dump(prefix_t *prefix)
297 : : {
298 [ # # ]: 0 : if (!_bf_global_ctx)
299 : : return;
300 : :
301 : 0 : _bf_ctx_dump(_bf_global_ctx, prefix);
302 : : }
303 : :
304 : 4728 : int bf_ctx_get_cgen(struct bf_lock *lock, struct bf_cgen **cgen)
305 : : {
306 : 4728 : _free_bf_cgen_ struct bf_cgen *_cgen = NULL;
307 : : int r;
308 : :
309 : : assert(lock);
310 : : assert(cgen);
311 : :
312 [ - + ]: 4728 : if (!_bf_global_ctx)
313 [ # # ]: 0 : return bf_err_r(-EINVAL, "context is not initialized");
314 : :
315 : 4728 : r = bf_cgen_new_from_dir_fd(&_cgen, lock);
316 [ + + ]: 4728 : if (r)
317 [ + - + - ]: 2 : return bf_err_r(r, "failed to load chain '%s' from bpffs",
318 : : lock->chain_name ? lock->chain_name : "(unknown)");
319 : :
320 : 4727 : *cgen = TAKE_PTR(_cgen);
321 : :
322 : 4727 : return 0;
323 : : }
324 : :
325 : 1155 : int bf_ctx_get_cgens(struct bf_lock *lock, bf_list **cgens)
326 : : {
327 : 1155 : _free_bf_list_ bf_list *_cgens = NULL;
328 : : _free_dir_ DIR *dir = NULL;
329 : : struct dirent *entry;
330 : : int iter_fd;
331 : : int r;
332 : :
333 : : assert(lock);
334 : : assert(cgens);
335 : :
336 [ - + ]: 1155 : if (!_bf_global_ctx)
337 [ # # ]: 0 : return bf_err_r(-EINVAL, "context is not initialized");
338 : :
339 : 1155 : r = bf_list_new(&_cgens, &bf_list_ops_default(bf_cgen_free, bf_cgen_pack));
340 [ - + ]: 1155 : if (r)
341 [ # # ]: 0 : return bf_err_r(r, "failed to allocate cgen list");
342 : :
343 : : /* fdopendir() takes ownership of the fd: dup so lock->pindir_fd remains
344 : : * valid for further uses. */
345 : 1155 : iter_fd = dup(lock->pindir_fd);
346 [ - + ]: 1155 : if (iter_fd < 0)
347 [ # # ]: 0 : return bf_err_r(-errno, "failed to dup pin directory fd");
348 : :
349 : 1155 : dir = fdopendir(iter_fd);
350 [ - + ]: 1155 : if (!dir) {
351 : 0 : r = -errno;
352 : 0 : close(iter_fd);
353 [ # # ]: 0 : return bf_err_r(r, "failed to open pin directory for iteration");
354 : : }
355 : :
356 : : while (true) {
357 : 4065 : _free_bf_cgen_ struct bf_cgen *cgen = NULL;
358 : :
359 : 4065 : errno = 0;
360 : 4065 : entry = readdir(dir);
361 [ + + - + ]: 4065 : if (!entry && errno != 0) {
362 [ # # ]: 0 : bf_warn_r(errno, "readdir failed, returning partial results");
363 : : break;
364 : : }
365 : : if (!entry)
366 : : break;
367 : :
368 [ + + + + ]: 2910 : if (bf_streq(entry->d_name, ".") || bf_streq(entry->d_name, ".."))
369 : 2306 : continue;
370 : :
371 [ + + ]: 604 : if (entry->d_type != DT_DIR)
372 : 1 : continue;
373 : :
374 : : /* Skip in-flight staging directories owned by concurrent writers. */
375 [ - + ]: 603 : if (bf_strneq(entry->d_name, BF_LOCK_STAGING_PREFIX,
376 : : sizeof(BF_LOCK_STAGING_PREFIX) - 1))
377 : 0 : continue;
378 : :
379 : 603 : r = bf_lock_acquire_chain(lock, entry->d_name, BF_LOCK_READ, false);
380 [ - + ]: 603 : if (r) {
381 [ # # ]: 0 : bf_warn_r(r, "failed to acquire READ lock on chain '%s', skipping",
382 : : entry->d_name);
383 : 0 : continue;
384 : : }
385 : :
386 : 603 : r = bf_cgen_new_from_dir_fd(&cgen, lock);
387 [ + + ]: 603 : if (r) {
388 [ + - ]: 2 : bf_warn_r(r, "failed to restore chain '%s', skipping",
389 : : entry->d_name);
390 : 2 : bf_lock_release_chain(lock);
391 : 2 : continue;
392 : : }
393 : :
394 : 601 : bf_lock_release_chain(lock);
395 : :
396 : 601 : r = bf_list_push(_cgens, (void **)&cgen);
397 [ - + ]: 601 : if (r) {
398 [ # # ]: 0 : bf_warn_r(r, "failed to push chain '%s' to list, skipping",
399 : : entry->d_name);
400 : 0 : continue;
401 : : }
402 : : }
403 : :
404 : 1155 : *cgens = TAKE_PTR(_cgens);
405 : :
406 : 1155 : return 0;
407 : : }
408 : :
409 : 6896 : int bf_ctx_token(void)
410 : : {
411 [ + + ]: 6896 : if (!_bf_global_ctx)
412 : : return -1;
413 : :
414 : 6895 : return _bf_global_ctx->token_fd;
415 : : }
416 : :
417 : 2609 : const struct bf_elfstub *bf_ctx_get_elfstub(enum bf_elfstub_id id)
418 : : {
419 [ + + ]: 2609 : if (!_bf_global_ctx)
420 : : return NULL;
421 : :
422 : 2608 : return _bf_global_ctx->stubs[id];
423 : : }
424 : :
425 : 6810 : bool bf_ctx_is_verbose(enum bf_verbose opt)
426 : : {
427 [ + + ]: 6810 : if (!_bf_global_ctx)
428 : : return false;
429 : :
430 : 6809 : return _bf_global_ctx->verbose & BF_FLAG(opt);
431 : : }
432 : :
433 : 6955 : const char *bf_ctx_get_bpffs_path(void)
434 : : {
435 [ + + ]: 6955 : if (!_bf_global_ctx)
436 : : return NULL;
437 : :
438 : 6953 : return _bf_global_ctx->bpffs_path;
439 : : }
|