diff options
Diffstat (limited to 'snekbox.py')
-rw-r--r-- | snekbox.py | 140 |
1 files changed, 0 insertions, 140 deletions
diff --git a/snekbox.py b/snekbox.py deleted file mode 100644 index 7491672..0000000 --- a/snekbox.py +++ /dev/null @@ -1,140 +0,0 @@ -import json -import multiprocessing -import os -import subprocess -import sys - -from rmq import Rmq - - -class Snekbox: - """Core snekbox functionality, providing safe execution of Python code.""" - - def __init__(self, - nsjail_binary='nsjail', - python_binary=os.path.dirname(sys.executable) + os.sep + 'python3.6'): - self.nsjail_binary = nsjail_binary - self.python_binary = python_binary - self._nsjail_workaround() - - env = { - 'PATH': ( - '/snekbox/.venv/bin:/usr/local/bin:/usr/local/' - 'sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' - ), - 'LANG': 'en_US.UTF-8', - 'PYTHON_VERSION': '3.6.5', - 'PYTHON_PIP_VERSION': '10.0.1', - 'PYTHONDONTWRITEBYTECODE': '1', - } - - def _nsjail_workaround(self): - dirs = ['/sys/fs/cgroup/pids/NSJAIL', '/sys/fs/cgroup/memory/NSJAIL'] - for d in dirs: - if not os.path.exists(d): - os.makedirs(d) - - def python3(self, cmd): - """ - Execute Python 3 code in a isolated environment. - - The value of ``cmd`` is passed using '-c' to a Python - interpreter that is started in a ``nsjail``, isolating it - from the rest of the system. - - Returns the output of executing the command (stdout) if - successful, or a error message if the execution failed. - """ - args = [self.nsjail_binary, '-Mo', - '--rlimit_as', '700', - '--chroot', '/', - '-E', 'LANG=en_US.UTF-8', - '-R/usr', '-R/lib', '-R/lib64', - '--user', 'nobody', - '--group', 'nogroup', - '--time_limit', '2', - '--disable_proc', - '--iface_no_lo', - '--cgroup_pids_max=1', - '--cgroup_mem_max=52428800', - '--quiet', '--', - self.python_binary, '-ISq', '-c', cmd] - try: - proc = subprocess.Popen(args, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - env=self.env, - universal_newlines=True) - except ValueError: - return 'ValueError: embedded null byte' - - stdout, stderr = proc.communicate() - if proc.returncode == 0: - output = stdout - - elif proc.returncode == 1: - try: - filtered = [] - for line in stderr.split('\n'): - if not line.startswith('['): - filtered.append(line) - output = '\n'.join(filtered) - except IndexError: - output = '' - - elif proc.returncode == 109: - return 'timed out or memory limit exceeded' - - elif proc.returncode == 255: - return 'permission denied (root required)' - - elif proc.returncode: - return f'unknown error, code: {proc.returncode}' - - else: - return 'unknown error, no error code' - - return output - - def execute(self, body): - """ - Handles execution of a raw JSON-formatted RMQ message, contained in ``body``. - - The message metadata, including the Python code to be executed, is - extracted from the message body. The code is then executed in the - isolated environment, and the results of the execution published - to RMQ. Once published, the system exits, since the snekboxes - are created and disposed of per-execution. - """ - msg = body.decode('utf-8') - result = '' - snek_msg = json.loads(msg) - snekid = snek_msg['snekid'] - snekcode = snek_msg['message'].strip() - - result = self.python3(snekcode) - - rmq.publish(result, - queue=snekid, - routingkey=snekid, - exchange=snekid) - exit(0) - - def message_handler(self, ch, method, properties, body, thread_ws=None): - """Spawns a daemon process that handles RMQ messages.""" - p = multiprocessing.Process(target=self.execute, args=(body,)) - p.daemon = True - p.start() - - ch.basic_ack(delivery_tag=method.delivery_tag) - - -if __name__ == '__main__': - try: - rmq = Rmq() - snkbx = Snekbox() - rmq.consume(callback=snkbx.message_handler) - except KeyboardInterrupt: - print('Exited') - exit(0) |