Add FEATURES=network-sandbox support, bug #481450
[portage.git] / pym / portage / package / ebuild / doebuild.py
index 1d44b93ab186a1278c599c3a87b878ad60d3371c..a35e717191c3dee76fae854934bce0287e30b0da 100644 (file)
@@ -12,6 +12,7 @@ import io
 from itertools import chain
 import logging
 import os as _os
+import platform
 import pwd
 import re
 import signal
@@ -81,6 +82,15 @@ _unsandboxed_phases = frozenset([
        "prerm", "setup"
 ])
 
+# phases in which networking access is allowed
+_networked_phases = frozenset([
+       # for VCS fetching
+       "unpack",
+       # for IPC
+       "setup", "pretend",
+       "preinst", "postinst", "prerm", "postrm",
+])
+
 _phase_func_map = {
        "config": "pkg_config",
        "setup": "pkg_setup",
@@ -110,6 +120,8 @@ def _doebuild_spawn(phase, settings, actionmap=None, **kwargs):
 
        if phase in _unsandboxed_phases:
                kwargs['free'] = True
+       if phase in _networked_phases:
+               kwargs['networked'] = True
 
        if phase == 'depend':
                kwargs['droppriv'] = 'userpriv' in settings.features
@@ -305,10 +317,11 @@ def doebuild_environment(myebuild, mydo, myroot=None, settings=None,
        if hasattr(mydbapi, 'repositories'):
                repo = mydbapi.repositories.get_repo_for_location(mytree)
                mysettings['PORTDIR'] = repo.eclass_db.porttrees[0]
-               mysettings['PORTDIR_OVERLAY'] = ' '.join(repo.eclass_db.porttrees[1:])
+               mysettings['PORTAGE_ECLASS_LOCATIONS'] = repo.eclass_db.eclass_locations_string
                mysettings.configdict["pkg"]["PORTAGE_REPO_NAME"] = repo.name
 
        mysettings["PORTDIR"] = os.path.realpath(mysettings["PORTDIR"])
+       mysettings.pop("PORTDIR_OVERLAY", None)
        mysettings["DISTDIR"] = os.path.realpath(mysettings["DISTDIR"])
        mysettings["RPMDIR"]  = os.path.realpath(mysettings["RPMDIR"])
 
@@ -574,7 +587,7 @@ def doebuild(myebuild, mydo, _unused=DeprecationWarning, settings=None, debug=0,
                        "fetch", "fetchall", "digest",
                        "unpack", "prepare", "configure", "compile", "test",
                        "install", "rpm", "qmerge", "merge",
-                       "package","unmerge", "manifest"]
+                       "package", "unmerge", "manifest", "nofetch"]
 
        if mydo not in validcommands:
                validcommands.sort()
@@ -745,6 +758,16 @@ def doebuild(myebuild, mydo, _unused=DeprecationWarning, settings=None, debug=0,
                        return _spawn_phase(mydo, mysettings,
                                fd_pipes=fd_pipes, returnpid=returnpid)
 
+               elif mydo == "nofetch":
+
+                       if returnpid:
+                               writemsg("!!! doebuild: %s\n" %
+                                       _("returnpid is not supported for phase '%s'\n" % mydo),
+                                       noiselevel=-1)
+
+                       return spawn_nofetch(mydbapi, myebuild, settings=mysettings,
+                               fd_pipes=fd_pipes)
+
                if tree == "porttree":
 
                        if not returnpid:
@@ -762,9 +785,9 @@ def doebuild(myebuild, mydo, _unused=DeprecationWarning, settings=None, debug=0,
                        if "noauto" in mysettings.features:
                                mysettings.features.discard("noauto")
 
-               # The info phase is special because it uses mkdtemp so and
-               # user (not necessarily in the portage group) can run it.
-               if mydo not in ('info',) and \
+               # If we are not using a private temp dir, then check access
+               # to the global temp dir.
+               if tmpdir is None and \
                        mydo not in _doebuild_commands_without_builddir:
                        rval = _check_temp_dir(mysettings)
                        if rval != os.EX_OK:
@@ -1376,7 +1399,7 @@ def _validate_deps(mysettings, myroot, mydo, mydbapi):
 
 # 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, **keywords):
+def spawn(mystring, mysettings, debug=0, free=0, droppriv=0, sesandbox=0, fakeroot=0, networked=0, **keywords):
        """
        Spawn a subprocess with extra portage-specific options.
        Optiosn include:
@@ -1406,6 +1429,8 @@ def spawn(mystring, mysettings, debug=0, free=0, droppriv=0, sesandbox=0, fakero
        @type sesandbox: Boolean
        @param fakeroot: Run this command with faked root privileges
        @type fakeroot: Boolean
+       @param networked: Run this command with networking access enabled
+       @type networked: Boolean
        @param keywords: Extra options encoded as a dict, to be passed to spawn
        @type keywords: Dictionary
        @rtype: Integer
@@ -1433,6 +1458,11 @@ def spawn(mystring, mysettings, debug=0, free=0, droppriv=0, sesandbox=0, fakero
                        break
 
        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
+
        # TODO: Enable fakeroot to be used together with droppriv.  The
        # fake ownership/permissions will have to be converted to real
        # permissions in the merge phase.
@@ -1448,8 +1478,10 @@ def spawn(mystring, mysettings, debug=0, free=0, droppriv=0, sesandbox=0, fakero
                                "umask": 0o02
                        })
                if "userpriv" in features and "userpriv" not in mysettings["PORTAGE_RESTRICT"].split() and secpass >= 2:
-                       portage_build_uid = portage_uid
-                       portage_build_gid = portage_gid
+                       # Since Python 3.4, getpwuid and getgrgid
+                       # require int type (no proxies).
+                       portage_build_uid = int(portage_uid)
+                       portage_build_gid = int(portage_gid)
 
        if "PORTAGE_BUILD_USER" not in mysettings:
                user = None