"digest", "distcc", "distcc-pump", "distlocks",
"downgrade-backup", "ebuild-locks", "fakeroot",
"fail-clean", "force-mirror", "force-prefix", "getbinpkg",
- "installsources", "keeptemp", "keepwork", "fixlafiles", "lmirror",
+ "installsources", "ipc-sandbox",
+ "keeptemp", "keepwork", "fixlafiles", "lmirror",
"merge-sync",
"metadata-transfer", "mirror", "multilib-strict",
"network-sandbox", "news",
"prerm", "setup"
])
+# phases in which IPC with host is allowed
+_ipc_phases = frozenset([
+ "setup", "pretend",
+ "preinst", "postinst", "prerm", "postrm",
+])
+
# phases in which networking access is allowed
_networked_phases = frozenset([
# for VCS fetching
"unpack",
- # for IPC
- "setup", "pretend",
- "preinst", "postinst", "prerm", "postrm",
-])
+ # + for network-bound IPC
+] + list(_ipc_phases))
_phase_func_map = {
"config": "pkg_config",
if phase in _unsandboxed_phases:
kwargs['free'] = True
+ if phase in _ipc_phases:
+ kwargs['ipc'] = True
if phase in _networked_phases:
kwargs['networked'] = True
# XXX This would be to replace getstatusoutput completely.
# XXX Issue: cannot block execution. Deadlock condition.
-def spawn(mystring, mysettings, debug=0, free=0, droppriv=0, sesandbox=0, fakeroot=0, networked=0, **keywords):
+def spawn(mystring, mysettings, debug=0, free=0, droppriv=0, sesandbox=0, fakeroot=0, networked=0, ipc=0, **keywords):
"""
Spawn a subprocess with extra portage-specific options.
Optiosn include:
@type fakeroot: Boolean
@param networked: Run this command with networking access enabled
@type networked: Boolean
+ @param ipc: Run this command with host IPC access enabled
+ @type ipc: Boolean
@param keywords: Extra options encoded as a dict, to be passed to spawn
@type keywords: Dictionary
@rtype: Integer
features = mysettings.features
- # Unshare network namespace to keep ebuilds sanitized
- if not networked and uid == 0 and platform.system() == 'Linux' and "network-sandbox" in features:
- keywords['unshare_net'] = True
+ # Use Linux namespaces if available
+ if uid == 0 and platform.system() == 'Linux':
+ if not networked and "network-sandbox" in features:
+ keywords['unshare_net'] = True
+ if not ipc and "ipc-sandbox" in features:
+ keywords['unshare_ipc'] = True
# TODO: Enable fakeroot to be used together with droppriv. The
# fake ownership/permissions will have to be converted to real
def spawn(mycommand, env={}, opt_name=None, fd_pipes=None, returnpid=False,
uid=None, gid=None, groups=None, umask=None, logfile=None,
- path_lookup=True, pre_exec=None, close_fds=True, unshare_net=False):
+ path_lookup=True, pre_exec=None, close_fds=True, unshare_net=False,
+ unshare_ipc=False):
"""
Spawns a given command.
@type close_fds: Boolean
@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
logfile requires stdout and stderr to be assigned to this process (ie not pointed
somewhere else.)
# This caches the libc library lookup in the current
# process, so that it's only done once rather than
# for each child process.
- if unshare_net:
+ if unshare_net or unshare_ipc:
find_library("c")
parent_pid = os.getpid()
try:
_exec(binary, mycommand, opt_name, fd_pipes,
env, gid, groups, uid, umask, pre_exec, close_fds,
- unshare_net)
+ unshare_net, unshare_ipc)
except SystemExit:
raise
except Exception as e:
return 0
def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask,
- pre_exec, close_fds, unshare_net):
+ pre_exec, close_fds, unshare_net, unshare_ipc):
"""
Execute a given binary with options
@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
@rtype: None
@return: Never returns (calls os.execve)
"""
_setup_pipes(fd_pipes, close_fds=close_fds)
- # Unshare network (while still uid==0)
- if unshare_net:
+ # Unshare (while still uid==0)
+ if unshare_net or unshare_ipc:
filename = find_library("c")
if filename is not None:
libc = LoadLibrary(filename)
if libc is not None:
+ CLONE_NEWIPC = 0x08000000
CLONE_NEWNET = 0x40000000
+
+ flags = 0
+ if unshare_net:
+ flags |= CLONE_NEWNET
+ if unshare_ipc:
+ flags |= CLONE_NEWIPC
+
try:
- if libc.unshare(CLONE_NEWNET) != 0:
- writemsg("Unable to unshare network: %s\n" % (
+ if libc.unshare(flags) != 0:
+ writemsg("Unable to unshare: %s\n" % (
errno.errorcode.get(ctypes.get_errno(), '?')),
noiselevel=-1)
else:
- # 'up' the loopback
- IFF_UP = 0x1
- ifreq = struct.pack('16sh', b'lo', IFF_UP)
- SIOCSIFFLAGS = 0x8914
-
- sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
- try:
- fcntl.ioctl(sock, SIOCSIFFLAGS, ifreq)
- except IOError as e:
- writemsg("Unable to enable loopback interface: %s\n" % (
- errno.errorcode.get(e.errno, '?')),
- noiselevel=-1)
- sock.close()
+ if unshare_net:
+ # 'up' the loopback
+ IFF_UP = 0x1
+ ifreq = struct.pack('16sh', b'lo', IFF_UP)
+ SIOCSIFFLAGS = 0x8914
+
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
+ try:
+ fcntl.ioctl(sock, SIOCSIFFLAGS, ifreq)
+ except IOError as e:
+ writemsg("Unable to enable loopback interface: %s\n" % (
+ errno.errorcode.get(e.errno, '?')),
+ noiselevel=-1)
+ sock.close()
except AttributeError:
# unshare() not supported by libc
pass