X-Git-Url: http://git.ieval.ro/?p=linux-seccomp.git;a=blobdiff_plain;f=libseccomp%2Fsrc%2Fdb.c;fp=libseccomp%2Fsrc%2Fdb.c;h=1a85b1f82aeb5498901bb8aeb6fbc23fa8a2f977;hp=0000000000000000000000000000000000000000;hb=8befd5cc4d2b478c697d81a5ac191083c203d081;hpb=bcf524c10c0ad85fcef711acffc3251bb8472352 diff --git a/libseccomp/src/db.c b/libseccomp/src/db.c new file mode 100644 index 0000000..1a85b1f --- /dev/null +++ b/libseccomp/src/db.c @@ -0,0 +1,1766 @@ +/** + * Enhanced Seccomp Filter DB + * + * Copyright (c) 2012,2016 Red Hat + * Author: Paul Moore + */ + +/* + * 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 . + */ + +#include +#include +#include +#include +#include +#include + +#include + +#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->args); + 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; +} + +/** + * 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; + + /* 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; + 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; + 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, + struct db_api_arg *chain) +{ + unsigned int iter; + int chain_len_max; + 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 */ + chain_len_max = arch_arg_count_max(arch); + if (chain_len_max < 0) + goto gen_64_failure; + for (iter = 0; iter < chain_len_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, + struct db_api_arg *chain) +{ + unsigned int iter; + int chain_len_max; + 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 */ + chain_len_max = arch_arg_count_max(arch); + if (chain_len_max < 0) + goto gen_32_failure; + for (iter = 0; iter < chain_len_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; + 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 (syscall < 0) then we need to + * rewrite the syscall for some arch specific reason */ + if (sc_tmp < 0) { + /* 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 chain_len; + 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_len = ARG_COUNT_MAX; + chain_size = sizeof(*chain) * chain_len; + 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 < chain_len && 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_len, 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; + size_t args_size; + 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 = malloc(sizeof(*rule_s)); + if (rule_s == NULL) + goto trans_start_failure; + args_size = sizeof(*rule_s->args) * rule_o->args_cnt; + rule_s->args = malloc(args_size); + if (rule_s->args == NULL) { + free(rule_s); + goto trans_start_failure; + } + rule_s->action = rule_o->action; + rule_s->syscall = rule_o->syscall; + rule_s->args_cnt = rule_o->args_cnt; + memcpy(rule_s->args, rule_o->args, args_size); + 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); +}