Commit 0021b0ac by Ned Batchelder

Refactor to move assumed_imports into capa, so that code_jail is more pure.

parent 89f6ef84
"""Capa's specialized use of codejail.safe_exec.""" """Capa's specialized use of codejail.safe_exec."""
import codejail.safe_exec import codejail.safe_exec
from . import lazymod
# Establish the Python environment for Capa. # Establish the Python environment for Capa.
# Capa assumes float-friendly division always. # Capa assumes float-friendly division always.
...@@ -16,23 +17,39 @@ del random_module ...@@ -16,23 +17,39 @@ del random_module
sys.modules['random'] = random sys.modules['random'] = random
""" """
ASSUMED_IMPORTS=[
("numpy", "numpy"),
("math", "math"),
("scipy", "scipy"),
("calc", "calc"),
("eia", "eia"),
("chemcalc", "chem.chemcalc"),
("chemtools", "chem.chemtools"),
("miller", "chem.miller"),
("draganddrop", "verifiers.draganddrop"),
]
# We'll need the code from lazymod.py for use in jailpy, so read it now.
lazymod_py_file = lazymod.__file__
if lazymod_py_file.endswith("c"):
lazymod_py_file = lazymod_py_file[:-1]
lazymod_py = open(lazymod_py_file).read()
LAZY_IMPORTS = [lazymod_py]
for name, modname in ASSUMED_IMPORTS:
LAZY_IMPORTS.append("{} = LazyModule('{}')\n".format(name, modname))
LAZY_IMPORTS = "".join(LAZY_IMPORTS)
def safe_exec(code, globals_dict, random_seed=None, python_path=None): def safe_exec(code, globals_dict, random_seed=None, python_path=None):
"""Exec python code safely. """Exec python code safely.
""" """
code_prolog = CODE_PROLOG % random_seed code_prolog = CODE_PROLOG % random_seed
codejail.safe_exec.safe_exec( codejail.safe_exec.safe_exec(
code_prolog + code, globals_dict, code_prolog + LAZY_IMPORTS + code, globals_dict,
python_path=python_path, python_path=python_path,
assumed_imports=[
"numpy",
"math",
"scipy",
"calc",
"eia",
("chemcalc", "chem.chemcalc"),
("chemtools", "chem.chemtools"),
("miller", "chem.miller"),
("draganddrop", "verifiers.draganddrop"),
],
) )
...@@ -3,8 +3,26 @@ ...@@ -3,8 +3,26 @@
import sys import sys
import unittest import unittest
from codejail.lazymod import LazyModule from capa.safe_exec.lazymod import LazyModule
from codejail.util import ModuleIsolation
class ModuleIsolation(object):
"""
Manage changes to sys.modules so that we can roll back imported modules.
Create this object, it will snapshot the currently imported modules. When
you call `clean_up()`, it will delete any module imported since its creation.
"""
def __init__(self):
# Save all the names of all the imported modules.
self.mods = set(sys.modules)
def clean_up(self):
# Get a list of modules that didn't exist when we were created
new_mods = [m for m in sys.modules if m not in self.mods]
# and delete them all so another import will run code for real again.
for m in new_mods:
del sys.modules[m]
class TestLazyMod(unittest.TestCase): class TestLazyMod(unittest.TestCase):
......
...@@ -6,40 +6,17 @@ import shutil ...@@ -6,40 +6,17 @@ import shutil
import sys import sys
import textwrap import textwrap
import lazymod
import jailpy import jailpy
from util import temp_directory, change_directory, TempDirectory from util import temp_directory, change_directory
# We'll need the code from lazymod.py for use in jailpy, so read it now. def safe_exec(code, globals_dict, files=None, python_path=None):
lazymod_py_file = lazymod.__file__
if lazymod_py_file.endswith("c"):
lazymod_py_file = lazymod_py_file[:-1]
lazymod_py = open(lazymod_py_file).read()
def names_and_modules(assumed_imports):
"""Get uniform names and modules from assumed_imports."""
for modname in assumed_imports:
if isinstance(modname, tuple):
yield modname
else:
yield modname, modname
def safe_exec(code, globals_dict, assumed_imports=None, files=None, python_path=None):
"""Execute code as "exec" does, but safely. """Execute code as "exec" does, but safely.
`code` is a string of Python code. `globals_dict` is used as the globals `code` is a string of Python code. `globals_dict` is used as the globals
during execution. Modifications the code makes to `globals_dict` are during execution. Modifications the code makes to `globals_dict` are
reflected in the dictionary on return. reflected in the dictionary on return.
`assumed_imports` is a list of modules to make available as implicit
imports for the code. Entries are either a name, "mod", which makes
"import mod" part of the code, or a pair, ("f", "fooey"), which makes
"import fooey as f" part of the code. The module name can be dotted.
Returns None. Changes made by `code` are visible in `globals_dict`. Returns None. Changes made by `code` are visible in `globals_dict`.
""" """
...@@ -72,11 +49,6 @@ def safe_exec(code, globals_dict, assumed_imports=None, files=None, python_path= ...@@ -72,11 +49,6 @@ def safe_exec(code, globals_dict, assumed_imports=None, files=None, python_path=
the_code.append("sys.path.append(%r)\n" % pybase) the_code.append("sys.path.append(%r)\n" % pybase)
files.append(pydir) files.append(pydir)
if assumed_imports:
the_code.append(lazymod_py)
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. # Execute the sandboxed code.
""" """
...@@ -140,7 +112,7 @@ def json_safe(d): ...@@ -140,7 +112,7 @@ def json_safe(d):
return json.loads(json.dumps(jd)) return json.loads(json.dumps(jd))
def not_safe_exec(code, globals_dict, assumed_imports=None, files=None, python_path=None): def not_safe_exec(code, globals_dict, files=None, python_path=None):
"""Another implementation of `safe_exec`, but not safe. """Another implementation of `safe_exec`, but not safe.
This can be swapped in for debugging problems in sandboxed Python code. This can be swapped in for debugging problems in sandboxed Python code.
...@@ -151,9 +123,6 @@ def not_safe_exec(code, globals_dict, assumed_imports=None, files=None, python_p ...@@ -151,9 +123,6 @@ def not_safe_exec(code, globals_dict, assumed_imports=None, files=None, python_p
""" """
g_dict = json_safe(globals_dict) g_dict = json_safe(globals_dict)
for name, modname in names_and_modules(assumed_imports or ()):
g_dict[name] = lazymod.LazyModule(modname)
with temp_directory(delete_when_done=True) as tmpdir: with temp_directory(delete_when_done=True) as tmpdir:
with change_directory(tmpdir): with change_directory(tmpdir):
# Copy the files here. # Copy the files here.
......
...@@ -14,19 +14,6 @@ class SafeExecTests(object): ...@@ -14,19 +14,6 @@ class SafeExecTests(object):
self.safe_exec("a = 17", g) self.safe_exec("a = 17", g)
self.assertEqual(g['a'], 17) self.assertEqual(g['a'], 17)
def test_assumed_imports(self):
g = {}
# Using string without importing it is bad.
with self.assertRaises(Exception):
self.safe_exec("a = string.ascii_lowercase[0]", g)
# Using string with an assumed import is fine.
self.safe_exec("a = string.ascii_lowercase[0]", g, assumed_imports=["string"])
self.assertEqual(g['a'], 'a')
# Can also import with a shorthand.
self.safe_exec("a = op.join('x', 'y')", g, assumed_imports=[("op", "os.path")])
self.assertEqual(g['a'][0], 'x')
self.assertEqual(g['a'][-1], 'y')
def test_files_are_copied(self): def test_files_are_copied(self):
g = {} g = {}
self.safe_exec( self.safe_exec(
......
...@@ -32,25 +32,6 @@ def temp_directory(delete_when_done=True): ...@@ -32,25 +32,6 @@ def temp_directory(delete_when_done=True):
tmp.clean_up() tmp.clean_up()
class ModuleIsolation(object):
"""
Manage changes to sys.modules so that we can roll back imported modules.
Create this object, it will snapshot the currently imported modules. When
you call `clean_up()`, it will delete any module imported since its creation.
"""
def __init__(self):
# Save all the names of all the imported modules.
self.mods = set(sys.modules)
def clean_up(self):
# Get a list of modules that didn't exist when we were created
new_mods = [m for m in sys.modules if m not in self.mods]
# and delete them all so another import will run code for real again.
for m in new_mods:
del sys.modules[m]
class ChangeDirectory(object): class ChangeDirectory(object):
def __init__(self, new_dir): def __init__(self, new_dir):
self.old_dir = os.getcwd() self.old_dir = os.getcwd()
......
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