From 9f77617b2e264da0fce7d59754ebf4776119a5df Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 30 Jul 2021 14:28:17 +0200 Subject: [PATCH] start: allow containers to use a native console After all of the previous rework we can make it possible for a container to use a console allocated from the container's devpts instance. Signed-off-by: Christian Brauner --- src/lxc/conf.c | 134 ++++++++++++++++++++++++++++++++++++++------- src/lxc/start.c | 31 ++++++----- src/lxc/terminal.c | 42 +++++++++++++- src/lxc/terminal.h | 6 ++ 4 files changed, 175 insertions(+), 38 deletions(-) diff --git a/src/lxc/conf.c b/src/lxc/conf.c index dbd7f012d..12b227959 100644 --- a/src/lxc/conf.c +++ b/src/lxc/conf.c @@ -1829,12 +1829,7 @@ static int setup_personality(personality_t persona) return 0; } -static inline bool wants_console(const struct lxc_terminal *terminal) -{ - return !terminal->path || !strequal(terminal->path, "none"); -} - -static int bind_mount_console(struct lxc_rootfs *rootfs, +static int bind_mount_console(int fd_devpts, struct lxc_rootfs *rootfs, struct lxc_terminal *console, int fd_to) { __do_close int fd_pty = -EBADF; @@ -1856,7 +1851,7 @@ static int bind_mount_console(struct lxc_rootfs *rootfs, * created a detached mount based on the newly opened O_PATH file * descriptor and then safely mount. */ - fd_pty = open_at_same(console->pty, rootfs->dfd_host, deabs(console->name), + fd_pty = open_at_same(console->pty, fd_devpts, fdstr(console->pty_nr), PROTECT_OPATH_FILE, PROTECT_LOOKUP_ABSOLUTE_XDEV, 0); if (fd_pty < 0) return syserror("Failed to open \"%s\"", console->name); @@ -1871,7 +1866,7 @@ static int bind_mount_console(struct lxc_rootfs *rootfs, return mount_fd(fd_pty, fd_to, "none", MS_BIND, 0); } -static int lxc_setup_dev_console(struct lxc_rootfs *rootfs, +static int lxc_setup_dev_console(int fd_devpts, struct lxc_rootfs *rootfs, struct lxc_terminal *console) { __do_close int fd_console = -EBADF; @@ -1911,7 +1906,7 @@ static int lxc_setup_dev_console(struct lxc_rootfs *rootfs, if (ret < 0) return syserror("Failed to change console mode"); - ret = bind_mount_console(rootfs, console, fd_console); + ret = bind_mount_console(fd_devpts, rootfs, console, fd_console); if (ret < 0) return syserror("Failed to mount \"%d(%s)\" on \"%d\"", console->pty, console->name, fd_console); @@ -1920,7 +1915,7 @@ static int lxc_setup_dev_console(struct lxc_rootfs *rootfs, return 0; } -static int lxc_setup_ttydir_console(struct lxc_rootfs *rootfs, +static int lxc_setup_ttydir_console(int fd_devpts, struct lxc_rootfs *rootfs, struct lxc_terminal *console, char *ttydir) { @@ -1982,7 +1977,7 @@ static int lxc_setup_ttydir_console(struct lxc_rootfs *rootfs, return syserror("Failed to change console mode"); /* bind mount console to '/dev//console' */ - ret = bind_mount_console(rootfs, console, fd_reg_ttydir_console); + ret = bind_mount_console(fd_devpts, rootfs, console, fd_reg_ttydir_console); if (ret < 0) return syserror("Failed to mount \"%d(%s)\" on \"%d\"", console->pty, console->name, fd_reg_ttydir_console); @@ -2022,21 +2017,53 @@ static int lxc_setup_console(const struct lxc_handler *handler, struct lxc_rootfs *rootfs, struct lxc_terminal *console, char *ttydir) { - __do_close int fd_pty = -EBADF; - int ret; + __do_close int fd_devpts_host = -EBADF; + int fd_devpts = handler->conf->devpts_fd; + int ret = -1; if (!wants_console(console)) return log_trace(0, "Skipping console setup"); + if (console->pty < 0) { + /* + * Allocate a console from the container's devpts instance. We + * have checked on the host that we have enough pty devices + * available. + */ + ret = lxc_devpts_terminal(handler->conf->devpts_fd, &console->ptx, + &console->pty, &console->pty_nr); + if (ret < 0) + return syserror("Failed to allocate console from container's devpts instance"); + + ret = strnprintf(console->name, sizeof(console->name), + "/dev/pts/%d", console->pty_nr); + if (ret < 0) + return syserror("Failed to create console path"); + } else { + /* + * We're using a console from the host's devpts instance. Open + * it again so we can later verify that the console we're + * supposed to use is still the same as the one we opened on + * the host. + */ + fd_devpts_host = open_at(rootfs->dfd_host, + "dev/pts", + PROTECT_OPATH_DIRECTORY, + PROTECT_LOOKUP_BENEATH_XDEV, + 0); + if (fd_devpts_host < 0) + return syserror("Failed to open host devpts"); + + fd_devpts = fd_devpts_host; + } + if (ttydir) - ret = lxc_setup_ttydir_console(rootfs, console, ttydir); + ret = lxc_setup_ttydir_console(fd_devpts, rootfs, console, ttydir); else - ret = lxc_setup_dev_console(rootfs, console); + ret = lxc_setup_dev_console(fd_devpts, rootfs, console); if (ret < 0) return syserror("Failed to setup console"); - fd_pty = move_fd(console->pty); - /* * Some init's such as busybox will set sane tty settings on stdin, * stdout, stderr which it thinks is the console. We already set them @@ -2044,13 +2071,20 @@ static int lxc_setup_console(const struct lxc_handler *handler, * setup on its console ie. the pty allocated in lxc_terminal_setup() so * make sure that that pty is stdin,stdout,stderr. */ - if (fd_pty >= 0) { + if (console->pty >= 0) { if (handler->daemonize || !handler->conf->is_execute) - ret = set_stdfds(fd_pty); + ret = set_stdfds(console->pty); else - ret = lxc_terminal_set_stdfds(fd_pty); + ret = lxc_terminal_set_stdfds(console->pty); if (ret < 0) - return syserror("Failed to redirect std{in,out,err} to pty file descriptor %d", fd_pty); + return syserror("Failed to redirect std{in,out,err} to pty file descriptor %d", console->pty); + + /* + * If the console has been allocated from the host's devpts + * we're done and we don't need to send fds to the parent. + */ + if (fd_devpts_host >= 0) + lxc_terminal_delete(console); } return ret; @@ -4057,6 +4091,56 @@ static int lxc_recv_ttys_from_child(struct lxc_handler *handler) return ret; } +static int lxc_send_console_to_parent(struct lxc_handler *handler) +{ + struct lxc_terminal *console = &handler->conf->console; + int ret; + + if (!wants_console(console)) + return 0; + + /* We've already allocated a console from the host's devpts instance. */ + if (console->pty < 0) + return 0; + + ret = __lxc_abstract_unix_send_two_fds(handler->data_sock[0], + console->ptx, console->pty, + console, + sizeof(struct lxc_terminal)); + if (ret < 0) + return syserror("Fail to send console to parent"); + + TRACE("Sent console to parent"); + return 0; +} + +static int lxc_recv_console_from_child(struct lxc_handler *handler) +{ + __do_close int fd_ptx = -EBADF, fd_pty = -EBADF; + struct lxc_terminal *console = &handler->conf->console; + int ret; + + if (!wants_console(console)) + return 0; + + /* We've already allocated a console from the host's devpts instance. */ + if (console->pty >= 0) + return 0; + + ret = __lxc_abstract_unix_recv_two_fds(handler->data_sock[1], + &fd_ptx, &fd_pty, + console, + sizeof(struct lxc_terminal)); + if (ret < 0) + return syserror("Fail to receive console from child"); + + console->ptx = move_fd(fd_ptx); + console->pty = move_fd(fd_pty); + + TRACE("Received console from child"); + return 0; +} + int lxc_sync_fds_parent(struct lxc_handler *handler) { int ret; @@ -4080,6 +4164,10 @@ int lxc_sync_fds_parent(struct lxc_handler *handler) return syserror_ret(ret, "Failed to receive names and ifindices for network devices from child"); } + ret = lxc_recv_console_from_child(handler); + if (ret < 0) + return syserror_ret(ret, "Failed to receive console from child"); + TRACE("Finished syncing file descriptors with child"); return 0; } @@ -4106,6 +4194,10 @@ int lxc_sync_fds_child(struct lxc_handler *handler) return syserror_ret(ret, "Failed to send network device names and ifindices to parent"); } + ret = lxc_send_console_to_parent(handler); + if (ret < 0) + return syserror_ret(ret, "Failed to send console to parent"); + TRACE("Finished syncing file descriptors with parent"); return 0; } diff --git a/src/lxc/start.c b/src/lxc/start.c index 2ab3f087d..7930974bb 100644 --- a/src/lxc/start.c +++ b/src/lxc/start.c @@ -850,6 +850,10 @@ int lxc_init(const char *name, struct lxc_handler *handler) return log_error(-1, "Failed to run lxc.hook.pre-start for container \"%s\"", name); TRACE("Ran pre-start hooks"); + ret = lxc_terminal_parent(conf); + if (ret < 0) + return log_error(-1, "Failed to allocate terminal"); + /* The signal fd has to be created before forking otherwise if the child * process exits before we setup the signal fd, the event will be lost * and the command will be stuck. @@ -859,30 +863,24 @@ int lxc_init(const char *name, struct lxc_handler *handler) return log_error(-1, "Failed to setup SIGCHLD fd handler."); TRACE("Set up signal fd"); - /* Do this after setting up signals since it might unblock SIGWINCH. */ - ret = lxc_terminal_setup(conf); - if (ret < 0) { - ERROR("Failed to create console"); - goto out_restore_sigmask; - } - TRACE("Created console"); - handler->cgroup_ops = cgroup_init(handler->conf); if (!handler->cgroup_ops) { ERROR("Failed to initialize cgroup driver"); - goto out_delete_terminal; + goto out_restore_sigmask; } TRACE("Initialized cgroup driver"); ret = lxc_read_seccomp_config(conf); - if (ret < 0) - return log_error(-1, "Failed loading seccomp policy"); + if (ret < 0) { + ERROR("Failed to read seccomp policy"); + goto out_restore_sigmask; + } TRACE("Read seccomp policy"); ret = handler->lsm_ops->prepare(handler->lsm_ops, conf, handler->lxcpath); if (ret < 0) { ERROR("Failed to initialize LSM"); - goto out_delete_terminal; + goto out_restore_sigmask; } TRACE("Initialized LSM"); @@ -890,9 +888,6 @@ int lxc_init(const char *name, struct lxc_handler *handler) handler->monitor_status_fd = move_fd(status_fd); return 0; -out_delete_terminal: - lxc_terminal_delete(&handler->conf->console); - out_restore_sigmask: (void)pthread_sigmask(SIG_SETMASK, &handler->oldmask, NULL); @@ -1930,6 +1925,12 @@ static int lxc_spawn(struct lxc_handler *handler) goto out_delete_net; } + ret = lxc_terminal_setup(conf); + if (ret < 0) { + SYSERROR("Failed to create console"); + goto out_delete_net; + } + /* * Tell the child to complete its initialization and wait for it to * exec or return an error. (The child will never return diff --git a/src/lxc/terminal.c b/src/lxc/terminal.c index 26470515b..1e1e2111b 100644 --- a/src/lxc/terminal.c +++ b/src/lxc/terminal.c @@ -917,6 +917,9 @@ int lxc_devpts_terminal(int devpts_fd, int *ret_ptx, int *ret_pty, int *ret_pty_ int pty_nr = -1; int ret; + if (devpts_fd < 0) + return ret_errno(EBADF); + fd_ptx = open_beneath(devpts_fd, "ptmx", O_RDWR | O_NOCTTY | O_CLOEXEC); if (fd_ptx < 0) { if (errno == ENOSPC) @@ -964,6 +967,41 @@ int lxc_devpts_terminal(int devpts_fd, int *ret_ptx, int *ret_pty, int *ret_pty_ return 0; } +int lxc_terminal_parent(struct lxc_conf *conf) +{ + __do_close int fd_devpts = -EBADF; + struct lxc_terminal *console = &conf->console; + int ret; + + if (!wants_console(&conf->console)) + return 0; + + /* Allocate console from the container's devpts. */ + if (conf->pty_max > 1) + return 0; + + /* Allocate console for the container from the host's devpts. */ + fd_devpts = open_at(-EBADF, + "/dev/pts", + PROTECT_OPATH_DIRECTORY, + PROTECT_LOOKUP_ABSOLUTE_XDEV, + 0); + if (fd_devpts < 0) + return syserror("Failed to open devpts instance"); + + ret = lxc_devpts_terminal(fd_devpts, &console->ptx, + &console->pty, &console->pty_nr); + if (ret < 0) + return syserror("Failed to allocate console"); + + ret = strnprintf(console->name, sizeof(console->name), + "/dev/pts/%d", console->pty_nr); + if (ret < 0) + return syserror("Failed to create console path"); + + return lxc_terminal_map_ids(conf, &conf->console); +} + static int lxc_terminal_create_native(const char *name, const char *lxcpath, struct lxc_terminal *terminal) { @@ -1010,9 +1048,9 @@ int lxc_terminal_setup(struct lxc_conf *conf) if (terminal->path && strequal(terminal->path, "none")) return log_info(0, "No terminal requested"); - ret = lxc_terminal_create_foreign(conf, terminal); + ret = lxc_terminal_peer_default(terminal); if (ret < 0) - return -1; + goto err; ret = lxc_terminal_create_log_file(terminal); if (ret < 0) diff --git a/src/lxc/terminal.h b/src/lxc/terminal.h index 9d3b61d32..05a01e550 100644 --- a/src/lxc/terminal.h +++ b/src/lxc/terminal.h @@ -255,5 +255,11 @@ __hidden extern void lxc_terminal_init(struct lxc_terminal *terminal); __hidden extern int lxc_terminal_signal_sigmask_safe_blocked(struct lxc_terminal *terminal); __hidden extern int lxc_devpts_terminal(int devpts_fd, int *ret_ptx, int *ret_pty, int *ret_pty_nr); +__hidden extern int lxc_terminal_parent(struct lxc_conf *conf); + +static inline bool wants_console(const struct lxc_terminal *terminal) +{ + return !terminal->path || !strequal(terminal->path, "none"); +} #endif /* __LXC_TERMINAL_H */ -- 2.47.2