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