LCOV - code coverage report
Current view: top level - libbpfilter - ctx.c (source / functions) Coverage Total Hit
Test: coverage.lcov Lines: 59.1 % 159 94
Test Date: 2026-05-09 09:49:18 Functions: 78.6 % 14 11
Branches: 35.2 % 142 50

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

Generated by: LCOV version 2.0-1