diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:09:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:09:30 +0000 |
commit | 81749f1fe87e489c4e2e7408a0fae9370c3810b3 (patch) | |
tree | 2d1345a5762855b6577495d90ac134c4e92d7ff8 /src/gen_pfc.c | |
parent | Initial commit. (diff) | |
download | libseccomp-upstream.tar.xz libseccomp-upstream.zip |
Adding upstream version 2.5.5.upstream/2.5.5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/gen_pfc.c | 507 |
1 files changed, 507 insertions, 0 deletions
diff --git a/src/gen_pfc.c b/src/gen_pfc.c new file mode 100644 index 0000000..d58f586 --- /dev/null +++ b/src/gen_pfc.c @@ -0,0 +1,507 @@ +/** + * Seccomp Pseudo Filter Code (PFC) Generator + * + * Copyright (c) 2012 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 <errno.h> +#include <inttypes.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +/* NOTE: needed for the arch->token decoding in _pfc_arch() */ +#include <linux/audit.h> + +#include <seccomp.h> + +#include "arch.h" +#include "db.h" +#include "gen_pfc.h" +#include "helper.h" +#include "system.h" + +struct pfc_sys_list { + struct db_sys_list *sys; + struct pfc_sys_list *next; +}; + +/* XXX - we should check the fprintf() return values */ + +/** + * Display a string representation of the architecture + * @param arch the architecture definition + */ +static const char *_pfc_arch(const struct arch_def *arch) +{ + switch (arch->token) { + case SCMP_ARCH_X86: + return "x86"; + case SCMP_ARCH_X86_64: + return "x86_64"; + case SCMP_ARCH_X32: + return "x32"; + case SCMP_ARCH_ARM: + return "arm"; + case SCMP_ARCH_AARCH64: + return "aarch64"; + case SCMP_ARCH_MIPS: + return "mips"; + case SCMP_ARCH_MIPSEL: + return "mipsel"; + case SCMP_ARCH_MIPS64: + return "mips64"; + case SCMP_ARCH_MIPSEL64: + return "mipsel64"; + case SCMP_ARCH_MIPS64N32: + return "mips64n32"; + case SCMP_ARCH_MIPSEL64N32: + return "mipsel64n32"; + case SCMP_ARCH_PARISC: + return "parisc"; + case SCMP_ARCH_PARISC64: + return "parisc64"; + case SCMP_ARCH_PPC64: + return "ppc64"; + case SCMP_ARCH_PPC64LE: + return "ppc64le"; + case SCMP_ARCH_PPC: + return "ppc"; + case SCMP_ARCH_S390X: + return "s390x"; + case SCMP_ARCH_S390: + return "s390"; + case SCMP_ARCH_RISCV64: + return "riscv64"; + default: + return "UNKNOWN"; + } +} + +/** + * Display a string representation of the node argument + * @param fds the file stream to send the output + * @param arch the architecture definition + * @param node the node + */ +static void _pfc_arg(FILE *fds, + const struct arch_def *arch, + const struct db_arg_chain_tree *node) +{ + if (arch->size == ARCH_SIZE_64) { + if (arch_arg_offset_hi(arch, node->arg) == node->arg_offset) + fprintf(fds, "$a%d.hi32", node->arg); + else + fprintf(fds, "$a%d.lo32", node->arg); + } else + fprintf(fds, "$a%d", node->arg); +} + +/** + * Display a string representation of the filter action + * @param fds the file stream to send the output + * @param action the action + */ +static void _pfc_action(FILE *fds, uint32_t action) +{ + switch (action & SECCOMP_RET_ACTION_FULL) { + case SCMP_ACT_KILL_PROCESS: + fprintf(fds, "action KILL_PROCESS;\n"); + break; + case SCMP_ACT_KILL_THREAD: + fprintf(fds, "action KILL;\n"); + break; + case SCMP_ACT_TRAP: + fprintf(fds, "action TRAP;\n"); + break; + case SCMP_ACT_ERRNO(0): + fprintf(fds, "action ERRNO(%u);\n", (action & 0x0000ffff)); + break; + case SCMP_ACT_TRACE(0): + fprintf(fds, "action TRACE(%u);\n", (action & 0x0000ffff)); + break; + case SCMP_ACT_LOG: + fprintf(fds, "action LOG;\n"); + break; + case SCMP_ACT_ALLOW: + fprintf(fds, "action ALLOW;\n"); + break; + default: + fprintf(fds, "action 0x%x;\n", action); + } +} + +/** + * Indent the output stream + * @param fds the file stream to send the output + * @param lvl the indentation level + * + * This function indents the output stream with whitespace based on the + * requested indentation level. + */ +static void _indent(FILE *fds, unsigned int lvl) +{ + while (lvl-- > 0) + fprintf(fds, " "); +} + +/** + * Generate the pseudo filter code for an argument chain + * @param arch the architecture definition + * @param node the head of the argument chain + * @param lvl the indentation level + * @param fds the file stream to send the output + * + * This function generates the pseudo filter code representation of the given + * argument chain and writes it to the given output stream. + * + */ +static void _gen_pfc_chain(const struct arch_def *arch, + const struct db_arg_chain_tree *node, + unsigned int lvl, FILE *fds) +{ + const struct db_arg_chain_tree *c_iter; + + /* get to the start */ + c_iter = node; + while (c_iter->lvl_prv != NULL) + c_iter = c_iter->lvl_prv; + + while (c_iter != NULL) { + /* comparison operation */ + _indent(fds, lvl); + fprintf(fds, "if ("); + _pfc_arg(fds, arch, c_iter); + switch (c_iter->op) { + case SCMP_CMP_EQ: + fprintf(fds, " == "); + break; + case SCMP_CMP_GE: + fprintf(fds, " >= "); + break; + case SCMP_CMP_GT: + fprintf(fds, " > "); + break; + case SCMP_CMP_MASKED_EQ: + fprintf(fds, " & 0x%.8x == ", c_iter->mask); + break; + case SCMP_CMP_NE: + case SCMP_CMP_LT: + case SCMP_CMP_LE: + default: + fprintf(fds, " ??? "); + } + fprintf(fds, "%u)\n", c_iter->datum); + + /* true result */ + if (c_iter->act_t_flg) { + _indent(fds, lvl + 1); + _pfc_action(fds, c_iter->act_t); + } else if (c_iter->nxt_t != NULL) + _gen_pfc_chain(arch, c_iter->nxt_t, lvl + 1, fds); + + /* false result */ + if (c_iter->act_f_flg) { + _indent(fds, lvl); + fprintf(fds, "else\n"); + _indent(fds, lvl + 1); + _pfc_action(fds, c_iter->act_f); + } else if (c_iter->nxt_f != NULL) { + _indent(fds, lvl); + fprintf(fds, "else\n"); + _gen_pfc_chain(arch, c_iter->nxt_f, lvl + 1, fds); + } + + c_iter = c_iter->lvl_nxt; + } +} + +/** + * Generate pseudo filter code for a syscall + * @param arch the architecture definition + * @param sys the syscall filter + * @param fds the file stream to send the output + * + * This function generates a pseudo filter code representation of the given + * syscall filter and writes it to the given output stream. + * + */ +static void _gen_pfc_syscall(const struct arch_def *arch, + const struct db_sys_list *sys, FILE *fds, + int lvl) +{ + unsigned int sys_num = sys->num; + const char *sys_name = arch_syscall_resolve_num(arch, sys_num); + + _indent(fds, lvl); + fprintf(fds, "# filter for syscall \"%s\" (%u) [priority: %d]\n", + (sys_name ? sys_name : "UNKNOWN"), sys_num, sys->priority); + _indent(fds, lvl); + fprintf(fds, "if ($syscall == %u)\n", sys_num); + if (sys->chains == NULL) { + _indent(fds, lvl + 1); + _pfc_action(fds, sys->action); + } else + _gen_pfc_chain(arch, sys->chains, lvl + 1, fds); +} + +#define SYSCALLS_PER_NODE (4) +static int _get_bintree_levels(unsigned int syscall_cnt, + uint32_t optimize) +{ + unsigned int i = 0, max_level; + + if (optimize != 2) + /* Only use a binary tree if requested */ + return 0; + + if (syscall_cnt == 0) + return 0; + + do { + max_level = SYSCALLS_PER_NODE << i; + i++; + } while(max_level < syscall_cnt); + + return i; +} + +static int _get_bintree_syscall_num(const struct pfc_sys_list *cur, + int lookahead_cnt, + int *const num) +{ + while (lookahead_cnt > 0 && cur != NULL) { + cur = cur->next; + lookahead_cnt--; + } + + if (cur == NULL) + return -EFAULT; + + *num = cur->sys->num; + return 0; +} + +static int _sys_num_sort(struct db_sys_list *syscalls, + struct pfc_sys_list **p_head) +{ + struct pfc_sys_list *p_iter = NULL, *p_new, *p_prev; + struct db_sys_list *s_iter; + + db_list_foreach(s_iter, syscalls) { + p_new = zmalloc(sizeof(*p_new)); + if (p_new == NULL) { + return -ENOMEM; + } + p_new->sys = s_iter; + + p_prev = NULL; + p_iter = *p_head; + while (p_iter != NULL && + s_iter->num < p_iter->sys->num) { + p_prev = p_iter; + p_iter = p_iter->next; + } + if (*p_head == NULL) + *p_head = p_new; + else if (p_prev == NULL) { + p_new->next = *p_head; + *p_head = p_new; + } else { + p_new->next = p_iter; + p_prev->next = p_new; + } + } + + return 0; +} + +static int _sys_priority_sort(struct db_sys_list *syscalls, + struct pfc_sys_list **p_head) +{ + struct pfc_sys_list *p_iter = NULL, *p_new, *p_prev; + struct db_sys_list *s_iter; + + db_list_foreach(s_iter, syscalls) { + p_new = zmalloc(sizeof(*p_new)); + if (p_new == NULL) { + return -ENOMEM; + } + p_new->sys = s_iter; + + p_prev = NULL; + p_iter = *p_head; + while (p_iter != NULL && + s_iter->priority < p_iter->sys->priority) { + p_prev = p_iter; + p_iter = p_iter->next; + } + if (*p_head == NULL) + *p_head = p_new; + else if (p_prev == NULL) { + p_new->next = *p_head; + *p_head = p_new; + } else { + p_new->next = p_iter; + p_prev->next = p_new; + } + } + + return 0; +} + +static int _sys_sort(struct db_sys_list *syscalls, + struct pfc_sys_list **p_head, + uint32_t optimize) +{ + if (optimize != 2) + return _sys_priority_sort(syscalls, p_head); + else + /* sort by number for the binary tree */ + return _sys_num_sort(syscalls, p_head); +} + +/** + * Generate pseudo filter code for an architecture + * @param col the seccomp filter collection + * @param db the single seccomp filter + * @param fds the file stream to send the output + * + * This function generates a pseudo filter code representation of the given + * filter DB and writes it to the given output stream. Returns zero on + * success, negative values on failure. + * + */ +static int _gen_pfc_arch(const struct db_filter_col *col, + const struct db_filter *db, FILE *fds, + uint32_t optimize) +{ + int rc = 0, i = 0, lookahead_num; + unsigned int syscall_cnt = 0, bintree_levels, level, indent = 1; + struct pfc_sys_list *p_iter = NULL, *p_head = NULL; + + /* sort the syscall list */ + rc = _sys_sort(db->syscalls, &p_head, optimize); + if (rc < 0) + goto arch_return; + + bintree_levels = _get_bintree_levels(db->syscall_cnt, optimize); + + fprintf(fds, "# filter for arch %s (%u)\n", + _pfc_arch(db->arch), db->arch->token_bpf); + fprintf(fds, "if ($arch == %u)\n", db->arch->token_bpf); + p_iter = p_head; + while (p_iter != NULL) { + if (!p_iter->sys->valid) { + p_iter = p_iter->next; + continue; + } + + for (i = bintree_levels - 1; i > 0; i--) { + level = SYSCALLS_PER_NODE << i; + + if (syscall_cnt == 0 || (syscall_cnt % level) == 0) { + rc = _get_bintree_syscall_num(p_iter, level / 2, + &lookahead_num); + if (rc < 0) + /* We have reached the end of the bintree. + * There aren't enough syscalls to construct + * any more if-elses. + */ + continue; + _indent(fds, indent); + fprintf(fds, "if ($syscall > %u)\n", lookahead_num); + indent++; + } else if ((syscall_cnt % (level / 2)) == 0) { + lookahead_num = p_iter->sys->num; + _indent(fds, indent - 1); + fprintf(fds, "else # ($syscall <= %u)\n", + p_iter->sys->num); + } + + } + + _gen_pfc_syscall(db->arch, p_iter->sys, fds, indent); + syscall_cnt++; + p_iter = p_iter->next; + + /* undo the indentations as the else statements complete */ + for (i = 0; i < bintree_levels; i++) { + if (syscall_cnt % ((SYSCALLS_PER_NODE * 2) << i) == 0) + indent--; + } + } + _indent(fds, 1); + fprintf(fds, "# default action\n"); + _indent(fds, 1); + _pfc_action(fds, col->attr.act_default); + +arch_return: + while (p_head != NULL) { + p_iter = p_head; + p_head = p_head->next; + free(p_iter); + } + return rc; +} +/** + * Generate a pseudo filter code string representation + * @param col the seccomp filter collection + * @param fd the fd to send the output + * + * This function generates a pseudo filter code representation of the given + * filter collection and writes it to the given fd. Returns zero on success, + * negative errno values on failure. + * + */ +int gen_pfc_generate(const struct db_filter_col *col, int fd) +{ + int newfd; + unsigned int iter; + FILE *fds; + + newfd = dup(fd); + if (newfd < 0) + return -errno; + fds = fdopen(newfd, "a"); + if (fds == NULL) { + close(newfd); + return -errno; + } + + /* generate the pfc */ + fprintf(fds, "#\n"); + fprintf(fds, "# pseudo filter code start\n"); + fprintf(fds, "#\n"); + + for (iter = 0; iter < col->filter_cnt; iter++) + _gen_pfc_arch(col, col->filters[iter], fds, + col->attr.optimize); + + fprintf(fds, "# invalid architecture action\n"); + _pfc_action(fds, col->attr.act_badarch); + fprintf(fds, "#\n"); + fprintf(fds, "# pseudo filter code end\n"); + fprintf(fds, "#\n"); + + fflush(fds); + fclose(fds); + + return 0; +} |