mirror of
https://git.proxmox.com/git/mirror_lxc
synced 2025-07-16 10:12:27 +00:00
container creation: support unpriv container creation in user namespaces
1. lxcapi_create: don't try to unshare and mount for dir backed containers It's unnecessary, and breaks unprivileged lxc-create (since unpriv users cannot yet unshare(CLONE_NEWNS)). 2. api_create: chown rootfs chown rootfs to the host uid to which container root will be mapped 3. create: run template in a mapped user ns 4. use (setuid-root) newxidmap to set id_map if we are not root This is needed to be able to set userns mappings as an unprivileged user, for unprivileged lxc-start. Signed-off-by: Serge Hallyn <serge.hallyn@ubuntu.com> Acked-by: Stéphane Graber <stgraber@ubuntu.com>
This commit is contained in:
parent
09bbd74578
commit
cf3ef16dc4
106
src/lxc/conf.c
106
src/lxc/conf.c
@ -2802,31 +2802,49 @@ 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);
|
||||
|
||||
for(type = ID_TYPE_UID; type <= ID_TYPE_GID; type++) {
|
||||
int left, fill;
|
||||
|
||||
pos = buf;
|
||||
lxc_list_for_each(iterator, idmap) {
|
||||
/* The kernel only takes <= 4k for writes to /proc/<nr>/[ug]id_map */
|
||||
if (!buf)
|
||||
buf = pos = malloc(4096);
|
||||
int had_entry = 0;
|
||||
if (!buf) {
|
||||
buf = pos = malloc(4096);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
map = iterator->elem;
|
||||
if (map->idtype == type) {
|
||||
left = 4096 - (pos - buf);
|
||||
fill = snprintf(pos, left, "%lu %lu %lu\n",
|
||||
map->nsid, map->hostid, map->range);
|
||||
if (fill <= 0 || fill >= left)
|
||||
SYSERROR("snprintf failed, too many mappings");
|
||||
pos += fill;
|
||||
}
|
||||
}
|
||||
if (pos == buf) // no mappings were found
|
||||
pos = buf;
|
||||
if (!am_root)
|
||||
pos += sprintf(buf, "new%cidmap %d ",
|
||||
type == ID_TYPE_UID ? 'u' : 'g',
|
||||
pid);
|
||||
|
||||
lxc_list_for_each(iterator, idmap) {
|
||||
/* The kernel only takes <= 4k for writes to /proc/<nr>/[ug]id_map */
|
||||
map = iterator->elem;
|
||||
if (map->idtype != type)
|
||||
continue;
|
||||
|
||||
had_entry = 1;
|
||||
left = 4096 - (pos - buf);
|
||||
fill = snprintf(pos, left, " %lu %lu %lu", map->nsid,
|
||||
map->hostid, map->range);
|
||||
if (fill <= 0 || fill >= left)
|
||||
SYSERROR("snprintf failed, too many mappings");
|
||||
pos += fill;
|
||||
}
|
||||
if (!had_entry)
|
||||
continue;
|
||||
ret = write_id_mapping(type, pid, buf, pos-buf);
|
||||
left = 4096 - (pos - buf);
|
||||
fill = snprintf(pos, left, "\n");
|
||||
if (fill <= 0 || fill >= left)
|
||||
SYSERROR("snprintf failed, too many mappings");
|
||||
pos += fill;
|
||||
|
||||
if (am_root)
|
||||
ret = write_id_mapping(type, pid, buf, pos-buf);
|
||||
else
|
||||
ret = system(buf);
|
||||
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
@ -2836,6 +2854,58 @@ int lxc_map_ids(struct lxc_list *idmap, pid_t pid)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* return the host uid to which the container root is mapped, or -1 on
|
||||
* error
|
||||
*/
|
||||
int get_mapped_rootid(struct lxc_conf *conf)
|
||||
{
|
||||
struct lxc_list *it;
|
||||
struct id_map *map;
|
||||
|
||||
lxc_list_for_each(it, &conf->id_map) {
|
||||
map = it->elem;
|
||||
if (map->idtype != ID_TYPE_UID)
|
||||
continue;
|
||||
if (map->nsid != 0)
|
||||
continue;
|
||||
return map->hostid;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool hostid_is_mapped(int id, struct lxc_conf *conf)
|
||||
{
|
||||
struct lxc_list *it;
|
||||
struct id_map *map;
|
||||
lxc_list_for_each(it, &conf->id_map) {
|
||||
map = it->elem;
|
||||
if (map->idtype != ID_TYPE_UID)
|
||||
continue;
|
||||
if (id >= map->hostid && id < map->hostid + map->range)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int find_unmapped_nsuid(struct lxc_conf *conf)
|
||||
{
|
||||
struct lxc_list *it;
|
||||
struct id_map *map;
|
||||
uid_t freeid = 0;
|
||||
again:
|
||||
lxc_list_for_each(it, &conf->id_map) {
|
||||
map = it->elem;
|
||||
if (map->idtype != ID_TYPE_UID)
|
||||
continue;
|
||||
if (freeid >= map->nsid && freeid < map->nsid + map->range) {
|
||||
freeid = map->nsid + map->range;
|
||||
goto again;
|
||||
}
|
||||
}
|
||||
return freeid;
|
||||
}
|
||||
|
||||
int lxc_find_gateway_addresses(struct lxc_handler *handler)
|
||||
{
|
||||
struct lxc_list *network = &handler->conf->network;
|
||||
|
@ -361,4 +361,8 @@ extern int lxc_setup(const char *name, struct lxc_conf *lxc_conf,
|
||||
const char *lxcpath, struct cgroup_process_info *cgroup_info);
|
||||
|
||||
extern void lxc_rename_phys_nics_on_shutdown(struct lxc_conf *conf);
|
||||
|
||||
extern int get_mapped_rootid(struct lxc_conf *conf);
|
||||
extern int find_unmapped_nsuid(struct lxc_conf *conf);
|
||||
extern bool hostid_is_mapped(int id, struct lxc_conf *conf);
|
||||
#endif
|
||||
|
@ -693,6 +693,49 @@ static bool create_container_dir(struct lxc_container *c)
|
||||
static const char *lxcapi_get_config_path(struct lxc_container *c);
|
||||
static bool lxcapi_set_config_item(struct lxc_container *c, const char *key, const char *v);
|
||||
|
||||
/*
|
||||
* chown_mapped: for an unprivileged user with uid X to chown a dir
|
||||
* to subuid Y, he needs to run chown as root in a userns where
|
||||
* nsid 0 is mapped to hostuid Y, and nsid Y is mapped to hostuid
|
||||
* X. That way, the container root is privileged with respect to
|
||||
* hostuid X, allowing him to do the chown.
|
||||
*/
|
||||
static int chown_mapped(int nsrootid, char *path)
|
||||
{
|
||||
if (nsrootid < 0)
|
||||
return nsrootid;
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
SYSERROR("Failed forking");
|
||||
return -1;
|
||||
}
|
||||
if (!pid) {
|
||||
int hostuid = geteuid(), ret;
|
||||
char map1[100], map2[100];
|
||||
char *args[] = {"lxc-usernsexec", "-m", map1, "-m", map2, "--", "chown",
|
||||
"0", path, NULL};
|
||||
|
||||
// "b:0:nsrootid:1"
|
||||
ret = snprintf(map1, 100, "b:0:%d:1", nsrootid);
|
||||
if (ret < 0 || ret >= 100) {
|
||||
ERROR("Error uid printing map string");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// "b:hostuid:hostuid:1"
|
||||
ret = snprintf(map2, 100, "b:%d:%d:1", hostuid, hostuid);
|
||||
if (ret < 0 || ret >= 100) {
|
||||
ERROR("Error uid printing map string");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = execvp("lxc-usernsexec", args);
|
||||
SYSERROR("Failed executing lxc-usernsexec");
|
||||
exit(1);
|
||||
}
|
||||
return wait_for_pid(pid);
|
||||
}
|
||||
|
||||
/*
|
||||
* do_bdev_create: thin wrapper around bdev_create(). Like bdev_create(),
|
||||
* it returns a mounted bdev on success, NULL on error.
|
||||
@ -720,6 +763,25 @@ static struct bdev *do_bdev_create(struct lxc_container *c, const char *type,
|
||||
}
|
||||
|
||||
lxcapi_set_config_item(c, "lxc.rootfs", bdev->src);
|
||||
|
||||
/* if we are not root, chown the rootfs dir to root in the
|
||||
* target uidmap */
|
||||
|
||||
if (geteuid() != 0) {
|
||||
int rootid;
|
||||
if ((rootid = get_mapped_rootid(c->lxc_conf)) <= 0) {
|
||||
ERROR("No mapping for container root");
|
||||
bdev_put(bdev);
|
||||
return NULL;
|
||||
}
|
||||
ret = chown_mapped(rootid, bdev->dest);
|
||||
if (ret < 0) {
|
||||
ERROR("Error chowning %s to %d\n", bdev->dest, rootid);
|
||||
bdev_put(bdev);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return bdev;
|
||||
}
|
||||
|
||||
@ -785,6 +847,7 @@ static bool create_run_template(struct lxc_container *c, char *tpath, bool quiet
|
||||
int i;
|
||||
int ret, len, nargs = 0;
|
||||
char **newargv;
|
||||
struct lxc_conf *conf = c->lxc_conf;
|
||||
|
||||
process_unlock(); // we're no longer sharing
|
||||
if (quiet) {
|
||||
@ -795,10 +858,6 @@ static bool create_run_template(struct lxc_container *c, char *tpath, bool quiet
|
||||
open("/dev/null", O_RDWR);
|
||||
open("/dev/null", O_RDWR);
|
||||
}
|
||||
if (unshare(CLONE_NEWNS) < 0) {
|
||||
ERROR("error unsharing mounts");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
src = c->lxc_conf->rootfs.path;
|
||||
/*
|
||||
@ -815,9 +874,19 @@ static bool create_run_template(struct lxc_container *c, char *tpath, bool quiet
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (bdev->ops->mount(bdev) < 0) {
|
||||
ERROR("Error mounting rootfs");
|
||||
exit(1);
|
||||
if (strcmp(bdev->type, "dir") != 0) {
|
||||
if (unshare(CLONE_NEWNS) < 0) {
|
||||
ERROR("error unsharing mounts");
|
||||
exit(1);
|
||||
}
|
||||
if (bdev->ops->mount(bdev) < 0) {
|
||||
ERROR("Error mounting rootfs");
|
||||
exit(1);
|
||||
}
|
||||
} else { // TODO come up with a better way here!
|
||||
if (bdev->dest)
|
||||
free(bdev->dest);
|
||||
bdev->dest = strdup(bdev->src);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -827,6 +896,7 @@ static bool create_run_template(struct lxc_container *c, char *tpath, bool quiet
|
||||
if (argv)
|
||||
for (nargs = 0; argv[nargs]; nargs++) ;
|
||||
nargs += 4; // template, path, rootfs and name args
|
||||
|
||||
newargv = malloc(nargs * sizeof(*newargv));
|
||||
if (!newargv)
|
||||
exit(1);
|
||||
@ -870,8 +940,68 @@ static bool create_run_template(struct lxc_container *c, char *tpath, bool quiet
|
||||
exit(1);
|
||||
newargv[nargs - 1] = NULL;
|
||||
|
||||
/*
|
||||
* If we're running the template in a mapped userns, then
|
||||
* we prepend the template command with:
|
||||
* lxc-usernsexec <-m map1> ... <-m mapn> --
|
||||
*/
|
||||
if (geteuid() != 0 && !lxc_list_empty(&conf->id_map)) {
|
||||
int n2args = 1;
|
||||
char **n2 = malloc(n2args * sizeof(*n2));
|
||||
struct lxc_list *it;
|
||||
struct id_map *map;
|
||||
|
||||
newargv[0] = tpath;
|
||||
tpath = "lxc-usernsexec";
|
||||
n2[0] = "lxc-usernsexec";
|
||||
lxc_list_for_each(it, &conf->id_map) {
|
||||
map = it->elem;
|
||||
n2args += 2;
|
||||
n2 = realloc(n2, n2args * sizeof(*n2));
|
||||
if (!n2)
|
||||
exit(1);
|
||||
n2[n2args-2] = "-m";
|
||||
n2[n2args-1] = malloc(200);
|
||||
if (!n2[n2args-1])
|
||||
exit(1);
|
||||
ret = snprintf(n2[n2args-1], 200, "%c:%lu:%lu:%lu",
|
||||
map->idtype == ID_TYPE_UID ? 'u' : 'g',
|
||||
map->nsid, map->hostid, map->range);
|
||||
if (ret < 0 || ret >= 200)
|
||||
exit(1);
|
||||
}
|
||||
bool hostid_mapped = hostid_is_mapped(geteuid(), conf);
|
||||
int extraargs = hostid_mapped ? 1 : 3;
|
||||
n2 = realloc(n2, (nargs + n2args + extraargs) * sizeof(*n2));
|
||||
if (!n2)
|
||||
exit(1);
|
||||
if (!hostid_mapped) {
|
||||
int free_id = find_unmapped_nsuid(conf);
|
||||
n2[n2args++] = "-m";
|
||||
if (free_id < 0) {
|
||||
ERROR("Could not find free uid to map");
|
||||
exit(1);
|
||||
}
|
||||
n2[n2args++] = malloc(200);
|
||||
if (!n2[n2args-1]) {
|
||||
SYSERROR("out of memory");
|
||||
exit(1);
|
||||
}
|
||||
ret = snprintf(n2[n2args-1], 200, "u:%d:%d:1",
|
||||
free_id, geteuid());
|
||||
if (ret < 0 || ret >= 200) {
|
||||
ERROR("string too long");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
n2[n2args++] = "--";
|
||||
for (i = 0; i < nargs; i++)
|
||||
n2[i + n2args] = newargv[i];
|
||||
free(newargv);
|
||||
newargv = n2;
|
||||
}
|
||||
/* execute */
|
||||
execv(tpath, newargv);
|
||||
execvp(tpath, newargv);
|
||||
SYSERROR("failed to execute template %s", tpath);
|
||||
exit(1);
|
||||
}
|
||||
@ -2102,15 +2232,21 @@ static int clone_update_rootfs(struct lxc_container *c0,
|
||||
return wait_for_pid(pid);
|
||||
|
||||
process_unlock(); // we're no longer sharing
|
||||
if (unshare(CLONE_NEWNS) < 0) {
|
||||
ERROR("error unsharing mounts");
|
||||
exit(1);
|
||||
}
|
||||
bdev = bdev_init(c->lxc_conf->rootfs.path, c->lxc_conf->rootfs.mount, NULL);
|
||||
if (!bdev)
|
||||
exit(1);
|
||||
if (bdev->ops->mount(bdev) < 0)
|
||||
exit(1);
|
||||
if (strcmp(bdev->type, "dir") != 0) {
|
||||
if (unshare(CLONE_NEWNS) < 0) {
|
||||
ERROR("error unsharing mounts");
|
||||
exit(1);
|
||||
}
|
||||
if (bdev->ops->mount(bdev) < 0)
|
||||
exit(1);
|
||||
} else { // TODO come up with a better way
|
||||
if (bdev->dest)
|
||||
free(bdev->dest);
|
||||
bdev->dest = strdup(bdev->src);
|
||||
}
|
||||
|
||||
if (!lxc_list_empty(&conf->hooks[LXCHOOK_CLONE])) {
|
||||
/* Start of environment variable setup for hooks */
|
||||
|
Loading…
Reference in New Issue
Block a user