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 : 360 : static int _bf_ctx_new(struct bf_ctx **ctx, bool with_bpf_token,
95 : : const char *bpffs_path, uint16_t verbose)
96 : : {
97 : 360 : _free_bf_ctx_ struct bf_ctx *_ctx = NULL;
98 : : int r;
99 : :
100 : : assert(ctx);
101 : : assert(bpffs_path);
102 : :
103 : 360 : _ctx = calloc(1, sizeof(*_ctx));
104 [ + - ]: 360 : if (!_ctx)
105 : : return -ENOMEM;
106 : :
107 : 360 : r = bf_btf_setup();
108 [ - + ]: 360 : if (r)
109 [ # # ]: 0 : return bf_err_r(r, "failed to load vmlinux BTF");
110 : :
111 : 360 : _ctx->with_bpf_token = with_bpf_token;
112 : 360 : _ctx->bpffs_path = bpffs_path;
113 : 360 : _ctx->verbose = verbose;
114 : :
115 : 360 : _ctx->token_fd = -1;
116 [ - + ]: 360 : if (_ctx->with_bpf_token) {
117 : 0 : _cleanup_close_ int token_fd = -1;
118 : :
119 : 0 : r = bf_btf_kernel_has_token();
120 [ # # ]: 0 : if (r == -ENOENT) {
121 [ # # ]: 0 : bf_err(
122 : : "--with-bpf-token requested, but this kernel doesn't support BPF token");
123 : 0 : return r;
124 : : }
125 [ # # ]: 0 : if (r)
126 [ # # ]: 0 : return bf_err_r(r, "failed to check for BPF token support");
127 : :
128 : 0 : token_fd = _bf_ctx_gen_token(_ctx->bpffs_path);
129 [ # # ]: 0 : if (token_fd < 0)
130 [ # # ]: 0 : return bf_err_r(token_fd, "failed to generate a BPF token");
131 : :
132 : 0 : _ctx->token_fd = TAKE_FD(token_fd);
133 : : }
134 : :
135 [ + + ]: 2520 : for (enum bf_elfstub_id id = 0; id < _BF_ELFSTUB_MAX; ++id) {
136 : 2160 : r = bf_elfstub_new(&_ctx->stubs[id], id);
137 [ - + ]: 2160 : if (r)
138 [ # # ]: 0 : return bf_err_r(r, "failed to create ELF stub ID %u", id);
139 : : }
140 : :
141 : 360 : *ctx = TAKE_PTR(_ctx);
142 : :
143 : 360 : return 0;
144 : : }
145 : :
146 : : /**
147 : : * Free a context.
148 : : *
149 : : * If @p ctx points to a NULL pointer, this function does nothing. Once
150 : : * the function returns, @p ctx points to a NULL pointer.
151 : : *
152 : : * @param ctx Context to free. Can't be NULL.
153 : : */
154 : 720 : static void _bf_ctx_free(struct bf_ctx **ctx)
155 : : {
156 : : assert(ctx);
157 : :
158 [ + + ]: 720 : if (!*ctx)
159 : : return;
160 : :
161 : 360 : closep(&(*ctx)->token_fd);
162 : :
163 [ + + ]: 2520 : for (enum bf_elfstub_id id = 0; id < _BF_ELFSTUB_MAX; ++id)
164 : 2160 : bf_elfstub_free(&(*ctx)->stubs[id]);
165 : :
166 : 360 : bf_btf_teardown();
167 : :
168 : : freep((void *)ctx);
169 : : }
170 : :
171 : : /**
172 : : * See @ref bf_ctx_dump for details.
173 : : */
174 : 0 : static void _bf_ctx_dump(const struct bf_ctx *ctx, prefix_t *prefix)
175 : : {
176 [ # # ]: 0 : DUMP(prefix, "struct bf_ctx at %p", ctx);
177 : :
178 : 0 : bf_dump_prefix_push(prefix);
179 : :
180 [ # # ]: 0 : DUMP(prefix, "token_fd: %d", ctx->token_fd);
181 : :
182 : 0 : bf_dump_prefix_pop(prefix);
183 : 0 : }
184 : :
185 : : static void _bf_free_dir(DIR **dir)
186 : : {
187 [ - + ]: 1155 : if (!*dir)
188 : : return;
189 : :
190 : 1515 : closedir(*dir);
191 : : *dir = NULL;
192 : : }
193 : :
194 : : #define _free_dir_ __attribute__((__cleanup__(_bf_free_dir)))
195 : :
196 : : /**
197 : : * @brief Sweep leftover staging directories from a previous run.
198 : : *
199 : : * `core/lock.c` creates uniquely-named `.staging.*` directories while
200 : : * publishing new chain dirs via `renameat2(RENAME_NOREPLACE)`. If a
201 : : * process crashes between the `mkdirat` and the `renameat2`, the staging
202 : : * dir is orphaned.
203 : : *
204 : : * This function walks the pindir under `BF_LOCK_WRITE` and removes any
205 : : * `.staging.*` entry whose `flock(LOCK_EX | LOCK_NB)` succeeds (meaning
206 : : * nobody currently owns it). Live staging dirs are left alone.
207 : : *
208 : : * Runs once from `bf_ctx_setup()`. Non-fatal: a failure to sweep only
209 : : * leaves garbage behind, it does not compromise correctness.
210 : : */
211 : 360 : static void _bf_ctx_sweep_staging(void)
212 : : {
213 : 360 : _clean_bf_lock_ struct bf_lock lock = bf_lock_default();
214 : : _free_dir_ DIR *dir = NULL;
215 : : struct dirent *entry;
216 : : int iter_fd;
217 : : int r;
218 : :
219 : 360 : r = bf_lock_init(&lock, BF_LOCK_WRITE);
220 [ - + ]: 360 : if (r) {
221 [ # # ]: 0 : bf_warn_r(r, "failed to lock pindir for staging sweep, skipping");
222 : : return;
223 : : }
224 : :
225 : 360 : iter_fd = dup(lock.pindir_fd);
226 [ - + ]: 360 : if (iter_fd < 0) {
227 [ # # ]: 0 : bf_warn_r(-errno, "failed to dup pindir fd for staging sweep");
228 : : return;
229 : : }
230 : :
231 : 360 : dir = fdopendir(iter_fd);
232 [ - + ]: 360 : if (!dir) {
233 : 0 : close(iter_fd);
234 [ # # ]: 0 : bf_warn_r(-errno, "failed to fdopendir pindir for staging sweep");
235 : : return;
236 : : }
237 : :
238 [ + + ]: 1421 : while ((entry = readdir(dir))) {
239 : 1061 : _cleanup_close_ int stage_fd = -1;
240 : :
241 [ + - ]: 1061 : if (!bf_strneq(entry->d_name, BF_LOCK_STAGING_PREFIX,
242 : : sizeof(BF_LOCK_STAGING_PREFIX) - 1))
243 : 1061 : continue;
244 : :
245 [ # # ]: 0 : if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN)
246 : 0 : continue;
247 : :
248 : 0 : stage_fd = openat(lock.pindir_fd, entry->d_name, O_DIRECTORY);
249 [ # # ]: 0 : if (stage_fd < 0)
250 : 0 : continue;
251 : :
252 : : /* LOCK_NB: if the staging dir is still live, skip it. */
253 [ # # ]: 0 : if (flock(stage_fd, LOCK_EX | LOCK_NB) < 0)
254 : 0 : continue;
255 : :
256 [ # # ]: 0 : if (bf_rmdir_at(lock.pindir_fd, entry->d_name, true)) {
257 [ # # ]: 0 : bf_warn("failed to sweep orphan staging dir '%s'", entry->d_name);
258 : : } else {
259 [ # # ]: 0 : bf_dbg("removed left-over staging directory '%s'", entry->d_name);
260 : : }
261 : : }
262 : : }
263 : :
264 : 360 : int bf_ctx_setup(bool with_bpf_token, const char *bpffs_path, uint16_t verbose)
265 : : {
266 : : int r;
267 : :
268 : 360 : r = _bf_ctx_new(&_bf_global_ctx, with_bpf_token, bpffs_path, verbose);
269 [ - + ]: 360 : if (r)
270 [ # # ]: 0 : return bf_err_r(r, "failed to create new context");
271 : :
272 : : /* Reclaim any orphan staging directory left by a previous crash. */
273 : 360 : _bf_ctx_sweep_staging();
274 : :
275 : 360 : return 0;
276 : : }
277 : :
278 : 360 : void bf_ctx_teardown(void)
279 : : {
280 : 360 : _bf_ctx_free(&_bf_global_ctx);
281 : 360 : }
282 : :
283 : 0 : void bf_ctx_dump(prefix_t *prefix)
284 : : {
285 [ # # ]: 0 : if (!_bf_global_ctx)
286 : : return;
287 : :
288 : 0 : _bf_ctx_dump(_bf_global_ctx, prefix);
289 : : }
290 : :
291 : 4727 : int bf_ctx_get_cgen(struct bf_lock *lock, struct bf_cgen **cgen)
292 : : {
293 : 4727 : _free_bf_cgen_ struct bf_cgen *_cgen = NULL;
294 : : int r;
295 : :
296 : : assert(lock);
297 : : assert(cgen);
298 : :
299 [ - + ]: 4727 : if (!_bf_global_ctx)
300 [ # # ]: 0 : return bf_err_r(-EINVAL, "context is not initialized");
301 : :
302 : 4727 : r = bf_cgen_new_from_dir_fd(&_cgen, lock);
303 [ + + ]: 4727 : if (r)
304 [ + - + - ]: 2 : return bf_err_r(r, "failed to load chain '%s' from bpffs",
305 : : lock->chain_name ? lock->chain_name : "(unknown)");
306 : :
307 : 4726 : *cgen = TAKE_PTR(_cgen);
308 : :
309 : 4726 : return 0;
310 : : }
311 : :
312 : 1155 : int bf_ctx_get_cgens(struct bf_lock *lock, bf_list **cgens)
313 : : {
314 : 1155 : _free_bf_list_ bf_list *_cgens = NULL;
315 : : _free_dir_ DIR *dir = NULL;
316 : : struct dirent *entry;
317 : : int iter_fd;
318 : : int r;
319 : :
320 : : assert(lock);
321 : : assert(cgens);
322 : :
323 [ - + ]: 1155 : if (!_bf_global_ctx)
324 [ # # ]: 0 : return bf_err_r(-EINVAL, "context is not initialized");
325 : :
326 : 1155 : r = bf_list_new(&_cgens, &bf_list_ops_default(bf_cgen_free, bf_cgen_pack));
327 [ - + ]: 1155 : if (r)
328 [ # # ]: 0 : return bf_err_r(r, "failed to allocate cgen list");
329 : :
330 : : /* fdopendir() takes ownership of the fd: dup so lock->pindir_fd remains
331 : : * valid for further uses. */
332 : 1155 : iter_fd = dup(lock->pindir_fd);
333 [ - + ]: 1155 : if (iter_fd < 0)
334 [ # # ]: 0 : return bf_err_r(-errno, "failed to dup pin directory fd");
335 : :
336 : 1155 : dir = fdopendir(iter_fd);
337 [ - + ]: 1155 : if (!dir) {
338 : 0 : r = -errno;
339 : 0 : close(iter_fd);
340 [ # # ]: 0 : return bf_err_r(r, "failed to open pin directory for iteration");
341 : : }
342 : :
343 : : while (true) {
344 : 4065 : _free_bf_cgen_ struct bf_cgen *cgen = NULL;
345 : :
346 : 4065 : errno = 0;
347 : 4065 : entry = readdir(dir);
348 [ + + - + ]: 4065 : if (!entry && errno != 0) {
349 [ # # ]: 0 : bf_warn_r(errno, "readdir failed, returning partial results");
350 : 0 : break;
351 : : }
352 : : if (!entry)
353 : : break;
354 : :
355 [ + + + + ]: 2910 : if (bf_streq(entry->d_name, ".") || bf_streq(entry->d_name, ".."))
356 : 2306 : continue;
357 : :
358 [ + + ]: 604 : if (entry->d_type != DT_DIR)
359 : 1 : continue;
360 : :
361 : : /* Skip in-flight staging directories owned by concurrent writers. */
362 [ - + ]: 603 : if (bf_strneq(entry->d_name, BF_LOCK_STAGING_PREFIX,
363 : : sizeof(BF_LOCK_STAGING_PREFIX) - 1))
364 : 0 : continue;
365 : :
366 : 603 : r = bf_lock_acquire_chain(lock, entry->d_name, BF_LOCK_READ, false);
367 [ - + ]: 603 : if (r) {
368 [ # # ]: 0 : bf_warn_r(r, "failed to acquire READ lock on chain '%s', skipping",
369 : : entry->d_name);
370 : 0 : continue;
371 : : }
372 : :
373 : 603 : r = bf_cgen_new_from_dir_fd(&cgen, lock);
374 [ + + ]: 603 : if (r) {
375 [ + - ]: 2 : bf_warn_r(r, "failed to restore chain '%s', skipping",
376 : : entry->d_name);
377 : 2 : bf_lock_release_chain(lock);
378 : 2 : continue;
379 : : }
380 : :
381 : 601 : bf_lock_release_chain(lock);
382 : :
383 : 601 : r = bf_list_push(_cgens, (void **)&cgen);
384 [ - + ]: 601 : if (r) {
385 [ # # ]: 0 : bf_warn_r(r, "failed to push chain '%s' to list, skipping",
386 : : entry->d_name);
387 : 0 : continue;
388 : : }
389 : : }
390 : :
391 : 1155 : *cgens = TAKE_PTR(_cgens);
392 : :
393 : 1155 : return 0;
394 : : }
395 : :
396 : 6889 : int bf_ctx_token(void)
397 : : {
398 [ + + ]: 6889 : if (!_bf_global_ctx)
399 : : return -1;
400 : :
401 : 6888 : return _bf_global_ctx->token_fd;
402 : : }
403 : :
404 : 2607 : const struct bf_elfstub *bf_ctx_get_elfstub(enum bf_elfstub_id id)
405 : : {
406 [ + + ]: 2607 : if (!_bf_global_ctx)
407 : : return NULL;
408 : :
409 : 2606 : return _bf_global_ctx->stubs[id];
410 : : }
411 : :
412 : 6805 : bool bf_ctx_is_verbose(enum bf_verbose opt)
413 : : {
414 [ + + ]: 6805 : if (!_bf_global_ctx)
415 : : return false;
416 : :
417 : 6804 : return _bf_global_ctx->verbose & BF_FLAG(opt);
418 : : }
419 : :
420 : 6940 : const char *bf_ctx_get_bpffs_path(void)
421 : : {
422 [ + + ]: 6940 : if (!_bf_global_ctx)
423 : : return NULL;
424 : :
425 : 6939 : return _bf_global_ctx->bpffs_path;
426 : : }
|