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<UID>.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<GID>.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.
This commit is contained in:
Daan De Meyer
2026-05-13 23:51:41 +01:00
committed by Luca Boccassi
parent a442440315
commit 95e7ead268
+13 -7
View File
@@ -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));