From 520420556ad9f2871cd8c5910645193508cb4082 Mon Sep 17 00:00:00 2001 From: Timotej Lazar Date: Wed, 14 Oct 2015 17:57:09 +0200 Subject: Use sandbox for testing Python programs Use interpreter.py for running tests as well as interactive sessions. Signals are now sent with "sandbox kill", so terminator is not needed anymore. --- python/runner/interpreter.py | 121 +++++++++++++++++++++++++++++-------------- python/runner/terminator.c | 31 ----------- python/util.py | 2 + 3 files changed, 83 insertions(+), 71 deletions(-) delete mode 100644 python/runner/terminator.c (limited to 'python') diff --git a/python/runner/interpreter.py b/python/runner/interpreter.py index da60d72..722bc33 100755 --- a/python/runner/interpreter.py +++ b/python/runner/interpreter.py @@ -1,6 +1,8 @@ #!/usr/bin/python3 -u import code +import io +import pickle import sys import seccomp @@ -43,49 +45,88 @@ 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)) +if len(sys.argv) > 1 and sys.argv[1] == 'run': + # Receive a pickled tuple (code, expr, stdin) on standard input, run it, + # and print a pickled tuple (result, out, err, exc) on standard output. + data = sys.stdin.buffer.read() + code, expr, stdin = pickle.loads(data) - 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 = '' + result, out, err, exc = None, None, None, None + try: + sys.stdin = io.StringIO(stdin) + sys.stdout = io.StringIO() + sys.stderr = io.StringIO() + env = {} + if code: + exec(code, env) + if expr: + result = eval(expr, env) + except Exception as ex: + # Exception is not JSON serializable, so return traceback as string + # (without the first entry, which is this function). + import traceback + e_type, e_value, e_tb = sys.exc_info() + stack = traceback.extract_tb(e_tb) + exc = ''.join( + ['Traceback (most recent call last):\n'] + + [' line {}, in {}\n'.format(lineno, name) + (line+'\n' if line else '') + for filename, lineno, name, line in stack[1:]] + + traceback.format_exception_only(e_type, e_value) + ).rstrip() + finally: + out = sys.stdout.getvalue() + err = sys.stderr.getvalue() + sys.stdin.close() + sys.stdout.close() + sys.stderr.close() + outdata = pickle.dumps((result, out, err, exc)) + sys.__stdout__.buffer.write(outdata) + +else: + # Run an interactive console. + 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: + buffer.append(line) + source = '\n'.join(buffer) + more = self.runsource(source) + if more: + if prompt: + prompt = '... ' + else: + prompt = '>>> ' + buffer = [] + except KeyboardInterrupt: prompt = '>>> ' buffer = [] - except KeyboardInterrupt: - prompt = '>>> ' - buffer = [] - self.write('\n') - except ValueError: - break - except EOFError: - break + self.write('\n') + except ValueError: + break + except EOFError: + break - def runcode(self, code): - try: - exec(code, self.locals) - except KeyboardInterrupt: - self.write('^C') - raise - except SystemExit as ex: - raise - except: - # Show traceback for all other exceptions. - self.showtraceback() + def runcode(self, code): + try: + exec(code, self.locals) + except KeyboardInterrupt: + self.write('^C') + raise + except SystemExit as ex: + raise + except: + # Show traceback for all other exceptions. + self.showtraceback() -MyConsole().interact() + MyConsole().interact() diff --git a/python/runner/terminator.c b/python/runner/terminator.c deleted file mode 100644 index 9eaca83..0000000 --- a/python/runner/terminator.c +++ /dev/null @@ -1,31 +0,0 @@ -#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 = setgid(pw->pw_gid)) != 0) - fprintf(stderr, "setgid returned %d\n", ret); - if ((ret = setuid(pw->pw_uid)) != 0) - fprintf(stderr, "setuid returned %d\n", ret); - - pid_t pid = atol(argv[2]); - int signum = atoi(argv[3]); - kill(pid, signum); - return 0; -} diff --git a/python/util.py b/python/util.py index ce89558..8c1a0c7 100644 --- a/python/util.py +++ b/python/util.py @@ -59,6 +59,8 @@ def get_exception_desc(exc): return [{'id':'eof_error'}] if 'timed out' in exc: return [{'id':'timed_out'}] + if 'sandbox violation' in exc: + return [{'id': 'sandbox_violation'}] if 'NameError' in exc: return [{'id':'name_error', 'args': {'message': exc}}] elif 'TypeError' in exc: -- cgit v1.2.1