</variablelist>
</refsect2>
+ <refsect2>
+ <title>UID mappings</title>
+ <para>
+ A container can be started in a private user namespace with
+ user and group id mappings. For instance, you can map userid
+ 0 in the container to userid 200000 on the host. The root
+ user in the container will be privileged in the container,
+ but unprivileged on the host. Normally a system container
+ will want a range of ids, so you would map, for instance,
+ user and group ids 0 through 20,000 in the container to the
+ ids 200,000 through 220,000.
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>
+ <option>lxc.id_map</option>
+ </term>
+ <listitem>
+ <para>
+ Four values must be provided. First a character, either
+ 'U', or 'G', to specify whether user or group ids are
+ being mapped. Next is the first userid as seen on the
+ host. Next is the userid to be mapped in the container.
+ Finally, a range indicating the number of consecutive
+ ids to map. For instance
+ </para>
+<programlisting>
+ lxc.id_map = U 200000 0 20000
+ lxc.id_map = G 200000 0 20000
+</programlisting>
+ <para>
+ will map both user and group ids in the
+ range 0-19999 in the container to the ids
+ 200000-219999 on the host.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+
<refsect2>
<title>Startup hooks</title>
<para>
lxc_list_init(&new->network);
lxc_list_init(&new->mount_list);
lxc_list_init(&new->caps);
+ lxc_list_init(&new->id_map);
for (i=0; i<NUM_LXC_HOOKS; i++)
lxc_list_init(&new->hooks[i]);
#if HAVE_APPARMOR
return 0;
}
+int add_id_mapping(enum idtype idtype, pid_t pid, uid_t host_start, uid_t ns_start, int range)
+{
+ char path[PATH_MAX];
+ int ret;
+ FILE *f;
+
+ ret = snprintf(path, PATH_MAX, "/proc/%d/%cid_map", pid, idtype == ID_TYPE_UID ? 'u' : 'g');
+ if (ret < 0 || ret >= PATH_MAX) {
+ fprintf(stderr, "%s: path name too long", __func__);
+ return -E2BIG;
+ }
+ f = fopen(path, "w");
+ if (!f) {
+ perror("open");
+ return -EINVAL;
+ }
+ ret = fprintf(f, "%d %d %d", ns_start, host_start, range);
+ if (ret < 0)
+ perror("write");
+ fclose(f);
+ return ret < 0 ? ret : 0;
+}
+
+int lxc_map_ids(struct lxc_list *idmap, pid_t pid)
+{
+ struct lxc_list *iterator;
+ struct id_map *map;
+ int ret = 0;
+
+ lxc_list_for_each(iterator, idmap) {
+ map = iterator->elem;
+ ret = add_id_mapping(map->idtype, pid, map->hostid, map->nsid, map->range);
+ if (ret)
+ break;
+ }
+ return ret;
+}
+
int lxc_find_gateway_addresses(struct lxc_handler *handler)
{
struct lxc_list *network = &handler->conf->network;
tty_info->nbtty = 0;
}
+/*
+ * given a host uid, return the ns uid if it is mapped.
+ * if it is not mapped, return the original host id.
+ */
+static int shiftid(struct lxc_conf *c, int uid, enum idtype w)
+{
+ struct lxc_list *iterator;
+ struct id_map *map;
+ int low, high;
+
+ lxc_list_for_each(iterator, &c->id_map) {
+ map = iterator->elem;
+ if (map->idtype != w)
+ continue;
+
+ low = map->nsid;
+ high = map->nsid + map->range;
+ if (uid < low || uid >= high)
+ continue;
+
+ return uid - low + map->hostid;
+ }
+
+ return uid;
+}
+
+/*
+ * Take a pathname for a file created on the host, and map the uid and gid
+ * into the container if needed. (Used for ttys)
+ */
+static int uid_shift_file(char *path, struct lxc_conf *c)
+{
+ struct stat statbuf;
+ int newuid, newgid;
+
+ if (stat(path, &statbuf)) {
+ SYSERROR("stat(%s)", path);
+ return -1;
+ }
+
+ newuid = shiftid(c, statbuf.st_uid, ID_TYPE_UID);
+ newgid = shiftid(c, statbuf.st_gid, ID_TYPE_GID);
+ if (newuid != statbuf.st_uid || newgid != statbuf.st_gid) {
+ DEBUG("chowning %s from %d:%d to %d:%d\n", path, statbuf.st_uid, statbuf.st_gid, newuid, newgid);
+ if (chown(path, newuid, newgid)) {
+ SYSERROR("chown(%s)", path);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int uid_shift_ttys(int pid, struct lxc_conf *conf)
+{
+ int i, ret;
+ struct lxc_tty_info *tty_info = &conf->tty_info;
+ char path[MAXPATHLEN];
+ char *ttydir = conf->ttydir;
+
+ if (!conf->rootfs.path)
+ return 0;
+ /* first the console */
+ ret = snprintf(path, sizeof(path), "/proc/%d/root/dev/%s/console", pid, ttydir ? ttydir : "");
+ if (ret < 0 || ret >= sizeof(path)) {
+ ERROR("console path too long\n");
+ return -1;
+ }
+ if (uid_shift_file(path, conf)) {
+ DEBUG("Failed to chown the console %s.\n", path);
+ return -1;
+ }
+ for (i=0; i< tty_info->nbtty; i++) {
+ ret = snprintf(path, sizeof(path), "/proc/%d/root/dev/%s/tty%d",
+ pid, ttydir ? ttydir : "", i + 1);
+ if (ret < 0 || ret >= sizeof(path)) {
+ ERROR("pathname too long for ttys");
+ return -1;
+ }
+ if (uid_shift_file(path, conf)) {
+ DEBUG("Failed to chown pty %s.\n", path);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
int lxc_setup(const char *name, struct lxc_conf *lxc_conf)
{
#if HAVE_APPARMOR /* || HAVE_SMACK || HAVE_SELINUX */
return -1;
}
- if (setup_caps(&lxc_conf->caps)) {
- ERROR("failed to drop capabilities");
- return -1;
+ if (lxc_list_empty(&lxc_conf->id_map)) {
+ if (setup_caps(&lxc_conf->caps)) {
+ ERROR("failed to drop capabilities");
+ return -1;
+ }
}
NOTICE("'%s' is setup.", name);
char *value;
};
+enum idtype {
+ ID_TYPE_UID,
+ ID_TYPE_GID
+};
+
+/*
+ * id_map is an id map entry. Form in confile is:
+ * lxc.id_map = U 9800 0 100
+ * lxc.id_map = U 9900 1000 100
+ * lxc.id_map = G 9800 0 100
+ * lxc.id_map = G 9900 1000 100
+ * meaning the container can use uids and gids 0-100 and 1000-1100,
+ * with uid 0 mapping to uid 9800 on the host, and gid 1000 to
+ * gid 9900 on the host.
+ */
+struct id_map {
+ enum idtype idtype;
+ int hostid, nsid, range;
+};
+
/*
* Defines a structure containing a pty information for
* virtualizing a tty
int personality;
struct utsname *utsname;
struct lxc_list cgroup;
+ struct lxc_list id_map;
struct lxc_list network;
struct saved_nic *saved_nics;
int num_savednics;
extern int lxc_create_network(struct lxc_handler *handler);
extern void lxc_delete_network(struct lxc_handler *handler);
extern int lxc_assign_network(struct lxc_list *networks, pid_t pid);
+extern int lxc_map_ids(struct lxc_list *idmap, pid_t pid);
extern int lxc_find_gateway_addresses(struct lxc_handler *handler);
extern int lxc_create_tty(const char *name, struct lxc_conf *conf);
extern int lxc_clear_mount_entries(struct lxc_conf *c);
extern int lxc_clear_hooks(struct lxc_conf *c, const char *key);
+extern int setup_cgroup(const char *name, struct lxc_list *cgroups);
+
+extern int uid_shift_ttys(int pid, struct lxc_conf *conf);
+
/*
* Configure the container from inside
*/
static int config_aa_profile(const char *, const char *, struct lxc_conf *);
#endif
static int config_cgroup(const char *, const char *, struct lxc_conf *);
+static int config_idmap(const char *, const char *, struct lxc_conf *);
static int config_loglevel(const char *, const char *, struct lxc_conf *);
static int config_logfile(const char *, const char *, struct lxc_conf *);
static int config_mount(const char *, const char *, struct lxc_conf *);
{ "lxc.aa_profile", config_aa_profile },
#endif
{ "lxc.cgroup", config_cgroup },
+ { "lxc.id_map", config_idmap },
{ "lxc.loglevel", config_loglevel },
{ "lxc.logfile", config_logfile },
{ "lxc.mount", config_mount },
return -1;
}
+static int config_idmap(const char *key, const char *value, struct lxc_conf *lxc_conf)
+{
+ char *token = "lxc.id_map";
+ char *subkey;
+ struct lxc_list *idmaplist = NULL;
+ struct id_map *idmap = NULL;
+ int hostid, nsid, range;
+ char type;
+ int ret;
+
+ subkey = strstr(key, token);
+
+ if (!subkey)
+ return -1;
+
+ if (!strlen(subkey))
+ return -1;
+
+ idmaplist = malloc(sizeof(*idmaplist));
+ if (!idmaplist)
+ goto out;
+
+ idmap = malloc(sizeof(*idmap));
+ if (!idmap)
+ goto out;
+ memset(idmap, 0, sizeof(*idmap));
+
+ idmaplist->elem = idmap;
+
+ lxc_list_add_tail(&lxc_conf->id_map, idmaplist);
+
+ ret = sscanf(value, "%c %d %d %d", &type, &hostid, &nsid, &range);
+ if (ret != 4)
+ goto out;
+ INFO("read uid map: type %c hostid %d nsid %d range %d", type, hostid, nsid, range);
+ if (type == 'U')
+ idmap->idtype = ID_TYPE_UID;
+ else if (type == 'G')
+ idmap->idtype = ID_TYPE_GID;
+ else
+ goto out;
+ idmap->hostid = hostid;
+ idmap->nsid = nsid;
+ idmap->range = range;
+
+ return 0;
+
+out:
+ if (idmaplist)
+ free(idmaplist);
+
+ if (idmap) {
+ free(idmap);
+ }
+
+ return -1;
+}
+
static int config_path_item(const char *key, const char *value,
struct lxc_conf *lxc_conf, char **conf_item)
{
if (lxc_sync_barrier_parent(handler, LXC_SYNC_CONFIGURE))
return -1;
+ /*
+ * if we are in a new user namespace, become root there to have
+ * privilege over our namespace
+ */
+ if (!lxc_list_empty(&handler->conf->id_map)) {
+ NOTICE("switching to gid/uid 0 in new user namespace");
+ if (setgid(0)) {
+ SYSERROR("setgid");
+ goto out_warn_father;
+ }
+ if (setuid(0)) {
+ SYSERROR("setuid");
+ goto out_warn_father;
+ }
+ }
+
#if HAVE_SYS_CAPABILITY_H
if (handler->conf->need_utmp_watch) {
if (prctl(PR_CAPBSET_DROP, CAP_SYS_BOOT, 0, 0, 0)) {
return -1;
handler->clone_flags = CLONE_NEWUTS|CLONE_NEWPID|CLONE_NEWIPC|CLONE_NEWNS;
+ if (!lxc_list_empty(&handler->conf->id_map)) {
+ INFO("Cloning a new user namespace");
+ handler->clone_flags |= CLONE_NEWUSER;
+ }
if (!lxc_list_empty(&handler->conf->network)) {
handler->clone_flags |= CLONE_NEWNET;
}
}
+ /* map the container uids - the container became an invalid
+ * userid the moment it was cloned with CLONE_NEWUSER - this
+ * call doesn't change anything immediately, but allows the
+ * container to setuid(0) (0 being mapped to something else on
+ * the host) later to become a valid uid again */
+ if (lxc_map_ids(&handler->conf->id_map, handler->pid)) {
+ ERROR("failed to set up id mapping");
+ goto out_delete_net;
+ }
+
/* Tell the child to continue its initialization. we'll get
* LXC_SYNC_CGROUP when it is ready for us to setup cgroups
*/
if (detect_shared_rootfs())
umount2(handler->conf->rootfs.mount, MNT_DETACH);
+ /* If child is in a fresh user namespace, chown his ptys for
+ * it */
+ if (uid_shift_ttys(handler->pid, handler->conf))
+ DEBUG("Failed to chown ptys.\n");
+
if (handler->ops->post_start(handler, handler->data))
goto out_abort;