Skip to content

Commit

Permalink
Fix quality violations; enable quality checks via tox/make (#99)
Browse files Browse the repository at this point in the history
  • Loading branch information
kdmccormick authored Nov 3, 2020
1 parent 43e4fa5 commit fd26192
Show file tree
Hide file tree
Showing 18 changed files with 131 additions and 84 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ reports/*

# OS detritus
*.DS_Store

# Virtual environment
/venv/
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,8 @@ upgrade: ## update the requirements/*.txt files with the latest packages satisfy
pip-compile --upgrade -o requirements/tox.txt requirements/tox.in
pip-compile --upgrade -o requirements/testing.txt requirements/testing.in
pip-compile --upgrade -o requirements/sandbox.txt requirements/sandbox.in

quality: ## check coding style with pycodestyle and pylint
pycodestyle codejail *.py
isort --check-only --diff --recursive codejail *.py
pylint codejail *.py
4 changes: 3 additions & 1 deletion codejail/django_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
"""

from django.core.exceptions import MiddlewareNotUsed
# pylint: skip-file

from django.conf import settings
from django.core.exceptions import MiddlewareNotUsed
from django.utils.deprecation import MiddlewareMixin

from . import django_integration_utils
Expand Down
13 changes: 7 additions & 6 deletions codejail/jail_code.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
"""Run code in a jail."""

from __future__ import absolute_import
import logging
import os
import os.path
import resource
import shutil
import sys

from builtins import bytes

from .proxy import run_subprocess_through_proxy
Expand All @@ -16,7 +14,7 @@

log = logging.getLogger("codejail")

# TODO: limit too much stdout data?
# TODO: limit too much stdout data? # pylint: disable=fixme

# Configure the commands

Expand Down Expand Up @@ -60,12 +58,15 @@ def is_configured(command):
"""
return command in COMMANDS


# By default, look where our current Python is, and maybe there's a
# python-sandbox alongside. Only do this if running in a virtualenv.
# The check for sys.real_prefix covers virtualenv
# the equality of non-empty sys.base_prefix with sys.prefix covers venv
running_in_virtualenv = (hasattr(sys, 'real_prefix') or
(hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix))
running_in_virtualenv = (
hasattr(sys, 'real_prefix') or
(hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix)
)

if running_in_virtualenv:
# On jenkins
Expand Down Expand Up @@ -176,7 +177,7 @@ def override_limit(limit_name, value, limit_overrides_context):
LIMIT_OVERRIDES[limit_overrides_context][limit_name] = value


class JailResult(object):
class JailResult:
"""
A passive object for us to return from jail_code.
"""
Expand Down
29 changes: 19 additions & 10 deletions codejail/proxy.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
"""A proxy subprocess-making process for CodeJail."""

from __future__ import absolute_import
import ast
import logging
import os
import os.path
import subprocess
import sys
import time

import six
from six.moves import range

from .subproc import run_subprocess
from six.moves import range

log = logging.getLogger("codejail")

Expand Down Expand Up @@ -47,7 +47,7 @@ def deserialize_out(bstr):


##
## Client code, runs in the parent CodeJail process.
# Client code, runs in the parent CodeJail process.
##

def run_subprocess_through_proxy(*args, **kwargs):
Expand All @@ -58,7 +58,7 @@ def run_subprocess_through_proxy(*args, **kwargs):
"""
last_exception = None
for tries in range(3):
for _tries in range(3):
try:
proxy = get_proxy()

Expand All @@ -79,7 +79,7 @@ def run_subprocess_through_proxy(*args, **kwargs):
for level, msg, args in log_calls:
log.log(level, msg, *args)
return status, stdout, stderr
except Exception as e:
except Exception: # pylint: disable=broad-except
log.exception("Proxy process failed")
# Give the proxy process a chance to die completely if it is dying.
time.sleep(.001)
Expand All @@ -94,8 +94,10 @@ def run_subprocess_through_proxy(*args, **kwargs):
# There is one global proxy process.
PROXY_PROCESS = None


def get_proxy():
global PROXY_PROCESS
# pylint: disable=missing-function-docstring
global PROXY_PROCESS # pylint: disable=global-statement

# If we had a proxy process, but it died, clean up.
if PROXY_PROCESS is not None:
Expand Down Expand Up @@ -132,7 +134,7 @@ def get_proxy():
return PROXY_PROCESS

##
## Proxy process code
# Proxy process code
##


Expand All @@ -145,6 +147,9 @@ class CapturingHandler(logging.Handler):
the caller, the current exception, the current time, etc.
"""
# pylint wants us to override emit().
# pylint: disable=abstract-method

def __init__(self):
super(CapturingHandler, self).__init__()
self.log_calls = []
Expand All @@ -156,6 +161,7 @@ def handle(self, record):
self.log_calls.append((record.levelno, record.msg, record.args))

def get_log_calls(self):
# pylint: disable=missing-function-docstring
retval = self.log_calls
self.log_calls = []
return retval
Expand Down Expand Up @@ -194,17 +200,20 @@ def proxy_main(argv):
try:
while True:
stdin = sys.stdin.readline()
log.debug("proxy stdin: %r" % stdin)
log.debug("proxy stdin: %r", stdin)
if not stdin:
break
args, kwargs = deserialize_in(stdin.rstrip())
status, stdout, stderr = run_subprocess(*args, **kwargs)
log.debug("run_subprocess result: status=%r\nstdout=%r\nstderr=%r" % (status, stdout, stderr))
log.debug(
"run_subprocess result: status=%r\nstdout=%r\nstderr=%r",
status, stdout, stderr,
)
log_calls = capture_log.get_log_calls()
stdout = serialize_out((status, stdout, stderr, log_calls))
sys.stdout.write(stdout+"\n")
sys.stdout.flush()
except Exception:
except Exception: # pylint: disable=broad-except
# Note that this log message will not get back to the parent, because
# we are dying and not communicating back to the parent. This will be
# useful only if you add another handler at the top of this function.
Expand Down
50 changes: 32 additions & 18 deletions codejail/safe_exec.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
"""Safe execution of untrusted Python code."""

from __future__ import absolute_import
import inspect
import logging
import os.path
import shutil
import sys
import textwrap

import six
import inspect

from codejail import jail_code
from codejail.util import change_directory, temp_directory

try:
import simplejson as json
except ImportError:
import json

from codejail import jail_code
from codejail.util import temp_directory, change_directory

log = logging.getLogger("codejail")

Expand All @@ -36,11 +37,17 @@ class SafeExecException(Exception):
contain the original exception message.
"""
pass


def safe_exec(code, globals_dict, files=None, python_path=None,
limit_overrides_context=None, slug=None, extra_files=None):
def safe_exec(
code,
globals_dict,
files=None,
python_path=None,
limit_overrides_context=None,
slug=None,
extra_files=None,
):
"""
Execute code as "exec" does, but safely.
Expand Down Expand Up @@ -170,8 +177,9 @@ def json_safe(d):
Used to emulate reading data through a serialization straw.
"""
# pylint: disable=invalid-name

# six.binary_type is here because bytes are sometimes ok if they represent valid utf8
# six.binary_type is here because bytes are sometimes ok if they represent valid utf8
# so we consider them valid for now and try to decode them with decode_object. If that
# doesn't work they'll get dropped later in the process.
ok_types = (type(None), int, float, six.binary_type, six.text_type, list, tuple, dict)
Expand All @@ -190,21 +198,20 @@ def decode_object(obj):
"""
if isinstance(obj, bytes):
return obj.decode('utf-8')
elif isinstance(obj, (list,tuple)):
if isinstance(obj, (list, tuple)):
new_list = []
for i in obj:
new_obj = decode_object(i)
new_list.append(new_obj)
return new_list
elif isinstance(obj, dict):
if isinstance(obj, dict):
new_dict = {}
for k,v in six.iteritems(obj):
for k, v in six.iteritems(obj):
new_key = decode_object(k)
new_value = decode_object(v)
new_dict[new_key] = new_value
return new_dict
else:
return obj
return obj

bad_keys = ("__builtins__",)
jd = {}
Expand All @@ -223,16 +230,22 @@ def decode_object(obj):

# Also ensure that the keys encode/decode correctly
k = json.loads(json.dumps(decode_object(k)))
except Exception:
except Exception: # pylint: disable=broad-except
continue
else:
jd[k] = v
return json.loads(json.dumps(jd))


def not_safe_exec(code, globals_dict, files=None, python_path=None,
limit_overrides_context=None, # pylint: disable=unused-argument
slug=None, extra_files=None):
def not_safe_exec(
code,
globals_dict,
files=None,
python_path=None,
limit_overrides_context=None, # pylint: disable=unused-argument
slug=None, # pylint: disable=unused-argument
extra_files=None,
):
"""
Another implementation of `safe_exec`, but not safe.
Expand All @@ -248,6 +261,7 @@ def not_safe_exec(code, globals_dict, files=None, python_path=None,

with temp_directory() as tmpdir:
with change_directory(tmpdir):
# pylint: disable=invalid-name
# Copy the files here.
for filename in files or ():
dest = os.path.join(tmpdir, os.path.basename(filename))
Expand All @@ -261,7 +275,7 @@ def not_safe_exec(code, globals_dict, files=None, python_path=None,
if python_path:
sys.path.extend(python_path)
try:
exec(code, g_dict)
exec(code, g_dict) # pylint: disable=exec-used
except Exception as e:
# Wrap the exception in a SafeExecException, but we don't
# try here to include the traceback, since this is just a
Expand Down
9 changes: 4 additions & 5 deletions codejail/subproc.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Subprocess helpers for CodeJail."""

from __future__ import absolute_import
import functools
import logging
import os
Expand All @@ -13,8 +12,8 @@


def run_subprocess(
cmd, stdin=None, cwd=None, env=None, rlimits=None, realtime=None,
slug=None,
cmd, stdin=None, cwd=None, env=None, rlimits=None, realtime=None,
slug=None,
):
"""
A helper to make a limited subprocess.
Expand All @@ -37,11 +36,11 @@ def run_subprocess(
the stdout and stderr of the process, as strings.
"""
subproc = subprocess.Popen(
subproc = subprocess.Popen( # pylint: disable=subprocess-popen-preexec-fn
cmd, cwd=cwd, env=env,
preexec_fn=functools.partial(set_process_limits, rlimits or ()),
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
)
)

if slug:
log.info("Executed jailed code %s in %s, with PID %s", slug, cwd, subproc.pid)
Expand Down
5 changes: 3 additions & 2 deletions codejail/tests/doit.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import absolute_import
from __future__ import print_function
# pylint: skip-file isort:skip_file
from __future__ import absolute_import, print_function

import sys

print("This is doit.py!")
Expand Down
1 change: 0 additions & 1 deletion codejail/tests/test_django_integration_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

from .. import jail_code
from ..django_integration_utils import apply_django_settings

from .util import ResetJailCodeStateMixin


Expand Down
Loading

0 comments on commit fd26192

Please sign in to comment.