[lxc-devel] [lxc/stable-1.0] stable-1.0: backport fixes for CVE-2016-10124
brauner on Github
lxc-bot at linuxcontainers.org
Mon Jan 16 14:37:30 UTC 2017
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 431 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20170116/77e31064/attachment.bin>
-------------- next part --------------
From cf6d7eb6c521f1491ebeb3550eb30e6965d2d03b Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 16 Jan 2017 15:03:59 +0100
Subject: [PATCH 1/2] CVE-2016-10124: backport new console backend
- Make escape sequence to exit tty optional since we want to reuse
lxc_console_cb_tty_stdin() in lxc_attach.c.
- Export the following functions since they can be reused in other modules:
- lxc_console_cb_tty_stdin()
- lxc_console_cb_tty_master()
- lxc_setup_tios(int fd, struct termios *oldtios);
- lxc_console_winsz(int srcfd, int dstfd);
- lxc_console_cb_sigwinch_fd(int fd, uint32_t events, void *cbdata, struct lxc_epoll_descr *descr);
- lxc_tty_state *lxc_console_sigwinch_init(int srcfd, int dstfd);
- lxc_console_sigwinch_fini(struct lxc_tty_state *ts);
- rewrite lxc_console_set_stdfds()
- Make lxc_console_set_stdfds useable by other callers that do not have
access to lxc_handler.
- Use ssh settings for ptys.
- Remove all asserts from console.{c,h}.
- Adapt start.c to changes.
Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
src/lxc/console.c | 304 ++++++++++++++++++++++--------------------------------
src/lxc/console.h | 182 +++++++++++++++++++++++++++++++-
src/lxc/start.c | 6 +-
3 files changed, 307 insertions(+), 185 deletions(-)
diff --git a/src/lxc/console.c b/src/lxc/console.c
index d48cb81..908ead0 100644
--- a/src/lxc/console.c
+++ b/src/lxc/console.c
@@ -21,27 +21,28 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
+#include <termios.h>
#include <unistd.h>
-#include <fcntl.h>
-#include <errno.h>
+#include <sys/epoll.h>
#include <sys/types.h>
-#include <termios.h>
#include <lxc/lxccontainer.h>
-#include "log.h"
-#include "conf.h"
-#include "config.h"
-#include "start.h" /* for struct lxc_handler */
+#include "af_unix.h"
#include "caps.h"
#include "commands.h"
-#include "mainloop.h"
-#include "af_unix.h"
+#include "conf.h"
+#include "config.h"
+#include "console.h"
+#include "log.h"
#include "lxclock.h"
+#include "mainloop.h"
+#include "start.h" /* for struct lxc_handler */
#include "utils.h"
#if HAVE_PTY_H
@@ -55,19 +56,6 @@ lxc_log_define(console, lxc);
static struct lxc_list lxc_ttys;
typedef void (*sighandler_t)(int);
-struct lxc_tty_state
-{
- struct lxc_list node;
- int stdinfd;
- int stdoutfd;
- int masterfd;
- int escape;
- int saw_escape;
- const char *winch_proxy;
- const char *winch_proxy_lxcpath;
- int sigfd;
- sigset_t oldmask;
-};
__attribute__((constructor))
void lxc_console_init(void)
@@ -75,12 +63,7 @@ void lxc_console_init(void)
lxc_list_init(&lxc_ttys);
}
-/* 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)
- */
-static void lxc_console_winsz(int srcfd, int dstfd)
+void lxc_console_winsz(int srcfd, int dstfd)
{
struct winsize wsz;
if (isatty(srcfd) && ioctl(srcfd, TIOCGWINSZ, &wsz) == 0) {
@@ -93,10 +76,8 @@ static void lxc_console_winsz(int srcfd, int dstfd)
static void lxc_console_winch(struct lxc_tty_state *ts)
{
lxc_console_winsz(ts->stdinfd, ts->masterfd);
- if (ts->winch_proxy) {
- lxc_cmd_console_winch(ts->winch_proxy,
- ts->winch_proxy_lxcpath);
- }
+ if (ts->winch_proxy)
+ lxc_cmd_console_winch(ts->winch_proxy, ts->winch_proxy_lxcpath);
}
void lxc_console_sigwinch(int sig)
@@ -110,13 +91,14 @@ void lxc_console_sigwinch(int sig)
}
}
-static int lxc_console_cb_sigwinch_fd(int fd, uint32_t events, void *cbdata,
- struct lxc_epoll_descr *descr)
+int lxc_console_cb_sigwinch_fd(int fd, uint32_t events, void *cbdata,
+ struct lxc_epoll_descr *descr)
{
struct signalfd_siginfo siginfo;
struct lxc_tty_state *ts = cbdata;
- if (read(fd, &siginfo, sizeof(siginfo)) < sizeof(siginfo)) {
+ ssize_t ret = read(fd, &siginfo, sizeof(siginfo));
+ if (ret < 0 || (size_t)ret < sizeof(siginfo)) {
ERROR("failed to read signal info");
return -1;
}
@@ -125,27 +107,7 @@ static int lxc_console_cb_sigwinch_fd(int fd, uint32_t events, void *cbdata,
return 0;
}
-/*
- * 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()).
- */
-static struct lxc_tty_state *lxc_console_sigwinch_init(int srcfd, int dstfd)
+struct lxc_tty_state *lxc_console_sigwinch_init(int srcfd, int dstfd)
{
sigset_t mask;
struct lxc_tty_state *ts;
@@ -155,9 +117,9 @@ static struct lxc_tty_state *lxc_console_sigwinch_init(int srcfd, int dstfd)
return NULL;
memset(ts, 0, sizeof(*ts));
- ts->stdinfd = srcfd;
+ ts->stdinfd = srcfd;
ts->masterfd = dstfd;
- ts->sigfd = -1;
+ ts->sigfd = -1;
/* add tty to list to be scanned at SIGWINCH time */
lxc_list_add_elem(&ts->node, ts);
@@ -166,45 +128,28 @@ static struct lxc_tty_state *lxc_console_sigwinch_init(int srcfd, int dstfd)
sigemptyset(&mask);
sigaddset(&mask, SIGWINCH);
if (sigprocmask(SIG_BLOCK, &mask, &ts->oldmask)) {
- SYSERROR("failed to block SIGWINCH");
- goto err1;
+ SYSERROR("failed to block SIGWINCH.");
+ ts->sigfd = -1;
+ return ts;
}
ts->sigfd = signalfd(-1, &mask, 0);
if (ts->sigfd < 0) {
- SYSERROR("failed to get signalfd");
- goto err2;
+ SYSERROR("failed to get signalfd.");
+ sigprocmask(SIG_SETMASK, &ts->oldmask, NULL);
+ ts->sigfd = -1;
+ return ts;
}
DEBUG("%d got SIGWINCH fd %d", getpid(), ts->sigfd);
- goto out;
-
-err2:
- sigprocmask(SIG_SETMASK, &ts->oldmask, NULL);
-err1:
- lxc_list_del(&ts->node);
- free(ts);
- ts = NULL;
-out:
return ts;
}
-/*
- * 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.
- */
-static void lxc_console_sigwinch_fini(struct lxc_tty_state *ts)
+void lxc_console_sigwinch_fini(struct lxc_tty_state *ts)
{
- if (ts->sigfd >= 0) {
+ if (ts->sigfd >= 0)
close(ts->sigfd);
- }
+
lxc_list_del(&ts->node);
sigprocmask(SIG_SETMASK, &ts->oldmask, NULL);
free(ts);
@@ -215,34 +160,30 @@ static int lxc_console_cb_con(int fd, uint32_t events, void *data,
{
struct lxc_console *console = (struct lxc_console *)data;
char buf[1024];
- int r,w;
-
- w = r = read(fd, buf, sizeof(buf));
- if (r < 0) {
- SYSERROR("failed to read");
- return 1;
- }
+ int r, w;
- if (!r) {
+ w = r = lxc_read_nointr(fd, buf, sizeof(buf));
+ if (r <= 0) {
INFO("console client on fd %d has exited", fd);
lxc_mainloop_del_handler(descr, fd);
close(fd);
- return 0;
+ return 1;
}
if (fd == console->peer)
- w = write(console->master, buf, r);
+ w = lxc_write_nointr(console->master, buf, r);
if (fd == console->master) {
if (console->log_fd >= 0)
- w = write(console->log_fd, buf, r);
+ w = lxc_write_nointr(console->log_fd, buf, r);
if (console->peer >= 0)
- w = write(console->peer, buf, r);
+ w = lxc_write_nointr(console->peer, buf, r);
}
if (w != r)
WARN("console short write r:%d w:%d", r, w);
+
return 0;
}
@@ -254,7 +195,7 @@ static void lxc_console_mainloop_add_peer(struct lxc_console *console)
WARN("console peer not added to mainloop");
}
- if (console->tty_state) {
+ if (console->tty_state && console->tty_state->sigfd != -1) {
if (lxc_mainloop_add_handler(console->descr,
console->tty_state->sigfd,
lxc_console_cb_sigwinch_fd,
@@ -265,10 +206,9 @@ static void lxc_console_mainloop_add_peer(struct lxc_console *console)
}
}
-int lxc_console_mainloop_add(struct lxc_epoll_descr *descr,
- struct lxc_handler *handler)
+extern int lxc_console_mainloop_add(struct lxc_epoll_descr *descr,
+ struct lxc_conf *conf)
{
- struct lxc_conf *conf = handler->conf;
struct lxc_console *console = &conf->console;
if (conf->is_execute) {
@@ -302,7 +242,7 @@ int lxc_console_mainloop_add(struct lxc_epoll_descr *descr,
return 0;
}
-static int setup_tios(int fd, struct termios *oldtios)
+int lxc_setup_tios(int fd, struct termios *oldtios)
{
struct termios newtios;
@@ -319,15 +259,21 @@ static int setup_tios(int fd, struct termios *oldtios)
newtios = *oldtios;
- /* Remove the echo characters and signal reception, the echo
- * will be done with master proxying */
- newtios.c_iflag &= ~IGNBRK;
- newtios.c_iflag &= BRKINT;
- newtios.c_lflag &= ~(ECHO|ICANON|ISIG);
+ /* We use the same settings that ssh does. */
+ newtios.c_iflag |= IGNPAR;
+ newtios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF);
+#ifdef IUCLC
+ newtios.c_iflag &= ~IUCLC;
+#endif
+ newtios.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL);
+#ifdef IEXTEN
+ newtios.c_lflag &= ~IEXTEN;
+#endif
+ newtios.c_oflag &= ~OPOST;
newtios.c_cc[VMIN] = 1;
newtios.c_cc[VTIME] = 0;
- /* Set new attributes */
+ /* Set new attributes. */
if (tcsetattr(fd, TCSAFLUSH, &newtios)) {
ERROR("failed to set new terminal settings");
return -1;
@@ -338,7 +284,7 @@ static int setup_tios(int fd, struct termios *oldtios)
static void lxc_console_peer_proxy_free(struct lxc_console *console)
{
- if (console->tty_state) {
+ if (console->tty_state && console->tty_state->sigfd != -1) {
lxc_console_sigwinch_fini(console->tty_state);
console->tty_state = NULL;
}
@@ -382,7 +328,7 @@ static int lxc_console_peer_proxy_alloc(struct lxc_console *console, int sockfd)
return -1;
}
- if (setup_tios(console->peerpty.slave, &oldtermio) < 0)
+ if (lxc_setup_tios(console->peerpty.slave, &oldtermio) < 0)
goto err1;
ts = lxc_console_sigwinch_init(console->peerpty.master, console->master);
@@ -402,13 +348,6 @@ static int lxc_console_peer_proxy_alloc(struct lxc_console *console, int sockfd)
return -1;
}
-/* 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
- */
int lxc_console_allocate(struct lxc_conf *conf, int sockfd, int *ttyreq)
{
int masterfd = -1, ttynum;
@@ -435,9 +374,8 @@ int lxc_console_allocate(struct lxc_conf *conf, int sockfd, int *ttyreq)
}
/* search for next available tty, fixup index tty1 => [0] */
- for (ttynum = 1;
- ttynum <= tty_info->nbtty && tty_info->pty_info[ttynum - 1].busy;
- ttynum++);
+ for (ttynum = 1; ttynum <= tty_info->nbtty && tty_info->pty_info[ttynum - 1].busy; ttynum++)
+ ;
/* we didn't find any available slot for tty */
if (ttynum > tty_info->nbtty)
@@ -452,14 +390,6 @@ int lxc_console_allocate(struct lxc_conf *conf, int sockfd, int *ttyreq)
return masterfd;
}
-/* 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.
- */
void lxc_console_free(struct lxc_conf *conf, int fd)
{
int i;
@@ -509,9 +439,11 @@ static void lxc_console_peer_default(struct lxc_console *console)
goto err1;
ts = lxc_console_sigwinch_init(console->peer, console->master);
- if (!ts)
- WARN("Unable to install SIGWINCH");
console->tty_state = ts;
+ if (!ts) {
+ WARN("Unable to install SIGWINCH");
+ goto err1;
+ }
lxc_console_winsz(console->peer, console->master);
@@ -521,7 +453,7 @@ static void lxc_console_peer_default(struct lxc_console *console)
goto err1;
}
- if (setup_tios(console->peer, console->tios) < 0)
+ if (lxc_setup_tios(console->peer, console->tios) < 0)
goto err2;
return;
@@ -534,6 +466,7 @@ static void lxc_console_peer_default(struct lxc_console *console)
console->peer = -1;
out:
DEBUG("no console peer");
+ return;
}
void lxc_console_delete(struct lxc_console *console)
@@ -611,70 +544,81 @@ int lxc_console_create(struct lxc_conf *conf)
return -1;
}
-int lxc_console_set_stdfds(struct lxc_handler *handler)
+int lxc_console_set_stdfds(int fd)
{
- struct lxc_conf *conf = handler->conf;
- struct lxc_console *console = &conf->console;
-
- if (console->slave < 0)
+ if (fd < 0)
return 0;
- if (dup2(console->slave, 0) < 0 ||
- dup2(console->slave, 1) < 0 ||
- dup2(console->slave, 2) < 0)
- {
- SYSERROR("failed to dup console");
- return -1;
- }
+ if (isatty(STDIN_FILENO))
+ if (dup2(fd, STDIN_FILENO) < 0) {
+ SYSERROR("failed to duplicate stdin.");
+ return -1;
+ }
+
+ if (isatty(STDOUT_FILENO))
+ if (dup2(fd, STDOUT_FILENO) < 0) {
+ SYSERROR("failed to duplicate stdout.");
+ return -1;
+ }
+
+ if (isatty(STDERR_FILENO))
+ if (dup2(fd, STDERR_FILENO) < 0) {
+ SYSERROR("failed to duplicate stderr.");
+ return -1;
+ }
+
return 0;
}
-static int lxc_console_cb_tty_stdin(int fd, uint32_t events, void *cbdata,
- struct lxc_epoll_descr *descr)
+int lxc_console_cb_tty_stdin(int fd, uint32_t events, void *cbdata,
+ struct lxc_epoll_descr *descr)
{
struct lxc_tty_state *ts = cbdata;
char c;
- assert(fd == ts->stdinfd);
- if (read(ts->stdinfd, &c, 1) < 0) {
- SYSERROR("failed to read");
+ if (fd != ts->stdinfd)
return 1;
- }
-
- /* we want to exit the console with Ctrl+a q */
- if (c == ts->escape && !ts->saw_escape) {
- ts->saw_escape = 1;
- return 0;
- }
- if (c == 'q' && ts->saw_escape)
+ if (lxc_read_nointr(ts->stdinfd, &c, 1) <= 0)
return 1;
- ts->saw_escape = 0;
- if (write(ts->masterfd, &c, 1) < 0) {
- SYSERROR("failed to write");
- return 1;
+ if (ts->escape != -1) {
+ /* we want to exit the console with Ctrl+a q */
+ if (c == ts->escape && !ts->saw_escape) {
+ ts->saw_escape = 1;
+ return 0;
+ }
+
+ if (c == 'q' && ts->saw_escape)
+ return 1;
+
+ ts->saw_escape = 0;
}
+ if (lxc_write_nointr(ts->masterfd, &c, 1) <= 0)
+ return 1;
+
return 0;
}
-static int lxc_console_cb_tty_master(int fd, uint32_t events, void *cbdata,
- struct lxc_epoll_descr *descr)
+int lxc_console_cb_tty_master(int fd, uint32_t events, void *cbdata,
+ struct lxc_epoll_descr *descr)
{
struct lxc_tty_state *ts = cbdata;
char buf[1024];
- int r,w;
+ int r, w;
- assert(fd == ts->masterfd);
- r = read(fd, buf, sizeof(buf));
- if (r < 0) {
- SYSERROR("failed to read");
+ if (fd != ts->masterfd)
+ return 1;
+
+ r = lxc_read_nointr(fd, buf, sizeof(buf));
+ if (r <= 0)
return 1;
- }
- w = write(ts->stdoutfd, buf, r);
- if (w < 0 || w != r) {
+ w = lxc_write_nointr(ts->stdoutfd, buf, r);
+ if (w <= 0) {
+ return 1;
+ } else if (w != r) {
SYSERROR("failed to write");
return 1;
}
@@ -701,7 +645,7 @@ int lxc_console(struct lxc_container *c, int ttynum,
return -1;
}
- ret = setup_tios(stdinfd, &oldtios);
+ ret = lxc_setup_tios(stdinfd, &oldtios);
if (ret) {
ERROR("failed to setup tios");
return -1;
@@ -741,11 +685,13 @@ int lxc_console(struct lxc_container *c, int ttynum,
goto err3;
}
- ret = lxc_mainloop_add_handler(&descr, ts->sigfd,
- lxc_console_cb_sigwinch_fd, ts);
- if (ret) {
- ERROR("failed to add handler for SIGWINCH fd");
- goto err4;
+ if (ts->sigfd != -1) {
+ ret = lxc_mainloop_add_handler(&descr, ts->sigfd,
+ lxc_console_cb_sigwinch_fd, ts);
+ if (ret) {
+ ERROR("failed to add handler for SIGWINCH fd");
+ goto err4;
+ }
}
ret = lxc_mainloop_add_handler(&descr, ts->stdinfd,
@@ -773,7 +719,8 @@ int lxc_console(struct lxc_container *c, int ttynum,
err4:
lxc_mainloop_close(&descr);
err3:
- lxc_console_sigwinch_fini(ts);
+ if (ts->sigfd != -1)
+ lxc_console_sigwinch_fini(ts);
err2:
close(masterfd);
close(ttyfd);
@@ -782,3 +729,4 @@ int lxc_console(struct lxc_container *c, int ttynum,
return ret;
}
+
diff --git a/src/lxc/console.h b/src/lxc/console.h
index 41d53e6..fefdb19 100644
--- a/src/lxc/console.h
+++ b/src/lxc/console.h
@@ -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
diff --git a/src/lxc/start.c b/src/lxc/start.c
index b46e859..d9b3c40 100644
--- a/src/lxc/start.c
+++ b/src/lxc/start.c
@@ -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;
From 93f07d3c4a3225c9d35c57186a69421bc902a5a4 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 16 Jan 2017 15:10:45 +0100
Subject: [PATCH 2/2] 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 <christian.brauner at ubuntu.com>
---
src/lxc/lxc_attach.c | 279 +++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 250 insertions(+), 29 deletions(-)
diff --git a/src/lxc/lxc_attach.c b/src/lxc/lxc_attach.c
index 1c24349..446c4ce 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 <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);
}
More information about the lxc-devel
mailing list