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:38
Message-Id: 1542543874.8aa33981d83913b6ae92e6ea8e44c1a37fb59872.mgorny@gentoo
1 commit: 8aa33981d83913b6ae92e6ea8e44c1a37fb59872
2 Author: Michał Górny <mgorny <AT> gentoo <DOT> org>
3 AuthorDate: Mon Nov 12 16:32:35 2018 +0000
4 Commit: Michał Górny <mgorny <AT> gentoo <DOT> org>
5 CommitDate: Sun Nov 18 12:24:34 2018 +0000
6 URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=8aa33981
7
8 Add FEATURES=mount-sandbox to take advantage of mount ns
9
10 Support FEATURES=mount-sandbox that unshares the ebuild processes
11 into a new mount namespace and makes all the mounts private by default.
12
13 Reviewed-by: Zac Medico <zmedico <AT> gentoo.org>
14 Signed-off-by: Michał Górny <mgorny <AT> gentoo.org>
15
16 lib/portage/const.py | 1 +
17 lib/portage/package/ebuild/doebuild.py | 7 ++++++-
18 lib/portage/process.py | 34 ++++++++++++++++++++++++++++------
19 man/make.conf.5 | 5 +++++
20 4 files changed, 40 insertions(+), 7 deletions(-)
21
22 diff --git a/lib/portage/const.py b/lib/portage/const.py
23 index 602caeb34..e0f93f7cc 100644
24 --- a/lib/portage/const.py
25 +++ b/lib/portage/const.py
26 @@ -160,6 +160,7 @@ SUPPORTED_FEATURES = frozenset([
27 "merge-sync",
28 "metadata-transfer",
29 "mirror",
30 + "mount-sandbox",
31 "multilib-strict",
32 "network-sandbox",
33 "network-sandbox-proxy",
34
35 diff --git a/lib/portage/package/ebuild/doebuild.py b/lib/portage/package/ebuild/doebuild.py
36 index d0e96f34c..e84a618d2 100644
37 --- a/lib/portage/package/ebuild/doebuild.py
38 +++ b/lib/portage/package/ebuild/doebuild.py
39 @@ -148,6 +148,7 @@ def _doebuild_spawn(phase, settings, actionmap=None, **kwargs):
40
41 kwargs['ipc'] = 'ipc-sandbox' not in settings.features or \
42 phase in _ipc_phases
43 + kwargs['mountns'] = 'mount-sandbox' in settings.features
44 kwargs['networked'] = 'network-sandbox' not in settings.features or \
45 phase in _networked_phases or \
46 'network-sandbox' in settings['PORTAGE_RESTRICT'].split()
47 @@ -1480,7 +1481,8 @@ def _validate_deps(mysettings, myroot, mydo, mydbapi):
48 # XXX This would be to replace getstatusoutput completely.
49 # XXX Issue: cannot block execution. Deadlock condition.
50 def spawn(mystring, mysettings, debug=False, free=False, droppriv=False,
51 - sesandbox=False, fakeroot=False, networked=True, ipc=True, **keywords):
52 + sesandbox=False, fakeroot=False, networked=True, ipc=True,
53 + mountns=False, **keywords):
54 """
55 Spawn a subprocess with extra portage-specific options.
56 Optiosn include:
57 @@ -1514,6 +1516,8 @@ def spawn(mystring, mysettings, debug=False, free=False, droppriv=False,
58 @type networked: Boolean
59 @param ipc: Run this command with host IPC access enabled
60 @type ipc: Boolean
61 + @param mountns: Run this command inside mount namespace
62 + @type mountns: Boolean
63 @param keywords: Extra options encoded as a dict, to be passed to spawn
64 @type keywords: Dictionary
65 @rtype: Integer
66 @@ -1546,6 +1550,7 @@ def spawn(mystring, mysettings, debug=False, free=False, droppriv=False,
67 if uid == 0 and platform.system() == 'Linux':
68 keywords['unshare_net'] = not networked
69 keywords['unshare_ipc'] = not ipc
70 + keywords['unshare_mount'] = mountns
71
72 if not networked and mysettings.get("EBUILD_PHASE") != "nofetch" and \
73 ("network-sandbox-proxy" in features or "distcc" in features):
74
75 diff --git a/lib/portage/process.py b/lib/portage/process.py
76 index fd326731a..46868f442 100644
77 --- a/lib/portage/process.py
78 +++ b/lib/portage/process.py
79 @@ -1,5 +1,5 @@
80 # portage.py -- core Portage functionality
81 -# Copyright 1998-2014 Gentoo Foundation
82 +# Copyright 1998-2018 Gentoo Authors
83 # Distributed under the terms of the GNU General Public License v2
84
85
86 @@ -10,6 +10,7 @@ import platform
87 import signal
88 import socket
89 import struct
90 +import subprocess
91 import sys
92 import traceback
93 import os as _os
94 @@ -222,7 +223,7 @@ def spawn(mycommand, env={}, opt_name=None, fd_pipes=None, returnpid=False,
95 uid=None, gid=None, groups=None, umask=None, logfile=None,
96 path_lookup=True, pre_exec=None,
97 close_fds=(sys.version_info < (3, 4)), unshare_net=False,
98 - unshare_ipc=False, cgroup=None):
99 + unshare_ipc=False, unshare_mount=False, cgroup=None):
100 """
101 Spawns a given command.
102
103 @@ -260,6 +261,9 @@ def spawn(mycommand, env={}, opt_name=None, fd_pipes=None, returnpid=False,
104 @type unshare_net: Boolean
105 @param unshare_ipc: If True, IPC will be unshared from the spawned process
106 @type unshare_ipc: Boolean
107 + @param unshare_mount: If True, mount namespace will be unshared and mounts will
108 + be private to the namespace
109 + @type unshare_mount: Boolean
110 @param cgroup: CGroup path to bind the process to
111 @type cgroup: String
112
113 @@ -328,7 +332,7 @@ def spawn(mycommand, env={}, opt_name=None, fd_pipes=None, returnpid=False,
114 # This caches the libc library lookup in the current
115 # process, so that it's only done once rather than
116 # for each child process.
117 - if unshare_net or unshare_ipc:
118 + if unshare_net or unshare_ipc or unshare_mount:
119 find_library("c")
120
121 # Force instantiation of portage.data.userpriv_groups before the
122 @@ -344,7 +348,7 @@ def spawn(mycommand, env={}, opt_name=None, fd_pipes=None, returnpid=False,
123 try:
124 _exec(binary, mycommand, opt_name, fd_pipes,
125 env, gid, groups, uid, umask, pre_exec, close_fds,
126 - unshare_net, unshare_ipc, cgroup)
127 + unshare_net, unshare_ipc, unshare_mount, cgroup)
128 except SystemExit:
129 raise
130 except Exception as e:
131 @@ -414,7 +418,7 @@ def spawn(mycommand, env={}, opt_name=None, fd_pipes=None, returnpid=False,
132 return 0
133
134 def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask,
135 - pre_exec, close_fds, unshare_net, unshare_ipc, cgroup):
136 + pre_exec, close_fds, unshare_net, unshare_ipc, unshare_mount, cgroup):
137
138 """
139 Execute a given binary with options
140 @@ -443,6 +447,9 @@ def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask,
141 @type unshare_net: Boolean
142 @param unshare_ipc: If True, IPC will be unshared from the spawned process
143 @type unshare_ipc: Boolean
144 + @param unshare_mount: If True, mount namespace will be unshared and mounts will
145 + be private to the namespace
146 + @type unshare_mount: Boolean
147 @param cgroup: CGroup path to bind the process to
148 @type cgroup: String
149 @rtype: None
150 @@ -499,11 +506,13 @@ def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask,
151 f.write('%d\n' % os.getpid())
152
153 # Unshare (while still uid==0)
154 - if unshare_net or unshare_ipc:
155 + if unshare_net or unshare_ipc or unshare_mount:
156 filename = find_library("c")
157 if filename is not None:
158 libc = LoadLibrary(filename)
159 if libc is not None:
160 + # from /usr/include/bits/sched.h
161 + CLONE_NEWNS = 0x00020000
162 CLONE_NEWIPC = 0x08000000
163 CLONE_NEWNET = 0x40000000
164
165 @@ -512,6 +521,9 @@ def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask,
166 flags |= CLONE_NEWNET
167 if unshare_ipc:
168 flags |= CLONE_NEWIPC
169 + if unshare_mount:
170 + # NEWNS = mount namespace
171 + flags |= CLONE_NEWNS
172
173 try:
174 if libc.unshare(flags) != 0:
175 @@ -519,6 +531,16 @@ def _exec(binary, mycommand, opt_name, fd_pipes, env, gid, groups, uid, umask,
176 errno.errorcode.get(ctypes.get_errno(), '?')),
177 noiselevel=-1)
178 else:
179 + if unshare_mount:
180 + # mark the whole filesystem as slave to avoid
181 + # mounts escaping the namespace
182 + s = subprocess.Popen(['mount',
183 + '--make-rslave', '/'])
184 + mount_ret = s.wait()
185 + if mount_ret != 0:
186 + # TODO: should it be fatal maybe?
187 + writemsg("Unable to mark mounts slave: %d\n" % (mount_ret,),
188 + noiselevel=-1)
189 if unshare_net:
190 # 'up' the loopback
191 IFF_UP = 0x1
192
193 diff --git a/man/make.conf.5 b/man/make.conf.5
194 index f69afd015..7cb5741ad 100644
195 --- a/man/make.conf.5
196 +++ b/man/make.conf.5
197 @@ -494,6 +494,11 @@ ${repository_location}/metadata/md5\-cache/ directory will be used directly
198 Fetch everything in \fBSRC_URI\fR regardless of \fBUSE\fR settings,
199 except do not fetch anything when \fImirror\fR is in \fBRESTRICT\fR.
200 .TP
201 +.B mount\-sandbox
202 +Isolate the ebuild phase functions from host mount namespace. This makes
203 +it possible for ebuild to alter mountpoints without affecting the host
204 +system. Supported only on Linux. Requires mount namespace support in kernel.
205 +.TP
206 .B multilib\-strict
207 Many Makefiles assume that their libraries should go to /usr/lib, or
208 $(prefix)/lib. This assumption can cause a serious mess if /usr/lib