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:
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);
+}