/**
* 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";
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 pseduo 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)
{
unsigned int sys_num = sys->num;
const char *sys_name = arch_syscall_resolve_num(arch, sys_num);
_indent(fds, 1);
fprintf(fds, "# filter for syscall \"%s\" (%u) [priority: %d]\n",
(sys_name ? sys_name : "UNKNOWN"), sys_num, sys->priority);
_indent(fds, 1);
fprintf(fds, "if ($syscall == %u)\n", sys_num);
if (sys->chains == NULL) {
_indent(fds, 2);
_pfc_action(fds, sys->action);
} else
_gen_pfc_chain(arch, sys->chains, 2, fds);
}
/**
* 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)
{
int rc = 0;
struct db_sys_list *s_iter;
struct pfc_sys_list *p_iter = NULL, *p_new, *p_head = NULL, *p_prev;
/* sort the syscall list */
db_list_foreach(s_iter, db->syscalls) {
p_new = zmalloc(sizeof(*p_new));
if (p_new == NULL) {
rc = -ENOMEM;
goto arch_return;
}
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;
}
}
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;
}
_gen_pfc_syscall(db->arch, p_iter->sys, fds);
p_iter = p_iter->next;
}
_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 values on failure.
*
*/
int gen_pfc_generate(const struct db_filter_col *col, int fd)
{
int rc = 0;
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);
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 rc;
}