nsresourced: Verify user namespace identity on registry lookup
When a user namespace dies and its registry entry is torn down, the kernel can recycle its inode number for a freshly-created namespace. A subsequent registration or operation request can therefore find a stale registry entry keyed by the same inode that actually belongs to a different, now-dead user namespace. Use NS_GET_ID to compare the kernel-assigned namespace identifier against the stored one whenever we look up the registry from a live userns fd (AddMount/AddControlGroup/AddNetworkInterface, plus the two registration paths). Extract release_userns_by_info()/release_userns_by_inode() into userns-registry.c so nsresourcework can fully clean up stale entries (BPF allowlist, fdstore fd, cgroups, netifs, on-disk record) before reusing the slot, and remove the now-unused userns_registry_inode_exists(). Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -501,6 +501,41 @@ static int name_is_available(
|
||||
return true;
|
||||
}
|
||||
|
||||
static int inode_slot_is_available(
|
||||
int registry_dir_fd,
|
||||
int userns_fd,
|
||||
struct userns_restrict_bpf *bpf,
|
||||
uint64_t inode) {
|
||||
|
||||
_cleanup_(userns_info_freep) UserNamespaceInfo *existing = NULL;
|
||||
int r;
|
||||
|
||||
assert(registry_dir_fd >= 0);
|
||||
assert(userns_fd >= 0);
|
||||
assert(inode != 0);
|
||||
|
||||
/* Returns true if the registry has no entry for this inode (after cleaning up any stale
|
||||
* leftover from a previously-registered namespace whose inode was recycled by the kernel),
|
||||
* false if a live registration already occupies the slot, negative on error. */
|
||||
|
||||
r = userns_registry_load_by_userns_inode(registry_dir_fd, inode, &existing);
|
||||
if (r == -ENOENT)
|
||||
return true;
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to load existing registry entry: %m");
|
||||
|
||||
r = userns_info_verify_fd(userns_fd, existing);
|
||||
if (r >= 0)
|
||||
return false;
|
||||
if (r != -ESTALE)
|
||||
return log_debug_errno(r, "Failed to verify user namespace identity: %m");
|
||||
|
||||
log_debug("Inode %" PRIu64 " was reused by the kernel; cleaning up stale registry entry for namespace id %" PRIu64 ".",
|
||||
inode, existing->userns_id);
|
||||
userns_registry_release_by_info(bpf, registry_dir_fd, existing);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int allocate_one(
|
||||
int registry_dir_fd,
|
||||
const char *name,
|
||||
@@ -604,6 +639,7 @@ static int allocate_now(
|
||||
int registry_dir_fd,
|
||||
int userns_fd,
|
||||
int parent_userns_fd,
|
||||
struct userns_restrict_bpf *bpf,
|
||||
UserNamespaceInfo *info,
|
||||
int *ret_lock_fd) {
|
||||
|
||||
@@ -638,10 +674,10 @@ static int allocate_now(
|
||||
if (r >= USERNS_PER_UID)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EUSERS), "User already registered %i user namespaces, refusing.", r);
|
||||
|
||||
r = userns_registry_inode_exists(registry_dir_fd, info->userns_inode);
|
||||
r = inode_slot_is_available(registry_dir_fd, userns_fd, bpf, info->userns_inode);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0)
|
||||
if (r == 0)
|
||||
return -EDEADLK;
|
||||
|
||||
r = name_is_available(registry_dir_fd, info->name);
|
||||
@@ -1342,7 +1378,7 @@ static int vl_method_allocate_user_range(sd_varlink *link, sd_json_variant *para
|
||||
userns_info->n_delegates = p.delegate_container_ranges;
|
||||
}
|
||||
|
||||
r = allocate_now(registry_dir_fd, userns_fd, parent_userns_fd, userns_info, &lock_fd);
|
||||
r = allocate_now(registry_dir_fd, userns_fd, parent_userns_fd, c->bpf, userns_info, &lock_fd);
|
||||
if (r == -EHOSTDOWN) /* The needed UID range is not delegated to us */
|
||||
return sd_varlink_error(link, "io.systemd.NamespaceResource.DynamicRangeUnavailable", NULL);
|
||||
if (r == -EBUSY) /* All used up */
|
||||
@@ -1555,10 +1591,10 @@ static int vl_method_register_user_namespace(sd_varlink *link, sd_json_variant *
|
||||
if (lock_fd < 0)
|
||||
return log_debug_errno(lock_fd, "Failed to open nsresource registry lock file: %m");
|
||||
|
||||
r = userns_registry_inode_exists(registry_dir_fd, userns_st.st_ino);
|
||||
r = inode_slot_is_available(registry_dir_fd, userns_fd, c->bpf, userns_st.st_ino);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0)
|
||||
if (r == 0)
|
||||
return sd_varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceExists", NULL);
|
||||
|
||||
r = name_is_available(registry_dir_fd, userns_name);
|
||||
@@ -1706,6 +1742,12 @@ static int vl_method_add_mount_to_user_namespace(sd_varlink *link, sd_json_varia
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = userns_info_verify_fd(userns_fd, userns_info);
|
||||
if (r == -ESTALE)
|
||||
return sd_varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceNotRegistered", NULL);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to verify user namespace identity: %m");
|
||||
|
||||
if (!c->bpf) {
|
||||
r = userns_restrict_install(/* pin= */ true, &c->bpf);
|
||||
if (r < 0)
|
||||
@@ -1858,6 +1900,12 @@ static int vl_method_add_cgroup_to_user_namespace(sd_varlink *link, sd_json_vari
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = userns_info_verify_fd(userns_fd, userns_info);
|
||||
if (r == -ESTALE)
|
||||
return sd_varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceNotRegistered", NULL);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to verify user namespace identity: %m");
|
||||
|
||||
/* The user namespace must have a user assigned */
|
||||
if (userns_info->size == 0)
|
||||
return sd_varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceWithoutUserRange", NULL);
|
||||
@@ -2251,6 +2299,12 @@ static int vl_method_add_netif_to_user_namespace(sd_varlink *link, sd_json_varia
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = userns_info_verify_fd(userns_fd, userns_info);
|
||||
if (r == -ESTALE)
|
||||
return sd_varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceNotRegistered", NULL);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to verify user namespace identity: %m");
|
||||
|
||||
if (strv_length(userns_info->netifs) > USER_NAMESPACE_NETIFS_DELEGATE_MAX)
|
||||
return sd_varlink_error(link, "io.systemd.NamespaceResource.TooManyNetworkInterfaces", NULL);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user