Gentoo Archives: gentoo-portage-dev

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

Replies