From: Chris Webb Date: Thu, 23 Nov 2023 12:24:58 +0000 (+0000) Subject: unshare: Support multiple ID ranges for user and group maps X-Git-Tag: v2.40-rc1~137^2~2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=a212f1fc8319efa19e55686f31deb65372343a01;p=thirdparty%2Futil-linux.git unshare: Support multiple ID ranges for user and group maps Accept multiple --map-users and --map-groups options to specify disjoint ID ranges to map inside a user namespace. This was already supported by mount --map-groups and --map-users, but paradoxically not by unshare itself. Factor out the hole-punching logic which removes single-ID mappings set with --map-user and --map-group from range mappings set with --map-users and --map-groups. add_single_map_range() now performs this fixup for every given range before prepending the single-ID mapping. Signed-off-by: Chris Webb --- diff --git a/sys-utils/unshare.1.adoc b/sys-utils/unshare.1.adoc index 670774a898..88cc7cabb2 100644 --- a/sys-utils/unshare.1.adoc +++ b/sys-utils/unshare.1.adoc @@ -94,7 +94,7 @@ Just before running the program, mount the proc filesystem at _mountpoint_ (defa Run the program only after the current effective user ID has been mapped to _uid_. If this option is specified multiple times, the last occurrence takes precedence. This option implies *--user*. **--map-users=**__inneruid:outeruid:count__|**auto**:: -Run the program only after the block of user IDs of size _count_ beginning at _outeruid_ has been mapped to the block of user IDs beginning at _inneruid_. This mapping is created with **newuidmap**(1). If the range of user IDs overlaps with the mapping specified by *--map-user*, then a "hole" will be removed from the mapping. This may result in the highest user ID of the mapping not being mapped. The special value *auto* will map the first block of user IDs owned by the effective user from _/etc/subuid_ to a block starting at user ID 0. If this option is specified multiple times, the last occurrence takes precedence. This option implies *--user*. +Run the program only after the block of user IDs of size _count_ beginning at _outeruid_ has been mapped to the block of user IDs beginning at _inneruid_. This mapping is created with **newuidmap**(1). If the range of user IDs overlaps with the mapping specified by *--map-user*, then a "hole" will be removed from the mapping. This may result in the highest user ID of the mapping not being mapped. Use *--map-users* multiple times to map more than one block of user IDs. The special value *auto* will map the first block of user IDs owned by the effective user from _/etc/subuid_ to a block starting at user ID 0. This option implies *--user*. + Before util-linux version 2.39, this option expected a comma-separated argument of the form _outeruid,inneruid,count_ but that format is now deprecated for consistency with the ordering used in _/proc/[pid]/uid_map_ and the _X-mount.idmap_ mount option. @@ -102,7 +102,7 @@ Before util-linux version 2.39, this option expected a comma-separated argument Run the program only after the current effective group ID has been mapped to _gid_. If this option is specified multiple times, the last occurrence takes precedence. This option implies *--setgroups=deny* and *--user*. **--map-groups=**__innergid:outergid:count__|**auto**:: -Run the program only after the block of group IDs of size _count_ beginning at _outergid_ has been mapped to the block of group IDs beginning at _innergid_. This mapping is created with **newgidmap**(1). If the range of group IDs overlaps with the mapping specified by *--map-group*, then a "hole" will be removed from the mapping. This may result in the highest group ID of the mapping not being mapped. The special value *auto* will map the first block of user IDs owned by the effective user from _/etc/subgid_ to a block starting at group ID 0. If this option is specified multiple times, the last occurrence takes precedence. This option implies *--user*. +Run the program only after the block of group IDs of size _count_ beginning at _outergid_ has been mapped to the block of group IDs beginning at _innergid_. This mapping is created with **newgidmap**(1). If the range of group IDs overlaps with the mapping specified by *--map-group*, then a "hole" will be removed from the mapping. This may result in the highest group ID of the mapping not being mapped. Use *--map-groups* multiple times to map more than one block of group IDs. The special value *auto* will map the first block of user IDs owned by the effective user from _/etc/subgid_ to a block starting at group ID 0. This option implies *--user*. + Before util-linux version 2.39, this option expected a comma-separated argument of the form _outergid,innergid,count_ but that format is now deprecated for consistency with the ordering used in _/proc/[pid]/gid_map_ and the _X-mount.idmap_ mount option. diff --git a/sys-utils/unshare.c b/sys-utils/unshare.c index 6222c1421c..f1c596cb3c 100644 --- a/sys-utils/unshare.c +++ b/sys-utils/unshare.c @@ -364,6 +364,7 @@ static gid_t get_group(const char *s, const char *err) * @outer: First ID mapped on the outside of the namespace * @inner: First ID mapped on the inside of the namespace * @count: Length of the inside and outside ranges + * @next: Next range of IDs in the chain * * A range of uids/gids to map using new[gu]idmap. */ @@ -371,9 +372,16 @@ struct map_range { unsigned int outer; unsigned int inner; unsigned int count; + struct map_range *next; }; -#define UID_BUFSIZ sizeof(stringify_value(ULONG_MAX)) +static void insert_map_range(struct map_range **chain, struct map_range map) +{ + struct map_range *tail = *chain; + *chain = xmalloc(sizeof(**chain)); + memcpy(*chain, &map, sizeof(**chain)); + (*chain)->next = tail; +} /** * get_map_range() - Parse a mapping range from a string @@ -382,20 +390,20 @@ struct map_range { * Parse a string of the form inner:outer:count or outer,inner,count into * a new mapping range. * - * Return: A new &struct map_range + * Return: A struct map_range */ -static struct map_range *get_map_range(const char *s) +static struct map_range get_map_range(const char *s) { int end; - struct map_range *ret; + struct map_range ret; - ret = xmalloc(sizeof(*ret)); + ret.next = NULL; - if (sscanf(s, "%u:%u:%u%n", &ret->inner, &ret->outer, &ret->count, + if (sscanf(s, "%u:%u:%u%n", &ret.inner, &ret.outer, &ret.count, &end) >= 3 && !s[end]) return ret; /* inner:outer:count */ - if (sscanf(s, "%u,%u,%u%n", &ret->outer, &ret->inner, &ret->count, + if (sscanf(s, "%u,%u,%u%n", &ret.outer, &ret.inner, &ret.count, &end) >= 3 && !s[end]) return ret; /* outer,inner,count */ @@ -410,16 +418,16 @@ static struct map_range *get_map_range(const char *s) * * This finds the first subid range matching @uid in @filename. */ -static struct map_range *read_subid_range(char *filename, uid_t uid) +static struct map_range read_subid_range(char *filename, uid_t uid) { char *line = NULL, *pwbuf; FILE *idmap; size_t n = 0; struct passwd *pw; - struct map_range *map; + struct map_range map; - map = xmalloc(sizeof(*map)); - map->inner = -1; + map.inner = -1; + map.next = NULL; pw = xgetpwuid(uid, &pwbuf); if (!pw) @@ -452,13 +460,13 @@ static struct map_range *read_subid_range(char *filename, uid_t uid) if (!rest) continue; *rest = '\0'; - map->outer = strtoul_or_err(s, _("failed to parse subid map")); + map.outer = strtoul_or_err(s, _("failed to parse subid map")); s = rest + 1; rest = strchr(s, '\n'); if (rest) *rest = '\0'; - map->count = strtoul_or_err(s, _("failed to parse subid map")); + map.count = strtoul_or_err(s, _("failed to parse subid map")); fclose(idmap); free(pw); @@ -472,124 +480,129 @@ static struct map_range *read_subid_range(char *filename, uid_t uid) } /** - * map_ids() - Create a new uid/gid map - * @idmapper: Either newuidmap or newgidmap - * @ppid: Pid to set the map for + * add_single_map_range() - Add a single-ID map into a list without overlap + * @chain: A linked list of ID range mappings * @outer: ID outside the namespace for a single map. - * @inner: ID inside the namespace for a single map. May be -1 to only use @map. - * @map: A range of IDs to map + * @inner: ID inside the namespace for a single map, or -1 for no map. * - * This creates a new uid/gid map for @ppid using @idmapper. The ID @outer in - * the parent (our) namespace is mapped to the ID @inner in the child (@ppid's) - * namespace. In addition, the range of IDs beginning at @map->outer is mapped - * to the range of IDs beginning at @map->inner. The tricky bit is that we - * cannot let these mappings overlap. We accomplish this by removing a "hole" - * from @map, if @outer or @inner overlap it. This may result in one less than - * @map->count IDs being mapped from @map. The unmapped IDs are always the - * topmost IDs of the mapping (either in the parent or the child namespace). + * Prepend a mapping to @chain for the single ID @outer to the single ID + * @inner. The tricky bit is that we cannot let existing mappings overlap it. + * We accomplish this by removing a "hole" from each existing range @map, if + * @outer or @inner overlap it. This may result in one less than @map->count + * IDs being mapped from @map. The unmapped IDs are always the topmost IDs + * of the mapping (either in the parent or the child namespace). * - * Most of the time, this function will be called with @map->outer as some - * large ID, @map->inner as 0, and @map->count as a large number (at least - * 1000, but less than @map->outer). Typically, there will be no conflict with - * @outer. However, @inner may split the mapping for e.g. --map-current-user. - * - * This function always exec()s or errors out and does not return. + * Most of the time, this function will be called with a single mapping range + * @map, @map->outer as some large ID, @map->inner as 0, and @map->count as a + * large number (at least 1000, but less than @map->outer). Typically, there + * will be no conflict with @outer. However, @inner may split the mapping for + * e.g. --map-current-user. */ -static void __attribute__((__noreturn__)) -map_ids(const char *idmapper, int ppid, unsigned int outer, unsigned int inner, - struct map_range *map) + +static void add_single_map_range(struct map_range **chain, unsigned int outer, + unsigned int inner) { - /* idmapper + pid + 4 * map + NULL */ - char *argv[15]; - /* argv - idmapper - "1" - NULL */ - char args[12][UID_BUFSIZ]; - int i = 0, j = 0; - struct map_range lo, mid, hi; - unsigned int inner_offset, outer_offset; - - /* Some helper macros to reduce bookkeeping */ -#define push_str(s) do { \ - argv[i++] = s; \ -} while (0) -#define push_ul(x) do { \ - snprintf(args[j], sizeof(args[j]), "%u", x); \ - push_str(args[j++]); \ -} while (0) - - push_str(xstrdup(idmapper)); - push_ul(ppid); - if ((int)inner == -1) { + struct map_range *map = *chain; + + if (inner + 1 == 0) + outer = (unsigned int) -1; + *chain = NULL; + + while (map) { + struct map_range lo, mid, hi, *next = map->next; + unsigned int inner_offset, outer_offset; + /* - * If we don't have a "single" mapping, then we can just use map - * directly, starting inner IDs from zero for an auto mapping + * Start inner IDs from zero for an auto mapping; otherwise, if + * the single mapping exists and overlaps the range, remove an ID */ - push_ul(map->inner + 1 ? map->inner : 0); - push_ul(map->outer); - push_ul(map->count); - push_str(NULL); + if (map->inner + 1 == 0) + map->inner = 0; + else if (inner + 1 != 0 && + ((outer >= map->outer && outer <= map->outer + map->count) || + (inner >= map->inner && inner <= map->inner + map->count))) + map->count--; + + /* Determine where the splits between lo, mid, and hi will be */ + outer_offset = min(outer > map->outer ? outer - map->outer : 0, + map->count); + inner_offset = min(inner > map->inner ? inner - map->inner : 0, + map->count); - execvp(idmapper, argv); - errexec(idmapper); + /* + * In the worst case, we need three mappings: + * From the bottom of map to either inner or outer + */ + lo.outer = map->outer; + lo.inner = map->inner; + lo.count = min(inner_offset, outer_offset); + + /* From the lower of inner or outer to the higher */ + mid.outer = lo.outer + lo.count; + mid.outer += mid.outer == outer; + mid.inner = lo.inner + lo.count; + mid.inner += mid.inner == inner; + mid.count = abs_diff(outer_offset, inner_offset); + + /* And from the higher of inner or outer to the end of the map */ + hi.outer = mid.outer + mid.count; + hi.outer += hi.outer == outer; + hi.inner = mid.inner + mid.count; + hi.inner += hi.inner == inner; + hi.count = map->count - lo.count - mid.count; + + /* Insert non-empty mappings into the output chain */ + if (hi.count) + insert_map_range(chain, hi); + if (mid.count) + insert_map_range(chain, mid); + if (lo.count) + insert_map_range(chain, lo); + + free(map); + map = next; } - /* - * Start inner IDs from zero for an auto mapping; otherwise, if the two - * fixed mappings overlap, remove an ID from map - */ - if (map->inner + 1 == 0) - map->inner = 0; - else if ((outer >= map->outer && outer <= map->outer + map->count) || - (inner >= map->inner && inner <= map->inner + map->count)) - map->count--; - - /* Determine where the splits between lo, mid, and hi will be */ - outer_offset = min(outer > map->outer ? outer - map->outer : 0, - map->count); - inner_offset = min(inner > map->inner ? inner - map->inner : 0, - map->count); - - /* - * In the worst case, we need three mappings: - * From the bottom of map to either inner or outer - */ - lo.outer = map->outer; - lo.inner = map->inner; - lo.count = min(inner_offset, outer_offset); - - /* From the lower of inner or outer to the higher */ - mid.outer = lo.outer + lo.count; - mid.outer += mid.outer == outer; - mid.inner = lo.inner + lo.count; - mid.inner += mid.inner == inner; - mid.count = abs_diff(outer_offset, inner_offset); - - /* And from the higher of inner or outer to the end of the map */ - hi.outer = mid.outer + mid.count; - hi.outer += hi.outer == outer; - hi.inner = mid.inner + mid.count; - hi.inner += hi.inner == inner; - hi.count = map->count - lo.count - mid.count; - - push_ul(inner); - push_ul(outer); - push_str("1"); - /* new[gu]idmap doesn't like zero-length mappings, so skip them */ - if (lo.count) { - push_ul(lo.inner); - push_ul(lo.outer); - push_ul(lo.count); + if (inner + 1 != 0) { + /* Insert single ID mapping as the first entry in the chain */ + insert_map_range(chain, (struct map_range) { + .inner = inner, + .outer = outer, + .count = 1 + }); } - if (mid.count) { - push_ul(mid.inner); - push_ul(mid.outer); - push_ul(mid.count); - } - if (hi.count) { - push_ul(hi.inner); - push_ul(hi.outer); - push_ul(hi.count); +} + +/** + * map_ids() - Create a new uid/gid map + * @idmapper: Either newuidmap or newgidmap + * @ppid: Pid to set the map for + * @chain: A linked list of ID range mappings + * + * This creates a new uid/gid map for @ppid using @idmapper to set the + * mapping for each of the ranges in @chain. + * + * This function always exec()s or errors out and does not return. + */ +static void __attribute__((__noreturn__)) +map_ids(const char *idmapper, int ppid, struct map_range *chain) +{ + unsigned int i = 0, length = 3; + char **argv; + + for (struct map_range *map = chain; map; map = map->next) + length += 3; + argv = xcalloc(length, sizeof(*argv)); + argv[i++] = xstrdup(idmapper); + xasprintf(&argv[i++], "%u", ppid); + + for (struct map_range *map = chain; map; map = map->next) { + xasprintf(&argv[i++], "%u", map->inner); + xasprintf(&argv[i++], "%u", map->outer); + xasprintf(&argv[i++], "%u", map->count); } - push_str(NULL); + + argv[i] = NULL; execvp(idmapper, argv); errexec(idmapper); } @@ -619,6 +632,11 @@ static pid_t map_ids_from_child(int *fd, uid_t mapuser, if (child) return child; + if (usermap) + add_single_map_range(&usermap, geteuid(), mapuser); + if (groupmap) + add_single_map_range(&groupmap, getegid(), mapgroup); + /* Avoid forking more than we need to */ if (usermap && groupmap) { pid = fork(); @@ -629,9 +647,9 @@ static pid_t map_ids_from_child(int *fd, uid_t mapuser, } if (!pid && usermap) - map_ids("newuidmap", ppid, geteuid(), mapuser, usermap); + map_ids("newuidmap", ppid, usermap); if (groupmap) - map_ids("newgidmap", ppid, getegid(), mapgroup, groupmap); + map_ids("newgidmap", ppid, groupmap); exit(EXIT_SUCCESS); } @@ -844,21 +862,23 @@ int main(int argc, char *argv[]) case OPT_MAPUSERS: unshare_flags |= CLONE_NEWUSER; if (!strcmp(optarg, "auto")) - usermap = read_subid_range(_PATH_SUBUID, real_euid); + insert_map_range(&usermap, + read_subid_range(_PATH_SUBUID, real_euid)); else - usermap = get_map_range(optarg); + insert_map_range(&usermap, get_map_range(optarg)); break; case OPT_MAPGROUPS: unshare_flags |= CLONE_NEWUSER; if (!strcmp(optarg, "auto")) - groupmap = read_subid_range(_PATH_SUBGID, real_euid); + insert_map_range(&groupmap, + read_subid_range(_PATH_SUBGID, real_euid)); else - groupmap = get_map_range(optarg); + insert_map_range(&groupmap, get_map_range(optarg)); break; case OPT_MAPAUTO: unshare_flags |= CLONE_NEWUSER; - usermap = read_subid_range(_PATH_SUBUID, real_euid); - groupmap = read_subid_range(_PATH_SUBGID, real_euid); + insert_map_range(&usermap, read_subid_range(_PATH_SUBUID, real_euid)); + insert_map_range(&groupmap, read_subid_range(_PATH_SUBGID, real_euid)); break; case OPT_SETGROUPS: setgrpcmd = setgroups_str2id(optarg);