def execute_python(code, stdin='', explain=False): code = bytes(code, 'utf-8') stdin = bytes(stdin, 'utf-8') this_dir = dirname(__file__) with open(this_dir + '/script.py', 'rb') as script_file: script = script_file.read() jail_res = jail_code( 'python', script, files=[ this_dir + '/execplainator.py', this_dir + '/execplainator_encoder.py' ], extra_files=[ ('code.py', code), ('stdin.txt', stdin), ], stdin=bytes( dumps({ # options 'trace': explain, }), 'utf-8'), ) return ExecuteResult(jail_res)
def _run(self, grader_path, thecode, seed): files = SUPPORT_FILES + [grader_path] if self.locale_dir.exists(): files.append(self.locale_dir) extra_files = [('submission.py', thecode.encode('utf-8'))] argv = ["-m", "grader_support.run", path(grader_path).basename(), 'submission.py', seed] r = jail_code(self.codejail_python, files=files, extra_files=extra_files, argv=argv) return r
def _run(self, grader_path, thecode, seed): files = SUPPORT_FILES + [grader_path] if self.locale_dir.exists(): files.append(self.locale_dir) extra_files = [('submission.py', thecode.encode('utf-8'))] argv = [ "-m", "grader_support.run", path(grader_path).basename(), 'submission.py', seed ] r = jail_code(self.codejail_python, files=files, extra_files=extra_files, argv=argv) return r
def run(self, grader_path, tests, grabber, app, seed): files = SUPPORT_FILES + [grader_path] if self.locale_dir.exists(): files.append(self.locale_dir) extra_files = [('submission.py', tests.encode('utf-8')), ('grabber.py', grabber.encode('utf-8')), ('flask_app.py', app.encode('utf-8'))] argv = [ "-m", "grader_support.run", path(grader_path).basename(), 'submission.py', seed ] #self.log.warning("\nOne: {0}\nFiles: {1}\nExtra: {2}\nArgv: {3}".format(self.codejail_python, files, extra_files, argv)) r = jail_code(self.codejail_python, files=files, extra_files=extra_files, argv=argv) return r
def execute_python(code, stdin='', explain=False): this_dir = dirname(__file__) with open(this_dir + '/script.py', 'r') as script_file: script = script_file.read() jail_res = jail_code( 'python', script, files = [this_dir + '/execplainator.py', this_dir + '/execplainator_encoder.py'], extra_files = [ ('code.py', code), ('stdin.txt', stdin), ], stdin = bytes(dumps({ # options 'trace': explain, }), 'utf-8'), ) return ExecuteResult(jail_res)
def save(self): program = self.cleaned_data['program'].read().decode('UTF-8') zipfile = ZipFile(self.cleaned_data['zipfile']) for name in zipfile.namelist(): prefix = '.'.join(name.split('.')[:-1]) zipfile.extract(name, 'media/zips') # potential race condition inputfile = zipfile.open(name) output = jail_code('python3', program, stdin=inputfile.read()).stdout outfile = open('media/zips/testout', 'w') outfile.write(output.decode('UTF-8')) outfile.close() TestCase( name=prefix, task=self.cleaned_data['task'], infile=InMemoryUploadedFile(open('media/zips/' + name), 'infile', name, 'text/plain', len(inputfile.read()), None), outfile=InMemoryUploadedFile(open('media/zips/testout'), 'outfile', prefix + '.out', 'text/plain', len(output), None), hidden=False, # TODO: make user able to change this order=0, # TODO: autoincrement this ).save()
def test_installed(self): self.assertEqual( jail_code('python3', 'print("Hello,", input())', stdin=b'Matt\n').stdout, b'Hello, Matt\n' )
def safe_exec(code, globals_dict, files=None, python_path=None, slug=None): """ Execute code as "exec" does, but safely. `code` is a string of Python code. `globals_dict` is used as the globals during execution. Modifications the code makes to `globals_dict` are reflected in the dictionary on return. `files` is a list of file paths, either files or directories. They will be copied into the temp directory used for execution. No attempt is made to determine whether the file is appropriate or safe to copy. The caller must determine which files to provide to the code. `python_path` is a list of directory paths. They will be copied just as `files` are, but will also be added to `sys.path` so that modules there can be imported. `slug` is an arbitrary string, a description that's meaningful to the caller, that will be used in log messages. Returns None. Changes made by `code` are visible in `globals_dict`. If the code raises an exception, this function will raise `SafeExecException` with the stderr of the sandbox process, which usually includes the original exception message and traceback. """ the_code = [] files = list(files or ()) the_code.append(textwrap.dedent( """ import sys try: import simplejson as json except ImportError: import json """ # We need to prevent the sandboxed code from printing to stdout, # or it will pollute the json we print there. This isn't a # security concern (they can put any values in the json output # anyway, either by writing to sys.__stdout__, or just by defining # global values), but keeps accidents from happening. """ class DevNull(object): def write(self, *args, **kwargs): pass sys.stdout = DevNull() """ # Read the code and the globals from the stdin. """ code, g_dict = json.load(sys.stdin) """)) for pydir in python_path or (): pybase = os.path.basename(pydir) the_code.append("sys.path.append(%r)\n" % pybase) files.append(pydir) the_code.append(textwrap.dedent( # Execute the sandboxed code. """ exec code in g_dict """ # Clean the globals for sending back as JSON over stdout. """ ok_types = ( type(None), int, long, float, str, unicode, list, tuple, dict ) bad_keys = ("__builtins__",) def jsonable(v): if not isinstance(v, ok_types): return False try: json.dumps(v) except Exception: return False return True g_dict = { k:v for k,v in g_dict.iteritems() if jsonable(v) and k not in bad_keys } """ # Write the globals back to the calling process. """ json.dump(g_dict, sys.__stdout__) """)) stdin = json.dumps([code, json_safe(globals_dict)]) jailed_code = "".join(the_code) # Turn this on to see what's being executed. if LOG_ALL_CODE: # pragma: no cover log.debug("Jailed code: %s", jailed_code) log.debug("Exec: %s", code) log.debug("Stdin: %s", stdin) res = jail_code.jail_code( "python", code=jailed_code, stdin=stdin, files=files, slug=slug, ) if res.status != 0: raise SafeExecException( "Couldn't execute jailed code: %s" % res.stderr ) globals_dict.update(json.loads(res.stdout))
def jailpy(code=None, *args, **kwargs): """Run `jail_code` on Python.""" if code: code = textwrap.dedent(code) return jail_code("python", code, *args, **kwargs)
stdin_contents = '' with open(sys.argv[6], "r") as f: stdin_contents = f.read() output_file = sys.argv[8] print "Output file: %s" % output_file unit_test_code = None if len(sys.argv) >= 10: with open(sys.argv[9], "r") as f: unit_test_code = f.read() print "Unit test code len: %d" % len(unit_test_code) jail_code.configure('python', python_path, sandbox_user) jail_code.set_limit('CPU', max_cpu) jail_code.set_limit('VMEM', max_memory) jail_code.set_limit('REALTIME', max_real_time) result = jail_code.jail_code('python', source_code, \ None, None, stdin_contents, "codemarathon", unit_test_code) cpu_time = result.res_data.ru_utime + result.res_data.ru_stime memory_usage = result.res_data.ru_maxrss * resource.getpagesize() / 1024 status = result.status error_message = result.stderr with open(output_file, "w") as f: f.write(result.stdout) with open("stat", "w") as f: f.write("Time: %f\n" % cpu_time) f.write("Memory: %d\n" % memory_usage) f.write("Status: %d\n" % status) f.write( "==== STDERR contents BEGIN ====\n%s\n==== STDERR contents END ====\n"
def safe_exec( code, globals_dict, files=None, python_path=None, limit_overrides_context=None, slug=None, extra_files=None, ): """ Execute code as "exec" does, but safely. `code` is a string of Python code. `globals_dict` is used as the globals during execution. Modifications the code makes to `globals_dict` are reflected in the dictionary on return. `files` is a list of file paths, either files or directories. They will be copied into the temp directory used for execution. No attempt is made to determine whether the file is appropriate or safe to copy. The caller must determine which files to provide to the code. `python_path` is a list of directory or file paths. These names will be added to `sys.path` so that modules they contain can be imported. Only directories and zip files are supported. If the name is not provided in `extras_files`, it will be copied just as if it had been listed in `files`. `limit_overrides_context` is an optional string to use as a key against the configured limit overrides contexts. If omitted or if no such limit override context has been configured, then use the default limits. `slug` is an arbitrary string, a description that's meaningful to the caller, that will be used in log messages. `extra_files` is a list of pairs, each pair is a filename and a bytestring of contents to write into that file. These files will be created in the temp directory and cleaned up automatically. No subdirectories are supported in the filename. Returns None. Changes made by `code` are visible in `globals_dict`. If the code raises an exception, this function will raise `SafeExecException` with the stderr of the sandbox process, which usually includes the original exception message and traceback. """ the_code = [] files = list(files or ()) extra_files = extra_files or () python_path = python_path or () extra_names = set(name for name, contents in extra_files) the_code.append( textwrap.dedent( """ import sys import six try: import simplejson as json except ImportError: import json """ # We need to prevent the sandboxed code from printing to stdout, # or it will pollute the json we print there. This isn't a # security concern (they can put any values in the json output # anyway, either by writing to sys.__stdout__, or just by defining # global values), but keeps accidents from happening. """ class DevNull(object): def write(self, *args, **kwargs): pass def flush(self, *args, **kwargs): pass sys.stdout = DevNull() """ # Read the code and the globals from the stdin. """ code, g_dict = json.load(sys.stdin) """)) for pydir in python_path: pybase = os.path.basename(pydir) the_code.append("sys.path.append(%r)\n" % pybase) if pybase not in extra_names: files.append(pydir) the_code.append( textwrap.dedent( # Execute the sandboxed code. """ exec(code, g_dict) """)) the_code.append(inspect.getsource(json_safe)) the_code.append( textwrap.dedent(""" g_dict = json_safe(g_dict) """ # Write the globals back to the calling process. """ json.dump(g_dict, sys.__stdout__) """)) stdin = json.dumps([code, json_safe(globals_dict)]) jailed_code = "".join(the_code) # Turn this on to see what's being executed. if LOG_ALL_CODE: # pragma: no cover log.debug("Jailed code: %s", jailed_code) log.debug("Exec: %s", code) log.debug("Stdin: %s", stdin) res = jail_code.jail_code( "python", code=jailed_code, stdin=stdin, files=files, limit_overrides_context=limit_overrides_context, slug=slug, extra_files=extra_files, ) if LOG_ALL_CODE: log.debug("Status: %s", res.status) log.debug("Stdout: %s", res.stdout) log.debug("Stderr: %s", res.stderr) if res.status != 0: raise SafeExecException( ("Couldn't execute jailed code: stdout: {res.stdout!r}, " "stderr: {res.stderr!r} with status code: {res.status}").format( res=res)) globals_dict.update(json.loads(res.stdout.decode('utf-8')))
def test_jail_code_functional_invocation(self): result = jail_code('python3', 'print("Huzzah")') self.assertEqual(result, JailResult(status=0, stdout='Huzzah\n', stderr=''))
def jailpy(code=None, *args, **kwargs): # pylint: disable=keyword-arg-before-vararg """Run `jail_code` on Python.""" if code: code = textwrap.dedent(code) return jail_code("python", code, *args, **kwargs)
def safe_exec(code, globals_dict, files=None, python_path=None, slug=None, extra_files=None, settings_code=None, extra_imports=None, initial_code=None): """ Execute code as "exec" does, but safely. `code` is a string of Python code. `globals_dict` is used as the globals during execution. Modifications the code makes to `globals_dict` are reflected in the dictionary on return. `files` is a list of file paths, either files or directories. They will be copied into the temp directory used for execution. No attempt is made to determine whether the file is appropriate or safe to copy. The caller must determine which files to provide to the code. `python_path` is a list of directory or file paths. These names will be added to `sys.path` so that modules they contain can be imported. Only directories and zip files are supported. If the name is not provided in `extras_files`, it will be copied just as if it had been listed in `files`. `slug` is an arbitrary string, a description that's meaningful to the caller, that will be used in log messages. `extra_files` is a list of pairs, each pair is a filename and a bytestring of contents to write into that file. These files will be created in the temp directory and cleaned up automatically. No subdirectories are supported in the filename. Returns None. Changes made by `code` are visible in `globals_dict`. If the code raises an exception, this function will raise `SafeExecException` with the stderr of the sandbox process, which usually includes the original exception message and traceback. """ the_code = [] files = list(files or ()) extra_files = extra_files or () python_path = python_path or () extra_names = set(name for name, contents in extra_files) if isinstance(extra_imports, str) and len(extra_imports) > 0: the_code.append(textwrap.dedent(extra_imports).strip()) if isinstance(settings_code, str) and len(settings_code) > 0: the_code.append(textwrap.dedent(settings_code).strip()) the_code.append(textwrap.dedent( """ import sys, numpy, pandas, json, math, pickle, random, traceback, io, matplotlib;matplotlib.use('svg') """ # Read the code and the globals from the stdin. """ data = sys.stdin.buffer.read() initial_code, code, g_dict = pickle.loads(data) if type(g_dict) == str: g_dict = pickle.loads(g_dict) """)) for pydir in python_path: pybase = os.path.basename(pydir) the_code.append("sys.path.append(%r)\n" % pybase) if pybase not in extra_names: files.append(pydir) the_code.append(textwrap.dedent( # Execute the sandboxed code. """ try: random.seed(0);numpy.random.seed(0) exec(initial_code, g_dict) except Exception as err: try: print(traceback.format_exc(0).encode("ascii", "ignore")) except UnicodeEncodeError: print("Unknown Error") raise err try: import matplotlib.pyplot as plt;plt.clf();random.seed(0);numpy.random.seed(0) exec(code, g_dict) except Exception as err: try: print(traceback.format_exc(0).encode("ascii", "ignore")) except UnicodeEncodeError: print("Unknown Error") raise err """ # Clean the globals for sending back as JSON over stdout. """ answer_key = "{0}{1}".format(SANDBOX_CORRECT_PREFIX,"correct_context") check_func_key = "{0}{1}".format(SANDBOX_CORRECT_PREFIX, "check_code_run") check_status_key = "{0}{1}".format(SANDBOX_CORRECT_PREFIX, "check_status") okay_list = [check_status_key] if g_dict[SANDBOX_CHECK_VARS_NAME] is not None: okay_list += g_dict[SANDBOX_CHECK_VARS_NAME] bad_keys = ("__builtins__", SANDBOX_CHECK_VARS_NAME, answer_key, check_func_key, check_status_key) def pickleable(v): try: pickle.dumps(v, protocol=pickle.HIGHEST_PROTOCOL) except Exception: return False return True if ANSWER_MODE: p_dict = { k:v for k,v in g_dict.items() if pickleable(v) and k in okay_list } else: p_dict = { k:v for k,v in g_dict.items() if pickleable(v) and k not in bad_keys and not k.startswith(SANDBOX_CORRECT_PREFIX) } def jsonable(v): try: val = json.dumps(v) except Exception: return False if len(val) > 100: return False return True def stringable(v): try: m = str(v) except Exception: return False if len(m) > 0 and len(m) < 100: return True return False def typeable(v): try: m = str(type(v)) except Exception: return False return True def jsonable_nolength(v): try: val = json.dumps(v) except Exception: return False return True def stringable_nolength(v): try: m = str(v) except Exception: return False return True v_dict = {} for k, v in g_dict.items(): if k in bad_keys or k.startswith(SANDBOX_CORRECT_PREFIX): continue elif k.startswith(DATAQUEST_PLOT_PREFIX): v_dict[k] = v.getvalue() elif v is None or (isinstance(v, float) and math.isnan(v)): v_dict[k] = "None" elif jsonable(v): v_dict[k] = v elif stringable(v): v_dict[k] = str(v) elif typeable(v): v_dict[k] = str(type(v)) def make_real_var(k, v, bad_keys): if k in bad_keys or k.startswith(SANDBOX_CORRECT_PREFIX): return None elif isinstance(v, io.StringIO): return v.getvalue() elif jsonable_nolength(v): return v elif stringable_nolength(v): return str(v) elif typeable(v): return str(type(v)) return None real_vars = {} for k, v in g_dict.items(): val = make_real_var(k, v, bad_keys) if val is not None: real_vars[k] = val plots = [] for var in g_dict[SANDBOX_CHECK_VARS_NAME]: if var.startswith(DATAQUEST_PLOT_PREFIX): given_var = g_dict.get(var) if given_var is not None: plots.append(given_var.getvalue().strip()) check_status = False if g_dict.get(check_func_key) is not None: exec(g_dict.get(check_func_key), g_dict) check_status, hint = g_dict.get(check_status_key) incorrect_vars = {} correct_vars = {} if answer_key in g_dict: answer_g_dict = g_dict[answer_key] if type(answer_g_dict) == str: try: answer_g_dict = pickle.loads(answer_g_dict) except Exception: answer_g_dict = pickle.loads(eval(answer_g_dict)) for var in g_dict[SANDBOX_CHECK_VARS_NAME]: given_var = g_dict.get(var) correct_var = answer_g_dict.get(var) val = make_real_var(var, correct_var, []) if val is not None: correct_vars[var] = val variable_okay = given_var is not None and type(given_var) == type(correct_var) if isinstance(given_var, io.StringIO) and isinstance(correct_var, str): variable_okay = True if variable_okay: if isinstance(correct_var, numpy.ndarray): equal = numpy.array_equal(given_var, correct_var) elif isinstance(correct_var, pandas.DataFrame) or isinstance(correct_var, pandas.Series): equal = given_var.equals(correct_var) elif var.startswith(DATAQUEST_PLOT_PREFIX): if isinstance(correct_var, io.StringIO): new_val = correct_var.getvalue().strip() else: new_val = correct_var.strip() equal = False for p in plots: similarity = 0 if len(p) != len(new_val): continue try: for i, s in enumerate(p): if s == new_val[i]: similarity += 1 if similarity/len(new_val) > .7: equal = True except Exception: pass elif isinstance(correct_var, float): # .sum() methods on numpy arrays vs the builtin sum function, among others, can have slight rounding differences. # Adding this tolerance helps ensure those don't get flagged as incorrect. equal = (correct_var - .001) <= given_var <= (correct_var + .001) else: equal = given_var == correct_var variable_okay = variable_okay and equal if not variable_okay: incorrect_vars[var] = { "given_type": str(type(given_var)), "correct_type": str(type(correct_var)) } """ # Write the globals back to the calling process. """ print("PICKLE_DATA:") d = pickle.dumps(p_dict, protocol=2) print(d) print("PICKLE_DATA:") json.dump(v_dict, sys.__stdout__) print("PICKLE_DATA:") json.dump(real_vars, sys.__stdout__) print("PICKLE_DATA:") json.dump(incorrect_vars, sys.__stdout__) print("PICKLE_DATA:") json.dump(correct_vars, sys.__stdout__) print("PICKLE_DATA:") json.dump(check_status, sys.__stdout__) """ )) if initial_code is None: initial_code = "" stdin = pickle.dumps([initial_code, code, globals_dict], protocol=pickle.HIGHEST_PROTOCOL) jailed_code = "".join(the_code) # Turn this on to see what's being executed. if LOG_ALL_CODE: # pragma: no cover log.debug("Jailed code: %s", jailed_code) log.debug("Exec: %s", code) log.debug("Stdin: %s", stdin) res = jail_code.jail_code( "python", code=jailed_code, stdin=stdin, files=files, slug=slug, extra_files=extra_files, ) if res.status != 0: log.error("Couldn't execute jailed code: %s" % res.stderr) output = res.stdout.decode("utf-8") if output is not None: output = output[:100000] display_vars = {} real_vars = {} incorrect_vars = {} correct_vars = {} data = "" error = True check_status = False else: output = res.stdout.decode("utf-8") output, data, display_vars, real_vars, incorrect_vars, correct_vars, check_status = output.split("PICKLE_DATA:\n") display_vars = json.loads(display_vars) real_vars = json.loads(real_vars) incorrect_vars = json.loads(incorrect_vars) correct_vars = json.loads(correct_vars) check_status = json.loads(check_status) error = False globals_dict = {"output": output, "data": data, "display_vars": display_vars, "real_vars": real_vars, "incorrect_vars": incorrect_vars, "error": error, "correct_vars": correct_vars, "check": check_status} return globals_dict
stdin_contents = '' with open(sys.argv[6], "r") as f: stdin_contents = f.read() output_file = sys.argv[8] print "Output file: %s" % output_file unit_test_code = None if len(sys.argv) >= 10: with open(sys.argv[9], "r") as f: unit_test_code = f.read() print "Unit test code len: %d" % len(unit_test_code) jail_code.configure('python', python_path, sandbox_user) jail_code.set_limit('CPU', max_cpu) jail_code.set_limit('VMEM', max_memory) jail_code.set_limit('REALTIME', max_real_time) result = jail_code.jail_code('python', source_code, \ None, None, stdin_contents, "codemarathon", unit_test_code) cpu_time = result.res_data.ru_utime + result.res_data.ru_stime memory_usage = result.res_data.ru_maxrss * resource.getpagesize() / 1024 status = result.status error_message = result.stderr with open(output_file, "w") as f: f.write(result.stdout) with open("stat", "w") as f: f.write("Time: %f\n" % cpu_time) f.write("Memory: %d\n" % memory_usage) f.write("Status: %d\n" % status) f.write("==== STDERR contents BEGIN ====\n%s\n==== STDERR contents END ====\n" % error_message)