Commit 19e3a0ce by Ned Batchelder

Implement safe_exec on top of jailpy (old unsafe safe_exec is still here);…

Implement safe_exec on top of jailpy (old unsafe safe_exec is still here); Remove some crazy stuff from the context; always pass globals and locals, locals are the things that can be changed.
parent 908f44b9
...@@ -440,9 +440,6 @@ class LoncapaProblem(object): ...@@ -440,9 +440,6 @@ class LoncapaProblem(object):
random.seed(self.seed) random.seed(self.seed)
context = {} context = {}
# pass instance of LoncapaProblem in
context['the_lcp'] = self
context['script_code'] = '' context['script_code'] = ''
self._execute_scripts(tree.findall('.//script'), context) self._execute_scripts(tree.findall('.//script'), context)
...@@ -473,7 +470,9 @@ class LoncapaProblem(object): ...@@ -473,7 +470,9 @@ class LoncapaProblem(object):
context['script_code'] += code context['script_code'] += code
try: try:
# use "context" for global context; thus defs in code are global within code # use "context" for global context; thus defs in code are global within code
safe_exec.safe_exec(code, context) locals_dict = {}
safe_exec.safe_exec(code, context, locals_dict)
context.update(locals_dict)
except Exception as err: except Exception as err:
log.exception("Error while execing script code: " + code) log.exception("Error while execing script code: " + code)
msg = "Error while executing script code: %s" % str(err).replace('<', '&lt;') msg = "Error while executing script code: %s" % str(err).replace('<', '&lt;')
......
...@@ -934,8 +934,9 @@ class CustomResponse(LoncapaResponse): ...@@ -934,8 +934,9 @@ class CustomResponse(LoncapaResponse):
'expect': expect, 'expect': expect,
'ans': ans, 'ans': ans,
} }
safe_exec.safe_exec(code, globals_dict) locals_dict = {}
return globals_dict['cfn_return'] safe_exec.safe_exec(code, globals_dict, locals_dict)
return locals_dict['cfn_return']
return check_function return check_function
self.code = make_check_function(self.context['script_code'], cfn) self.code = make_check_function(self.context['script_code'], cfn)
...@@ -995,9 +996,6 @@ class CustomResponse(LoncapaResponse): ...@@ -995,9 +996,6 @@ class CustomResponse(LoncapaResponse):
# put these in the context of the check function evaluator # put these in the context of the check function evaluator
# note that this doesn't help the "cfn" version - only the exec version # note that this doesn't help the "cfn" version - only the exec version
self.context.update({ self.context.update({
# our subtree
'xml': self.xml,
# my ID # my ID
'response_id': self.myid, 'response_id': self.myid,
...@@ -1037,7 +1035,9 @@ class CustomResponse(LoncapaResponse): ...@@ -1037,7 +1035,9 @@ class CustomResponse(LoncapaResponse):
# exec the check function # exec the check function
if isinstance(self.code, basestring): if isinstance(self.code, basestring):
try: try:
safe_exec.safe_exec(self.code, self.context) locals_dict = {}
safe_exec.safe_exec(self.code, self.context, locals_dict)
self.context.update(locals_dict)
correct = self.context['correct'] correct = self.context['correct']
messages = self.context['messages'] messages = self.context['messages']
overall_message = self.context['overall_message'] overall_message = self.context['overall_message']
...@@ -1754,10 +1754,10 @@ class SchematicResponse(LoncapaResponse): ...@@ -1754,10 +1754,10 @@ class SchematicResponse(LoncapaResponse):
json.loads(student_answers[k]) for k in sorted(self.answer_ids) json.loads(student_answers[k]) for k in sorted(self.answer_ids)
] ]
self.context.update({'submission': submission}) self.context.update({'submission': submission})
safe_exec.safe_exec(self.code, {}, self.context) locals_dict = {}
safe_exec.safe_exec(self.code, self.context, locals_dict)
cmap = CorrectMap() cmap = CorrectMap()
cmap.set_dict(dict(zip(sorted( cmap.set_dict(dict(zip(sorted(self.answer_ids), locals_dict['correct'])))
self.answer_ids), self.context['correct'])))
return cmap return cmap
def get_answers(self): def get_answers(self):
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import codejail.safe_exec import codejail.safe_exec
def safe_exec(code, globals_dict, locals_dict=None): def safe_exec(code, globals_dict, locals_dict):
codejail.safe_exec.safe_exec( codejail.safe_exec.safe_exec(
code, globals_dict, locals_dict, future_division=True, code, globals_dict, locals_dict, future_division=True,
assumed_imports=[ assumed_imports=[
......
"""Safe execution of untrusted Python code.""" """Safe execution of untrusted Python code."""
import json import json
import textwrap
from .lazymod import LazyModule import lazymod
import jailpy
def straw(v): # If we aren't running safe, then we need to artificially pass the values
return json.loads(json.dumps(jsonable_dict(v))) # through a JSON straw to ensure we aren't passing something that won't
# be executable in the safe context.
def jsonable_dict(d): def straw(d):
jd = {} jd = {}
for k,v in d.iteritems(): for k,v in d.iteritems():
try: try:
...@@ -16,9 +18,9 @@ def jsonable_dict(d): ...@@ -16,9 +18,9 @@ def jsonable_dict(d):
continue continue
else: else:
jd[k] = v jd[k] = v
return jd return json.loads(json.dumps(jd))
def safe_exec(code, globals_dict, locals_dict=None, future_division=False, assumed_imports=None): def safe_exec(code, globals_dict, locals_dict, future_division=False, assumed_imports=None):
"""Execute code safely. """Execute code safely.
Returns None. The code can modify globals in `global_dict`. Returns None. The code can modify globals in `global_dict`.
...@@ -28,21 +30,57 @@ def safe_exec(code, globals_dict, locals_dict=None, future_division=False, assum ...@@ -28,21 +30,57 @@ def safe_exec(code, globals_dict, locals_dict=None, future_division=False, assum
code = "from __future__ import division\n" + code code = "from __future__ import division\n" + code
g_dict = straw(globals_dict) g_dict = straw(globals_dict)
l_dict = straw(locals_dict)
if locals_dict is None:
l_dict = g_dict
else:
l_dict = straw(locals_dict)
for modname in assumed_imports or (): for modname in assumed_imports or ():
if isinstance(modname, tuple): if isinstance(modname, tuple):
name, modname = modname name, modname = modname
else: else:
name = modname name = modname
g_dict[name] = LazyModule(modname) g_dict[name] = lazymod.LazyModule(modname)
exec code in g_dict, l_dict exec code in g_dict, l_dict
globals_dict.update(straw(g_dict)) globals_dict.update(straw(g_dict))
if locals_dict is not None: locals_dict.update(straw(l_dict))
locals_dict.update(straw(l_dict))
# 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()
def xxxsafe_exec(code, globals_dict, locals_dict, future_division=False, assumed_imports=None):
the_code = []
the_code.append(textwrap.dedent("""\
import json
import sys
code, g_dict, l_dict = json.load(sys.stdin)
"""))
if assumed_imports:
the_code.append(lazymod_py)
for modname in assumed_imports:
if isinstance(modname, tuple):
name, modname = modname
else:
name = modname
the_code.append("g_dict['{}'] = LazyModule('{}')\n".format(name, modname))
the_code.append(textwrap.dedent("""\
exec code in g_dict, l_dict
print >>sys.stderr, l_dict.keys()
ok_types = (int, long, float, str, unicode, list, tuple, dict)
l_dict = {k:v for k,v in l_dict.iteritems() if isinstance(v, ok_types)}
json.dump(l_dict, sys.stdout)
"""))
print "".join(the_code)
stdin = json.dumps([code, globals_dict, locals_dict])
res = jailpy.jailpy("".join(the_code), stdin=stdin)
if res.status != 0:
raise Exception("Couldn't excecute jailed code: %s" % res.stderr)
locals_dict.update(json.loads(res.stdout))
...@@ -33,6 +33,13 @@ class TestFeatures(unittest.TestCase): ...@@ -33,6 +33,13 @@ class TestFeatures(unittest.TestCase):
Exception: FAIL Exception: FAIL
""")) """))
def test_stdin_is_provided(self):
res = jailpy(
"import json,sys; print sum(json.load(sys.stdin))",
stdin="[1, 2.5, 33]"
)
self.assertEqual(res.stdout.strip(), "36.5")
class TestLimits(unittest.TestCase): class TestLimits(unittest.TestCase):
def test_cant_use_too_much_memory(self): def test_cant_use_too_much_memory(self):
......
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