kore

An easy to use, scalable and secure web application framework for writing web APIs in C.
Commits | Files | Refs | README | LICENSE | git clone https://git.kore.io/kore.git

commit 8bbdaedf947a1ae18d1a3ce29ddace6cf20f17eb
parent bcf03557040282bfcfac1de37b6398944ba9d4c9
Author: Joris Vink <joris@coders.se>
Date:   Fri,  4 Oct 2019 10:59:48 +0200

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.

Diffstat:
include/kore/python_api.h | 5+++++
include/kore/python_methods.h | 44++++++++++++++++++++++++++++++++++++++++++++
include/kore/seccomp.h | 9+++++++++
misc/linux-platform.sh | 15+++++++++++++++
src/python.c | 236++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
src/seccomp.c | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
6 files changed, 405 insertions(+), 11 deletions(-)

diff --git a/include/kore/python_api.h b/include/kore/python_api.h @@ -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 diff --git a/include/kore/python_methods.h b/include/kore/python_methods.h @@ -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; diff --git a/include/kore/seccomp.h b/include/kore/seccomp.h @@ -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 diff --git a/misc/linux-platform.sh b/misc/linux-platform.sh @@ -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 diff --git a/src/python.c b/src/python.c @@ -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 diff --git a/src/seccomp.c b/src/seccomp.c @@ -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); +}