/
githubmirror
/
libseccomp
Обзор
Документация
Войти
/
githubmirror
/
libseccomp
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
v2.3.3
src/db.c
1 774 строки
45 KB
Paul Moore
all: treat syscall -1 as a valid syscall
23 фев 2017, 20:25
23 фев 2017, 20:25
10dd41e
Код
Авторство
О чём код?
/** * Enhanced Seccomp Filter DB * * Copyright (c) 2012,2016 Red Hat <pmoore@redhat.com> * Author: Paul Moore <paul@paul-moore.com> */ /* * This library is free software; you can redistribute it and/or modify it * under the terms of version 2.1 of the GNU Lesser General Public License as * published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; if not, see <http://www.gnu.org/licenses>. */ #include <assert.h> #include <errno.h> #include <inttypes.h> #include <stdlib.h> #include <string.h> #include <stdarg.h> #include <seccomp.h> #include "arch.h" #include "db.h" #include "system.h" /* state values */ #define _DB_STA_VALID 0xA1B2C3D4 #define _DB_STA_FREED 0x1A2B3C4D /* the priority field is fairly simple - without any user hints, or in the case * of a hint "tie", we give higher priority to syscalls with less chain nodes * (filter is easier to evaluate) */ #define _DB_PRI_MASK_CHAIN 0x0000FFFF #define _DB_PRI_MASK_USER 0x00FF0000 #define _DB_PRI_USER(x) (((x) << 16) & _DB_PRI_MASK_USER) /* private structure for tracking the state of the sub-tree "pruning" */ struct db_prune_state { bool prefix_exist; bool prefix_new; bool matched; }; static unsigned int _db_tree_free(struct db_arg_chain_tree *tree); /** * Do not call this function directly, use _db_tree_free() instead */ static unsigned int __db_tree_free(struct db_arg_chain_tree *tree) { int cnt; if (tree == NULL || --(tree->refcnt) > 0) return 0; /* we assume the caller has ensured that 'tree->lvl_prv == NULL' */ cnt = __db_tree_free(tree->lvl_nxt); cnt += _db_tree_free(tree->nxt_t); cnt += _db_tree_free(tree->nxt_f); free(tree); return cnt + 1; } /** * Free a syscall filter argument chain tree * @param tree the argument chain list * * This function frees a tree and returns the number of nodes freed. * */ static unsigned int _db_tree_free(struct db_arg_chain_tree *tree) { struct db_arg_chain_tree *iter; if (tree == NULL) return 0; iter = tree; while (iter->lvl_prv != NULL) iter = iter->lvl_prv; return __db_tree_free(iter); } /** * Remove a node from an argument chain tree * @param tree the pointer to the tree * @param node the node to remove * * This function searches the tree looking for the node and removes it once * found. Returns the number of nodes freed. * */ static unsigned int _db_tree_remove(struct db_arg_chain_tree **tree, struct db_arg_chain_tree *node) { int cnt = 0; struct db_arg_chain_tree *c_iter; if (tree == NULL || *tree == NULL || node == NULL) return 0; c_iter = *tree; while (c_iter->lvl_prv != NULL) c_iter = c_iter->lvl_prv; do { if (c_iter == node || db_chain_zombie(c_iter)) { /* remove from the tree */ if (c_iter == *tree) { if (c_iter->lvl_prv != NULL) *tree = c_iter->lvl_prv; else *tree = c_iter->lvl_nxt; } if (c_iter->lvl_prv != NULL) c_iter->lvl_prv->lvl_nxt = c_iter->lvl_nxt; if (c_iter->lvl_nxt != NULL) c_iter->lvl_nxt->lvl_prv = c_iter->lvl_prv; /* free and return */ c_iter->lvl_prv = NULL; c_iter->lvl_nxt = NULL; cnt += _db_tree_free(c_iter); return cnt; } /* check the true/false sub-trees */ cnt += _db_tree_remove(&(c_iter->nxt_t), node); cnt += _db_tree_remove(&(c_iter->nxt_f), node); c_iter = c_iter->lvl_nxt; } while (c_iter != NULL); return cnt; } /** * Traverse a tree checking the action values * @param tree the pointer to the tree * @param action the action * * Traverse the tree inspecting each action to see if it matches the given * action. Returns zero if all actions match the given action, negative values * on failure. * */ static int _db_tree_act_check(struct db_arg_chain_tree *tree, uint32_t action) { int rc; struct db_arg_chain_tree *c_iter; if (tree == NULL) return 0; c_iter = tree; while (c_iter->lvl_prv != NULL) c_iter = c_iter->lvl_prv; do { if (c_iter->act_t_flg && c_iter->act_t != action) return -EEXIST; if (c_iter->act_f_flg && c_iter->act_f != action) return -EEXIST; rc = _db_tree_act_check(c_iter->nxt_t, action); if (rc < 0) return rc; rc = _db_tree_act_check(c_iter->nxt_f, action); if (rc < 0) return rc; c_iter = c_iter->lvl_nxt; } while (c_iter != NULL); return 0; } /** * Checks for a sub-tree match in an existing tree and prunes the tree * @param prev the head of the existing tree or sub-tree * @param existing the starting point into the existing tree * @param new pointer to the new tree * @param state pointer to the pruning state * * This function searches the existing and new trees trying to prune each to * eliminate redundancy. Returns the number of nodes removed from the tree on * success, zero if no changes were made, and negative values if the new tree * should be discarded. * */ static int _db_tree_sub_prune(struct db_arg_chain_tree **prev, struct db_arg_chain_tree *existing, struct db_arg_chain_tree *new, struct db_prune_state *state) { int rc = 0; int rc_tmp; struct db_arg_chain_tree *ec_iter; struct db_arg_chain_tree *ec_iter_tmp; struct db_arg_chain_tree *c_iter; struct db_prune_state state_new; if (!state || !existing || !new) return 0; ec_iter = existing; c_iter = new; do { if (db_chain_eq(ec_iter, c_iter)) { /* equal */ if (db_chain_leaf(c_iter)) { /* leaf */ if (db_chain_eq_result(ec_iter, c_iter)) { /* identical results */ if (prev != NULL) return _db_tree_remove(prev, ec_iter); else return -1; } if (c_iter->act_t_flg && ec_iter->nxt_t) { /* new is shorter (true) */ if (prev == NULL) return -1; rc += _db_tree_remove(&(ec_iter->nxt_t), ec_iter->nxt_t); ec_iter->act_t = c_iter->act_t; ec_iter->act_t_flg = true; } if (c_iter->act_f_flg && ec_iter->nxt_f) { /* new is shorter (false) */ if (prev == NULL) return -1; rc += _db_tree_remove(&(ec_iter->nxt_f), ec_iter->nxt_f); ec_iter->act_f = c_iter->act_f; ec_iter->act_f_flg = true; } return rc; } if (c_iter->nxt_t && ec_iter->act_t_flg) /* existing is shorter (true) */ return -1; if (c_iter->nxt_f && ec_iter->act_f_flg) /* existing is shorter (false) */ return -1; if (c_iter->nxt_t) { state_new = *state; state_new.matched = true; rc_tmp = _db_tree_sub_prune((prev ? &ec_iter : NULL), ec_iter->nxt_t, c_iter->nxt_t, &state_new); rc += (rc_tmp > 0 ? rc_tmp : 0); if (state->prefix_new && rc_tmp < 0) return (rc > 0 ? rc : rc_tmp); } if (c_iter->nxt_f) { state_new = *state; state_new.matched = true; rc_tmp = _db_tree_sub_prune((prev ? &ec_iter : NULL), ec_iter->nxt_f, c_iter->nxt_f, &state_new); rc += (rc_tmp > 0 ? rc_tmp : 0); if (state->prefix_new && rc_tmp < 0) return (rc > 0 ? rc : rc_tmp); } } else if (db_chain_lt(ec_iter, c_iter)) { /* less than */ if (state->matched || state->prefix_new) goto next; state_new = *state; state_new.prefix_exist = true; if (ec_iter->nxt_t) { rc_tmp = _db_tree_sub_prune((prev ? &ec_iter : NULL), ec_iter->nxt_t, c_iter, &state_new); rc += (rc_tmp > 0 ? rc_tmp : 0); } if (ec_iter->nxt_f) { rc_tmp = _db_tree_sub_prune((prev ? &ec_iter : NULL), ec_iter->nxt_f, c_iter, &state_new); rc += (rc_tmp > 0 ? rc_tmp : 0); } } else if (db_chain_gt(ec_iter, c_iter)) { /* greater than */ if (state->matched || state->prefix_exist) goto next; state_new = *state; state_new.prefix_new = true; if (c_iter->nxt_t) { rc_tmp = _db_tree_sub_prune(NULL, ec_iter, c_iter->nxt_t, &state_new); rc += (rc_tmp > 0 ? rc_tmp : 0); if (rc_tmp < 0) return (rc > 0 ? rc : rc_tmp); } if (c_iter->nxt_f) { rc_tmp = _db_tree_sub_prune(NULL, ec_iter, c_iter->nxt_f, &state_new); rc += (rc_tmp > 0 ? rc_tmp : 0); if (rc_tmp < 0) return (rc > 0 ? rc : rc_tmp); } } next: /* re-check current node and advance to the next node */ if (db_chain_zombie(ec_iter)) { ec_iter_tmp = ec_iter->lvl_nxt; rc += _db_tree_remove(prev, ec_iter); ec_iter = ec_iter_tmp; } else ec_iter = ec_iter->lvl_nxt; } while (ec_iter); return rc; } /** * Free and reset the seccomp filter DB * @param db the seccomp filter DB * * This function frees any existing filters and resets the filter DB to a * default state; only the DB architecture is preserved. * */ static void _db_reset(struct db_filter *db) { struct db_sys_list *s_iter; struct db_api_rule_list *r_iter; if (db == NULL) return; /* free any filters */ if (db->syscalls != NULL) { s_iter = db->syscalls; while (s_iter != NULL) { db->syscalls = s_iter->next; _db_tree_free(s_iter->chains); free(s_iter); s_iter = db->syscalls; } db->syscalls = NULL; } /* free any rules */ if (db->rules != NULL) { /* split the loop first then loop and free */ db->rules->prev->next = NULL; r_iter = db->rules; while (r_iter != NULL) { db->rules = r_iter->next; free(r_iter); r_iter = db->rules; } db->rules = NULL; } } /** * Intitalize a seccomp filter DB * @param arch the architecture definition * * This function initializes a seccomp filter DB and readies it for use. * Returns a pointer to the DB on success, NULL on failure. * */ static struct db_filter *_db_init(const struct arch_def *arch) { struct db_filter *db; db = malloc(sizeof(*db)); if (db == NULL) return NULL; /* clear the buffer for the first time and set the arch */ memset(db, 0, sizeof(*db)); db->arch = arch; /* reset the DB to a known state */ _db_reset(db); return db; } /** * Destroy a seccomp filter DB * @param db the seccomp filter DB * * This function destroys a seccomp filter DB. After calling this function, * the filter should no longer be referenced. * */ static void _db_release(struct db_filter *db) { if (db == NULL) return; /* free and reset the DB */ _db_reset(db); free(db); } /** * Destroy a seccomp filter snapshot * @param snap the seccomp filter snapshot * * This function destroys a seccomp filter snapshot. After calling this * function, the snapshot should no longer be referenced. * */ static void _db_snap_release(struct db_filter_snap *snap) { unsigned int iter; if (snap->filter_cnt > 0) { for (iter = 0; iter < snap->filter_cnt; iter++) { if (snap->filters[iter]) _db_release(snap->filters[iter]); } free(snap->filters); } free(snap); } /** * Update the user specified portion of the syscall priority * @param db the seccomp filter db * @param syscall the syscall number * @param priority the syscall priority * * This function sets, or updates, the syscall priority; the highest priority * value between the existing and specified value becomes the new syscall * priority. If the syscall entry does not already exist, a new phantom * syscall entry is created as a placeholder. Returns zero on success, * negative values on failure. * */ static int _db_syscall_priority(struct db_filter *db, int syscall, uint8_t priority) { unsigned int sys_pri = _DB_PRI_USER(priority); struct db_sys_list *s_new, *s_iter, *s_prev = NULL; assert(db != NULL); s_iter = db->syscalls; while (s_iter != NULL && s_iter->num < syscall) { s_prev = s_iter; s_iter = s_iter->next; } /* matched an existing syscall entry */ if (s_iter != NULL && s_iter->num == syscall) { if (sys_pri > (s_iter->priority & _DB_PRI_MASK_USER)) { s_iter->priority &= (~_DB_PRI_MASK_USER); s_iter->priority |= sys_pri; } return 0; } /* no existing syscall entry - create a phantom entry */ s_new = malloc(sizeof(*s_new)); if (s_new == NULL) return -ENOMEM; memset(s_new, 0, sizeof(*s_new)); s_new->num = syscall; s_new->priority = sys_pri; s_new->valid = false; /* add it before s_iter */ if (s_prev != NULL) { s_new->next = s_prev->next; s_prev->next = s_new; } else { s_new->next = db->syscalls; db->syscalls = s_new; } return 0; } /** * Duplicate an existing filter rule * @param src the rule to duplicate * * This function makes an exact copy of the given rule, but does not add it * to any lists. Returns a pointer to the new rule on success, NULL on * failure. * */ struct db_api_rule_list *db_rule_dup(const struct db_api_rule_list *src) { struct db_api_rule_list *dest; dest = malloc(sizeof(*dest)); if (dest == NULL) return NULL; memcpy(dest, src, sizeof(*dest)); dest->prev = NULL; dest->next = NULL; return dest; } /** * Free and reset the seccomp filter collection * @param col the seccomp filter collection * @param def_action the default filter action * * This function frees any existing filter DBs and resets the collection to a * default state. In the case of failure the filter collection may be in an * unknown state and should be released. Returns zero on success, negative * values on failure. * */ int db_col_reset(struct db_filter_col *col, uint32_t def_action) { unsigned int iter; struct db_filter *db; struct db_filter_snap *snap; if (col == NULL) return -EINVAL; /* free any filters */ for (iter = 0; iter < col->filter_cnt; iter++) _db_release(col->filters[iter]); col->filter_cnt = 0; if (col->filters) free(col->filters); col->filters = NULL; /* set the endianess to undefined */ col->endian = 0; /* set the default attribute values */ col->attr.act_default = def_action; col->attr.act_badarch = SCMP_ACT_KILL; col->attr.nnp_enable = 1; col->attr.tsync_enable = 0; col->attr.api_tskip = 0; /* set the state */ col->state = _DB_STA_VALID; /* reset the initial db */ db = _db_init(arch_def_native); if (db == NULL) return -ENOMEM; if (db_col_db_add(col, db) < 0) { _db_release(db); return -ENOMEM; } /* reset the transactions */ while (col->snapshots) { snap = col->snapshots; col->snapshots = snap->next; for (iter = 0; iter < snap->filter_cnt; iter++) _db_release(snap->filters[iter]); free(snap->filters); free(snap); } return 0; } /** * Intitalize a seccomp filter collection * @param def_action the default filter action * * This function initializes a seccomp filter collection and readies it for * use. Returns a pointer to the collection on success, NULL on failure. * */ struct db_filter_col *db_col_init(uint32_t def_action) { struct db_filter_col *col; col = malloc(sizeof(*col)); if (col == NULL) return NULL; /* clear the buffer for the first time */ memset(col, 0, sizeof(*col)); /* reset the DB to a known state */ if (db_col_reset(col, def_action) < 0) goto init_failure; return col; init_failure: db_col_release(col); return NULL; } /** * Destroy a seccomp filter collection * @param col the seccomp filter collection * * This function destroys a seccomp filter collection. After calling this * function, the filter should no longer be referenced. * */ void db_col_release(struct db_filter_col *col) { unsigned int iter; if (col == NULL) return; /* set the state, just in case */ col->state = _DB_STA_FREED; /* free any filters */ for (iter = 0; iter < col->filter_cnt; iter++) _db_release(col->filters[iter]); col->filter_cnt = 0; if (col->filters) free(col->filters); col->filters = NULL; /* free the collection */ free(col); } /** * Validate the seccomp action * @param action the seccomp action * * Verify that the given action is a valid seccomp action; return zero if * valid, -EINVAL if invalid. */ int db_action_valid(uint32_t action) { if (action == SCMP_ACT_KILL) return 0; else if (action == SCMP_ACT_TRAP) return 0; else if ((action == SCMP_ACT_ERRNO(action & 0x0000ffff)) && ((action & 0x0000ffff) < MAX_ERRNO)) return 0; else if (action == SCMP_ACT_TRACE(action & 0x0000ffff)) return 0; else if (action == SCMP_ACT_ALLOW) return 0; return -EINVAL; } /** * Validate a filter collection * @param col the seccomp filter collection * * This function validates a seccomp filter collection. Returns zero if the * collection is valid, negative values on failure. * */ int db_col_valid(struct db_filter_col *col) { if (col != NULL && col->state == _DB_STA_VALID && col->filter_cnt > 0) return 0; return -EINVAL; } /** * Merge two filter collections * @param col_dst the destination filter collection * @param col_src the source filter collection * * This function merges two filter collections into the given destination * collection. The source filter collection is no longer valid if the function * returns successfully. Returns zero on success, negative values on failure. * */ int db_col_merge(struct db_filter_col *col_dst, struct db_filter_col *col_src) { unsigned int iter_a, iter_b; struct db_filter **dbs; /* verify that the endianess is a match */ if (col_dst->endian != col_src->endian) return -EEXIST; /* make sure we don't have any arch/filter collisions */ for (iter_a = 0; iter_a < col_dst->filter_cnt; iter_a++) { for (iter_b = 0; iter_b < col_src->filter_cnt; iter_b++) { if (col_dst->filters[iter_a]->arch->token == col_src->filters[iter_b]->arch->token) return -EEXIST; } } /* expand the destination */ dbs = realloc(col_dst->filters, sizeof(struct db_filter *) * (col_dst->filter_cnt + col_src->filter_cnt)); if (dbs == NULL) return -ENOMEM; col_dst->filters = dbs; /* transfer the architecture filters */ for (iter_a = col_dst->filter_cnt, iter_b = 0; iter_b < col_src->filter_cnt; iter_a++, iter_b++) { col_dst->filters[iter_a] = col_src->filters[iter_b]; col_dst->filter_cnt++; } /* free the source */ col_src->filter_cnt = 0; db_col_release(col_src); return 0; } /** * Check to see if an architecture filter exists in the filter collection * @param col the seccomp filter collection * @param arch_token the architecture token * * Iterate through the given filter collection checking to see if a filter * exists for the specified architecture. Returns -EEXIST if a filter is found, * zero if a matching filter does not exist. * */ int db_col_arch_exist(struct db_filter_col *col, uint32_t arch_token) { unsigned int iter; for (iter = 0; iter < col->filter_cnt; iter++) if (col->filters[iter]->arch->token == arch_token) return -EEXIST; return 0; } /** * Get a filter attribute * @param col the seccomp filter collection * @param attr the filter attribute * @param value the filter attribute value * * Get the requested filter attribute and provide it via @value. Returns zero * on success, negative values on failure. * */ int db_col_attr_get(const struct db_filter_col *col, enum scmp_filter_attr attr, uint32_t *value) { int rc = 0; switch (attr) { case SCMP_FLTATR_ACT_DEFAULT: *value = col->attr.act_default; break; case SCMP_FLTATR_ACT_BADARCH: *value = col->attr.act_badarch; break; case SCMP_FLTATR_CTL_NNP: *value = col->attr.nnp_enable; break; case SCMP_FLTATR_CTL_TSYNC: *value = col->attr.tsync_enable; break; case SCMP_FLTATR_API_TSKIP: *value = col->attr.api_tskip; break; default: rc = -EEXIST; break; } return rc; } /** * Set a filter attribute * @param col the seccomp filter collection * @param attr the filter attribute * @param value the filter attribute value * * Set the requested filter attribute with the given value. Returns zero on * success, negative values on failure. * */ int db_col_attr_set(struct db_filter_col *col, enum scmp_filter_attr attr, uint32_t value) { int rc = 0; switch (attr) { case SCMP_FLTATR_ACT_DEFAULT: /* read only */ return -EACCES; break; case SCMP_FLTATR_ACT_BADARCH: if (db_action_valid(value) == 0) col->attr.act_badarch = value; else return -EINVAL; break; case SCMP_FLTATR_CTL_NNP: col->attr.nnp_enable = (value ? 1 : 0); break; case SCMP_FLTATR_CTL_TSYNC: rc = sys_chk_seccomp_flag(SECCOMP_FILTER_FLAG_TSYNC); if (rc == 1) { /* supported */ rc = 0; col->attr.tsync_enable = (value ? 1 : 0); } else if (rc == 0) /* unsupported */ rc = -EOPNOTSUPP; break; case SCMP_FLTATR_API_TSKIP: col->attr.api_tskip = (value ? 1 : 0); break; default: rc = -EEXIST; break; } return rc; } /** * Add a new architecture filter to a filter collection * @param col the seccomp filter collection * @param arch the architecture * * This function adds a new architecture filter DB to an existing seccomp * filter collection assuming there isn't a filter DB already present with the * same architecture. Returns zero on success, negative values on failure. * */ int db_col_db_new(struct db_filter_col *col, const struct arch_def *arch) { int rc; struct db_filter *db; db = _db_init(arch); if (db == NULL) return -ENOMEM; rc = db_col_db_add(col, db); if (rc < 0) _db_release(db); return rc; } /** * Add a new filter DB to a filter collection * @param col the seccomp filter collection * @param db the seccomp filter DB * * This function adds an existing seccomp filter DB to an existing seccomp * filter collection assuming there isn't a filter DB already present with the * same architecture. Returns zero on success, negative values on failure. * */ int db_col_db_add(struct db_filter_col *col, struct db_filter *db) { struct db_filter **dbs; if (col->endian != 0 && col->endian != db->arch->endian) return -EEXIST; if (db_col_arch_exist(col, db->arch->token)) return -EEXIST; dbs = realloc(col->filters, sizeof(struct db_filter *) * (col->filter_cnt + 1)); if (dbs == NULL) return -ENOMEM; col->filters = dbs; col->filter_cnt++; col->filters[col->filter_cnt - 1] = db; if (col->endian == 0) col->endian = db->arch->endian; return 0; } /** * Remove a filter DB from a filter collection * @param col the seccomp filter collection * @param arch_token the architecture token * * This function removes an existing seccomp filter DB from an existing seccomp * filter collection. Returns zero on success, negative values on failure. * */ int db_col_db_remove(struct db_filter_col *col, uint32_t arch_token) { unsigned int iter; unsigned int found; struct db_filter **dbs; if ((col->filter_cnt <= 0) || (db_col_arch_exist(col, arch_token) == 0)) return -EINVAL; for (found = 0, iter = 0; iter < col->filter_cnt; iter++) { if (found) col->filters[iter - 1] = col->filters[iter]; else if (col->filters[iter]->arch->token == arch_token) { _db_release(col->filters[iter]); found = 1; } } col->filters[--col->filter_cnt] = NULL; if (col->filter_cnt > 0) { /* NOTE: if we can't do the realloc it isn't fatal, we just * have some extra space allocated */ dbs = realloc(col->filters, sizeof(struct db_filter *) * col->filter_cnt); if (dbs != NULL) col->filters = dbs; } else { /* this was the last filter so free all the associated memory * and reset the endian token */ free(col->filters); col->filters = NULL; col->endian = 0; } return 0; } /** * Test if the argument filter can be skipped because it's a tautology * @param arg argument filter * * If this argument filter applied to the lower 32 bit can be skipped this * function returns false. * */ static bool _db_arg_cmp_need_lo(const struct db_api_arg *arg) { if (arg->op == SCMP_CMP_MASKED_EQ && D64_LO(arg->mask) == 0) return false; return true; } /** * Test if the argument filter can be skipped because it's a tautology * @param arg argument filter * * If this argument filter applied to the upper 32 bit can be skipped this * function returns false. * */ static bool _db_arg_cmp_need_hi(const struct db_api_arg *arg) { if (arg->op == SCMP_CMP_MASKED_EQ && D64_HI(arg->mask) == 0) return false; return true; } /** * Fixup the node based on the op/mask * @param node the chain node * * Ensure the datum is masked as well. * */ static void _db_node_mask_fixup(struct db_arg_chain_tree *node) { node->datum &= node->mask; } /** * Generate a new filter rule for a 64 bit system * @param arch the architecture definition * @param action the filter action * @param syscall the syscall number * @param chain argument filter chain * * This function generates a new syscall filter for a 64 bit system. Returns * zero on success, negative values on failure. * */ static struct db_sys_list *_db_rule_gen_64(const struct arch_def *arch, uint32_t action, unsigned int syscall, const struct db_api_arg *chain) { unsigned int iter; struct db_sys_list *s_new; struct db_arg_chain_tree *c_iter_hi = NULL, *c_iter_lo = NULL; struct db_arg_chain_tree *c_prev_hi = NULL, *c_prev_lo = NULL; bool tf_flag; s_new = malloc(sizeof(*s_new)); if (s_new == NULL) return NULL; memset(s_new, 0, sizeof(*s_new)); s_new->num = syscall; s_new->valid = true; /* run through the argument chain */ for (iter = 0; iter < ARG_COUNT_MAX; iter++) { if (chain[iter].valid == 0) continue; /* TODO: handle the case were either hi or lo isn't needed */ /* skip generating instruction which are no-ops */ if (!_db_arg_cmp_need_hi(&chain[iter]) && !_db_arg_cmp_need_lo(&chain[iter])) continue; c_iter_hi = malloc(sizeof(*c_iter_hi)); if (c_iter_hi == NULL) goto gen_64_failure; memset(c_iter_hi, 0, sizeof(*c_iter_hi)); c_iter_hi->refcnt = 1; c_iter_lo = malloc(sizeof(*c_iter_lo)); if (c_iter_lo == NULL) { free(c_iter_hi); goto gen_64_failure; } memset(c_iter_lo, 0, sizeof(*c_iter_lo)); c_iter_lo->refcnt = 1; /* link this level to the previous level */ if (c_prev_lo != NULL) { if (!tf_flag) { c_prev_lo->nxt_f = c_iter_hi; c_prev_hi->nxt_f = c_iter_hi; c_iter_hi->refcnt++; } else c_prev_lo->nxt_t = c_iter_hi; } else s_new->chains = c_iter_hi; s_new->node_cnt += 2; /* set the arg, op, and datum fields */ c_iter_hi->arg = chain[iter].arg; c_iter_lo->arg = chain[iter].arg; c_iter_hi->arg_offset = arch_arg_offset_hi(arch, c_iter_hi->arg); c_iter_lo->arg_offset = arch_arg_offset_lo(arch, c_iter_lo->arg); switch (chain[iter].op) { case SCMP_CMP_GT: c_iter_hi->op = SCMP_CMP_GE; c_iter_lo->op = SCMP_CMP_GT; tf_flag = true; break; case SCMP_CMP_NE: c_iter_hi->op = SCMP_CMP_EQ; c_iter_lo->op = SCMP_CMP_EQ; tf_flag = false; break; case SCMP_CMP_LT: c_iter_hi->op = SCMP_CMP_GE; c_iter_lo->op = SCMP_CMP_GE; tf_flag = false; break; case SCMP_CMP_LE: c_iter_hi->op = SCMP_CMP_GE; c_iter_lo->op = SCMP_CMP_GT; tf_flag = false; break; default: c_iter_hi->op = chain[iter].op; c_iter_lo->op = chain[iter].op; tf_flag = true; } c_iter_hi->mask = D64_HI(chain[iter].mask); c_iter_lo->mask = D64_LO(chain[iter].mask); c_iter_hi->datum = D64_HI(chain[iter].datum); c_iter_lo->datum = D64_LO(chain[iter].datum); /* fixup the mask/datum */ _db_node_mask_fixup(c_iter_hi); _db_node_mask_fixup(c_iter_lo); /* link the hi and lo chain nodes */ c_iter_hi->nxt_t = c_iter_lo; c_prev_hi = c_iter_hi; c_prev_lo = c_iter_lo; } if (c_iter_lo != NULL) { /* set the leaf node */ if (!tf_flag) { c_iter_lo->act_f_flg = true; c_iter_lo->act_f = action; c_iter_hi->act_f_flg = true; c_iter_hi->act_f = action; } else { c_iter_lo->act_t_flg = true; c_iter_lo->act_t = action; } } else s_new->action = action; return s_new; gen_64_failure: /* free the new chain and its syscall struct */ _db_tree_free(s_new->chains); free(s_new); return NULL; } /** * Generate a new filter rule for a 32 bit system * @param arch the architecture definition * @param action the filter action * @param syscall the syscall number * @param chain argument filter chain * * This function generates a new syscall filter for a 32 bit system. Returns * zero on success, negative values on failure. * */ static struct db_sys_list *_db_rule_gen_32(const struct arch_def *arch, uint32_t action, unsigned int syscall, const struct db_api_arg *chain) { unsigned int iter; struct db_sys_list *s_new; struct db_arg_chain_tree *c_iter = NULL, *c_prev = NULL; bool tf_flag; s_new = malloc(sizeof(*s_new)); if (s_new == NULL) return NULL; memset(s_new, 0, sizeof(*s_new)); s_new->num = syscall; s_new->valid = true; /* run through the argument chain */ for (iter = 0; iter < ARG_COUNT_MAX; iter++) { if (chain[iter].valid == 0) continue; /* skip generating instructions which are no-ops */ if (!_db_arg_cmp_need_lo(&chain[iter])) continue; c_iter = malloc(sizeof(*c_iter)); if (c_iter == NULL) goto gen_32_failure; memset(c_iter, 0, sizeof(*c_iter)); c_iter->refcnt = 1; c_iter->arg = chain[iter].arg; c_iter->arg_offset = arch_arg_offset(arch, c_iter->arg); c_iter->op = chain[iter].op; /* implicitly strips off the upper 32 bit */ c_iter->mask = chain[iter].mask; c_iter->datum = chain[iter].datum; /* link in the new node and update the chain */ if (c_prev != NULL) { if (tf_flag) c_prev->nxt_t = c_iter; else c_prev->nxt_f = c_iter; } else s_new->chains = c_iter; s_new->node_cnt++; /* rewrite the op to reduce the op/datum combos */ switch (c_iter->op) { case SCMP_CMP_NE: c_iter->op = SCMP_CMP_EQ; tf_flag = false; break; case SCMP_CMP_LT: c_iter->op = SCMP_CMP_GE; tf_flag = false; break; case SCMP_CMP_LE: c_iter->op = SCMP_CMP_GT; tf_flag = false; break; default: tf_flag = true; } /* fixup the mask/datum */ _db_node_mask_fixup(c_iter); c_prev = c_iter; } if (c_iter != NULL) { /* set the leaf node */ if (tf_flag) { c_iter->act_t_flg = true; c_iter->act_t = action; } else { c_iter->act_f_flg = true; c_iter->act_f = action; } } else s_new->action = action; return s_new; gen_32_failure: /* free the new chain and its syscall struct */ _db_tree_free(s_new->chains); free(s_new); return NULL; } /** * Add a new rule to the seccomp filter DB * @param db the seccomp filter db * @param rule the filter rule * * This function adds a new syscall filter to the seccomp filter DB, adding to * the existing filters for the syscall, unless no argument specific filters * are present (filtering only on the syscall). When adding new chains, the * shortest chain, or most inclusive filter match, will be entered into the * filter DB. Returns zero on success, negative values on failure. * */ int db_rule_add(struct db_filter *db, const struct db_api_rule_list *rule) { int rc = -ENOMEM; int syscall = rule->syscall; uint32_t action = rule->action; const struct db_api_arg *chain = rule->args; struct db_sys_list *s_new, *s_iter, *s_prev = NULL; struct db_arg_chain_tree *c_iter = NULL, *c_prev = NULL; struct db_arg_chain_tree *ec_iter; struct db_prune_state state; bool rm_flag = false; unsigned int new_chain_cnt = 0; unsigned int n_cnt; assert(db != NULL); /* do all our possible memory allocation up front so we don't have to * worry about failure once we get to the point where we start updating * the filter db */ if (db->arch->size == ARCH_SIZE_64) s_new = _db_rule_gen_64(db->arch, action, syscall, chain); else if (db->arch->size == ARCH_SIZE_32) s_new = _db_rule_gen_32(db->arch, action, syscall, chain); else return -EFAULT; if (s_new == NULL) return -ENOMEM; new_chain_cnt = s_new->node_cnt; /* no more failures allowed after this point that would result in the * stored filter being in an inconsistent state */ /* find a matching syscall/chain or insert a new one */ s_iter = db->syscalls; while (s_iter != NULL && s_iter->num < syscall) { s_prev = s_iter; s_iter = s_iter->next; } add_reset: s_new->node_cnt = new_chain_cnt; s_new->priority = _DB_PRI_MASK_CHAIN - s_new->node_cnt; c_prev = NULL; c_iter = s_new->chains; if (s_iter != NULL) ec_iter = s_iter->chains; else ec_iter = NULL; if (s_iter == NULL || s_iter->num != syscall) { /* new syscall, add before s_iter */ if (s_prev != NULL) { s_new->next = s_prev->next; s_prev->next = s_new; } else { s_new->next = db->syscalls; db->syscalls = s_new; } return 0; } else if (s_iter->chains == NULL) { if (rm_flag || !s_iter->valid) { /* we are here because our previous pass cleared the * entire syscall chain when searching for a subtree * match or the existing syscall entry is a phantom, * so either way add the new chain */ s_iter->chains = s_new->chains; s_iter->action = s_new->action; s_iter->node_cnt = s_new->node_cnt; if (s_iter->valid) s_iter->priority = s_new->priority; s_iter->valid = true; free(s_new); rc = 0; goto add_priority_update; } else /* syscall exists without any chains - existing filter * is at least as large as the new entry so cleanup and * exit */ goto add_free_ok; } else if (s_iter->chains != NULL && s_new->chains == NULL) { /* syscall exists with chains but the new filter has no chains * so we need to clear the existing chains and exit */ _db_tree_free(s_iter->chains); s_iter->chains = NULL; s_iter->node_cnt = 0; s_iter->action = action; goto add_free_ok; } /* check for sub-tree matches */ memset(&state, 0, sizeof(state)); rc = _db_tree_sub_prune(&(s_iter->chains), ec_iter, c_iter, &state); if (rc > 0) { rm_flag = true; s_iter->node_cnt -= rc; goto add_reset; } else if (rc < 0) goto add_free_ok; /* syscall exists and has at least one existing chain - start at the * top and walk the two chains */ do { /* insert the new rule into the existing tree */ if (db_chain_eq(c_iter, ec_iter)) { /* found a matching node on this chain level */ if (db_chain_action(c_iter) && db_chain_action(ec_iter)) { /* both are "action" nodes */ if (c_iter->act_t_flg && ec_iter->act_t_flg) { if (ec_iter->act_t != action) goto add_free_exist; } else if (c_iter->act_t_flg) { ec_iter->act_t_flg = true; ec_iter->act_t = action; } if (c_iter->act_f_flg && ec_iter->act_f_flg) { if (ec_iter->act_f != action) goto add_free_exist; } else if (c_iter->act_f_flg) { ec_iter->act_f_flg = true; ec_iter->act_f = action; } if (ec_iter->act_t_flg == ec_iter->act_f_flg && ec_iter->act_t == ec_iter->act_f) { n_cnt = _db_tree_remove( &(s_iter->chains), ec_iter); s_iter->node_cnt -= n_cnt; goto add_free_ok; } } else if (db_chain_action(c_iter)) { /* new is shorter */ if (c_iter->act_t_flg) { rc = _db_tree_act_check(ec_iter->nxt_t, action); if (rc < 0) goto add_free; n_cnt = _db_tree_free(ec_iter->nxt_t); ec_iter->nxt_t = NULL; ec_iter->act_t_flg = true; ec_iter->act_t = action; } else { rc = _db_tree_act_check(ec_iter->nxt_f, action); if (rc < 0) goto add_free; n_cnt = _db_tree_free(ec_iter->nxt_f); ec_iter->nxt_f = NULL; ec_iter->act_f_flg = true; ec_iter->act_f = action; } s_iter->node_cnt -= n_cnt; } if (c_iter->nxt_t != NULL) { if (ec_iter->nxt_t != NULL) { /* jump to the next level */ c_prev = c_iter; c_iter = c_iter->nxt_t; ec_iter = ec_iter->nxt_t; s_new->node_cnt--; } else if (ec_iter->act_t_flg) { /* existing is shorter */ if (ec_iter->act_t == action) goto add_free_ok; goto add_free_exist; } else { /* add a new branch */ c_prev = c_iter; ec_iter->nxt_t = c_iter->nxt_t; s_iter->node_cnt += (s_new->node_cnt - 1); goto add_free_match; } } else if (c_iter->nxt_f != NULL) { if (ec_iter->nxt_f != NULL) { /* jump to the next level */ c_prev = c_iter; c_iter = c_iter->nxt_f; ec_iter = ec_iter->nxt_f; s_new->node_cnt--; } else if (ec_iter->act_f_flg) { /* existing is shorter */ if (ec_iter->act_f == action) goto add_free_ok; goto add_free_exist; } else { /* add a new branch */ c_prev = c_iter; ec_iter->nxt_f = c_iter->nxt_f; s_iter->node_cnt += (s_new->node_cnt - 1); goto add_free_match; } } else goto add_free_ok; } else { /* need to check other nodes on this level */ if (db_chain_lt(c_iter, ec_iter)) { if (ec_iter->lvl_prv == NULL) { /* add to the start of the level */ ec_iter->lvl_prv = c_iter; c_iter->lvl_nxt = ec_iter; if (ec_iter == s_iter->chains) s_iter->chains = c_iter; s_iter->node_cnt += s_new->node_cnt; goto add_free_match; } else ec_iter = ec_iter->lvl_prv; } else { if (ec_iter->lvl_nxt == NULL) { /* add to the end of the level */ ec_iter->lvl_nxt = c_iter; c_iter->lvl_prv = ec_iter; s_iter->node_cnt += s_new->node_cnt; goto add_free_match; } else if (db_chain_lt(c_iter, ec_iter->lvl_nxt)) { /* add new chain in between */ c_iter->lvl_nxt = ec_iter->lvl_nxt; ec_iter->lvl_nxt->lvl_prv = c_iter; ec_iter->lvl_nxt = c_iter; c_iter->lvl_prv = ec_iter; s_iter->node_cnt += s_new->node_cnt; goto add_free_match; } else ec_iter = ec_iter->lvl_nxt; } } } while ((c_iter != NULL) && (ec_iter != NULL)); /* we should never be here! */ return -EFAULT; add_free_exist: rc = -EEXIST; goto add_free; add_free_ok: rc = 0; add_free: /* free the new chain and its syscall struct */ _db_tree_free(s_new->chains); free(s_new); goto add_priority_update; add_free_match: /* free the matching portion of new chain */ if (c_prev != NULL) { c_prev->nxt_t = NULL; c_prev->nxt_f = NULL; _db_tree_free(s_new->chains); } free(s_new); rc = 0; add_priority_update: /* update the priority */ if (s_iter != NULL) { s_iter->priority &= (~_DB_PRI_MASK_CHAIN); s_iter->priority |= (_DB_PRI_MASK_CHAIN - s_iter->node_cnt); } return rc; } /** * Set the priority of a given syscall * @param col the filter collection * @param syscall the syscall number * @param priority priority value, higher value == higher priority * * This function sets the priority of the given syscall; this value is used * when generating the seccomp filter code such that higher priority syscalls * will incur less filter code overhead than the lower priority syscalls in the * filter. Returns zero on success, negative values on failure. * */ int db_col_syscall_priority(struct db_filter_col *col, int syscall, uint8_t priority) { int rc = 0, rc_tmp; unsigned int iter; int sc_tmp; struct db_filter *filter; for (iter = 0; iter < col->filter_cnt; iter++) { filter = col->filters[iter]; sc_tmp = syscall; rc_tmp = arch_syscall_translate(filter->arch, &sc_tmp); if (rc_tmp < 0) goto priority_failure; /* if this is a pseudo syscall then we need to rewrite the * syscall for some arch specific reason, don't forget the * special handling for syscall -1 */ if (sc_tmp < -1) { /* we set this as a strict op - we don't really care * since priorities are a "best effort" thing - as we * want to catch the -EDOM error and bail on this * architecture */ rc_tmp = arch_syscall_rewrite(filter->arch, &sc_tmp); if (rc_tmp == -EDOM) continue; if (rc_tmp < 0) goto priority_failure; } rc_tmp = _db_syscall_priority(filter, sc_tmp, priority); priority_failure: if (rc == 0 && rc_tmp < 0) rc = rc_tmp; } return rc; } /** * Add a new rule to the current filter * @param col the filter collection * @param strict the strict flag * @param action the filter action * @param syscall the syscall number * @param arg_cnt the number of argument filters in the argument filter chain * @param arg_array the argument filter chain, (uint, enum scmp_compare, ulong) * * This function adds a new argument/comparison/value to the seccomp filter for * a syscall; multiple arguments can be specified and they will be chained * together (essentially AND'd together) in the filter. When the strict flag * is true the function will fail if the exact rule can not be added to the * filter, if the strict flag is false the function will not fail if the * function needs to adjust the rule due to architecture specifics. Returns * zero on success, negative values on failure. * */ int db_col_rule_add(struct db_filter_col *col, bool strict, uint32_t action, int syscall, unsigned int arg_cnt, const struct scmp_arg_cmp *arg_array) { int rc = 0, rc_tmp; unsigned int iter; unsigned int arg_num; size_t chain_size; struct db_api_arg *chain = NULL; struct scmp_arg_cmp arg_data; /* collect the arguments for the filter rule */ chain_size = sizeof(*chain) * ARG_COUNT_MAX; chain = malloc(chain_size); if (chain == NULL) return -ENOMEM; memset(chain, 0, chain_size); for (iter = 0; iter < arg_cnt; iter++) { arg_data = arg_array[iter]; arg_num = arg_data.arg; if (arg_num < ARG_COUNT_MAX && chain[arg_num].valid == 0) { chain[arg_num].valid = 1; chain[arg_num].arg = arg_num; chain[arg_num].op = arg_data.op; /* XXX - we should check datum/mask size against the * arch definition, e.g. 64 bit datum on x86 */ switch (chain[arg_num].op) { case SCMP_CMP_NE: case SCMP_CMP_LT: case SCMP_CMP_LE: case SCMP_CMP_EQ: case SCMP_CMP_GE: case SCMP_CMP_GT: chain[arg_num].mask = DATUM_MAX; chain[arg_num].datum = arg_data.datum_a; break; case SCMP_CMP_MASKED_EQ: chain[arg_num].mask = arg_data.datum_a; chain[arg_num].datum = arg_data.datum_b; break; default: rc = -EINVAL; goto add_return; } } else { rc = -EINVAL; goto add_return; } } for (iter = 0; iter < col->filter_cnt; iter++) { rc_tmp = arch_filter_rule_add(col, col->filters[iter], strict, action, syscall, chain); if (rc == 0 && rc_tmp < 0) rc = rc_tmp; } add_return: if (chain != NULL) free(chain); return rc; } /** * Start a new seccomp filter transaction * @param col the filter collection * * This function starts a new seccomp filter transaction for the given filter * collection. Returns zero on success, negative values on failure. * */ int db_col_transaction_start(struct db_filter_col *col) { unsigned int iter; struct db_filter_snap *snap; struct db_filter *filter_o, *filter_s; struct db_api_rule_list *rule_o, *rule_s; /* allocate the snapshot */ snap = malloc(sizeof(*snap)); if (snap == NULL) return -ENOMEM; snap->filters = malloc(sizeof(struct db_filter *) * col->filter_cnt); if (snap->filters == NULL) { free(snap); return -ENOMEM; } snap->filter_cnt = col->filter_cnt; for (iter = 0; iter < snap->filter_cnt; iter++) snap->filters[iter] = NULL; snap->next = NULL; /* create a snapshot of the current filter state */ for (iter = 0; iter < col->filter_cnt; iter++) { /* allocate a new filter */ filter_o = col->filters[iter]; filter_s = _db_init(filter_o->arch); if (filter_s == NULL) goto trans_start_failure; snap->filters[iter] = filter_s; /* create a filter snapshot from existing rules */ rule_o = filter_o->rules; if (rule_o == NULL) continue; do { /* copy the rule */ rule_s = db_rule_dup(rule_o); if (rule_s == NULL) goto trans_start_failure; if (filter_s->rules != NULL) { rule_s->prev = filter_s->rules->prev; rule_s->next = filter_s->rules; filter_s->rules->prev->next = rule_s; filter_s->rules->prev = rule_s; } else { rule_s->prev = rule_s; rule_s->next = rule_s; filter_s->rules = rule_s; } /* insert the rule into the filter */ if (db_rule_add(filter_s, rule_o) != 0) goto trans_start_failure; /* next rule */ rule_o = rule_o->next; } while (rule_o != filter_o->rules); } /* add the snapshot to the list */ snap->next = col->snapshots; col->snapshots = snap; return 0; trans_start_failure: _db_snap_release(snap); return -ENOMEM; } /** * Abort the top most seccomp filter transaction * @param col the filter collection * * This function aborts the most recent seccomp filter transaction. * */ void db_col_transaction_abort(struct db_filter_col *col) { int iter; unsigned int filter_cnt; struct db_filter **filters; struct db_filter_snap *snap; if (col->snapshots == NULL) return; /* replace the current filter with the last snapshot */ snap = col->snapshots; col->snapshots = snap->next; filter_cnt = col->filter_cnt; filters = col->filters; col->filter_cnt = snap->filter_cnt; col->filters = snap->filters; free(snap); /* free the filter we swapped out */ for (iter = 0; iter < filter_cnt; iter++) _db_release(filters[iter]); free(filters); } /** * Commit the top most seccomp filter transaction * @param col the filter collection * * This function commits the most recent seccomp filter transaction. * */ void db_col_transaction_commit(struct db_filter_col *col) { struct db_filter_snap *snap; snap = col->snapshots; col->snapshots = snap->next; _db_snap_release(snap); }