LCOV - code coverage report
Current view: top level - libbpfilter - ctx.c (source / functions) Coverage Total Hit
Test: coverage.lcov Lines: 61.6 % 164 101
Test Date: 2026-05-28 10:53:42 Functions: 80.0 % 15 12
Branches: 36.5 % 148 54

             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                 :             : }
        

Generated by: LCOV version 2.0-1