diff options
| author | 2018-05-30 19:52:07 +0200 | |
|---|---|---|
| committer | 2018-05-30 19:52:07 +0200 | |
| commit | 7f756fd7f709bc74b3eb558dccb44607844fedb4 (patch) | |
| tree | 5844b9023466373233094a5de2ae3c192dd9b67b /snekbox.py | |
| parent | typo 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.py | 160 | 
1 files changed, 89 insertions, 71 deletions
| @@ -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) | 
