aboutsummaryrefslogtreecommitdiffstats
path: root/snekbox.py
diff options
context:
space:
mode:
authorGravatar Christopher Baklid <[email protected]>2018-05-30 19:52:07 +0200
committerGravatar GitHub <[email protected]>2018-05-30 19:52:07 +0200
commit7f756fd7f709bc74b3eb558dccb44607844fedb4 (patch)
tree5844b9023466373233094a5de2ae3c192dd9b67b /snekbox.py
parenttypo in debug output (diff)
secure python execution
make snekbox a class adds nsjail 2.5 (compiled on alpine 3.7) execute python code via nsjail
Diffstat (limited to 'snekbox.py')
-rw-r--r--snekbox.py160
1 files changed, 89 insertions, 71 deletions
diff --git a/snekbox.py b/snekbox.py
index 726ba4f..c19e463 100644
--- a/snekbox.py
+++ b/snekbox.py
@@ -1,83 +1,101 @@
-import sys
-import io
import json
import multiprocessing
+import subprocess
import threading
import time
from logs import log
from rmq import Rmq
-rmq = Rmq()
-
-def execute(body):
- msg = body.decode('utf-8')
- log.info(f"incoming: {msg}")
-
- failed = False
-
- old_stdout = sys.stdout
- old_stderr = sys.stderr
- redirected_output = sys.stdout = io.StringIO()
- redirected_error = sys.stderr = io.StringIO()
- snek_msg = json.loads(msg)
- snekid = snek_msg['snekid']
- snekcode = snek_msg['message'].strip()
-
- try:
- exec(snekcode)
-
- except Exception as e:
- failed = str(e)
-
- finally:
- sys.stdout = old_stdout
- sys.stderr = old_stderr
-
- if failed:
- result = failed.strip()
- log.debug(f"this was captured via exception: {result}")
-
- result_err = redirected_error.getvalue().strip()
- result_ok = redirected_output.getvalue().strip()
-
- if result_err:
- log.debug(f"this was captured via stderr: {result_err}")
- result = result_err
- if result_ok:
- result = result_ok
-
- log.info(f"outgoing: {result}")
-
- rmq.publish(result,
- queue=snekid,
- routingkey=snekid,
- exchange=snekid)
- exit(0)
-
-def stopwatch(process):
- log.debug(f"10 second timer started for process {process.pid}")
- for _ in range(10):
- time.sleep(1)
- if not process.is_alive():
- log.debug(f"Clean exit on process {process.pid}")
- exit(0)
-
- process.terminate()
- log.debug(f"Terminated process {process.pid} forcefully")
-
-def message_handler(ch, method, properties, body, thread_ws=None):
- p = multiprocessing.Process(target=execute, args=(body,))
- p.daemon = True
- p.start()
-
- t = threading.Thread(target=stopwatch, args=(p,))
- t.daemon = True
- t.start()
-
- ch.basic_ack(delivery_tag=method.delivery_tag)
+class Snekbox(object):
+ 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 python3(self, cmd):
+ args = ["nsjail", "-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",
+ "--quiet", "--", "/usr/local/bin/python3.6", "-ISq", "-c", cmd]
+
+ proc = subprocess.Popen(args,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ env=self.env,
+ universal_newlines=True)
+
+ stdout, stderr = proc.communicate()
+ if proc.returncode == 0:
+ output = stdout
+ elif proc.returncode == 1:
+ try:
+ output = stderr.split('\n')[-2]
+ except IndexError:
+ output = ''
+ elif proc.returncode == 109:
+ output = 'timed out or memory limit exceeded'
+ else:
+ output = 'unknown error'
+ return output
+
+ def execute(self, body):
+ msg = body.decode('utf-8')
+ log.info(f"incoming: {msg}")
+ result = ""
+ snek_msg = json.loads(msg)
+ snekid = snek_msg['snekid']
+ snekcode = snek_msg['message'].strip()
+
+ result = self.python3(snekcode)
+
+ log.info(f"outgoing: {result}")
+
+ rmq.publish(result,
+ queue=snekid,
+ routingkey=snekid,
+ exchange=snekid)
+ exit(0)
+
+ def stopwatch(self, process):
+ log.debug(f"10 second timer started for process {process.pid}")
+ for _ in range(10):
+ time.sleep(1)
+ if not process.is_alive():
+ log.debug(f"Clean exit on process {process.pid}")
+ exit(0)
+
+ process.terminate()
+ log.debug(f"Terminated process {process.pid} forcefully")
+
+ def message_handler(self, ch, method, properties, body, thread_ws=None):
+ p = multiprocessing.Process(target=self.execute, args=(body,))
+ p.daemon = True
+ p.start()
+ t = threading.Thread(target=self.stopwatch, args=(p,))
+ t.daemon = True
+ t.start()
+
+ ch.basic_ack(delivery_tag=method.delivery_tag)
if __name__ == '__main__':
- rmq.consume(callback=message_handler)
+ try:
+ rmq = Rmq()
+ snkbx = Snekbox()
+ rmq.consume(callback=snkbx.message_handler)
+ except KeyboardInterrupt:
+ print("Exited")
+ exit(0)