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 |