]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
unshare: Set uid and gid maps directly when run as root
authorChris Webb <chris@arachsys.com>
Thu, 23 Nov 2023 13:14:58 +0000 (13:14 +0000)
committerChris Webb <chris@arachsys.com>
Wed, 29 Nov 2023 21:22:00 +0000 (21:22 +0000)
The newuidmap and newgidmap setuid helpers provided by shadow don't allow
root to set up arbitrary mappings without explicit wildcard configuration
in /etc/subuid and /etc/subgid, and are an unnecessary dependency when
unshare is run privileged.

ID-mapped mount already knows how to create uid/gid maps directly for new
user namespaces, so teach unshare to do the same thing when run as root.
Continue to use the setuid helpers when we are not sufficiently privileged
to do the job ourselves.

Signed-off-by: Chris Webb <chris@arachsys.com>
sys-utils/unshare.1.adoc
sys-utils/unshare.c

index 88cc7cabb2226f86fb2263cf4a3a410a9ef06c9a..7036612e2dd9358eae761af1183651ff81329f23 100644 (file)
@@ -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. 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*.
+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 *unshare* was run unprivileged. 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. 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*.
+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 *unshare* was run unprivileged. 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.
 
index f1c596cb3c32d529912498b9dcb9a5e142a1caef..d1e8db4a7bdd665f1d55a53fcc6e496c61707a51 100644 (file)
@@ -574,7 +574,7 @@ static void add_single_map_range(struct map_range **chain, unsigned int outer,
 }
 
 /**
- * map_ids() - Create a new uid/gid map
+ * map_ids_external() - Create a new uid/gid map using setuid helper
  * @idmapper: Either newuidmap or newgidmap
  * @ppid: Pid to set the map for
  * @chain: A linked list of ID range mappings
@@ -585,7 +585,7 @@ static void add_single_map_range(struct map_range **chain, unsigned int outer,
  * 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)
+map_ids_external(const char *idmapper, int ppid, struct map_range *chain)
 {
        unsigned int i = 0, length = 3;
        char **argv;
@@ -607,6 +607,41 @@ map_ids(const char *idmapper, int ppid, struct map_range *chain)
        errexec(idmapper);
 }
 
+/**
+ * map_ids_internal() - Create a new uid/gid map using root privilege
+ * @type: Either uid_map or gid_map
+ * @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 a privileged write to
+ * /proc/@ppid/@type to set a mapping for each of the ranges in @chain.
+ */
+static void map_ids_internal(const char *type, int ppid, struct map_range *chain)
+{
+       int count, fd;
+       unsigned int length = 0;
+       char buffer[4096], *path;
+
+       xasprintf(&path, "/proc/%u/%s", ppid, type);
+       for (struct map_range *map = chain; map; map = map->next) {
+               count = snprintf(buffer + length, sizeof(buffer) - length,
+                                "%u %u %u\n",
+                                map->inner, map->outer, map->count);
+               if (count < 0 || count + length > sizeof(buffer))
+                       errx(EXIT_FAILURE,
+                               _("%s too large for kernel 4k limit"), path);
+               length += count;
+       }
+
+       fd = open(path, O_WRONLY | O_CLOEXEC | O_NOCTTY);
+       if (fd < 0)
+               err(EXIT_FAILURE, _("failed to open %s"), path);
+       if (write_all(fd, buffer, length) < 0)
+               err(EXIT_FAILURE, _("failed to write %s"), path);
+       close(fd);
+       free(path);
+}
+
 /**
  * map_ids_from_child() - Set up a new uid/gid map
  * @fd: The eventfd to wait on
@@ -637,6 +672,14 @@ static pid_t map_ids_from_child(int *fd, uid_t mapuser,
        if (groupmap)
                add_single_map_range(&groupmap, getegid(), mapgroup);
 
+       if (geteuid() == 0) {
+               if (usermap)
+                       map_ids_internal("uid_map", ppid, usermap);
+               if (groupmap)
+                       map_ids_internal("gid_map", ppid, groupmap);
+               exit(EXIT_SUCCESS);
+       }
+
        /* Avoid forking more than we need to */
        if (usermap && groupmap) {
                pid = fork();
@@ -647,9 +690,9 @@ static pid_t map_ids_from_child(int *fd, uid_t mapuser,
        }
 
        if (!pid && usermap)
-               map_ids("newuidmap", ppid, usermap);
+               map_ids_external("newuidmap", ppid, usermap);
        if (groupmap)
-               map_ids("newgidmap", ppid, groupmap);
+               map_ids_external("newgidmap", ppid, groupmap);
        exit(EXIT_SUCCESS);
 }