diff options
author | Zac Medico <zmedico@gentoo.org> | 2024-02-23 12:35:04 -0800 |
---|---|---|
committer | Zac Medico <zmedico@gentoo.org> | 2024-02-24 12:07:39 -0800 |
commit | 3f4250dc7d32e9915224b1c9c4bc04c2740abcda (patch) | |
tree | cd9cd6e6f5a5c27267bbc1cd684cdbfcd080427b | |
parent | _dynamic_deps_preload: Fix settings reference (diff) | |
download | portage-3f4250dc7d32e9915224b1c9c4bc04c2740abcda.tar.gz portage-3f4250dc7d32e9915224b1c9c4bc04c2740abcda.tar.bz2 portage-3f4250dc7d32e9915224b1c9c4bc04c2740abcda.zip |
process.spawn: Fix logic for missing libc.unshare on musl
Fix unshare_* variables to be False when the libc is missing,
libc.unshare is missing, or libc.unshare fails.
Also, if socket.sethostname is missing then _exec2 needs
libc for the network-sandbox sethostname call which is wrapped
by a blanket Exception handler.
Fixes: 419cce79f908 ("process._exec: Use _start_fork for os.fork() error handling")
Bug: https://bugs.gentoo.org/925311
Signed-off-by: Zac Medico <zmedico@gentoo.org>
-rw-r--r-- | lib/portage/process.py | 213 |
1 files changed, 110 insertions, 103 deletions
diff --git a/lib/portage/process.py b/lib/portage/process.py index f4758c824..d16262e75 100644 --- a/lib/portage/process.py +++ b/lib/portage/process.py @@ -956,114 +956,119 @@ def _exec( signal.signal(signal.SIGQUIT, signal.SIG_DFL) # Unshare (while still uid==0) + have_unshare = False + libc = None 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: - # 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 - # 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: - # 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 - - # 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, - ) - - # 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) + have_unshare = hasattr(libc, "unshare") + + if not have_unshare: + # unshare() may not be supported by libc + unshare_net = False + unshare_ipc = False + unshare_mount = False + unshare_pid = False + + if unshare_net or unshare_ipc or unshare_mount or unshare_pid: + # 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, + ) + + unshare_net = False + unshare_ipc = False + unshare_mount = False + unshare_pid = False + + if unshare_pid: + # 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 + + # 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, + libc, + ), + fd_pipes=None, + close_fds=False, + ) + + # 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( @@ -1080,6 +1085,7 @@ def _exec( unshare_ipc, unshare_mount, unshare_pid, + libc, ) @@ -1097,6 +1103,7 @@ def _exec2( unshare_ipc, unshare_mount, unshare_pid, + libc, ): if unshare_mount: # mark the whole filesystem as slave to avoid |