Commit cab49716 by Ned Batchelder

Whitelisted courses now run Python code outside the sandbox.

parent 9a631fe4
...@@ -470,6 +470,7 @@ class LoncapaProblem(object): ...@@ -470,6 +470,7 @@ class LoncapaProblem(object):
python_path=python_path, python_path=python_path,
cache=self.system.cache, cache=self.system.cache,
slug=self.problem_id, slug=self.problem_id,
unsafely=self.system.can_execute_unsafe_code(),
) )
except Exception as err: except Exception as err:
log.exception("Error while execing script code: " + all_code) log.exception("Error while execing script code: " + all_code)
......
...@@ -294,6 +294,7 @@ class LoncapaResponse(object): ...@@ -294,6 +294,7 @@ class LoncapaResponse(object):
python_path=self.context['python_path'], python_path=self.context['python_path'],
slug=self.id, slug=self.id,
random_seed=self.context['seed'], random_seed=self.context['seed'],
unsafely=self.system.can_execute_unsafe_code(),
) )
except Exception as err: except Exception as err:
msg = 'Error %s in evaluating hint function %s' % (err, hintfn) msg = 'Error %s in evaluating hint function %s' % (err, hintfn)
...@@ -985,6 +986,7 @@ class CustomResponse(LoncapaResponse): ...@@ -985,6 +986,7 @@ class CustomResponse(LoncapaResponse):
python_path=self.context['python_path'], python_path=self.context['python_path'],
slug=self.id, slug=self.id,
random_seed=self.context['seed'], random_seed=self.context['seed'],
unsafely=self.system.can_execute_unsafe_code(),
) )
return globals_dict['cfn_return'] return globals_dict['cfn_return']
return check_function return check_function
...@@ -1108,6 +1110,7 @@ class CustomResponse(LoncapaResponse): ...@@ -1108,6 +1110,7 @@ class CustomResponse(LoncapaResponse):
cache=self.system.cache, cache=self.system.cache,
slug=self.id, slug=self.id,
random_seed=self.context['seed'], random_seed=self.context['seed'],
unsafely=self.system.can_execute_unsafe_code(),
) )
except Exception as err: except Exception as err:
self._handle_exec_exception(err) self._handle_exec_exception(err)
...@@ -1838,6 +1841,7 @@ class SchematicResponse(LoncapaResponse): ...@@ -1838,6 +1841,7 @@ class SchematicResponse(LoncapaResponse):
cache=self.system.cache, cache=self.system.cache,
slug=self.id, slug=self.id,
random_seed=self.context['seed'], random_seed=self.context['seed'],
unsafely=self.system.can_execute_unsafe_code(),
) )
except Exception as err: except Exception as err:
msg = 'Error %s in evaluating SchematicResponse' % err msg = 'Error %s in evaluating SchematicResponse' % err
......
"""Capa's specialized use of codejail.safe_exec.""" """Capa's specialized use of codejail.safe_exec."""
from codejail.safe_exec import safe_exec as codejail_safe_exec from codejail.safe_exec import safe_exec as codejail_safe_exec
from codejail.safe_exec import not_safe_exec as codejail_not_safe_exec
from codejail.safe_exec import json_safe, SafeExecException from codejail.safe_exec import json_safe, SafeExecException
from . import lazymod from . import lazymod
from statsd import statsd from statsd import statsd
...@@ -71,7 +72,7 @@ def update_hash(hasher, obj): ...@@ -71,7 +72,7 @@ def update_hash(hasher, obj):
@statsd.timed('capa.safe_exec.time') @statsd.timed('capa.safe_exec.time')
def safe_exec(code, globals_dict, random_seed=None, python_path=None, cache=None, slug=None): def safe_exec(code, globals_dict, random_seed=None, python_path=None, cache=None, slug=None, unsafely=False):
""" """
Execute python code safely. Execute python code safely.
...@@ -90,6 +91,8 @@ def safe_exec(code, globals_dict, random_seed=None, python_path=None, cache=None ...@@ -90,6 +91,8 @@ def safe_exec(code, globals_dict, random_seed=None, python_path=None, cache=None
`slug` is an arbitrary string, a description that's meaningful to the `slug` is an arbitrary string, a description that's meaningful to the
caller, that will be used in log messages. caller, that will be used in log messages.
If `unsafely` is true, then the code will actually be executed without sandboxing.
""" """
# Check the cache for a previous result. # Check the cache for a previous result.
if cache: if cache:
...@@ -111,9 +114,15 @@ def safe_exec(code, globals_dict, random_seed=None, python_path=None, cache=None ...@@ -111,9 +114,15 @@ def safe_exec(code, globals_dict, random_seed=None, python_path=None, cache=None
# Create the complete code we'll run. # Create the complete code we'll run.
code_prolog = CODE_PROLOG % random_seed code_prolog = CODE_PROLOG % random_seed
# Decide which code executor to use.
if unsafely:
exec_fn = codejail_not_safe_exec
else:
exec_fn = codejail_safe_exec
# Run the code! Results are side effects in globals_dict. # Run the code! Results are side effects in globals_dict.
try: try:
codejail_safe_exec( exec_fn(
code_prolog + LAZY_IMPORTS + code, globals_dict, code_prolog + LAZY_IMPORTS + code, globals_dict,
python_path=python_path, slug=slug, python_path=python_path, slug=slug,
) )
......
"""Test safe_exec.py""" """Test safe_exec.py"""
import hashlib import hashlib
import os
import os.path import os.path
import random import random
import textwrap import textwrap
import unittest import unittest
from nose.plugins.skip import SkipTest
from capa.safe_exec import safe_exec, update_hash from capa.safe_exec import safe_exec, update_hash
from codejail.safe_exec import SafeExecException from codejail.safe_exec import SafeExecException
from codejail.jail_code import is_configured
class TestSafeExec(unittest.TestCase): class TestSafeExec(unittest.TestCase):
...@@ -68,6 +72,24 @@ class TestSafeExec(unittest.TestCase): ...@@ -68,6 +72,24 @@ class TestSafeExec(unittest.TestCase):
self.assertIn("ZeroDivisionError", cm.exception.message) self.assertIn("ZeroDivisionError", cm.exception.message)
class TestSafeOrNot(unittest.TestCase):
def test_cant_do_something_forbidden(self):
# Can't test for forbiddenness if CodeJail isn't configured for python.
if not is_configured("python"):
raise SkipTest
g = {}
with self.assertRaises(SafeExecException) as cm:
safe_exec("import os; files = os.listdir('/')", g)
self.assertIn("OSError", cm.exception.message)
self.assertIn("Permission denied", cm.exception.message)
def test_can_do_something_forbidden_if_run_unsafely(self):
g = {}
safe_exec("import os; files = os.listdir('/')", g, unsafely=True)
self.assertEqual(g['files'], os.listdir('/'))
class DictCache(object): class DictCache(object):
"""A cache implementation over a simple dict, for testing.""" """A cache implementation over a simple dict, for testing."""
......
...@@ -9,5 +9,5 @@ ...@@ -9,5 +9,5 @@
# Our libraries: # Our libraries:
-e git+https://github.com/edx/XBlock.git@2144a25d#egg=XBlock -e git+https://github.com/edx/XBlock.git@2144a25d#egg=XBlock
-e git+https://github.com/edx/codejail.git@5fb5fa0#egg=codejail -e git+https://github.com/edx/codejail.git@0a1b468#egg=codejail
-e git+https://github.com/edx/diff-cover.git@v0.1.0#egg=diff_cover -e git+https://github.com/edx/diff-cover.git@v0.1.0#egg=diff_cover
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