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
...@@ -21,27 +21,28 @@ ...@@ -21,27 +21,28 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * 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 <signal.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <termios.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <sys/epoll.h>
#include <errno.h>
#include <sys/types.h> #include <sys/types.h>
#include <termios.h>
#include <lxc/lxccontainer.h> #include <lxc/lxccontainer.h>
#include "log.h" #include "af_unix.h"
#include "conf.h"
#include "config.h"
#include "start.h" /* for struct lxc_handler */
#include "caps.h" #include "caps.h"
#include "commands.h" #include "commands.h"
#include "mainloop.h" #include "conf.h"
#include "af_unix.h" #include "config.h"
#include "console.h"
#include "log.h"
#include "lxclock.h" #include "lxclock.h"
#include "mainloop.h"
#include "start.h" /* for struct lxc_handler */
#include "utils.h" #include "utils.h"
#if HAVE_PTY_H #if HAVE_PTY_H
...@@ -55,19 +56,6 @@ lxc_log_define(console, lxc); ...@@ -55,19 +56,6 @@ lxc_log_define(console, lxc);
static struct lxc_list lxc_ttys; static struct lxc_list lxc_ttys;
typedef void (*sighandler_t)(int); 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)) __attribute__((constructor))
void lxc_console_init(void) void lxc_console_init(void)
...@@ -75,12 +63,7 @@ void lxc_console_init(void) ...@@ -75,12 +63,7 @@ void lxc_console_init(void)
lxc_list_init(&lxc_ttys); lxc_list_init(&lxc_ttys);
} }
/* lxc_console_winsz: propagte winsz from one terminal to another void lxc_console_winsz(int srcfd, int dstfd)
*
* @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)
{ {
struct winsize wsz; struct winsize wsz;
if (isatty(srcfd) && ioctl(srcfd, TIOCGWINSZ, &wsz) == 0) { if (isatty(srcfd) && ioctl(srcfd, TIOCGWINSZ, &wsz) == 0) {
...@@ -93,10 +76,8 @@ static void lxc_console_winsz(int srcfd, int dstfd) ...@@ -93,10 +76,8 @@ static void lxc_console_winsz(int srcfd, int dstfd)
static void lxc_console_winch(struct lxc_tty_state *ts) static void lxc_console_winch(struct lxc_tty_state *ts)
{ {
lxc_console_winsz(ts->stdinfd, ts->masterfd); lxc_console_winsz(ts->stdinfd, ts->masterfd);
if (ts->winch_proxy) { if (ts->winch_proxy)
lxc_cmd_console_winch(ts->winch_proxy, lxc_cmd_console_winch(ts->winch_proxy, ts->winch_proxy_lxcpath);
ts->winch_proxy_lxcpath);
}
} }
void lxc_console_sigwinch(int sig) void lxc_console_sigwinch(int sig)
...@@ -110,13 +91,14 @@ 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, int lxc_console_cb_sigwinch_fd(int fd, uint32_t events, void *cbdata,
struct lxc_epoll_descr *descr) struct lxc_epoll_descr *descr)
{ {
struct signalfd_siginfo siginfo; struct signalfd_siginfo siginfo;
struct lxc_tty_state *ts = cbdata; 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"); ERROR("failed to read signal info");
return -1; return -1;
} }
...@@ -125,27 +107,7 @@ static int lxc_console_cb_sigwinch_fd(int fd, uint32_t events, void *cbdata, ...@@ -125,27 +107,7 @@ static int lxc_console_cb_sigwinch_fd(int fd, uint32_t events, void *cbdata,
return 0; return 0;
} }
/* struct lxc_tty_state *lxc_console_sigwinch_init(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()).
*/
static struct lxc_tty_state *lxc_console_sigwinch_init(int srcfd, int dstfd)
{ {
sigset_t mask; sigset_t mask;
struct lxc_tty_state *ts; struct lxc_tty_state *ts;
...@@ -155,9 +117,9 @@ static struct lxc_tty_state *lxc_console_sigwinch_init(int srcfd, int dstfd) ...@@ -155,9 +117,9 @@ static struct lxc_tty_state *lxc_console_sigwinch_init(int srcfd, int dstfd)
return NULL; return NULL;
memset(ts, 0, sizeof(*ts)); memset(ts, 0, sizeof(*ts));
ts->stdinfd = srcfd; ts->stdinfd = srcfd;
ts->masterfd = dstfd; ts->masterfd = dstfd;
ts->sigfd = -1; ts->sigfd = -1;
/* add tty to list to be scanned at SIGWINCH time */ /* add tty to list to be scanned at SIGWINCH time */
lxc_list_add_elem(&ts->node, ts); lxc_list_add_elem(&ts->node, ts);
...@@ -166,45 +128,28 @@ static struct lxc_tty_state *lxc_console_sigwinch_init(int srcfd, int dstfd) ...@@ -166,45 +128,28 @@ static struct lxc_tty_state *lxc_console_sigwinch_init(int srcfd, int dstfd)
sigemptyset(&mask); sigemptyset(&mask);
sigaddset(&mask, SIGWINCH); sigaddset(&mask, SIGWINCH);
if (sigprocmask(SIG_BLOCK, &mask, &ts->oldmask)) { if (sigprocmask(SIG_BLOCK, &mask, &ts->oldmask)) {
SYSERROR("failed to block SIGWINCH"); SYSERROR("failed to block SIGWINCH.");
goto err1; ts->sigfd = -1;
return ts;
} }
ts->sigfd = signalfd(-1, &mask, 0); ts->sigfd = signalfd(-1, &mask, 0);
if (ts->sigfd < 0) { if (ts->sigfd < 0) {
SYSERROR("failed to get signalfd"); SYSERROR("failed to get signalfd.");
goto err2; sigprocmask(SIG_SETMASK, &ts->oldmask, NULL);
ts->sigfd = -1;
return ts;
} }
DEBUG("%d got SIGWINCH fd %d", getpid(), ts->sigfd); 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; return ts;
} }
/* void lxc_console_sigwinch_fini(struct lxc_tty_state *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)
{ {
if (ts->sigfd >= 0) { if (ts->sigfd >= 0)
close(ts->sigfd); close(ts->sigfd);
}
lxc_list_del(&ts->node); lxc_list_del(&ts->node);
sigprocmask(SIG_SETMASK, &ts->oldmask, NULL); sigprocmask(SIG_SETMASK, &ts->oldmask, NULL);
free(ts); free(ts);
...@@ -215,34 +160,30 @@ static int lxc_console_cb_con(int fd, uint32_t events, void *data, ...@@ -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; struct lxc_console *console = (struct lxc_console *)data;
char buf[1024]; char buf[1024];
int r,w; int r, w;
w = r = read(fd, buf, sizeof(buf));
if (r < 0) {
SYSERROR("failed to read");
return 1;
}
if (!r) { w = r = lxc_read_nointr(fd, buf, sizeof(buf));
if (r <= 0) {
INFO("console client on fd %d has exited", fd); INFO("console client on fd %d has exited", fd);
lxc_mainloop_del_handler(descr, fd); lxc_mainloop_del_handler(descr, fd);
close(fd); close(fd);
return 0; return 1;
} }
if (fd == console->peer) if (fd == console->peer)
w = write(console->master, buf, r); w = lxc_write_nointr(console->master, buf, r);
if (fd == console->master) { if (fd == console->master) {
if (console->log_fd >= 0) 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) if (console->peer >= 0)
w = write(console->peer, buf, r); w = lxc_write_nointr(console->peer, buf, r);
} }
if (w != r) if (w != r)
WARN("console short write r:%d w:%d", r, w); WARN("console short write r:%d w:%d", r, w);
return 0; return 0;
} }
...@@ -254,7 +195,7 @@ static void lxc_console_mainloop_add_peer(struct lxc_console *console) ...@@ -254,7 +195,7 @@ static void lxc_console_mainloop_add_peer(struct lxc_console *console)
WARN("console peer not added to mainloop"); 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, if (lxc_mainloop_add_handler(console->descr,
console->tty_state->sigfd, console->tty_state->sigfd,
lxc_console_cb_sigwinch_fd, lxc_console_cb_sigwinch_fd,
...@@ -265,10 +206,9 @@ static void lxc_console_mainloop_add_peer(struct lxc_console *console) ...@@ -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, extern int lxc_console_mainloop_add(struct lxc_epoll_descr *descr,
struct lxc_handler *handler) struct lxc_conf *conf)
{ {
struct lxc_conf *conf = handler->conf;
struct lxc_console *console = &conf->console; struct lxc_console *console = &conf->console;
if (conf->is_execute) { if (conf->is_execute) {
...@@ -302,7 +242,7 @@ int lxc_console_mainloop_add(struct lxc_epoll_descr *descr, ...@@ -302,7 +242,7 @@ int lxc_console_mainloop_add(struct lxc_epoll_descr *descr,
return 0; return 0;
} }
static int setup_tios(int fd, struct termios *oldtios) int lxc_setup_tios(int fd, struct termios *oldtios)
{ {
struct termios newtios; struct termios newtios;
...@@ -317,17 +257,31 @@ static int setup_tios(int fd, struct termios *oldtios) ...@@ -317,17 +257,31 @@ static int setup_tios(int fd, struct termios *oldtios)
return -1; return -1;
} }
/* ensure we don't end up in an endless loop:
* The kernel might fire SIGTTOU while an
* ioctl() in tcsetattr() is executed. When the ioctl()
* is resumed and retries, the signal handler interrupts it again.
*/
signal (SIGTTIN, SIG_IGN);
signal (SIGTTOU, SIG_IGN);
newtios = *oldtios; newtios = *oldtios;
/* Remove the echo characters and signal reception, the echo /* We use the same settings that ssh does. */
* will be done with master proxying */ newtios.c_iflag |= IGNPAR;
newtios.c_iflag &= ~IGNBRK; newtios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF);
newtios.c_iflag &= BRKINT; #ifdef IUCLC
newtios.c_lflag &= ~(ECHO|ICANON|ISIG); newtios.c_iflag &= ~IUCLC;
#endif
newtios.c_lflag &= ~(TOSTOP | 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[VMIN] = 1;
newtios.c_cc[VTIME] = 0; newtios.c_cc[VTIME] = 0;
/* Set new attributes */ /* Set new attributes. */
if (tcsetattr(fd, TCSAFLUSH, &newtios)) { if (tcsetattr(fd, TCSAFLUSH, &newtios)) {
ERROR("failed to set new terminal settings"); ERROR("failed to set new terminal settings");
return -1; return -1;
...@@ -338,7 +292,7 @@ static int setup_tios(int fd, struct termios *oldtios) ...@@ -338,7 +292,7 @@ static int setup_tios(int fd, struct termios *oldtios)
static void lxc_console_peer_proxy_free(struct lxc_console *console) 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); lxc_console_sigwinch_fini(console->tty_state);
console->tty_state = NULL; console->tty_state = NULL;
} }
...@@ -382,7 +336,7 @@ static int lxc_console_peer_proxy_alloc(struct lxc_console *console, int sockfd) ...@@ -382,7 +336,7 @@ static int lxc_console_peer_proxy_alloc(struct lxc_console *console, int sockfd)
return -1; return -1;
} }
if (setup_tios(console->peerpty.slave, &oldtermio) < 0) if (lxc_setup_tios(console->peerpty.slave, &oldtermio) < 0)
goto err1; goto err1;
ts = lxc_console_sigwinch_init(console->peerpty.master, console->master); ts = lxc_console_sigwinch_init(console->peerpty.master, console->master);
...@@ -402,13 +356,6 @@ err1: ...@@ -402,13 +356,6 @@ err1:
return -1; 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 lxc_console_allocate(struct lxc_conf *conf, int sockfd, int *ttyreq)
{ {
int masterfd = -1, ttynum; int masterfd = -1, ttynum;
...@@ -435,9 +382,8 @@ int lxc_console_allocate(struct lxc_conf *conf, int sockfd, int *ttyreq) ...@@ -435,9 +382,8 @@ int lxc_console_allocate(struct lxc_conf *conf, int sockfd, int *ttyreq)
} }
/* search for next available tty, fixup index tty1 => [0] */ /* search for next available tty, fixup index tty1 => [0] */
for (ttynum = 1; for (ttynum = 1; ttynum <= tty_info->nbtty && tty_info->pty_info[ttynum - 1].busy; ttynum++)
ttynum <= tty_info->nbtty && tty_info->pty_info[ttynum - 1].busy; ;
ttynum++);
/* we didn't find any available slot for tty */ /* we didn't find any available slot for tty */
if (ttynum > tty_info->nbtty) if (ttynum > tty_info->nbtty)
...@@ -452,14 +398,6 @@ out: ...@@ -452,14 +398,6 @@ out:
return masterfd; 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) void lxc_console_free(struct lxc_conf *conf, int fd)
{ {
int i; int i;
...@@ -509,9 +447,11 @@ static void lxc_console_peer_default(struct lxc_console *console) ...@@ -509,9 +447,11 @@ static void lxc_console_peer_default(struct lxc_console *console)
goto err1; goto err1;
ts = lxc_console_sigwinch_init(console->peer, console->master); ts = lxc_console_sigwinch_init(console->peer, console->master);
if (!ts)
WARN("Unable to install SIGWINCH");
console->tty_state = ts; console->tty_state = ts;
if (!ts) {
WARN("Unable to install SIGWINCH");
goto err1;
}
lxc_console_winsz(console->peer, console->master); lxc_console_winsz(console->peer, console->master);
...@@ -521,7 +461,7 @@ static void lxc_console_peer_default(struct lxc_console *console) ...@@ -521,7 +461,7 @@ static void lxc_console_peer_default(struct lxc_console *console)
goto err1; goto err1;
} }
if (setup_tios(console->peer, console->tios) < 0) if (lxc_setup_tios(console->peer, console->tios) < 0)
goto err2; goto err2;
return; return;
...@@ -534,6 +474,7 @@ err1: ...@@ -534,6 +474,7 @@ err1:
console->peer = -1; console->peer = -1;
out: out:
DEBUG("no console peer"); DEBUG("no console peer");
return;
} }
void lxc_console_delete(struct lxc_console *console) void lxc_console_delete(struct lxc_console *console)
...@@ -611,70 +552,81 @@ err: ...@@ -611,70 +552,81 @@ err:
return -1; return -1;
} }
int lxc_console_set_stdfds(struct lxc_handler *handler) int lxc_console_set_stdfds(int fd)
{ {
struct lxc_conf *conf = handler->conf; if (fd < 0)
struct lxc_console *console = &conf->console;
if (console->slave < 0)
return 0; return 0;
if (dup2(console->slave, 0) < 0 || if (isatty(STDIN_FILENO))
dup2(console->slave, 1) < 0 || if (dup2(fd, STDIN_FILENO) < 0) {
dup2(console->slave, 2) < 0) SYSERROR("failed to duplicate stdin.");
{ return -1;
SYSERROR("failed to dup console"); }
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; return 0;
} }
static int lxc_console_cb_tty_stdin(int fd, uint32_t events, void *cbdata, int lxc_console_cb_tty_stdin(int fd, uint32_t events, void *cbdata,
struct lxc_epoll_descr *descr) struct lxc_epoll_descr *descr)
{ {
struct lxc_tty_state *ts = cbdata; struct lxc_tty_state *ts = cbdata;
char c; char c;
assert(fd == ts->stdinfd); if (fd != ts->stdinfd)
if (read(ts->stdinfd, &c, 1) < 0) {
SYSERROR("failed to read");
return 1; return 1;
}
/* we want to exit the console with Ctrl+a q */ if (lxc_read_nointr(ts->stdinfd, &c, 1) <= 0)
if (c == ts->escape && !ts->saw_escape) {
ts->saw_escape = 1;
return 0;
}
if (c == 'q' && ts->saw_escape)
return 1; return 1;
ts->saw_escape = 0; if (ts->escape != -1) {
if (write(ts->masterfd, &c, 1) < 0) { /* we want to exit the console with Ctrl+a q */
SYSERROR("failed to write"); if (c == ts->escape && !ts->saw_escape) {
return 1; 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; return 0;
} }
static int lxc_console_cb_tty_master(int fd, uint32_t events, void *cbdata, int lxc_console_cb_tty_master(int fd, uint32_t events, void *cbdata,
struct lxc_epoll_descr *descr) struct lxc_epoll_descr *descr)
{ {
struct lxc_tty_state *ts = cbdata; struct lxc_tty_state *ts = cbdata;
char buf[1024]; char buf[1024];
int r,w; int r, w;
assert(fd == ts->masterfd); if (fd != ts->masterfd)
r = read(fd, buf, sizeof(buf)); return 1;
if (r < 0) {
SYSERROR("failed to read"); r = lxc_read_nointr(fd, buf, sizeof(buf));
if (r <= 0)
return 1; return 1;
}
w = write(ts->stdoutfd, buf, r); w = lxc_write_nointr(ts->stdoutfd, buf, r);
if (w < 0 || w != r) { if (w <= 0) {
return 1;
} else if (w != r) {
SYSERROR("failed to write"); SYSERROR("failed to write");
return 1; return 1;
} }
...@@ -701,7 +653,7 @@ int lxc_console(struct lxc_container *c, int ttynum, ...@@ -701,7 +653,7 @@ int lxc_console(struct lxc_container *c, int ttynum,
return -1; return -1;
} }
ret = setup_tios(stdinfd, &oldtios); ret = lxc_setup_tios(stdinfd, &oldtios);
if (ret) { if (ret) {
ERROR("failed to setup tios"); ERROR("failed to setup tios");
return -1; return -1;
...@@ -741,11 +693,13 @@ int lxc_console(struct lxc_container *c, int ttynum, ...@@ -741,11 +693,13 @@ int lxc_console(struct lxc_container *c, int ttynum,
goto err3; goto err3;
} }
ret = lxc_mainloop_add_handler(&descr, ts->sigfd, if (ts->sigfd != -1) {
lxc_console_cb_sigwinch_fd, ts); ret = lxc_mainloop_add_handler(&descr, ts->sigfd,
if (ret) { lxc_console_cb_sigwinch_fd, ts);
ERROR("failed to add handler for SIGWINCH fd"); if (ret) {
goto err4; ERROR("failed to add handler for SIGWINCH fd");
goto err4;
}
} }
ret = lxc_mainloop_add_handler(&descr, ts->stdinfd, ret = lxc_mainloop_add_handler(&descr, ts->stdinfd,
...@@ -773,7 +727,8 @@ int lxc_console(struct lxc_container *c, int ttynum, ...@@ -773,7 +727,8 @@ int lxc_console(struct lxc_container *c, int ttynum,
err4: err4:
lxc_mainloop_close(&descr); lxc_mainloop_close(&descr);
err3: err3:
lxc_console_sigwinch_fini(ts); if (ts->sigfd != -1)
lxc_console_sigwinch_fini(ts);
err2: err2:
close(masterfd); close(masterfd);
close(ttyfd); close(ttyfd);
...@@ -782,3 +737,4 @@ err1: ...@@ -782,3 +737,4 @@ err1:
return ret; return ret;
} }
...@@ -24,21 +24,195 @@ ...@@ -24,21 +24,195 @@
#ifndef __LXC_CONSOLE_H #ifndef __LXC_CONSOLE_H
#define __LXC_CONSOLE_H #define __LXC_CONSOLE_H
struct lxc_epoll_descr; #include "conf.h"
struct lxc_container; #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); 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 *); 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 *); 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 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); 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, extern int lxc_console(struct lxc_container *c, int ttynum,
int stdinfd, int stdoutfd, int stderrfd, int stdinfd, int stdoutfd, int stderrfd,
int escape); 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, extern int lxc_console_getfd(struct lxc_container *c, int *ttynum,
int *masterfd); 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 #endif
...@@ -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);
} }
...@@ -339,7 +339,7 @@ static int lxc_poll(const char *name, struct lxc_handler *handler) ...@@ -339,7 +339,7 @@ static int lxc_poll(const char *name, struct lxc_handler *handler)
goto out_mainloop_open; 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"); ERROR("failed to add console handler to mainloop");
goto out_mainloop_open; goto out_mainloop_open;
} }
...@@ -751,7 +751,7 @@ static int do_start(void *data) ...@@ -751,7 +751,7 @@ static int do_start(void *data)
* setup on its console ie. the pty allocated in lxc_console_create() * setup on its console ie. the pty allocated in lxc_console_create()
* so make sure that that pty is stdin,stdout,stderr. * 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; goto out_warn_father;
/* If we mounted a temporary proc, then unmount it now */ /* If we mounted a temporary proc, then unmount it now */
...@@ -800,7 +800,7 @@ static int save_phys_nics(struct lxc_conf *conf) ...@@ -800,7 +800,7 @@ static int save_phys_nics(struct lxc_conf *conf)
if (!am_root) if (!am_root)
return 0; return 0;
lxc_list_for_each(iterator, &conf->network) { lxc_list_for_each(iterator, &conf->network) {
struct lxc_netdev *netdev = iterator->elem; 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