diff --git a/doc/lxc-attach.sgml.in b/doc/lxc-attach.sgml.in index 713a30e7f..14fa77d79 100644 --- a/doc/lxc-attach.sgml.in +++ b/doc/lxc-attach.sgml.in @@ -60,6 +60,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA --clear-env -v, --set-var variable --keep-var variable + -u, --uid uid + -g, --gid gid -- command @@ -282,6 +284,30 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + + + + + + Executes the command with user ID + uid inside the container. + + + + + + + + + + + Executes the command with group ID + gid inside the container. + + + + diff --git a/doc/lxc-execute.sgml.in b/doc/lxc-execute.sgml.in index 20814348d..8b249b329 100644 --- a/doc/lxc-execute.sgml.in +++ b/doc/lxc-execute.sgml.in @@ -53,6 +53,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -d -f config_file -s KEY=VAL + -u, --uid uid + -g, --gid gid -- command @@ -139,6 +141,30 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + + + + + + Executes the command with user ID + uid inside the container. + + + + + + + + + + + Executes the command with group ID + gid inside the container. + + + + diff --git a/src/lxc/attach.c b/src/lxc/attach.c index 87f14398f..425f257e4 100644 --- a/src/lxc/attach.c +++ b/src/lxc/attach.c @@ -749,6 +749,8 @@ static int attach_child_main(struct attach_clone_payload *payload) int fd, lsm_fd, ret; uid_t new_uid; gid_t new_gid; + uid_t ns_root_uid = 0; + gid_t ns_root_gid = 0; lxc_attach_options_t* options = payload->options; struct lxc_proc_context_info* init_ctx = payload->init_ctx; bool needs_lsm = (options->namespaces & CLONE_NEWNS) && @@ -836,33 +838,40 @@ static int attach_child_main(struct attach_clone_payload *payload) goto on_error; } - /* Set {u,g}id. */ - new_uid = 0; - new_gid = 0; + if (options->namespaces & CLONE_NEWUSER) { + /* Check whether nsuid 0 has a mapping. */ + ns_root_uid = get_ns_uid(0); - /* Ignore errors, we will fall back to root in that case (/proc was not - * mounted etc.). - */ - if (options->namespaces & CLONE_NEWUSER) - lxc_attach_get_init_uidgid(&new_uid, &new_gid); + /* Check whether nsgid 0 has a mapping. */ + ns_root_gid = get_ns_gid(0); - if (options->uid != (uid_t)-1) - new_uid = options->uid; + /* If there's no mapping for nsuid 0 try to retrieve the nsuid + * init was started with. + */ + if (ns_root_uid == LXC_INVALID_UID) + lxc_attach_get_init_uidgid(&ns_root_uid, &ns_root_gid); - if (options->gid != (gid_t)-1) - new_gid = options->gid; + if (ns_root_uid == LXC_INVALID_UID) + goto on_error; - /* Try to set the {u,g}id combination. */ - if (new_uid != 0 || new_gid != 0 || options->namespaces & CLONE_NEWUSER) { - ret = lxc_switch_uid_gid(new_uid, new_gid); - if (ret < 0) + if (!lxc_switch_uid_gid(ns_root_uid, ns_root_gid)) goto on_error; } - ret = lxc_setgroups(0, NULL); - if (ret < 0 && errno != EPERM) + 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_uid = ns_root_uid; + + if (options->gid != LXC_INVALID_GID) + new_gid = options->gid; + 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)) { @@ -952,6 +961,16 @@ static int attach_child_main(struct attach_clone_payload *payload) TRACE("Prepared terminal file descriptor %d", payload->terminal_slave_fd); } + /* Avoid unnecessary syscalls. */ + if (new_uid == ns_root_uid) + new_uid = LXC_INVALID_UID; + + if (new_gid == ns_root_gid) + new_gid = LXC_INVALID_GID; + + if (!lxc_switch_uid_gid(new_uid, new_gid)) + goto on_error; + /* We're done, so we can now do whatever the user intended us to do. */ _exit(payload->exec_function(payload->exec_payload)); diff --git a/src/lxc/cgroups/cgfsng.c b/src/lxc/cgroups/cgfsng.c index 1fe561498..c37456b92 100644 --- a/src/lxc/cgroups/cgfsng.c +++ b/src/lxc/cgroups/cgfsng.c @@ -1388,6 +1388,8 @@ static int chown_cgroup_wrapper(void *data) } destuid = get_ns_uid(arg->origuid); + if (destuid == LXC_INVALID_UID) + destuid = 0; for (i = 0; arg->hierarchies[i]; i++) { char *fullpath; diff --git a/src/lxc/cmd/lxc_usernsexec.c b/src/lxc/cmd/lxc_usernsexec.c index bdfef0fb2..e5b5d1f01 100644 --- a/src/lxc/cmd/lxc_usernsexec.c +++ b/src/lxc/cmd/lxc_usernsexec.c @@ -104,12 +104,10 @@ static int do_child(void *vargv) char **argv = (char **)vargv; /* Assume we want to become root */ - ret = lxc_switch_uid_gid(0, 0); - if (ret < 0) + if (!lxc_switch_uid_gid(0, 0)) return -1; - ret = lxc_setgroups(0, NULL); - if (ret < 0) + if (!lxc_setgroups(0, NULL)) return -1; ret = unshare(CLONE_NEWNS); diff --git a/src/lxc/macro.h b/src/lxc/macro.h index d44e2f9b1..c0a50371d 100644 --- a/src/lxc/macro.h +++ b/src/lxc/macro.h @@ -340,4 +340,7 @@ extern int __build_bug_on_failed; #define PTR_TO_INTMAX(p) ((intmax_t)((intptr_t)(p))) #define INTMAX_TO_PTR(u) ((void *)((intptr_t)(u))) +#define LXC_INVALID_UID ((uid_t)-1) +#define LXC_INVALID_GID ((gid_t)-1) + #endif /* __LXC_MACRO_H */ diff --git a/src/lxc/start.c b/src/lxc/start.c index 66a801eaf..8d3a7ced5 100644 --- a/src/lxc/start.c +++ b/src/lxc/start.c @@ -1046,10 +1046,11 @@ static int do_start(void *data) { int ret; char path[PATH_MAX]; - bool have_cap_setgid; uid_t new_uid; gid_t new_gid; struct lxc_list *iterator; + uid_t nsuid = 0; + gid_t nsgid = 0; int devnull_fd = -1; struct lxc_handler *handler = data; @@ -1117,22 +1118,20 @@ static int do_start(void *data) * privilege over our namespace. */ if (!lxc_list_empty(&handler->conf->id_map)) { - uid_t nsuid = (handler->conf->root_nsuid_map != NULL) - ? 0 - : handler->conf->init_uid; - gid_t nsgid = (handler->conf->root_nsgid_map != NULL) - ? 0 - : handler->conf->init_gid; + if (!handler->conf->root_nsuid_map) + nsuid = handler->conf->init_uid; - ret = lxc_switch_uid_gid(nsuid, nsgid); - if (ret < 0) + if (!handler->conf->root_nsgid_map) + nsgid = handler->conf->init_gid; + + if (!lxc_switch_uid_gid(nsuid, nsgid)) goto out_warn_father; /* Drop groups only after we switched to a valid gid in the new * user namespace. */ - ret = lxc_setgroups(0, NULL); - if (ret < 0 && (handler->am_root || errno != EPERM)) + if (!lxc_setgroups(0, NULL) && + (handler->am_root || errno != EPERM)) goto out_warn_father; ret = prctl(PR_SET_DUMPABLE, prctl_arg(1), prctl_arg(0), @@ -1355,25 +1354,27 @@ static int do_start(void *data) new_uid = handler->conf->init_uid; new_gid = handler->conf->init_gid; - /* If we are in a new user namespace we already dropped all groups when - * we switched to root in the new user namespace further above. Only - * drop groups if we can, so ensure that we have necessary privilege. - */ - #if HAVE_LIBCAP - have_cap_setgid = lxc_proc_cap_is_set(CAP_SETGID, CAP_EFFECTIVE); - #else - have_cap_setgid = false; - #endif - if (lxc_list_empty(&handler->conf->id_map) && have_cap_setgid) { - ret = lxc_setgroups(0, NULL); - if (ret < 0) - goto out_warn_father; - } + /* Avoid unnecessary syscalls. */ + if (new_uid == nsuid) + new_uid = LXC_INVALID_UID; - ret = lxc_switch_uid_gid(new_uid, new_gid); - if (ret < 0) + if (new_gid == nsgid) + new_gid = LXC_INVALID_GID; + + if (!lxc_switch_uid_gid(new_uid, new_gid)) goto out_warn_father; + /* If we are in a new user namespace we already dropped all groups when + * we switched to root in the new user namespace further above. Only + * drop groups if we can, so ensure that we have necessary privilege. + */ + if (lxc_list_empty(&handler->conf->id_map)) + #if HAVE_LIBCAP + if (lxc_proc_cap_is_set(CAP_SETGID, CAP_EFFECTIVE)) + #endif + if (!lxc_setgroups(0, NULL)) + goto out_warn_father; + ret = lxc_ambient_caps_down(); if (ret < 0) { ERROR("Failed to clear ambient capabilities"); diff --git a/src/lxc/storage/rsync.c b/src/lxc/storage/rsync.c index 83871ae80..ca2da186e 100644 --- a/src/lxc/storage/rsync.c +++ b/src/lxc/storage/rsync.c @@ -50,12 +50,10 @@ int lxc_rsync_exec_wrapper(void *data) int ret; struct rsync_data_char *args = data; - ret = lxc_switch_uid_gid(0, 0); - if (ret < 0) + if (!lxc_switch_uid_gid(0, 0)) return -1; - ret = lxc_setgroups(0, NULL); - if (ret < 0) + if (!lxc_setgroups(0, NULL)) return -1; return lxc_rsync_exec(args->src, args->dest); @@ -117,12 +115,10 @@ int lxc_rsync(struct rsync_data *data) return -1; } - ret = lxc_switch_uid_gid(0, 0); - if (ret < 0) + if (!lxc_switch_uid_gid(0, 0)) return -1; - ret = lxc_setgroups(0, NULL); - if (ret < 0) + if (!lxc_setgroups(0, NULL)) return -1; src = lxc_storage_get_path(orig->dest, orig->type); diff --git a/src/lxc/tools/lxc_attach.c b/src/lxc/tools/lxc_attach.c index e98f0a056..551f9f396 100644 --- a/src/lxc/tools/lxc_attach.c +++ b/src/lxc/tools/lxc_attach.c @@ -72,6 +72,8 @@ static const struct option my_longopts[] = { {"set-var", required_argument, 0, 'v'}, {"pty-log", required_argument, 0, 'L'}, {"rcfile", required_argument, 0, 'f'}, + {"uid", required_argument, 0, 'u'}, + {"gid", required_argument, 0, 'g'}, LXC_COMMON_OPTIONS }; @@ -122,6 +124,8 @@ Options :\n\ multiple times.\n\ -f, --rcfile=FILE\n\ Load configuration file FILE\n\ + -u, --uid=UID Execute COMMAND with UID inside the container\n\ + -g, --gid=GID Execute COMMAND with GID inside the container\n\ ", .options = my_longopts, .parser = my_parser, @@ -187,6 +191,14 @@ static int my_parser(struct lxc_arguments *args, int c, char *arg) case 'f': args->rcfile = arg; break; + case 'u': + if (lxc_safe_uint(arg, &args->uid) < 0) + return -1; + break; + case 'g': + if (lxc_safe_uint(arg, &args->gid) < 0) + return -1; + break; } return 0; @@ -333,6 +345,12 @@ int main(int argc, char *argv[]) goto out; } + if (my_args.uid) + attach_options.uid = my_args.uid; + + if (my_args.gid) + attach_options.gid = my_args.gid; + if (command.program) ret = c->attach(c, lxc_attach_run_command, &command, &attach_options, &pid); else diff --git a/src/lxc/utils.c b/src/lxc/utils.c index 9795b51b6..9b6f0a617 100644 --- a/src/lxc/utils.c +++ b/src/lxc/utils.c @@ -544,7 +544,34 @@ uid_t get_ns_uid(uid_t orig) } } - nsid = 0; + nsid = LXC_INVALID_UID; + +found: + fclose(f); + free(line); + return nsid; +} + +gid_t get_ns_gid(gid_t orig) +{ + char *line = NULL; + size_t sz = 0; + gid_t nsid, hostid, range; + FILE *f = fopen("/proc/self/gid_map", "r"); + if (!f) + return 0; + + while (getline(&line, &sz, f) != -1) { + if (sscanf(line, "%u %u %u", &nsid, &hostid, &range) != 3) + continue; + + if (hostid <= orig && hostid + range > orig) { + nsid += orig - hostid; + goto found; + } + } + + nsid = LXC_INVALID_GID; found: fclose(f); @@ -1324,33 +1351,41 @@ int lxc_preserve_ns(const int pid, const char *ns) return open(path, O_RDONLY | O_CLOEXEC); } -int lxc_switch_uid_gid(uid_t uid, gid_t gid) +bool lxc_switch_uid_gid(uid_t uid, gid_t gid) { - if (setgid(gid) < 0) { - SYSERROR("Failed to switch to gid %d.", gid); - return -errno; - } - NOTICE("Switched to gid %d.", gid); + int ret = 0; - if (setuid(uid) < 0) { - SYSERROR("Failed to switch to uid %d.", uid); - return -errno; + if (gid != LXC_INVALID_GID) { + ret = setgid(gid); + if (ret < 0) { + SYSERROR("Failed to switch to gid %d", gid); + return false; + } + NOTICE("Switched to gid %d", gid); } - NOTICE("Switched to uid %d.", uid); - return 0; + if (uid != LXC_INVALID_UID) { + ret = setuid(uid); + if (ret < 0) { + SYSERROR("Failed to switch to uid %d", uid); + return false; + } + NOTICE("Switched to uid %d", uid); + } + + return true; } /* Simple covenience function which enables uniform logging. */ -int lxc_setgroups(int size, gid_t list[]) +bool lxc_setgroups(int size, gid_t list[]) { if (setgroups(size, list) < 0) { - SYSERROR("Failed to setgroups()."); - return -errno; + SYSERROR("Failed to setgroups()"); + return false; } - NOTICE("Dropped additional groups."); + NOTICE("Dropped additional groups"); - return 0; + return true; } static int lxc_get_unused_loop_dev_legacy(char *loop_name) diff --git a/src/lxc/utils.h b/src/lxc/utils.h index f2d802991..0f121e673 100644 --- a/src/lxc/utils.h +++ b/src/lxc/utils.h @@ -328,6 +328,10 @@ inline static bool am_host_unpriv(void) * parse /proc/self/uid_map to find what @orig maps to */ extern uid_t get_ns_uid(uid_t orig); +/* + * parse /proc/self/gid_map to find what @orig maps to + */ +extern gid_t get_ns_gid(gid_t orig); extern bool dir_exists(const char *path); @@ -354,9 +358,11 @@ extern int lxc_preserve_ns(const int pid, const char *ns); /* Check whether a signal is blocked by a process. */ extern bool task_blocks_signal(pid_t pid, int signal); -/* Switch to a new uid and gid. */ -extern int lxc_switch_uid_gid(uid_t uid, gid_t gid); -extern int lxc_setgroups(int size, gid_t list[]); +/* Switch to a new uid and gid. + * If LXC_INVALID_{G,U}ID is passed then the set{g,u}id() will not be called. + */ +extern bool lxc_switch_uid_gid(uid_t uid, gid_t gid); +extern bool lxc_setgroups(int size, gid_t list[]); /* Find an unused loop device and associate it with source. */ extern int lxc_prepare_loop_dev(const char *source, char *loop_dev, int flags);