From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from lists.gentoo.org (pigeon.gentoo.org [208.92.234.80]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by finch.gentoo.org (Postfix) with ESMTPS id F20C015815E for ; Mon, 12 Feb 2024 07:58:10 +0000 (UTC) Received: from pigeon.gentoo.org (localhost [127.0.0.1]) by pigeon.gentoo.org (Postfix) with SMTP id 29132E2A0D; Mon, 12 Feb 2024 07:58:10 +0000 (UTC) Received: from smtp.gentoo.org (smtp.gentoo.org [IPv6:2001:470:ea4a:1:5054:ff:fec7:86e4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by pigeon.gentoo.org (Postfix) with ESMTPS id 0C44EE2A0D for ; Mon, 12 Feb 2024 07:58:10 +0000 (UTC) Received: from oystercatcher.gentoo.org (oystercatcher.gentoo.org [148.251.78.52]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits)) (No client certificate requested) by smtp.gentoo.org (Postfix) with ESMTPS id 26F93343062 for ; Mon, 12 Feb 2024 07:58:09 +0000 (UTC) Received: from localhost.localdomain (localhost [IPv6:::1]) by oystercatcher.gentoo.org (Postfix) with ESMTP id 97473CB1 for ; Mon, 12 Feb 2024 07:58:07 +0000 (UTC) From: "Zac Medico" To: gentoo-commits@lists.gentoo.org Content-Transfer-Encoding: 8bit Content-type: text/plain; charset=UTF-8 Reply-To: gentoo-dev@lists.gentoo.org, "Zac Medico" Message-ID: <1707724570.419cce79f9082308c848df0a98f367de4d1c50a3.zmedico@gentoo> Subject: [gentoo-commits] proj/portage:master commit in: lib/portage/ X-VCS-Repository: proj/portage X-VCS-Files: lib/portage/process.py X-VCS-Directories: lib/portage/ X-VCS-Committer: zmedico X-VCS-Committer-Name: Zac Medico X-VCS-Revision: 419cce79f9082308c848df0a98f367de4d1c50a3 X-VCS-Branch: master Date: Mon, 12 Feb 2024 07:58:07 +0000 (UTC) Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-Id: Gentoo Linux mail X-BeenThere: gentoo-commits@lists.gentoo.org X-Auto-Response-Suppress: DR, RN, NRN, OOF, AutoReply X-Archives-Salt: cd4e58a2-57de-444d-9c72-33e2d27eefe4 X-Archives-Hash: b85981d4a53cb3a000fdecc3441c44af commit: 419cce79f9082308c848df0a98f367de4d1c50a3 Author: Zac Medico gentoo org> AuthorDate: Sun Feb 11 21:58:10 2024 +0000 Commit: Zac Medico gentoo org> CommitDate: Mon Feb 12 07:56:10 2024 +0000 URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=419cce79 process._exec: Use _start_fork for os.fork() error handling Use _start_fork for os.fork() error handling, ensuring that if exec fails then the child process will display a traceback before it exits via os._exit to suppress any finally blocks from parent's call stack (bug 345289). Bug: https://bugs.gentoo.org/345289 Bug: https://bugs.gentoo.org/916566 Bug: https://bugs.gentoo.org/924313 Signed-off-by: Zac Medico gentoo.org> lib/portage/process.py | 259 ++++++++++++++++++++++++++++--------------------- 1 file changed, 151 insertions(+), 108 deletions(-) diff --git a/lib/portage/process.py b/lib/portage/process.py index 6cad250e34..f4758c824c 100644 --- a/lib/portage/process.py +++ b/lib/portage/process.py @@ -961,7 +961,13 @@ def _exec( if filename is not None: libc = LoadLibrary(filename) if libc is not None: - try: + # unshare() may not be supported by libc + if not hasattr(libc, "unshare"): + unshare_net = False + unshare_ipc = False + unshare_mount = False + unshare_pid = False + else: # Since a failed unshare call could corrupt process # state, first validate that the call can succeed. # The parent process should call _unshare_validate @@ -992,117 +998,154 @@ def _exec( ) else: if unshare_pid: - main_child_pid = os.fork() - if main_child_pid == 0: - # 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, [ + # pid namespace requires us to become init + binary, myargs = ( + portage._python_interpreter, + [ portage._python_interpreter, os.path.join(portage._bin_path, "pid-ns-init"), - str(main_child_pid), + _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 + + # Use _start_fork for os.fork() error handling, ensuring + # that if exec fails then the child process will display + # a traceback before it exits via os._exit to suppress any + # finally blocks from parent's call stack (bug 345289). + main_child_pid = _start_fork( + _exec2, + args=( + binary, + myargs, + env, + gid, + groups, + uid, + umask, + cwd, + pre_exec, + unshare_net, + unshare_ipc, + unshare_mount, + unshare_pid, + ), + fd_pipes=None, + close_fds=False, + ) - os.execve(binary, myargs, env) + # 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) + + # Reachable only if unshare_pid is False. + _exec2( + binary, + myargs, + env, + gid, + groups, + uid, + umask, + cwd, + pre_exec, + unshare_net, + unshare_ipc, + unshare_mount, + unshare_pid, + ) - 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 + +def _exec2( + binary, + myargs, + env, + gid, + groups, + uid, + umask, + cwd, + pre_exec, + unshare_net, + unshare_ipc, + unshare_mount, + unshare_pid, +): + 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( + f'Unable to set hostname: {e} (for FEATURES="network-sandbox")\n', + noiselevel=-1, + ) + _configure_loopback_interface() # Set requested process permissions. if gid: @@ -1260,7 +1303,7 @@ def _setup_pipes(fd_pipes, close_fds=True, inheritable=None): actually does nothing in this case), which avoids possible interference. """ - + fd_pipes = {} if fd_pipes is None else fd_pipes reverse_map = {} # To protect from cases where direct assignment could # clobber needed fds ({1:2, 2:1}) we create a reverse map