From 95e7ead268736d8a0e71d2f6fb7a5e54247d82da Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 13 May 2026 12:21:10 +0200 Subject: [PATCH] nsresourced: re-link GID delegation file after atomic UID file write userns_registry_remove() restores a sub-delegated UID range by writing the previous owner's data to u.delegate with WRITE_STRING_FILE_ATOMIC. Atomic writes go via a temp file and rename, which replaces the directory entry with a fresh inode and severs the hardlink to g.delegate. The stale GID side then keeps pointing at the prior inode with outdated owner and ancestor data, so subsequent lookups via GID return wrong results. Re-create the hardlink after the atomic write so the two views stay in sync, matching what userns_registry_store() already does after writing a new delegation. --- src/nsresourced/userns-registry.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/nsresourced/userns-registry.c b/src/nsresourced/userns-registry.c index 50efcd1153..a9e3f82e59 100644 --- a/src/nsresourced/userns-registry.c +++ b/src/nsresourced/userns-registry.c @@ -838,9 +838,11 @@ int userns_registry_remove(int dir_fd, UserNamespaceInfo *info) { continue; } - _cleanup_free_ char *delegate_uid_fn = NULL; + _cleanup_free_ char *delegate_uid_fn = NULL, *delegate_gid_fn = NULL; if (asprintf(&delegate_uid_fn, "u" UID_FMT ".delegate", delegate->start_uid) < 0) return log_oom_debug(); + if (asprintf(&delegate_gid_fn, "g" GID_FMT ".delegate", delegate->start_gid) < 0) + return log_oom_debug(); if (existing.n_ancestor_userns > 0) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *delegate_def = NULL, *ancestor_array = NULL; @@ -876,10 +878,18 @@ int userns_registry_remove(int dir_fd, UserNamespaceInfo *info) { return log_debug_errno(r, "Failed to format delegation JSON object: %m"); r = write_string_file_at(dir_fd, delegate_uid_fn, delegate_buf, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); - if (r < 0) + if (r < 0) { RET_GATHER(ret, log_debug_errno(r, "Failed to write restored delegation data to '%s' in registry: %m", delegate_uid_fn)); + continue; + } + + /* The atomic write above replaced the UID file with a new inode, so the + * hardlink to the GID file is now broken. Re-create it to keep the two in + * sync. */ + r = linkat_replace(dir_fd, delegate_uid_fn, dir_fd, delegate_gid_fn); + if (r < 0) + RET_GATHER(ret, log_debug_errno(r, "Failed to re-link '%s' to '%s' in registry: %m", delegate_uid_fn, delegate_gid_fn)); - /* GID link already points to the UID file, no need to update it */ continue; } @@ -891,10 +901,6 @@ int userns_registry_remove(int dir_fd, UserNamespaceInfo *info) { if (r < 0) RET_GATHER(ret, log_debug_errno(r, "Failed to remove %s: %m", delegate_uid_fn)); - _cleanup_free_ char *delegate_gid_fn = NULL; - if (asprintf(&delegate_gid_fn, "g" GID_FMT ".delegate", delegate->start_gid) < 0) - return log_oom_debug(); - r = RET_NERRNO(unlinkat(dir_fd, delegate_gid_fn, 0)); if (r < 0) RET_GATHER(ret, log_debug_errno(r, "Failed to remove %s: %m", delegate_gid_fn));