/** * Seccomp Pseudo Filter Code (PFC) Generator * * Copyright (c) 2012 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 /* NOTE: needed for the arch->token decoding in _pfc_arch() */ #include #include #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_LOONGARCH64: return "loongarch64"; case SCMP_ARCH_M68K: return "m68k"; 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"; case SCMP_ARCH_SHEB: return "sheb"; case SCMP_ARCH_SH: return "sh"; 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; }