LCOV - code coverage report
Current view: top level - libbpfilter - io.c (source / functions) Coverage Total Hit
Test: coverage.lcov Lines: 82.8 % 169 140
Test Date: 2025-11-24 12:34:34 Functions: 100.0 % 15 15
Branches: 43.6 % 156 68

             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 "bpfilter/io.h"
       7                 :             : 
       8                 :             : #include <dirent.h>
       9                 :             : #include <errno.h>
      10                 :             : #include <fcntl.h>
      11                 :             : #include <stdio.h>
      12                 :             : #include <stdlib.h>
      13                 :             : #include <string.h>
      14                 :             : #include <sys/file.h>
      15                 :             : #include <sys/socket.h>
      16                 :             : #include <sys/stat.h>
      17                 :             : #include <sys/un.h>
      18                 :             : #include <unistd.h>
      19                 :             : 
      20                 :             : #include "bpfilter/dynbuf.h"
      21                 :             : #include "bpfilter/helper.h"
      22                 :             : #include "bpfilter/logger.h"
      23                 :             : #include "bpfilter/request.h"
      24                 :             : #include "bpfilter/response.h"
      25                 :             : 
      26                 :             : #define BF_PERM_755 (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)
      27                 :             : #define BF_MSG_BUF_SIZE 1024U
      28                 :             : 
      29                 :         208 : static int _bf_recv_in_buff(int fd, struct bf_dynbuf *buf)
      30                 :             : {
      31                 :         208 :     size_t remaining = 1;
      32                 :             : 
      33                 :             :     bf_assert(buf);
      34                 :             : 
      35         [ +  + ]:         466 :     while (remaining > 0) {
      36                 :             :         ssize_t r;
      37                 :             :         uint8_t tmpbuf[BF_MSG_BUF_SIZE];
      38                 :             : 
      39                 :         258 :         struct iovec iov[2] = {
      40                 :             :             {
      41                 :             :                 .iov_base = &remaining,
      42                 :             :                 .iov_len = sizeof(remaining),
      43                 :             :             },
      44                 :             :             {
      45                 :             :                 .iov_base = tmpbuf,
      46                 :             :                 .iov_len = BF_MSG_BUF_SIZE,
      47                 :             :             },
      48                 :             :         };
      49                 :             : 
      50                 :         258 :         struct msghdr msg = {
      51                 :             :             .msg_iov = iov,
      52                 :             :             .msg_iovlen = ARRAY_SIZE(iov),
      53                 :             :             .msg_name = NULL,
      54                 :             :             .msg_namelen = 0,
      55                 :             :             .msg_control = NULL,
      56                 :             :             .msg_controllen = 0,
      57                 :             :         };
      58                 :             : 
      59                 :         258 :         r = recvmsg(fd, &msg, 0);
      60         [ -  + ]:         258 :         if (r < 0)
      61         [ #  # ]:           0 :             return bf_err_r(-errno, "failed to receive data");
      62         [ -  + ]:         258 :         if ((size_t)r < sizeof(remaining))
      63         [ #  # ]:           0 :             return bf_err_r(-EIO, "received partial data");
      64                 :             : 
      65                 :         258 :         r = bf_dynbuf_write(buf, tmpbuf, r - sizeof(remaining));
      66         [ -  + ]:         258 :         if (r) {
      67         [ #  # ]:           0 :             return bf_err_r((int)r,
      68                 :             :                             "failed to write received data to dynamic buffer");
      69                 :             :         }
      70                 :             :     }
      71                 :             : 
      72                 :             :     return 0;
      73                 :             : }
      74                 :             : 
      75                 :         208 : static int _bf_send_from_buff(int fd, void *buf, size_t buf_len)
      76                 :             : {
      77                 :             :     size_t sent = 0;
      78                 :             : 
      79                 :             :     bf_assert(buf);
      80                 :             : 
      81         [ +  + ]:         466 :     while (buf_len > 0) {
      82                 :         258 :         size_t send_size = bf_min(BF_MSG_BUF_SIZE, buf_len);
      83                 :             :         ssize_t r;
      84                 :         258 :         size_t rem = buf_len - send_size;
      85                 :             : 
      86                 :         258 :         struct iovec iov[2] = {
      87                 :             :             {
      88                 :             :                 .iov_base = &rem,
      89                 :             :                 .iov_len = sizeof(buf_len),
      90                 :             :             },
      91                 :             :             {
      92                 :         258 :                 .iov_base = buf + sent,
      93                 :             :                 .iov_len = send_size,
      94                 :             :             },
      95                 :             :         };
      96                 :             : 
      97                 :         258 :         struct msghdr msg = {
      98                 :             :             .msg_iov = iov,
      99                 :             :             .msg_iovlen = ARRAY_SIZE(iov),
     100                 :             :             .msg_name = NULL,
     101                 :             :             .msg_namelen = 0,
     102                 :             :             .msg_control = NULL,
     103                 :             :             .msg_controllen = 0,
     104                 :             :         };
     105                 :             : 
     106                 :         258 :         r = sendmsg(fd, &msg, MSG_NOSIGNAL);
     107         [ -  + ]:         258 :         if (r < 0)
     108         [ #  # ]:           0 :             return bf_err_r(-errno, "failed to send data from buff");
     109         [ -  + ]:         258 :         if ((size_t)r != send_size + sizeof(buf_len))
     110         [ #  # ]:           0 :             return bf_err_r(-EIO, "sent partial data");
     111                 :             : 
     112                 :         258 :         sent += (size_t)r - sizeof(buf_len);
     113                 :         258 :         buf_len -= (size_t)r - sizeof(buf_len);
     114                 :             :     }
     115                 :             : 
     116                 :             :     return 0;
     117                 :             : }
     118                 :             : 
     119                 :             : /**
     120                 :             :  * Send a request to the given file descriptor.
     121                 :             :  *
     122                 :             :  * @param fd File descriptor to send the request to. Must be a valid file
     123                 :             :  *        descriptor.
     124                 :             :  * @param request Request to send. Can't be NULL.
     125                 :             :  * @return 0 on success, negative error code on failure.
     126                 :             :  */
     127                 :         104 : static int _bf_send_request(int fd, const struct bf_request *request)
     128                 :             : {
     129                 :             :     int r;
     130                 :             : 
     131                 :             :     bf_assert(request);
     132                 :             : 
     133                 :         104 :     r = _bf_send_from_buff(fd, (void *)request, bf_request_size(request));
     134         [ -  + ]:         104 :     if (r < 0)
     135         [ #  # ]:           0 :         return bf_err_r(r, "failed to send request");
     136                 :             : 
     137                 :             :     return 0;
     138                 :             : }
     139                 :             : 
     140                 :         104 : int bf_recv_request(int fd, struct bf_request **request)
     141                 :             : {
     142                 :         104 :     _clean_bf_dynbuf_ struct bf_dynbuf dynbuf = bf_dynbuf_default();
     143                 :             :     int r;
     144                 :             : 
     145                 :             :     bf_assert(request);
     146                 :             : 
     147                 :         104 :     r = _bf_recv_in_buff(fd, &dynbuf);
     148         [ -  + ]:         104 :     if (r)
     149         [ #  # ]:           0 :         return bf_err_r(r, "failed to receive request");
     150                 :             : 
     151                 :         104 :     r = bf_request_new_from_dynbuf(request, &dynbuf);
     152         [ -  + ]:         104 :     if (r)
     153         [ #  # ]:           0 :         return bf_err_r((int)r, "failed to create request from buffer");
     154                 :             : 
     155                 :             :     return 0;
     156                 :             : }
     157                 :             : 
     158                 :         104 : int bf_send_response(int fd, struct bf_response *response)
     159                 :             : {
     160                 :             :     int r;
     161                 :             : 
     162                 :             :     bf_assert(response);
     163                 :             : 
     164                 :         104 :     r = _bf_send_from_buff(fd, (void *)response, bf_response_size(response));
     165         [ -  + ]:         104 :     if (r < 0)
     166         [ #  # ]:           0 :         return bf_err_r(r, "failed to send response");
     167                 :             : 
     168                 :             :     return 0;
     169                 :             : }
     170                 :             : 
     171                 :             : /**
     172                 :             :  * Received a response from the file descriptor.
     173                 :             :  *
     174                 :             :  * @param fd File descriptor to receive the response from. Must be a valid file
     175                 :             :  *        descriptor.
     176                 :             :  * @param response Response to receive. Can't be NULL. Will be allocated by the
     177                 :             :  *        function.
     178                 :             :  * @return 0 on success, negative error code on failure.
     179                 :             :  */
     180                 :         104 : static int _bf_recv_response(int fd, struct bf_response **response)
     181                 :             : {
     182                 :         104 :     _clean_bf_dynbuf_ struct bf_dynbuf dynbuf = bf_dynbuf_default();
     183                 :             :     int r;
     184                 :             : 
     185                 :             :     bf_assert(response);
     186                 :             : 
     187                 :         104 :     r = _bf_recv_in_buff(fd, &dynbuf);
     188         [ -  + ]:         104 :     if (r)
     189         [ #  # ]:           0 :         return bf_err_r((int)r, "failed to receive response");
     190                 :             : 
     191                 :         104 :     r = bf_response_new_from_dynbuf(response, &dynbuf);
     192         [ -  + ]:         104 :     if (r)
     193         [ #  # ]:           0 :         return bf_err_r((int)r, "failed to create response from buffer");
     194                 :             : 
     195                 :             :     return 0;
     196                 :             : }
     197                 :             : 
     198                 :          24 : int bf_ensure_dir(const char *dir)
     199                 :             : {
     200                 :             :     struct stat stats;
     201                 :             :     int r;
     202                 :             : 
     203                 :             :     bf_assert(dir);
     204                 :             : 
     205                 :          24 :     r = access(dir, R_OK | W_OK);
     206   [ +  +  +  - ]:          24 :     if (r && errno == ENOENT) {
     207         [ +  - ]:          17 :         if (mkdir(dir, BF_PERM_755) == 0)
     208                 :             :             return 0;
     209                 :             : 
     210         [ #  # ]:           0 :         return bf_err_r(errno, "failed to create directory '%s'", dir);
     211                 :             :     }
     212                 :             :     if (r)
     213         [ #  # ]:           0 :         return bf_err_r(errno, "no R/W permissions on '%s'", dir);
     214                 :             : 
     215   [ +  -  +  + ]:           7 :     if (stat(dir, &stats) != 0 || !S_ISDIR(stats.st_mode))
     216         [ +  - ]:           1 :         return bf_err_r(-EINVAL, "'%s' is not a valid directory", dir);
     217                 :             : 
     218                 :             :     return 0;
     219                 :             : }
     220                 :             : 
     221                 :         187 : int bf_opendir(const char *path)
     222                 :             : {
     223                 :         187 :     _cleanup_close_ int fd = -1;
     224                 :             : 
     225                 :             :     bf_assert(path);
     226                 :             : 
     227                 :         187 :     fd = open(path, O_DIRECTORY);
     228         [ +  + ]:         187 :     if (fd < 0)
     229                 :           1 :         return -errno;
     230                 :             : 
     231                 :         186 :     return TAKE_FD(fd);
     232                 :             : }
     233                 :             : 
     234                 :         276 : int bf_opendir_at(int parent_fd, const char *dir_name, bool mkdir_if_missing)
     235                 :             : {
     236                 :         552 :     _cleanup_close_ int fd = -1;
     237                 :             :     int r;
     238                 :             : 
     239                 :             :     bf_assert(dir_name);
     240                 :             : 
     241                 :         349 : retry:
     242                 :         349 :     fd = openat(parent_fd, dir_name, O_DIRECTORY);
     243         [ +  + ]:         349 :     if (fd < 0) {
     244   [ +  -  +  + ]:          74 :         if (errno != ENOENT || !mkdir_if_missing)
     245                 :           1 :             return -errno;
     246                 :             : 
     247                 :          73 :         r = mkdirat(parent_fd, dir_name, BF_PERM_755);
     248         [ -  + ]:          73 :         if (r)
     249                 :           0 :             return -errno;
     250                 :             : 
     251                 :          73 :         goto retry;
     252                 :             :     }
     253                 :             : 
     254                 :         275 :     return TAKE_FD(fd);
     255                 :             : }
     256                 :             : 
     257                 :             : static void bf_free_dir(DIR **dir)
     258                 :             : {
     259         [ #  # ]:           0 :     if (!*dir)
     260                 :             :         return;
     261                 :             : 
     262                 :           2 :     closedir(*dir);
     263                 :             :     *dir = NULL;
     264                 :             : }
     265                 :             : 
     266                 :             : #define _free_dir_ __attribute__((__cleanup__(bf_free_dir)))
     267                 :             : 
     268                 :          83 : int bf_rmdir_at(int parent_fd, const char *dir_name, bool recursive)
     269                 :             : {
     270                 :             :     int r;
     271                 :             : 
     272                 :             :     bf_assert(dir_name);
     273                 :             : 
     274         [ +  + ]:          83 :     if (recursive) {
     275                 :           3 :         _cleanup_close_ int child_fd = -1;
     276                 :             :         _free_dir_ DIR *dir = NULL;
     277                 :             :         struct dirent *entry;
     278                 :             : 
     279                 :           3 :         child_fd = openat(parent_fd, dir_name, O_DIRECTORY);
     280         [ +  + ]:           3 :         if (child_fd < 0) {
     281         [ +  - ]:           1 :             return bf_err_r(errno,
     282                 :             :                             "failed to open child directory for removal");
     283                 :             :         }
     284                 :             : 
     285                 :           2 :         dir = fdopendir(child_fd);
     286         [ -  + ]:           2 :         if (!dir)
     287         [ #  # ]:           0 :             return bf_err_r(errno, "failed to open DIR from file descriptor");
     288                 :             : 
     289         [ +  + ]:           7 :         while ((entry = readdir(dir))) {
     290                 :             :             struct stat stat;
     291                 :             : 
     292   [ +  +  +  + ]:           5 :             if (bf_streq(entry->d_name, ".") || bf_streq(entry->d_name, ".."))
     293                 :           4 :                 continue;
     294                 :             : 
     295         [ -  + ]:           1 :             if (fstatat(child_fd, entry->d_name, &stat, 0) < 0) {
     296         [ #  # ]:           0 :                 return bf_err_r(errno,
     297                 :             :                                 "failed to fstatat() file '%s' for removal",
     298                 :             :                                 entry->d_name);
     299                 :             :             }
     300                 :             : 
     301         [ +  - ]:           1 :             if (S_ISDIR(stat.st_mode))
     302                 :           1 :                 r = bf_rmdir_at(child_fd, entry->d_name, true);
     303                 :             :             else
     304         [ #  # ]:           0 :                 r = unlinkat(child_fd, entry->d_name, 0) == 0 ? 0 : -errno;
     305         [ -  + ]:           1 :             if (r)
     306         [ #  # ]:           0 :                 return bf_err_r(r, "failed to remove '%s'", entry->d_name);
     307                 :             :         }
     308                 :             :     }
     309                 :             : 
     310                 :          82 :     r = unlinkat(parent_fd, dir_name, AT_REMOVEDIR);
     311         [ +  + ]:          82 :     if (r)
     312                 :          37 :         return -errno;
     313                 :             : 
     314                 :             :     return 0;
     315                 :             : }
     316                 :             : 
     317                 :          22 : int bf_acquire_lock(const char *path)
     318                 :             : {
     319                 :          22 :     _cleanup_close_ int fd = -1;
     320                 :             : 
     321                 :             :     bf_assert(path);
     322                 :             : 
     323                 :          22 :     fd = open(path, O_CREAT | O_RDWR, BF_PERM_755);
     324         [ -  + ]:          22 :     if (fd < 0)
     325                 :           0 :         return -errno;
     326                 :             : 
     327         [ +  + ]:          22 :     if (flock(fd, LOCK_EX | LOCK_NB) < 0)
     328                 :           2 :         return -errno;
     329                 :             : 
     330                 :          20 :     return TAKE_FD(fd);
     331                 :             : }
     332                 :             : 
     333                 :           1 : int bf_send_fd(int sock_fd, int fd)
     334                 :             : {
     335                 :           1 :     char dummy = 'X';
     336                 :             :     struct cmsghdr *cmsg;
     337                 :           1 :     struct msghdr msg = {0};
     338                 :             :     char buf[CMSG_SPACE(sizeof(int))];
     339                 :           1 :     struct iovec iov = {.iov_base = &dummy, .iov_len = 1};
     340                 :             :     ssize_t r;
     341                 :             : 
     342                 :           1 :     msg.msg_iov = &iov;
     343                 :           1 :     msg.msg_iovlen = 1;
     344                 :           1 :     msg.msg_control = buf;
     345                 :           1 :     msg.msg_controllen = sizeof(buf);
     346                 :             : 
     347                 :             :     cmsg = CMSG_FIRSTHDR(&msg);
     348                 :           1 :     cmsg->cmsg_level = SOL_SOCKET;
     349                 :           1 :     cmsg->cmsg_type = SCM_RIGHTS;
     350                 :           1 :     cmsg->cmsg_len = CMSG_LEN(sizeof(int));
     351                 :             : 
     352                 :           1 :     memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
     353                 :             : 
     354                 :           1 :     r = sendmsg(sock_fd, &msg, 0);
     355         [ -  + ]:           1 :     if (r < 0)
     356         [ #  # ]:           0 :         return bf_err_r(errno, "failed to send file descriptor");
     357                 :             : 
     358                 :             :     return 0;
     359                 :             : }
     360                 :             : 
     361                 :             : /**
     362                 :             :  * @brief Receive a file descriptor over a Unix Domain Socket.
     363                 :             :  *
     364                 :             :  * @param sock_fd Socket file descriptor to receive the file descriptor through.
     365                 :             :  * @return A file descriptor, or a negative error value on failure. The caller
     366                 :             :  *         owns the file descriptor.
     367                 :             :  */
     368                 :           1 : static int _bf_recv_fd(int sock_fd)
     369                 :             : {
     370                 :             :     int fd;
     371                 :             :     char dummy;
     372                 :             :     struct cmsghdr *cmsg;
     373                 :           1 :     struct msghdr msg = {0};
     374                 :             :     char buf[CMSG_SPACE(sizeof(int))];
     375                 :           1 :     struct iovec iov = {.iov_base = &dummy, .iov_len = 1};
     376                 :             :     ssize_t r;
     377                 :             : 
     378                 :           1 :     msg.msg_iov = &iov;
     379                 :           1 :     msg.msg_iovlen = 1;
     380                 :           1 :     msg.msg_control = buf;
     381                 :           1 :     msg.msg_controllen = sizeof(buf);
     382                 :             : 
     383                 :           1 :     r = recvmsg(sock_fd, &msg, 0);
     384         [ -  + ]:           1 :     if (r < 0)
     385         [ #  # ]:           0 :         return bf_err_r(errno, "failed to receive file descriptor");
     386                 :             : 
     387         [ +  - ]:           1 :     cmsg = CMSG_FIRSTHDR(&msg);
     388         [ -  + ]:           1 :     if (!cmsg)
     389         [ #  # ]:           0 :         return bf_err_r(-ENOENT, "no control message received");
     390         [ -  + ]:           1 :     if (cmsg->cmsg_level != SOL_SOCKET)
     391         [ #  # ]:           0 :         return bf_err_r(-EINVAL, "invalid control message level");
     392         [ -  + ]:           1 :     if (cmsg->cmsg_type != SCM_RIGHTS)
     393         [ #  # ]:           0 :         return bf_err_r(-EINVAL, "invalid control message type");
     394                 :             : 
     395                 :           1 :     memcpy(&fd, CMSG_DATA(cmsg), sizeof(int));
     396                 :             : 
     397                 :           1 :     return fd;
     398                 :             : }
     399                 :             : 
     400                 :         119 : int bf_connect_to_daemon(void)
     401                 :             : {
     402                 :         119 :     _cleanup_close_ int fd = -1;
     403                 :         119 :     struct sockaddr_un addr = {};
     404                 :             :     int r;
     405                 :             : 
     406                 :         119 :     fd = socket(AF_UNIX, SOCK_STREAM, 0);
     407         [ -  + ]:         119 :     if (fd < 0)
     408         [ #  # ]:           0 :         return bf_err_r(errno, "bpfilter: can't create socket");
     409                 :             : 
     410                 :         119 :     addr.sun_family = AF_UNIX;
     411                 :         119 :     strncpy(addr.sun_path, BF_SOCKET_PATH, sizeof(addr.sun_path) - 1);
     412                 :             : 
     413                 :         119 :     r = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
     414         [ +  + ]:         119 :     if (r < 0)
     415         [ +  - ]:          18 :         return bf_err_r(errno, "bpfilter: failed to connect to socket");
     416                 :             : 
     417                 :         101 :     return TAKE_FD(fd);
     418                 :             : }
     419                 :             : 
     420                 :         104 : int bf_send(int fd, const struct bf_request *request,
     421                 :             :             struct bf_response **response, int *recv_fd)
     422                 :             : {
     423                 :         104 :     _cleanup_close_ int _recv_fd = -1;
     424                 :             :     int r;
     425                 :             : 
     426                 :             :     bf_assert(request);
     427                 :             :     bf_assert(response);
     428                 :             : 
     429                 :         104 :     r = _bf_send_request(fd, request);
     430         [ -  + ]:         104 :     if (r < 0)
     431         [ #  # ]:           0 :         return bf_err_r(r, "bpfilter: failed to send request to the daemon");
     432                 :             : 
     433         [ +  + ]:         104 :     if (recv_fd) {
     434                 :           1 :         _recv_fd = _bf_recv_fd(fd);
     435         [ -  + ]:           1 :         if (_recv_fd < 0)
     436         [ #  # ]:           0 :             return bf_err_r(_recv_fd, "failed to receive file descriptor");
     437                 :             :     }
     438                 :             : 
     439                 :         104 :     r = _bf_recv_response(fd, response);
     440         [ -  + ]:         104 :     if (r < 0) {
     441         [ #  # ]:           0 :         return bf_err_r(r,
     442                 :             :                         "bpfilter: failed to receive response from the daemon");
     443                 :             :     }
     444                 :             : 
     445         [ +  + ]:         104 :     if (recv_fd)
     446                 :           1 :         *recv_fd = TAKE_FD(_recv_fd);
     447                 :             : 
     448                 :             :     return 0;
     449                 :             : }
        

Generated by: LCOV version 2.0-1