Commit fb534323 by Ned Batchelder

jail_code can execute a provided file also.

parent 9683098f
...@@ -68,15 +68,20 @@ class JailResult(object): ...@@ -68,15 +68,20 @@ class JailResult(object):
self.stdout = self.stderr = self.status = None 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. """Run code in a jailed subprocess.
`command` is an abstract command ("python", "node", ...) that must have `command` is an abstract command ("python", "node", ...) that must have
been configured using `configure`. 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: Return an object with:
...@@ -92,6 +97,8 @@ def jail_code(command, code, files=None, argv=None, stdin=None): ...@@ -92,6 +97,8 @@ def jail_code(command, code, files=None, argv=None, stdin=None):
log.debug("Executing jailed code: %r", code) log.debug("Executing jailed code: %r", code)
argv = argv or []
# All the supporting files are copied into our directory. # All the supporting files are copied into our directory.
for filename in files or (): for filename in files or ():
if os.path.isfile(filename): if os.path.isfile(filename):
...@@ -101,10 +108,13 @@ def jail_code(command, code, files=None, argv=None, stdin=None): ...@@ -101,10 +108,13 @@ def jail_code(command, code, files=None, argv=None, stdin=None):
shutil.copytree(filename, dest) shutil.copytree(filename, dest)
# Create the main file. # Create the main file.
with open(os.path.join(tmpdir, "jailed_code.py"), "w") as jailed: if code:
with open(os.path.join(tmpdir, "jailed_code"), "w") as jailed:
jailed.write(code) jailed.write(code)
cmd = COMMANDS[command] + ['jailed_code.py'] + (argv or []) argv = ["jailed_code"] + argv
cmd = COMMANDS[command] + argv
subproc = subprocess.Popen( subproc = subprocess.Popen(
cmd, preexec_fn=set_process_limits, cwd=tmpdir, cmd, preexec_fn=set_process_limits, cwd=tmpdir,
......
...@@ -85,7 +85,7 @@ def safe_exec(code, globals_dict, files=None, python_path=None): ...@@ -85,7 +85,7 @@ def safe_exec(code, globals_dict, files=None, python_path=None):
log.debug("Exec: %s", code) log.debug("Exec: %s", code)
log.debug("Stdin: %s", stdin) 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: if res.status != 0:
raise Exception("Couldn't execute jailed code: %s" % res.stderr) raise Exception("Couldn't execute jailed code: %s" % res.stderr)
globals_dict.update(json.loads(res.stdout)) globals_dict.update(json.loads(res.stdout))
......
import sys
print "This is doit.py!"
print "My args are %r" % (sys.argv,)
...@@ -11,9 +11,15 @@ dedent = textwrap.dedent ...@@ -11,9 +11,15 @@ dedent = textwrap.dedent
def jailpy(*args, **kwargs): def jailpy(*args, **kwargs):
"""Run `jail_code` on Python."""
return jail_code("python", *args, **kwargs) 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): class JailCodeHelpers(object):
"""Assert helpers for jail_code tests.""" """Assert helpers for jail_code tests."""
def setUp(self): def setUp(self):
...@@ -28,32 +34,32 @@ class JailCodeHelpers(object): ...@@ -28,32 +34,32 @@ class JailCodeHelpers(object):
class TestFeatures(JailCodeHelpers, unittest.TestCase): class TestFeatures(JailCodeHelpers, unittest.TestCase):
def test_hello_world(self): def test_hello_world(self):
res = jailpy("print 'Hello, world!'") res = jailpy(code="print 'Hello, world!'")
self.assertResultOk(res) self.assertResultOk(res)
self.assertEqual(res.stdout, 'Hello, world!\n') self.assertEqual(res.stdout, 'Hello, world!\n')
def test_argv(self): def test_argv(self):
res = jailpy( res = jailpy(
"import sys; print ':'.join(sys.argv[1:])", code="import sys; print ':'.join(sys.argv[1:])",
argv=["Hello", "world", "-x"] argv=["Hello", "world", "-x"]
) )
self.assertResultOk(res) self.assertResultOk(res)
self.assertEqual(res.stdout, "Hello:world:-x\n") self.assertEqual(res.stdout, "Hello:world:-x\n")
def test_ends_with_exception(self): def test_ends_with_exception(self):
res = jailpy("""raise Exception('FAIL')""") res = jailpy(code="""raise Exception('FAIL')""")
self.assertNotEqual(res.status, 0) self.assertNotEqual(res.status, 0)
self.assertEqual(res.stdout, "") self.assertEqual(res.stdout, "")
self.assertEqual(res.stderr, dedent("""\ self.assertEqual(res.stderr, dedent("""\
Traceback (most recent call last): Traceback (most recent call last):
File "jailed_code.py", line 1, in <module> File "jailed_code", line 1, in <module>
raise Exception('FAIL') raise Exception('FAIL')
Exception: FAIL Exception: FAIL
""")) """))
def test_stdin_is_provided(self): def test_stdin_is_provided(self):
res = jailpy( 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]" stdin="[1, 2.5, 33]"
) )
self.assertResultOk(res) self.assertResultOk(res)
...@@ -61,27 +67,35 @@ class TestFeatures(JailCodeHelpers, unittest.TestCase): ...@@ -61,27 +67,35 @@ class TestFeatures(JailCodeHelpers, unittest.TestCase):
def test_files_are_copied(self): def test_files_are_copied(self):
res = jailpy( res = jailpy(
"print 'Look:', open('hello.txt').read()", code="print 'Look:', open('hello.txt').read()",
files=[os.path.dirname(__file__) + "/hello.txt"] files=[file_here("hello.txt")]
) )
self.assertResultOk(res) self.assertResultOk(res)
self.assertEqual(res.stdout, 'Look: Hello there.\n\n') 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): class TestLimits(JailCodeHelpers, unittest.TestCase):
def test_cant_use_too_much_memory(self): 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.assertNotEqual(res.status, 0)
self.assertEqual(res.stdout, "") self.assertEqual(res.stdout, "")
def test_cant_use_too_much_cpu(self): 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.assertNotEqual(res.status, 0)
self.assertEqual(res.stdout, "") self.assertEqual(res.stdout, "")
def test_cant_use_too_much_time(self): def test_cant_use_too_much_time(self):
raise SkipTest # TODO: test this once we can kill sleeping processes. raise SkipTest # TODO: test this once we can kill sleeping processes.
res = jailpy(dedent("""\ res = jailpy(code=dedent("""\
import time import time
time.sleep(5) time.sleep(5)
print 'Done!' print 'Done!'
...@@ -90,7 +104,7 @@ class TestLimits(JailCodeHelpers, unittest.TestCase): ...@@ -90,7 +104,7 @@ class TestLimits(JailCodeHelpers, unittest.TestCase):
self.assertEqual(res.stdout, "") self.assertEqual(res.stdout, "")
def test_cant_write_files(self): def test_cant_write_files(self):
res = jailpy(dedent("""\ res = jailpy(code=dedent("""\
print "Trying" print "Trying"
with open("mydata.txt", "w") as f: with open("mydata.txt", "w") as f:
f.write("hello") f.write("hello")
...@@ -102,7 +116,7 @@ class TestLimits(JailCodeHelpers, unittest.TestCase): ...@@ -102,7 +116,7 @@ class TestLimits(JailCodeHelpers, unittest.TestCase):
self.assertIn("ermission denied", res.stderr) self.assertIn("ermission denied", res.stderr)
def test_cant_use_network(self): def test_cant_use_network(self):
res = jailpy(dedent("""\ res = jailpy(code=dedent("""\
import urllib import urllib
print "Reading google" print "Reading google"
u = urllib.urlopen("http://google.com") u = urllib.urlopen("http://google.com")
...@@ -121,7 +135,7 @@ class TestLimits(JailCodeHelpers, unittest.TestCase): ...@@ -121,7 +135,7 @@ class TestLimits(JailCodeHelpers, unittest.TestCase):
class TestMalware(JailCodeHelpers, unittest.TestCase): class TestMalware(JailCodeHelpers, unittest.TestCase):
def test_crash_cpython(self): def test_crash_cpython(self):
# http://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html # http://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
res = jailpy(dedent("""\ res = jailpy(code=dedent("""\
import new, sys import new, sys
crash_me = new.function(new.code(0,0,0,0,"KABOOM",(),(),(),"","",0,""), {}) crash_me = new.function(new.code(0,0,0,0,"KABOOM",(),(),(),"","",0,""), {})
print "Here we go..." print "Here we go..."
...@@ -134,7 +148,7 @@ class TestMalware(JailCodeHelpers, unittest.TestCase): ...@@ -134,7 +148,7 @@ class TestMalware(JailCodeHelpers, unittest.TestCase):
self.assertEqual(res.stderr, "") self.assertEqual(res.stderr, "")
def test_read_etc_passwd(self): def test_read_etc_passwd(self):
res = jailpy(dedent("""\ res = jailpy(code=dedent("""\
bytes = len(open('/etc/passwd').read()) bytes = len(open('/etc/passwd').read())
print 'Gotcha', bytes print 'Gotcha', bytes
""")) """))
...@@ -143,7 +157,7 @@ class TestMalware(JailCodeHelpers, unittest.TestCase): ...@@ -143,7 +157,7 @@ class TestMalware(JailCodeHelpers, unittest.TestCase):
self.assertIn("ermission denied", res.stderr) self.assertIn("ermission denied", res.stderr)
def test_find_other_sandboxes(self): def test_find_other_sandboxes(self):
res = jailpy(dedent(""" res = jailpy(code=dedent("""
import os; import os;
places = [ places = [
"..", "/tmp", "/", "/home", "/etc", "..", "/tmp", "/", "/home", "/etc",
......
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