Gentoo Archives: gentoo-commits

From: Zac Medico <zmedico@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] proj/portage:master commit in: lib/portage/
Date: Fri, 04 Jan 2019 03:49:57
Message-Id: 1546571089.c2a9850a25b2f32a25b43ef30189cd6657f397ad.zmedico@gentoo
1 commit: c2a9850a25b2f32a25b43ef30189cd6657f397ad
2 Author: Zac Medico <zmedico <AT> gentoo <DOT> org>
3 AuthorDate: Sat Dec 29 06:56:40 2018 +0000
4 Commit: Zac Medico <zmedico <AT> gentoo <DOT> org>
5 CommitDate: Fri Jan 4 03:04:49 2019 +0000
6 URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=c2a9850a
7
8 process.spawn: validate unshare calls (bug 673900)
9
10 In order to prevent failed unshare calls from corrupting the state
11 of an essential process, validate the relevant unshare call in a
12 short-lived subprocess. An unshare call is considered valid if it
13 successfully executes in a short-lived subprocess.
14
15 Bug: https://bugs.gentoo.org/673900
16 Signed-off-by: Zac Medico <zmedico <AT> gentoo.org>
17
18 lib/portage/process.py | 159 +++++++++++++++++++++++++++++++++++++++++--------
19 1 file changed, 133 insertions(+), 26 deletions(-)
20
21 diff --git a/lib/portage/process.py b/lib/portage/process.py
22 index ce3e42a8f..7103b6b31 100644
23 --- a/lib/portage/process.py
24 +++ b/lib/portage/process.py
25 @@ -6,6 +6,7 @@
26 import atexit
27 import errno
28 import fcntl
29 +import multiprocessing
30 import platform
31 import signal
32 import socket
33 @@ -338,11 +339,29 @@ def spawn(mycommand, env=None, opt_name=None, fd_pipes=None, returnpid=False,
34 fd_pipes[1] = pw
35 fd_pipes[2] = pw
36
37 - # This caches the libc library lookup in the current
38 - # process, so that it's only done once rather than
39 - # for each child process.
40 + # This caches the libc library lookup and _unshare_validator results
41 + # in the current process, so that results are cached for use in
42 + # child processes.
43 + unshare_flags = 0
44 if unshare_net or unshare_ipc or unshare_mount or unshare_pid:
45 - find_library("c")
46 + # from /usr/include/bits/sched.h
47 + CLONE_NEWNS = 0x00020000
48 + CLONE_NEWIPC = 0x08000000
49 + CLONE_NEWPID = 0x20000000
50 + CLONE_NEWNET = 0x40000000
51 +
52 + if unshare_net:
53 + unshare_flags |= CLONE_NEWNET
54 + if unshare_ipc:
55 + unshare_flags |= CLONE_NEWIPC
56 + if unshare_mount:
57 + # NEWNS = mount namespace
58 + unshare_flags |= CLONE_NEWNS
59 + if unshare_pid:
60 + # we also need mount namespace for slave /proc
61 + unshare_flags |= CLONE_NEWPID | CLONE_NEWNS
62 +
63 + _unshare_validate(unshare_flags)
64
65 # Force instantiation of portage.data.userpriv_groups before the
66 # fork, so that the result is cached in the main process.
67 @@ -358,7 +377,7 @@ def spawn(mycommand, env=None, opt_name=None, fd_pipes=None, returnpid=False,
68 _exec(binary, mycommand, opt_name, fd_pipes,
69 env, gid, groups, uid, umask, cwd, pre_exec, close_fds,
70 unshare_net, unshare_ipc, unshare_mount, unshare_pid,
71 - cgroup)
72 + unshare_flags, cgroup)
73 except SystemExit:
74 raise
75 except Exception as e:
76 @@ -430,7 +449,7 @@ def spawn(mycommand, env=None, opt_name=None, fd_pipes=None, returnpid=False,
77 def _exec(binary, mycommand, opt_name, fd_pipes,
78 env, gid, groups, uid, umask, cwd,
79 pre_exec, close_fds, unshare_net, unshare_ipc, unshare_mount, unshare_pid,
80 - cgroup):
81 + unshare_flags, cgroup):
82
83 """
84 Execute a given binary with options
85 @@ -466,6 +485,8 @@ def _exec(binary, mycommand, opt_name, fd_pipes,
86 @type unshare_mount: Boolean
87 @param unshare_pid: If True, PID ns will be unshared from the spawned process
88 @type unshare_pid: Boolean
89 + @param unshare_flags: Flags for the unshare(2) function
90 + @type unshare_flags: Integer
91 @param cgroup: CGroup path to bind the process to
92 @type cgroup: String
93 @rtype: None
94 @@ -527,28 +548,19 @@ def _exec(binary, mycommand, opt_name, fd_pipes,
95 if filename is not None:
96 libc = LoadLibrary(filename)
97 if libc is not None:
98 - # from /usr/include/bits/sched.h
99 - CLONE_NEWNS = 0x00020000
100 - CLONE_NEWIPC = 0x08000000
101 - CLONE_NEWPID = 0x20000000
102 - CLONE_NEWNET = 0x40000000
103 -
104 - flags = 0
105 - if unshare_net:
106 - flags |= CLONE_NEWNET
107 - if unshare_ipc:
108 - flags |= CLONE_NEWIPC
109 - if unshare_mount:
110 - # NEWNS = mount namespace
111 - flags |= CLONE_NEWNS
112 - if unshare_pid:
113 - # we also need mount namespace for slave /proc
114 - flags |= CLONE_NEWPID | CLONE_NEWNS
115 -
116 try:
117 - if libc.unshare(flags) != 0:
118 + # Since a failed unshare call could corrupt process
119 + # state, first validate that the call can succeed.
120 + # The parent process should call _unshare_validate
121 + # before it forks, so that all child processes can
122 + # reuse _unshare_validate results that have been
123 + # cached by the parent process.
124 + errno_value = _unshare_validate(unshare_flags)
125 + if errno_value == 0 and libc.unshare(unshare_flags) != 0:
126 + errno_value = ctypes.get_errno()
127 + if errno_value != 0:
128 writemsg("Unable to unshare: %s\n" % (
129 - errno.errorcode.get(ctypes.get_errno(), '?')),
130 + errno.errorcode.get(errno_value, '?')),
131 noiselevel=-1)
132 else:
133 if unshare_pid:
134 @@ -626,6 +638,101 @@ def _exec(binary, mycommand, opt_name, fd_pipes,
135 # And switch to the new process.
136 os.execve(binary, myargs, env)
137
138 +
139 +class _unshare_validator(object):
140 + """
141 + In order to prevent failed unshare calls from corrupting the state
142 + of an essential process, validate the relevant unshare call in a
143 + short-lived subprocess. An unshare call is considered valid if it
144 + successfully executes in a short-lived subprocess.
145 + """
146 +
147 + def __init__(self):
148 + self._results = {}
149 +
150 + def __call__(self, flags):
151 + """
152 + Validate unshare with the given flags. Results are cached.
153 +
154 + @rtype: int
155 + @returns: errno value, or 0 if no error occurred.
156 + """
157 +
158 + try:
159 + return self._results[flags]
160 + except KeyError:
161 + result = self._results[flags] = self._validate(flags)
162 + return result
163 +
164 + @classmethod
165 + def _validate(cls, flags):
166 + """
167 + Perform validation.
168 +
169 + @param flags: unshare flags
170 + @type flags: int
171 + @rtype: int
172 + @returns: errno value, or 0 if no error occurred.
173 + """
174 + filename = find_library("c")
175 + if filename is None:
176 + return errno.ENOTSUP
177 +
178 + libc = LoadLibrary(filename)
179 + if libc is None:
180 + return errno.ENOTSUP
181 +
182 + parent_pipe, subproc_pipe = multiprocessing.Pipe(duplex=False)
183 +
184 + proc = multiprocessing.Process(
185 + target=cls._run_subproc,
186 + args=(subproc_pipe, cls._validate_subproc, (libc.unshare, flags)))
187 + proc.start()
188 + subproc_pipe.close()
189 +
190 + result = parent_pipe.recv()
191 + parent_pipe.close()
192 + proc.join()
193 +
194 + return result
195 +
196 + @staticmethod
197 + def _run_subproc(subproc_pipe, target, args=(), kwargs={}):
198 + """
199 + Call function and send return value to parent process.
200 +
201 + @param subproc_pipe: connection to parent process
202 + @type subproc_pipe: multiprocessing.Connection
203 + @param target: target is the callable object to be invoked
204 + @type target: callable
205 + @param args: the argument tuple for the target invocation
206 + @type args: tuple
207 + @param kwargs: dictionary of keyword arguments for the target invocation
208 + @type kwargs: dict
209 + """
210 + subproc_pipe.send(target(*args, **kwargs))
211 + subproc_pipe.close()
212 +
213 + @staticmethod
214 + def _validate_subproc(unshare, flags):
215 + """
216 + Perform validation. Calls to this method must be isolated in a
217 + subprocess, since the unshare function is called for purposes of
218 + validation.
219 +
220 + @param unshare: unshare function
221 + @type unshare: callable
222 + @param flags: unshare flags
223 + @type flags: int
224 + @rtype: int
225 + @returns: errno value, or 0 if no error occurred.
226 + """
227 + return 0 if unshare(flags) == 0 else ctypes.get_errno()
228 +
229 +
230 +_unshare_validate = _unshare_validator()
231 +
232 +
233 def _setup_pipes(fd_pipes, close_fds=True, inheritable=None):
234 """Setup pipes for a forked process.