def not_safe_exec(code, globals_dict, files=None, python_path=None, slug=None): """ Another implementation of `safe_exec`, but not safe. This can be swapped in for debugging problems in sandboxed Python code. This is not thread-safe, due to temporarily changing the current directory and modifying sys.path. """ g_dict = json_safe(globals_dict) with temp_directory() as tmpdir: with change_directory(tmpdir): # Copy the files here. for filename in files or (): dest = os.path.join(tmpdir, os.path.basename(filename)) shutil.copyfile(filename, dest) original_path = sys.path if python_path: sys.path.extend(python_path) try: exec code in g_dict except Exception as e: # Wrap the exception in a SafeExecException, but we don't # try here to include the traceback, since this is just a # substitute implementation. msg = "{0.__class__.__name__}: {0!s}".format(e) raise SafeExecException(msg) finally: sys.path = original_path globals_dict.update(json_safe(g_dict))
def not_safe_exec( code, globals_dict, files=None, python_path=None, limit_overrides_context=None, # pylint: disable=unused-argument slug=None, # pylint: disable=unused-argument extra_files=None, ): """ Another implementation of `safe_exec`, but not safe. This can be swapped in for debugging problems in sandboxed Python code. This is not thread-safe, due to temporarily changing the current directory and modifying sys.path. Note that `limit_overrides_context` is ignored here, because resource limits are not applied. """ g_dict = json_safe(globals_dict) with temp_directory() as tmpdir: with change_directory(tmpdir): # pylint: disable=invalid-name # Copy the files here. for filename in files or (): dest = os.path.join(tmpdir, os.path.basename(filename)) shutil.copyfile(filename, dest) for filename, contents in extra_files or (): dest = os.path.join(tmpdir, filename) with open(dest, "wb") as f: f.write(contents) original_path = sys.path if python_path: sys.path.extend(python_path) try: exec(code, g_dict) # pylint: disable=exec-used except Exception as e: # Wrap the exception in a SafeExecException, but we don't # try here to include the traceback, since this is just a # substitute implementation. msg = "{0.__class__.__name__}: {0!s}".format(e) raise SafeExecException(msg) # pylint: disable=raise-missing-from finally: sys.path = original_path globals_dict.update(json_safe(g_dict))
def jail_code(command, code=None, files=None, extra_files=None, argv=None, stdin=None, slug=None): """ Run code in a jailed subprocess. `command` is an abstract command ("python", "node", ...) that must have been configured using `configure`. `code` is a string containing the code to run. If no code is supplied, then the code to run must be in one of the `files` copied, and must be named in the `argv` list. `files` is a list of file paths, they are all copied to the jailed directory. Note that no check is made here that the files don't contain sensitive information. The caller must somehow determine whether to allow the code access to the files. Symlinks will be copied as symlinks. If the linked-to file is not accessible to the sandbox, the symlink will be unreadable as well. `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. `argv` is the command-line arguments to supply. `stdin` is a string, the data to provide as the stdin for the process. `slug` is an arbitrary string, a description that's meaningful to the caller, that will be used in log messages. Return an object with: .stdout: stdout of the program, a string .stderr: stderr of the program, a string .status: exit status of the process: an int, 0 for success """ if not is_configured(command): raise Exception("jail_code needs to be configured for %r" % command) # We make a temp directory to serve as the home of the sandboxed code. # It has a writable "tmp" directory within it for temp files. with temp_directory() as homedir: # Make directory readable by other users ('sandbox' user needs to be # able to read it). os.chmod(homedir, 0775) # Make a subdir to use for temp files, world-writable so that the # sandbox user can write to it. tmptmp = os.path.join(homedir, "tmp") os.mkdir(tmptmp) os.chmod(tmptmp, 0777) argv = argv or [] # All the supporting files are copied into our directory. for filename in files or (): dest = os.path.join(homedir, os.path.basename(filename)) if os.path.islink(filename): os.symlink(os.readlink(filename), dest) elif os.path.isfile(filename): shutil.copy(filename, homedir) else: shutil.copytree(filename, dest, symlinks=True) # Create the main file. if code: with open(os.path.join(homedir, "jailed_code"), "wb") as jailed: jailed.write(code) argv = ["jailed_code"] + argv # Create extra files requested by the caller: for name, content in extra_files or (): with open(os.path.join(homedir, name), "wb") as extra: extra.write(content) cmd = [] # Build the command to run. user = COMMANDS[command]['user'] if user: # Run as the specified user cmd.extend(['sudo', '-u', user]) # Point TMPDIR at our temp directory. cmd.extend(['TMPDIR=tmp']) # Start with the command line dictated by "python" or whatever. cmd.extend(COMMANDS[command]['cmdline_start']) # Add the code-specific command line pieces. cmd.extend(argv) # Run the subprocess. subproc = subprocess.Popen( cmd, preexec_fn=set_process_limits, cwd=homedir, env={}, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) if slug: log.info("Executing jailed code %s in %s, with PID %s", slug, homedir, subproc.pid) # Start the time killer thread. realtime = LIMITS["REALTIME"] if realtime: killer = ProcessKillerThread(subproc, limit=realtime) killer.start() result = JailResult() result.stdout, result.stderr = subproc.communicate(stdin) result.status = subproc.returncode return result