Esempio n. 1
0
def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask,
          cwd, pre_exec, close_fds, unshare_net, unshare_ipc, unshare_mount,
          unshare_pid, unshare_flags, cgroup):
    """
	Execute a given binary with options

	@param binary: Name of program to execute
	@type binary: String
	@param mycommand: Options for program
	@type mycommand: String
	@param opt_name: Name of process (defaults to binary)
	@type opt_name: String
	@param fd_pipes: Mapping pipes to destination; { 0:0, 1:1, 2:2 }
	@type fd_pipes: Dictionary
	@param env: Key,Value mapping for Environmental Variables
	@type env: Dictionary
	@param gid: Group ID to run the process under
	@type gid: Integer
	@param groups: Groups the Process should be in.
	@type groups: List
	@param uid: User ID to run the process under
	@type uid: Integer
	@param umask: an int representing a unix umask (see man chmod for umask details)
	@type umask: Integer
	@param cwd: Current working directory
	@type cwd: String
	@param pre_exec: A function to be called with no arguments just prior to the exec call.
	@type pre_exec: callable
	@param unshare_net: If True, networking will be unshared from the spawned process
	@type unshare_net: Boolean
	@param unshare_ipc: If True, IPC will be unshared from the spawned process
	@type unshare_ipc: Boolean
	@param unshare_mount: If True, mount namespace will be unshared and mounts will
		be private to the namespace
	@type unshare_mount: Boolean
	@param unshare_pid: If True, PID ns will be unshared from the spawned process
	@type unshare_pid: Boolean
	@param unshare_flags: Flags for the unshare(2) function
	@type unshare_flags: Integer
	@param cgroup: CGroup path to bind the process to
	@type cgroup: String
	@rtype: None
	@return: Never returns (calls os.execve)
	"""

    # If the process we're creating hasn't been given a name
    # assign it the name of the executable.
    if not opt_name:
        if binary is portage._python_interpreter:
            # NOTE: PyPy 1.7 will die due to "libary path not found" if argv[0]
            # does not contain the full path of the binary.
            opt_name = binary
        else:
            opt_name = os.path.basename(binary)

    # Set up the command's argument list.
    myargs = [opt_name]
    myargs.extend(mycommand[1:])

    # Avoid a potential UnicodeEncodeError from os.execve().
    myargs = [
        _unicode_encode(x, encoding=_encodings['fs'], errors='strict')
        for x in myargs
    ]

    # Use default signal handlers in order to avoid problems
    # killing subprocesses as reported in bug #353239.
    signal.signal(signal.SIGINT, signal.SIG_DFL)
    signal.signal(signal.SIGTERM, signal.SIG_DFL)

    # Unregister SIGCHLD handler and wakeup_fd for the parent
    # process's event loop (bug 655656).
    signal.signal(signal.SIGCHLD, signal.SIG_DFL)
    try:
        wakeup_fd = signal.set_wakeup_fd(-1)
        if wakeup_fd > 0:
            os.close(wakeup_fd)
    except (ValueError, OSError):
        pass

    # Quiet killing of subprocesses by SIGPIPE (see bug #309001).
    signal.signal(signal.SIGPIPE, signal.SIG_DFL)

    # Avoid issues triggered by inheritance of SIGQUIT handler from
    # the parent process (see bug #289486).
    signal.signal(signal.SIGQUIT, signal.SIG_DFL)

    _setup_pipes(fd_pipes, close_fds=close_fds, inheritable=True)

    # Add to cgroup
    # it's better to do it from the child since we can guarantee
    # it is done before we start forking children
    if cgroup:
        with open(os.path.join(cgroup, 'cgroup.procs'), 'a') as f:
            f.write('%d\n' % portage.getpid())

    # Unshare (while still uid==0)
    if unshare_net or unshare_ipc or unshare_mount or unshare_pid:
        filename = find_library("c")
        if filename is not None:
            libc = LoadLibrary(filename)
            if libc is not None:
                try:
                    # Since a failed unshare call could corrupt process
                    # state, first validate that the call can succeed.
                    # The parent process should call _unshare_validate
                    # before it forks, so that all child processes can
                    # reuse _unshare_validate results that have been
                    # cached by the parent process.
                    errno_value = _unshare_validate(unshare_flags)
                    if errno_value == 0 and libc.unshare(unshare_flags) != 0:
                        errno_value = ctypes.get_errno()
                    if errno_value != 0:

                        involved_features = []
                        if unshare_ipc:
                            involved_features.append('ipc-sandbox')
                        if unshare_mount:
                            involved_features.append('mount-sandbox')
                        if unshare_net:
                            involved_features.append('network-sandbox')
                        if unshare_pid:
                            involved_features.append('pid-sandbox')

                        writemsg(
                            "Unable to unshare: %s (for FEATURES=\"%s\")\n" %
                            (errno.errorcode.get(errno_value, '?'),
                             ' '.join(involved_features)),
                            noiselevel=-1)
                    else:
                        if unshare_pid:
                            main_child_pid = os.fork()
                            if main_child_pid == 0:
                                # The portage.getpid() cache may need to be updated here,
                                # in case the pre_exec function invokes portage APIs.
                                portage._ForkWatcher.hook(portage._ForkWatcher)
                                # pid namespace requires us to become init
                                binary, myargs = portage._python_interpreter, [
                                    portage._python_interpreter,
                                    os.path.join(portage._bin_path,
                                                 'pid-ns-init'),
                                    _unicode_encode(
                                        '' if uid is None else str(uid)),
                                    _unicode_encode(
                                        '' if gid is None else str(gid)),
                                    _unicode_encode(
                                        '' if groups is None else ','.join(
                                            str(group) for group in groups)),
                                    _unicode_encode(
                                        '' if umask is None else str(umask)),
                                    _unicode_encode(','.join(
                                        str(fd) for fd in fd_pipes)), binary
                                ] + myargs
                                uid = None
                                gid = None
                                groups = None
                                umask = None
                            else:
                                # Execute a supervisor process which will forward
                                # signals to init and forward exit status to the
                                # parent process. The supervisor process runs in
                                # the global pid namespace, so skip /proc remount
                                # and other setup that's intended only for the
                                # init process.
                                binary, myargs = portage._python_interpreter, [
                                    portage._python_interpreter,
                                    os.path.join(portage._bin_path,
                                                 'pid-ns-init'),
                                    str(main_child_pid)
                                ]

                                os.execve(binary, myargs, env)

                        if unshare_mount:
                            # mark the whole filesystem as slave to avoid
                            # mounts escaping the namespace
                            s = subprocess.Popen(
                                ['mount', '--make-rslave', '/'])
                            mount_ret = s.wait()
                            if mount_ret != 0:
                                # TODO: should it be fatal maybe?
                                writemsg("Unable to mark mounts slave: %d\n" %
                                         (mount_ret, ),
                                         noiselevel=-1)
                        if unshare_pid:
                            # we need at least /proc being slave
                            s = subprocess.Popen(
                                ['mount', '--make-slave', '/proc'])
                            mount_ret = s.wait()
                            if mount_ret != 0:
                                # can't proceed with shared /proc
                                writemsg("Unable to mark /proc slave: %d\n" %
                                         (mount_ret, ),
                                         noiselevel=-1)
                                os._exit(1)
                            # mount new /proc for our namespace
                            s = subprocess.Popen(
                                ['mount', '-n', '-t', 'proc', 'proc', '/proc'])
                            mount_ret = s.wait()
                            if mount_ret != 0:
                                writemsg("Unable to mount new /proc: %d\n" %
                                         (mount_ret, ),
                                         noiselevel=-1)
                                os._exit(1)
                        if unshare_net:
                            # use 'localhost' to avoid hostname resolution problems
                            try:
                                # pypy3 does not implement socket.sethostname()
                                new_hostname = b'localhost'
                                if hasattr(socket, 'sethostname'):
                                    socket.sethostname(new_hostname)
                                else:
                                    if libc.sethostname(
                                            new_hostname,
                                            len(new_hostname)) != 0:
                                        errno_value = ctypes.get_errno()
                                        raise OSError(errno_value,
                                                      os.strerror(errno_value))
                            except Exception as e:
                                writemsg(
                                    "Unable to set hostname: %s (for FEATURES=\"network-sandbox\")\n"
                                    % (e, ),
                                    noiselevel=-1)
                            _configure_loopback_interface()
                except AttributeError:
                    # unshare() not supported by libc
                    pass

    # Set requested process permissions.
    if gid:
        # Cast proxies to int, in case it matters.
        os.setgid(int(gid))
    if groups:
        os.setgroups(groups)
    if uid:
        # Cast proxies to int, in case it matters.
        os.setuid(int(uid))
    if umask:
        os.umask(umask)
    if cwd is not None:
        os.chdir(cwd)
    if pre_exec:
        pre_exec()

    # And switch to the new process.
    os.execve(binary, myargs, env)