From bde976dad21757451398ddeba4e42a9a2b2f053f Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Tue, 9 Apr 2013 17:07:57 -0400 Subject: [PATCH] Refactor code_jail to accommodate non-Python code. --- common/lib/capa/capa/safe_exec/safe_exec.py | 2 +- .../codejail/codejail/django_integration.py | 4 +- .../codejail/{jailpy.py => jail_code.py} | 46 +++++++++++-------- common/lib/codejail/codejail/safe_exec.py | 6 +-- .../codejail/codejail/tests/test_jailpy.py | 22 +++++---- 5 files changed, 47 insertions(+), 33 deletions(-) rename common/lib/codejail/codejail/{jailpy.py => jail_code.py} (74%) diff --git a/common/lib/capa/capa/safe_exec/safe_exec.py b/common/lib/capa/capa/safe_exec/safe_exec.py index da74f3aaf5..a07fe359fd 100644 --- a/common/lib/capa/capa/safe_exec/safe_exec.py +++ b/common/lib/capa/capa/safe_exec/safe_exec.py @@ -30,7 +30,7 @@ ASSUMED_IMPORTS=[ ("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__ if lazymod_py_file.endswith("c"): lazymod_py_file = lazymod_py_file[:-1] diff --git a/common/lib/codejail/codejail/django_integration.py b/common/lib/codejail/codejail/django_integration.py index b720ffcff1..56c47f88d9 100644 --- a/common/lib/codejail/codejail/django_integration.py +++ b/common/lib/codejail/codejail/django_integration.py @@ -3,7 +3,7 @@ from django.core.exceptions import MiddlewareNotUsed from django.conf import settings -import codejail.jailpy +import codejail.jail_code class ConfigureCodeJailMiddleware(object): @@ -13,5 +13,5 @@ class ConfigureCodeJailMiddleware(object): python_bin = settings.CODE_JAIL.get('python_bin') if python_bin: user = settings.CODE_JAIL['user'] - codejail.jailpy.configure(python_bin, user=user) + codejail.jail_code.configure("python", python_bin, user=user) raise MiddlewareNotUsed diff --git a/common/lib/codejail/codejail/jailpy.py b/common/lib/codejail/codejail/jail_code.py similarity index 74% rename from common/lib/codejail/codejail/jailpy.py rename to common/lib/codejail/codejail/jail_code.py index df1bd8df12..a44004d585 100644 --- a/common/lib/codejail/codejail/jailpy.py +++ b/common/lib/codejail/codejail/jail_code.py @@ -19,39 +19,49 @@ log = logging.getLogger(__name__) # 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): - """Configure the jailpy module.""" - global PYTHON_CMD - PYTHON_CMD = [] +def configure(command, bin_path, user=None): + """Configure a command for jail_code to use. + + `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: - PYTHON_CMD.extend(['sudo', '-u', 'sandbox']) - PYTHON_CMD.extend([python_bin, '-E']) + cmd_argv.extend(['sudo', '-u', 'sandbox']) + cmd_argv.extend([bin_path, '-E']) + COMMANDS[command] = cmd_argv -def is_configured(): - return bool(PYTHON_CMD) +def is_configured(command): + return command in COMMANDS # By default, look where our current Python is, and maybe there's a # python-sandbox alongside. Only do this if running in a virtualenv. if hasattr(sys, 'real_prefix'): 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): - """A passive object for us to return from jailpy.""" + """A passive object for us to return from jail_code.""" def __init__(self): self.stdout = self.stderr = self.status = None -def jailpy(code, files=None, argv=None, stdin=None): - """ - Run Python code in a jailed subprocess. +def jail_code(command, code, 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. @@ -64,8 +74,8 @@ def jailpy(code, files=None, argv=None, stdin=None): .status: return status of the process: an int, 0 for successful """ - if not PYTHON_CMD: - raise Exception("jailpy needs to be configured") + if not is_configured(command): + raise Exception("jail_code needs to be configured for %r" % command) with temp_directory(delete_when_done=True) as tmpdir: @@ -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: jailed.write(code) - cmd = PYTHON_CMD + ['jailed_code.py'] + (argv or []) + cmd = COMMANDS[command] + ['jailed_code.py'] + (argv or []) 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 682ae809af..5379052ce0 100644 --- a/common/lib/codejail/codejail/safe_exec.py +++ b/common/lib/codejail/codejail/safe_exec.py @@ -7,7 +7,7 @@ import shutil import sys import textwrap -from codejail import jailpy +from codejail import jail_code from codejail.util import temp_directory, change_directory log = logging.getLogger(__name__) @@ -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 = jailpy.jailpy(jailed_code, stdin=stdin, files=files) + res = jail_code.jail_code("python", 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)) @@ -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. # 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 diff --git a/common/lib/codejail/codejail/tests/test_jailpy.py b/common/lib/codejail/codejail/tests/test_jailpy.py index c0d51ba684..395cfc4d53 100644 --- a/common/lib/codejail/codejail/tests/test_jailpy.py +++ b/common/lib/codejail/codejail/tests/test_jailpy.py @@ -1,20 +1,24 @@ -"""Test jailpy.py""" +"""Test jail_code.py""" import os.path import textwrap import unittest 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 -class JailPyHelpers(object): - """Assert helpers for jailpy tests.""" +def jailpy(*args, **kwargs): + return jail_code("python", *args, **kwargs) + + +class JailCodeHelpers(object): + """Assert helpers for jail_code tests.""" def setUp(self): - super(JailPyHelpers, self).setUp() - if not is_configured(): + super(JailCodeHelpers, self).setUp() + if not is_configured("python"): raise SkipTest def assertResultOk(self, res): @@ -22,7 +26,7 @@ class JailPyHelpers(object): self.assertEqual(res.status, 0) -class TestFeatures(JailPyHelpers, unittest.TestCase): +class TestFeatures(JailCodeHelpers, unittest.TestCase): def test_hello_world(self): res = jailpy("print 'Hello, world!'") self.assertResultOk(res) @@ -64,7 +68,7 @@ class TestFeatures(JailPyHelpers, unittest.TestCase): 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): res = jailpy("print sum(range(100000000))") self.assertNotEqual(res.status, 0) @@ -114,7 +118,7 @@ class TestLimits(JailPyHelpers, unittest.TestCase): # TODO: fork -class TestMalware(JailPyHelpers, 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("""\