diff --git a/common/lib/capa/capa/capa_problem.py b/common/lib/capa/capa/capa_problem.py index 5518da8318..202d197428 100644 --- a/common/lib/capa/capa/capa_problem.py +++ b/common/lib/capa/capa/capa_problem.py @@ -440,9 +440,6 @@ class LoncapaProblem(object): random.seed(self.seed) context = {} - - # pass instance of LoncapaProblem in - context['the_lcp'] = self context['script_code'] = '' self._execute_scripts(tree.findall('.//script'), context) @@ -473,7 +470,9 @@ class LoncapaProblem(object): context['script_code'] += code try: # 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: log.exception("Error while execing script code: " + code) msg = "Error while executing script code: %s" % str(err).replace('<', '<') diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 3aabc4adbd..1471093a98 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -934,8 +934,9 @@ class CustomResponse(LoncapaResponse): 'expect': expect, 'ans': ans, } - safe_exec.safe_exec(code, globals_dict) - return globals_dict['cfn_return'] + locals_dict = {} + safe_exec.safe_exec(code, globals_dict, locals_dict) + return locals_dict['cfn_return'] return check_function self.code = make_check_function(self.context['script_code'], cfn) @@ -995,9 +996,6 @@ class CustomResponse(LoncapaResponse): # put these in the context of the check function evaluator # note that this doesn't help the "cfn" version - only the exec version self.context.update({ - # our subtree - 'xml': self.xml, - # my ID 'response_id': self.myid, @@ -1037,7 +1035,9 @@ class CustomResponse(LoncapaResponse): # exec the check function if isinstance(self.code, basestring): 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'] messages = self.context['messages'] overall_message = self.context['overall_message'] @@ -1754,10 +1754,10 @@ class SchematicResponse(LoncapaResponse): json.loads(student_answers[k]) for k in sorted(self.answer_ids) ] 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.set_dict(dict(zip(sorted( - self.answer_ids), self.context['correct']))) + cmap.set_dict(dict(zip(sorted(self.answer_ids), locals_dict['correct']))) return cmap def get_answers(self): diff --git a/common/lib/capa/capa/safe_exec.py b/common/lib/capa/capa/safe_exec.py index 0f57ece529..e6fa0e7bb5 100644 --- a/common/lib/capa/capa/safe_exec.py +++ b/common/lib/capa/capa/safe_exec.py @@ -2,7 +2,7 @@ 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( code, globals_dict, locals_dict, future_division=True, assumed_imports=[ diff --git a/common/lib/codejail/codejail/safe_exec.py b/common/lib/codejail/codejail/safe_exec.py index 92beba98cc..9de8398030 100644 --- a/common/lib/codejail/codejail/safe_exec.py +++ b/common/lib/codejail/codejail/safe_exec.py @@ -1,13 +1,15 @@ """Safe execution of untrusted Python code.""" import json +import textwrap -from .lazymod import LazyModule +import lazymod +import jailpy -def straw(v): - return json.loads(json.dumps(jsonable_dict(v))) - -def jsonable_dict(d): +# If we aren't running safe, then we need to artificially pass the values +# through a JSON straw to ensure we aren't passing something that won't +# be executable in the safe context. +def straw(d): jd = {} for k,v in d.iteritems(): try: @@ -16,9 +18,9 @@ def jsonable_dict(d): continue else: 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. 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 code = "from __future__ import division\n" + code g_dict = straw(globals_dict) - - if locals_dict is None: - l_dict = g_dict - else: - l_dict = straw(locals_dict) + l_dict = straw(locals_dict) for modname in assumed_imports or (): if isinstance(modname, tuple): name, modname = modname else: name = modname - g_dict[name] = LazyModule(modname) + g_dict[name] = lazymod.LazyModule(modname) exec code in g_dict, l_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)) diff --git a/common/lib/codejail/codejail/tests/test_jailpy.py b/common/lib/codejail/codejail/tests/test_jailpy.py index d1133d745d..a04bf3bf0f 100644 --- a/common/lib/codejail/codejail/tests/test_jailpy.py +++ b/common/lib/codejail/codejail/tests/test_jailpy.py @@ -33,6 +33,13 @@ class TestFeatures(unittest.TestCase): 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): def test_cant_use_too_much_memory(self):