Add FEATURES=ipc-sandbox to isolate IPC from host.

This way, only privileged phases (pkg_*) can use *nix IPC to communicate
with host applications. src_* use private IPC namespace.
diff --git a/man/make.conf.5 b/man/make.conf.5
index 461172c..91817ae 100644
--- a/man/make.conf.5
+++ b/man/make.conf.5
@@ -385,6 +385,10 @@
 compatibility with the prefix branch of portage, which also supports EPREFIX
 for all EAPIs (for obvious reasons).
 .TP
+.B ipc\-sandbox
+Isolate the ebuild phase functions from host IPC namespace. Supported
+only on Linux. Requires network namespace support in kernel.
+.TP
 .B lmirror
 When \fImirror\fR is enabled in \fBFEATURES\fR, fetch files even
 when \fImirror\fR is also in the \fBebuild\fR(5) \fBRESTRICT\fR variable.
diff --git a/pym/portage/const.py b/pym/portage/const.py
index cde0079..88c199b 100644
--- a/pym/portage/const.py
+++ b/pym/portage/const.py
@@ -102,7 +102,8 @@
                            "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",
diff --git a/pym/portage/package/ebuild/doebuild.py b/pym/portage/package/ebuild/doebuild.py
index a35e717..2d26d2c 100644
--- a/pym/portage/package/ebuild/doebuild.py
+++ b/pym/portage/package/ebuild/doebuild.py
@@ -82,14 +82,18 @@
 	"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",
@@ -120,6 +124,8 @@
 
 	if phase in _unsandboxed_phases:
 		kwargs['free'] = True
+	if phase in _ipc_phases:
+		kwargs['ipc'] = True
 	if phase in _networked_phases:
 		kwargs['networked'] = True
 
@@ -1399,7 +1405,7 @@
 
 # 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:
@@ -1431,6 +1437,8 @@
 	@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
@@ -1459,9 +1467,12 @@
 
 	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
diff --git a/pym/portage/process.py b/pym/portage/process.py
index 6a60dec..22c6a88 100644
--- a/pym/portage/process.py
+++ b/pym/portage/process.py
@@ -184,7 +184,8 @@
 
 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.
 	
@@ -219,6 +220,8 @@
 	@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.)
@@ -285,7 +288,7 @@
 	# 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()
@@ -297,7 +300,7 @@
 			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:
@@ -367,7 +370,7 @@
 	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
@@ -394,6 +397,8 @@
 	@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)
 	"""
@@ -430,32 +435,41 @@
 
 	_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
+						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()
+							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