diff --git a/src/mountfsd/mountwork.c b/src/mountfsd/mountwork.c index bd2ffbf8b7..905324ffdd 100644 --- a/src/mountfsd/mountwork.c +++ b/src/mountfsd/mountwork.c @@ -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;