Commit 0e6e3a41 by Stéphane Graber

Fix unprivileged containers started by root

This change makes it possible to create unprivileged containers as root. They will be stored in the usual system wide location, use the usual system wide cache but will be running using a uid/gid map. This also updates lxc_usernsexec to use the same function as the rest of LXC, centralizing all the userns switch in a single function. That function now detects the presence of newuidmap and newgidmap on the system, if they are present, they will be used for containers created as either user or root. If they're not and the user isn't root, an error is shown. If they're not and the user is root, LXC will directly set the uid_map and gid_map values. All that should allow for a consistent experience as well as supporting distributions that don't yet ship newuidmap/newgidmap. To make things simpler in the future, an helper function "on_path" is also introduced and used to detect the presence of newuidmap and newgidmap. Signed-off-by: 's avatarStéphane Graber <stgraber@ubuntu.com> Acked-by: 's avatarSerge E. Hallyn <serge.hallyn@ubuntu.com>
parent 99b71824
......@@ -3164,7 +3164,12 @@ int lxc_map_ids(struct lxc_list *idmap, pid_t pid)
int ret = 0;
enum idtype type;
char *buf = NULL, *pos;
int am_root = (getuid() == 0);
int use_shadow = (on_path("newuidmap") && on_path("newuidmap"));
if (!use_shadow && geteuid()) {
ERROR("Missing newuidmap/newgidmap");
return -1;
}
for(type = ID_TYPE_UID; type <= ID_TYPE_GID; type++) {
int left, fill;
......@@ -3175,7 +3180,7 @@ int lxc_map_ids(struct lxc_list *idmap, pid_t pid)
return -ENOMEM;
}
pos = buf;
if (!am_root)
if (use_shadow)
pos += sprintf(buf, "new%cidmap %d",
type == ID_TYPE_UID ? 'u' : 'g',
pid);
......@@ -3189,9 +3194,9 @@ int lxc_map_ids(struct lxc_list *idmap, pid_t pid)
had_entry = 1;
left = 4096 - (pos - buf);
fill = snprintf(pos, left, "%s%lu %lu %lu%s",
am_root ? "" : " ",
use_shadow ? " " : "",
map->nsid, map->hostid, map->range,
am_root ? "\n" : "");
use_shadow ? "" : "\n");
if (fill <= 0 || fill >= left)
SYSERROR("snprintf failed, too many mappings");
pos += fill;
......@@ -3199,7 +3204,7 @@ int lxc_map_ids(struct lxc_list *idmap, pid_t pid)
if (!had_entry)
continue;
if (am_root) {
if (!use_shadow) {
ret = write_id_mapping(type, pid, buf, pos-buf);
} else {
left = 4096 - (pos - buf);
......
......@@ -41,6 +41,7 @@
#include <pwd.h>
#include <grp.h>
#include "conf.h"
#include "namespace.h"
#include "utils.h"
......@@ -131,19 +132,7 @@ static int do_child(void *vargv)
return -1;
}
struct id_map {
char which; // b or u or g
long host_id, ns_id, range;
struct id_map *next;
};
static struct id_map default_map = {
.which = 'b',
.host_id = 100000,
.ns_id = 0,
.range = 10000,
};
static struct id_map *active_map = &default_map;
static struct lxc_list active_map;
/*
* given a string like "b:0:100000:10", map both uids and gids
......@@ -152,28 +141,51 @@ static struct id_map *active_map = &default_map;
static int parse_map(char *map)
{
struct id_map *newmap;
int ret;
struct lxc_list *tmp = NULL;
int ret;
int i;
char types[2] = {'u', 'g'};
char which;
long host_id, ns_id, range;
if (!map)
return -1;
newmap = malloc(sizeof(*newmap));
if (!newmap)
return -1;
ret = sscanf(map, "%c:%ld:%ld:%ld", &newmap->which, &newmap->ns_id, &newmap->host_id, &newmap->range);
ret = sscanf(map, "%c:%ld:%ld:%ld", &which, &ns_id, &host_id, &range);
if (ret != 4)
goto out_free_map;
if (newmap->which != 'b' && newmap->which != 'u' && newmap->which != 'g')
goto out_free_map;
if (active_map != &default_map)
newmap->next = active_map;
else
newmap->next = NULL;
active_map = newmap;
return 0;
return -1;
out_free_map:
free(newmap);
return -1;
if (which != 'b' && which != 'u' && which != 'g')
return -1;
for (i = 0; i < 2; i++) {
if (which != types[i] && which != 'b')
continue;
newmap = malloc(sizeof(*newmap));
if (!newmap)
return -1;
newmap->hostid = host_id;
newmap->nsid = ns_id;
newmap->range = range;
if (types[i] == 'u')
newmap->idtype = ID_TYPE_UID;
else
newmap->idtype = ID_TYPE_GID;
tmp = malloc(sizeof(*tmp));
if (!tmp) {
free(newmap);
return -1;
}
tmp->elem = newmap;
lxc_list_add_tail(&active_map, tmp);
}
return 0;
}
/*
......@@ -185,12 +197,13 @@ out_free_map:
* gid, because otherwise we're not sure which entries the user
* wanted.
*/
static int read_default_map(char *fnam, char which, char *username)
static int read_default_map(char *fnam, int which, char *username)
{
FILE *fin;
char *line = NULL;
size_t sz = 0;
struct id_map *newmap;
struct lxc_list *tmp = NULL;
char *p1, *p2;
fin = fopen(fnam, "r");
......@@ -213,15 +226,21 @@ static int read_default_map(char *fnam, char which, char *username)
free(line);
return -1;
}
newmap->host_id = atol(p1+1);
newmap->hostid = atol(p1+1);
newmap->range = atol(p2+1);
newmap->ns_id = 0;
newmap->which = which;
if (active_map != &default_map)
newmap->next = active_map;
else
newmap->next = NULL;
active_map = newmap;
newmap->nsid = 0;
newmap->idtype = which;
tmp = malloc(sizeof(*tmp));
if (!tmp) {
fclose(fin);
free(line);
free(newmap);
return -1;
}
tmp->elem = newmap;
lxc_list_add_tail(&active_map, tmp);
break;
}
......@@ -238,119 +257,13 @@ static int find_default_map(void)
struct passwd *p = getpwuid(getuid());
if (!p)
return -1;
if (read_default_map(subuidfile, 'u', p->pw_name) < 0)
if (read_default_map(subuidfile, ID_TYPE_UID, p->pw_name) < 0)
return -1;
if (read_default_map(subgidfile, 'g', p->pw_name) < 0)
if (read_default_map(subgidfile, ID_TYPE_GID, p->pw_name) < 0)
return -1;
return 0;
}
static int run_cmd(char **argv)
{
int status;
pid_t pid = fork();
if (pid < 0)
return pid;
if (pid == 0) {
execvp(argv[0], argv);
perror("exec failed");
exit(1);
}
if (waitpid(pid, &status, __WALL) < 0) {
perror("waitpid");
return -1;
}
return WEXITSTATUS(status);
}
static int map_child_uids(int pid, struct id_map *map)
{
char **uidargs = NULL, **gidargs = NULL;
char **newuidargs = NULL, **newgidargs = NULL;
int i, nuargs = 2, ngargs = 2, ret = -1;
struct id_map *m;
uidargs = malloc(3 * sizeof(*uidargs));
if (uidargs == NULL)
return -1;
gidargs = malloc(3 * sizeof(*gidargs));
if (gidargs == NULL) {
free(uidargs);
return -1;
}
uidargs[0] = malloc(10);
gidargs[0] = malloc(10);
uidargs[1] = malloc(21);
gidargs[1] = malloc(21);
uidargs[2] = NULL;
gidargs[2] = NULL;
if (!uidargs[0] || !uidargs[1] || !gidargs[0] || !gidargs[1])
goto out;
sprintf(uidargs[0], "newuidmap");
sprintf(gidargs[0], "newgidmap");
sprintf(uidargs[1], "%d", pid);
sprintf(gidargs[1], "%d", pid);
for (m=map; m; m = m->next) {
if (m->which == 'b' || m->which == 'u') {
nuargs += 3;
newuidargs = realloc(uidargs, (nuargs+1) * sizeof(*uidargs));
if (!newuidargs)
goto out;
uidargs = newuidargs;
uidargs[nuargs - 3] = malloc(21);
uidargs[nuargs - 2] = malloc(21);
uidargs[nuargs - 1] = malloc(21);
if (!uidargs[nuargs-3] || !uidargs[nuargs-2] || !uidargs[nuargs-1])
goto out;
sprintf(uidargs[nuargs - 3], "%ld", m->ns_id);
sprintf(uidargs[nuargs - 2], "%ld", m->host_id);
sprintf(uidargs[nuargs - 1], "%ld", m->range);
uidargs[nuargs] = NULL;
}
if (m->which == 'b' || m->which == 'g') {
ngargs += 3;
newgidargs = realloc(gidargs, (ngargs+1) * sizeof(*gidargs));
if (!newgidargs)
goto out;
gidargs = newgidargs;
gidargs[ngargs - 3] = malloc(21);
gidargs[ngargs - 2] = malloc(21);
gidargs[ngargs - 1] = malloc(21);
if (!gidargs[ngargs-3] || !gidargs[ngargs-2] || !gidargs[ngargs-1])
goto out;
sprintf(gidargs[ngargs - 3], "%ld", m->ns_id);
sprintf(gidargs[ngargs - 2], "%ld", m->host_id);
sprintf(gidargs[ngargs - 1], "%ld", m->range);
gidargs[ngargs] = NULL;
}
}
ret = -2;
// exec newuidmap
if (nuargs > 2 && run_cmd(uidargs) != 0) {
fprintf(stderr, "Error mapping uids\n");
goto out;
}
// exec newgidmap
if (ngargs > 2 && run_cmd(gidargs) != 0) {
fprintf(stderr, "Error mapping gids\n");
goto out;
}
ret = 0;
out:
for (i=0; i<nuargs; i++)
free(uidargs[i]);
for (i=0; i<ngargs; i++)
free(gidargs[i]);
free(uidargs);
free(gidargs);
return ret;
}
int main(int argc, char *argv[])
{
int c;
......@@ -371,6 +284,8 @@ int main(int argc, char *argv[])
exit(1);
}
lxc_list_init(&active_map);
while ((c = getopt(argc, argv, "m:h")) != EOF) {
switch (c) {
case 'm': if (parse_map(optarg)) usage(argv[0]); break;
......@@ -380,7 +295,7 @@ int main(int argc, char *argv[])
}
};
if (active_map == &default_map) {
if (lxc_list_empty(&active_map)) {
if (find_default_map()) {
fprintf(stderr, "You have no allocated subuids or subgids\n");
exit(1);
......@@ -437,7 +352,8 @@ int main(int argc, char *argv[])
}
buf[0] = '1';
if (map_child_uids(pid, active_map)) {
if (lxc_map_ids(&active_map, pid)) {
fprintf(stderr, "error mapping child\n");
ret = 0;
}
......
......@@ -806,7 +806,7 @@ static struct bdev *do_bdev_create(struct lxc_container *c, const char *type,
/* if we are not root, chown the rootfs dir to root in the
* target uidmap */
if (geteuid() != 0) {
if (geteuid() != 0 || (c->lxc_conf && !lxc_list_empty(&c->lxc_conf->id_map))) {
if (chown_mapped_root(bdev->dest, c->lxc_conf) < 0) {
ERROR("Error chowning %s to container root", bdev->dest);
bdev_put(bdev);
......@@ -992,7 +992,7 @@ static bool create_run_template(struct lxc_container *c, char *tpath, bool quiet
* and we append "--mapped-uid x", where x is the mapped uid
* for our geteuid()
*/
if (geteuid() != 0 && !lxc_list_empty(&conf->id_map)) {
if (!lxc_list_empty(&conf->id_map)) {
int n2args = 1;
char txtuid[20];
char txtgid[20];
......@@ -1450,7 +1450,7 @@ static inline bool enter_to_ns(struct lxc_container *c) {
init_pid = c->init_pid(c);
/* Switch to new userns */
if (geteuid() && access("/proc/self/ns/user", F_OK) == 0) {
if ((geteuid() != 0 || (c->lxc_conf && !lxc_list_empty(&c->lxc_conf->id_map))) && access("/proc/self/ns/user", F_OK) == 0) {
ret = snprintf(new_userns_path, MAXPATHLEN, "/proc/%d/ns/user", init_pid);
if (ret < 0 || ret >= MAXPATHLEN)
goto out;
......
......@@ -1234,3 +1234,38 @@ int detect_shared_rootfs(void)
fclose(f);
return 0;
}
bool on_path(char *cmd) {
char *path = NULL;
char *entry = NULL;
char *saveptr = NULL;
char cmdpath[MAXPATHLEN];
int ret;
path = getenv("PATH");
if (!path)
return false;
path = strdup(path);
if (!path)
return false;
entry = strtok_r(path, ":", &saveptr);
while (entry) {
ret = snprintf(cmdpath, MAXPATHLEN, "%s/%s", entry, cmd);
if (ret < 0 || ret >= MAXPATHLEN)
goto next_loop;
if (access(cmdpath, X_OK) == 0) {
free(path);
return true;
}
next_loop:
entry = strtok(NULL, ":");
}
free(path);
return false;
}
......@@ -278,3 +278,4 @@ uint64_t fnv_64a_buf(void *buf, size_t len, uint64_t hval);
#endif
int detect_shared_rootfs(void);
bool on_path(char *cmd);
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