Skip to content

Commit 48d59e9

Browse files
committed
fix: Require an explicit opt in to unsafety; defer decision to call time
Codejail currently makes a decision at module load time of whether it should run all code safely or unsafely, and defaults to unsafely. This causes several problems: - Any misconfiguration of codejail (such as a missing Django setting or middleware) results in the application becoming immediately and entirely vulnerable to anyone who can submit code. - Codejail's behavior changes depending on when the `codejail.safe_exec` module is loaded during application initialization. This causes unstable behavior and is confusing for developers. This change switches the `ALWAYS_BE_UNSAFE` check to occur only at the time that `safe_exec` is actually called, rather than at module load time. The check for whether codejail is configured for Python is also moved to call time, but no longer automatically switches codejail to unsafe mode. Instead, it raises an exception to notify the user of their error.
1 parent b5682ea commit 48d59e9

File tree

3 files changed

+35
-27
lines changed

3 files changed

+35
-27
lines changed

README.rst

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,12 @@ Installation
6666
------------
6767

6868
These instructions detail how to configure your operating system so that
69-
CodeJail can execute Python code safely. You can run CodeJail without these
70-
steps, and you will have an unsafe CodeJail. This is fine for developers'
71-
machines who are unconcerned with security, and simplifies the integration of
72-
CodeJail into your project.
69+
CodeJail can execute Python code safely. However, it is also possible to set
70+
``codejail.safe_exec.ALWAYS_BE_UNSAFE = True`` and execute submitted Python
71+
directly on the machine, with no security whatsoever. This may be fine for
72+
developers' machines who are unconcerned with security, and allows testing
73+
an integration with CodeJail's API. It must not be used if any input is coming
74+
from untrusted sources, however.
7375

7476
To secure Python execution, you'll be creating a new virtualenv. This means
7577
you'll have two: the main virtualenv for your project, and the new one for

codejail/safe_exec.py

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,13 @@
2323

2424
# Set this to True to log all the code and globals being executed.
2525
LOG_ALL_CODE = False
26-
# Set this to True to use the unsafe code, so that you can debug it.
26+
27+
# Set this to True to run submitted code with no confinement and no sandbox.
28+
#
29+
# WARNING: This is deeply dangerous; anyone who can submit code can take
30+
# over the computer immediately and entirely.
31+
#
32+
# The only purpose of this setting is for local debugging.
2733
ALWAYS_BE_UNSAFE = False
2834

2935

@@ -80,8 +86,22 @@ def safe_exec(
8086
the code raises an exception, this function will raise `SafeExecException`
8187
with the stderr of the sandbox process, which usually includes the original
8288
exception message and traceback.
83-
8489
"""
90+
if ALWAYS_BE_UNSAFE:
91+
not_safe_exec(
92+
code,
93+
globals_dict,
94+
files=files,
95+
python_path=python_path,
96+
limit_overrides_context=limit_overrides_context,
97+
slug=slug,
98+
extra_files=extra_files,
99+
)
100+
return
101+
102+
if not jail_code.is_configured('python'):
103+
raise RuntimeError("safe_exec has not been configured for Python")
104+
85105
the_code = []
86106

87107
files = list(files or ())
@@ -257,6 +277,11 @@ def not_safe_exec(
257277
Note that `limit_overrides_context` is ignored here, because resource limits
258278
are not applied.
259279
"""
280+
# Because it would be bad if this function were used in production,
281+
# let's log a warning when it is used. Developers can live with
282+
# one more log line.
283+
log.warning("Using codejail/safe_exec.py:not_safe_exec for %s", slug)
284+
260285
g_dict = json_safe(globals_dict)
261286

262287
with temp_directory() as tmpdir:
@@ -286,22 +311,3 @@ def not_safe_exec(
286311
sys.path = original_path
287312

288313
globals_dict.update(json_safe(g_dict))
289-
290-
291-
# If the developer wants us to be unsafe (ALWAYS_BE_UNSAFE), or if there isn't
292-
# a configured jail for Python, then we'll be UNSAFE.
293-
UNSAFE = ALWAYS_BE_UNSAFE or not jail_code.is_configured("python")
294-
295-
if UNSAFE: # pragma: no cover
296-
# Make safe_exec actually call not_safe_exec, but log that we're doing so.
297-
298-
def safe_exec(*args, **kwargs): # pylint: disable=E0102
299-
"""An actually-unsafe safe_exec, that warns it's being used."""
300-
301-
# Because it would be bad if this function were used in production,
302-
# let's log a warning when it is used. Developers can live with
303-
# one more log line.
304-
slug = kwargs.get('slug', None)
305-
log.warning("Using codejail/safe_exec.py:not_safe_exec for %s", slug)
306-
307-
return not_safe_exec(*args, **kwargs)

codejail/tests/test_safe_exec.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,9 @@ class TestNotSafeExec(SafeExecTests, TestCase):
183183
__test__ = True
184184

185185
def setUp(self):
186-
# If safe_exec is actually an alias to not_safe_exec, then there's no
186+
# If safe_exec will actually just call not_safe_exec, then there's no
187187
# point running these tests.
188-
if safe_exec.UNSAFE: # pragma: no cover
188+
if safe_exec.ALWAYS_BE_UNSAFE: # pragma: no cover
189189
raise SkipTest
190190

191191
def safe_exec(self, *args, **kwargs):

0 commit comments

Comments
 (0)