From: Christian Brauner Date: Mon, 16 Jan 2017 14:10:45 +0000 (+0100) Subject: CVE-2016-10124: make lxc-attach use a pty X-Git-Tag: lxc-1.0.10~10^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=99e98d9afa2bea1d299859d35f7547ef9e2fabe6;p=thirdparty%2Flxc.git CVE-2016-10124: make lxc-attach use a pty 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 --- diff --git a/src/lxc/lxc_attach.c b/src/lxc/lxc_attach.c index 1c2434982..446c4ce07 100644 --- a/src/lxc/lxc_attach.c +++ b/src/lxc/lxc_attach.c @@ -21,21 +21,36 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#define _GNU_SOURCE -#include -#include -#include +#include "config.h" + +#include +#include +#include #include +#include +#include +#include +#include +#include + +#include #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 +#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); }