From 76cbfe9d620ca66a374b828c011c937918f80c2c Mon Sep 17 00:00:00 2001 From: Timotej Lazar Date: Wed, 7 Oct 2015 17:25:35 +0200 Subject: Add a sandbox for Python interpreter Switch to user "nobody" and set additional limits. --- python/interpreter.py | 87 -------------------------------------------- python/runner/interpreter.py | 87 ++++++++++++++++++++++++++++++++++++++++++++ python/runner/sandbox.c | 47 ++++++++++++++++++++++++ python/runner/terminator.c | 32 ++++++++++++++++ 4 files changed, 166 insertions(+), 87 deletions(-) delete mode 100755 python/interpreter.py create mode 100755 python/runner/interpreter.py create mode 100644 python/runner/sandbox.c create mode 100644 python/runner/terminator.c (limited to 'python') diff --git a/python/interpreter.py b/python/interpreter.py deleted file mode 100755 index dae4d59..0000000 --- a/python/interpreter.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/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('\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/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('\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 +#include +#include +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#include +#include + +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; +} -- cgit v1.2.1