1 |
commit: a1e4f4e2eaaba19d9bf5b30fe799df91b8613a6b |
2 |
Author: Manuel Rüger <mrueg <AT> gentoo <DOT> org> |
3 |
AuthorDate: Mon Feb 11 17:43:06 2019 +0000 |
4 |
Commit: Manuel Rüger <mrueg <AT> gentoo <DOT> org> |
5 |
CommitDate: Mon Feb 11 17:44:14 2019 +0000 |
6 |
URL: https://gitweb.gentoo.org/repo/gentoo.git/commit/?id=a1e4f4e2 |
7 |
|
8 |
app-emulation/runc: Fix security vuln |
9 |
|
10 |
Package-Manager: Portage-2.3.59, Repoman-2.3.12 |
11 |
Signed-off-by: Manuel Rüger <mrueg <AT> gentoo.org> |
12 |
|
13 |
app-emulation/runc/files/runc-fix-cve.patch | 334 +++++++++++++++++++++ |
14 |
.../runc/runc-1.0.0_rc6_p20181203-r1.ebuild | 65 ++++ |
15 |
2 files changed, 399 insertions(+) |
16 |
|
17 |
diff --git a/app-emulation/runc/files/runc-fix-cve.patch b/app-emulation/runc/files/runc-fix-cve.patch |
18 |
new file mode 100644 |
19 |
index 00000000000..fa85cb0444f |
20 |
--- /dev/null |
21 |
+++ b/app-emulation/runc/files/runc-fix-cve.patch |
22 |
@@ -0,0 +1,334 @@ |
23 |
+From 0a8e4117e7f715d5fbeef398405813ce8e88558b Mon Sep 17 00:00:00 2001 |
24 |
+From: Aleksa Sarai <asarai@××××.de> |
25 |
+Date: Wed, 9 Jan 2019 13:40:01 +1100 |
26 |
+Subject: [PATCH] nsenter: clone /proc/self/exe to avoid exposing host binary |
27 |
+ to container |
28 |
+ |
29 |
+There are quite a few circumstances where /proc/self/exe pointing to a |
30 |
+pretty important container binary is a _bad_ thing, so to avoid this we |
31 |
+have to make a copy (preferably doing self-clean-up and not being |
32 |
+writeable). |
33 |
+ |
34 |
+We require memfd_create(2) -- though there is an O_TMPFILE fallback -- |
35 |
+but we can always extend this to use a scratch MNT_DETACH overlayfs or |
36 |
+tmpfs. The main downside to this approach is no page-cache sharing for |
37 |
+the runc binary (which overlayfs would give us) but this is far less |
38 |
+complicated. |
39 |
+ |
40 |
+This is only done during nsenter so that it happens transparently to the |
41 |
+Go code, and any libcontainer users benefit from it. This also makes |
42 |
+ExtraFiles and --preserve-fds handling trivial (because we don't need to |
43 |
+worry about it). |
44 |
+ |
45 |
+Fixes: CVE-2019-5736 |
46 |
+Co-developed-by: Christian Brauner <christian.brauner@××××××.com> |
47 |
+Signed-off-by: Aleksa Sarai <asarai@××××.de> |
48 |
+--- |
49 |
+ libcontainer/nsenter/cloned_binary.c | 268 +++++++++++++++++++++++++++ |
50 |
+ libcontainer/nsenter/nsexec.c | 11 ++ |
51 |
+ 2 files changed, 279 insertions(+) |
52 |
+ create mode 100644 libcontainer/nsenter/cloned_binary.c |
53 |
+ |
54 |
+diff --git a/libcontainer/nsenter/cloned_binary.c b/libcontainer/nsenter/cloned_binary.c |
55 |
+new file mode 100644 |
56 |
+index 000000000..c8a42c23f |
57 |
+--- /dev/null |
58 |
++++ b/libcontainer/nsenter/cloned_binary.c |
59 |
+@@ -0,0 +1,268 @@ |
60 |
++/* |
61 |
++ * Copyright (C) 2019 Aleksa Sarai <cyphar@××××××.com> |
62 |
++ * Copyright (C) 2019 SUSE LLC |
63 |
++ * |
64 |
++ * Licensed under the Apache License, Version 2.0 (the "License"); |
65 |
++ * you may not use this file except in compliance with the License. |
66 |
++ * You may obtain a copy of the License at |
67 |
++ * |
68 |
++ * http://www.apache.org/licenses/LICENSE-2.0 |
69 |
++ * |
70 |
++ * Unless required by applicable law or agreed to in writing, software |
71 |
++ * distributed under the License is distributed on an "AS IS" BASIS, |
72 |
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
73 |
++ * See the License for the specific language governing permissions and |
74 |
++ * limitations under the License. |
75 |
++ */ |
76 |
++ |
77 |
++#define _GNU_SOURCE |
78 |
++#include <unistd.h> |
79 |
++#include <stdio.h> |
80 |
++#include <stdlib.h> |
81 |
++#include <stdbool.h> |
82 |
++#include <string.h> |
83 |
++#include <limits.h> |
84 |
++#include <fcntl.h> |
85 |
++#include <errno.h> |
86 |
++ |
87 |
++#include <sys/types.h> |
88 |
++#include <sys/stat.h> |
89 |
++#include <sys/vfs.h> |
90 |
++#include <sys/mman.h> |
91 |
++#include <sys/sendfile.h> |
92 |
++#include <sys/syscall.h> |
93 |
++ |
94 |
++/* Use our own wrapper for memfd_create. */ |
95 |
++#if !defined(SYS_memfd_create) && defined(__NR_memfd_create) |
96 |
++# define SYS_memfd_create __NR_memfd_create |
97 |
++#endif |
98 |
++#ifdef SYS_memfd_create |
99 |
++# define HAVE_MEMFD_CREATE |
100 |
++/* memfd_create(2) flags -- copied from <linux/memfd.h>. */ |
101 |
++# ifndef MFD_CLOEXEC |
102 |
++# define MFD_CLOEXEC 0x0001U |
103 |
++# define MFD_ALLOW_SEALING 0x0002U |
104 |
++# endif |
105 |
++int memfd_create(const char *name, unsigned int flags) |
106 |
++{ |
107 |
++ return syscall(SYS_memfd_create, name, flags); |
108 |
++} |
109 |
++#endif |
110 |
++ |
111 |
++/* This comes directly from <linux/fcntl.h>. */ |
112 |
++#ifndef F_LINUX_SPECIFIC_BASE |
113 |
++# define F_LINUX_SPECIFIC_BASE 1024 |
114 |
++#endif |
115 |
++#ifndef F_ADD_SEALS |
116 |
++# define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9) |
117 |
++# define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10) |
118 |
++#endif |
119 |
++#ifndef F_SEAL_SEAL |
120 |
++# define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */ |
121 |
++# define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */ |
122 |
++# define F_SEAL_GROW 0x0004 /* prevent file from growing */ |
123 |
++# define F_SEAL_WRITE 0x0008 /* prevent writes */ |
124 |
++#endif |
125 |
++ |
126 |
++#define RUNC_SENDFILE_MAX 0x7FFFF000 /* sendfile(2) is limited to 2GB. */ |
127 |
++#ifdef HAVE_MEMFD_CREATE |
128 |
++# define RUNC_MEMFD_COMMENT "runc_cloned:/proc/self/exe" |
129 |
++# define RUNC_MEMFD_SEALS \ |
130 |
++ (F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE) |
131 |
++#endif |
132 |
++ |
133 |
++static void *must_realloc(void *ptr, size_t size) |
134 |
++{ |
135 |
++ void *old = ptr; |
136 |
++ do { |
137 |
++ ptr = realloc(old, size); |
138 |
++ } while(!ptr); |
139 |
++ return ptr; |
140 |
++} |
141 |
++ |
142 |
++/* |
143 |
++ * Verify whether we are currently in a self-cloned program (namely, is |
144 |
++ * /proc/self/exe a memfd). F_GET_SEALS will only succeed for memfds (or rather |
145 |
++ * for shmem files), and we want to be sure it's actually sealed. |
146 |
++ */ |
147 |
++static int is_self_cloned(void) |
148 |
++{ |
149 |
++ int fd, ret, is_cloned = 0; |
150 |
++ |
151 |
++ fd = open("/proc/self/exe", O_RDONLY|O_CLOEXEC); |
152 |
++ if (fd < 0) |
153 |
++ return -ENOTRECOVERABLE; |
154 |
++ |
155 |
++#ifdef HAVE_MEMFD_CREATE |
156 |
++ ret = fcntl(fd, F_GET_SEALS); |
157 |
++ is_cloned = (ret == RUNC_MEMFD_SEALS); |
158 |
++#else |
159 |
++ struct stat statbuf = {0}; |
160 |
++ ret = fstat(fd, &statbuf); |
161 |
++ if (ret >= 0) |
162 |
++ is_cloned = (statbuf.st_nlink == 0); |
163 |
++#endif |
164 |
++ close(fd); |
165 |
++ return is_cloned; |
166 |
++} |
167 |
++ |
168 |
++/* |
169 |
++ * Basic wrapper around mmap(2) that gives you the file length so you can |
170 |
++ * safely treat it as an ordinary buffer. Only gives you read access. |
171 |
++ */ |
172 |
++static char *read_file(char *path, size_t *length) |
173 |
++{ |
174 |
++ int fd; |
175 |
++ char buf[4096], *copy = NULL; |
176 |
++ |
177 |
++ if (!length) |
178 |
++ return NULL; |
179 |
++ |
180 |
++ fd = open(path, O_RDONLY | O_CLOEXEC); |
181 |
++ if (fd < 0) |
182 |
++ return NULL; |
183 |
++ |
184 |
++ *length = 0; |
185 |
++ for (;;) { |
186 |
++ int n; |
187 |
++ |
188 |
++ n = read(fd, buf, sizeof(buf)); |
189 |
++ if (n < 0) |
190 |
++ goto error; |
191 |
++ if (!n) |
192 |
++ break; |
193 |
++ |
194 |
++ copy = must_realloc(copy, (*length + n) * sizeof(*copy)); |
195 |
++ memcpy(copy + *length, buf, n); |
196 |
++ *length += n; |
197 |
++ } |
198 |
++ close(fd); |
199 |
++ return copy; |
200 |
++ |
201 |
++error: |
202 |
++ close(fd); |
203 |
++ free(copy); |
204 |
++ return NULL; |
205 |
++} |
206 |
++ |
207 |
++/* |
208 |
++ * A poor-man's version of "xargs -0". Basically parses a given block of |
209 |
++ * NUL-delimited data, within the given length and adds a pointer to each entry |
210 |
++ * to the array of pointers. |
211 |
++ */ |
212 |
++static int parse_xargs(char *data, int data_length, char ***output) |
213 |
++{ |
214 |
++ int num = 0; |
215 |
++ char *cur = data; |
216 |
++ |
217 |
++ if (!data || *output != NULL) |
218 |
++ return -1; |
219 |
++ |
220 |
++ while (cur < data + data_length) { |
221 |
++ num++; |
222 |
++ *output = must_realloc(*output, (num + 1) * sizeof(**output)); |
223 |
++ (*output)[num - 1] = cur; |
224 |
++ cur += strlen(cur) + 1; |
225 |
++ } |
226 |
++ (*output)[num] = NULL; |
227 |
++ return num; |
228 |
++} |
229 |
++ |
230 |
++/* |
231 |
++ * "Parse" out argv and envp from /proc/self/cmdline and /proc/self/environ. |
232 |
++ * This is necessary because we are running in a context where we don't have a |
233 |
++ * main() that we can just get the arguments from. |
234 |
++ */ |
235 |
++static int fetchve(char ***argv, char ***envp) |
236 |
++{ |
237 |
++ char *cmdline = NULL, *environ = NULL; |
238 |
++ size_t cmdline_size, environ_size; |
239 |
++ |
240 |
++ cmdline = read_file("/proc/self/cmdline", &cmdline_size); |
241 |
++ if (!cmdline) |
242 |
++ goto error; |
243 |
++ environ = read_file("/proc/self/environ", &environ_size); |
244 |
++ if (!environ) |
245 |
++ goto error; |
246 |
++ |
247 |
++ if (parse_xargs(cmdline, cmdline_size, argv) <= 0) |
248 |
++ goto error; |
249 |
++ if (parse_xargs(environ, environ_size, envp) <= 0) |
250 |
++ goto error; |
251 |
++ |
252 |
++ return 0; |
253 |
++ |
254 |
++error: |
255 |
++ free(environ); |
256 |
++ free(cmdline); |
257 |
++ return -EINVAL; |
258 |
++} |
259 |
++ |
260 |
++static int clone_binary(void) |
261 |
++{ |
262 |
++ int binfd, memfd; |
263 |
++ ssize_t sent = 0; |
264 |
++ |
265 |
++#ifdef HAVE_MEMFD_CREATE |
266 |
++ memfd = memfd_create(RUNC_MEMFD_COMMENT, MFD_CLOEXEC | MFD_ALLOW_SEALING); |
267 |
++#else |
268 |
++ memfd = open("/tmp", O_TMPFILE | O_EXCL | O_RDWR | O_CLOEXEC, 0711); |
269 |
++#endif |
270 |
++ if (memfd < 0) |
271 |
++ return -ENOTRECOVERABLE; |
272 |
++ |
273 |
++ binfd = open("/proc/self/exe", O_RDONLY | O_CLOEXEC); |
274 |
++ if (binfd < 0) |
275 |
++ goto error; |
276 |
++ |
277 |
++ sent = sendfile(memfd, binfd, NULL, RUNC_SENDFILE_MAX); |
278 |
++ close(binfd); |
279 |
++ if (sent < 0) |
280 |
++ goto error; |
281 |
++ |
282 |
++#ifdef HAVE_MEMFD_CREATE |
283 |
++ int err = fcntl(memfd, F_ADD_SEALS, RUNC_MEMFD_SEALS); |
284 |
++ if (err < 0) |
285 |
++ goto error; |
286 |
++#else |
287 |
++ /* Need to re-open "memfd" as read-only to avoid execve(2) giving -EXTBUSY. */ |
288 |
++ int newfd; |
289 |
++ char *fdpath = NULL; |
290 |
++ |
291 |
++ if (asprintf(&fdpath, "/proc/self/fd/%d", memfd) < 0) |
292 |
++ goto error; |
293 |
++ newfd = open(fdpath, O_RDONLY | O_CLOEXEC); |
294 |
++ free(fdpath); |
295 |
++ if (newfd < 0) |
296 |
++ goto error; |
297 |
++ |
298 |
++ close(memfd); |
299 |
++ memfd = newfd; |
300 |
++#endif |
301 |
++ return memfd; |
302 |
++ |
303 |
++error: |
304 |
++ close(memfd); |
305 |
++ return -EIO; |
306 |
++} |
307 |
++ |
308 |
++int ensure_cloned_binary(void) |
309 |
++{ |
310 |
++ int execfd; |
311 |
++ char **argv = NULL, **envp = NULL; |
312 |
++ |
313 |
++ /* Check that we're not self-cloned, and if we are then bail. */ |
314 |
++ int cloned = is_self_cloned(); |
315 |
++ if (cloned > 0 || cloned == -ENOTRECOVERABLE) |
316 |
++ return cloned; |
317 |
++ |
318 |
++ if (fetchve(&argv, &envp) < 0) |
319 |
++ return -EINVAL; |
320 |
++ |
321 |
++ execfd = clone_binary(); |
322 |
++ if (execfd < 0) |
323 |
++ return -EIO; |
324 |
++ |
325 |
++ fexecve(execfd, argv, envp); |
326 |
++ return -ENOEXEC; |
327 |
++} |
328 |
+diff --git a/libcontainer/nsenter/nsexec.c b/libcontainer/nsenter/nsexec.c |
329 |
+index 28269dfc0..7750af35e 100644 |
330 |
+--- a/libcontainer/nsenter/nsexec.c |
331 |
++++ b/libcontainer/nsenter/nsexec.c |
332 |
+@@ -534,6 +534,9 @@ void join_namespaces(char *nslist) |
333 |
+ free(namespaces); |
334 |
+ } |
335 |
+ |
336 |
++/* Defined in cloned_binary.c. */ |
337 |
++extern int ensure_cloned_binary(void); |
338 |
++ |
339 |
+ void nsexec(void) |
340 |
+ { |
341 |
+ int pipenum; |
342 |
+@@ -549,6 +552,14 @@ void nsexec(void) |
343 |
+ if (pipenum == -1) |
344 |
+ return; |
345 |
+ |
346 |
++ /* |
347 |
++ * We need to re-exec if we are not in a cloned binary. This is necessary |
348 |
++ * to ensure that containers won't be able to access the host binary |
349 |
++ * through /proc/self/exe. See CVE-2019-5736. |
350 |
++ */ |
351 |
++ if (ensure_cloned_binary() < 0) |
352 |
++ bail("could not ensure we are a cloned binary"); |
353 |
++ |
354 |
+ /* Parse all of the netlink configuration. */ |
355 |
+ nl_parse(pipenum, &config); |
356 |
+ |
357 |
|
358 |
diff --git a/app-emulation/runc/runc-1.0.0_rc6_p20181203-r1.ebuild b/app-emulation/runc/runc-1.0.0_rc6_p20181203-r1.ebuild |
359 |
new file mode 100644 |
360 |
index 00000000000..44d7e6ca8b4 |
361 |
--- /dev/null |
362 |
+++ b/app-emulation/runc/runc-1.0.0_rc6_p20181203-r1.ebuild |
363 |
@@ -0,0 +1,65 @@ |
364 |
+# Copyright 1999-2019 Gentoo Authors |
365 |
+# Distributed under the terms of the GNU General Public License v2 |
366 |
+ |
367 |
+EAPI=6 |
368 |
+EGO_PN="github.com/opencontainers/${PN}" |
369 |
+ |
370 |
+if [[ ${PV} == *9999 ]]; then |
371 |
+ inherit golang-build golang-vcs |
372 |
+else |
373 |
+ MY_PV="${PV/_/-}" |
374 |
+ RUNC_COMMIT="96ec2177ae841256168fcf76954f7177af9446eb" # Change this when you update the ebuild |
375 |
+ SRC_URI="https://${EGO_PN}/archive/${RUNC_COMMIT}.tar.gz -> ${P}.tar.gz" |
376 |
+ KEYWORDS="~amd64 ~arm ~arm64 ~ppc64" |
377 |
+ inherit golang-build golang-vcs-snapshot |
378 |
+fi |
379 |
+ |
380 |
+DESCRIPTION="runc container cli tools" |
381 |
+HOMEPAGE="http://runc.io" |
382 |
+ |
383 |
+LICENSE="Apache-2.0" |
384 |
+SLOT="0" |
385 |
+IUSE="+ambient apparmor hardened +kmem +seccomp" |
386 |
+ |
387 |
+RDEPEND=" |
388 |
+ apparmor? ( sys-libs/libapparmor ) |
389 |
+ seccomp? ( sys-libs/libseccomp ) |
390 |
+ !app-emulation/docker-runc |
391 |
+" |
392 |
+ |
393 |
+PATCHES=( "${FILESDIR}/${PN}-fix-cve.patch" ) |
394 |
+ |
395 |
+src_prepare() { |
396 |
+ pushd src/${EGO_PN} |
397 |
+ default |
398 |
+ sed -i -e "/^GIT_BRANCH/d"\ |
399 |
+ -e "/^GIT_BRANCH_CLEAN/d"\ |
400 |
+ -e "/^COMMIT_NO/d"\ |
401 |
+ -e "s/COMMIT :=.*/COMMIT := ${RUNC_COMMIT}/"\ |
402 |
+ Makefile || die |
403 |
+ popd || die |
404 |
+} |
405 |
+ |
406 |
+src_compile() { |
407 |
+ # Taken from app-emulation/docker-1.7.0-r1 |
408 |
+ export CGO_CFLAGS="-I${ROOT}/usr/include" |
409 |
+ export CGO_LDFLAGS="$(usex hardened '-fno-PIC ' '') |
410 |
+ -L${ROOT}/usr/$(get_libdir)" |
411 |
+ |
412 |
+ # build up optional flags |
413 |
+ local options=( |
414 |
+ $(usex ambient 'ambient' '') |
415 |
+ $(usex apparmor 'apparmor' '') |
416 |
+ $(usex seccomp 'seccomp' '') |
417 |
+ $(usex kmem '' 'nokmem') |
418 |
+ ) |
419 |
+ |
420 |
+ GOPATH="${S}" emake BUILDTAGS="${options[*]}" -C src/${EGO_PN} |
421 |
+} |
422 |
+ |
423 |
+src_install() { |
424 |
+ pushd src/${EGO_PN} || die |
425 |
+ dobin runc |
426 |
+ dodoc README.md PRINCIPLES.md |
427 |
+ popd || die |
428 |
+} |