def run_sandbox(command, cwd=None, env=None, filesystem_root='/', filesystem_writable_paths='all', mounts='undefined', extra_mounts=None, network='undefined', stdout=sandboxlib.CAPTURE, stderr=sandboxlib.CAPTURE): if type(command) == str: command = [command] linux_user_chroot_command = [linux_user_chroot_program()] extra_mounts = sandboxlib.validate_extra_mounts(extra_mounts) linux_user_chroot_command += process_network_config(network) if cwd is not None: linux_user_chroot_command.extend(['--chdir', cwd]) linux_user_chroot_command += process_writable_paths( filesystem_root, filesystem_writable_paths) create_mount_points_if_missing(filesystem_root, extra_mounts) mount_context = process_mount_config( mounts=mounts, extra_mounts=extra_mounts or []) with mount_context as linux_user_chroot_mount_args: linux_user_chroot_command.extend(linux_user_chroot_mount_args) argv = linux_user_chroot_command + [filesystem_root] + command exit, out, err = sandboxlib._run_command(argv, stdout, stderr, env=env) return exit, out, err
def mount(source, path, mount_type, mount_options): # We depend on the host system's 'mount' program here, which is a # little sad. It's possible to call the libc's mount() function # directly from Python using the 'ctypes' library, and perhaps we # should do that instead. The 'mount' requires that a source is # given even for the special filesystems (e.g. proc, tmpfs), so we # use the mount type as the source if the latter is not explicitly # given. def is_none(value): return value in (None, 'none', '') argv = ['mount'] if not is_none(mount_type): argv.extend(('-t', mount_type)) if not is_none(mount_options): argv.extend(('-o', mount_options)) #If this is left empty, mount looks in fstab which will fail if not is_none(source): argv.append(source) else: argv.append("none") argv.append(path) exit, out, err = sandboxlib._run_command( argv, stdout=sandboxlib.CAPTURE, stderr=sandboxlib.CAPTURE) if exit != 0: raise RuntimeError( "%s failed: %s" % ( argv, err.decode('utf-8')))
def run_sandbox(command, cwd=None, env=None, filesystem_root='/', filesystem_writable_paths='all', mounts='undefined', extra_mounts=None, network='undefined', stdout=sandboxlib.CAPTURE, stderr=sandboxlib.CAPTURE): if type(command) == str: command = [command] linux_user_chroot_command = ['linux-user-chroot'] extra_mounts = sandboxlib.validate_extra_mounts(extra_mounts) unshare_command = process_mount_config( root=filesystem_root, mounts=mounts, extra_mounts=extra_mounts or []) linux_user_chroot_command += process_network_config(network) if cwd is not None: linux_user_chroot_command.extend(['--chdir', cwd]) linux_user_chroot_command += process_writable_paths( filesystem_root, filesystem_writable_paths) linux_user_chroot_command.append(filesystem_root) create_mount_points_if_missing(filesystem_root, extra_mounts) argv = (unshare_command + linux_user_chroot_command + command) exit, out, err = sandboxlib._run_command(argv, stdout, stderr, env=env) return exit, out, err
def unmount(path): argv = ['umount', path] exit, out, err = sandboxlib._run_command(argv, stdout=None, stderr=None) if exit != 0: warnings.warn("%s failed: %s" % ( argv, err.decode('utf-8')))
def run_sandbox(command, cwd=None, env=None, filesystem_root='/', filesystem_writable_paths='all', mounts='undefined', extra_mounts=None, network='undefined', stdout=sandboxlib.CAPTURE, stderr=sandboxlib.CAPTURE): if type(command) == str: command = [command] linux_user_chroot_command = [linux_user_chroot_program()] extra_mounts = sandboxlib.validate_extra_mounts(extra_mounts) linux_user_chroot_command += process_network_config(network) if cwd is not None: linux_user_chroot_command.extend(['--chdir', cwd]) linux_user_chroot_command += process_writable_paths( filesystem_root, filesystem_writable_paths) create_mount_points_if_missing(filesystem_root, extra_mounts) mount_context = process_mount_config(mounts=mounts, extra_mounts=extra_mounts or []) with mount_context as linux_user_chroot_mount_args: linux_user_chroot_command.extend(linux_user_chroot_mount_args) argv = linux_user_chroot_command + [filesystem_root] + command exit, out, err = sandboxlib._run_command(argv, stdout, stderr, env=env) return exit, out, err
def unmount(path): argv = ['umount', path] exit, out, err = sandboxlib._run_command(argv, stdout=sandboxlib.CAPTURE, stderr=sandboxlib.CAPTURE) if exit != 0: warnings.warn("%s failed: %s" % (argv, err.decode('utf-8')))
def mount(source, path, mount_type, mount_options): # We depend on the host system's 'mount' program here, which is a # little sad. It's possible to call the libc's mount() function # directly from Python using the 'ctypes' library, and perhaps we # should do that instead. argv = [ 'mount', '-t', mount_type, '-o', mount_options, source, path] exit, out, err = sandboxlib._run_command(argv, stdout=None, stderr=None) if exit != 0: raise RuntimeError( "%s failed: %s" % ( argv, err.decode('utf-8')))
def run_command_in_chroot(pipe, stdout, stderr, extra_mounts, chroot_path, command, cwd, env): # This function should be run in a multiprocessing.Process() subprocess, # because it calls os.chroot(). There's no 'unchroot()' function! After # chrooting, it calls sandboxlib._run_command(), which uses the # 'subprocess' module to exec 'command'. This means there are actually # two subprocesses, which is not ideal, but it seems to be the simplest # implementation. # # An alternative approach would be to use the 'preexec_fn' feature of # subprocess.Popen() to call os.chroot(rootfs_path) and os.chdir(cwd). # The Python 3 '_posixsubprocess' module hints in several places that # deadlocks can occur when using preexec_fn, and it is very difficult to # propagate exceptions from that function, so it seems best to avoid it. try: # You have most likely got to be the 'root' user in order for this to # work. try: os.chroot(chroot_path) except OSError as e: raise RuntimeError("Unable to chroot: %s" % e) # This is important in case 'cwd' is a relative path. os.chdir('/') if cwd is not None: try: os.chdir(cwd) except OSError as e: raise RuntimeError( "Unable to set current working directory: %s" % e) exit, out, err = sandboxlib._run_command(command, stdout, stderr, env=env) pipe.send([exit, out, err]) result = 0 except Exception as e: pipe.send(e) result = 1 os._exit(result)
def run_command_in_chroot(pipe, stdout, stderr, extra_mounts, chroot_path, command, cwd, env): # This function should be run in a multiprocessing.Process() subprocess, # because it calls os.chroot(). There's no 'unchroot()' function! After # chrooting, it calls sandboxlib._run_command(), which uses the # 'subprocess' module to exec 'command'. This means there are actually # two subprocesses, which is not ideal, but it seems to be the simplest # implementation. # # An alternative approach would be to use the 'preexec_fn' feature of # subprocess.Popen() to call os.chroot(rootfs_path) and os.chdir(cwd). # The Python 3 '_posixsubprocess' module hints in several places that # deadlocks can occur when using preexec_fn, and it is very difficult to # propagate exceptions from that function, so it seems best to avoid it. try: # You have most likely got to be the 'root' user in order for this to # work. try: os.chroot(chroot_path) except OSError as e: raise RuntimeError("Unable to chroot: %s" % e) # This is important in case 'cwd' is a relative path. os.chdir('/') if cwd is not None: try: os.chdir(cwd) except OSError as e: raise RuntimeError( "Unable to set current working directory: %s" % e) exit, out, err = sandboxlib._run_command( command, stdout, stderr, env=env) pipe.send([exit, out, err]) result = 0 except Exception as e: tb = traceback.format_exc() pipe.send((e, tb)) result = 1 os._exit(result)
def mount(source, path, mount_type, mount_options): # We depend on the host system's 'mount' program here, which is a # little sad. It's possible to call the libc's mount() function # directly from Python using the 'ctypes' library, and perhaps we # should do that instead. def is_none(value): return value in (None, 'none', '') argv = ['mount'] if not is_none(mount_type): argv.extend(('-t', mount_type)) if not is_none(mount_options): argv.extend(('-o', mount_options)) if not is_none(source): argv.append(source) argv.append(path) exit, out, err = sandboxlib._run_command(argv, stdout=sandboxlib.CAPTURE, stderr=sandboxlib.CAPTURE) if exit != 0: raise RuntimeError("%s failed: %s" % (argv, err.decode('utf-8')))
def mount(source, path, mount_type, mount_options): # We depend on the host system's 'mount' program here, which is a # little sad. It's possible to call the libc's mount() function # directly from Python using the 'ctypes' library, and perhaps we # should do that instead. def is_none(value): return value in (None, 'none', '') argv = ['mount'] if not is_none(mount_type): argv.extend(('-t', mount_type)) if not is_none(mount_options): argv.extend(('-o', mount_options)) if not is_none(source): argv.append(source) argv.append(path) exit, out, err = sandboxlib._run_command( argv, stdout=sandboxlib.CAPTURE, stderr=sandboxlib.CAPTURE) if exit != 0: raise RuntimeError( "%s failed: %s" % ( argv, err.decode('utf-8')))
def run_sandbox(command, cwd=None, env=None, filesystem_root='/', filesystem_writable_paths='all', mounts='undefined', extra_mounts=None, network='undefined', stderr=sandboxlib.CAPTURE, stdout=sandboxlib.CAPTURE): """Run 'command' in a sandboxed environment. Parameters: - command: the command to run. Pass a list of parameters rather than using spaces to separate them, e.g. ['echo', '"Hello world"']. - cwd: the working directory of 'command', relative to 'rootfs_path'. Defaults to '/' if "rootfs_path" is specified, and the current directory of the calling process otherwise. - env: environment variables to set - filesystem_root: the path to the root of the sandbox. Defaults to '/', which doesn't isolate the command from the host filesystem at all. - filesystem_writable_paths: defaults to 'all', which allows the command to write to anywhere under 'filesystem_root' that the user of the calling process could write to. Backends may accept a list of paths instead of 'all', and will prevent writes to any files not under a path in that whitelist. If 'none' or an empty list is passed, the whole file-system will be read-only. The paths should be relative to filesystem_root. This will processed /after/ extra_mounts are mounted. - mounts: configures mount sharing. Defaults to 'undefined', where no no attempt is made to isolate mounts. Backends may support 'isolated' as well. - extra_mounts: a list of locations to mount inside 'rootfs_path', specified as a list of tuples of (source_path, target_path, type, options). The 'type' and 'options' should match what would be specified in /etc/fstab, but a backends may support only a limited subset of values. The 'target_path' is relative to filesystem_root and will be created before mounting if it doesn't exist. - network: configures network sharing. Defaults to 'undefined', where no attempt is made to either prevent or provide networking inside the sandbox. Backends may support 'isolated' and/or other values as well. - stdout: whether to capture stdout, or redirect stdout to a file handle. If set to sandboxlib.CAPTURE, the function will return the stdout data, if not, it will return None for that. If stdout=None, the data will be discarded -- it will NOT inherit the parent process's stdout, unlike with subprocess.Popen(). Set 'stdout=sys.stdout' if you want that. - stderr: same as stdout Returns: a tuple of (exit code, stdout output, stderr output). """ log.debug("cmd: {}, cwd: {}, env: {}, filesystem_root: {}, " "filesystem_writable_paths: {}, mounts: {}, extra_mounts: {}, " "network: {}, stderr: {}, stdout: {}".format( command, cwd, env, filesystem_root, filesystem_writable_paths, mounts, extra_mounts, network, stderr, stdout)) if type(command) == str: command = [command] # Bwrap full path bwrap_command = [bubblewrap_program()] log.debug("/path/to/bwrap : {}".format(bwrap_command)) # Add in the root filesystem stuff first # rootfs is mounted as RW initially so that further mounts can be placed on top # If a RO root is required, after all other mounts are complete, root is # remounted as RO bwrap_command += ["--bind", filesystem_root, "/"] bwrap_command += process_network_config(network) if cwd is not None: log.debug("Setting cwd to '{}'".format(cwd)) bwrap_command.extend(['--chdir', cwd]) # do pre checks on mounts extra_mounts = sandboxlib.validate_extra_mounts(extra_mounts) create_mount_points_if_missing(filesystem_root, extra_mounts) # Handles the ro and rw mounts bwrap_command += process_mounts(filesystem_root, extra_mounts, filesystem_writable_paths) # Set UID and GUI bwrap_command.extend(['--unshare-user', '--uid', '0', '--gid', '0']) argv = bwrap_command + command log.info("bubblewrap.run_command({}, stdout:{}, stderr:{}, env:{})" .format(" ".join(argv), stdout, stderr, env)) exit, out, err = sandboxlib._run_command(argv, stdout, stderr, env=env) return exit, out, err