Gentoo Archives: gentoo-portage-dev

From: Zac Medico <zmedico@g.o>
To: gentoo-portage-dev@l.g.o
Cc: Zac Medico <zmedico@g.o>
Subject: [gentoo-portage-dev] [PATCH] fetch: drop privileges early for NFS root_squash (bug 601252)
Date: Sat, 15 Feb 2020 22:42:55
Message-Id: 20200215223859.45671-1-zmedico@gentoo.org
1 Drop privileges prior to fetch function calls, so that
2 all necessary operations can succeed when DISTDIR is
3 on NFS with root_squash enabled.
4
5 Bug: https://bugs.gentoo.org/601252
6 Signed-off-by: Zac Medico <zmedico@g.o>
7 ---
8 lib/_emerge/EbuildFetcher.py | 12 +++++++++++-
9 lib/portage/package/ebuild/doebuild.py | 20 +++++++++++++++----
10 lib/portage/package/ebuild/fetch.py | 27 ++++++++++++++++++++++++++
11 3 files changed, 54 insertions(+), 5 deletions(-)
12
13 diff --git a/lib/_emerge/EbuildFetcher.py b/lib/_emerge/EbuildFetcher.py
14 index ad5109c28..b9dc3a10c 100644
15 --- a/lib/_emerge/EbuildFetcher.py
16 +++ b/lib/_emerge/EbuildFetcher.py
17 @@ -12,7 +12,12 @@ from portage import _unicode_encode
18 from portage import _unicode_decode
19 from portage.checksum import _hash_filter
20 from portage.elog.messages import eerror
21 -from portage.package.ebuild.fetch import _check_distfile, fetch
22 +from portage.package.ebuild.fetch import (
23 + _check_distfile,
24 + _drop_privs_userfetch,
25 + _want_userfetch,
26 + fetch,
27 +)
28 from portage.util._async.AsyncTaskFuture import AsyncTaskFuture
29 from portage.util._async.ForkProcess import ForkProcess
30 from portage.util._pty import _create_pty_or_pipe
31 @@ -229,6 +234,11 @@ class _EbuildFetcherProcess(ForkProcess):
32 self._settings = None
33
34 def _run(self):
35 + # For userfetch, drop privileges for the entire fetch call, in
36 + # order to handle DISTDIR on NFS with root_squash for bug 601252.
37 + if _want_userfetch(self._settings):
38 + _drop_privs_userfetch(self._settings)
39 +
40 # Force consistent color output, in case we are capturing fetch
41 # output through a normal pipe due to unavailability of ptys.
42 portage.output.havecolor = self._settings.get('NOCOLOR') \
43 diff --git a/lib/portage/package/ebuild/doebuild.py b/lib/portage/package/ebuild/doebuild.py
44 index 92e9d755c..be4a6019d 100644
45 --- a/lib/portage/package/ebuild/doebuild.py
46 +++ b/lib/portage/package/ebuild/doebuild.py
47 @@ -30,7 +30,7 @@ portage.proxy.lazyimport.lazyimport(globals(),
48 'portage.package.ebuild.config:check_config_instance',
49 'portage.package.ebuild.digestcheck:digestcheck',
50 'portage.package.ebuild.digestgen:digestgen',
51 - 'portage.package.ebuild.fetch:fetch',
52 + 'portage.package.ebuild.fetch:_drop_privs_userfetch,_want_userfetch,fetch',
53 'portage.package.ebuild.prepare_build_dirs:_prepare_fake_distdir',
54 'portage.package.ebuild._ipc.QueryCommand:QueryCommand',
55 'portage.dep._slot_operator:evaluate_slot_operator_equal_deps',
56 @@ -83,6 +83,7 @@ from portage.util.cpuinfo import get_cpu_count
57 from portage.util.lafilefixer import rewrite_lafile
58 from portage.util.compression_probe import _compressors
59 from portage.util.futures import asyncio
60 +from portage.util.futures.executor.fork import ForkExecutor
61 from portage.util.path import first_existing
62 from portage.util.socks5 import get_socks5_proxy
63 from portage.versions import _pkgsplit
64 @@ -1082,9 +1083,20 @@ def doebuild(myebuild, mydo, _unused=DeprecationWarning, settings=None, debug=0,
65 dist_digests = None
66 if mf is not None:
67 dist_digests = mf.getTypeDigests("DIST")
68 - if not fetch(fetchme, mysettings, listonly=listonly,
69 - fetchonly=fetchonly, allow_missing_digests=False,
70 - digests=dist_digests):
71 +
72 + def _fetch_subprocess(fetchme, mysettings, listonly, dist_digests):
73 + # For userfetch, drop privileges for the entire fetch call, in
74 + # order to handle DISTDIR on NFS with root_squash for bug 601252.
75 + if _want_userfetch(mysettings):
76 + _drop_privs_userfetch(mysettings)
77 +
78 + return fetch(fetchme, mysettings, listonly=listonly,
79 + fetchonly=fetchonly, allow_missing_digests=False,
80 + digests=dist_digests)
81 +
82 + loop = asyncio._safe_loop()
83 + if not loop.run_until_complete(loop.run_in_executor(ForkExecutor(loop=loop),
84 + _fetch_subprocess, fetchme, mysettings, listonly, dist_digests)):
85 # Since listonly mode is called by emerge --pretend in an
86 # asynchronous context, spawn_nofetch would trigger event loop
87 # recursion here, therefore delegate execution of pkg_nofetch
88 diff --git a/lib/portage/package/ebuild/fetch.py b/lib/portage/package/ebuild/fetch.py
89 index 06118b1a6..6189c0245 100644
90 --- a/lib/portage/package/ebuild/fetch.py
91 +++ b/lib/portage/package/ebuild/fetch.py
92 @@ -69,6 +69,33 @@ _userpriv_spawn_kwargs = (
93 def _hide_url_passwd(url):
94 return re.sub(r'//(.+):.+@(.+)', r'//\1:*password*@\2', url)
95
96 +
97 +def _want_userfetch(settings):
98 + """
99 + Check if it's desirable to drop privileges for userfetch.
100 +
101 + @param settings: portage config
102 + @type settings: portage.package.ebuild.config.config
103 + @return: True if desirable, False otherwise
104 + """
105 + return ('userfetch' in settings.features and
106 + portage.data.secpass >= 2 and os.getuid() == 0)
107 +
108 +
109 +def _drop_privs_userfetch(settings):
110 + """
111 + Drop privileges for userfetch, and update portage.data.secpass
112 + to correspond to the new privilege level.
113 + """
114 + spawn_kwargs = dict(_userpriv_spawn_kwargs)
115 + _ensure_distdir(settings, settings['DISTDIR'])
116 + os.setgid(int(spawn_kwargs['gid']))
117 + os.setgroups(spawn_kwargs['groups'])
118 + os.setuid(int(spawn_kwargs['uid']))
119 + os.umask(spawn_kwargs['umask'])
120 + portage.data.secpass = 1
121 +
122 +
123 def _spawn_fetch(settings, args, **kwargs):
124 """
125 Spawn a process with appropriate settings for fetching, including
126 --
127 2.24.1