/** * Seccomp Library API * * Copyright (c) 2012,2013 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 #include #include #include "arch.h" #include "db.h" #include "gen_pfc.h" #include "gen_bpf.h" #include "helper.h" #include "system.h" #define API __attribute__((visibility("default"))) const struct scmp_version library_version = { .major = SCMP_VER_MAJOR, .minor = SCMP_VER_MINOR, .micro = SCMP_VER_MICRO, }; unsigned int seccomp_api_level = 0; /** * Validate a filter context * @param ctx the filter context * * Attempt to validate the provided filter context. Returns zero if the * context is valid, negative values on failure. * */ static int _ctx_valid(const scmp_filter_ctx *ctx) { return db_col_valid((struct db_filter_col *)ctx); } /** * Validate a syscall number * @param syscall the syscall number * * Attempt to perform basic syscall number validation. Returns zero of the * syscall appears valid, negative values on failure. * */ static int _syscall_valid(const struct db_filter_col *col, int syscall) { /* syscall -1 is used by tracers to skip the syscall */ if (col->attr.api_tskip && syscall == -1) return 0; if (syscall <= -1 && syscall >= -99) return -EINVAL; return 0; } /** * Update the API level * * This function performs a series of tests to determine what functionality is * supported given the current running environment (kernel, etc.). It is * important to note that this function only does meaningful checks the first * time it is run, the resulting API level is cached after this first run and * used for all subsequent calls. The API level value is returned. * */ static unsigned int _seccomp_api_update(void) { unsigned int level = 1; /* if seccomp_api_level > 0 then it's already been set, we're done */ if (seccomp_api_level >= 1) return seccomp_api_level; /* NOTE: level 1 is the base level, start checking at 2 */ /* level 2 */ if (sys_chk_seccomp_syscall() && sys_chk_seccomp_flag(SECCOMP_FILTER_FLAG_TSYNC) == 1) level = 2; /* level 3 */ if (level == 2 && sys_chk_seccomp_flag(SECCOMP_FILTER_FLAG_LOG) == 1 && sys_chk_seccomp_action(SCMP_ACT_LOG) == 1 && sys_chk_seccomp_action(SCMP_ACT_KILL_PROCESS) == 1) level = 3; if (level == 3 && sys_chk_seccomp_flag(SECCOMP_FILTER_FLAG_SPEC_ALLOW) == 1) level = 4; if (level == 4 && sys_chk_seccomp_flag(SECCOMP_FILTER_FLAG_NEW_LISTENER) == 1 && sys_chk_seccomp_action(SCMP_ACT_NOTIFY) == 1) level = 5; /* update the stored api level and return */ seccomp_api_level = level; return seccomp_api_level; } /* NOTE - function header comment in include/seccomp.h */ API const struct scmp_version *seccomp_version(void) { return &library_version; } /* NOTE - function header comment in include/seccomp.h */ API unsigned int seccomp_api_get(void) { /* update the api level, if needed */ return _seccomp_api_update(); } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_api_set(unsigned int level) { switch (level) { case 1: sys_set_seccomp_syscall(false); sys_set_seccomp_flag(SECCOMP_FILTER_FLAG_TSYNC, false); sys_set_seccomp_flag(SECCOMP_FILTER_FLAG_LOG, false); sys_set_seccomp_action(SCMP_ACT_LOG, false); sys_set_seccomp_action(SCMP_ACT_KILL_PROCESS, false); break; case 2: sys_set_seccomp_syscall(true); sys_set_seccomp_flag(SECCOMP_FILTER_FLAG_TSYNC, true); sys_set_seccomp_flag(SECCOMP_FILTER_FLAG_LOG, false); sys_set_seccomp_action(SCMP_ACT_LOG, false); sys_set_seccomp_action(SCMP_ACT_KILL_PROCESS, false); break; case 3: sys_set_seccomp_syscall(true); sys_set_seccomp_flag(SECCOMP_FILTER_FLAG_TSYNC, true); sys_set_seccomp_flag(SECCOMP_FILTER_FLAG_LOG, true); sys_set_seccomp_action(SCMP_ACT_LOG, true); sys_set_seccomp_action(SCMP_ACT_KILL_PROCESS, true); break; case 4: sys_set_seccomp_syscall(true); sys_set_seccomp_flag(SECCOMP_FILTER_FLAG_TSYNC, true); sys_set_seccomp_flag(SECCOMP_FILTER_FLAG_LOG, true); sys_set_seccomp_action(SCMP_ACT_LOG, true); sys_set_seccomp_action(SCMP_ACT_KILL_PROCESS, true); sys_set_seccomp_flag(SECCOMP_FILTER_FLAG_SPEC_ALLOW, true); break; case 5: sys_set_seccomp_syscall(true); sys_set_seccomp_flag(SECCOMP_FILTER_FLAG_TSYNC, true); sys_set_seccomp_flag(SECCOMP_FILTER_FLAG_LOG, true); sys_set_seccomp_action(SCMP_ACT_LOG, true); sys_set_seccomp_action(SCMP_ACT_KILL_PROCESS, true); sys_set_seccomp_flag(SECCOMP_FILTER_FLAG_SPEC_ALLOW, true); sys_set_seccomp_flag(SECCOMP_FILTER_FLAG_NEW_LISTENER, true); sys_set_seccomp_action(SCMP_ACT_NOTIFY, true); break; default: return -EINVAL; } seccomp_api_level = level; return 0; } /* NOTE - function header comment in include/seccomp.h */ API scmp_filter_ctx seccomp_init(uint32_t def_action) { /* force a runtime api level detection */ _seccomp_api_update(); if (db_col_action_valid(NULL, def_action) < 0) return NULL; return db_col_init(def_action); } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_reset(scmp_filter_ctx ctx, uint32_t def_action) { struct db_filter_col *col = (struct db_filter_col *)ctx; /* use a NULL filter collection here since we are resetting it */ if (ctx == NULL || db_col_action_valid(NULL, def_action) < 0) return -EINVAL; return db_col_reset(col, def_action); } /* NOTE - function header comment in include/seccomp.h */ API void seccomp_release(scmp_filter_ctx ctx) { db_col_release((struct db_filter_col *)ctx); } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_merge(scmp_filter_ctx ctx_dst, scmp_filter_ctx ctx_src) { struct db_filter_col *col_dst = (struct db_filter_col *)ctx_dst; struct db_filter_col *col_src = (struct db_filter_col *)ctx_src; if (db_col_valid(col_dst) || db_col_valid(col_src)) return -EINVAL; /* NOTE: only the default action, NNP, and TSYNC settings must match */ if ((col_dst->attr.act_default != col_src->attr.act_default) || (col_dst->attr.nnp_enable != col_src->attr.nnp_enable) || (col_dst->attr.tsync_enable != col_src->attr.tsync_enable)) return -EINVAL; return db_col_merge(col_dst, col_src); } /* NOTE - function header comment in include/seccomp.h */ API uint32_t seccomp_arch_resolve_name(const char *arch_name) { const struct arch_def *arch; if (arch_name == NULL) return 0; arch = arch_def_lookup_name(arch_name); if (arch == NULL) return 0; return arch->token; } /* NOTE - function header comment in include/seccomp.h */ API uint32_t seccomp_arch_native(void) { return arch_def_native->token; } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_arch_exist(const scmp_filter_ctx ctx, uint32_t arch_token) { struct db_filter_col *col = (struct db_filter_col *)ctx; if (arch_token == 0) arch_token = arch_def_native->token; if (arch_valid(arch_token)) return -EINVAL; return (db_col_arch_exist(col, arch_token) ? 0 : -EEXIST); } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_arch_add(scmp_filter_ctx ctx, uint32_t arch_token) { const struct arch_def *arch; struct db_filter_col *col = (struct db_filter_col *)ctx; if (arch_token == 0) arch_token = arch_def_native->token; arch = arch_def_lookup(arch_token); if (arch == NULL) return -EINVAL; if (db_col_arch_exist(col, arch_token)) return -EEXIST; return db_col_db_new(col, arch); } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_arch_remove(scmp_filter_ctx ctx, uint32_t arch_token) { struct db_filter_col *col = (struct db_filter_col *)ctx; if (arch_token == 0) arch_token = arch_def_native->token; if (arch_valid(arch_token)) return -EINVAL; if (db_col_arch_exist(col, arch_token) != -EEXIST) return -EEXIST; return db_col_db_remove(col, arch_token); } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_load(const scmp_filter_ctx ctx) { struct db_filter_col *col; if (_ctx_valid(ctx)) return -EINVAL; col = (struct db_filter_col *)ctx; return sys_filter_load(col); } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_attr_get(const scmp_filter_ctx ctx, enum scmp_filter_attr attr, uint32_t *value) { if (_ctx_valid(ctx)) return -EINVAL; return db_col_attr_get((const struct db_filter_col *)ctx, attr, value); } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_attr_set(scmp_filter_ctx ctx, enum scmp_filter_attr attr, uint32_t value) { if (_ctx_valid(ctx)) return -EINVAL; return db_col_attr_set((struct db_filter_col *)ctx, attr, value); } /* NOTE - function header comment in include/seccomp.h */ API char *seccomp_syscall_resolve_num_arch(uint32_t arch_token, int num) { const struct arch_def *arch; const char *name; if (arch_token == 0) arch_token = arch_def_native->token; if (arch_valid(arch_token)) return NULL; arch = arch_def_lookup(arch_token); if (arch == NULL) return NULL; name = arch_syscall_resolve_num(arch, num); if (name == NULL) return NULL; return strdup(name); } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_syscall_resolve_name_arch(uint32_t arch_token, const char *name) { const struct arch_def *arch; if (name == NULL) return __NR_SCMP_ERROR; if (arch_token == 0) arch_token = arch_def_native->token; if (arch_valid(arch_token)) return __NR_SCMP_ERROR; arch = arch_def_lookup(arch_token); if (arch == NULL) return __NR_SCMP_ERROR; return arch_syscall_resolve_name(arch, name); } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_syscall_resolve_name_rewrite(uint32_t arch_token, const char *name) { int rc; int syscall; const struct arch_def *arch; if (name == NULL) return __NR_SCMP_ERROR; if (arch_token == 0) arch_token = arch_def_native->token; if (arch_valid(arch_token)) return __NR_SCMP_ERROR; arch = arch_def_lookup(arch_token); if (arch == NULL) return __NR_SCMP_ERROR; syscall = arch_syscall_resolve_name(arch, name); if (syscall == __NR_SCMP_ERROR) return __NR_SCMP_ERROR; rc = arch_syscall_rewrite(arch, &syscall); if (rc == -EDOM) /* if we can't rewrite the syscall, just pass it through */ return syscall; else if (rc < 0) return __NR_SCMP_ERROR; return syscall; } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_syscall_resolve_name(const char *name) { return seccomp_syscall_resolve_name_arch(SCMP_ARCH_NATIVE, name); } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_syscall_priority(scmp_filter_ctx ctx, int syscall, uint8_t priority) { struct db_filter_col *col = (struct db_filter_col *)ctx; if (db_col_valid(col) || _syscall_valid(col, syscall)) return -EINVAL; return db_col_syscall_priority(col, syscall, priority); } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_rule_add_array(scmp_filter_ctx ctx, uint32_t action, int syscall, unsigned int arg_cnt, const struct scmp_arg_cmp *arg_array) { int rc; struct db_filter_col *col = (struct db_filter_col *)ctx; if (arg_cnt > ARG_COUNT_MAX) return -EINVAL; if (arg_cnt > 0 && arg_array == NULL) return -EINVAL; if (db_col_valid(col) || _syscall_valid(col, syscall)) return -EINVAL; rc = db_col_action_valid(col, action); if (rc < 0) return rc; if (action == col->attr.act_default) return -EPERM; return db_col_rule_add(col, 0, action, syscall, arg_cnt, arg_array); } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_rule_add(scmp_filter_ctx ctx, uint32_t action, int syscall, unsigned int arg_cnt, ...) { int rc; int iter; struct scmp_arg_cmp arg_array[ARG_COUNT_MAX]; va_list arg_list; /* arg_cnt is unsigned, so no need to check the lower bound */ if (arg_cnt > ARG_COUNT_MAX) return -EINVAL; va_start(arg_list, arg_cnt); for (iter = 0; iter < arg_cnt; ++iter) arg_array[iter] = va_arg(arg_list, struct scmp_arg_cmp); rc = seccomp_rule_add_array(ctx, action, syscall, arg_cnt, arg_array); va_end(arg_list); return rc; } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_rule_add_exact_array(scmp_filter_ctx ctx, uint32_t action, int syscall, unsigned int arg_cnt, const struct scmp_arg_cmp *arg_array) { int rc; struct db_filter_col *col = (struct db_filter_col *)ctx; if (arg_cnt > ARG_COUNT_MAX) return -EINVAL; if (arg_cnt > 0 && arg_array == NULL) return -EINVAL; if (db_col_valid(col) || _syscall_valid(col, syscall)) return -EINVAL; rc = db_col_action_valid(col, action); if (rc < 0) return rc; if (action == col->attr.act_default) return -EPERM; if (col->filter_cnt > 1) return -EOPNOTSUPP; return db_col_rule_add(col, 1, action, syscall, arg_cnt, arg_array); } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_rule_add_exact(scmp_filter_ctx ctx, uint32_t action, int syscall, unsigned int arg_cnt, ...) { int rc; int iter; struct scmp_arg_cmp arg_array[ARG_COUNT_MAX]; va_list arg_list; /* arg_cnt is unsigned, so no need to check the lower bound */ if (arg_cnt > ARG_COUNT_MAX) return -EINVAL; va_start(arg_list, arg_cnt); for (iter = 0; iter < arg_cnt; ++iter) arg_array[iter] = va_arg(arg_list, struct scmp_arg_cmp); rc = seccomp_rule_add_exact_array(ctx, action, syscall, arg_cnt, arg_array); va_end(arg_list); return rc; } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_notify_alloc(struct seccomp_notif **req, struct seccomp_notif_resp **resp) { /* force a runtime api level detection */ _seccomp_api_update(); return sys_notify_alloc(req, resp); } /* NOTE - function header comment in include/seccomp.h */ API void seccomp_notify_free(struct seccomp_notif *req, struct seccomp_notif_resp *resp) { if (req) free(req); if (resp) free(resp); } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_notify_receive(int fd, struct seccomp_notif *req) { return sys_notify_receive(fd, req); } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_notify_respond(int fd, struct seccomp_notif_resp *resp) { return sys_notify_respond(fd, resp); } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_notify_id_valid(int fd, uint64_t id) { /* force a runtime api level detection */ _seccomp_api_update(); return sys_notify_id_valid(fd, id); } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_notify_fd(const scmp_filter_ctx ctx) { struct db_filter_col *col; /* force a runtime api level detection */ _seccomp_api_update(); if (_ctx_valid(ctx)) return -EINVAL; col = (struct db_filter_col *)ctx; return col->notify_fd; } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_export_pfc(const scmp_filter_ctx ctx, int fd) { if (_ctx_valid(ctx)) return -EINVAL; return gen_pfc_generate((struct db_filter_col *)ctx, fd); } /* NOTE - function header comment in include/seccomp.h */ API int seccomp_export_bpf(const scmp_filter_ctx ctx, int fd) { int rc; struct bpf_program *program; if (_ctx_valid(ctx)) return -EINVAL; program = gen_bpf_generate((struct db_filter_col *)ctx); if (program == NULL) return -ENOMEM; rc = write(fd, program->blks, BPF_PGM_SIZE(program)); gen_bpf_release(program); if (rc < 0) return -errno; return 0; }