Commit bd3417ab by Ned Batchelder

Formalize what happens if the sandboxed code raises an exception.

parent cb724a3e
...@@ -13,6 +13,16 @@ from codejail.util import temp_directory, change_directory ...@@ -13,6 +13,16 @@ from codejail.util import temp_directory, change_directory
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class SafeExecException(Exception):
"""Python code running in the sandbox has failed.
The message will be the stdout of the sandboxed process, which will usually
contain the original exception message.
"""
pass
def safe_exec(code, globals_dict, files=None, python_path=None): def safe_exec(code, globals_dict, files=None, python_path=None):
"""Execute code as "exec" does, but safely. """Execute code as "exec" does, but safely.
...@@ -20,7 +30,10 @@ def safe_exec(code, globals_dict, files=None, python_path=None): ...@@ -20,7 +30,10 @@ def safe_exec(code, globals_dict, files=None, python_path=None):
during execution. Modifications the code makes to `globals_dict` are during execution. Modifications the code makes to `globals_dict` are
reflected in the dictionary on return. reflected in the dictionary on return.
Returns None. Changes made by `code` are visible in `globals_dict`. Returns None. Changes made by `code` are visible in `globals_dict`. If
the code raises an exception, this function will raise `SafeExecException`
with the stderr of the sandbox process, which usually includes the original
exception message and traceback.
""" """
the_code = [] the_code = []
...@@ -87,7 +100,7 @@ def safe_exec(code, globals_dict, files=None, python_path=None): ...@@ -87,7 +100,7 @@ def safe_exec(code, globals_dict, files=None, python_path=None):
res = jail_code.jail_code("python", code=jailed_code, stdin=stdin, files=files) res = jail_code.jail_code("python", code=jailed_code, stdin=stdin, files=files)
if res.status != 0: if res.status != 0:
raise Exception("Couldn't execute jailed code: %s" % res.stderr) raise SafeExecException("Couldn't execute jailed code: %s" % res.stderr)
globals_dict.update(json.loads(res.stdout)) globals_dict.update(json.loads(res.stdout))
...@@ -137,6 +150,11 @@ def not_safe_exec(code, globals_dict, files=None, python_path=None): ...@@ -137,6 +150,11 @@ def not_safe_exec(code, globals_dict, files=None, python_path=None):
sys.path.extend(python_path) sys.path.extend(python_path)
try: try:
exec code in g_dict exec code in g_dict
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
# substitute implementation.
raise SafeExecException("{0.__class__.__name__}: {0!s}".format(e))
finally: finally:
sys.path = original_path sys.path = original_path
......
...@@ -5,7 +5,7 @@ import textwrap ...@@ -5,7 +5,7 @@ import textwrap
import unittest import unittest
from nose.plugins.skip import SkipTest from nose.plugins.skip import SkipTest
from codejail.safe_exec import safe_exec, not_safe_exec from codejail.safe_exec import safe_exec, not_safe_exec, SafeExecException
class SafeExecTests(object): class SafeExecTests(object):
...@@ -55,6 +55,15 @@ class SafeExecTests(object): ...@@ -55,6 +55,15 @@ class SafeExecTests(object):
"""), g) """), g)
self.assertEqual(g['a'], 1723) self.assertEqual(g['a'], 1723)
def test_raising_exceptions(self):
g = {}
with self.assertRaises(SafeExecException) as cm:
self.safe_exec(textwrap.dedent("""\
raise ValueError("That's not how you pour soup!")
"""), g)
msg = str(cm.exception)
self.assertIn("ValueError: That's not how you pour soup!", msg)
class TestSafeExec(SafeExecTests, unittest.TestCase): class TestSafeExec(SafeExecTests, unittest.TestCase):
"""Run SafeExecTests, with the real safe_exec.""" """Run SafeExecTests, with the real safe_exec."""
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment