summaryrefslogtreecommitdiff
path: root/python/runner
diff options
context:
space:
mode:
authorTimotej Lazar <timotej.lazar@fri.uni-lj.si>2015-10-14 17:57:09 +0200
committerTimotej Lazar <timotej.lazar@fri.uni-lj.si>2015-10-14 17:57:09 +0200
commit520420556ad9f2871cd8c5910645193508cb4082 (patch)
tree08efd34b6c9d3cc071b6c5345b213ec9f7f59dd5 /python/runner
parent7ae2d8824ab59dfbda6eaf7f621b6d3bfdec56e7 (diff)
Use sandbox for testing Python programs
Use interpreter.py for running tests as well as interactive sessions. Signals are now sent with "sandbox <user> kill", so terminator is not needed anymore.
Diffstat (limited to 'python/runner')
-rwxr-xr-xpython/runner/interpreter.py121
-rw-r--r--python/runner/terminator.c31
2 files changed, 81 insertions, 71 deletions
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('<run>\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('<run>\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 <pwd.h>
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.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 = 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;
-}