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: 's avatarChristian Brauner <christian.brauner@ubuntu.com>
parent d6704216
...@@ -21,21 +21,36 @@ ...@@ -21,21 +21,36 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
#define _GNU_SOURCE #include "config.h"
#include <assert.h>
#include <sys/wait.h> #include <errno.h>
#include <sys/types.h> #include <fcntl.h>
#include <stdio.h>
#include <stdlib.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 "attach.h"
#include "arguments.h" #include "arguments.h"
#include "config.h"
#include "confile.h"
#include "namespace.h"
#include "caps.h" #include "caps.h"
#include "confile.h"
#include "console.h"
#include "log.h" #include "log.h"
#include "list.h"
#include "mainloop.h"
#include "utils.h" #include "utils.h"
#if HAVE_PTY_H
#include <pty.h>
#else
#include <../include/openpty.h>
#endif
lxc_log_define(lxc_attach_ui, lxc); lxc_log_define(lxc_attach_ui, lxc);
static const struct option my_longopts[] = { static const struct option my_longopts[] = {
...@@ -48,6 +63,8 @@ static const struct option my_longopts[] = { ...@@ -48,6 +63,8 @@ static const struct option my_longopts[] = {
{"keep-env", no_argument, 0, 501}, {"keep-env", no_argument, 0, 501},
{"keep-var", required_argument, 0, 502}, {"keep-var", required_argument, 0, 502},
{"set-var", required_argument, 0, 'v'}, {"set-var", required_argument, 0, 'v'},
{"pty-log", required_argument, 0, 'L'},
{"rcfile", required_argument, 0, 'f'},
LXC_COMMON_OPTIONS LXC_COMMON_OPTIONS
}; };
...@@ -65,7 +82,8 @@ static int add_to_simple_array(char ***array, ssize_t *capacity, char *value) ...@@ -65,7 +82,8 @@ static int add_to_simple_array(char ***array, ssize_t *capacity, char *value)
{ {
ssize_t count = 0; ssize_t count = 0;
assert(array); if (!array)
return -1;
if (*array) if (*array)
for (; (*array)[count]; count++); for (; (*array)[count]; count++);
...@@ -81,7 +99,8 @@ static int add_to_simple_array(char ***array, ssize_t *capacity, char *value) ...@@ -81,7 +99,8 @@ static int add_to_simple_array(char ***array, ssize_t *capacity, char *value)
*capacity = new_capacity; *capacity = new_capacity;
} }
assert(*array); if (!(*array))
return -1;
(*array)[count] = value; (*array)[count] = value;
return 0; return 0;
...@@ -133,6 +152,12 @@ static int my_parser(struct lxc_arguments* args, int c, char* arg) ...@@ -133,6 +152,12 @@ static int my_parser(struct lxc_arguments* args, int c, char* arg)
return -1; return -1;
} }
break; break;
case 'L':
args->console_log = arg;
break;
case 'f':
args->rcfile = arg;
break;
} }
return 0; return 0;
...@@ -175,41 +200,214 @@ Options :\n\ ...@@ -175,41 +200,214 @@ Options :\n\
--keep-env Keep all current environment variables. This\n\ --keep-env Keep all current environment variables. This\n\
is the current default behaviour, but is likely to\n\ is the current default behaviour, but is likely to\n\
change in the future.\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\ -v, --set-var Set an additional variable that is seen by the\n\
attached program in the container. May be specified\n\ attached program in the container. May be specified\n\
multiple times.\n\ multiple times.\n\
--keep-var Keep an additional environment variable. Only\n\ --keep-var Keep an additional environment variable. Only\n\
applicable if --clear-env is specified. May be used\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, .options = my_longopts,
.parser = my_parser, .parser = my_parser,
.checker = NULL, .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 main(int argc, char *argv[])
{ {
int ret; int ret = -1, r;
int wexit = 0;
pid_t pid; pid_t pid;
lxc_attach_options_t attach_options = LXC_ATTACH_OPTIONS_DEFAULT; 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(); r = lxc_caps_init();
if (ret) if (r)
return 1; exit(EXIT_FAILURE);
ret = lxc_arguments_parse(&my_args, argc, argv); r = lxc_arguments_parse(&my_args, argc, argv);
if (ret) if (r)
return 1; exit(EXIT_FAILURE);
if (!my_args.log_file) if (!my_args.log_file)
my_args.log_file = "none"; 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]); my_args.progname, my_args.quiet, my_args.lxcpath[0]);
if (ret) if (r)
return 1; exit(EXIT_FAILURE);
lxc_log_options_no_override(); 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) if (remount_sys_proc)
attach_options.attach_flags |= LXC_ATTACH_REMOUNT_PROC_SYS; attach_options.attach_flags |= LXC_ATTACH_REMOUNT_PROC_SYS;
if (elevated_privileges) if (elevated_privileges)
...@@ -220,23 +418,46 @@ int main(int argc, char *argv[]) ...@@ -220,23 +418,46 @@ int main(int argc, char *argv[])
attach_options.extra_env_vars = extra_env; attach_options.extra_env_vars = extra_env;
attach_options.extra_keep_env = extra_keep; attach_options.extra_keep_env = extra_keep;
if (my_args.argc) { if (my_args.argc > 0) {
command.program = my_args.argv[0]; command.program = my_args.argv[0];
command.argv = (char**)my_args.argv; 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 { } 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) if (ret < 0)
return 1; goto out;
ret = lxc_wait_for_pid_status(pid); ret = lxc_wait_for_pid_status(pid);
if (ret < 0) if (ret < 0)
return 1; goto out;
if (WIFEXITED(ret)) if (WIFEXITED(ret))
return WEXITSTATUS(ret); wexit = WEXITSTATUS(ret);
out:
return 1; lxc_container_put(c);
if (ret >= 0)
exit(wexit);
exit(EXIT_FAILURE);
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment