Gentoo Archives: gentoo-commits

From: "Michał Górny" <mgorny@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] proj/portage:master commit in: lib/portage/package/ebuild/, man/, lib/portage/
Date: Sun, 18 Nov 2018 12:25:36
Message-Id: 1542543892.88ba0f45b54609666445c7f8008ff699eef52b39.mgorny@gentoo
1 commit: 88ba0f45b54609666445c7f8008ff699eef52b39
2 Author: Michał Górny <mgorny <AT> gentoo <DOT> org>
3 AuthorDate: Sun Nov 11 11:48:24 2018 +0000
4 Commit: Michał Górny <mgorny <AT> gentoo <DOT> org>
5 CommitDate: Sun Nov 18 12:24:52 2018 +0000
6 URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=88ba0f45
7
8 Support FEATURES=pid-sandbox
9
10 Supporting using PID namespace in order to isolate the ebuild processes
11 from host system, and make it possible to kill them all easily
12 (similarly to cgroups but easier to use).
13
14 Bug: https://bugs.gentoo.org/659582
15 Reviewed-by: Zac Medico <zmedico <AT> gentoo.org>
16 Signed-off-by: Michał Górny <mgorny <AT> gentoo.org>
17
18 lib/portage/const.py | 1 +
19 lib/portage/package/ebuild/doebuild.py | 8 ++++--
20 lib/portage/process.py | 48 ++++++++++++++++++++++++++++++----
21 man/make.conf.5 | 7 +++++
22 4 files changed, 57 insertions(+), 7 deletions(-)
23
24 diff --git a/lib/portage/const.py b/lib/portage/const.py
25 index e0f93f7cc..ca66bc46e 100644
26 --- a/lib/portage/const.py
27 +++ b/lib/portage/const.py
28 @@ -174,6 +174,7 @@ SUPPORTED_FEATURES = frozenset([
29 "notitles",
30 "parallel-fetch",
31 "parallel-install",
32 + "pid-sandbox",
33 "prelink-checksums",
34 "preserve-libs",
35 "protect-owned",
36
37 diff --git a/lib/portage/package/ebuild/doebuild.py b/lib/portage/package/ebuild/doebuild.py
38 index e84a618d2..9917ac82c 100644
39 --- a/lib/portage/package/ebuild/doebuild.py
40 +++ b/lib/portage/package/ebuild/doebuild.py
41 @@ -1,4 +1,4 @@
42 -# Copyright 2010-2018 Gentoo Foundation
43 +# Copyright 2010-2018 Gentoo Authors
44 # Distributed under the terms of the GNU General Public License v2
45
46 from __future__ import unicode_literals
47 @@ -152,6 +152,7 @@ def _doebuild_spawn(phase, settings, actionmap=None, **kwargs):
48 kwargs['networked'] = 'network-sandbox' not in settings.features or \
49 phase in _networked_phases or \
50 'network-sandbox' in settings['PORTAGE_RESTRICT'].split()
51 + kwargs['pidns'] = 'pid-sandbox' in settings.features
52
53 if phase == 'depend':
54 kwargs['droppriv'] = 'userpriv' in settings.features
55 @@ -1482,7 +1483,7 @@ def _validate_deps(mysettings, myroot, mydo, mydbapi):
56 # XXX Issue: cannot block execution. Deadlock condition.
57 def spawn(mystring, mysettings, debug=False, free=False, droppriv=False,
58 sesandbox=False, fakeroot=False, networked=True, ipc=True,
59 - mountns=False, **keywords):
60 + mountns=False, pidns=False, **keywords):
61 """
62 Spawn a subprocess with extra portage-specific options.
63 Optiosn include:
64 @@ -1518,6 +1519,8 @@ def spawn(mystring, mysettings, debug=False, free=False, droppriv=False,
65 @type ipc: Boolean
66 @param mountns: Run this command inside mount namespace
67 @type mountns: Boolean
68 + @param pidns: Run this command in isolated PID namespace
69 + @type pidns: Boolean
70 @param keywords: Extra options encoded as a dict, to be passed to spawn
71 @type keywords: Dictionary
72 @rtype: Integer
73 @@ -1551,6 +1554,7 @@ def spawn(mystring, mysettings, debug=False, free=False, droppriv=False,
74 keywords['unshare_net'] = not networked
75 keywords['unshare_ipc'] = not ipc
76 keywords['unshare_mount'] = mountns
77 + keywords['unshare_pid'] = pidns
78
79 if not networked and mysettings.get("EBUILD_PHASE") != "nofetch" and \
80 ("network-sandbox-proxy" in features or "distcc" in features):
81
82 diff --git a/lib/portage/process.py b/lib/portage/process.py
83 index 46868f442..dee126c3c 100644
84 --- a/lib/portage/process.py
85 +++ b/lib/portage/process.py
86 @@ -223,7 +223,8 @@ def spawn(mycommand, env={}, opt_name=None, fd_pipes=None, returnpid=False,
87 uid=None, gid=None, groups=None, umask=None, logfile=None,
88 path_lookup=True, pre_exec=None,
89 close_fds=(sys.version_info < (3, 4)), unshare_net=False,
90 - unshare_ipc=False, unshare_mount=False, cgroup=None):
91 + unshare_ipc=False, unshare_mount=False, unshare_pid=False,
92 + cgroup=None):
93 """
94 Spawns a given command.
95
96 @@ -264,6 +265,8 @@ def spawn(mycommand, env={}, opt_name=None, fd_pipes=None, returnpid=False,
97 @param unshare_mount: If True, mount namespace will be unshared and mounts will
98 be private to the namespace
99 @type unshare_mount: Boolean
100 + @param unshare_pid: If True, PID ns will be unshared from the spawned process
101 + @type unshare_pid: Boolean
102 @param cgroup: CGroup path to bind the process to
103 @type cgroup: String
104
105 @@ -332,7 +335,7 @@ def spawn(mycommand, env={}, opt_name=None, fd_pipes=None, returnpid=False,
106 # This caches the libc library lookup in the current
107 # process, so that it's only done once rather than
108 # for each child process.
109 - if unshare_net or unshare_ipc or unshare_mount:
110 + if unshare_net or unshare_ipc or unshare_mount or unshare_pid:
111 find_library("c")
112
113 # Force instantiation of portage.data.userpriv_groups before the
114 @@ -348,7 +351,8 @@ def spawn(mycommand, env={}, opt_name=None, fd_pipes=None, returnpid=False,
115 try:
116 _exec(binary, mycommand, opt_name, fd_pipes,
117 env, gid, groups, uid, umask, pre_exec, close_fds,
118 - unshare_net, unshare_ipc, unshare_mount, cgroup)
119 + unshare_net, unshare_ipc, unshare_mount, unshare_pid,
120 + cgroup)
121 except SystemExit:
122 raise
123 except Exception as e:
124 @@ -418,7 +422,8 @@ def spawn(mycommand, env={}, opt_name=None, fd_pipes=None, returnpid=False,
125 return 0
126
127 def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask,
128 - pre_exec, close_fds, unshare_net, unshare_ipc, unshare_mount, cgroup):
129 + pre_exec, close_fds, unshare_net, unshare_ipc, unshare_mount, unshare_pid,
130 + cgroup):
131
132 """
133 Execute a given binary with options
134 @@ -450,6 +455,8 @@ def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask,
135 @param unshare_mount: If True, mount namespace will be unshared and mounts will
136 be private to the namespace
137 @type unshare_mount: Boolean
138 + @param unshare_pid: If True, PID ns will be unshared from the spawned process
139 + @type unshare_pid: Boolean
140 @param cgroup: CGroup path to bind the process to
141 @type cgroup: String
142 @rtype: None
143 @@ -506,7 +513,7 @@ def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask,
144 f.write('%d\n' % os.getpid())
145
146 # Unshare (while still uid==0)
147 - if unshare_net or unshare_ipc or unshare_mount:
148 + if unshare_net or unshare_ipc or unshare_mount or unshare_pid:
149 filename = find_library("c")
150 if filename is not None:
151 libc = LoadLibrary(filename)
152 @@ -514,6 +521,7 @@ def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask,
153 # from /usr/include/bits/sched.h
154 CLONE_NEWNS = 0x00020000
155 CLONE_NEWIPC = 0x08000000
156 + CLONE_NEWPID = 0x20000000
157 CLONE_NEWNET = 0x40000000
158
159 flags = 0
160 @@ -524,6 +532,9 @@ def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask,
161 if unshare_mount:
162 # NEWNS = mount namespace
163 flags |= CLONE_NEWNS
164 + if unshare_pid:
165 + # we also need mount namespace for slave /proc
166 + flags |= CLONE_NEWPID | CLONE_NEWNS
167
168 try:
169 if libc.unshare(flags) != 0:
170 @@ -531,6 +542,15 @@ def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask,
171 errno.errorcode.get(ctypes.get_errno(), '?')),
172 noiselevel=-1)
173 else:
174 + if unshare_pid:
175 + # pid namespace requires us to become init
176 + # TODO: do init-ty stuff
177 + # therefore, fork() ASAP
178 + fork_ret = os.fork()
179 + if fork_ret != 0:
180 + pid, status = os.waitpid(fork_ret, 0)
181 + assert pid == fork_ret
182 + os._exit(status)
183 if unshare_mount:
184 # mark the whole filesystem as slave to avoid
185 # mounts escaping the namespace
186 @@ -541,6 +561,24 @@ def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask,
187 # TODO: should it be fatal maybe?
188 writemsg("Unable to mark mounts slave: %d\n" % (mount_ret,),
189 noiselevel=-1)
190 + if unshare_pid:
191 + # we need at least /proc being slave
192 + s = subprocess.Popen(['mount',
193 + '--make-slave', '/proc'])
194 + mount_ret = s.wait()
195 + if mount_ret != 0:
196 + # can't proceed with shared /proc
197 + writemsg("Unable to mark /proc slave: %d\n" % (mount_ret,),
198 + noiselevel=-1)
199 + os._exit(1)
200 + # mount new /proc for our namespace
201 + s = subprocess.Popen(['mount',
202 + '-t', 'proc', 'proc', '/proc'])
203 + mount_ret = s.wait()
204 + if mount_ret != 0:
205 + writemsg("Unable to mount new /proc: %d\n" % (mount_ret,),
206 + noiselevel=-1)
207 + os._exit(1)
208 if unshare_net:
209 # 'up' the loopback
210 IFF_UP = 0x1
211
212 diff --git a/man/make.conf.5 b/man/make.conf.5
213 index 7cb5741ad..de04e5e34 100644
214 --- a/man/make.conf.5
215 +++ b/man/make.conf.5
216 @@ -558,6 +558,13 @@ Use finer\-grained locks when installing packages, allowing for greater
217 parallelization. For additional parallelization, disable
218 \fIebuild\-locks\fR.
219 .TP
220 +.B pid\-sandbox
221 +Isolate the process space for the ebuild processes. This makes it
222 +possible to cleanly kill all processes spawned by the ebuild.
223 +Supported only on Linux. Requires PID and mount namespace support
224 +in kernel. /proc is remounted inside the mount namespace to account
225 +for new PID namespace.
226 +.TP
227 .B prelink\-checksums
228 If \fBprelink\fR(8) is installed then use it to undo any prelinks on files
229 before computing checksums for merge and unmerge. This feature is