diff --git a/common/lib/codejail/codejail/jail_code.py b/common/lib/codejail/codejail/jail_code.py index 9b48568bd8..2e1b78fe0a 100644 --- a/common/lib/codejail/codejail/jail_code.py +++ b/common/lib/codejail/codejail/jail_code.py @@ -68,15 +68,20 @@ class JailResult(object): self.stdout = self.stderr = self.status = None -def jail_code(command, code, files=None, argv=None, stdin=None): +def jail_code(command, code=None, files=None, argv=None, stdin=None): """Run code in a jailed subprocess. `command` is an abstract command ("python", "node", ...) that must have been configured using `configure`. - `code` is a string containing the Python code to run. + `code` is a string containing the code to run. If no code is supplied, + then the code to run must be in one of the `files` copied, and must be + named in the `argv` list. - `files` is a list of file paths. + `files` is a list of file paths, they are all copied to the jailed + directory. + + `argv` is the command-line arguments to supply. Return an object with: @@ -92,6 +97,8 @@ def jail_code(command, code, files=None, argv=None, stdin=None): log.debug("Executing jailed code: %r", code) + argv = argv or [] + # All the supporting files are copied into our directory. for filename in files or (): if os.path.isfile(filename): @@ -101,10 +108,13 @@ def jail_code(command, code, files=None, argv=None, stdin=None): shutil.copytree(filename, dest) # Create the main file. - with open(os.path.join(tmpdir, "jailed_code.py"), "w") as jailed: - jailed.write(code) + if code: + with open(os.path.join(tmpdir, "jailed_code"), "w") as jailed: + jailed.write(code) - cmd = COMMANDS[command] + ['jailed_code.py'] + (argv or []) + argv = ["jailed_code"] + argv + + cmd = COMMANDS[command] + argv subproc = subprocess.Popen( cmd, preexec_fn=set_process_limits, cwd=tmpdir, diff --git a/common/lib/codejail/codejail/safe_exec.py b/common/lib/codejail/codejail/safe_exec.py index 5379052ce0..79729565f7 100644 --- a/common/lib/codejail/codejail/safe_exec.py +++ b/common/lib/codejail/codejail/safe_exec.py @@ -85,7 +85,7 @@ def safe_exec(code, globals_dict, files=None, python_path=None): log.debug("Exec: %s", code) log.debug("Stdin: %s", stdin) - res = jail_code.jail_code("python", jailed_code, stdin=stdin, files=files) + res = jail_code.jail_code("python", code=jailed_code, stdin=stdin, files=files) if res.status != 0: 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/doit.py b/common/lib/codejail/codejail/tests/doit.py new file mode 100644 index 0000000000..5786635067 --- /dev/null +++ b/common/lib/codejail/codejail/tests/doit.py @@ -0,0 +1,4 @@ +import sys + +print "This is doit.py!" +print "My args are %r" % (sys.argv,) diff --git a/common/lib/codejail/codejail/tests/test_jailpy.py b/common/lib/codejail/codejail/tests/test_jailpy.py index 395cfc4d53..21cb780946 100644 --- a/common/lib/codejail/codejail/tests/test_jailpy.py +++ b/common/lib/codejail/codejail/tests/test_jailpy.py @@ -11,9 +11,15 @@ dedent = textwrap.dedent def jailpy(*args, **kwargs): + """Run `jail_code` on Python.""" return jail_code("python", *args, **kwargs) +def file_here(fname): + """Return the full path to a file alongside this code.""" + return os.path.join(os.path.dirname(__file__), fname) + + class JailCodeHelpers(object): """Assert helpers for jail_code tests.""" def setUp(self): @@ -28,32 +34,32 @@ class JailCodeHelpers(object): class TestFeatures(JailCodeHelpers, unittest.TestCase): def test_hello_world(self): - res = jailpy("print 'Hello, world!'") + res = jailpy(code="print 'Hello, world!'") self.assertResultOk(res) self.assertEqual(res.stdout, 'Hello, world!\n') def test_argv(self): res = jailpy( - "import sys; print ':'.join(sys.argv[1:])", + code="import sys; print ':'.join(sys.argv[1:])", argv=["Hello", "world", "-x"] ) self.assertResultOk(res) self.assertEqual(res.stdout, "Hello:world:-x\n") def test_ends_with_exception(self): - res = jailpy("""raise Exception('FAIL')""") + res = jailpy(code="""raise Exception('FAIL')""") self.assertNotEqual(res.status, 0) self.assertEqual(res.stdout, "") self.assertEqual(res.stderr, dedent("""\ Traceback (most recent call last): - File "jailed_code.py", line 1, in + File "jailed_code", line 1, in raise Exception('FAIL') Exception: FAIL """)) def test_stdin_is_provided(self): res = jailpy( - "import json,sys; print sum(json.load(sys.stdin))", + code="import json,sys; print sum(json.load(sys.stdin))", stdin="[1, 2.5, 33]" ) self.assertResultOk(res) @@ -61,27 +67,35 @@ class TestFeatures(JailCodeHelpers, unittest.TestCase): def test_files_are_copied(self): res = jailpy( - "print 'Look:', open('hello.txt').read()", - files=[os.path.dirname(__file__) + "/hello.txt"] + code="print 'Look:', open('hello.txt').read()", + files=[file_here("hello.txt")] ) self.assertResultOk(res) self.assertEqual(res.stdout, 'Look: Hello there.\n\n') + def test_executing_a_copied_file(self): + res = jailpy( + files=[file_here("doit.py")], + argv=["doit.py", "1", "2", "3"] + ) + self.assertResultOk(res) + self.assertEqual(res.stdout, "This is doit.py!\nMy args are ['doit.py', '1', '2', '3']\n") + class TestLimits(JailCodeHelpers, unittest.TestCase): def test_cant_use_too_much_memory(self): - res = jailpy("print sum(range(100000000))") + res = jailpy(code="print sum(range(100000000))") self.assertNotEqual(res.status, 0) self.assertEqual(res.stdout, "") def test_cant_use_too_much_cpu(self): - res = jailpy("print sum(xrange(100000000))") + res = jailpy(code="print sum(xrange(100000000))") self.assertNotEqual(res.status, 0) self.assertEqual(res.stdout, "") def test_cant_use_too_much_time(self): raise SkipTest # TODO: test this once we can kill sleeping processes. - res = jailpy(dedent("""\ + res = jailpy(code=dedent("""\ import time time.sleep(5) print 'Done!' @@ -90,7 +104,7 @@ class TestLimits(JailCodeHelpers, unittest.TestCase): self.assertEqual(res.stdout, "") def test_cant_write_files(self): - res = jailpy(dedent("""\ + res = jailpy(code=dedent("""\ print "Trying" with open("mydata.txt", "w") as f: f.write("hello") @@ -102,7 +116,7 @@ class TestLimits(JailCodeHelpers, unittest.TestCase): self.assertIn("ermission denied", res.stderr) def test_cant_use_network(self): - res = jailpy(dedent("""\ + res = jailpy(code=dedent("""\ import urllib print "Reading google" u = urllib.urlopen("http://google.com") @@ -121,7 +135,7 @@ class TestLimits(JailCodeHelpers, unittest.TestCase): class TestMalware(JailCodeHelpers, unittest.TestCase): def test_crash_cpython(self): # http://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html - res = jailpy(dedent("""\ + res = jailpy(code=dedent("""\ import new, sys crash_me = new.function(new.code(0,0,0,0,"KABOOM",(),(),(),"","",0,""), {}) print "Here we go..." @@ -134,7 +148,7 @@ class TestMalware(JailCodeHelpers, unittest.TestCase): self.assertEqual(res.stderr, "") def test_read_etc_passwd(self): - res = jailpy(dedent("""\ + res = jailpy(code=dedent("""\ bytes = len(open('/etc/passwd').read()) print 'Gotcha', bytes """)) @@ -143,7 +157,7 @@ class TestMalware(JailCodeHelpers, unittest.TestCase): self.assertIn("ermission denied", res.stderr) def test_find_other_sandboxes(self): - res = jailpy(dedent(""" + res = jailpy(code=dedent(""" import os; places = [ "..", "/tmp", "/", "/home", "/etc",