From b95ea4422bdb6c161a8ff466403d48f92ed88ce6 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Tue, 26 Feb 2013 17:27:31 -0500 Subject: [PATCH] Prevent a print statement from accidentally borking the sandbox. --- common/lib/codejail/codejail/safe_exec.py | 31 ++++++++++++++++--- .../codejail/codejail/tests/test_safe_exec.py | 5 +++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/common/lib/codejail/codejail/safe_exec.py b/common/lib/codejail/codejail/safe_exec.py index c4c6a8145b..7b4037a2a9 100644 --- a/common/lib/codejail/codejail/safe_exec.py +++ b/common/lib/codejail/codejail/safe_exec.py @@ -46,9 +46,24 @@ def safe_exec(code, globals_dict, assumed_imports=None, files=None, python_path= the_code = [] files = list(files or ()) - the_code.append(textwrap.dedent("""\ + the_code.append(textwrap.dedent( + """ import json import sys + """ + # We need to prevent the sandboxed code from printing to stdout, + # or it will pollute the json we print there. This isn't a + # security concern (they can put any values in the json output + # anyway, either by writing to sys.__stdout__, or just by defining + # global values), but keeps accidents from happening. + """ + class DevNull(object): + def write(self, *args, **kwargs): + pass + sys.stdout = DevNull() + """ + # Read the code and the globals from the stdin. + """ code, g_dict = json.load(sys.stdin) """)) @@ -62,12 +77,20 @@ def safe_exec(code, globals_dict, assumed_imports=None, files=None, python_path= for name, modname in names_and_modules(assumed_imports): the_code.append("g_dict['{}'] = LazyModule('{}')\n".format(name, modname)) - the_code.append(textwrap.dedent("""\ + the_code.append(textwrap.dedent( + # Execute the sandboxed code. + """ exec code in g_dict + """ + # Clean the globals for sending back as JSON over stdout. + """ ok_types = (type(None), int, long, float, str, unicode, list, tuple, dict) bad_keys = ("__builtins__",) g_dict = {k:v for k,v in g_dict.iteritems() if isinstance(v, ok_types) and k not in bad_keys} - json.dump(g_dict, sys.stdout) + """ + # Write the globals back to the calling process. + """ + json.dump(g_dict, sys.__stdout__) """)) stdin = json.dumps([code, globals_dict]) @@ -82,7 +105,7 @@ def safe_exec(code, globals_dict, assumed_imports=None, files=None, python_path= res = jailpy.jailpy(jailed_code, stdin=stdin, files=files) if res.status != 0: - raise Exception("Couldn't excecute jailed code: %s" % res.stderr) + raise Exception("Couldn't execute jailed code: %s" % res.stderr) globals_dict.update(json.loads(res.stdout)) diff --git a/common/lib/codejail/codejail/tests/test_safe_exec.py b/common/lib/codejail/codejail/tests/test_safe_exec.py index bf4a0408cd..b4f3627ad6 100644 --- a/common/lib/codejail/codejail/tests/test_safe_exec.py +++ b/common/lib/codejail/codejail/tests/test_safe_exec.py @@ -54,6 +54,11 @@ class SafeExecTests(object): """), g) self.assertEqual(g['x'], 1723) + def test_printing_stuff_when_you_shouldnt(self): + g = {} + self.safe_exec("a = 17; print 'hi!'", g) + self.assertEqual(g['a'], 17) + class TestSafeExec(SafeExecTests, unittest.TestCase): """Run SafeExecTests, with the real safe_exec."""