]>
Commit | Line | Data |
---|---|---|
8befd5cc MG |
1 | /** |
2 | * Enhanced Seccomp Filter DB | |
3 | * | |
4 | * Copyright (c) 2012,2016 Red Hat <pmoore@redhat.com> | |
5 | * Author: Paul Moore <paul@paul-moore.com> | |
6 | */ | |
7 | ||
8 | /* | |
9 | * This library is free software; you can redistribute it and/or modify it | |
10 | * under the terms of version 2.1 of the GNU Lesser General Public License as | |
11 | * published by the Free Software Foundation. | |
12 | * | |
13 | * This library is distributed in the hope that it will be useful, but WITHOUT | |
14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License | |
16 | * for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU Lesser General Public License | |
19 | * along with this library; if not, see <http://www.gnu.org/licenses>. | |
20 | */ | |
21 | ||
22 | #include <assert.h> | |
23 | #include <errno.h> | |
24 | #include <inttypes.h> | |
25 | #include <stdlib.h> | |
26 | #include <string.h> | |
27 | #include <stdarg.h> | |
28 | ||
29 | #include <seccomp.h> | |
30 | ||
31 | #include "arch.h" | |
32 | #include "db.h" | |
33 | #include "system.h" | |
34 | ||
35 | /* state values */ | |
36 | #define _DB_STA_VALID 0xA1B2C3D4 | |
37 | #define _DB_STA_FREED 0x1A2B3C4D | |
38 | ||
39 | /* the priority field is fairly simple - without any user hints, or in the case | |
40 | * of a hint "tie", we give higher priority to syscalls with less chain nodes | |
41 | * (filter is easier to evaluate) */ | |
42 | #define _DB_PRI_MASK_CHAIN 0x0000FFFF | |
43 | #define _DB_PRI_MASK_USER 0x00FF0000 | |
44 | #define _DB_PRI_USER(x) (((x) << 16) & _DB_PRI_MASK_USER) | |
45 | ||
46 | /* private structure for tracking the state of the sub-tree "pruning" */ | |
47 | struct db_prune_state { | |
48 | bool prefix_exist; | |
49 | bool prefix_new; | |
50 | bool matched; | |
51 | }; | |
52 | ||
53 | static unsigned int _db_tree_free(struct db_arg_chain_tree *tree); | |
54 | ||
55 | /** | |
56 | * Do not call this function directly, use _db_tree_free() instead | |
57 | */ | |
58 | static unsigned int __db_tree_free(struct db_arg_chain_tree *tree) | |
59 | { | |
60 | int cnt; | |
61 | ||
62 | if (tree == NULL || --(tree->refcnt) > 0) | |
63 | return 0; | |
64 | ||
65 | /* we assume the caller has ensured that 'tree->lvl_prv == NULL' */ | |
66 | cnt = __db_tree_free(tree->lvl_nxt); | |
67 | cnt += _db_tree_free(tree->nxt_t); | |
68 | cnt += _db_tree_free(tree->nxt_f); | |
69 | ||
70 | free(tree); | |
71 | return cnt + 1; | |
72 | } | |
73 | ||
74 | /** | |
75 | * Free a syscall filter argument chain tree | |
76 | * @param tree the argument chain list | |
77 | * | |
78 | * This function frees a tree and returns the number of nodes freed. | |
79 | * | |
80 | */ | |
81 | static unsigned int _db_tree_free(struct db_arg_chain_tree *tree) | |
82 | { | |
83 | struct db_arg_chain_tree *iter; | |
84 | ||
85 | if (tree == NULL) | |
86 | return 0; | |
87 | ||
88 | iter = tree; | |
89 | while (iter->lvl_prv != NULL) | |
90 | iter = iter->lvl_prv; | |
91 | ||
92 | return __db_tree_free(iter); | |
93 | } | |
94 | ||
95 | /** | |
96 | * Remove a node from an argument chain tree | |
97 | * @param tree the pointer to the tree | |
98 | * @param node the node to remove | |
99 | * | |
100 | * This function searches the tree looking for the node and removes it once | |
101 | * found. Returns the number of nodes freed. | |
102 | * | |
103 | */ | |
104 | static unsigned int _db_tree_remove(struct db_arg_chain_tree **tree, | |
105 | struct db_arg_chain_tree *node) | |
106 | { | |
107 | int cnt = 0; | |
108 | struct db_arg_chain_tree *c_iter; | |
109 | ||
110 | if (tree == NULL || *tree == NULL || node == NULL) | |
111 | return 0; | |
112 | ||
113 | c_iter = *tree; | |
114 | while (c_iter->lvl_prv != NULL) | |
115 | c_iter = c_iter->lvl_prv; | |
116 | ||
117 | do { | |
118 | if (c_iter == node || db_chain_zombie(c_iter)) { | |
119 | /* remove from the tree */ | |
120 | if (c_iter == *tree) { | |
121 | if (c_iter->lvl_prv != NULL) | |
122 | *tree = c_iter->lvl_prv; | |
123 | else | |
124 | *tree = c_iter->lvl_nxt; | |
125 | } | |
126 | if (c_iter->lvl_prv != NULL) | |
127 | c_iter->lvl_prv->lvl_nxt = c_iter->lvl_nxt; | |
128 | if (c_iter->lvl_nxt != NULL) | |
129 | c_iter->lvl_nxt->lvl_prv = c_iter->lvl_prv; | |
130 | ||
131 | /* free and return */ | |
132 | c_iter->lvl_prv = NULL; | |
133 | c_iter->lvl_nxt = NULL; | |
134 | cnt += _db_tree_free(c_iter); | |
135 | return cnt; | |
136 | } | |
137 | ||
138 | /* check the true/false sub-trees */ | |
139 | cnt += _db_tree_remove(&(c_iter->nxt_t), node); | |
140 | cnt += _db_tree_remove(&(c_iter->nxt_f), node); | |
141 | ||
142 | c_iter = c_iter->lvl_nxt; | |
143 | } while (c_iter != NULL); | |
144 | ||
145 | return cnt; | |
146 | } | |
147 | ||
148 | /** | |
149 | * Traverse a tree checking the action values | |
150 | * @param tree the pointer to the tree | |
151 | * @param action the action | |
152 | * | |
153 | * Traverse the tree inspecting each action to see if it matches the given | |
154 | * action. Returns zero if all actions match the given action, negative values | |
155 | * on failure. | |
156 | * | |
157 | */ | |
158 | static int _db_tree_act_check(struct db_arg_chain_tree *tree, uint32_t action) | |
159 | { | |
160 | int rc; | |
161 | struct db_arg_chain_tree *c_iter; | |
162 | ||
163 | if (tree == NULL) | |
164 | return 0; | |
165 | ||
166 | c_iter = tree; | |
167 | while (c_iter->lvl_prv != NULL) | |
168 | c_iter = c_iter->lvl_prv; | |
169 | ||
170 | do { | |
171 | if (c_iter->act_t_flg && c_iter->act_t != action) | |
172 | return -EEXIST; | |
173 | if (c_iter->act_f_flg && c_iter->act_f != action) | |
174 | return -EEXIST; | |
175 | ||
176 | rc = _db_tree_act_check(c_iter->nxt_t, action); | |
177 | if (rc < 0) | |
178 | return rc; | |
179 | rc = _db_tree_act_check(c_iter->nxt_f, action); | |
180 | if (rc < 0) | |
181 | return rc; | |
182 | ||
183 | c_iter = c_iter->lvl_nxt; | |
184 | } while (c_iter != NULL); | |
185 | ||
186 | return 0; | |
187 | } | |
188 | ||
189 | /** | |
190 | * Checks for a sub-tree match in an existing tree and prunes the tree | |
191 | * @param prev the head of the existing tree or sub-tree | |
192 | * @param existing the starting point into the existing tree | |
193 | * @param new pointer to the new tree | |
194 | * @param state pointer to the pruning state | |
195 | * | |
196 | * This function searches the existing and new trees trying to prune each to | |
197 | * eliminate redundancy. Returns the number of nodes removed from the tree on | |
198 | * success, zero if no changes were made, and negative values if the new tree | |
199 | * should be discarded. | |
200 | * | |
201 | */ | |
202 | static int _db_tree_sub_prune(struct db_arg_chain_tree **prev, | |
203 | struct db_arg_chain_tree *existing, | |
204 | struct db_arg_chain_tree *new, | |
205 | struct db_prune_state *state) | |
206 | { | |
207 | int rc = 0; | |
208 | int rc_tmp; | |
209 | struct db_arg_chain_tree *ec_iter; | |
210 | struct db_arg_chain_tree *ec_iter_tmp; | |
211 | struct db_arg_chain_tree *c_iter; | |
212 | struct db_prune_state state_new; | |
213 | ||
214 | if (!state || !existing || !new) | |
215 | return 0; | |
216 | ||
217 | ec_iter = existing; | |
218 | c_iter = new; | |
219 | do { | |
220 | if (db_chain_eq(ec_iter, c_iter)) { | |
221 | /* equal */ | |
222 | ||
223 | if (db_chain_leaf(c_iter)) { | |
224 | /* leaf */ | |
225 | if (db_chain_eq_result(ec_iter, c_iter)) { | |
226 | /* identical results */ | |
227 | if (prev != NULL) | |
228 | return _db_tree_remove(prev, | |
229 | ec_iter); | |
230 | else | |
231 | return -1; | |
232 | } | |
233 | if (c_iter->act_t_flg && ec_iter->nxt_t) { | |
234 | /* new is shorter (true) */ | |
235 | if (prev == NULL) | |
236 | return -1; | |
237 | rc += _db_tree_remove(&(ec_iter->nxt_t), | |
238 | ec_iter->nxt_t); | |
239 | ec_iter->act_t = c_iter->act_t; | |
240 | ec_iter->act_t_flg = true; | |
241 | } | |
242 | if (c_iter->act_f_flg && ec_iter->nxt_f) { | |
243 | /* new is shorter (false) */ | |
244 | if (prev == NULL) | |
245 | return -1; | |
246 | rc += _db_tree_remove(&(ec_iter->nxt_f), | |
247 | ec_iter->nxt_f); | |
248 | ec_iter->act_f = c_iter->act_f; | |
249 | ec_iter->act_f_flg = true; | |
250 | } | |
251 | ||
252 | return rc; | |
253 | } | |
254 | ||
255 | if (c_iter->nxt_t && ec_iter->act_t_flg) | |
256 | /* existing is shorter (true) */ | |
257 | return -1; | |
258 | if (c_iter->nxt_f && ec_iter->act_f_flg) | |
259 | /* existing is shorter (false) */ | |
260 | return -1; | |
261 | ||
262 | if (c_iter->nxt_t) { | |
263 | state_new = *state; | |
264 | state_new.matched = true; | |
265 | rc_tmp = _db_tree_sub_prune((prev ? | |
266 | &ec_iter : NULL), | |
267 | ec_iter->nxt_t, | |
268 | c_iter->nxt_t, | |
269 | &state_new); | |
270 | rc += (rc_tmp > 0 ? rc_tmp : 0); | |
271 | if (state->prefix_new && rc_tmp < 0) | |
272 | return (rc > 0 ? rc : rc_tmp); | |
273 | } | |
274 | if (c_iter->nxt_f) { | |
275 | state_new = *state; | |
276 | state_new.matched = true; | |
277 | rc_tmp = _db_tree_sub_prune((prev ? | |
278 | &ec_iter : NULL), | |
279 | ec_iter->nxt_f, | |
280 | c_iter->nxt_f, | |
281 | &state_new); | |
282 | rc += (rc_tmp > 0 ? rc_tmp : 0); | |
283 | if (state->prefix_new && rc_tmp < 0) | |
284 | return (rc > 0 ? rc : rc_tmp); | |
285 | } | |
286 | } else if (db_chain_lt(ec_iter, c_iter)) { | |
287 | /* less than */ | |
288 | if (state->matched || state->prefix_new) | |
289 | goto next; | |
290 | state_new = *state; | |
291 | state_new.prefix_exist = true; | |
292 | ||
293 | if (ec_iter->nxt_t) { | |
294 | rc_tmp = _db_tree_sub_prune((prev ? | |
295 | &ec_iter : NULL), | |
296 | ec_iter->nxt_t, | |
297 | c_iter, | |
298 | &state_new); | |
299 | rc += (rc_tmp > 0 ? rc_tmp : 0); | |
300 | } | |
301 | if (ec_iter->nxt_f) { | |
302 | rc_tmp = _db_tree_sub_prune((prev ? | |
303 | &ec_iter : NULL), | |
304 | ec_iter->nxt_f, | |
305 | c_iter, | |
306 | &state_new); | |
307 | rc += (rc_tmp > 0 ? rc_tmp : 0); | |
308 | } | |
309 | } else if (db_chain_gt(ec_iter, c_iter)) { | |
310 | /* greater than */ | |
311 | if (state->matched || state->prefix_exist) | |
312 | goto next; | |
313 | state_new = *state; | |
314 | state_new.prefix_new = true; | |
315 | ||
316 | if (c_iter->nxt_t) { | |
317 | rc_tmp = _db_tree_sub_prune(NULL, | |
318 | ec_iter, | |
319 | c_iter->nxt_t, | |
320 | &state_new); | |
321 | rc += (rc_tmp > 0 ? rc_tmp : 0); | |
322 | if (rc_tmp < 0) | |
323 | return (rc > 0 ? rc : rc_tmp); | |
324 | } | |
325 | if (c_iter->nxt_f) { | |
326 | rc_tmp = _db_tree_sub_prune(NULL, | |
327 | ec_iter, | |
328 | c_iter->nxt_f, | |
329 | &state_new); | |
330 | rc += (rc_tmp > 0 ? rc_tmp : 0); | |
331 | if (rc_tmp < 0) | |
332 | return (rc > 0 ? rc : rc_tmp); | |
333 | } | |
334 | } | |
335 | ||
336 | next: | |
337 | /* re-check current node and advance to the next node */ | |
338 | if (db_chain_zombie(ec_iter)) { | |
339 | ec_iter_tmp = ec_iter->lvl_nxt; | |
340 | rc += _db_tree_remove(prev, ec_iter); | |
341 | ec_iter = ec_iter_tmp; | |
342 | } else | |
343 | ec_iter = ec_iter->lvl_nxt; | |
344 | } while (ec_iter); | |
345 | ||
346 | return rc; | |
347 | } | |
348 | ||
349 | /** | |
350 | * Free and reset the seccomp filter DB | |
351 | * @param db the seccomp filter DB | |
352 | * | |
353 | * This function frees any existing filters and resets the filter DB to a | |
354 | * default state; only the DB architecture is preserved. | |
355 | * | |
356 | */ | |
357 | static void _db_reset(struct db_filter *db) | |
358 | { | |
359 | struct db_sys_list *s_iter; | |
360 | struct db_api_rule_list *r_iter; | |
361 | ||
362 | if (db == NULL) | |
363 | return; | |
364 | ||
365 | /* free any filters */ | |
366 | if (db->syscalls != NULL) { | |
367 | s_iter = db->syscalls; | |
368 | while (s_iter != NULL) { | |
369 | db->syscalls = s_iter->next; | |
370 | _db_tree_free(s_iter->chains); | |
371 | free(s_iter); | |
372 | s_iter = db->syscalls; | |
373 | } | |
374 | db->syscalls = NULL; | |
375 | } | |
376 | ||
377 | /* free any rules */ | |
378 | if (db->rules != NULL) { | |
379 | /* split the loop first then loop and free */ | |
380 | db->rules->prev->next = NULL; | |
381 | r_iter = db->rules; | |
382 | while (r_iter != NULL) { | |
383 | db->rules = r_iter->next; | |
384 | free(r_iter->args); | |
385 | free(r_iter); | |
386 | r_iter = db->rules; | |
387 | } | |
388 | db->rules = NULL; | |
389 | } | |
390 | } | |
391 | ||
392 | /** | |
393 | * Intitalize a seccomp filter DB | |
394 | * @param arch the architecture definition | |
395 | * | |
396 | * This function initializes a seccomp filter DB and readies it for use. | |
397 | * Returns a pointer to the DB on success, NULL on failure. | |
398 | * | |
399 | */ | |
400 | static struct db_filter *_db_init(const struct arch_def *arch) | |
401 | { | |
402 | struct db_filter *db; | |
403 | ||
404 | db = malloc(sizeof(*db)); | |
405 | if (db == NULL) | |
406 | return NULL; | |
407 | ||
408 | /* clear the buffer for the first time and set the arch */ | |
409 | memset(db, 0, sizeof(*db)); | |
410 | db->arch = arch; | |
411 | ||
412 | /* reset the DB to a known state */ | |
413 | _db_reset(db); | |
414 | ||
415 | return db; | |
416 | } | |
417 | ||
418 | /** | |
419 | * Destroy a seccomp filter DB | |
420 | * @param db the seccomp filter DB | |
421 | * | |
422 | * This function destroys a seccomp filter DB. After calling this function, | |
423 | * the filter should no longer be referenced. | |
424 | * | |
425 | */ | |
426 | static void _db_release(struct db_filter *db) | |
427 | { | |
428 | if (db == NULL) | |
429 | return; | |
430 | ||
431 | /* free and reset the DB */ | |
432 | _db_reset(db); | |
433 | free(db); | |
434 | } | |
435 | ||
436 | /** | |
437 | * Destroy a seccomp filter snapshot | |
438 | * @param snap the seccomp filter snapshot | |
439 | * | |
440 | * This function destroys a seccomp filter snapshot. After calling this | |
441 | * function, the snapshot should no longer be referenced. | |
442 | * | |
443 | */ | |
444 | static void _db_snap_release(struct db_filter_snap *snap) | |
445 | { | |
446 | unsigned int iter; | |
447 | ||
448 | if (snap->filter_cnt > 0) { | |
449 | for (iter = 0; iter < snap->filter_cnt; iter++) { | |
450 | if (snap->filters[iter]) | |
451 | _db_release(snap->filters[iter]); | |
452 | } | |
453 | free(snap->filters); | |
454 | } | |
455 | free(snap); | |
456 | } | |
457 | ||
458 | /** | |
459 | * Update the user specified portion of the syscall priority | |
460 | * @param db the seccomp filter db | |
461 | * @param syscall the syscall number | |
462 | * @param priority the syscall priority | |
463 | * | |
464 | * This function sets, or updates, the syscall priority; the highest priority | |
465 | * value between the existing and specified value becomes the new syscall | |
466 | * priority. If the syscall entry does not already exist, a new phantom | |
467 | * syscall entry is created as a placeholder. Returns zero on success, | |
468 | * negative values on failure. | |
469 | * | |
470 | */ | |
471 | static int _db_syscall_priority(struct db_filter *db, | |
472 | int syscall, uint8_t priority) | |
473 | { | |
474 | unsigned int sys_pri = _DB_PRI_USER(priority); | |
475 | struct db_sys_list *s_new, *s_iter, *s_prev = NULL; | |
476 | ||
477 | assert(db != NULL); | |
478 | ||
479 | s_iter = db->syscalls; | |
480 | while (s_iter != NULL && s_iter->num < syscall) { | |
481 | s_prev = s_iter; | |
482 | s_iter = s_iter->next; | |
483 | } | |
484 | ||
485 | /* matched an existing syscall entry */ | |
486 | if (s_iter != NULL && s_iter->num == syscall) { | |
487 | if (sys_pri > (s_iter->priority & _DB_PRI_MASK_USER)) { | |
488 | s_iter->priority &= (~_DB_PRI_MASK_USER); | |
489 | s_iter->priority |= sys_pri; | |
490 | } | |
491 | return 0; | |
492 | } | |
493 | ||
494 | /* no existing syscall entry - create a phantom entry */ | |
495 | s_new = malloc(sizeof(*s_new)); | |
496 | if (s_new == NULL) | |
497 | return -ENOMEM; | |
498 | memset(s_new, 0, sizeof(*s_new)); | |
499 | s_new->num = syscall; | |
500 | s_new->priority = sys_pri; | |
501 | s_new->valid = false; | |
502 | ||
503 | /* add it before s_iter */ | |
504 | if (s_prev != NULL) { | |
505 | s_new->next = s_prev->next; | |
506 | s_prev->next = s_new; | |
507 | } else { | |
508 | s_new->next = db->syscalls; | |
509 | db->syscalls = s_new; | |
510 | } | |
511 | ||
512 | return 0; | |
513 | } | |
514 | ||
515 | /** | |
516 | * Free and reset the seccomp filter collection | |
517 | * @param col the seccomp filter collection | |
518 | * @param def_action the default filter action | |
519 | * | |
520 | * This function frees any existing filter DBs and resets the collection to a | |
521 | * default state. In the case of failure the filter collection may be in an | |
522 | * unknown state and should be released. Returns zero on success, negative | |
523 | * values on failure. | |
524 | * | |
525 | */ | |
526 | int db_col_reset(struct db_filter_col *col, uint32_t def_action) | |
527 | { | |
528 | unsigned int iter; | |
529 | struct db_filter *db; | |
530 | struct db_filter_snap *snap; | |
531 | ||
532 | if (col == NULL) | |
533 | return -EINVAL; | |
534 | ||
535 | /* free any filters */ | |
536 | for (iter = 0; iter < col->filter_cnt; iter++) | |
537 | _db_release(col->filters[iter]); | |
538 | col->filter_cnt = 0; | |
539 | if (col->filters) | |
540 | free(col->filters); | |
541 | col->filters = NULL; | |
542 | ||
543 | /* set the endianess to undefined */ | |
544 | col->endian = 0; | |
545 | ||
546 | /* set the default attribute values */ | |
547 | col->attr.act_default = def_action; | |
548 | col->attr.act_badarch = SCMP_ACT_KILL; | |
549 | col->attr.nnp_enable = 1; | |
550 | col->attr.tsync_enable = 0; | |
551 | ||
552 | /* set the state */ | |
553 | col->state = _DB_STA_VALID; | |
554 | ||
555 | /* reset the initial db */ | |
556 | db = _db_init(arch_def_native); | |
557 | if (db == NULL) | |
558 | return -ENOMEM; | |
559 | if (db_col_db_add(col, db) < 0) { | |
560 | _db_release(db); | |
561 | return -ENOMEM; | |
562 | } | |
563 | ||
564 | /* reset the transactions */ | |
565 | while (col->snapshots) { | |
566 | snap = col->snapshots; | |
567 | col->snapshots = snap->next; | |
568 | for (iter = 0; iter < snap->filter_cnt; iter++) | |
569 | _db_release(snap->filters[iter]); | |
570 | free(snap->filters); | |
571 | free(snap); | |
572 | } | |
573 | ||
574 | return 0; | |
575 | } | |
576 | ||
577 | /** | |
578 | * Intitalize a seccomp filter collection | |
579 | * @param def_action the default filter action | |
580 | * | |
581 | * This function initializes a seccomp filter collection and readies it for | |
582 | * use. Returns a pointer to the collection on success, NULL on failure. | |
583 | * | |
584 | */ | |
585 | struct db_filter_col *db_col_init(uint32_t def_action) | |
586 | { | |
587 | struct db_filter_col *col; | |
588 | ||
589 | col = malloc(sizeof(*col)); | |
590 | if (col == NULL) | |
591 | return NULL; | |
592 | ||
593 | /* clear the buffer for the first time */ | |
594 | memset(col, 0, sizeof(*col)); | |
595 | ||
596 | /* reset the DB to a known state */ | |
597 | if (db_col_reset(col, def_action) < 0) | |
598 | goto init_failure; | |
599 | ||
600 | return col; | |
601 | ||
602 | init_failure: | |
603 | db_col_release(col); | |
604 | return NULL; | |
605 | } | |
606 | ||
607 | /** | |
608 | * Destroy a seccomp filter collection | |
609 | * @param col the seccomp filter collection | |
610 | * | |
611 | * This function destroys a seccomp filter collection. After calling this | |
612 | * function, the filter should no longer be referenced. | |
613 | * | |
614 | */ | |
615 | void db_col_release(struct db_filter_col *col) | |
616 | { | |
617 | unsigned int iter; | |
618 | ||
619 | if (col == NULL) | |
620 | return; | |
621 | ||
622 | /* set the state, just in case */ | |
623 | col->state = _DB_STA_FREED; | |
624 | ||
625 | /* free any filters */ | |
626 | for (iter = 0; iter < col->filter_cnt; iter++) | |
627 | _db_release(col->filters[iter]); | |
628 | col->filter_cnt = 0; | |
629 | if (col->filters) | |
630 | free(col->filters); | |
631 | col->filters = NULL; | |
632 | ||
633 | /* free the collection */ | |
634 | free(col); | |
635 | } | |
636 | ||
637 | /** | |
638 | * Validate the seccomp action | |
639 | * @param action the seccomp action | |
640 | * | |
641 | * Verify that the given action is a valid seccomp action; return zero if | |
642 | * valid, -EINVAL if invalid. | |
643 | */ | |
644 | int db_action_valid(uint32_t action) | |
645 | { | |
646 | if (action == SCMP_ACT_KILL) | |
647 | return 0; | |
648 | else if (action == SCMP_ACT_TRAP) | |
649 | return 0; | |
650 | else if ((action == SCMP_ACT_ERRNO(action & 0x0000ffff)) && | |
651 | ((action & 0x0000ffff) < MAX_ERRNO)) | |
652 | return 0; | |
653 | else if (action == SCMP_ACT_TRACE(action & 0x0000ffff)) | |
654 | return 0; | |
655 | else if (action == SCMP_ACT_ALLOW) | |
656 | return 0; | |
657 | ||
658 | return -EINVAL; | |
659 | } | |
660 | ||
661 | /** | |
662 | * Validate a filter collection | |
663 | * @param col the seccomp filter collection | |
664 | * | |
665 | * This function validates a seccomp filter collection. Returns zero if the | |
666 | * collection is valid, negative values on failure. | |
667 | * | |
668 | */ | |
669 | int db_col_valid(struct db_filter_col *col) | |
670 | { | |
671 | if (col != NULL && col->state == _DB_STA_VALID && col->filter_cnt > 0) | |
672 | return 0; | |
673 | return -EINVAL; | |
674 | } | |
675 | ||
676 | /** | |
677 | * Merge two filter collections | |
678 | * @param col_dst the destination filter collection | |
679 | * @param col_src the source filter collection | |
680 | * | |
681 | * This function merges two filter collections into the given destination | |
682 | * collection. The source filter collection is no longer valid if the function | |
683 | * returns successfully. Returns zero on success, negative values on failure. | |
684 | * | |
685 | */ | |
686 | int db_col_merge(struct db_filter_col *col_dst, struct db_filter_col *col_src) | |
687 | { | |
688 | unsigned int iter_a, iter_b; | |
689 | struct db_filter **dbs; | |
690 | ||
691 | /* verify that the endianess is a match */ | |
692 | if (col_dst->endian != col_src->endian) | |
693 | return -EEXIST; | |
694 | ||
695 | /* make sure we don't have any arch/filter collisions */ | |
696 | for (iter_a = 0; iter_a < col_dst->filter_cnt; iter_a++) { | |
697 | for (iter_b = 0; iter_b < col_src->filter_cnt; iter_b++) { | |
698 | if (col_dst->filters[iter_a]->arch->token == | |
699 | col_src->filters[iter_b]->arch->token) | |
700 | return -EEXIST; | |
701 | } | |
702 | } | |
703 | ||
704 | /* expand the destination */ | |
705 | dbs = realloc(col_dst->filters, | |
706 | sizeof(struct db_filter *) * | |
707 | (col_dst->filter_cnt + col_src->filter_cnt)); | |
708 | if (dbs == NULL) | |
709 | return -ENOMEM; | |
710 | col_dst->filters = dbs; | |
711 | ||
712 | /* transfer the architecture filters */ | |
713 | for (iter_a = col_dst->filter_cnt, iter_b = 0; | |
714 | iter_b < col_src->filter_cnt; iter_a++, iter_b++) { | |
715 | col_dst->filters[iter_a] = col_src->filters[iter_b]; | |
716 | col_dst->filter_cnt++; | |
717 | } | |
718 | ||
719 | /* free the source */ | |
720 | col_src->filter_cnt = 0; | |
721 | db_col_release(col_src); | |
722 | ||
723 | return 0; | |
724 | } | |
725 | ||
726 | /** | |
727 | * Check to see if an architecture filter exists in the filter collection | |
728 | * @param col the seccomp filter collection | |
729 | * @param arch_token the architecture token | |
730 | * | |
731 | * Iterate through the given filter collection checking to see if a filter | |
732 | * exists for the specified architecture. Returns -EEXIST if a filter is found, | |
733 | * zero if a matching filter does not exist. | |
734 | * | |
735 | */ | |
736 | int db_col_arch_exist(struct db_filter_col *col, uint32_t arch_token) | |
737 | { | |
738 | unsigned int iter; | |
739 | ||
740 | for (iter = 0; iter < col->filter_cnt; iter++) | |
741 | if (col->filters[iter]->arch->token == arch_token) | |
742 | return -EEXIST; | |
743 | ||
744 | return 0; | |
745 | } | |
746 | ||
747 | /** | |
748 | * Get a filter attribute | |
749 | * @param col the seccomp filter collection | |
750 | * @param attr the filter attribute | |
751 | * @param value the filter attribute value | |
752 | * | |
753 | * Get the requested filter attribute and provide it via @value. Returns zero | |
754 | * on success, negative values on failure. | |
755 | * | |
756 | */ | |
757 | int db_col_attr_get(const struct db_filter_col *col, | |
758 | enum scmp_filter_attr attr, uint32_t *value) | |
759 | { | |
760 | int rc = 0; | |
761 | ||
762 | switch (attr) { | |
763 | case SCMP_FLTATR_ACT_DEFAULT: | |
764 | *value = col->attr.act_default; | |
765 | break; | |
766 | case SCMP_FLTATR_ACT_BADARCH: | |
767 | *value = col->attr.act_badarch; | |
768 | break; | |
769 | case SCMP_FLTATR_CTL_NNP: | |
770 | *value = col->attr.nnp_enable; | |
771 | break; | |
772 | case SCMP_FLTATR_CTL_TSYNC: | |
773 | *value = col->attr.tsync_enable; | |
774 | break; | |
775 | default: | |
776 | rc = -EEXIST; | |
777 | break; | |
778 | } | |
779 | ||
780 | return rc; | |
781 | } | |
782 | ||
783 | /** | |
784 | * Set a filter attribute | |
785 | * @param col the seccomp filter collection | |
786 | * @param attr the filter attribute | |
787 | * @param value the filter attribute value | |
788 | * | |
789 | * Set the requested filter attribute with the given value. Returns zero on | |
790 | * success, negative values on failure. | |
791 | * | |
792 | */ | |
793 | int db_col_attr_set(struct db_filter_col *col, | |
794 | enum scmp_filter_attr attr, uint32_t value) | |
795 | { | |
796 | int rc = 0; | |
797 | ||
798 | switch (attr) { | |
799 | case SCMP_FLTATR_ACT_DEFAULT: | |
800 | /* read only */ | |
801 | return -EACCES; | |
802 | break; | |
803 | case SCMP_FLTATR_ACT_BADARCH: | |
804 | if (db_action_valid(value) == 0) | |
805 | col->attr.act_badarch = value; | |
806 | else | |
807 | return -EINVAL; | |
808 | break; | |
809 | case SCMP_FLTATR_CTL_NNP: | |
810 | col->attr.nnp_enable = (value ? 1 : 0); | |
811 | break; | |
812 | case SCMP_FLTATR_CTL_TSYNC: | |
813 | rc = sys_chk_seccomp_flag(SECCOMP_FILTER_FLAG_TSYNC); | |
814 | if (rc == 1) { | |
815 | /* supported */ | |
816 | rc = 0; | |
817 | col->attr.tsync_enable = (value ? 1 : 0); | |
818 | } else if (rc == 0) | |
819 | /* unsupported */ | |
820 | rc = -EOPNOTSUPP; | |
821 | break; | |
822 | default: | |
823 | rc = -EEXIST; | |
824 | break; | |
825 | } | |
826 | ||
827 | return rc; | |
828 | } | |
829 | ||
830 | /** | |
831 | * Add a new architecture filter to a filter collection | |
832 | * @param col the seccomp filter collection | |
833 | * @param arch the architecture | |
834 | * | |
835 | * This function adds a new architecture filter DB to an existing seccomp | |
836 | * filter collection assuming there isn't a filter DB already present with the | |
837 | * same architecture. Returns zero on success, negative values on failure. | |
838 | * | |
839 | */ | |
840 | int db_col_db_new(struct db_filter_col *col, const struct arch_def *arch) | |
841 | { | |
842 | int rc; | |
843 | struct db_filter *db; | |
844 | ||
845 | db = _db_init(arch); | |
846 | if (db == NULL) | |
847 | return -ENOMEM; | |
848 | rc = db_col_db_add(col, db); | |
849 | if (rc < 0) | |
850 | _db_release(db); | |
851 | ||
852 | return rc; | |
853 | } | |
854 | ||
855 | /** | |
856 | * Add a new filter DB to a filter collection | |
857 | * @param col the seccomp filter collection | |
858 | * @param db the seccomp filter DB | |
859 | * | |
860 | * This function adds an existing seccomp filter DB to an existing seccomp | |
861 | * filter collection assuming there isn't a filter DB already present with the | |
862 | * same architecture. Returns zero on success, negative values on failure. | |
863 | * | |
864 | */ | |
865 | int db_col_db_add(struct db_filter_col *col, struct db_filter *db) | |
866 | { | |
867 | struct db_filter **dbs; | |
868 | ||
869 | if (col->endian != 0 && col->endian != db->arch->endian) | |
870 | return -EEXIST; | |
871 | ||
872 | if (db_col_arch_exist(col, db->arch->token)) | |
873 | return -EEXIST; | |
874 | ||
875 | dbs = realloc(col->filters, | |
876 | sizeof(struct db_filter *) * (col->filter_cnt + 1)); | |
877 | if (dbs == NULL) | |
878 | return -ENOMEM; | |
879 | col->filters = dbs; | |
880 | col->filter_cnt++; | |
881 | col->filters[col->filter_cnt - 1] = db; | |
882 | if (col->endian == 0) | |
883 | col->endian = db->arch->endian; | |
884 | ||
885 | return 0; | |
886 | } | |
887 | ||
888 | /** | |
889 | * Remove a filter DB from a filter collection | |
890 | * @param col the seccomp filter collection | |
891 | * @param arch_token the architecture token | |
892 | * | |
893 | * This function removes an existing seccomp filter DB from an existing seccomp | |
894 | * filter collection. Returns zero on success, negative values on failure. | |
895 | * | |
896 | */ | |
897 | int db_col_db_remove(struct db_filter_col *col, uint32_t arch_token) | |
898 | { | |
899 | unsigned int iter; | |
900 | unsigned int found; | |
901 | struct db_filter **dbs; | |
902 | ||
903 | if ((col->filter_cnt <= 0) || (db_col_arch_exist(col, arch_token) == 0)) | |
904 | return -EINVAL; | |
905 | ||
906 | for (found = 0, iter = 0; iter < col->filter_cnt; iter++) { | |
907 | if (found) | |
908 | col->filters[iter - 1] = col->filters[iter]; | |
909 | else if (col->filters[iter]->arch->token == arch_token) { | |
910 | _db_release(col->filters[iter]); | |
911 | found = 1; | |
912 | } | |
913 | } | |
914 | col->filters[--col->filter_cnt] = NULL; | |
915 | ||
916 | if (col->filter_cnt > 0) { | |
917 | /* NOTE: if we can't do the realloc it isn't fatal, we just | |
918 | * have some extra space allocated */ | |
919 | dbs = realloc(col->filters, | |
920 | sizeof(struct db_filter *) * col->filter_cnt); | |
921 | if (dbs != NULL) | |
922 | col->filters = dbs; | |
923 | } else { | |
924 | /* this was the last filter so free all the associated memory | |
925 | * and reset the endian token */ | |
926 | free(col->filters); | |
927 | col->filters = NULL; | |
928 | col->endian = 0; | |
929 | } | |
930 | ||
931 | return 0; | |
932 | } | |
933 | ||
934 | /** | |
935 | * Test if the argument filter can be skipped because it's a tautology | |
936 | * @param arg argument filter | |
937 | * | |
938 | * If this argument filter applied to the lower 32 bit can be skipped this | |
939 | * function returns false. | |
940 | * | |
941 | */ | |
942 | static bool _db_arg_cmp_need_lo(const struct db_api_arg *arg) | |
943 | { | |
944 | if (arg->op == SCMP_CMP_MASKED_EQ && D64_LO(arg->mask) == 0) | |
945 | return false; | |
946 | ||
947 | return true; | |
948 | } | |
949 | ||
950 | /** | |
951 | * Test if the argument filter can be skipped because it's a tautology | |
952 | * @param arg argument filter | |
953 | * | |
954 | * If this argument filter applied to the upper 32 bit can be skipped this | |
955 | * function returns false. | |
956 | * | |
957 | */ | |
958 | static bool _db_arg_cmp_need_hi(const struct db_api_arg *arg) | |
959 | { | |
960 | if (arg->op == SCMP_CMP_MASKED_EQ && D64_HI(arg->mask) == 0) | |
961 | return false; | |
962 | ||
963 | return true; | |
964 | } | |
965 | ||
966 | /** | |
967 | * Fixup the node based on the op/mask | |
968 | * @param node the chain node | |
969 | * | |
970 | * Ensure the datum is masked as well. | |
971 | * | |
972 | */ | |
973 | static void _db_node_mask_fixup(struct db_arg_chain_tree *node) | |
974 | { | |
975 | node->datum &= node->mask; | |
976 | } | |
977 | ||
978 | /** | |
979 | * Generate a new filter rule for a 64 bit system | |
980 | * @param arch the architecture definition | |
981 | * @param action the filter action | |
982 | * @param syscall the syscall number | |
983 | * @param chain argument filter chain | |
984 | * | |
985 | * This function generates a new syscall filter for a 64 bit system. Returns | |
986 | * zero on success, negative values on failure. | |
987 | * | |
988 | */ | |
989 | static struct db_sys_list *_db_rule_gen_64(const struct arch_def *arch, | |
990 | uint32_t action, | |
991 | unsigned int syscall, | |
992 | struct db_api_arg *chain) | |
993 | { | |
994 | unsigned int iter; | |
995 | int chain_len_max; | |
996 | struct db_sys_list *s_new; | |
997 | struct db_arg_chain_tree *c_iter_hi = NULL, *c_iter_lo = NULL; | |
998 | struct db_arg_chain_tree *c_prev_hi = NULL, *c_prev_lo = NULL; | |
999 | bool tf_flag; | |
1000 | ||
1001 | s_new = malloc(sizeof(*s_new)); | |
1002 | if (s_new == NULL) | |
1003 | return NULL; | |
1004 | memset(s_new, 0, sizeof(*s_new)); | |
1005 | s_new->num = syscall; | |
1006 | s_new->valid = true; | |
1007 | /* run through the argument chain */ | |
1008 | chain_len_max = arch_arg_count_max(arch); | |
1009 | if (chain_len_max < 0) | |
1010 | goto gen_64_failure; | |
1011 | for (iter = 0; iter < chain_len_max; iter++) { | |
1012 | if (chain[iter].valid == 0) | |
1013 | continue; | |
1014 | ||
1015 | /* TODO: handle the case were either hi or lo isn't needed */ | |
1016 | ||
1017 | /* skip generating instruction which are no-ops */ | |
1018 | if (!_db_arg_cmp_need_hi(&chain[iter]) && | |
1019 | !_db_arg_cmp_need_lo(&chain[iter])) | |
1020 | continue; | |
1021 | ||
1022 | c_iter_hi = malloc(sizeof(*c_iter_hi)); | |
1023 | if (c_iter_hi == NULL) | |
1024 | goto gen_64_failure; | |
1025 | memset(c_iter_hi, 0, sizeof(*c_iter_hi)); | |
1026 | c_iter_hi->refcnt = 1; | |
1027 | c_iter_lo = malloc(sizeof(*c_iter_lo)); | |
1028 | if (c_iter_lo == NULL) { | |
1029 | free(c_iter_hi); | |
1030 | goto gen_64_failure; | |
1031 | } | |
1032 | memset(c_iter_lo, 0, sizeof(*c_iter_lo)); | |
1033 | c_iter_lo->refcnt = 1; | |
1034 | ||
1035 | /* link this level to the previous level */ | |
1036 | if (c_prev_lo != NULL) { | |
1037 | if (!tf_flag) { | |
1038 | c_prev_lo->nxt_f = c_iter_hi; | |
1039 | c_prev_hi->nxt_f = c_iter_hi; | |
1040 | c_iter_hi->refcnt++; | |
1041 | } else | |
1042 | c_prev_lo->nxt_t = c_iter_hi; | |
1043 | } else | |
1044 | s_new->chains = c_iter_hi; | |
1045 | s_new->node_cnt += 2; | |
1046 | ||
1047 | /* set the arg, op, and datum fields */ | |
1048 | c_iter_hi->arg = chain[iter].arg; | |
1049 | c_iter_lo->arg = chain[iter].arg; | |
1050 | c_iter_hi->arg_offset = arch_arg_offset_hi(arch, | |
1051 | c_iter_hi->arg); | |
1052 | c_iter_lo->arg_offset = arch_arg_offset_lo(arch, | |
1053 | c_iter_lo->arg); | |
1054 | switch (chain[iter].op) { | |
1055 | case SCMP_CMP_GT: | |
1056 | c_iter_hi->op = SCMP_CMP_GE; | |
1057 | c_iter_lo->op = SCMP_CMP_GT; | |
1058 | tf_flag = true; | |
1059 | break; | |
1060 | case SCMP_CMP_NE: | |
1061 | c_iter_hi->op = SCMP_CMP_EQ; | |
1062 | c_iter_lo->op = SCMP_CMP_EQ; | |
1063 | tf_flag = false; | |
1064 | break; | |
1065 | case SCMP_CMP_LT: | |
1066 | c_iter_hi->op = SCMP_CMP_GE; | |
1067 | c_iter_lo->op = SCMP_CMP_GE; | |
1068 | tf_flag = false; | |
1069 | break; | |
1070 | case SCMP_CMP_LE: | |
1071 | c_iter_hi->op = SCMP_CMP_GE; | |
1072 | c_iter_lo->op = SCMP_CMP_GT; | |
1073 | tf_flag = false; | |
1074 | break; | |
1075 | default: | |
1076 | c_iter_hi->op = chain[iter].op; | |
1077 | c_iter_lo->op = chain[iter].op; | |
1078 | tf_flag = true; | |
1079 | } | |
1080 | c_iter_hi->mask = D64_HI(chain[iter].mask); | |
1081 | c_iter_lo->mask = D64_LO(chain[iter].mask); | |
1082 | c_iter_hi->datum = D64_HI(chain[iter].datum); | |
1083 | c_iter_lo->datum = D64_LO(chain[iter].datum); | |
1084 | ||
1085 | /* fixup the mask/datum */ | |
1086 | _db_node_mask_fixup(c_iter_hi); | |
1087 | _db_node_mask_fixup(c_iter_lo); | |
1088 | ||
1089 | /* link the hi and lo chain nodes */ | |
1090 | c_iter_hi->nxt_t = c_iter_lo; | |
1091 | ||
1092 | c_prev_hi = c_iter_hi; | |
1093 | c_prev_lo = c_iter_lo; | |
1094 | } | |
1095 | if (c_iter_lo != NULL) { | |
1096 | /* set the leaf node */ | |
1097 | if (!tf_flag) { | |
1098 | c_iter_lo->act_f_flg = true; | |
1099 | c_iter_lo->act_f = action; | |
1100 | c_iter_hi->act_f_flg = true; | |
1101 | c_iter_hi->act_f = action; | |
1102 | } else { | |
1103 | c_iter_lo->act_t_flg = true; | |
1104 | c_iter_lo->act_t = action; | |
1105 | } | |
1106 | } else | |
1107 | s_new->action = action; | |
1108 | ||
1109 | return s_new; | |
1110 | ||
1111 | gen_64_failure: | |
1112 | /* free the new chain and its syscall struct */ | |
1113 | _db_tree_free(s_new->chains); | |
1114 | free(s_new); | |
1115 | return NULL; | |
1116 | } | |
1117 | ||
1118 | /** | |
1119 | * Generate a new filter rule for a 32 bit system | |
1120 | * @param arch the architecture definition | |
1121 | * @param action the filter action | |
1122 | * @param syscall the syscall number | |
1123 | * @param chain argument filter chain | |
1124 | * | |
1125 | * This function generates a new syscall filter for a 32 bit system. Returns | |
1126 | * zero on success, negative values on failure. | |
1127 | * | |
1128 | */ | |
1129 | static struct db_sys_list *_db_rule_gen_32(const struct arch_def *arch, | |
1130 | uint32_t action, | |
1131 | unsigned int syscall, | |
1132 | struct db_api_arg *chain) | |
1133 | { | |
1134 | unsigned int iter; | |
1135 | int chain_len_max; | |
1136 | struct db_sys_list *s_new; | |
1137 | struct db_arg_chain_tree *c_iter = NULL, *c_prev = NULL; | |
1138 | bool tf_flag; | |
1139 | ||
1140 | s_new = malloc(sizeof(*s_new)); | |
1141 | if (s_new == NULL) | |
1142 | return NULL; | |
1143 | memset(s_new, 0, sizeof(*s_new)); | |
1144 | s_new->num = syscall; | |
1145 | s_new->valid = true; | |
1146 | /* run through the argument chain */ | |
1147 | chain_len_max = arch_arg_count_max(arch); | |
1148 | if (chain_len_max < 0) | |
1149 | goto gen_32_failure; | |
1150 | for (iter = 0; iter < chain_len_max; iter++) { | |
1151 | if (chain[iter].valid == 0) | |
1152 | continue; | |
1153 | ||
1154 | /* skip generating instructions which are no-ops */ | |
1155 | if (!_db_arg_cmp_need_lo(&chain[iter])) | |
1156 | continue; | |
1157 | ||
1158 | c_iter = malloc(sizeof(*c_iter)); | |
1159 | if (c_iter == NULL) | |
1160 | goto gen_32_failure; | |
1161 | memset(c_iter, 0, sizeof(*c_iter)); | |
1162 | c_iter->refcnt = 1; | |
1163 | c_iter->arg = chain[iter].arg; | |
1164 | c_iter->arg_offset = arch_arg_offset(arch, c_iter->arg); | |
1165 | c_iter->op = chain[iter].op; | |
1166 | /* implicitly strips off the upper 32 bit */ | |
1167 | c_iter->mask = chain[iter].mask; | |
1168 | c_iter->datum = chain[iter].datum; | |
1169 | ||
1170 | /* link in the new node and update the chain */ | |
1171 | if (c_prev != NULL) { | |
1172 | if (tf_flag) | |
1173 | c_prev->nxt_t = c_iter; | |
1174 | else | |
1175 | c_prev->nxt_f = c_iter; | |
1176 | } else | |
1177 | s_new->chains = c_iter; | |
1178 | s_new->node_cnt++; | |
1179 | ||
1180 | /* rewrite the op to reduce the op/datum combos */ | |
1181 | switch (c_iter->op) { | |
1182 | case SCMP_CMP_NE: | |
1183 | c_iter->op = SCMP_CMP_EQ; | |
1184 | tf_flag = false; | |
1185 | break; | |
1186 | case SCMP_CMP_LT: | |
1187 | c_iter->op = SCMP_CMP_GE; | |
1188 | tf_flag = false; | |
1189 | break; | |
1190 | case SCMP_CMP_LE: | |
1191 | c_iter->op = SCMP_CMP_GT; | |
1192 | tf_flag = false; | |
1193 | break; | |
1194 | default: | |
1195 | tf_flag = true; | |
1196 | } | |
1197 | ||
1198 | /* fixup the mask/datum */ | |
1199 | _db_node_mask_fixup(c_iter); | |
1200 | ||
1201 | c_prev = c_iter; | |
1202 | } | |
1203 | if (c_iter != NULL) { | |
1204 | /* set the leaf node */ | |
1205 | if (tf_flag) { | |
1206 | c_iter->act_t_flg = true; | |
1207 | c_iter->act_t = action; | |
1208 | } else { | |
1209 | c_iter->act_f_flg = true; | |
1210 | c_iter->act_f = action; | |
1211 | } | |
1212 | } else | |
1213 | s_new->action = action; | |
1214 | ||
1215 | return s_new; | |
1216 | ||
1217 | gen_32_failure: | |
1218 | /* free the new chain and its syscall struct */ | |
1219 | _db_tree_free(s_new->chains); | |
1220 | free(s_new); | |
1221 | return NULL; | |
1222 | } | |
1223 | ||
1224 | /** | |
1225 | * Add a new rule to the seccomp filter DB | |
1226 | * @param db the seccomp filter db | |
1227 | * @param rule the filter rule | |
1228 | * | |
1229 | * This function adds a new syscall filter to the seccomp filter DB, adding to | |
1230 | * the existing filters for the syscall, unless no argument specific filters | |
1231 | * are present (filtering only on the syscall). When adding new chains, the | |
1232 | * shortest chain, or most inclusive filter match, will be entered into the | |
1233 | * filter DB. Returns zero on success, negative values on failure. | |
1234 | * | |
1235 | */ | |
1236 | int db_rule_add(struct db_filter *db, const struct db_api_rule_list *rule) | |
1237 | { | |
1238 | int rc = -ENOMEM; | |
1239 | int syscall = rule->syscall; | |
1240 | uint32_t action = rule->action; | |
1241 | struct db_api_arg *chain = rule->args; | |
1242 | struct db_sys_list *s_new, *s_iter, *s_prev = NULL; | |
1243 | struct db_arg_chain_tree *c_iter = NULL, *c_prev = NULL; | |
1244 | struct db_arg_chain_tree *ec_iter; | |
1245 | struct db_prune_state state; | |
1246 | bool rm_flag = false; | |
1247 | unsigned int new_chain_cnt = 0; | |
1248 | unsigned int n_cnt; | |
1249 | ||
1250 | assert(db != NULL); | |
1251 | ||
1252 | /* do all our possible memory allocation up front so we don't have to | |
1253 | * worry about failure once we get to the point where we start updating | |
1254 | * the filter db */ | |
1255 | if (db->arch->size == ARCH_SIZE_64) | |
1256 | s_new = _db_rule_gen_64(db->arch, action, syscall, chain); | |
1257 | else if (db->arch->size == ARCH_SIZE_32) | |
1258 | s_new = _db_rule_gen_32(db->arch, action, syscall, chain); | |
1259 | else | |
1260 | return -EFAULT; | |
1261 | if (s_new == NULL) | |
1262 | return -ENOMEM; | |
1263 | new_chain_cnt = s_new->node_cnt; | |
1264 | ||
1265 | /* no more failures allowed after this point that would result in the | |
1266 | * stored filter being in an inconsistent state */ | |
1267 | ||
1268 | /* find a matching syscall/chain or insert a new one */ | |
1269 | s_iter = db->syscalls; | |
1270 | while (s_iter != NULL && s_iter->num < syscall) { | |
1271 | s_prev = s_iter; | |
1272 | s_iter = s_iter->next; | |
1273 | } | |
1274 | add_reset: | |
1275 | s_new->node_cnt = new_chain_cnt; | |
1276 | s_new->priority = _DB_PRI_MASK_CHAIN - s_new->node_cnt; | |
1277 | c_prev = NULL; | |
1278 | c_iter = s_new->chains; | |
1279 | if (s_iter != NULL) | |
1280 | ec_iter = s_iter->chains; | |
1281 | else | |
1282 | ec_iter = NULL; | |
1283 | if (s_iter == NULL || s_iter->num != syscall) { | |
1284 | /* new syscall, add before s_iter */ | |
1285 | if (s_prev != NULL) { | |
1286 | s_new->next = s_prev->next; | |
1287 | s_prev->next = s_new; | |
1288 | } else { | |
1289 | s_new->next = db->syscalls; | |
1290 | db->syscalls = s_new; | |
1291 | } | |
1292 | return 0; | |
1293 | } else if (s_iter->chains == NULL) { | |
1294 | if (rm_flag || !s_iter->valid) { | |
1295 | /* we are here because our previous pass cleared the | |
1296 | * entire syscall chain when searching for a subtree | |
1297 | * match or the existing syscall entry is a phantom, | |
1298 | * so either way add the new chain */ | |
1299 | s_iter->chains = s_new->chains; | |
1300 | s_iter->action = s_new->action; | |
1301 | s_iter->node_cnt = s_new->node_cnt; | |
1302 | if (s_iter->valid) | |
1303 | s_iter->priority = s_new->priority; | |
1304 | s_iter->valid = true; | |
1305 | free(s_new); | |
1306 | rc = 0; | |
1307 | goto add_priority_update; | |
1308 | } else | |
1309 | /* syscall exists without any chains - existing filter | |
1310 | * is at least as large as the new entry so cleanup and | |
1311 | * exit */ | |
1312 | goto add_free_ok; | |
1313 | } else if (s_iter->chains != NULL && s_new->chains == NULL) { | |
1314 | /* syscall exists with chains but the new filter has no chains | |
1315 | * so we need to clear the existing chains and exit */ | |
1316 | _db_tree_free(s_iter->chains); | |
1317 | s_iter->chains = NULL; | |
1318 | s_iter->node_cnt = 0; | |
1319 | s_iter->action = action; | |
1320 | goto add_free_ok; | |
1321 | } | |
1322 | ||
1323 | /* check for sub-tree matches */ | |
1324 | memset(&state, 0, sizeof(state)); | |
1325 | rc = _db_tree_sub_prune(&(s_iter->chains), ec_iter, c_iter, &state); | |
1326 | if (rc > 0) { | |
1327 | rm_flag = true; | |
1328 | s_iter->node_cnt -= rc; | |
1329 | goto add_reset; | |
1330 | } else if (rc < 0) | |
1331 | goto add_free_ok; | |
1332 | ||
1333 | /* syscall exists and has at least one existing chain - start at the | |
1334 | * top and walk the two chains */ | |
1335 | do { | |
1336 | /* insert the new rule into the existing tree */ | |
1337 | if (db_chain_eq(c_iter, ec_iter)) { | |
1338 | /* found a matching node on this chain level */ | |
1339 | if (db_chain_action(c_iter) && | |
1340 | db_chain_action(ec_iter)) { | |
1341 | /* both are "action" nodes */ | |
1342 | if (c_iter->act_t_flg && ec_iter->act_t_flg) { | |
1343 | if (ec_iter->act_t != action) | |
1344 | goto add_free_exist; | |
1345 | } else if (c_iter->act_t_flg) { | |
1346 | ec_iter->act_t_flg = true; | |
1347 | ec_iter->act_t = action; | |
1348 | } | |
1349 | if (c_iter->act_f_flg && ec_iter->act_f_flg) { | |
1350 | if (ec_iter->act_f != action) | |
1351 | goto add_free_exist; | |
1352 | } else if (c_iter->act_f_flg) { | |
1353 | ec_iter->act_f_flg = true; | |
1354 | ec_iter->act_f = action; | |
1355 | } | |
1356 | if (ec_iter->act_t_flg == ec_iter->act_f_flg && | |
1357 | ec_iter->act_t == ec_iter->act_f) { | |
1358 | n_cnt = _db_tree_remove( | |
1359 | &(s_iter->chains), | |
1360 | ec_iter); | |
1361 | s_iter->node_cnt -= n_cnt; | |
1362 | goto add_free_ok; | |
1363 | } | |
1364 | } else if (db_chain_action(c_iter)) { | |
1365 | /* new is shorter */ | |
1366 | if (c_iter->act_t_flg) { | |
1367 | rc = _db_tree_act_check(ec_iter->nxt_t, | |
1368 | action); | |
1369 | if (rc < 0) | |
1370 | goto add_free; | |
1371 | n_cnt = _db_tree_free(ec_iter->nxt_t); | |
1372 | ec_iter->nxt_t = NULL; | |
1373 | ec_iter->act_t_flg = true; | |
1374 | ec_iter->act_t = action; | |
1375 | } else { | |
1376 | rc = _db_tree_act_check(ec_iter->nxt_f, | |
1377 | action); | |
1378 | if (rc < 0) | |
1379 | goto add_free; | |
1380 | n_cnt = _db_tree_free(ec_iter->nxt_f); | |
1381 | ec_iter->nxt_f = NULL; | |
1382 | ec_iter->act_f_flg = true; | |
1383 | ec_iter->act_f = action; | |
1384 | } | |
1385 | s_iter->node_cnt -= n_cnt; | |
1386 | } | |
1387 | if (c_iter->nxt_t != NULL) { | |
1388 | if (ec_iter->nxt_t != NULL) { | |
1389 | /* jump to the next level */ | |
1390 | c_prev = c_iter; | |
1391 | c_iter = c_iter->nxt_t; | |
1392 | ec_iter = ec_iter->nxt_t; | |
1393 | s_new->node_cnt--; | |
1394 | } else if (ec_iter->act_t_flg) { | |
1395 | /* existing is shorter */ | |
1396 | if (ec_iter->act_t == action) | |
1397 | goto add_free_ok; | |
1398 | goto add_free_exist; | |
1399 | } else { | |
1400 | /* add a new branch */ | |
1401 | c_prev = c_iter; | |
1402 | ec_iter->nxt_t = c_iter->nxt_t; | |
1403 | s_iter->node_cnt += | |
1404 | (s_new->node_cnt - 1); | |
1405 | goto add_free_match; | |
1406 | } | |
1407 | } else if (c_iter->nxt_f != NULL) { | |
1408 | if (ec_iter->nxt_f != NULL) { | |
1409 | /* jump to the next level */ | |
1410 | c_prev = c_iter; | |
1411 | c_iter = c_iter->nxt_f; | |
1412 | ec_iter = ec_iter->nxt_f; | |
1413 | s_new->node_cnt--; | |
1414 | } else if (ec_iter->act_f_flg) { | |
1415 | /* existing is shorter */ | |
1416 | if (ec_iter->act_f == action) | |
1417 | goto add_free_ok; | |
1418 | goto add_free_exist; | |
1419 | } else { | |
1420 | /* add a new branch */ | |
1421 | c_prev = c_iter; | |
1422 | ec_iter->nxt_f = c_iter->nxt_f; | |
1423 | s_iter->node_cnt += | |
1424 | (s_new->node_cnt - 1); | |
1425 | goto add_free_match; | |
1426 | } | |
1427 | } else | |
1428 | goto add_free_ok; | |
1429 | } else { | |
1430 | /* need to check other nodes on this level */ | |
1431 | if (db_chain_lt(c_iter, ec_iter)) { | |
1432 | if (ec_iter->lvl_prv == NULL) { | |
1433 | /* add to the start of the level */ | |
1434 | ec_iter->lvl_prv = c_iter; | |
1435 | c_iter->lvl_nxt = ec_iter; | |
1436 | if (ec_iter == s_iter->chains) | |
1437 | s_iter->chains = c_iter; | |
1438 | s_iter->node_cnt += s_new->node_cnt; | |
1439 | goto add_free_match; | |
1440 | } else | |
1441 | ec_iter = ec_iter->lvl_prv; | |
1442 | } else { | |
1443 | if (ec_iter->lvl_nxt == NULL) { | |
1444 | /* add to the end of the level */ | |
1445 | ec_iter->lvl_nxt = c_iter; | |
1446 | c_iter->lvl_prv = ec_iter; | |
1447 | s_iter->node_cnt += s_new->node_cnt; | |
1448 | goto add_free_match; | |
1449 | } else if (db_chain_lt(c_iter, | |
1450 | ec_iter->lvl_nxt)) { | |
1451 | /* add new chain in between */ | |
1452 | c_iter->lvl_nxt = ec_iter->lvl_nxt; | |
1453 | ec_iter->lvl_nxt->lvl_prv = c_iter; | |
1454 | ec_iter->lvl_nxt = c_iter; | |
1455 | c_iter->lvl_prv = ec_iter; | |
1456 | s_iter->node_cnt += s_new->node_cnt; | |
1457 | goto add_free_match; | |
1458 | } else | |
1459 | ec_iter = ec_iter->lvl_nxt; | |
1460 | } | |
1461 | } | |
1462 | } while ((c_iter != NULL) && (ec_iter != NULL)); | |
1463 | ||
1464 | /* we should never be here! */ | |
1465 | return -EFAULT; | |
1466 | ||
1467 | add_free_exist: | |
1468 | rc = -EEXIST; | |
1469 | goto add_free; | |
1470 | add_free_ok: | |
1471 | rc = 0; | |
1472 | add_free: | |
1473 | /* free the new chain and its syscall struct */ | |
1474 | _db_tree_free(s_new->chains); | |
1475 | free(s_new); | |
1476 | goto add_priority_update; | |
1477 | add_free_match: | |
1478 | /* free the matching portion of new chain */ | |
1479 | if (c_prev != NULL) { | |
1480 | c_prev->nxt_t = NULL; | |
1481 | c_prev->nxt_f = NULL; | |
1482 | _db_tree_free(s_new->chains); | |
1483 | } | |
1484 | free(s_new); | |
1485 | rc = 0; | |
1486 | add_priority_update: | |
1487 | /* update the priority */ | |
1488 | if (s_iter != NULL) { | |
1489 | s_iter->priority &= (~_DB_PRI_MASK_CHAIN); | |
1490 | s_iter->priority |= (_DB_PRI_MASK_CHAIN - s_iter->node_cnt); | |
1491 | } | |
1492 | return rc; | |
1493 | } | |
1494 | ||
1495 | /** | |
1496 | * Set the priority of a given syscall | |
1497 | * @param col the filter collection | |
1498 | * @param syscall the syscall number | |
1499 | * @param priority priority value, higher value == higher priority | |
1500 | * | |
1501 | * This function sets the priority of the given syscall; this value is used | |
1502 | * when generating the seccomp filter code such that higher priority syscalls | |
1503 | * will incur less filter code overhead than the lower priority syscalls in the | |
1504 | * filter. Returns zero on success, negative values on failure. | |
1505 | * | |
1506 | */ | |
1507 | int db_col_syscall_priority(struct db_filter_col *col, | |
1508 | int syscall, uint8_t priority) | |
1509 | { | |
1510 | int rc = 0, rc_tmp; | |
1511 | unsigned int iter; | |
1512 | int sc_tmp; | |
1513 | struct db_filter *filter; | |
1514 | ||
1515 | for (iter = 0; iter < col->filter_cnt; iter++) { | |
1516 | filter = col->filters[iter]; | |
1517 | sc_tmp = syscall; | |
1518 | ||
1519 | rc_tmp = arch_syscall_translate(filter->arch, &sc_tmp); | |
1520 | if (rc_tmp < 0) | |
1521 | goto priority_failure; | |
1522 | ||
1523 | /* if this is a pseudo syscall (syscall < 0) then we need to | |
1524 | * rewrite the syscall for some arch specific reason */ | |
1525 | if (sc_tmp < 0) { | |
1526 | /* we set this as a strict op - we don't really care | |
1527 | * since priorities are a "best effort" thing - as we | |
1528 | * want to catch the -EDOM error and bail on this | |
1529 | * architecture */ | |
1530 | rc_tmp = arch_syscall_rewrite(filter->arch, &sc_tmp); | |
1531 | if (rc_tmp == -EDOM) | |
1532 | continue; | |
1533 | if (rc_tmp < 0) | |
1534 | goto priority_failure; | |
1535 | } | |
1536 | ||
1537 | rc_tmp = _db_syscall_priority(filter, sc_tmp, priority); | |
1538 | ||
1539 | priority_failure: | |
1540 | if (rc == 0 && rc_tmp < 0) | |
1541 | rc = rc_tmp; | |
1542 | } | |
1543 | ||
1544 | return rc; | |
1545 | } | |
1546 | ||
1547 | /** | |
1548 | * Add a new rule to the current filter | |
1549 | * @param col the filter collection | |
1550 | * @param strict the strict flag | |
1551 | * @param action the filter action | |
1552 | * @param syscall the syscall number | |
1553 | * @param arg_cnt the number of argument filters in the argument filter chain | |
1554 | * @param arg_array the argument filter chain, (uint, enum scmp_compare, ulong) | |
1555 | * | |
1556 | * This function adds a new argument/comparison/value to the seccomp filter for | |
1557 | * a syscall; multiple arguments can be specified and they will be chained | |
1558 | * together (essentially AND'd together) in the filter. When the strict flag | |
1559 | * is true the function will fail if the exact rule can not be added to the | |
1560 | * filter, if the strict flag is false the function will not fail if the | |
1561 | * function needs to adjust the rule due to architecture specifics. Returns | |
1562 | * zero on success, negative values on failure. | |
1563 | * | |
1564 | */ | |
1565 | int db_col_rule_add(struct db_filter_col *col, | |
1566 | bool strict, uint32_t action, int syscall, | |
1567 | unsigned int arg_cnt, const struct scmp_arg_cmp *arg_array) | |
1568 | { | |
1569 | int rc = 0, rc_tmp; | |
1570 | unsigned int iter; | |
1571 | unsigned int chain_len; | |
1572 | unsigned int arg_num; | |
1573 | size_t chain_size; | |
1574 | struct db_api_arg *chain = NULL; | |
1575 | struct scmp_arg_cmp arg_data; | |
1576 | ||
1577 | /* collect the arguments for the filter rule */ | |
1578 | chain_len = ARG_COUNT_MAX; | |
1579 | chain_size = sizeof(*chain) * chain_len; | |
1580 | chain = malloc(chain_size); | |
1581 | if (chain == NULL) | |
1582 | return -ENOMEM; | |
1583 | memset(chain, 0, chain_size); | |
1584 | for (iter = 0; iter < arg_cnt; iter++) { | |
1585 | arg_data = arg_array[iter]; | |
1586 | arg_num = arg_data.arg; | |
1587 | if (arg_num < chain_len && chain[arg_num].valid == 0) { | |
1588 | chain[arg_num].valid = 1; | |
1589 | chain[arg_num].arg = arg_num; | |
1590 | chain[arg_num].op = arg_data.op; | |
1591 | /* XXX - we should check datum/mask size against the | |
1592 | * arch definition, e.g. 64 bit datum on x86 */ | |
1593 | switch (chain[arg_num].op) { | |
1594 | case SCMP_CMP_NE: | |
1595 | case SCMP_CMP_LT: | |
1596 | case SCMP_CMP_LE: | |
1597 | case SCMP_CMP_EQ: | |
1598 | case SCMP_CMP_GE: | |
1599 | case SCMP_CMP_GT: | |
1600 | chain[arg_num].mask = DATUM_MAX; | |
1601 | chain[arg_num].datum = arg_data.datum_a; | |
1602 | break; | |
1603 | case SCMP_CMP_MASKED_EQ: | |
1604 | chain[arg_num].mask = arg_data.datum_a; | |
1605 | chain[arg_num].datum = arg_data.datum_b; | |
1606 | break; | |
1607 | default: | |
1608 | rc = -EINVAL; | |
1609 | goto add_return; | |
1610 | } | |
1611 | } else { | |
1612 | rc = -EINVAL; | |
1613 | goto add_return; | |
1614 | } | |
1615 | } | |
1616 | ||
1617 | for (iter = 0; iter < col->filter_cnt; iter++) { | |
1618 | rc_tmp = arch_filter_rule_add(col, col->filters[iter], strict, | |
1619 | action, syscall, | |
1620 | chain_len, chain); | |
1621 | if (rc == 0 && rc_tmp < 0) | |
1622 | rc = rc_tmp; | |
1623 | } | |
1624 | ||
1625 | add_return: | |
1626 | if (chain != NULL) | |
1627 | free(chain); | |
1628 | return rc; | |
1629 | } | |
1630 | ||
1631 | /** | |
1632 | * Start a new seccomp filter transaction | |
1633 | * @param col the filter collection | |
1634 | * | |
1635 | * This function starts a new seccomp filter transaction for the given filter | |
1636 | * collection. Returns zero on success, negative values on failure. | |
1637 | * | |
1638 | */ | |
1639 | int db_col_transaction_start(struct db_filter_col *col) | |
1640 | { | |
1641 | unsigned int iter; | |
1642 | size_t args_size; | |
1643 | struct db_filter_snap *snap; | |
1644 | struct db_filter *filter_o, *filter_s; | |
1645 | struct db_api_rule_list *rule_o, *rule_s; | |
1646 | ||
1647 | /* allocate the snapshot */ | |
1648 | snap = malloc(sizeof(*snap)); | |
1649 | if (snap == NULL) | |
1650 | return -ENOMEM; | |
1651 | snap->filters = malloc(sizeof(struct db_filter *) * col->filter_cnt); | |
1652 | if (snap->filters == NULL) { | |
1653 | free(snap); | |
1654 | return -ENOMEM; | |
1655 | } | |
1656 | snap->filter_cnt = col->filter_cnt; | |
1657 | for (iter = 0; iter < snap->filter_cnt; iter++) | |
1658 | snap->filters[iter] = NULL; | |
1659 | snap->next = NULL; | |
1660 | ||
1661 | /* create a snapshot of the current filter state */ | |
1662 | for (iter = 0; iter < col->filter_cnt; iter++) { | |
1663 | /* allocate a new filter */ | |
1664 | filter_o = col->filters[iter]; | |
1665 | filter_s = _db_init(filter_o->arch); | |
1666 | if (filter_s == NULL) | |
1667 | goto trans_start_failure; | |
1668 | snap->filters[iter] = filter_s; | |
1669 | ||
1670 | /* create a filter snapshot from existing rules */ | |
1671 | rule_o = filter_o->rules; | |
1672 | if (rule_o == NULL) | |
1673 | continue; | |
1674 | do { | |
1675 | /* copy the rule */ | |
1676 | rule_s = malloc(sizeof(*rule_s)); | |
1677 | if (rule_s == NULL) | |
1678 | goto trans_start_failure; | |
1679 | args_size = sizeof(*rule_s->args) * rule_o->args_cnt; | |
1680 | rule_s->args = malloc(args_size); | |
1681 | if (rule_s->args == NULL) { | |
1682 | free(rule_s); | |
1683 | goto trans_start_failure; | |
1684 | } | |
1685 | rule_s->action = rule_o->action; | |
1686 | rule_s->syscall = rule_o->syscall; | |
1687 | rule_s->args_cnt = rule_o->args_cnt; | |
1688 | memcpy(rule_s->args, rule_o->args, args_size); | |
1689 | if (filter_s->rules != NULL) { | |
1690 | rule_s->prev = filter_s->rules->prev; | |
1691 | rule_s->next = filter_s->rules; | |
1692 | filter_s->rules->prev->next = rule_s; | |
1693 | filter_s->rules->prev = rule_s; | |
1694 | } else { | |
1695 | rule_s->prev = rule_s; | |
1696 | rule_s->next = rule_s; | |
1697 | filter_s->rules = rule_s; | |
1698 | } | |
1699 | ||
1700 | /* insert the rule into the filter */ | |
1701 | if (db_rule_add(filter_s, rule_o) != 0) | |
1702 | goto trans_start_failure; | |
1703 | ||
1704 | /* next rule */ | |
1705 | rule_o = rule_o->next; | |
1706 | } while (rule_o != filter_o->rules); | |
1707 | } | |
1708 | ||
1709 | /* add the snapshot to the list */ | |
1710 | snap->next = col->snapshots; | |
1711 | col->snapshots = snap; | |
1712 | ||
1713 | return 0; | |
1714 | ||
1715 | trans_start_failure: | |
1716 | _db_snap_release(snap); | |
1717 | return -ENOMEM; | |
1718 | } | |
1719 | ||
1720 | /** | |
1721 | * Abort the top most seccomp filter transaction | |
1722 | * @param col the filter collection | |
1723 | * | |
1724 | * This function aborts the most recent seccomp filter transaction. | |
1725 | * | |
1726 | */ | |
1727 | void db_col_transaction_abort(struct db_filter_col *col) | |
1728 | { | |
1729 | int iter; | |
1730 | unsigned int filter_cnt; | |
1731 | struct db_filter **filters; | |
1732 | struct db_filter_snap *snap; | |
1733 | ||
1734 | if (col->snapshots == NULL) | |
1735 | return; | |
1736 | ||
1737 | /* replace the current filter with the last snapshot */ | |
1738 | snap = col->snapshots; | |
1739 | col->snapshots = snap->next; | |
1740 | filter_cnt = col->filter_cnt; | |
1741 | filters = col->filters; | |
1742 | col->filter_cnt = snap->filter_cnt; | |
1743 | col->filters = snap->filters; | |
1744 | free(snap); | |
1745 | ||
1746 | /* free the filter we swapped out */ | |
1747 | for (iter = 0; iter < filter_cnt; iter++) | |
1748 | _db_release(filters[iter]); | |
1749 | free(filters); | |
1750 | } | |
1751 | ||
1752 | /** | |
1753 | * Commit the top most seccomp filter transaction | |
1754 | * @param col the filter collection | |
1755 | * | |
1756 | * This function commits the most recent seccomp filter transaction. | |
1757 | * | |
1758 | */ | |
1759 | void db_col_transaction_commit(struct db_filter_col *col) | |
1760 | { | |
1761 | struct db_filter_snap *snap; | |
1762 | ||
1763 | snap = col->snapshots; | |
1764 | col->snapshots = snap->next; | |
1765 | _db_snap_release(snap); | |
1766 | } |