From: Daan De Meyer Date: Wed, 13 May 2026 10:21:10 +0000 (+0200) Subject: nsresourced: re-link GID delegation file after atomic UID file write X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=95e7ead268736d8a0e71d2f6fb7a5e54247d82da;p=thirdparty%2Fsystemd.git 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. --- diff --git a/src/nsresourced/userns-registry.c b/src/nsresourced/userns-registry.c index 50efcd1153a..a9e3f82e59c 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));