]> git.ipfire.org Git - thirdparty/lxc.git/commitdiff
rexec: handle old kernels
authorChristian Brauner <christian.brauner@ubuntu.com>
Mon, 18 Feb 2019 22:36:03 +0000 (23:36 +0100)
committerChristian Brauner <christian.brauner@ubuntu.com>
Mon, 18 Feb 2019 22:36:53 +0000 (23:36 +0100)
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
src/lxc/Makefile.am
src/lxc/file_utils.c
src/lxc/file_utils.h
src/lxc/macro.h
src/lxc/memory_utils.h [new file with mode: 0644]
src/lxc/rexec.c
src/lxc/rexec.h [new file with mode: 0644]
src/lxc/tools/lxc_attach.c

index e6d544d20a08f4271827addcb6691edce17fad6b..604acbc168112bac11c2b4144ad07f02fa81f575 100644 (file)
@@ -20,9 +20,11 @@ noinst_HEADERS = attach.h \
                 lxc.h \
                 lxclock.h \
                 macro.h \
+                memory_utils.h \
                 monitor.h \
                 namespace.h \
                 raw_syscalls.h \
+                rexec.h \
                 start.h \
                 state.h \
                 storage/btrfs.h \
@@ -111,6 +113,7 @@ liblxc_la_SOURCES = af_unix.c af_unix.h \
                    lxclock.c lxclock.h \
                    lxcseccomp.h \
                    macro.h \
+                   memory_utils.h \
                    mainloop.c mainloop.h \
                    namespace.c namespace.h \
                    nl.c nl.h \
@@ -176,7 +179,7 @@ liblxc_la_SOURCES += ../include/strlcat.c ../include/strlcat.h
 endif
 
 if ENFORCE_MEMFD_REXEC
-liblxc_la_SOURCES += rexec.c
+liblxc_la_SOURCES += rexec.c rexec.h
 endif
 
 AM_CFLAGS = -DLXCROOTFSMOUNT=\"$(LXCROOTFSMOUNT)\" \
@@ -295,6 +298,7 @@ LDADD = liblxc.la \
 
 if ENABLE_TOOLS
 lxc_attach_SOURCES = tools/lxc_attach.c \
+                    rexec.c rexec.h \
                     tools/arguments.c tools/arguments.h
 lxc_autostart_SOURCES = tools/lxc_autostart.c \
                        tools/arguments.c tools/arguments.h
index 930fd738a942464a1c8ec3a50f764c214ae3afab..f80aa642190aa988a3dbb4c6057af34f3c9bb6af 100644 (file)
@@ -31,7 +31,9 @@
 #include "config.h"
 #include "file_utils.h"
 #include "macro.h"
+#include "memory_utils.h"
 #include "string_utils.h"
+#include "utils.h"
 
 int lxc_write_to_file(const char *filename, const void *buf, size_t count,
                      bool add_newline, mode_t mode)
@@ -218,7 +220,8 @@ int lxc_count_file_lines(const char *fn)
 
 int lxc_make_tmpfile(char *template, bool rm)
 {
-       int fd, ret;
+       __do_close_prot_errno int fd = -EBADF;
+       int ret;
        mode_t msk;
 
        msk = umask(0022);
@@ -227,16 +230,17 @@ int lxc_make_tmpfile(char *template, bool rm)
        if (fd < 0)
                return -1;
 
+       if (lxc_set_cloexec(fd))
+               return -1;
+
        if (!rm)
-               return fd;
+               return move_fd(fd);
 
        ret = unlink(template);
-       if (ret < 0) {
-               close(fd);
+       if (ret < 0)
                return -1;
-       }
 
-       return fd;
+       return move_fd(fd);
 }
 
 bool is_fs_type(const struct statfs *fs, fs_type_magic magic_val)
@@ -366,3 +370,33 @@ on_error:
 
        return NULL;
 }
+
+int fd_to_fd(int from, int to)
+{
+       for (;;) {
+               uint8_t buf[PATH_MAX];
+               uint8_t *p = buf;
+               ssize_t bytes_to_write;
+               ssize_t bytes_read;
+
+               bytes_read = lxc_read_nointr(from, buf, sizeof buf);
+               if (bytes_read < 0)
+                       return -1;
+               if (bytes_read == 0)
+                       break;
+
+               bytes_to_write = (size_t)bytes_read;
+               do {
+                       ssize_t bytes_written;
+
+                       bytes_written = lxc_write_nointr(to, p, bytes_to_write);
+                       if (bytes_written < 0)
+                               return -1;
+
+                       bytes_to_write -= bytes_written;
+                       p += bytes_written;
+               } while (bytes_to_write > 0);
+       }
+
+       return 0;
+}
index 518a61af3292e1ac6373a76c0d5c13fb8581ecc3..cc8f69e1830133e2f132981cc26dffcc8213f385 100644 (file)
@@ -3,18 +3,19 @@
  * Copyright © 2018 Christian Brauner <christian.brauner@ubuntu.com>.
  * Copyright © 2018 Canonical Ltd.
  *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2, as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
 #ifndef __LXC_FILE_UTILS_H
@@ -56,5 +57,6 @@ extern FILE *fopen_cloexec(const char *path, const char *mode);
 extern ssize_t lxc_sendfile_nointr(int out_fd, int in_fd, off_t *offset,
                                   size_t count);
 extern char *file_to_buf(char *path, size_t *length);
+extern int fd_to_fd(int from, int to);
 
 #endif /* __LXC_FILE_UTILS_H */
index 30aa2a5a59592246f0f107e254ea3b1b0d4e8a61..6e07516f20abdcba4d3fb100bea82f09bca66613 100644 (file)
@@ -3,18 +3,19 @@
  * Copyright © 2018 Christian Brauner <christian.brauner@ubuntu.com>.
  * Copyright © 2018 Canonical Ltd.
  *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2, as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
 #ifndef __LXC_MACRO_H
@@ -392,4 +393,18 @@ enum {
 /* Maximum number of bytes sendfile() is able to send in one go. */
 #define LXC_SENDFILE_MAX 0x7ffff000
 
+#define move_ptr(ptr)                                 \
+       ({                                            \
+               typeof(ptr) __internal_ptr__ = (ptr); \
+               (ptr) = NULL;                         \
+               __internal_ptr__;                     \
+       })
+
+#define move_fd(fd)                         \
+       ({                                  \
+               int __internal_fd__ = (fd); \
+               (fd) = -EBADF;              \
+               __internal_fd__;            \
+       })
+
 #endif /* __LXC_MACRO_H */
diff --git a/src/lxc/memory_utils.h b/src/lxc/memory_utils.h
new file mode 100644 (file)
index 0000000..c1dafb4
--- /dev/null
@@ -0,0 +1,68 @@
+/* liblxcapi
+ *
+ * Copyright © 2019 Christian Brauner <christian.brauner@ubuntu.com>.
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __LXC_MEMORY_UTILS_H
+#define __LXC_MEMORY_UTILS_H
+
+#include <dirent.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "macro.h"
+
+static inline void __auto_free__(void *p)
+{
+       free(*(void **)p);
+}
+
+static inline void __auto_fclose__(FILE **f)
+{
+       if (*f)
+               fclose(*f);
+}
+
+static inline void __auto_closedir__(DIR **d)
+{
+       if (*d)
+               closedir(*d);
+}
+
+#define close_prot_errno_disarm(fd) \
+       if (fd >= 0) {              \
+               int _e_ = errno;    \
+               close(fd);          \
+               errno = _e_;        \
+               fd = -EBADF;        \
+       }
+
+static inline void __auto_close__(int *fd)
+{
+       close_prot_errno_disarm(*fd);
+}
+
+#define __do_close_prot_errno __attribute__((__cleanup__(__auto_close__)))
+#define __do_free __attribute__((__cleanup__(__auto_free__)))
+#define __do_fclose __attribute__((__cleanup__(__auto_fclose__)))
+#define __do_closedir __attribute__((__cleanup__(__auto_closedir__)))
+
+#endif /* __LXC_MEMORY_UTILS_H */
index f08985525af6b8f072e7797eeb421b995a31da5b..39399826f203013d5d67797c7dd30f287eaf58b5 100644 (file)
@@ -3,18 +3,19 @@
  * Copyright © 2019 Christian Brauner <christian.brauner@ubuntu.com>.
  * Copyright © 2019 Canonical Ltd.
  *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2, as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
 #ifndef _GNU_SOURCE
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <unistd.h>
 
 #include "config.h"
 #include "file_utils.h"
+#include "macro.h"
+#include "memory_utils.h"
 #include "raw_syscalls.h"
 #include "string_utils.h"
 #include "syscall_wrappers.h"
@@ -59,92 +63,132 @@ static int push_vargs(char *data, int data_length, char ***output)
        return num;
 }
 
-static int parse_exec_params(char ***argv, char ***envp)
+static int parse_argv(char ***argv)
 {
+       __do_free char *cmdline = NULL;
        int ret;
-       char *cmdline = NULL, *env = NULL;
-       size_t cmdline_size, env_size;
+       size_t cmdline_size;
 
        cmdline = file_to_buf("/proc/self/cmdline", &cmdline_size);
        if (!cmdline)
-               goto on_error;
-
-       env = file_to_buf("/proc/self/environ", &env_size);
-       if (!env)
-               goto on_error;
+               return -1;
 
        ret = push_vargs(cmdline, cmdline_size, argv);
        if (ret <= 0)
-               goto on_error;
-
-       ret = push_vargs(env, env_size, envp);
-       if (ret <= 0)
-               goto on_error;
+               return -1;
 
+       move_ptr(cmdline);
        return 0;
-
-on_error:
-       free(env);
-       free(cmdline);
-
-       return -1;
 }
 
 static int is_memfd(void)
 {
-       int fd, saved_errno, seals;
+       __do_close_prot_errno int fd = -EBADF;
+       int seals;
 
        fd = open("/proc/self/exe", O_RDONLY | O_CLOEXEC);
        if (fd < 0)
                return -ENOTRECOVERABLE;
 
        seals = fcntl(fd, F_GET_SEALS);
-       saved_errno = errno;
-       close(fd);
-       errno = saved_errno;
-       if (seals < 0)
+       if (seals < 0) {
+               struct stat s = {0};
+
+               if (fstat(fd, &s) == 0)
+                       return (s.st_nlink == 0);
+
                return -EINVAL;
+       }
 
        return seals == LXC_MEMFD_REXEC_SEALS;
 }
 
 static void lxc_rexec_as_memfd(char **argv, char **envp, const char *memfd_name)
 {
-       int saved_errno;
-       ssize_t bytes_sent;
-       int fd = -1, memfd = -1;
+       __do_close_prot_errno int execfd = -EBADF, fd = -EBADF, memfd = -EBADF,
+                                 tmpfd = -EBADF;
+       int ret;
+       ssize_t bytes_sent = 0;
+       struct stat st = {0};
 
        memfd = memfd_create(memfd_name, MFD_ALLOW_SEALING | MFD_CLOEXEC);
-       if (memfd < 0)
-               return;
+       if (memfd < 0) {
+               char template[PATH_MAX];
+
+               ret = snprintf(template, sizeof(template),
+                              P_tmpdir "/.%s_XXXXXX", memfd_name);
+               if (ret < 0 || (size_t)ret >= sizeof(template))
+                       return;
+
+               tmpfd = lxc_make_tmpfile(template, true);
+               if (tmpfd < 0)
+                       return;
+
+               ret = fchmod(tmpfd, 0700);
+               if (ret)
+                       return;
+       }
 
        fd = open("/proc/self/exe", O_RDONLY | O_CLOEXEC);
        if (fd < 0)
-               goto on_error;
+               return;
 
        /* sendfile() handles up to 2GB. */
-       bytes_sent = lxc_sendfile_nointr(memfd, fd, NULL, LXC_SENDFILE_MAX);
-       saved_errno = errno;
-       close(fd);
-       errno = saved_errno;
-       if (bytes_sent < 0)
-               goto on_error;
-
-       if (fcntl(memfd, F_ADD_SEALS, LXC_MEMFD_REXEC_SEALS))
-               goto on_error;
-
-       fexecve(memfd, argv, envp);
-
-on_error:
-       saved_errno = errno;
-       close(memfd);
-       errno = saved_errno;
+       ret = fstat(fd, &st);
+       if (ret)
+               return;
+
+       while (bytes_sent < st.st_size) {
+               ssize_t sent;
+
+               sent = lxc_sendfile_nointr(memfd >= 0 ? memfd : tmpfd, fd, NULL,
+                                          st.st_size - bytes_sent);
+               if (sent < 0) {
+                       /* Fallback to shoveling data between kernel- and
+                        * userspace.
+                        */
+                       lseek(fd, 0, SEEK_SET);
+                       if (fd_to_fd(fd, memfd >= 0 ? memfd : tmpfd))
+                               break;
+
+                       return;
+               }
+               bytes_sent += sent;
+       }
+       close_prot_errno_disarm(fd);
+
+       if (memfd >= 0) {
+               if (fcntl(memfd, F_ADD_SEALS, LXC_MEMFD_REXEC_SEALS))
+                       return;
+
+               execfd = memfd;
+       } else {
+               char procfd[LXC_PROC_PID_FD_LEN];
+
+               ret = snprintf(procfd, sizeof(procfd), "/proc/self/fd/%d", tmpfd);
+               if (ret < 0 || (size_t)ret >= sizeof(procfd))
+                       return;
+
+               execfd = open(procfd, O_PATH | O_CLOEXEC);
+               close_prot_errno_disarm(tmpfd);
+
+       }
+       if (execfd < 0)
+               return;
+
+       fexecve(execfd, argv, envp);
 }
 
-static int lxc_rexec(const char *memfd_name)
+/*
+ * Get cheap access to the environment. This must be declared by the user as
+ * mandated by POSIX. The definition is located in unistd.h.
+ */
+extern char **environ;
+
+int lxc_rexec(const char *memfd_name)
 {
        int ret;
-       char **argv = NULL, **envp = NULL;
+       char **argv = NULL;
 
        ret = is_memfd();
        if (ret < 0 && ret == -ENOTRECOVERABLE) {
@@ -156,7 +200,7 @@ static int lxc_rexec(const char *memfd_name)
                return 0;
        }
 
-       ret = parse_exec_params(&argv, &envp);
+       ret = parse_argv(&argv);
        if (ret < 0) {
                fprintf(stderr,
                        "%s - Failed to parse command line parameters\n",
@@ -164,7 +208,7 @@ static int lxc_rexec(const char *memfd_name)
                return -1;
        }
 
-       lxc_rexec_as_memfd(argv, envp, memfd_name);
+       lxc_rexec_as_memfd(argv, environ, memfd_name);
        fprintf(stderr, "%s - Failed to rexec as memfd\n", strerror(errno));
        return -1;
 }
@@ -178,7 +222,7 @@ static int lxc_rexec(const char *memfd_name)
  */
 __attribute__((constructor)) static void liblxc_rexec(void)
 {
-       if (lxc_rexec("liblxc")) {
+       if (getenv("LXC_MEMFD_REXEC") && lxc_rexec("liblxc")) {
                fprintf(stderr, "Failed to re-execute liblxc via memory file descriptor\n");
                _exit(EXIT_FAILURE);
        }
diff --git a/src/lxc/rexec.h b/src/lxc/rexec.h
new file mode 100644 (file)
index 0000000..088ded9
--- /dev/null
@@ -0,0 +1,26 @@
+/* liblxcapi
+ *
+ * Copyright © 2019 Christian Brauner <christian.brauner@ubuntu.com>.
+ * Copyright © 2019 Canonical Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __LXC_REXEC_H
+#define __LXC_REXEC_H
+
+extern int lxc_rexec(const char *memfd_name);
+
+#endif /* __LXC_REXEC_H */
index 8c8e7d3977794dc138da3c60ed3b44a8bbac88d3..80b3693d39ab2edced4871146c1f11cca1f2cdde 100644 (file)
 #include "config.h"
 #include "confile.h"
 #include "log.h"
+#include "rexec.h"
 #include "utils.h"
 
 lxc_log_define(lxc_attach, lxc);
 
+/**
+ * This function will copy any binary that calls liblxc into a memory file and
+ * will use the memfd to rexecute the binary. This is done to prevent attacks
+ * through the /proc/self/exe symlink to corrupt the host binary when host and
+ * container are in the same user namespace or have set up an identity id
+ * mapping: CVE-2019-5736.
+ */
+#ifdef ENFORCE_MEMFD_REXEC
+__attribute__((constructor)) static void lxc_attach_rexec(void)
+{
+       if (!getenv("LXC_MEMFD_REXEC") && lxc_rexec("lxc-attach")) {
+               fprintf(stderr, "Failed to re-execute lxc-attach via memory file descriptor\n");
+               _exit(EXIT_FAILURE);
+       }
+}
+#endif
+
 static int my_parser(struct lxc_arguments *args, int c, char *arg);
 static int add_to_simple_array(char ***array, ssize_t *capacity, char *value);
 static bool stdfd_is_pty(void);