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 <stdlib.h>
7 : : #include <string.h>
8 : : #include <unistd.h>
9 : :
10 : : #include <bpfilter/bpfilter.h>
11 : : #include <bpfilter/ctx.h>
12 : :
13 : : #include "bpfilter/chain.h"
14 : : #include "bpfilter/core/list.h"
15 : : #include "bpfilter/counter.h"
16 : : #include "bpfilter/helper.h"
17 : : #include "bpfilter/hook.h"
18 : : #include "bpfilter/logger.h"
19 : : #include "bpfilter/set.h"
20 : : #include "cgen/cgen.h"
21 : : #include "cgen/handle.h"
22 : : #include "cgen/prog/link.h"
23 : : #include "cgen/prog/map.h"
24 : : #include "core/lock.h"
25 : :
26 : 190 : static int copy_hookopts(struct bf_hookopts **dest,
27 : : const struct bf_hookopts *src)
28 : : {
29 : : struct bf_hookopts *copy;
30 : :
31 : 190 : copy = bf_memdup(src, sizeof(*src));
32 [ + - ]: 190 : if (!copy)
33 : : return -ENOMEM;
34 : :
35 [ + + ]: 190 : if (src->cgpath) {
36 : 85 : copy->cgpath = strdup(src->cgpath);
37 [ - + ]: 85 : if (!copy->cgpath) {
38 : 0 : free(copy);
39 : 0 : return -ENOMEM;
40 : : }
41 : : }
42 : :
43 : 190 : *dest = copy;
44 : :
45 : 190 : return 0;
46 : : }
47 : :
48 : : /**
49 : : * @brief Unload every chain currently pinned in the pin directory.
50 : : *
51 : : * The caller must already hold a `BF_LOCK_WRITE` lock on the pin directory
52 : : * (typically via `bf_lock_init(BF_LOCK_WRITE)`). For each chain entry, a
53 : : * per-chain `BF_LOCK_WRITE` lock is acquired (so the chain dir is removed
54 : : * by `bf_lock_release_chain` after unload).
55 : : */
56 : 1228 : static int _bf_ruleset_flush(struct bf_lock *lock)
57 : : {
58 : 1228 : _free_bf_list_ bf_list *cgens = NULL;
59 : : int r;
60 : :
61 : : assert(lock);
62 : :
63 : 1228 : r = bf_ctx_get_cgens(lock, &cgens);
64 [ - + ]: 1228 : if (r)
65 [ # # ]: 0 : return bf_err_r(r, "failed to discover chains during flush");
66 : :
67 [ + + + + : 3698 : bf_list_foreach (cgens, cgen_node) {
+ + ]
68 : : struct bf_cgen *cgen = bf_list_node_get_data(cgen_node);
69 : :
70 : 621 : r = bf_lock_acquire_chain(lock, cgen->chain->name, BF_LOCK_WRITE,
71 : : false);
72 [ - + ]: 621 : if (r) {
73 [ # # ]: 0 : bf_warn_r(
74 : : r,
75 : : "failed to acquire WRITE lock on chain '%s' during flush, skipping",
76 : : cgen->chain->name);
77 : 0 : continue;
78 : : }
79 : :
80 : 621 : bf_cgen_unload(cgen, lock);
81 : 621 : bf_lock_release_chain(lock);
82 : : }
83 : :
84 : : return 0;
85 : : }
86 : :
87 : 8 : int bf_ruleset_get(bf_list *chains, bf_list *hookopts)
88 : : {
89 : 8 : _clean_bf_lock_ struct bf_lock lock = bf_lock_default();
90 : 8 : _free_bf_list_ bf_list *cgens = NULL;
91 : 8 : _clean_bf_list_ bf_list _chains = bf_list_default_from(*chains);
92 : 8 : _clean_bf_list_ bf_list _hookopts = bf_list_default_from(*hookopts);
93 : : int r;
94 : :
95 : 8 : r = bf_lock_init(&lock, BF_LOCK_READ);
96 [ - + ]: 8 : if (r)
97 [ # # ]: 0 : return bf_err_r(r, "failed to acquire READ lock for ruleset get");
98 : :
99 : 8 : r = bf_ctx_get_cgens(&lock, &cgens);
100 [ - + ]: 8 : if (r < 0)
101 [ # # ]: 0 : return bf_err_r(r, "failed to get cgen list");
102 : :
103 [ + + + + : 30 : bf_list_foreach (cgens, cgen_node) {
+ + ]
104 : : struct bf_cgen *cgen = bf_list_node_get_data(cgen_node);
105 : 0 : _free_bf_hookopts_ struct bf_hookopts *hookopts_copy = NULL;
106 : :
107 : 7 : r = bf_cgen_load_counters(cgen);
108 [ - + ]: 7 : if (r) {
109 [ # # ]: 0 : return bf_err_r(r, "failed to read counters for '%s'",
110 : : cgen->chain->name);
111 : : }
112 : :
113 : : // cgen will be destroyed, we can steal the chain
114 : 7 : r = bf_list_push(&_chains, (void **)&cgen->chain);
115 [ + - ]: 7 : if (r)
116 : : return r;
117 : :
118 [ + + + - ]: 7 : if (cgen->handle->link && cgen->handle->link->hookopts) {
119 : 6 : r = copy_hookopts(&hookopts_copy, cgen->handle->link->hookopts);
120 [ - + ]: 6 : if (r)
121 [ # # ]: 0 : return bf_err_r(r, "failed to copy hookopts");
122 : : }
123 : 7 : r = bf_list_add_tail(&_hookopts, hookopts_copy);
124 [ + - ]: 7 : if (r)
125 : : return r;
126 : 7 : TAKE_PTR(hookopts_copy);
127 : : }
128 : :
129 : 8 : *chains = bf_list_move(&_chains);
130 : 8 : *hookopts = bf_list_move(&_hookopts);
131 : :
132 : 8 : return 0;
133 : : }
134 : :
135 : 10 : int bf_ruleset_set(bf_list *chains, bf_list *hookopts)
136 : : {
137 [ + + ]: 19 : _clean_bf_lock_ struct bf_lock lock = bf_lock_default();
138 : : struct bf_list_node *chain_node = bf_list_get_head(chains);
139 : : struct bf_list_node *hookopts_node = bf_list_get_head(hookopts);
140 : : int r;
141 : :
142 [ + + ]: 10 : if (bf_list_size(chains) != bf_list_size(hookopts))
143 : : return -EINVAL;
144 : :
145 : 9 : r = bf_lock_init(&lock, BF_LOCK_WRITE);
146 [ - + ]: 9 : if (r)
147 [ # # ]: 0 : return bf_err_r(r, "failed to acquire WRITE lock for ruleset set");
148 : :
149 : 9 : r = _bf_ruleset_flush(&lock);
150 [ + - ]: 9 : if (r)
151 : : return r;
152 : :
153 [ + + ]: 20 : while (chain_node && hookopts_node) {
154 : 13 : _free_bf_cgen_ struct bf_cgen *cgen = NULL;
155 : 13 : _free_bf_chain_ struct bf_chain *chain_copy = NULL;
156 : 26 : _free_bf_hookopts_ struct bf_hookopts *hookopts_copy = NULL;
157 : : struct bf_chain *chain = bf_list_node_get_data(chain_node);
158 : : struct bf_hookopts *node_hookopts =
159 : : bf_list_node_get_data(hookopts_node);
160 : :
161 : 13 : r = bf_chain_new_from_copy(&chain_copy, chain);
162 [ - + ]: 13 : if (r)
163 : 0 : goto err_load;
164 : :
165 [ + + ]: 13 : if (node_hookopts) {
166 : 11 : r = copy_hookopts(&hookopts_copy, node_hookopts);
167 [ - + ]: 11 : if (r)
168 : 0 : goto err_load;
169 : : }
170 : :
171 : 13 : r = bf_cgen_new(&cgen, &chain_copy);
172 [ - + ]: 13 : if (r)
173 : 0 : goto err_load;
174 : :
175 : 13 : r = bf_lock_acquire_chain(&lock, cgen->chain->name, BF_LOCK_WRITE,
176 : : true);
177 [ - + ]: 13 : if (r) {
178 [ # # ]: 0 : bf_err_r(r, "failed to acquire WRITE lock on chain '%s'",
179 : : cgen->chain->name);
180 : 0 : goto err_load;
181 : : }
182 : :
183 [ + + ]: 15 : r = bf_cgen_set(cgen, hookopts_copy ? &hookopts_copy : NULL, &lock);
184 [ + + ]: 13 : if (r) {
185 [ + - ]: 2 : bf_err_r(r, "failed to set chain '%s'", cgen->chain->name);
186 : 2 : bf_lock_release_chain(&lock);
187 : 2 : goto err_load;
188 : : }
189 : :
190 : 11 : bf_lock_release_chain(&lock);
191 : :
192 : : chain_node = bf_list_node_next(chain_node);
193 : : hookopts_node = bf_list_node_next(hookopts_node);
194 : : }
195 : :
196 : : return 0;
197 : :
198 : : err_load:
199 : 2 : _bf_ruleset_flush(&lock);
200 : 2 : return r;
201 : : }
202 : :
203 : 1217 : int bf_ruleset_flush(void)
204 : : {
205 : 1217 : _clean_bf_lock_ struct bf_lock lock = bf_lock_default();
206 : : int r;
207 : :
208 : 1217 : r = bf_lock_init(&lock, BF_LOCK_WRITE);
209 [ - + ]: 1217 : if (r)
210 [ # # ]: 0 : return bf_err_r(r, "failed to acquire WRITE lock for ruleset flush");
211 : :
212 : 1217 : return _bf_ruleset_flush(&lock);
213 : : }
214 : :
215 : 1411 : int bf_chain_set(struct bf_chain *chain, struct bf_hookopts *hookopts)
216 : : {
217 : 1411 : _clean_bf_lock_ struct bf_lock lock = bf_lock_default();
218 : 1411 : _free_bf_cgen_ struct bf_cgen *old_cgen = NULL;
219 : 1411 : _free_bf_cgen_ struct bf_cgen *new_cgen = NULL;
220 : 1411 : _free_bf_chain_ struct bf_chain *chain_copy = NULL;
221 : 1411 : _free_bf_hookopts_ struct bf_hookopts *hookopts_copy = NULL;
222 : : int r;
223 : :
224 : : assert(chain);
225 : :
226 : : /* bf_chain_set is a namespace mutator: the previous chain (if any) is
227 : : * destroyed and a fresh one is published under the same name. Take
228 : : * pindir WRITE for the whole operation so the flush-then-load
229 : : * sequence is atomic w.r.t. every other libbpfilter caller. */
230 : 1411 : r = bf_lock_init(&lock, BF_LOCK_WRITE);
231 [ + - ]: 1411 : if (r)
232 : : return r;
233 : :
234 : : /* Unload any pre-existing chain under this name, and remove its pindir
235 : : * entry so stage-and-rename can publish the new one. */
236 : 1411 : r = bf_lock_acquire_chain(&lock, chain->name, BF_LOCK_WRITE, false);
237 [ + + ]: 1411 : if (r == 0) {
238 : 745 : r = bf_ctx_get_cgen(&lock, &old_cgen);
239 [ - + ]: 745 : if (r && r != -ENOENT) {
240 : 0 : bf_lock_release_chain(&lock);
241 : 0 : return r;
242 : : }
243 [ + - ]: 745 : if (old_cgen)
244 : 745 : bf_cgen_unload(old_cgen, &lock);
245 : : /* Release drops the chain flock and (because we held WRITE)
246 : : * removes the now-empty chain dir. */
247 : 745 : bf_lock_release_chain(&lock);
248 [ + - ]: 666 : } else if (r != -ENOENT) {
249 : : return r;
250 : : }
251 : :
252 : 1411 : r = bf_chain_new_from_copy(&chain_copy, chain);
253 [ + - ]: 1411 : if (r)
254 : : return r;
255 : :
256 [ + + ]: 1411 : if (hookopts) {
257 : 130 : r = copy_hookopts(&hookopts_copy, hookopts);
258 [ + - ]: 130 : if (r)
259 : : return r;
260 : : }
261 : :
262 : 1411 : r = bf_cgen_new(&new_cgen, &chain_copy);
263 [ + - ]: 1411 : if (r)
264 : : return r;
265 : :
266 : : /* Create the new chain dir via stage-and-rename (I3). */
267 : 1411 : r = bf_lock_acquire_chain(&lock, chain->name, BF_LOCK_WRITE, true);
268 [ + - ]: 1411 : if (r)
269 : : return r;
270 : :
271 [ + + ]: 2692 : return bf_cgen_set(new_cgen, hookopts_copy ? &hookopts_copy : NULL, &lock);
272 : : }
273 : :
274 : 1255 : int bf_chain_get(const char *name, struct bf_chain **chain,
275 : : struct bf_hookopts **hookopts)
276 : : {
277 : 1255 : _clean_bf_lock_ struct bf_lock lock = bf_lock_default();
278 : 1255 : _free_bf_chain_ struct bf_chain *_chain = NULL;
279 : 1255 : _free_bf_hookopts_ struct bf_hookopts *_hookopts = NULL;
280 : 1255 : _free_bf_cgen_ struct bf_cgen *cgen = NULL;
281 : : int r;
282 : :
283 : : assert(name);
284 : : assert(chain);
285 : : assert(hookopts);
286 : :
287 : 1255 : r = bf_lock_init_for_chain(&lock, name, BF_LOCK_READ, BF_LOCK_READ, false);
288 [ + + ]: 1255 : if (r)
289 : : return r;
290 : :
291 : 1253 : r = bf_ctx_get_cgen(&lock, &cgen);
292 [ + - ]: 1253 : if (r)
293 : : return r;
294 : :
295 : 1253 : r = bf_cgen_load_counters(cgen);
296 [ - + ]: 1253 : if (r) {
297 [ # # ]: 0 : return bf_err_r(r, "failed to load counters for '%s'",
298 : : cgen->chain->name);
299 : : }
300 : :
301 : : // cgen will be destroyed, we can steal the chain
302 : 1253 : _chain = TAKE_PTR(cgen->chain);
303 : :
304 [ + + + - ]: 1253 : if (cgen->handle->link && cgen->handle->link->hookopts) {
305 : 29 : r = copy_hookopts(&_hookopts, cgen->handle->link->hookopts);
306 [ + - ]: 29 : if (r)
307 : : return r;
308 : : }
309 : :
310 : 1253 : *chain = TAKE_PTR(_chain);
311 : 1253 : *hookopts = TAKE_PTR(_hookopts);
312 : :
313 : 1253 : return 0;
314 : : }
315 : :
316 : 3087 : int bf_chain_prog_fd(const char *name)
317 : : {
318 : 3087 : _clean_bf_lock_ struct bf_lock lock = bf_lock_default();
319 : 3087 : _free_bf_cgen_ struct bf_cgen *cgen = NULL;
320 : : int r;
321 : :
322 : : assert(name);
323 : :
324 : 3087 : r = bf_lock_init_for_chain(&lock, name, BF_LOCK_READ, BF_LOCK_READ, false);
325 [ + + ]: 3087 : if (r)
326 : : return r;
327 : :
328 : 3086 : r = bf_ctx_get_cgen(&lock, &cgen);
329 [ + - ]: 3086 : if (r)
330 : : return r;
331 : :
332 [ - + ]: 3086 : if (cgen->handle->prog_fd == -1)
333 [ # # ]: 0 : return bf_err_r(-ENODEV, "chain '%s' has no loaded program", name);
334 : :
335 : 3086 : return dup(cgen->handle->prog_fd);
336 : : }
337 : :
338 : 7 : int bf_chain_logs_fd(const char *name)
339 : : {
340 : 7 : _clean_bf_lock_ struct bf_lock lock = bf_lock_default();
341 : 7 : _free_bf_cgen_ struct bf_cgen *cgen = NULL;
342 : : int r;
343 : :
344 : : assert(name);
345 : :
346 : 7 : r = bf_lock_init_for_chain(&lock, name, BF_LOCK_READ, BF_LOCK_READ, false);
347 [ + + ]: 7 : if (r)
348 : : return r;
349 : :
350 : 6 : r = bf_ctx_get_cgen(&lock, &cgen);
351 [ + - ]: 6 : if (r)
352 : : return r;
353 : :
354 [ - + ]: 6 : if (!cgen->handle->lmap)
355 [ # # ]: 0 : return bf_err_r(-ENOENT, "chain '%s' has no logs buffer", name);
356 : :
357 : 6 : return dup(cgen->handle->lmap->fd);
358 : : }
359 : :
360 : 17 : int bf_chain_load(struct bf_chain *chain)
361 : : {
362 : 17 : _clean_bf_lock_ struct bf_lock lock = bf_lock_default();
363 : 17 : _free_bf_cgen_ struct bf_cgen *cgen = NULL;
364 : 17 : _free_bf_chain_ struct bf_chain *chain_copy = NULL;
365 : : int r;
366 : :
367 : : assert(chain);
368 : :
369 : : /* chain_load is a namespace mutator: pindir WRITE + chain WRITE on the
370 : : * staged inode. Stage-and-rename (I3) returns `-EEXIST` if another
371 : : * creator already claimed the name, which replaces the former
372 : : * check-then-create sequence atomically. */
373 : 17 : r = bf_lock_init_for_chain(&lock, chain->name, BF_LOCK_WRITE, BF_LOCK_WRITE,
374 : : true);
375 [ + - ]: 17 : if (r)
376 : : return r;
377 : :
378 : 17 : r = bf_chain_new_from_copy(&chain_copy, chain);
379 [ + - ]: 17 : if (r)
380 : : return r;
381 : :
382 : 17 : r = bf_cgen_new(&cgen, &chain_copy);
383 [ + - ]: 17 : if (r)
384 : : return r;
385 : :
386 : 17 : return bf_cgen_load(cgen, &lock);
387 : : }
388 : :
389 : 16 : int bf_chain_attach(const char *name, const struct bf_hookopts *hookopts)
390 : : {
391 : 16 : _clean_bf_lock_ struct bf_lock lock = bf_lock_default();
392 : 16 : _free_bf_cgen_ struct bf_cgen *cgen = NULL;
393 : 16 : _free_bf_hookopts_ struct bf_hookopts *hookopts_copy = NULL;
394 : : int r;
395 : :
396 : : assert(name);
397 : : assert(hookopts);
398 : :
399 : 16 : r = bf_lock_init_for_chain(&lock, name, BF_LOCK_READ, BF_LOCK_WRITE, false);
400 [ + + ]: 16 : if (r)
401 : : return r;
402 : :
403 : 15 : r = bf_ctx_get_cgen(&lock, &cgen);
404 [ + - ]: 15 : if (r)
405 : : return r;
406 : :
407 [ - + ]: 15 : if (cgen->handle->link)
408 [ # # ]: 0 : return bf_err_r(-EBUSY, "chain '%s' is already linked to a hook", name);
409 : :
410 : 15 : r = bf_hookopts_validate(hookopts, cgen->chain->hook);
411 [ + + ]: 15 : if (r)
412 [ + - ]: 1 : return bf_err_r(r, "failed to validate hook options");
413 : :
414 : 14 : r = copy_hookopts(&hookopts_copy, hookopts);
415 [ + - ]: 14 : if (r)
416 : : return r;
417 : :
418 : 14 : r = bf_cgen_attach(cgen, &hookopts_copy, &lock);
419 [ + + ]: 14 : if (r)
420 [ + - ]: 2 : return bf_err_r(r, "failed to attach codegen to hook");
421 : :
422 : : return 0;
423 : : }
424 : :
425 : 10 : int bf_chain_update(const struct bf_chain *chain)
426 : : {
427 : 10 : _clean_bf_lock_ struct bf_lock lock = bf_lock_default();
428 : 10 : _free_bf_chain_ struct bf_chain *chain_copy = NULL;
429 : 10 : _free_bf_cgen_ struct bf_cgen *cgen = NULL;
430 : : int r;
431 : :
432 : : assert(chain);
433 : :
434 : 10 : r = bf_lock_init_for_chain(&lock, chain->name, BF_LOCK_READ, BF_LOCK_WRITE,
435 : : false);
436 [ + + ]: 10 : if (r)
437 : : return r;
438 : :
439 : 8 : r = bf_ctx_get_cgen(&lock, &cgen);
440 [ + - ]: 8 : if (r)
441 : : return r;
442 : :
443 [ + + ]: 8 : if (chain->hook != cgen->chain->hook) {
444 [ + - ]: 2 : return bf_err_r(
445 : : -EINVAL,
446 : : "can't update chain '%s': hook %s doesn't match the existing hook %s",
447 : : chain->name, bf_hook_to_str(chain->hook),
448 : : bf_hook_to_str(cgen->chain->hook));
449 : : }
450 : :
451 : 6 : r = bf_chain_new_from_copy(&chain_copy, chain);
452 [ + - ]: 6 : if (r)
453 : : return r;
454 : :
455 : 6 : return bf_cgen_update(cgen, &chain_copy, 0, &lock);
456 : : }
457 : :
458 : 18 : int bf_chain_update_set(const char *name, const struct bf_set *to_add,
459 : : const struct bf_set *to_remove)
460 : : {
461 : 18 : _clean_bf_lock_ struct bf_lock lock = bf_lock_default();
462 : 18 : _free_bf_chain_ struct bf_chain *new_chain = NULL;
463 : 18 : _free_bf_cgen_ struct bf_cgen *cgen = NULL;
464 : : int r;
465 : :
466 : : assert(name);
467 : : assert(to_add);
468 : : assert(to_remove);
469 : :
470 [ + + ]: 18 : if (!bf_streq(to_add->name, to_remove->name))
471 [ + - ]: 1 : return bf_err_r(-EINVAL, "to_add->name must match to_remove->name");
472 : :
473 : 17 : r = bf_lock_init_for_chain(&lock, name, BF_LOCK_READ, BF_LOCK_WRITE, false);
474 [ + - ]: 17 : if (r)
475 : : return r;
476 : :
477 : 17 : r = bf_ctx_get_cgen(&lock, &cgen);
478 [ + - ]: 17 : if (r)
479 : : return r;
480 : :
481 : 17 : r = bf_chain_new_from_copy(&new_chain, cgen->chain);
482 [ + - ]: 17 : if (r)
483 : : return r;
484 : :
485 : 17 : r = bf_chain_apply_set_delta(new_chain, to_add->name, to_add, to_remove);
486 [ + - ]: 17 : if (r)
487 : : return r;
488 : :
489 : 17 : r = bf_cgen_update(cgen, &new_chain,
490 : : BF_FLAG(BF_CGEN_UPDATE_PRESERVE_COUNTERS), &lock);
491 [ - + ]: 17 : if (r)
492 [ # # ]: 0 : return bf_err_r(r, "failed to update chain with new set data");
493 : :
494 : : return 0;
495 : : }
496 : :
497 : 45 : int bf_chain_flush(const char *name)
498 : : {
499 : 45 : _clean_bf_lock_ struct bf_lock lock = bf_lock_default();
500 : 45 : _free_bf_cgen_ struct bf_cgen *cgen = NULL;
501 : : int r;
502 : :
503 : : assert(name);
504 : :
505 : : /* chain_flush removes the chain dir from the pindir namespace, so it
506 : : * needs pindir WRITE (I2). */
507 : 45 : r = bf_lock_init_for_chain(&lock, name, BF_LOCK_WRITE, BF_LOCK_WRITE,
508 : : false);
509 [ + + ]: 45 : if (r)
510 : : return r;
511 : :
512 : 44 : r = bf_ctx_get_cgen(&lock, &cgen);
513 [ + - ]: 44 : if (r)
514 : : return r;
515 : :
516 : 44 : bf_cgen_unload(cgen, &lock);
517 : :
518 : 44 : return 0;
519 : : }
|