]> git.ipfire.org Git - thirdparty/lxc.git/commitdiff
CVE-2016-10124: make lxc-attach use a pty
authorChristian Brauner <christian.brauner@ubuntu.com>
Mon, 16 Jan 2017 14:10:45 +0000 (15:10 +0100)
committerChristian Brauner <christian.brauner@ubuntu.com>
Tue, 4 Apr 2017 14:52:34 +0000 (16:52 +0200)
Previous versions of lxc-attach simply attached to the specified namespaces of
a container and ran a shell or the specified command without first allocating a
pseudo terminal. This made them vulnerable to input faking via a TIOCSTI ioctl
call after switching between userspace execution contexts with different
privilege levels. Newer versions of lxc-attach will try to allocate a pseudo
terminal master/slave pair on the host and attach any standard file descriptors
which refer to a terminal to the slave side of the pseudo terminal before
executing a shell or command. Note, that if none of the standard file
descriptors refer to a  terminal lxc-attach will not try to allocate a pseudo
terminal. Instead it will simply attach to the containers namespaces and run a
shell or the specified command.

(This is a backport of a series of patches fixing CVE-2016-10124.)

Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
src/lxc/lxc_attach.c

index 1c2434982d38688dbd5114818e645be78466eaae..446c4ce0701e3720d9b9a0092b381fb9af4f7505 100644 (file)
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
-#define _GNU_SOURCE
-#include <assert.h>
-#include <sys/wait.h>
-#include <sys/types.h>
+#include "config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
 #include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <lxc/lxccontainer.h>
 
 #include "attach.h"
 #include "arguments.h"
-#include "config.h"
-#include "confile.h"
-#include "namespace.h"
 #include "caps.h"
+#include "confile.h"
+#include "console.h"
 #include "log.h"
+#include "list.h"
+#include "mainloop.h"
 #include "utils.h"
 
+#if HAVE_PTY_H
+#include <pty.h>
+#else
+#include <../include/openpty.h>
+#endif
+
 lxc_log_define(lxc_attach_ui, lxc);
 
 static const struct option my_longopts[] = {
@@ -48,6 +63,8 @@ static const struct option my_longopts[] = {
        {"keep-env", no_argument, 0, 501},
        {"keep-var", required_argument, 0, 502},
        {"set-var", required_argument, 0, 'v'},
+       {"pty-log", required_argument, 0, 'L'},
+       {"rcfile", required_argument, 0, 'f'},
        LXC_COMMON_OPTIONS
 };
 
@@ -65,7 +82,8 @@ static int add_to_simple_array(char ***array, ssize_t *capacity, char *value)
 {
        ssize_t count = 0;
 
-       assert(array);
+       if (!array)
+               return -1;
 
        if (*array)
                for (; (*array)[count]; count++);
@@ -81,7 +99,8 @@ static int add_to_simple_array(char ***array, ssize_t *capacity, char *value)
                *capacity = new_capacity;
        }
 
-       assert(*array);
+       if (!(*array))
+               return -1;
 
        (*array)[count] = value;
        return 0;
@@ -133,6 +152,12 @@ static int my_parser(struct lxc_arguments* args, int c, char* arg)
                        return -1;
                }
                break;
+       case 'L':
+               args->console_log = arg;
+               break;
+       case 'f':
+               args->rcfile = arg;
+               break;
        }
 
        return 0;
@@ -175,41 +200,214 @@ Options :\n\
       --keep-env    Keep all current environment variables. This\n\
                     is the current default behaviour, but is likely to\n\
                     change in the future.\n\
+  -L, --pty-log=FILE\n\
+                    Log pty output to FILE\n\
   -v, --set-var     Set an additional variable that is seen by the\n\
                     attached program in the container. May be specified\n\
                     multiple times.\n\
       --keep-var    Keep an additional environment variable. Only\n\
                     applicable if --clear-env is specified. May be used\n\
-                    multiple times.\n",
+                    multiple times.\n\
+  -f, --rcfile=FILE\n\
+                    Load configuration file FILE\n\
+",
        .options  = my_longopts,
        .parser   = my_parser,
        .checker  = NULL,
 };
 
+struct wrapargs {
+       lxc_attach_options_t *options;
+       lxc_attach_command_t *command;
+       struct lxc_console *console;
+       int ptyfd;
+};
+
+/* Minimalistic login_tty() implementation. */
+static int login_pty(int fd)
+{
+       setsid();
+       if (ioctl(fd, TIOCSCTTY, NULL) < 0)
+               return -1;
+       if (lxc_console_set_stdfds(fd) < 0)
+               return -1;
+       if (fd > STDERR_FILENO)
+               close(fd);
+       return 0;
+}
+
+static int get_pty_on_host_callback(void *p)
+{
+       struct wrapargs *wrap = p;
+
+       close(wrap->console->master);
+       if (login_pty(wrap->console->slave) < 0)
+               return -1;
+
+       if (wrap->command->program)
+               lxc_attach_run_command(wrap->command);
+       else
+               lxc_attach_run_shell(NULL);
+       return -1;
+}
+
+static int get_pty_on_host(struct lxc_container *c, struct wrapargs *wrap, int *pid)
+{
+       int ret = -1;
+       struct wrapargs *args = wrap;
+       struct lxc_epoll_descr descr;
+       struct lxc_conf *conf;
+       struct lxc_tty_state *ts;
+
+       INFO("Trying to allocate a pty on the host");
+
+       if (!isatty(args->ptyfd)) {
+               ERROR("Standard file descriptor does not refer to a pty\n.");
+               return -1;
+       }
+
+       conf = c->lxc_conf;
+       free(conf->console.log_path);
+       if (my_args.console_log)
+               conf->console.log_path = strdup(my_args.console_log);
+       else
+               conf->console.log_path = NULL;
+
+       /* In the case of lxc-attach our peer pty will always be the current
+        * controlling terminal. We clear whatever was set by the user for
+        * lxc.console.path here and set it to "/dev/tty". Doing this will (a)
+        * prevent segfaults when the container has been setup with
+        * lxc.console = none and (b) provide an easy way to ensure that we
+        * always do the correct thing. strdup() must be used since console.path
+        * is free()ed when we call lxc_container_put(). */
+       free(conf->console.path);
+       conf->console.path = strdup("/dev/tty");
+       if (!conf->console.path)
+               return -1;
+
+       /* Create pty on the host. */
+       if (lxc_console_create(conf) < 0)
+               return -1;
+       ts = conf->console.tty_state;
+       conf->console.descr = &descr;
+
+       /* Shift ttys to container. */
+       if (ttys_shift_ids(conf) < 0) {
+               ERROR("Failed to shift tty into container");
+               goto err1;
+       }
+
+       /* Send wrapper function on its way. */
+       wrap->console = &conf->console;
+       if (c->attach(c, get_pty_on_host_callback, wrap, wrap->options, pid) < 0)
+               goto err1;
+       close(conf->console.slave); /* Close slave side. */
+
+       ret = lxc_mainloop_open(&descr);
+       if (ret) {
+               ERROR("failed to create mainloop");
+               goto err2;
+       }
+
+       if (lxc_console_mainloop_add(&descr, conf) < 0) {
+               ERROR("Failed to add handlers to lxc mainloop.");
+               goto err3;
+       }
+
+       ret = lxc_mainloop(&descr, -1);
+       if (ret) {
+               ERROR("mainloop returned an error");
+               goto err3;
+       }
+       ret = 0;
+
+err3:
+       lxc_mainloop_close(&descr);
+err2:
+       if (ts && ts->sigfd != -1)
+               lxc_console_sigwinch_fini(ts);
+err1:
+       lxc_console_delete(&conf->console);
+
+       return ret;
+}
+
+static int stdfd_is_pty(void)
+{
+       if (isatty(STDIN_FILENO))
+               return STDIN_FILENO;
+       if (isatty(STDOUT_FILENO))
+               return STDOUT_FILENO;
+       if (isatty(STDERR_FILENO))
+               return STDERR_FILENO;
+
+       return -1;
+}
+
 int main(int argc, char *argv[])
 {
-       int ret;
+       int ret = -1, r;
+       int wexit = 0;
        pid_t pid;
        lxc_attach_options_t attach_options = LXC_ATTACH_OPTIONS_DEFAULT;
-       lxc_attach_command_t command;
+       lxc_attach_command_t command = (lxc_attach_command_t){.program = NULL};
 
-       ret = lxc_caps_init();
-       if (ret)
-               return 1;
+       r = lxc_caps_init();
+       if (r)
+               exit(EXIT_FAILURE);
 
-       ret = lxc_arguments_parse(&my_args, argc, argv);
-       if (ret)
-               return 1;
+       r = lxc_arguments_parse(&my_args, argc, argv);
+       if (r)
+               exit(EXIT_FAILURE);
 
        if (!my_args.log_file)
                my_args.log_file = "none";
 
-       ret = lxc_log_init(my_args.name, my_args.log_file, my_args.log_priority,
+       r = lxc_log_init(my_args.name, my_args.log_file, my_args.log_priority,
                           my_args.progname, my_args.quiet, my_args.lxcpath[0]);
-       if (ret)
-               return 1;
+       if (r)
+               exit(EXIT_FAILURE);
        lxc_log_options_no_override();
 
+       if (geteuid()) {
+               if (access(my_args.lxcpath[0], O_RDONLY) < 0) {
+                       if (!my_args.quiet)
+                               fprintf(stderr, "You lack access to %s\n", my_args.lxcpath[0]);
+                       exit(EXIT_FAILURE);
+               }
+       }
+
+       struct lxc_container *c = lxc_container_new(my_args.name, my_args.lxcpath[0]);
+       if (!c)
+               exit(EXIT_FAILURE);
+
+       if (my_args.rcfile) {
+               c->clear_config(c);
+               if (!c->load_config(c, my_args.rcfile)) {
+                       ERROR("Failed to load rcfile");
+                       lxc_container_put(c);
+                       exit(EXIT_FAILURE);
+               }
+               c->configfile = strdup(my_args.rcfile);
+               if (!c->configfile) {
+                       ERROR("Out of memory setting new config filename");
+                       lxc_container_put(c);
+                       exit(EXIT_FAILURE);
+               }
+       }
+
+       if (!c->may_control(c)) {
+               fprintf(stderr, "Insufficent privileges to control %s\n", c->name);
+               lxc_container_put(c);
+               exit(EXIT_FAILURE);
+       }
+
+       if (!c->is_defined(c)) {
+               fprintf(stderr, "Error: container %s is not defined\n", c->name);
+               lxc_container_put(c);
+               exit(EXIT_FAILURE);
+       }
+
        if (remount_sys_proc)
                attach_options.attach_flags |= LXC_ATTACH_REMOUNT_PROC_SYS;
        if (elevated_privileges)
@@ -220,23 +418,46 @@ int main(int argc, char *argv[])
        attach_options.extra_env_vars = extra_env;
        attach_options.extra_keep_env = extra_keep;
 
-       if (my_args.argc) {
+       if (my_args.argc > 0) {
                command.program = my_args.argv[0];
                command.argv = (char**)my_args.argv;
-               ret = lxc_attach(my_args.name, my_args.lxcpath[0], lxc_attach_run_command, &command, &attach_options, &pid);
+       }
+
+       struct wrapargs wrap = (struct wrapargs){
+               .command = &command,
+                       .options = &attach_options
+       };
+
+       wrap.ptyfd = stdfd_is_pty();
+       if (wrap.ptyfd >= 0) {
+               if ((!isatty(STDOUT_FILENO) || !isatty(STDERR_FILENO)) && my_args.console_log) {
+                       fprintf(stderr, "-L/--pty-log can only be used when stdout and stderr refer to a pty.\n");
+                       goto out;
+               }
+               ret = get_pty_on_host(c, &wrap, &pid);
        } else {
-               ret = lxc_attach(my_args.name, my_args.lxcpath[0], lxc_attach_run_shell, NULL, &attach_options, &pid);
+               if (my_args.console_log) {
+                       fprintf(stderr, "-L/--pty-log can only be used when stdout and stderr refer to a pty.\n");
+                       goto out;
+               }
+               if (command.program)
+                       ret = c->attach(c, lxc_attach_run_command, &command, &attach_options, &pid);
+               else
+                       ret = c->attach(c, lxc_attach_run_shell, NULL, &attach_options, &pid);
        }
 
        if (ret < 0)
-               return 1;
+               goto out;
 
        ret = lxc_wait_for_pid_status(pid);
        if (ret < 0)
-               return 1;
+               goto out;
 
        if (WIFEXITED(ret))
-               return WEXITSTATUS(ret);
-
-       return 1;
+               wexit = WEXITSTATUS(ret);
+out:
+       lxc_container_put(c);
+       if (ret >= 0)
+               exit(wexit);
+       exit(EXIT_FAILURE);
 }