Secure your code as it's written. Use Snyk Code to scan source code in minutes - no build needed - and fix issues immediately.
@param dir_modes: Dict that specifies which directories should be accessible
and how in the container.
@param container_system_config: Whether to use a special system configuration in
the container that disables all remote host and user lookups, sets a custom
hostname, etc.
"""
super(ContainerExecutor, self).__init__(*args, **kwargs)
self._use_namespaces = use_namespaces
if not use_namespaces:
return
self._container_tmpfs = container_tmpfs
self._container_system_config = container_system_config
self._uid = (
uid
if uid is not None
else container.CONTAINER_UID
if container_system_config
else os.getuid()
)
self._gid = (
gid
if gid is not None
else container.CONTAINER_GID
if container_system_config
else os.getgid()
)
self._allow_network = network_access
self._env_override = {}
if container_system_config:
self._env_override["HOME"] = container.CONTAINER_HOME
if container.CONTAINER_HOME not in dir_modes:
def is_accessible(path):
mode = container.determine_directory_mode(self._dir_modes, path)
return os.access(path, os.R_OK) and mode not in [None, container.DIR_HIDDEN]
def child():
"""Setup everything inside the container,
start the tool, and wait for result."""
try:
logging.debug(
"Child: child process of RunExecutor with PID %d started",
container.get_my_pid_from_procfs(),
)
# Put all received signals on hold until we handle them later.
container.block_all_signals()
# We want to avoid leaking file descriptors to the executed child.
# It is also nice if the child has only the minimal necessary file
# descriptors, to avoid keeping other pipes and files open, e.g.,
# those that the parent uses to communicate with other containers
# (if containers are started in parallel).
# Thus we do not use the close_fds feature of subprocess.Popen,
# but do the same here manually. We keep the relevant ends of our pipes,
# and stdin/out/err of child and grandchild.
necessary_fds = {
sys.stdin,
sys.stdout,
sys.stderr,
to_parent,
from_parent,
stdin,
necessary_capabilities = (
[libc.CAP_SYS_ADMIN] if result_files_patterns else []
)
container.drop_capabilities(keep=necessary_capabilities)
# Close other fds that were still necessary above.
container.close_open_fds(
keep_files={sys.stdout, sys.stderr, to_parent, from_parent}
)
# Set up signal handlers to forward signals to grandchild
# (because we are PID 1, there is a special signal handling otherwise).
# cf. dumb-init project: https://github.com/Yelp/dumb-init
# Also wait for grandchild and return its result.
if _HAS_SIGWAIT:
grandchild_result = container.wait_for_child_and_forward_signals(
grandchild_proc.pid, args[0]
)
else:
container.forward_all_signals_async(grandchild_proc.pid, args[0])
grandchild_result = self._wait_for_process(
grandchild_proc.pid, args[0]
)
logging.debug(
"Child: process %s terminated with exit code %d.",
args[0],
grandchild_result[0],
)
if result_files_patterns:
# Remove the bind mount that _setup_container_filesystem added
def _init_container(
temp_dir,
network_access,
dir_modes,
container_system_config,
container_tmpfs, # ignored, tmpfs is always used
):
"""
Create a fork of this process in a container. This method only returns in the fork,
so calling it seems like moving the current process into a container.
"""
# Prepare for private home directory, some tools write there
if container_system_config:
dir_modes.setdefault(container.CONTAINER_HOME, container.DIR_HIDDEN)
os.environ["HOME"] = container.CONTAINER_HOME
# Preparations
temp_dir = temp_dir.encode()
dir_modes = collections.OrderedDict(
sorted(
((path.encode(), kind) for (path, kind) in dir_modes.items()),
key=lambda tupl: len(tupl[0]),
)
)
uid = container.CONTAINER_UID if container_system_config else os.getuid()
gid = container.CONTAINER_GID if container_system_config else os.getgid()
# Create container.
# Contrary to ContainerExecutor, which uses clone to start a new process in new
# namespaces, we use unshare, which puts the current process (the multiprocessing
# via a socket (but Python < 3.3 lacks a convenient API for sendmsg),
# and reading /proc/self in the outer procfs instance
# (that's what we do).
my_outer_pid = container.get_my_pid_from_procfs()
container.mount_proc(self._container_system_config)
container.reset_signal_handling()
child_setup_fn() # Do some other setup the caller wants.
# Signal readiness to parent by sending our PID
# and wait until parent is also ready
os.write(to_parent, str(my_outer_pid).encode())
received = os.read(from_parent, 1)
assert received == MARKER_PARENT_COMPLETED, received
libc.unshare(libc.CLONE_NEWCGROUP)
container.drop_capabilities()
finally:
# close remaining ends of pipe
os.close(from_parent)
os.close(to_parent)
# here Python will exec() the tool for us
def make_tmpfs_dir(path):
"""Ensure that a tmpfs is mounted on path, if the path exists"""
if path in dir_modes:
return # explicitly configured by user
mount_tmpfs = mount_base + path
if os.path.isdir(mount_tmpfs):
temp_tmpfs = temp_base + path
util.makedirs(temp_tmpfs, exist_ok=True)
container.make_bind_mount(temp_tmpfs, mount_tmpfs)
# those that the parent uses to communicate with other containers
# (if containers are started in parallel).
# Thus we do not use the close_fds feature of subprocess.Popen,
# but do the same here manually. We keep the relevant ends of our pipes,
# and stdin/out/err of child and grandchild.
necessary_fds = {
sys.stdin,
sys.stdout,
sys.stderr,
to_parent,
from_parent,
stdin,
stdout,
stderr,
} - {None}
container.close_open_fds(keep_files=necessary_fds)
try:
if self._container_system_config:
# A standard hostname increases reproducibility.
libc.sethostname(container.CONTAINER_HOSTNAME)
if not self._allow_network:
container.activate_network_interface("lo")
# Wait until user mapping is finished,
# this is necessary for filesystem writes
received = os.read(from_parent, len(MARKER_USER_MAPPING_COMPLETED))
assert received == MARKER_USER_MAPPING_COMPLETED, received
if root_dir is not None:
self._setup_root_filesystem(root_dir)
if container_system_config
else os.getuid()
)
self._gid = (
gid
if gid is not None
else container.CONTAINER_GID
if container_system_config
else os.getgid()
)
self._allow_network = network_access
self._env_override = {}
if container_system_config:
self._env_override["HOME"] = container.CONTAINER_HOME
if container.CONTAINER_HOME not in dir_modes:
dir_modes[container.CONTAINER_HOME] = DIR_HIDDEN
if "/" not in dir_modes:
raise ValueError("Need directory mode for '/'.")
for path, kind in dir_modes.items():
if kind not in DIR_MODES:
raise ValueError(
"Invalid value '{}' for directory '{}'.".format(kind, path)
)
if not os.path.isabs(path):
raise ValueError("Invalid non-absolute directory '{}'.".format(path))
if path == "/proc":
raise ValueError("Cannot specify directory mode for /proc.")
# All dir_modes in dir_modes are sorted by length
# to ensure parent directories come before child directories
# All directories are bytes to avoid issues if existing mountpoints are invalid