summaryrefslogtreecommitdiff
path: root/python/runner
diff options
context:
space:
mode:
authorTimotej Lazar <timotej.lazar@araneo.org>2015-10-07 17:25:35 +0200
committerTimotej Lazar <timotej.lazar@araneo.org>2015-10-07 17:25:35 +0200
commit76cbfe9d620ca66a374b828c011c937918f80c2c (patch)
tree8ee165821df9ab46fda38869d1ae856be4efd2c7 /python/runner
parentb7b4979f03f4d06919e251cfcc24642ccf9407ad (diff)
Add a sandbox for Python interpreter
Switch to user "nobody" and set additional limits.
Diffstat (limited to 'python/runner')
-rwxr-xr-xpython/runner/interpreter.py87
-rw-r--r--python/runner/sandbox.c47
-rw-r--r--python/runner/terminator.c32
3 files changed, 166 insertions, 0 deletions
diff --git a/python/runner/interpreter.py b/python/runner/interpreter.py
new file mode 100755
index 0000000..dae4d59
--- /dev/null
+++ b/python/runner/interpreter.py
@@ -0,0 +1,87 @@
+#!/usr/bin/python3
+
+import code
+import sys
+
+import seccomp
+
+f = seccomp.SyscallFilter(defaction=seccomp.KILL)
+# Necessary for Python.
+f.add_rule(seccomp.ALLOW, "brk")
+f.add_rule(seccomp.ALLOW, "exit_group")
+f.add_rule(seccomp.ALLOW, "ioctl")
+f.add_rule(seccomp.ALLOW, "mmap")
+f.add_rule(seccomp.ALLOW, "munmap")
+f.add_rule(seccomp.ALLOW, "rt_sigaction")
+f.add_rule(seccomp.ALLOW, "rt_sigreturn")
+
+# Mostly harmless.
+f.add_rule(seccomp.ALLOW, "mprotect")
+
+# Allow reading from stdin and writing to stdout/stderr.
+f.add_rule(seccomp.ALLOW, "read", seccomp.Arg(0, seccomp.EQ, sys.stdin.fileno()))
+f.add_rule(seccomp.ALLOW, "write", seccomp.Arg(0, seccomp.EQ, sys.stdout.fileno()))
+f.add_rule(seccomp.ALLOW, "write", seccomp.Arg(0, seccomp.EQ, sys.stderr.fileno()))
+
+# Needed for finding source code for exceptions.
+f.add_rule(seccomp.ALLOW, "stat")
+f.add_rule(seccomp.ALLOW, "open", seccomp.Arg(1, seccomp.MASKED_EQ, 0x3, 0)) # O_RDONLY
+f.add_rule(seccomp.ALLOW, "close")
+f.add_rule(seccomp.ALLOW, "read")
+f.add_rule(seccomp.ALLOW, "fstat")
+f.add_rule(seccomp.ALLOW, "lseek")
+f.add_rule(seccomp.ALLOW, "fcntl")
+
+# Needed for help().
+f.add_rule(seccomp.ALLOW, "openat", seccomp.Arg(2, seccomp.MASKED_EQ, 0x3, 0)) # O_RDONLY
+f.add_rule(seccomp.ALLOW, "getdents")
+f.add_rule(seccomp.ALLOW, "getrlimit", seccomp.Arg(0, seccomp.EQ, 3)) # RLIMIT_STACK
+f.add_rule(seccomp.ALLOW, "getrlimit", seccomp.Arg(0, seccomp.EQ, 7)) # RLIMIT_NOFILE
+
+# Needed for code.InteractiveConsole.
+f.add_rule(seccomp.ALLOW, "access")
+f.add_rule(seccomp.ALLOW, "select")
+f.load()
+
+class MyConsole(code.InteractiveConsole):
+ def interact(self, banner=None):
+ if banner is not None:
+ self.write('{}\n'.format(banner))
+
+ buffer = []
+ prompt = '>>> '
+ while True:
+ try:
+ line = input(prompt)
+ # Assume we are running the user's program; silence the prompt.
+ if line == 'exec("""\\':
+ self.write('<run>\n')
+ prompt = ''
+
+ buffer.append(line)
+ source = '\n'.join(buffer)
+ more = self.runsource(source)
+ if more:
+ if prompt:
+ prompt = '... '
+ else:
+ prompt = '>>> '
+ buffer = []
+ except KeyboardInterrupt:
+ prompt = '>>> '
+ buffer = []
+ self.write('\n')
+ except EOFError:
+ break
+
+ def runcode(self, code):
+ try:
+ exec(code, self.locals)
+ except KeyboardInterrupt:
+ # Don't show traceback on SIGINT.
+ self.write('^C')
+ raise
+ except:
+ self.showtraceback()
+
+MyConsole().interact()
diff --git a/python/runner/sandbox.c b/python/runner/sandbox.c
new file mode 100644
index 0000000..12e2720
--- /dev/null
+++ b/python/runner/sandbox.c
@@ -0,0 +1,47 @@
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <sys/prctl.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+int main(int argc, char* argv[])
+{
+ if (argc < 3) {
+ fprintf(stderr, "usage: %s USERNAME FILE\n", argv[0]);
+ return 1;
+ }
+
+ // switch user (requires root or "setcap cap_setuid,cap_setgid+ep")
+ char const* username = argv[1];
+ struct passwd const* pw = getpwnam(username);
+ if (!pw) {
+ fprintf(stderr, "no such user: %s\n", username);
+ return 1;
+ }
+ int ret = 0;
+ if ((ret = setuid(pw->pw_uid)) != 0)
+ fprintf(stderr, "setuid returned %d\n", ret);
+ if ((ret = setgid(pw->pw_gid)) != 0)
+ fprintf(stderr, "setgid returned %d\n", ret);
+
+ // limit CPU time to 1 second
+ struct rlimit const cpu_limit = { .rlim_cur = 1, .rlim_max = 1 };
+ if ((ret = setrlimit(RLIMIT_CPU, &cpu_limit)) != 0)
+ fprintf(stderr, "setrlimit(CPU) returned %d\n", ret);
+
+ // don't allow writing files of any size
+ struct rlimit const fsize_limit = { .rlim_cur = 0, .rlim_max = 0 };
+ if ((ret = setrlimit(RLIMIT_FSIZE, &fsize_limit)) != 0)
+ fprintf(stderr, "setrlimit(FSIZE) returned %d\n", ret);
+
+ // there will be no fork
+ struct rlimit const nproc_limit = { .rlim_cur = 0, .rlim_max = 0 };
+ if ((ret = setrlimit(RLIMIT_NPROC, &nproc_limit)) != 0)
+ fprintf(stderr, "setrlimit(NPROC) returned %d\n", ret);
+
+ char* const args[] = { argv[2], (char*)0 };
+ return execvp(argv[2], args);
+}
diff --git a/python/runner/terminator.c b/python/runner/terminator.c
new file mode 100644
index 0000000..a994bde
--- /dev/null
+++ b/python/runner/terminator.c
@@ -0,0 +1,32 @@
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+int main(int argc, char* argv[])
+{
+ if (argc < 4) {
+ fprintf(stderr, "usage: %s USERNAME PID SIGNAL\n", argv[0]);
+ return 1;
+ }
+
+ // switch user (requires root or "setcap cap_setuid,cap_setgid+ep")
+ char const* username = argv[1];
+ struct passwd const* pw = getpwnam(username);
+ if (!pw) {
+ fprintf(stderr, "no such user: %s\n", username);
+ return 1;
+ }
+ int ret = 0;
+ if ((ret = setuid(pw->pw_uid)) != 0)
+ fprintf(stderr, "setuid returned %d\n", ret);
+ if ((ret = setgid(pw->pw_gid)) != 0)
+ fprintf(stderr, "setgid returned %d\n", ret);
+
+ pid_t pid = atol(argv[2]);
+ int signum = atoi(argv[3]);
+ kill(pid, signum);
+ return 0;
+}