Commit bde976da by Ned Batchelder

Refactor code_jail to accommodate non-Python code.

parent 55e910aa
...@@ -30,7 +30,7 @@ ASSUMED_IMPORTS=[ ...@@ -30,7 +30,7 @@ ASSUMED_IMPORTS=[
("draganddrop", "verifiers.draganddrop"), ("draganddrop", "verifiers.draganddrop"),
] ]
# We'll need the code from lazymod.py for use in jailpy, so read it now. # We'll need the code from lazymod.py for use in safe_exec, so read it now.
lazymod_py_file = lazymod.__file__ lazymod_py_file = lazymod.__file__
if lazymod_py_file.endswith("c"): if lazymod_py_file.endswith("c"):
lazymod_py_file = lazymod_py_file[:-1] lazymod_py_file = lazymod_py_file[:-1]
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
from django.core.exceptions import MiddlewareNotUsed from django.core.exceptions import MiddlewareNotUsed
from django.conf import settings from django.conf import settings
import codejail.jailpy import codejail.jail_code
class ConfigureCodeJailMiddleware(object): class ConfigureCodeJailMiddleware(object):
...@@ -13,5 +13,5 @@ class ConfigureCodeJailMiddleware(object): ...@@ -13,5 +13,5 @@ class ConfigureCodeJailMiddleware(object):
python_bin = settings.CODE_JAIL.get('python_bin') python_bin = settings.CODE_JAIL.get('python_bin')
if python_bin: if python_bin:
user = settings.CODE_JAIL['user'] user = settings.CODE_JAIL['user']
codejail.jailpy.configure(python_bin, user=user) codejail.jail_code.configure("python", python_bin, user=user)
raise MiddlewareNotUsed raise MiddlewareNotUsed
...@@ -19,39 +19,49 @@ log = logging.getLogger(__name__) ...@@ -19,39 +19,49 @@ log = logging.getLogger(__name__)
# TODO: limit too much stdout data? # TODO: limit too much stdout data?
# Configure the Python command # Configure the commands
PYTHON_CMD = None # COMMANDS is a map from an abstract command name to a list of command-line
# pieces, such as subprocess.Popen wants.
COMMANDS = {}
def configure(python_bin, user=None): def configure(command, bin_path, user=None):
"""Configure the jailpy module.""" """Configure a command for jail_code to use.
global PYTHON_CMD
PYTHON_CMD = [] `command` is the abstract command you're configuring, such as "python" or
"node". `bin_path` is the path to the binary. `user`, if provided, is
the user name to run the command under.
"""
cmd_argv = []
if user: if user:
PYTHON_CMD.extend(['sudo', '-u', 'sandbox']) cmd_argv.extend(['sudo', '-u', 'sandbox'])
PYTHON_CMD.extend([python_bin, '-E']) cmd_argv.extend([bin_path, '-E'])
COMMANDS[command] = cmd_argv
def is_configured(): def is_configured(command):
return bool(PYTHON_CMD) return command in COMMANDS
# By default, look where our current Python is, and maybe there's a # By default, look where our current Python is, and maybe there's a
# python-sandbox alongside. Only do this if running in a virtualenv. # python-sandbox alongside. Only do this if running in a virtualenv.
if hasattr(sys, 'real_prefix'): if hasattr(sys, 'real_prefix'):
if os.path.isdir(sys.prefix + "-sandbox"): if os.path.isdir(sys.prefix + "-sandbox"):
configure(sys.prefix + "-sandbox/bin/python", "sandbox") configure("python", sys.prefix + "-sandbox/bin/python", "sandbox")
class JailResult(object): class JailResult(object):
"""A passive object for us to return from jailpy.""" """A passive object for us to return from jail_code."""
def __init__(self): def __init__(self):
self.stdout = self.stderr = self.status = None self.stdout = self.stderr = self.status = None
def jailpy(code, files=None, argv=None, stdin=None): def jail_code(command, code, files=None, argv=None, stdin=None):
""" """Run code in a jailed subprocess.
Run Python 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 Python code to run.
...@@ -64,8 +74,8 @@ def jailpy(code, files=None, argv=None, stdin=None): ...@@ -64,8 +74,8 @@ def jailpy(code, files=None, argv=None, stdin=None):
.status: return status of the process: an int, 0 for successful .status: return status of the process: an int, 0 for successful
""" """
if not PYTHON_CMD: if not is_configured(command):
raise Exception("jailpy needs to be configured") raise Exception("jail_code needs to be configured for %r" % command)
with temp_directory(delete_when_done=True) as tmpdir: with temp_directory(delete_when_done=True) as tmpdir:
...@@ -83,7 +93,7 @@ def jailpy(code, files=None, argv=None, stdin=None): ...@@ -83,7 +93,7 @@ def jailpy(code, files=None, argv=None, stdin=None):
with open(os.path.join(tmpdir, "jailed_code.py"), "w") as jailed: with open(os.path.join(tmpdir, "jailed_code.py"), "w") as jailed:
jailed.write(code) jailed.write(code)
cmd = PYTHON_CMD + ['jailed_code.py'] + (argv or []) cmd = COMMANDS[command] + ['jailed_code.py'] + (argv or [])
subproc = subprocess.Popen( subproc = subprocess.Popen(
cmd, preexec_fn=set_process_limits, cwd=tmpdir, cmd, preexec_fn=set_process_limits, cwd=tmpdir,
......
...@@ -7,7 +7,7 @@ import shutil ...@@ -7,7 +7,7 @@ import shutil
import sys import sys
import textwrap import textwrap
from codejail import jailpy from codejail import jail_code
from codejail.util import temp_directory, change_directory from codejail.util import temp_directory, change_directory
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -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 = jailpy.jailpy(jailed_code, stdin=stdin, files=files) res = jail_code.jail_code("python", 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))
...@@ -144,5 +144,5 @@ def not_safe_exec(code, globals_dict, files=None, python_path=None): ...@@ -144,5 +144,5 @@ def not_safe_exec(code, globals_dict, files=None, python_path=None):
# Running Python code in the sandbox makes it difficult to debug. # Running Python code in the sandbox makes it difficult to debug.
# Change 0 to 1 to run the code directly. # Change 0 to 1 to run the code directly.
if 0 or not jailpy.is_configured(): if 0 or not jail_code.is_configured("python"):
safe_exec = not_safe_exec safe_exec = not_safe_exec
"""Test jailpy.py""" """Test jail_code.py"""
import os.path import os.path
import textwrap import textwrap
import unittest import unittest
from nose.plugins.skip import SkipTest from nose.plugins.skip import SkipTest
from codejail.jailpy import jailpy, is_configured from codejail.jail_code import jail_code, is_configured
dedent = textwrap.dedent dedent = textwrap.dedent
class JailPyHelpers(object): def jailpy(*args, **kwargs):
"""Assert helpers for jailpy tests.""" return jail_code("python", *args, **kwargs)
class JailCodeHelpers(object):
"""Assert helpers for jail_code tests."""
def setUp(self): def setUp(self):
super(JailPyHelpers, self).setUp() super(JailCodeHelpers, self).setUp()
if not is_configured(): if not is_configured("python"):
raise SkipTest raise SkipTest
def assertResultOk(self, res): def assertResultOk(self, res):
...@@ -22,7 +26,7 @@ class JailPyHelpers(object): ...@@ -22,7 +26,7 @@ class JailPyHelpers(object):
self.assertEqual(res.status, 0) self.assertEqual(res.status, 0)
class TestFeatures(JailPyHelpers, unittest.TestCase): class TestFeatures(JailCodeHelpers, unittest.TestCase):
def test_hello_world(self): def test_hello_world(self):
res = jailpy("print 'Hello, world!'") res = jailpy("print 'Hello, world!'")
self.assertResultOk(res) self.assertResultOk(res)
...@@ -64,7 +68,7 @@ class TestFeatures(JailPyHelpers, unittest.TestCase): ...@@ -64,7 +68,7 @@ class TestFeatures(JailPyHelpers, unittest.TestCase):
self.assertEqual(res.stdout, 'Look: Hello there.\n\n') self.assertEqual(res.stdout, 'Look: Hello there.\n\n')
class TestLimits(JailPyHelpers, 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("print sum(range(100000000))")
self.assertNotEqual(res.status, 0) self.assertNotEqual(res.status, 0)
...@@ -114,7 +118,7 @@ class TestLimits(JailPyHelpers, unittest.TestCase): ...@@ -114,7 +118,7 @@ class TestLimits(JailPyHelpers, unittest.TestCase):
# TODO: fork # TODO: fork
class TestMalware(JailPyHelpers, 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(dedent("""\
......
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