summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xpython/interpreter.py59
-rw-r--r--server/handlers.py24
-rw-r--r--server/python_session.py15
-rw-r--r--web/main.js10
4 files changed, 97 insertions, 11 deletions
diff --git a/python/interpreter.py b/python/interpreter.py
index 87de3aa..3439ae8 100755
--- a/python/interpreter.py
+++ b/python/interpreter.py
@@ -7,9 +7,13 @@ 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, "brk")
+f.add_rule(seccomp.ALLOW, "rt_sigreturn")
# Mostly harmless.
f.add_rule(seccomp.ALLOW, "mprotect")
@@ -19,22 +23,59 @@ 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()))
-f.add_rule(seccomp.ALLOW, "ioctl")
-f.add_rule(seccomp.ALLOW, "mmap")
-f.add_rule(seccomp.ALLOW, "munmap")
-
# Needed for finding source code for exceptions.
f.add_rule(seccomp.ALLOW, "stat")
+# Read-only open.
f.add_rule(seccomp.ALLOW, "open", seccomp.Arg(1, seccomp.MASKED_EQ, 0x3, 0))
-f.add_rule(seccomp.ALLOW, "fcntl")
+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, "read")
-f.add_rule(seccomp.ALLOW, "close")
+f.add_rule(seccomp.ALLOW, "fcntl")
# Needed for code.InteractiveConsole.
f.add_rule(seccomp.ALLOW, "access")
f.add_rule(seccomp.ALLOW, "select")
f.load()
-code.interact(banner='')
+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('^C\n')
+ except EOFError:
+ break
+
+ def runcode(self, code):
+ try:
+ exec(code, self.locals)
+ except KeyboardInterrupt:
+ # Don't show traceback on SIGINT.
+ raise
+ except:
+ self.showtraceback()
+
+MyConsole().interact()
diff --git a/server/handlers.py b/server/handlers.py
index 52a33f5..08994a0 100644
--- a/server/handlers.py
+++ b/server/handlers.py
@@ -106,7 +106,27 @@ class Query(CodeqService):
request.reply(result)
-# Push stdin to the session's Python interpreter. TODO: convert to async handling
+# Push user program to the Python interpreter to be exec'd.
+class PythonExec(CodeqService):
+ def process(self, request):
+ program = request.data.get('program')
+ if program is None:
+ request.reply({'code': 1, 'message': 'No program specified'})
+ else:
+ python = request.session.get_python()
+ python.exec(program)
+ request.reply({'code': 0, 'message': 'ok'})
+
+
+# Send an interrupt to the Python interpreter.
+class PythonStop(CodeqService):
+ def process(self, request):
+ python = request.session.get_python()
+ python.stop()
+ request.reply({'code': 0, 'message': 'ok'})
+
+
+# Push stdin to the Python interpreter.
class PythonPush(CodeqService):
def process(self, request):
text = request.data.get('text')
@@ -194,7 +214,9 @@ incoming_handlers = {
'logout': None,
'activity': Activity(),
'query': Query(),
+ 'python_exec': PythonExec(),
'python_push': PythonPush(),
+ 'python_stop': PythonStop(),
'hint': Hint(),
'test': Test()
}
diff --git a/server/python_session.py b/server/python_session.py
index 62fcbf8..6f2c999 100644
--- a/server/python_session.py
+++ b/server/python_session.py
@@ -6,6 +6,7 @@ import io
import multiprocessing
import os
import queue
+import signal
import subprocess
import sys
import threading
@@ -55,9 +56,15 @@ class PythonSession(object):
p.terminate()
return results
+ def exec(self, program):
+ self._control.put_nowait(('exec', program))
+
def push(self, stdin):
self._control.put_nowait(('push', stdin))
+ def stop(self):
+ self._control.put_nowait(('stop', None))
+
def destroy(self):
self._control.put_nowait(('done', None))
@@ -125,9 +132,15 @@ def _interpreter(control, callback):
# Get a control command.
try:
cmd, data = control.get_nowait()
- if cmd == 'push':
+ if cmd == 'exec':
+ exec_str = 'exec("""\\\n{}\n""")\n'.format(data.replace('"', '\\"'))
+ proc.stdin.write(exec_str.encode('utf-8'))
+ proc.stdin.flush()
+ elif cmd == 'push':
proc.stdin.write(data.encode('utf-8'))
proc.stdin.flush()
+ elif cmd == 'stop':
+ proc.send_signal(signal.SIGINT)
elif cmd == 'done':
break
except:
diff --git a/web/main.js b/web/main.js
index 03e9dad..fb393c8 100644
--- a/web/main.js
+++ b/web/main.js
@@ -132,11 +132,21 @@ var guiHandlers = {
sendDataToPython(message).then(session.send, session.end).done();
},
+ 'python_exec': function actionPythonExec(session, message) {
+ logger.debug('Received python_exec from GUI');
+ sendDataToPython(message).then(session.send, session.end).done();
+ },
+
'python_push': function actionPythonPush(session, message) {
logger.debug('Received python_push from GUI');
sendDataToPython(message).then(session.send, session.end).done();
},
+ 'python_stop': function actionPythonStop(session, message) {
+ logger.debug('Received python_stop from GUI');
+ sendDataToPython(message).then(session.send, session.end).done();
+ },
+
'hint': function actionHint(session, message) {
logger.debug('Received hint from GUI');
sendDataToPython(message).then(session.send, session.end).done();