/**
* Enhanced Seccomp Syscall Table Functions
*
* Copyright (c) 2012, 2020 Red Hat <pmoore@redhat.com>
* Author: Paul Moore <paul@paul-moore.com>
* gperf support: Giuseppe Scrivano <gscrivan@redhat.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 <stdlib.h>
#include <errno.h>
#include <string.h>
#include <seccomp.h>
#include "db.h"
#include "arch.h"
#include "syscalls.h"
/**
* Resolve a syscall name to a number
* @param arch the arch definition
* @param name the syscall name
*
* Resolve the given syscall name to the syscall number using the syscall table.
* Returns the syscall number on success, including negative pseudo syscall
* numbers; returns __NR_SCMP_ERROR on failure.
*
*/
int abi_syscall_resolve_name_munge(const struct arch_def *arch,
const char *name)
{
#define _ABI_SYSCALL_RES_NAME_CHK(NAME) \
if (!strcmp(name, #NAME)) return __PNR_##NAME;
_ABI_SYSCALL_RES_NAME_CHK(socket)
_ABI_SYSCALL_RES_NAME_CHK(bind)
_ABI_SYSCALL_RES_NAME_CHK(connect)
_ABI_SYSCALL_RES_NAME_CHK(listen)
_ABI_SYSCALL_RES_NAME_CHK(accept)
_ABI_SYSCALL_RES_NAME_CHK(getsockname)
_ABI_SYSCALL_RES_NAME_CHK(getpeername)
_ABI_SYSCALL_RES_NAME_CHK(socketpair)
_ABI_SYSCALL_RES_NAME_CHK(send)
_ABI_SYSCALL_RES_NAME_CHK(recv)
_ABI_SYSCALL_RES_NAME_CHK(sendto)
_ABI_SYSCALL_RES_NAME_CHK(recvfrom)
_ABI_SYSCALL_RES_NAME_CHK(shutdown)
_ABI_SYSCALL_RES_NAME_CHK(setsockopt)
_ABI_SYSCALL_RES_NAME_CHK(getsockopt)
_ABI_SYSCALL_RES_NAME_CHK(sendmsg)
_ABI_SYSCALL_RES_NAME_CHK(recvmsg)
_ABI_SYSCALL_RES_NAME_CHK(accept4)
_ABI_SYSCALL_RES_NAME_CHK(recvmmsg)
_ABI_SYSCALL_RES_NAME_CHK(sendmmsg)
_ABI_SYSCALL_RES_NAME_CHK(semop)
_ABI_SYSCALL_RES_NAME_CHK(semget)
_ABI_SYSCALL_RES_NAME_CHK(semctl)
_ABI_SYSCALL_RES_NAME_CHK(semtimedop)
_ABI_SYSCALL_RES_NAME_CHK(msgsnd)
_ABI_SYSCALL_RES_NAME_CHK(msgrcv)
_ABI_SYSCALL_RES_NAME_CHK(msgget)
_ABI_SYSCALL_RES_NAME_CHK(msgctl)
_ABI_SYSCALL_RES_NAME_CHK(shmat)
_ABI_SYSCALL_RES_NAME_CHK(shmdt)
_ABI_SYSCALL_RES_NAME_CHK(shmget)
_ABI_SYSCALL_RES_NAME_CHK(shmctl)
return arch->syscall_resolve_name_raw(name);
}
/**
* Resolve a syscall number to a name
* @param arch the arch definition
* @param num the syscall number
*
* Resolve the given syscall number to the syscall name using the syscall table.
* Returns a pointer to the syscall name string on success, including pseudo
* syscall names; returns NULL on failure.
*
*/
const char *abi_syscall_resolve_num_munge(const struct arch_def *arch, int num)
{
#define _ABI_SYSCALL_RES_NUM_CHK(NAME) \
if (num == __PNR_##NAME) return #NAME;
_ABI_SYSCALL_RES_NUM_CHK(socket)
_ABI_SYSCALL_RES_NUM_CHK(bind)
_ABI_SYSCALL_RES_NUM_CHK(connect)
_ABI_SYSCALL_RES_NUM_CHK(listen)
_ABI_SYSCALL_RES_NUM_CHK(accept)
_ABI_SYSCALL_RES_NUM_CHK(getsockname)
_ABI_SYSCALL_RES_NUM_CHK(getpeername)
_ABI_SYSCALL_RES_NUM_CHK(socketpair)
_ABI_SYSCALL_RES_NUM_CHK(send)
_ABI_SYSCALL_RES_NUM_CHK(recv)
_ABI_SYSCALL_RES_NUM_CHK(sendto)
_ABI_SYSCALL_RES_NUM_CHK(recvfrom)
_ABI_SYSCALL_RES_NUM_CHK(shutdown)
_ABI_SYSCALL_RES_NUM_CHK(setsockopt)
_ABI_SYSCALL_RES_NUM_CHK(getsockopt)
_ABI_SYSCALL_RES_NUM_CHK(sendmsg)
_ABI_SYSCALL_RES_NUM_CHK(recvmsg)
_ABI_SYSCALL_RES_NUM_CHK(accept4)
_ABI_SYSCALL_RES_NUM_CHK(recvmmsg)
_ABI_SYSCALL_RES_NUM_CHK(sendmmsg)
_ABI_SYSCALL_RES_NUM_CHK(semop)
_ABI_SYSCALL_RES_NUM_CHK(semget)
_ABI_SYSCALL_RES_NUM_CHK(semctl)
_ABI_SYSCALL_RES_NUM_CHK(semtimedop)
_ABI_SYSCALL_RES_NUM_CHK(msgsnd)
_ABI_SYSCALL_RES_NUM_CHK(msgrcv)
_ABI_SYSCALL_RES_NUM_CHK(msgget)
_ABI_SYSCALL_RES_NUM_CHK(msgctl)
_ABI_SYSCALL_RES_NUM_CHK(shmat)
_ABI_SYSCALL_RES_NUM_CHK(shmdt)
_ABI_SYSCALL_RES_NUM_CHK(shmget)
_ABI_SYSCALL_RES_NUM_CHK(shmctl)
return arch->syscall_resolve_num_raw(num);
}
/**
* Check if a syscall is a socket syscall
* @param arch the arch definition
* @param sys the syscall number
*
* Returns true if the syscall is a socket related syscall, false otherwise.
*
*/
static bool _abi_syscall_socket_test(const struct arch_def *arch, int sys)
{
const char *name;
/* multiplexed pseduo-syscalls */
if (sys <= -100 && sys >= -120)
return true;
name = arch->syscall_resolve_num_raw(sys);
if (!name)
return false;
#define _ABI_SYSCALL_SOCK_CHK(NAME) \
if (!strcmp(name, #NAME)) return true;
_ABI_SYSCALL_SOCK_CHK(socket)
_ABI_SYSCALL_SOCK_CHK(bind)
_ABI_SYSCALL_SOCK_CHK(connect)
_ABI_SYSCALL_SOCK_CHK(listen)
_ABI_SYSCALL_SOCK_CHK(accept)
_ABI_SYSCALL_SOCK_CHK(getsockname)
_ABI_SYSCALL_SOCK_CHK(getpeername)
_ABI_SYSCALL_SOCK_CHK(socketpair)
_ABI_SYSCALL_SOCK_CHK(send)
_ABI_SYSCALL_SOCK_CHK(recv)
_ABI_SYSCALL_SOCK_CHK(sendto)
_ABI_SYSCALL_SOCK_CHK(recvfrom)
_ABI_SYSCALL_SOCK_CHK(shutdown)
_ABI_SYSCALL_SOCK_CHK(setsockopt)
_ABI_SYSCALL_SOCK_CHK(getsockopt)
_ABI_SYSCALL_SOCK_CHK(sendmsg)
_ABI_SYSCALL_SOCK_CHK(recvmsg)
_ABI_SYSCALL_SOCK_CHK(accept4)
_ABI_SYSCALL_SOCK_CHK(recvmmsg)
_ABI_SYSCALL_SOCK_CHK(sendmmsg)
return false;
}
/**
* Check if a syscall is an ipc syscall
* @param arch the arch definition
* @param sys the syscall number
*
* Returns true if the syscall is an ipc related syscall, false otherwise.
*
*/
static bool _abi_syscall_ipc_test(const struct arch_def *arch, int sys)
{
const char *name;
/* multiplexed pseduo-syscalls */
if (sys <= -200 && sys >= -224)
return true;
name = arch->syscall_resolve_num_raw(sys);
if (!name)
return false;
#define _ABI_SYSCALL_IPC_CHK(NAME) \
if (!strcmp(name, #NAME)) return true;
_ABI_SYSCALL_IPC_CHK(semop)
_ABI_SYSCALL_IPC_CHK(semget)
_ABI_SYSCALL_IPC_CHK(semctl)
_ABI_SYSCALL_IPC_CHK(semtimedop)
_ABI_SYSCALL_IPC_CHK(msgsnd)
_ABI_SYSCALL_IPC_CHK(msgrcv)
_ABI_SYSCALL_IPC_CHK(msgget)
_ABI_SYSCALL_IPC_CHK(msgctl)
_ABI_SYSCALL_IPC_CHK(shmat)
_ABI_SYSCALL_IPC_CHK(shmdt)
_ABI_SYSCALL_IPC_CHK(shmget)
_ABI_SYSCALL_IPC_CHK(shmctl)
return false;
}
/**
* Convert a multiplexed pseudo syscall into a direct syscall
* @param arch the arch definition
* @param syscall the multiplexed pseudo syscall number
*
* Return the related direct syscall number, __NR_SCMP_UNDEF is there is
* no related syscall, or __NR_SCMP_ERROR otherwise.
*
*/
static int _abi_syscall_demux(const struct arch_def *arch, int syscall)
{
int sys = __NR_SCMP_UNDEF;
#define _ABI_SYSCALL_DEMUX_CHK(NAME) \
case __PNR_##NAME: \
sys = arch->syscall_resolve_name_raw(#NAME); break;
switch (syscall) {
_ABI_SYSCALL_DEMUX_CHK(socket)
_ABI_SYSCALL_DEMUX_CHK(bind)
_ABI_SYSCALL_DEMUX_CHK(connect)
_ABI_SYSCALL_DEMUX_CHK(listen)
_ABI_SYSCALL_DEMUX_CHK(accept)
_ABI_SYSCALL_DEMUX_CHK(getsockname)
_ABI_SYSCALL_DEMUX_CHK(getpeername)
_ABI_SYSCALL_DEMUX_CHK(socketpair)
_ABI_SYSCALL_DEMUX_CHK(send)
_ABI_SYSCALL_DEMUX_CHK(recv)
_ABI_SYSCALL_DEMUX_CHK(sendto)
_ABI_SYSCALL_DEMUX_CHK(recvfrom)
_ABI_SYSCALL_DEMUX_CHK(shutdown)
_ABI_SYSCALL_DEMUX_CHK(setsockopt)
_ABI_SYSCALL_DEMUX_CHK(getsockopt)
_ABI_SYSCALL_DEMUX_CHK(sendmsg)
_ABI_SYSCALL_DEMUX_CHK(recvmsg)
_ABI_SYSCALL_DEMUX_CHK(accept4)
_ABI_SYSCALL_DEMUX_CHK(recvmmsg)
_ABI_SYSCALL_DEMUX_CHK(sendmmsg)
_ABI_SYSCALL_DEMUX_CHK(semop)
_ABI_SYSCALL_DEMUX_CHK(semget)
_ABI_SYSCALL_DEMUX_CHK(semctl)
_ABI_SYSCALL_DEMUX_CHK(semtimedop)
_ABI_SYSCALL_DEMUX_CHK(msgsnd)
_ABI_SYSCALL_DEMUX_CHK(msgrcv)
_ABI_SYSCALL_DEMUX_CHK(msgget)
_ABI_SYSCALL_DEMUX_CHK(msgctl)
_ABI_SYSCALL_DEMUX_CHK(shmat)
_ABI_SYSCALL_DEMUX_CHK(shmdt)
_ABI_SYSCALL_DEMUX_CHK(shmget)
_ABI_SYSCALL_DEMUX_CHK(shmctl)
}
/* this looks odd because the arch resolver returns _ERROR if it can't
* resolve the syscall, but we want to use _UNDEF for that, so we set
* 'sys' to a sentinel value of _UNDEF and if it is error here we know
* the resolve failed to find a match */
if (sys == __NR_SCMP_UNDEF)
sys = __NR_SCMP_ERROR;
else if (sys == __NR_SCMP_ERROR)
sys = __NR_SCMP_UNDEF;
return sys;
}
/**
* Convert a direct syscall into multiplexed pseudo socket syscall
* @param arch the arch definition
* @param syscall the direct syscall
*
* Return the related multiplexed pseduo syscall number, __NR_SCMP_UNDEF is
* there is no related pseudo syscall, or __NR_SCMP_ERROR otherwise.
*
*/
static int _abi_syscall_mux(const struct arch_def *arch, int syscall)
{
const char *sys;
sys = arch->syscall_resolve_num_raw(syscall);
if (!sys)
return __NR_SCMP_ERROR;
#define _ABI_SYSCALL_MUX_CHK(NAME) \
if (!strcmp(sys, #NAME)) return __PNR_##NAME;
_ABI_SYSCALL_MUX_CHK(socket)
_ABI_SYSCALL_MUX_CHK(bind)
_ABI_SYSCALL_MUX_CHK(connect)
_ABI_SYSCALL_MUX_CHK(listen)
_ABI_SYSCALL_MUX_CHK(accept)
_ABI_SYSCALL_MUX_CHK(getsockname)
_ABI_SYSCALL_MUX_CHK(getpeername)
_ABI_SYSCALL_MUX_CHK(socketpair)
_ABI_SYSCALL_MUX_CHK(send)
_ABI_SYSCALL_MUX_CHK(recv)
_ABI_SYSCALL_MUX_CHK(sendto)
_ABI_SYSCALL_MUX_CHK(recvfrom)
_ABI_SYSCALL_MUX_CHK(shutdown)
_ABI_SYSCALL_MUX_CHK(setsockopt)
_ABI_SYSCALL_MUX_CHK(getsockopt)
_ABI_SYSCALL_MUX_CHK(sendmsg)
_ABI_SYSCALL_MUX_CHK(recvmsg)
_ABI_SYSCALL_MUX_CHK(accept4)
_ABI_SYSCALL_MUX_CHK(recvmmsg)
_ABI_SYSCALL_MUX_CHK(sendmmsg)
_ABI_SYSCALL_MUX_CHK(semop)
_ABI_SYSCALL_MUX_CHK(semget)
_ABI_SYSCALL_MUX_CHK(semctl)
_ABI_SYSCALL_MUX_CHK(semtimedop)
_ABI_SYSCALL_MUX_CHK(msgsnd)
_ABI_SYSCALL_MUX_CHK(msgrcv)
_ABI_SYSCALL_MUX_CHK(msgget)
_ABI_SYSCALL_MUX_CHK(msgctl)
_ABI_SYSCALL_MUX_CHK(shmat)
_ABI_SYSCALL_MUX_CHK(shmdt)
_ABI_SYSCALL_MUX_CHK(shmget)
_ABI_SYSCALL_MUX_CHK(shmctl)
return __NR_SCMP_ERROR;
}
/**
* Rewrite a syscall value to match the architecture
* @param arch the arch definition
* @param syscall the syscall number
*
* Syscalls can vary across different architectures so this function rewrites
* the syscall into the correct value for the specified architecture. Returns
* zero on success, negative values on failure.
*
*/
int abi_syscall_rewrite(const struct arch_def *arch, int *syscall)
{
int sys = *syscall;
if (sys <= -100 && sys >= -120)
*syscall = arch->sys_socketcall;
else if (sys <= -200 && sys >= -224)
*syscall = arch->sys_ipc;
else if (sys < 0)
return -EDOM;
return 0;
}
/**
* add a new rule to the abi seccomp filter
* @param db the seccomp filter db
* @param rule the filter rule
*
* This function adds a new syscall filter to the seccomp filter db, making any
* necessary adjustments for the abi ABI. Returns zero on success, negative
* values on failure.
*
* It is important to note that in the case of failure the db may be corrupted,
* the caller must use the transaction mechanism if the db integrity is
* important.
*
*/
int abi_rule_add(struct db_filter *db, struct db_api_rule_list *rule)
{
int rc = 0;
unsigned int iter;
int sys = rule->syscall;
int sys_a, sys_b;
struct db_api_rule_list *rule_a, *rule_b, *rule_dup = NULL;
if (_abi_syscall_socket_test(db->arch, sys)) {
/* socket syscalls */
/* strict check for the multiplexed socket syscalls */
for (iter = 0; iter < ARG_COUNT_MAX; iter++) {
if ((rule->args[iter].valid != 0) && (rule->strict)) {
rc = -EINVAL;
goto add_return;
}
}
/* determine both the muxed and direct syscall numbers */
if (sys > 0) {
sys_a = _abi_syscall_mux(db->arch, sys);
if (sys_a == __NR_SCMP_ERROR) {
rc = __NR_SCMP_ERROR;
goto add_return;
}
sys_b = sys;
} else {
sys_a = sys;
sys_b = _abi_syscall_demux(db->arch, sys);
if (sys_b == __NR_SCMP_ERROR) {
rc = __NR_SCMP_ERROR;
goto add_return;
}
}
/* use rule_a for the multiplexed syscall and use rule_b for
* the direct wired syscall */
if (sys_a == __NR_SCMP_UNDEF) {
rule_a = NULL;
rule_b = rule;
} else if (sys_b == __NR_SCMP_UNDEF) {
rule_a = rule;
rule_b = NULL;
} else {
/* need two rules, dup the first and link together */
rule_a = rule;
rule_dup = db_rule_dup(rule_a);
rule_b = rule_dup;
if (rule_b == NULL)
goto add_return;
rule_b->prev = rule_a;
rule_b->next = NULL;
rule_a->next = rule_b;
}
/* multiplexed socket syscalls */
if (rule_a != NULL) {
rule_a->syscall = db->arch->sys_socketcall;
rule_a->args[0].arg = 0;
rule_a->args[0].op = SCMP_CMP_EQ;
rule_a->args[0].mask = DATUM_MAX;
rule_a->args[0].datum = (-sys_a) % 100;
rule_a->args[0].valid = 1;
}
/* direct wired socket syscalls */
if (rule_b != NULL)
rule_b->syscall = sys_b;
/* we should be protected by a transaction checkpoint */
if (rule_a != NULL) {
rc = db_rule_add(db, rule_a);
if (rc < 0)
goto add_return;
}
if (rule_b != NULL) {
rc = db_rule_add(db, rule_b);
if (rc < 0)
goto add_return;
}
} else if (_abi_syscall_ipc_test(db->arch, sys)) {
/* ipc syscalls */
/* strict check for the multiplexed socket syscalls */
for (iter = 0; iter < ARG_COUNT_MAX; iter++) {
if ((rule->args[iter].valid != 0) && (rule->strict)) {
rc = -EINVAL;
goto add_return;
}
}
/* determine both the muxed and direct syscall numbers */
if (sys > 0) {
sys_a = _abi_syscall_mux(db->arch, sys);
if (sys_a == __NR_SCMP_ERROR) {
rc = __NR_SCMP_ERROR;
goto add_return;
}
sys_b = sys;
} else {
sys_a = sys;
sys_b = _abi_syscall_demux(db->arch, sys);
if (sys_b == __NR_SCMP_ERROR) {
rc = __NR_SCMP_ERROR;
goto add_return;
}
}
/* use rule_a for the multiplexed syscall and use rule_b for
* the direct wired syscall */
if (sys_a == __NR_SCMP_UNDEF) {
rule_a = NULL;
rule_b = rule;
} else if (sys_b == __NR_SCMP_UNDEF) {
rule_a = rule;
rule_b = NULL;
} else {
/* need two rules, dup the first and link together */
rule_a = rule;
rule_dup = db_rule_dup(rule_a);
rule_b = rule_dup;
if (rule_b == NULL)
goto add_return;
rule_b->prev = rule_a;
rule_b->next = NULL;
rule_a->next = rule_b;
}
/* multiplexed socket syscalls */
if (rule_a != NULL) {
rule_a->syscall = db->arch->sys_ipc;
rule_a->args[0].arg = 0;
rule_a->args[0].op = SCMP_CMP_EQ;
rule_a->args[0].mask = DATUM_MAX;
rule_a->args[0].datum = (-sys_a) % 200;
rule_a->args[0].valid = 1;
}
/* direct wired socket syscalls */
if (rule_b != NULL)
rule_b->syscall = sys_b;
/* we should be protected by a transaction checkpoint */
if (rule_a != NULL) {
rc = db_rule_add(db, rule_a);
if (rc < 0)
goto add_return;
}
if (rule_b != NULL) {
rc = db_rule_add(db, rule_b);
if (rc < 0)
goto add_return;
}
} else if (sys >= 0) {
/* normal syscall processing */
rc = db_rule_add(db, rule);
if (rc < 0)
goto add_return;
} else if (rule->strict) {
rc = -EDOM;
goto add_return;
}
add_return:
if (rule_dup != NULL)
free(rule_dup);
return rc;
}