mountfsd: Relax user namespace checks

- Allow foreign UID range
- Allow delegated UID ranges

Both of these can now be mapped by nsresourced into user namespaces
and hence should be accepted by mountfsd.
This commit is contained in:
Daan De Meyer
2026-02-24 18:29:37 +01:00
parent 838528104b
commit 4bc0d8a9a1
+41 -11
View File
@@ -1208,30 +1208,60 @@ static int vl_method_mount_directory(
uid_t start;
if (userns_fd >= 0) {
/* Load ranges without coalescing to preserve the 1:1 correspondence
* between inside and outside entries */
_cleanup_(uid_range_freep) UIDRange *uid_range_outside = NULL, *uid_range_inside = NULL, *gid_range_outside = NULL, *gid_range_inside = NULL;
r = uid_range_load_userns_by_fd(userns_fd, UID_RANGE_USERNS_OUTSIDE, &uid_range_outside);
r = uid_range_load_userns_by_fd_full(userns_fd, UID_RANGE_USERNS_OUTSIDE, /* coalesce= */ false, &uid_range_outside);
if (r < 0)
return log_debug_errno(r, "Failed to load outside UID range of provided userns: %m");
r = uid_range_load_userns_by_fd(userns_fd, UID_RANGE_USERNS_INSIDE, &uid_range_inside);
r = uid_range_load_userns_by_fd_full(userns_fd, UID_RANGE_USERNS_INSIDE, /* coalesce= */ false, &uid_range_inside);
if (r < 0)
return log_debug_errno(r, "Failed to load inside UID range of provided userns: %m");
r = uid_range_load_userns_by_fd(userns_fd, GID_RANGE_USERNS_OUTSIDE, &gid_range_outside);
r = uid_range_load_userns_by_fd_full(userns_fd, GID_RANGE_USERNS_OUTSIDE, /* coalesce= */ false, &gid_range_outside);
if (r < 0)
return log_debug_errno(r, "Failed to load outside GID range of provided userns: %m");
r = uid_range_load_userns_by_fd(userns_fd, GID_RANGE_USERNS_INSIDE, &gid_range_inside);
r = uid_range_load_userns_by_fd_full(userns_fd, GID_RANGE_USERNS_INSIDE, /* coalesce= */ false, &gid_range_inside);
if (r < 0)
return log_debug_errno(r, "Failed to load inside GID range of provided userns: %m");
/* Be very strict for now */
/* UID and GID mappings must match */
if (!uid_range_equal(uid_range_outside, gid_range_outside) ||
!uid_range_equal(uid_range_inside, gid_range_inside) ||
uid_range_outside->n_entries != 1 ||
uid_range_outside->entries[0].nr != 0x10000 ||
uid_range_inside->n_entries != 1 ||
uid_range_inside->entries[0].start != 0 ||
uid_range_inside->entries[0].nr != 0x10000)
!uid_range_equal(uid_range_inside, gid_range_inside))
return sd_varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor");
/* Must have at least one entry, and inside/outside must have matching entry counts */
if (uid_range_is_empty(uid_range_outside) ||
uid_range_outside->n_entries != uid_range_inside->n_entries)
return sd_varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor");
/* The first range must be a root UID in the transient range (i.e. aligned
* to a 64K boundary) and mapped to 0 inside the user namespace (size 65536) */
if (!uid_is_transient(uid_range_outside->entries[0].start) ||
(uid_range_outside->entries[0].start & 0xFFFFU) != 0 ||
uid_range_outside->entries[0].nr != NSRESOURCE_UIDS_64K ||
uid_range_inside->entries[0].start != 0 ||
uid_range_inside->entries[0].nr != NSRESOURCE_UIDS_64K)
return sd_varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor");
/* All remaining entries must also be root UIDs in the transient range and
* mapped 1:1, which identifies them as delegated ranges. The last entry
* may also be the root UID in the foreign UID range. */
for (size_t i = 1; i < uid_range_outside->n_entries; i++) {
bool is_last = i + 1 == uid_range_outside->n_entries;
uid_t entry_start = uid_range_outside->entries[i].start;
if (!(uid_is_transient(entry_start) ||
(is_last && uid_is_foreign(entry_start))) ||
(entry_start & 0xFFFFU) != 0 ||
uid_range_outside->entries[i].nr != NSRESOURCE_UIDS_64K ||
uid_range_outside->entries[i].start != uid_range_inside->entries[i].start ||
uid_range_outside->entries[i].nr != uid_range_inside->entries[i].nr)
return sd_varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor");
}
start = uid_range_outside->entries[0].start;
} else
start = 0;