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, stdout, stderr } - {None} container.close_open_fds(keep_files=necessary_fds) try: if not self._allow_network: container.activate_network_interface("lo") if root_dir is not None: self._setup_root_filesystem(root_dir) else: self._setup_container_filesystem(temp_dir) except EnvironmentError as e: logging.critical("Failed to configure container: %s", e) return CHILD_OSERROR try: os.chdir(cwd) except EnvironmentError as e: logging.critical( "Cannot change into working directory inside container: %s", e) return CHILD_OSERROR try: grandchild_proc = subprocess.Popen(args, stdin=stdin, stdout=stdout, stderr=stderr, env=env, close_fds=False, preexec_fn=grandchild) except (EnvironmentError, RuntimeError) as e: logging.critical("Cannot start process: %s", e) return CHILD_OSERROR container.drop_capabilities() # Close other fds that were still necessary above. container.close_open_fds( keep_files={sys.stdout, sys.stderr, to_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_all_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]) os.write(to_parent, pickle.dumps(grandchild_result)) os.close(to_parent) return 0 except EnvironmentError as e: logging.exception("Error in child process of RunExecutor") return CHILD_OSERROR except: # Need to catch everything because this method always needs to return a int # (we are inside a C callback that requires returning int). logging.exception("Error in child process of RunExecutor") return CHILD_UNKNOWN_ERROR
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, 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) else: self._setup_container_filesystem( temp_dir, output_dir if result_files_patterns else None, memlimit, memory_nodes, ) # Marking this process as "non-dumpable" (no core dumps) also # forbids several other ways how other processes can access and # influence it: # ptrace is forbidden and much of /proc/<child>/ is inaccessible. # We set this to prevent the benchmarked tool from messing with this # process or using it to escape from the container. More info: # http://man7.org/linux/man-pages/man5/proc.5.html # It needs to be done after MARKER_USER_MAPPING_COMPLETED. libc.prctl(libc.PR_SET_DUMPABLE, libc.SUID_DUMP_DISABLE, 0, 0, 0) except EnvironmentError as e: logging.critical("Failed to configure container: %s", e) return CHILD_OSERROR try: os.chdir(cwd) except EnvironmentError as e: logging.critical( "Cannot change into working directory inside container: %s", e ) return CHILD_OSERROR container.setup_seccomp_filter() try: grandchild_proc = subprocess.Popen( args, stdin=stdin, stdout=stdout, stderr=stderr, env=env, close_fds=False, preexec_fn=grandchild, ) except (EnvironmentError, RuntimeError) as e: logging.critical("Cannot start process: %s", e) return CHILD_OSERROR # keep capability for unmount if necessary later 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 # such that the parent can access the result files. libc.umount(temp_dir.encode()) # Re-allow access to /proc/<child>/..., # this is used by the parent for accessing output files libc.prctl(libc.PR_SET_DUMPABLE, libc.SUID_DUMP_USER, 0, 0, 0) os.write(to_parent, pickle.dumps(grandchild_result)) os.close(to_parent) # Now the parent copies the output files, we need to wait until this is # finished. If the child terminates, the container file system and its # tmpfs go away. assert os.read(from_parent, 1) == MARKER_PARENT_POST_RUN_COMPLETED os.close(from_parent) return 0 except EnvironmentError: logging.exception("Error in child process of RunExecutor") return CHILD_OSERROR except: # noqa: E722 # Need to catch everything because this method always needs to return an # int (we are inside a C callback that requires returning int). logging.exception("Error in child process of RunExecutor") return CHILD_UNKNOWN_ERROR
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, stdout, stderr} - { None } container.close_open_fds(keep_files=necessary_fds) try: if not self._allow_network: container.activate_network_interface("lo") self._setup_container_filesystem(temp_dir) except EnvironmentError as e: logging.critical("Failed to configure container: %s", e) return CHILD_OSERROR try: os.chdir(cwd) except EnvironmentError as e: logging.critical("Cannot change into working directory inside container: %s", e) return CHILD_OSERROR try: grandchild_proc = subprocess.Popen( args, stdin=stdin, stdout=stdout, stderr=stderr, env=env, close_fds=False, preexec_fn=grandchild ) except (EnvironmentError, RuntimeError) as e: logging.critical("Cannot start process: %s", e) return CHILD_OSERROR container.drop_capabilities() # Close other fds that were still necessary above. container.close_open_fds(keep_files={sys.stdout, sys.stderr, to_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_all_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]) os.write(to_parent, pickle.dumps(grandchild_result)) os.close(to_parent) return 0 except EnvironmentError as e: logging.exception("Error in child process of RunExecutor") return CHILD_OSERROR except: # Need to catch everything because this method always needs to return a int # (we are inside a C callback that requires returning int). logging.exception("Error in child process of RunExecutor") return CHILD_UNKNOWN_ERROR
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, 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) else: self._setup_container_filesystem( temp_dir, output_dir if result_files_patterns else None, memlimit, memory_nodes) except EnvironmentError as e: logging.critical("Failed to configure container: %s", e) return CHILD_OSERROR try: os.chdir(cwd) except EnvironmentError as e: logging.critical( "Cannot change into working directory inside container: %s", e) return CHILD_OSERROR try: grandchild_proc = subprocess.Popen(args, stdin=stdin, stdout=stdout, stderr=stderr, env=env, close_fds=False, preexec_fn=grandchild) except (EnvironmentError, RuntimeError) as e: logging.critical("Cannot start process: %s", e) return CHILD_OSERROR # keep capability for unmount if necessary later 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_all_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 # such that the parent can access the result files. libc.umount(temp_dir.encode()) os.write(to_parent, pickle.dumps(grandchild_result)) os.close(to_parent) # Now the parent copies the output files, we need to wait until this is finished. # If the child terminates, the container file system and its tmpfs go away. os.read(from_parent, 1) os.close(from_parent) return 0 except EnvironmentError as e: logging.exception("Error in child process of RunExecutor") return CHILD_OSERROR except: # Need to catch everything because this method always needs to return a int # (we are inside a C callback that requires returning int). logging.exception("Error in child process of RunExecutor") return CHILD_UNKNOWN_ERROR