Gentoo Archives: gentoo-commits

From: "Manuel Rüger" <mrueg@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] repo/gentoo:master commit in: app-emulation/runc/, app-emulation/runc/files/
Date: Mon, 11 Feb 2019 17:45:17
Message-Id: 1549907054.a1e4f4e2eaaba19d9bf5b30fe799df91b8613a6b.mrueg@gentoo
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 +}