-/*
- * lxc: linux Container library
- *
- * (C) Copyright IBM Corp. 2007, 2008
- *
- * Authors:
- * Daniel Lezcano <daniel.lezcano at free.fr>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
+/* SPDX-License-Identifier: LGPL-2.1+ */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#include "macro.h"
#include "mainloop.h"
#include "memory_utils.h"
+#include "mount_utils.h"
#include "namespace.h"
-#include "raw_syscalls.h"
+#include "process_utils.h"
#include "syscall_wrappers.h"
#include "terminal.h"
#include "utils.h"
{
__do_free char *line = NULL;
__do_fclose FILE *proc_file = NULL;
+ __do_free struct lxc_proc_context_info *info = NULL;
int ret;
bool found;
char proc_fn[LXC_PROC_STATUS_LEN];
- struct lxc_proc_context_info *info;
size_t line_bufsz = 0;
/* Read capabilities. */
if (ret < 0 || ret >= LXC_PROC_STATUS_LEN)
return NULL;
- proc_file = fopen(proc_fn, "r");
- if (!proc_file) {
- SYSERROR("Failed to open %s", proc_fn);
- return NULL;
- }
+ proc_file = fopen(proc_fn, "re");
+ if (!proc_file)
+ return log_error_errno(NULL, errno, "Failed to open %s", proc_fn);
info = calloc(1, sizeof(*info));
if (!info)
}
}
- if (!found) {
- ERROR("Could not read capability bounding set from %s", proc_fn);
- free(info);
- return NULL;
- }
+ if (!found)
+ return log_error_errno(NULL, ENOENT, "Failed to read capability bounding set from %s", proc_fn);
+
+ info->lsm_ops = lsm_init();
- info->lsm_label = lsm_process_label_get(pid);
+ info->lsm_label = info->lsm_ops->process_label_get(info->lsm_ops, pid);
info->ns_inherited = 0;
- memset(info->ns_fd, -1, sizeof(int) * LXC_NS_MAX);
+ for (int i = 0; i < LXC_NS_MAX; i++)
+ info->ns_fd[i] = -EBADF;
- return info;
+ return move_ptr(info);
}
static inline void lxc_proc_close_ns_fd(struct lxc_proc_context_info *ctx)
{
- for (int i = 0; i < LXC_NS_MAX; i++) {
- __do_close_prot_errno int fd ATTR_UNUSED = move_fd(ctx->ns_fd[i]);
- }
+ for (int i = 0; i < LXC_NS_MAX; i++)
+ close_prot_errno_disarm(ctx->ns_fd[i]);
}
static void lxc_proc_put_context_info(struct lxc_proc_context_info *ctx)
*/
static int in_same_namespace(pid_t pid1, pid_t pid2, const char *ns)
{
- __do_close_prot_errno int ns_fd1 = -1, ns_fd2 = -1;
+ __do_close int ns_fd1 = -EBADF, ns_fd2 = -EBADF;
int ret = -1;
struct stat ns_st1, ns_st2;
static int lxc_attach_to_ns(pid_t pid, struct lxc_proc_context_info *ctx)
{
- int i, ret;
+ for (int i = 0; i < LXC_NS_MAX; i++) {
+ int ret;
- for (i = 0; i < LXC_NS_MAX; i++) {
if (ctx->ns_fd[i] < 0)
continue;
ret = setns(ctx->ns_fd[i], ns_info[i].clone_flag);
- if (ret < 0) {
- SYSERROR("Failed to attach to %s namespace of %d",
- ns_info[i].proc_name, pid);
- return -1;
- }
+ if (ret < 0)
+ return log_error_errno(-1,
+ errno, "Failed to attach to %s namespace of %d",
+ ns_info[i].proc_name, pid);
DEBUG("Attached to %s namespace of %d", ns_info[i].proc_name, pid);
}
int ret;
ret = unshare(CLONE_NEWNS);
- if (ret < 0) {
- SYSERROR("Failed to unshare mount namespace");
- return -1;
- }
+ if (ret < 0)
+ return log_error_errno(-1, errno, "Failed to unshare mount namespace");
- if (detect_shared_rootfs()) {
- if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL)) {
- SYSERROR("Failed to make / rslave");
- ERROR("Continuing...");
- }
- }
+ if (detect_shared_rootfs() && mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL))
+ SYSERROR("Failed to recursively turn root mount tree into dependent mount. Continuing...");
/* Assume /proc is always mounted, so remount it. */
ret = umount2("/proc", MNT_DETACH);
- if (ret < 0) {
- SYSERROR("Failed to unmount /proc");
- return -1;
- }
+ if (ret < 0)
+ return log_error_errno(-1, errno, "Failed to unmount /proc");
- ret = mount("none", "/proc", "proc", 0, NULL);
- if (ret < 0) {
- SYSERROR("Failed to remount /proc");
- return -1;
- }
+ ret = mount_filesystem("proc", "/proc", 0);
+ if (ret < 0)
+ return log_error_errno(-1, errno, "Failed to remount /proc");
- /* Try to umount /sys. If it's not a mount point, we'll get EINVAL, then
+ /*
+ * Try to umount /sys. If it's not a mount point, we'll get EINVAL, then
* we ignore it because it may not have been mounted in the first place.
*/
ret = umount2("/sys", MNT_DETACH);
- if (ret < 0 && errno != EINVAL) {
- SYSERROR("Failed to unmount /sys");
- return -1;
- } else if (ret == 0) {
- /* Remount it. */
- ret = mount("none", "/sys", "sysfs", 0, NULL);
- if (ret < 0) {
- SYSERROR("Failed to remount /sys");
- return -1;
- }
- }
+ if (ret < 0 && errno != EINVAL)
+ return log_error_errno(-1, errno, "Failed to unmount /sys");
+
+ /* Remount it. */
+ if (ret == 0 && mount_filesystem("sysfs", "/sys", 0))
+ return log_error_errno(-1, errno, "Failed to remount /sys");
return 0;
}
static int lxc_attach_drop_privs(struct lxc_proc_context_info *ctx)
{
- int cap, last_cap;
+ int last_cap;
last_cap = lxc_caps_last_cap();
- for (cap = 0; cap <= last_cap; cap++) {
+ for (int cap = 0; cap <= last_cap; cap++) {
if (ctx->capability_mask & (1LL << cap))
continue;
if (prctl(PR_CAPBSET_DROP, prctl_arg(cap), prctl_arg(0),
- prctl_arg(0), prctl_arg(0))) {
- SYSERROR("Failed to drop capability %d", cap);
- return -1;
- }
+ prctl_arg(0), prctl_arg(0)))
+ return log_error_errno(-1, errno, "Failed to drop capability %d", cap);
TRACE("Dropped capability %d", cap);
}
free(extra_keep_store);
}
- ERROR("Failed to clear environment");
- return -1;
+ return log_error(-1, "Failed to clear environment");
}
if (extra_keep_store) {
}
ret = putenv("container=lxc");
- if (ret < 0) {
- SYSWARN("Failed to set environment variable");
- return -1;
- }
+ if (ret < 0)
+ return log_warn(-1, "Failed to set environment variable");
/* Set container environment variables.*/
if (init_ctx && init_ctx->container && init_ctx->container->lxc_conf) {
return -1;
ret = putenv(env_tmp);
- if (ret < 0) {
- SYSERROR("Failed to set environment variable: %s", (char *)iterator->elem);
- return -1;
- }
+ if (ret < 0)
+ return log_error_errno(-1, errno, "Failed to set environment variable: %s", (char *)iterator->elem);
}
}
static char *lxc_attach_getpwshell(uid_t uid)
{
- __do_free char *line = NULL;
+ __do_free char *line = NULL, *result = NULL;
__do_fclose FILE *pipe_f = NULL;
int fd, ret;
pid_t pid;
int pipes[2];
bool found = false;
size_t line_bufsz = 0;
- char *result = NULL;
/* We need to fork off a process that runs the getent program, and we
* need to capture its output, so we use a pipe for that purpose.
close(pipes[1]);
- pipe_f = fdopen(pipes[0], "r");
+ pipe_f = fdopen(pipes[0], "re");
+ if (!pipe_f) {
+ close(pipes[0]);
+ goto reap_child;
+ }
+ /* Transfer ownership of pipes[0] to pipe_f. */
+ move_fd(pipes[0]);
+
while (getline(&line, &line_bufsz, pipe_f) != -1) {
int i;
long value;
if (!token)
continue;
- free(result);
+ free_disarm(result);
result = strdup(token);
/* Sanity check that there are no fields after that. */
found = true;
}
+reap_child:
ret = wait_for_pid(pid);
- if (ret < 0) {
- free(result);
+ if (ret < 0)
return NULL;
- }
- if (!found) {
- free(result);
+ if (!found)
return NULL;
- }
- return result;
+ return move_ptr(result);
}
static void lxc_attach_get_init_uidgid(uid_t *init_uid, gid_t *init_gid)
int ret;
size_t line_bufsz = 0;
long value = -1;
- uid_t uid = (uid_t)-1;
- gid_t gid = (gid_t)-1;
+ uid_t uid = LXC_INVALID_UID;
+ gid_t gid = LXC_INVALID_GID;
ret = snprintf(proc_fn, LXC_PROC_STATUS_LEN, "/proc/%d/status", 1);
if (ret < 0 || ret >= LXC_PROC_STATUS_LEN)
return;
- proc_file = fopen(proc_fn, "r");
+ proc_file = fopen(proc_fn, "re");
if (!proc_file)
return;
gid = (gid_t)value;
}
- if (uid != (uid_t)-1 && gid != (gid_t)-1)
+ if (uid != LXC_INVALID_UID && gid != LXC_INVALID_GID)
break;
}
/* Only override arguments if we found something. */
- if (uid != (uid_t)-1)
+ if (uid != LXC_INVALID_UID)
*init_uid = uid;
- if (gid != (gid_t)-1)
+ if (gid != LXC_INVALID_GID)
*init_gid = gid;
/* TODO: we should also parse supplementary groups and use
if (!(options->namespaces & CLONE_NEWNS) ||
!(options->attach_flags & LXC_ATTACH_LSM)) {
- free(c->lxc_conf->seccomp.seccomp);
- c->lxc_conf->seccomp.seccomp = NULL;
+ free_disarm(c->lxc_conf->seccomp.seccomp);
return true;
}
INFO("Failed to retrieve lxc.seccomp.profile");
path = c->get_running_config_item(c, "lxc.seccomp");
- if (!path) {
- INFO("Failed to retrieve lxc.seccomp");
- return true;
- }
+ if (!path)
+ return log_info(true, "Failed to retrieve lxc.seccomp");
}
/* Copy the value into the new lxc_conf. */
/* Attempt to parse the resulting config. */
ret = lxc_read_seccomp_config(c->lxc_conf);
- if (ret < 0) {
- ERROR("Failed to retrieve seccomp policy");
- return false;
- }
+ if (ret < 0)
+ return log_error(false, "Failed to retrieve seccomp policy");
- INFO("Retrieved seccomp policy");
- return true;
+ return log_info(true, "Retrieved seccomp policy");
}
static bool no_new_privs(struct lxc_container *c, lxc_attach_options_t *options)
__do_free char *val = NULL;
/* Remove current setting. */
- if (!c->set_config_item(c, "lxc.no_new_privs", "")) {
- INFO("Failed to unset lxc.no_new_privs");
- return false;
- }
+ if (!c->set_config_item(c, "lxc.no_new_privs", ""))
+ return log_info(false, "Failed to unset lxc.no_new_privs");
/* Retrieve currently active setting. */
val = c->get_running_config_item(c, "lxc.no_new_privs");
- if (!val) {
- INFO("Failed to retrieve lxc.no_new_privs");
- return false;
- }
+ if (!val)
+ return log_info(false, "Failed to retrieve lxc.no_new_privs");
/* Set currently active setting. */
return c->set_config_item(c, "lxc.no_new_privs", val);
struct attach_clone_payload {
int ipc_socket;
- int terminal_slave_fd;
+ int terminal_pts_fd;
lxc_attach_options_t *options;
struct lxc_proc_context_info *init_ctx;
lxc_attach_exec_t exec_function;
static void lxc_put_attach_clone_payload(struct attach_clone_payload *p)
{
- __do_close_prot_errno int ipc_socket ATTR_UNUSED = p->ipc_socket;
- __do_close_prot_errno int terminal_slave_fd ATTR_UNUSED = p->terminal_slave_fd;
-
+ close_prot_errno_disarm(p->ipc_socket);
+ close_prot_errno_disarm(p->terminal_pts_fd);
if (p->init_ctx) {
lxc_proc_put_context_info(p->init_ctx);
p->init_ctx = NULL;
bool needs_lsm = (options->namespaces & CLONE_NEWNS) &&
(options->attach_flags & LXC_ATTACH_LSM) &&
init_ctx->lsm_label;
+ char *lsm_label = NULL;
/* A description of the purpose of this functionality is provided in the
* lxc-attach(1) manual page. We have to remount here and not in the
goto on_error;
}
+ if (!lxc_setgroups(0, NULL) && errno != EPERM)
+ goto on_error;
+
if (options->namespaces & CLONE_NEWUSER) {
/* Check whether nsuid 0 has a mapping. */
ns_root_uid = get_ns_uid(0);
goto on_error;
}
- if (!lxc_setgroups(0, NULL) && errno != EPERM)
- goto on_error;
-
/* Set {u,g}id. */
if (options->uid != LXC_INVALID_UID)
new_uid = options->uid;
else
new_gid = ns_root_gid;
- if ((init_ctx->container && init_ctx->container->lxc_conf &&
- init_ctx->container->lxc_conf->no_new_privs) ||
- (options->attach_flags & LXC_ATTACH_NO_NEW_PRIVS)) {
- ret = prctl(PR_SET_NO_NEW_PRIVS, prctl_arg(1), prctl_arg(0),
- prctl_arg(0), prctl_arg(0));
- if (ret < 0)
- goto on_error;
-
- TRACE("Set PR_SET_NO_NEW_PRIVS");
- }
-
if (needs_lsm) {
bool on_exec;
/* Change into our new LSM profile. */
on_exec = options->attach_flags & LXC_ATTACH_LSM_EXEC ? true : false;
-
- ret = lsm_process_label_set_at(lsm_fd, init_ctx->lsm_label, on_exec);
+ lsm_label = options->lsm_label ? options->lsm_label : init_ctx->lsm_label;
+ ret = init_ctx->lsm_ops->process_label_set_at(init_ctx->lsm_ops, lsm_fd,
+ lsm_label, on_exec);
close(lsm_fd);
if (ret < 0)
goto on_error;
- TRACE("Set %s LSM label to \"%s\"", lsm_name(), init_ctx->lsm_label);
+ TRACE("Set %s LSM label to \"%s\"", init_ctx->lsm_ops->name, init_ctx->lsm_label);
+ }
+
+ if ((init_ctx->container && init_ctx->container->lxc_conf &&
+ init_ctx->container->lxc_conf->no_new_privs) ||
+ (options->attach_flags & LXC_ATTACH_NO_NEW_PRIVS)) {
+ ret = prctl(PR_SET_NO_NEW_PRIVS, prctl_arg(1), prctl_arg(0),
+ prctl_arg(0), prctl_arg(0));
+ if (ret < 0)
+ goto on_error;
+
+ TRACE("Set PR_SET_NO_NEW_PRIVS");
}
if (init_ctx->container && init_ctx->container->lxc_conf &&
}
if (options->attach_flags & LXC_ATTACH_TERMINAL) {
- ret = lxc_terminal_prepare_login(payload->terminal_slave_fd);
+ ret = lxc_terminal_prepare_login(payload->terminal_pts_fd);
if (ret < 0) {
- SYSERROR("Failed to prepare terminal file descriptor %d", payload->terminal_slave_fd);
+ SYSERROR("Failed to prepare terminal file descriptor %d", payload->terminal_pts_fd);
goto on_error;
}
- TRACE("Prepared terminal file descriptor %d", payload->terminal_slave_fd);
+ TRACE("Prepared terminal file descriptor %d", payload->terminal_pts_fd);
}
/* Avoid unnecessary syscalls. */
if (new_gid == ns_root_gid)
new_gid = LXC_INVALID_GID;
+ /* Make sure that the processes STDIO is correctly owned by the user that we are switching to */
+ ret = fix_stdio_permissions(new_uid);
+ if (ret)
+ WARN("Failed to adjust stdio permissions");
+
if (!lxc_switch_uid_gid(new_uid, new_gid))
goto on_error;
_exit(EXIT_FAILURE);
}
-static int lxc_attach_terminal(struct lxc_conf *conf,
+static int lxc_attach_terminal(const char *name, const char *lxcpath, struct lxc_conf *conf,
struct lxc_terminal *terminal)
{
int ret;
lxc_terminal_init(terminal);
- ret = lxc_terminal_create(terminal);
- if (ret < 0) {
- ERROR("Failed to create terminal");
- return -1;
- }
-
- /* Shift ttys to container. */
- ret = lxc_terminal_map_ids(conf, terminal);
- if (ret < 0) {
- ERROR("Failed to chown terminal");
- goto on_error;
- }
+ ret = lxc_terminal_create(name, lxcpath, conf, terminal);
+ if (ret < 0)
+ return log_error(-1, "Failed to create terminal");
return 0;
-
-on_error:
- lxc_terminal_delete(terminal);
- lxc_terminal_conf_free(terminal);
- return -1;
}
static int lxc_attach_terminal_mainloop_init(struct lxc_terminal *terminal,
int ret;
ret = lxc_mainloop_open(descr);
- if (ret < 0) {
- ERROR("Failed to create mainloop");
- return -1;
- }
+ if (ret < 0)
+ return log_error(-1, "Failed to create mainloop");
ret = lxc_terminal_mainloop_add(descr, terminal);
if (ret < 0) {
- ERROR("Failed to add handlers to mainloop");
lxc_mainloop_close(descr);
- return -1;
+ return log_error(-1, "Failed to add handlers to mainloop");
}
return 0;
}
-static inline void lxc_attach_terminal_close_master(struct lxc_terminal *terminal)
+static inline void lxc_attach_terminal_close_ptx(struct lxc_terminal *terminal)
{
- close_prot_errno_disarm(terminal->master);
+ close_prot_errno_disarm(terminal->ptx);
}
-static inline void lxc_attach_terminal_close_slave(struct lxc_terminal *terminal)
+static inline void lxc_attach_terminal_close_pts(struct lxc_terminal *terminal)
{
- close_prot_errno_disarm(terminal->slave);
+ close_prot_errno_disarm(terminal->pty);
}
static inline void lxc_attach_terminal_close_peer(struct lxc_terminal *terminal)
struct attach_clone_payload payload = {0};
ret = access("/proc/self/ns", X_OK);
- if (ret) {
- SYSERROR("Does this kernel version support namespaces?");
- return -1;
- }
+ if (ret)
+ return log_error_errno(-1, errno, "Does this kernel version support namespaces?");
if (!container)
- return minus_one_set_errno(EINVAL);
+ return ret_set_errno(-1, EINVAL);
if (!lxc_container_get(container))
- return minus_one_set_errno(EINVAL);
+ return ret_set_errno(-1, EINVAL);
name = container->name;
lxcpath = container->config_path;
init_pid = lxc_cmd_get_init_pid(name, lxcpath);
if (init_pid < 0) {
- ERROR("Failed to get init pid");
lxc_container_put(container);
- return -1;
+ return log_error(-1, "Failed to get init pid");
}
init_ctx = lxc_proc_get_context_info(init_pid);
}
}
conf = init_ctx->container->lxc_conf;
+ if (!conf)
+ return log_error_errno(-EINVAL, EINVAL, "Missing container confifg");
if (!fetch_seccomp(init_ctx->container, options))
WARN("Failed to get seccomp policy");
}
if (options->attach_flags & LXC_ATTACH_TERMINAL) {
- ret = lxc_attach_terminal(conf, &terminal);
+ ret = lxc_attach_terminal(name, lxcpath, conf, &terminal);
if (ret < 0) {
ERROR("Failed to setup new terminal");
free(cwd);
free(cwd);
lxc_proc_close_ns_fd(init_ctx);
if (options->attach_flags & LXC_ATTACH_TERMINAL)
- lxc_attach_terminal_close_slave(&terminal);
+ lxc_attach_terminal_close_pts(&terminal);
/* Attach to cgroup, if requested. */
if (options->attach_flags & LXC_ATTACH_MOVE_TO_CGROUP) {
- struct cgroup_ops *cgroup_ops;
-
- cgroup_ops = cgroup_init(conf);
- if (!cgroup_ops)
- goto on_error;
+ /*
+ * If this is the unified hierarchy cgroup_attach() is
+ * enough.
+ */
+ ret = cgroup_attach(conf, name, lxcpath, pid);
+ if (ret) {
+ call_cleaner(cgroup_exit) struct cgroup_ops *cgroup_ops = NULL;
- if (!cgroup_ops->attach(cgroup_ops, name, lxcpath, pid))
- goto on_error;
+ cgroup_ops = cgroup_init(conf);
+ if (!cgroup_ops)
+ goto on_error;
- cgroup_exit(cgroup_ops);
+ if (!cgroup_ops->attach(cgroup_ops, conf, name, lxcpath, pid))
+ goto on_error;
+ }
TRACE("Moved intermediate process %d into container's cgroups", pid);
}
ret = -1;
on_exec = options->attach_flags & LXC_ATTACH_LSM_EXEC ? true : false;
- labelfd = lsm_process_label_fd_get(attached_pid, on_exec);
+ labelfd = init_ctx->lsm_ops->process_label_fd_get(init_ctx->lsm_ops,
+ attached_pid, on_exec);
if (labelfd < 0)
goto close_mainloop;
TRACE("Sent LSM label file descriptor %d to child", labelfd);
}
- if (conf && conf->seccomp.seccomp) {
+ if (conf->seccomp.seccomp) {
ret = lxc_seccomp_recv_notifier_fd(&conf->seccomp, ipc_sockets[0]);
if (ret < 0)
goto close_mainloop;
}
/* close unneeded file descriptors */
- close(ipc_sockets[0]);
- ipc_sockets[0] = -EBADF;
+ close_prot_errno_disarm(ipc_sockets[0]);
if (options->attach_flags & LXC_ATTACH_TERMINAL) {
- lxc_attach_terminal_close_master(&terminal);
+ lxc_attach_terminal_close_ptx(&terminal);
lxc_attach_terminal_close_peer(&terminal);
lxc_attach_terminal_close_log(&terminal);
}
payload.ipc_socket = ipc_sockets[1];
payload.options = options;
payload.init_ctx = init_ctx;
- payload.terminal_slave_fd = terminal.slave;
+ payload.terminal_pts_fd = terminal.pty;
payload.exec_function = exec_function;
payload.exec_payload = exec_payload;
if (pid == 0) {
if (options->attach_flags & LXC_ATTACH_TERMINAL) {
- ret = pthread_sigmask(SIG_SETMASK,
- &terminal.tty_state->oldmask, NULL);
+ ret = lxc_terminal_signal_sigmask_safe_blocked(&terminal);
if (ret < 0) {
SYSERROR("Failed to reset signal mask");
_exit(EXIT_FAILURE);
}
if (options->attach_flags & LXC_ATTACH_TERMINAL)
- lxc_attach_terminal_close_slave(&terminal);
+ lxc_attach_terminal_close_pts(&terminal);
/* Tell grandparent the pid of the pid of the newly created child. */
ret = lxc_write_nointr(ipc_sockets[1], &pid, sizeof(pid));
}
}
- SYSERROR("Failed to exec \"%s\"", cmd->program);
- return ret;
+ return log_error_errno(ret, errno, "Failed to exec \"%s\"", cmd->program);
}
int lxc_attach_run_shell(void* payload)