Allow configuring seccomp on Linux via the python api.

A new hook in the koreapp class is called right before seccomp
is enabled. This hook receives a Kore seccomp object which has
the following methods:

	seccomp.allow("syscall")
	seccomp.allow_arg("syscall", arg, value)
	seccomp.allow_flag("syscall", arg, flag)
	seccomp.allow_mask("syscall", arg, mask)

	seccomp.deny("syscall")
	seccomp.deny_arg("syscall", arg, value, errno=EACCES)
	seccomp.deny_flag("syscall", arg, flag, errno=EACCES)
	seccomp.deny_mask("syscall", arg, mask, errno=EACCES)

This allows you to finetune the seccomp filters for your application
from inside your koreapp.
This commit is contained in:
Joris Vink 2019-10-04 10:59:48 +02:00
parent bcf0355704
commit 8bbdaedf94
6 changed files with 405 additions and 11 deletions

View File

@ -35,6 +35,11 @@ void kore_python_log_error(const char *);
PyObject *kore_python_callable(PyObject *, const char *);
#if defined(__linux__)
void kore_python_seccomp_hook(void);
void kore_python_seccomp_cleanup(void);
#endif
#if !defined(KORE_SINGLE_BINARY)
extern const char *kore_pymodule;
#endif

View File

@ -132,6 +132,50 @@ static PyTypeObject pyconfig_type = {
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
};
#if defined(__linux__)
struct pyseccomp {
PyObject_HEAD
size_t elm;
u_int8_t *filters;
};
static PyObject *pyseccomp_allow(struct pyseccomp *, PyObject *);
static PyObject *pyseccomp_allow_arg(struct pyseccomp *, PyObject *);
static PyObject *pyseccomp_allow_flag(struct pyseccomp *, PyObject *);
static PyObject *pyseccomp_allow_mask(struct pyseccomp *, PyObject *);
static PyObject *pyseccomp_deny(struct pyseccomp *, PyObject *, PyObject *);
static PyObject *pyseccomp_deny_arg(struct pyseccomp *, PyObject *, PyObject *);
static PyObject *pyseccomp_deny_flag(struct pyseccomp *,
PyObject *, PyObject *);
static PyObject *pyseccomp_deny_mask(struct pyseccomp *,
PyObject *, PyObject *);
static PyMethodDef pyseccomp_methods[] = {
METHOD("allow", pyseccomp_allow, METH_VARARGS),
METHOD("allow_arg", pyseccomp_allow_arg, METH_VARARGS),
METHOD("allow_flag", pyseccomp_allow_flag, METH_VARARGS),
METHOD("allow_mask", pyseccomp_allow_mask, METH_VARARGS),
METHOD("deny", pyseccomp_deny, METH_VARARGS | METH_KEYWORDS),
METHOD("deny_arg", pyseccomp_deny_arg, METH_VARARGS | METH_KEYWORDS),
METHOD("deny_flag", pyseccomp_deny_flag, METH_VARARGS | METH_KEYWORDS),
METHOD("deny_mask", pyseccomp_deny_mask, METH_VARARGS | METH_KEYWORDS),
METHOD(NULL, NULL, -1)
};
static void pyseccomp_dealloc(struct pyseccomp *);
static PyTypeObject pyseccomp_type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "kore.seccomp",
.tp_doc = "kore seccomp configuration",
.tp_methods = pyseccomp_methods,
.tp_basicsize = sizeof(struct pyseccomp),
.tp_dealloc = (destructor)pyseccomp_dealloc,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
};
#endif
struct pydomain {
PyObject_HEAD
struct kore_domain *config;

View File

@ -134,6 +134,9 @@
/* The length of a filter. */
#define KORE_FILTER_LEN(x) (sizeof(x) / sizeof(x[0]))
/* Used to mark the end of a BPF program. */
#define KORE_BPF_GUARD { USHRT_MAX, UCHAR_MAX, UCHAR_MAX, UINT_MAX }
/*
* Macro for applications to make easily define custom filter.
*
@ -158,6 +161,12 @@
void kore_seccomp_init(void);
void kore_seccomp_drop(void);
void kore_seccomp_enable(void);
int kore_seccomp_syscall_resolve(const char *);
int kore_seccomp_filter(const char *, void *, size_t);
struct sock_filter *kore_seccomp_syscall_filter(const char *, int);
struct sock_filter *kore_seccomp_syscall_arg(const char *, int, int, int);
struct sock_filter *kore_seccomp_syscall_flag(const char *, int, int, int);
struct sock_filter *kore_seccomp_syscall_mask(const char *, int, int, int);
#endif

View File

@ -19,5 +19,20 @@ esac
cat << __EOF
/* Auto generated by linux-platform.sh - DO NOT EDIT */
#include <sys/syscall.h>
#define SECCOMP_AUDIT_ARCH $seccomp_audit_arch
struct {
const char *name;
int nr;
} kore_syscall_map [] = {
__EOF
awk 'BEGIN { print "#include <sys/syscall.h>" } /p_syscall_meta/ { syscall = substr($NF, 19); printf "#if defined(SYS_%s)\n { \"%s\", SYS_%s },\n#endif\n", syscall, syscall, syscall }' /proc/kallsyms | gcc -E -P -
cat << __EOF
{ NULL, 0 }
};
__EOF

View File

@ -214,14 +214,27 @@ static struct sock_filter filter_python[] = {
KORE_SYSCALL_ALLOW(listen),
KORE_SYSCALL_ALLOW(sendto),
KORE_SYSCALL_ALLOW(recvfrom),
KORE_SYSCALL_ALLOW(getsockopt),
KORE_SYSCALL_ALLOW(setsockopt),
KORE_SYSCALL_ALLOW(getsockname),
KORE_SYSCALL_ALLOW(getpeername),
KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_INET),
KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_INET6),
KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_UNIX),
};
#define PYSECCOMP_ACTION_ALLOW 1
#define PYSECCOMP_ACTION_DENY 2
#define PYSECCOMP_SYSCALL_FILTER 1
#define PYSECCOMP_SYSCALL_ARG 2
#define PYSECCOMP_SYSCALL_MASK 3
#define PYSECCOMP_SYSCALL_FLAG 4
static int pyseccomp_filter_install(struct pyseccomp *,
const char *, int, int, int, int);
static PyObject *pyseccomp_common_action(struct pyseccomp *, PyObject *,
PyObject *, int, int);
static struct pyseccomp *py_seccomp = NULL;
#endif
static TAILQ_HEAD(, pyproc) procs;
@ -471,6 +484,219 @@ kore_python_proc_reap(void)
}
}
#if defined(__linux__)
void
kore_python_seccomp_hook(void)
{
struct kore_runtime *rt;
PyObject *func, *result;
if ((func = kore_module_getsym("koreapp.seccomp", &rt)) == NULL)
return;
if (rt->type != KORE_RUNTIME_PYTHON)
return;
py_seccomp = PyObject_New(struct pyseccomp, &pyseccomp_type);
if (py_seccomp == NULL)
fatal("failed to create seccomp object");
py_seccomp->elm = 0;
py_seccomp->filters = NULL;
result = PyObject_CallFunctionObjArgs(func,
(PyObject *)py_seccomp, NULL);
kore_python_log_error("koreapp.seccomp");
kore_seccomp_filter("koreapp", py_seccomp->filters, py_seccomp->elm);
Py_XDECREF(result);
}
void
kore_python_seccomp_cleanup(void)
{
Py_XDECREF(py_seccomp);
py_seccomp = NULL;
}
static void
pyseccomp_dealloc(struct pyseccomp *seccomp)
{
kore_free(seccomp->filters);
seccomp->elm = 0;
seccomp->filters = NULL;
}
static PyObject *
pyseccomp_allow(struct pyseccomp *seccomp, PyObject *args)
{
const char *syscall;
if (!PyArg_ParseTuple(args, "s", &syscall))
return (NULL);
if (!pyseccomp_filter_install(seccomp, syscall,
PYSECCOMP_SYSCALL_FILTER, 0, 0, SECCOMP_RET_ALLOW))
return (NULL);
Py_RETURN_NONE;
}
static PyObject *
pyseccomp_allow_arg(struct pyseccomp *seccomp, PyObject *args)
{
return (pyseccomp_common_action(seccomp, args, NULL,
PYSECCOMP_SYSCALL_ARG, PYSECCOMP_ACTION_ALLOW));
}
static PyObject *
pyseccomp_allow_flag(struct pyseccomp *seccomp, PyObject *args)
{
return (pyseccomp_common_action(seccomp, args, NULL,
PYSECCOMP_SYSCALL_FLAG, PYSECCOMP_ACTION_ALLOW));
}
static PyObject *
pyseccomp_allow_mask(struct pyseccomp *seccomp, PyObject *args)
{
return (pyseccomp_common_action(seccomp, args, NULL,
PYSECCOMP_SYSCALL_MASK, PYSECCOMP_ACTION_ALLOW));
}
static PyObject *
pyseccomp_deny(struct pyseccomp *seccomp, PyObject *args, PyObject *kwargs)
{
long err;
const char *syscall;
if (!PyArg_ParseTuple(args, "s", &syscall))
return (NULL);
err = EACCES;
if (kwargs != NULL)
python_long_from_dict(kwargs, "errno", &err);
if (!pyseccomp_filter_install(seccomp, syscall,
PYSECCOMP_SYSCALL_FILTER, 0, 0, SECCOMP_RET_ERRNO | (int)err))
return (NULL);
Py_RETURN_NONE;
}
static PyObject *
pyseccomp_deny_arg(struct pyseccomp *seccomp, PyObject *args, PyObject *kwargs)
{
return (pyseccomp_common_action(seccomp, args, kwargs,
PYSECCOMP_SYSCALL_ARG, PYSECCOMP_ACTION_DENY));
}
static PyObject *
pyseccomp_deny_flag(struct pyseccomp *seccomp, PyObject *args, PyObject *kwargs)
{
return (pyseccomp_common_action(seccomp, args, kwargs,
PYSECCOMP_SYSCALL_FLAG, PYSECCOMP_ACTION_DENY));
}
static PyObject *
pyseccomp_deny_mask(struct pyseccomp *seccomp, PyObject *args, PyObject *kwargs)
{
return (pyseccomp_common_action(seccomp, args, kwargs,
PYSECCOMP_SYSCALL_MASK, PYSECCOMP_ACTION_DENY));
}
static PyObject *
pyseccomp_common_action(struct pyseccomp *sc, PyObject *args,
PyObject *kwargs, int which, int action)
{
long err;
const char *syscall;
int arg, val;
if (!PyArg_ParseTuple(args, "sii", &syscall, &arg, &val))
return (NULL);
switch (action) {
case PYSECCOMP_ACTION_ALLOW:
action = SECCOMP_RET_ALLOW;
break;
case PYSECCOMP_ACTION_DENY:
err = EACCES;
if (kwargs != NULL)
python_long_from_dict(kwargs, "errno", &err);
action = SECCOMP_RET_ERRNO | (int)err;
break;
default:
fatal("%s: bad action %d", __func__, action);
}
if (!pyseccomp_filter_install(sc, syscall, which, arg, val, action))
return (NULL);
Py_RETURN_NONE;
}
static int
pyseccomp_filter_install(struct pyseccomp *seccomp, const char *syscall,
int which, int arg, int val, int action)
{
struct sock_filter *filter;
size_t elm, len, off;
switch (which) {
case PYSECCOMP_SYSCALL_FILTER:
filter = kore_seccomp_syscall_filter(syscall, action);
break;
case PYSECCOMP_SYSCALL_ARG:
filter = kore_seccomp_syscall_arg(syscall, action, arg, val);
break;
case PYSECCOMP_SYSCALL_MASK:
filter = kore_seccomp_syscall_mask(syscall, action, arg, val);
break;
case PYSECCOMP_SYSCALL_FLAG:
filter = kore_seccomp_syscall_flag(syscall, action, arg, val);
break;
default:
fatal("%s: invalid syscall instruction %d", __func__, which);
}
if (filter == NULL) {
PyErr_Format(PyExc_RuntimeError,
"system call '%s' does not exist", syscall);
return (KORE_RESULT_ERROR);
}
elm = 0;
/*
* Find the number of elements in the BPF program, by looking for
* the KORE_BPF_GUARD element.
*/
for (;;) {
if (filter[elm].code == USHRT_MAX &&
filter[elm].jt == UCHAR_MAX &&
filter[elm].jf == UCHAR_MAX &&
filter[elm].k == UINT_MAX)
break;
elm++;
}
len = elm * sizeof(struct sock_filter);
off = seccomp->elm * sizeof(struct sock_filter);
seccomp->filters = kore_realloc(seccomp->filters, off + len);
memcpy(seccomp->filters + off, filter, len);
seccomp->elm += elm;
kore_free(filter);
return (KORE_RESULT_OK);
}
#endif
static int
python_long_from_dict(PyObject *dict, const char *key, long *result)
{
@ -1205,6 +1431,10 @@ python_module_init(void)
python_push_type("pydomain", pykore, &pydomain_type);
python_push_type("pyconnection", pykore, &pyconnection_type);
#if defined(__linux__)
python_push_type("pyseccomp", pykore, &pyseccomp_type);
#endif
#if defined(KORE_USE_CURL)
python_push_type("pyhttpclient", pykore, &pyhttp_client_type);
#endif

View File

@ -31,6 +31,10 @@
#include "seccomp.h"
#include "platform.h"
#if defined(KORE_USE_PYTHON)
#include "python_api.h"
#endif
#if defined(KORE_DEBUG)
#define SECCOMP_KILL_POLICY SECCOMP_RET_TRAP
#else
@ -128,6 +132,9 @@ static struct sock_filter filter_epilogue[] = {
BPF_STMT(BPF_RET+BPF_K, SECCOMP_KILL_POLICY)
};
static struct sock_filter *seccomp_filter_update(struct sock_filter *,
const char *, size_t);
#define filter_prologue_len KORE_FILTER_LEN(filter_prologue)
#define filter_epilogue_len KORE_FILTER_LEN(filter_epilogue)
@ -188,9 +195,15 @@ kore_seccomp_enable(void)
sa.sa_sigaction = seccomp_trap;
if (sigfillset(&sa.sa_mask) == -1)
fatal("sigfillset: %s", errno_s);
fatalx("sigfillset: %s", errno_s);
if (sigaction(SIGSYS, &sa, NULL) == -1)
fatal("sigaction: %s", errno_s);
fatalx("sigaction: %s", errno_s);
#endif
#if defined(KORE_USE_PYTHON)
ufilter = TAILQ_FIRST(&filters);
kore_python_seccomp_hook();
ufilter = NULL;
#endif
/* Allow application to add its own filters. */
@ -219,7 +232,7 @@ kore_seccomp_enable(void)
/* Build the entire bpf program now. */
if ((sf = calloc(prog_len, sizeof(*sf))) == NULL)
fatal("calloc");
fatalx("calloc");
off = 0;
for (i = 0; i < filter_prologue_len; i++)
@ -228,11 +241,10 @@ kore_seccomp_enable(void)
TAILQ_FOREACH(filter, &filters, list) {
for (i = 0; i < filter->instructions; i++)
sf[off++] = filter->prog[i];
if (!kore_quiet) {
#if defined(KORE_DEBUG)
kore_log(LOG_INFO,
"seccomp filter '%s' added", filter->name);
}
#endif
}
for (i = 0; i < filter_epilogue_len; i++)
@ -240,16 +252,20 @@ kore_seccomp_enable(void)
/* Lock and load it. */
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1)
fatal("prctl: %s", errno_s);
fatalx("prctl: %s", errno_s);
prog.filter = sf;
prog.len = prog_len;
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1)
fatal("prctl: %s", errno_s);
fatalx("prctl: %s", errno_s);
if (!kore_quiet)
kore_log(LOG_INFO, "seccomp sandbox activated");
#if defined(KORE_USE_PYTHON)
kore_python_seccomp_cleanup();
#endif
}
int
@ -277,6 +293,63 @@ kore_seccomp_filter(const char *name, void *prog, size_t len)
return (KORE_RESULT_OK);
}
int
kore_seccomp_syscall_resolve(const char *name)
{
int i;
for (i = 0; kore_syscall_map[i].name != NULL; i++) {
if (!strcmp(name, kore_syscall_map[i].name))
return (kore_syscall_map[i].nr);
}
return (-1);
}
struct sock_filter *
kore_seccomp_syscall_filter(const char *name, int action)
{
struct sock_filter filter[] = {
KORE_SYSCALL_FILTER(exit, action),
KORE_BPF_GUARD
};
return (seccomp_filter_update(filter, name, KORE_FILTER_LEN(filter)));
}
struct sock_filter *
kore_seccomp_syscall_arg(const char *name, int action, int arg, int value)
{
struct sock_filter filter[] = {
KORE_SYSCALL_ARG(exit, arg, value, action),
KORE_BPF_GUARD
};
return (seccomp_filter_update(filter, name, KORE_FILTER_LEN(filter)));
}
struct sock_filter *
kore_seccomp_syscall_mask(const char *name, int action, int arg, int value)
{
struct sock_filter filter[] = {
KORE_SYSCALL_MASK(exit, arg, value, action),
KORE_BPF_GUARD
};
return (seccomp_filter_update(filter, name, KORE_FILTER_LEN(filter)));
}
struct sock_filter *
kore_seccomp_syscall_flag(const char *name, int action, int arg, int value)
{
struct sock_filter filter[] = {
KORE_SYSCALL_WITH_FLAG(exit, arg, value, action),
KORE_BPF_GUARD
};
return (seccomp_filter_update(filter, name, KORE_FILTER_LEN(filter)));
}
#if defined(KORE_DEBUG)
static void
seccomp_trap(int sig, siginfo_t *info, void *ucontext)
@ -284,3 +357,21 @@ seccomp_trap(int sig, siginfo_t *info, void *ucontext)
kore_log(LOG_INFO, "sandbox violation - syscall=%d", info->si_syscall);
}
#endif
static struct sock_filter *
seccomp_filter_update(struct sock_filter *filter, const char *name, size_t elm)
{
int nr;
struct sock_filter *result;
if ((nr = kore_seccomp_syscall_resolve(name)) == -1)
return (NULL);
result = kore_calloc(elm, sizeof(struct sock_filter));
memcpy(result, filter, elm * sizeof(struct sock_filter));
/* Update the syscall number to the one specified. */
result[0].k = nr;
return (result);
}