Commit 3196b9ac by Stéphane Graber Committed by GitHub

Merge pull request #1386 from brauner/2017-01-16/fix_CVE-2016-10124_stable-1.0

stable-1.0: backport fixes for CVE-2016-10124
parents b2ed6cc2 6aae6d02
......@@ -24,21 +24,195 @@
#ifndef __LXC_CONSOLE_H
#define __LXC_CONSOLE_H
struct lxc_epoll_descr;
struct lxc_container;
#include "conf.h"
#include "list.h"
struct lxc_epoll_descr; /* defined in mainloop.h */
struct lxc_container; /* defined in lxccontainer.h */
struct lxc_tty_state
{
struct lxc_list node;
int stdinfd;
int stdoutfd;
int masterfd;
/* Escape sequence to use for exiting the pty. A single char can be
* specified. The pty can then exited by doing: Ctrl + specified_char + q.
* This field is checked by lxc_console_cb_tty_stdin(). Set to -1 to
* disable exiting the pty via a escape sequence. */
int escape;
/* Used internally by lxc_console_cb_tty_stdin() to check whether an
* escape sequence has been received. */
int saw_escape;
/* Name of the container to forward the SIGWINCH event to. */
const char *winch_proxy;
/* Path of the container to forward the SIGWINCH event to. */
const char *winch_proxy_lxcpath;
/* File descriptor that accepts SIGWINCH signals. If set to -1 no
* SIGWINCH handler could be installed. This also means that
* the sigset_t oldmask member is meaningless. */
int sigfd;
sigset_t oldmask;
};
/*
* lxc_console_allocate: allocate the console or a tty
*
* @conf : the configuration of the container to allocate from
* @sockfd : the socket fd whose remote side when closed, will be an
* indication that the console or tty is no longer in use
* @ttyreq : the tty requested to be opened, -1 for any, 0 for the console
*/
extern int lxc_console_allocate(struct lxc_conf *conf, int sockfd, int *ttynum);
/*
* Create a new pty:
* - calls openpty() to allocate a master/slave pty pair
* - sets the FD_CLOEXEC flag on the master/slave fds
* - allocates either the current controlling pty (default) or a user specified
* pty as peer pty for the newly created master/slave pair
* - sets up SIGWINCH handler, winsz, and new terminal settings
* (Handlers for SIGWINCH and I/O are not registered in a mainloop.)
* (For an unprivileged container the created pty on the host is not
* automatically chowned to the uid/gid of the unprivileged user. For this
* ttys_shift_ids() can be called.)
*/
extern int lxc_console_create(struct lxc_conf *);
/*
* Delete a pty created via lxc_console_create():
* - set old terminal settings
* - memory allocated via lxc_console_create() is free()ed.
* - close master/slave pty pair and allocated fd for the peer (usually
* /dev/tty)
* Registered handlers in a mainloop are not automatically deleted.
*/
extern void lxc_console_delete(struct lxc_console *);
/*
* lxc_console_free: mark the console or a tty as unallocated, free any
* resources allocated by lxc_console_allocate().
*
* @conf : the configuration of the container whose tty was closed
* @fd : the socket fd whose remote side was closed, which indicated
* the console or tty is no longer in use. this is used to match
* which console/tty is being freed.
*/
extern void lxc_console_free(struct lxc_conf *conf, int fd);
extern int lxc_console_mainloop_add(struct lxc_epoll_descr *, struct lxc_handler *);
/*
* Register pty event handlers in an open mainloop
*/
extern int lxc_console_mainloop_add(struct lxc_epoll_descr *, struct lxc_conf *);
/*
* Handle SIGWINCH events on the allocated ptys.
*/
extern void lxc_console_sigwinch(int sig);
/*
* Connect to one of the ptys given to the container via lxc.tty.
* - allocates either the current controlling pty (default) or a user specified
* pty as peer pty for the containers tty
* - sets up SIGWINCH handler, winsz, and new terminal settings
* - opens mainloop
* - registers SIGWINCH, I/O handlers in the mainloop
* - performs all necessary cleanup operations
*/
extern int lxc_console(struct lxc_container *c, int ttynum,
int stdinfd, int stdoutfd, int stderrfd,
int escape);
/*
* Allocate one of the ptys given to the container via lxc.tty. Returns an open
* fd to the allocated pty.
* Set ttynum to -1 to allocate the first available pty, or to a value within
* the range specified by lxc.tty to allocate a specific pty.
*/
extern int lxc_console_getfd(struct lxc_container *c, int *ttynum,
int *masterfd);
extern int lxc_console_set_stdfds(struct lxc_handler *);
/*
* Make fd a duplicate of the standard file descriptors:
* fd is made a duplicate of a specific standard file descriptor iff the
* standard file descriptor refers to a pty.
*/
extern int lxc_console_set_stdfds(int fd);
/*
* Handler for events on the stdin fd of the pty. To be registered via the
* corresponding functions declared and defined in mainloop.{c,h} or
* lxc_console_mainloop_add().
* This function exits the loop cleanly when an EPOLLHUP event is received.
*/
extern int lxc_console_cb_tty_stdin(int fd, uint32_t events, void *cbdata,
struct lxc_epoll_descr *descr);
/*
* Handler for events on the master fd of the pty. To be registered via the
* corresponding functions declared and defined in mainloop.{c,h} or
* lxc_console_mainloop_add().
* This function exits the loop cleanly when an EPOLLHUP event is received.
*/
extern int lxc_console_cb_tty_master(int fd, uint32_t events, void *cbdata,
struct lxc_epoll_descr *descr);
/*
* Setup new terminal properties. The old terminal settings are stored in
* oldtios.
*/
extern int lxc_setup_tios(int fd, struct termios *oldtios);
/*
* lxc_console_winsz: propagte winsz from one terminal to another
*
* @srcfd : terminal to get size from (typically a slave pty)
* @dstfd : terminal to set size on (typically a master pty)
*/
extern void lxc_console_winsz(int srcfd, int dstfd);
/*
* lxc_console_sigwinch_init: install SIGWINCH handler
*
* @srcfd : src for winsz in SIGWINCH handler
* @dstfd : dst for winsz in SIGWINCH handler
*
* Returns lxc_tty_state structure on success or NULL on failure. The sigfd
* member of the returned lxc_tty_state can be select()/poll()ed/epoll()ed
* on (ie added to a mainloop) for SIGWINCH.
*
* Must be called with process_lock held to protect the lxc_ttys list, or
* from a non-threaded context.
*
* Note that SIGWINCH isn't installed as a classic asychronous handler,
* rather signalfd(2) is used so that we can handle the signal when we're
* ready for it. This avoids deadlocks since a signal handler
* (ie lxc_console_sigwinch()) would need to take the thread mutex to
* prevent lxc_ttys list corruption, but using the fd we can provide the
* tty_state needed to the callback (lxc_console_cb_sigwinch_fd()).
*
* This function allocates memory. It is up to the caller to free it.
*/
extern struct lxc_tty_state *lxc_console_sigwinch_init(int srcfd, int dstfd);
/*
* Handler for SIGWINCH events. To be registered via the corresponding functions
* declared and defined in mainloop.{c,h} or lxc_console_mainloop_add().
*/
extern int lxc_console_cb_sigwinch_fd(int fd, uint32_t events, void *cbdata,
struct lxc_epoll_descr *descr);
/*
* lxc_console_sigwinch_fini: uninstall SIGWINCH handler
*
* @ts : the lxc_tty_state returned by lxc_console_sigwinch_init
*
* Restore the saved signal handler that was in effect at the time
* lxc_console_sigwinch_init() was called.
*
* Must be called with process_lock held to protect the lxc_ttys list, or
* from a non-threaded context.
*/
extern void lxc_console_sigwinch_fini(struct lxc_tty_state *ts);
#endif
......@@ -21,21 +21,36 @@
* 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);
}
......@@ -339,7 +339,7 @@ static int lxc_poll(const char *name, struct lxc_handler *handler)
goto out_mainloop_open;
}
if (lxc_console_mainloop_add(&descr, handler)) {
if (lxc_console_mainloop_add(&descr, handler->conf)) {
ERROR("failed to add console handler to mainloop");
goto out_mainloop_open;
}
......@@ -751,7 +751,7 @@ static int do_start(void *data)
* setup on its console ie. the pty allocated in lxc_console_create()
* so make sure that that pty is stdin,stdout,stderr.
*/
if (lxc_console_set_stdfds(handler) < 0)
if (lxc_console_set_stdfds(handler->conf->console.slave) < 0)
goto out_warn_father;
/* If we mounted a temporary proc, then unmount it now */
......@@ -800,7 +800,7 @@ static int save_phys_nics(struct lxc_conf *conf)
if (!am_root)
return 0;
lxc_list_for_each(iterator, &conf->network) {
struct lxc_netdev *netdev = iterator->elem;
......
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