machine: preserve read-only clone mode for foreign UID images

machinectl clone --read-only ignored the requested read-only mode when
cloning a foreign-UID-owned directory because the unprivileged clone path
did not receive the read_only argument.

Pass the flag through and apply the immutable bit via the already-open
destination directory fd after copying. Keep this best-effort, matching
the ordinary directory clone fallback, so failure to set the attribute
does not turn a completed copy into a reported clone failure.

Add a regression test using foreign-0 rather than a fixed UID. Verify
the cloned image's ReadOnly property, copied contents and ownership, and
that the immutable directory rejects new entries. Run this subtest only
when file attributes and the existing rootless nspawn prerequisites are
available, leaving the other machined tests unchanged.
This commit is contained in:
Totoro
2026-09-05 08:34:55 +02:00
committed by Lennart Poettering
parent 0c8f57cae2
commit f789245918
2 changed files with 29 additions and 3 deletions
+11 -3
View File
@@ -1687,7 +1687,7 @@ static int get_pool_directory(
return 0;
}
static int unprivileged_clone(Image *i, const char *new_path) {
static int unprivileged_clone(Image *i, const char *new_path, bool read_only) {
int r;
assert(i);
@@ -1741,7 +1741,15 @@ static int unprivileged_clone(Image *i, const char *new_path) {
link = sd_varlink_unref(link);
/* Fork off child that moves into userns and does the copying */
return copy_tree_at_foreign(tree_fd, target_fd, userns_fd);
r = copy_tree_at_foreign(tree_fd, target_fd, userns_fd);
if (r < 0)
return r;
/* Match the best-effort immutable bit handling of ordinary directory clones. */
if (read_only)
(void) chattr_fd(new_fd, FS_IMMUTABLE_FL, FS_IMMUTABLE_FL);
return 0;
}
int image_clone(Image *i, const char *new_name, bool read_only, RuntimeScope scope) {
@@ -1784,7 +1792,7 @@ int image_clone(Image *i, const char *new_name, bool read_only, RuntimeScope sco
return r;
if (i->foreign_uid_owned)
r = unprivileged_clone(i, new_path);
r = unprivileged_clone(i, new_path, read_only);
else {
r = btrfs_subvol_snapshot_at(
AT_FDCWD, i->path,
+18
View File
@@ -181,6 +181,24 @@ if lsattr -d /var/lib/machines >/dev/null; then
[[ "$(machinectl show-image --property=ReadOnly --value clone2)" == yes ]]
machinectl read-only clone2 no
[[ "$(machinectl show-image --property=ReadOnly --value clone2)" == no ]]
# Foreign-UID cloning also needs mountfsd/nsresourced and user namespace delegation.
if can_do_rootless_nspawn; then
mkdir /var/lib/machines/foreign-source
echo "foreign clone contents" >/var/lib/machines/foreign-source/payload
chown foreign-0:foreign-0 /var/lib/machines/foreign-source{,/payload}
machinectl clone --read-only foreign-source foreign-clone
[[ "$(machinectl show-image --property=ReadOnly --value foreign-clone)" == yes ]]
cmp /var/lib/machines/foreign-source/payload /var/lib/machines/foreign-clone/payload
assert_eq "$(stat -c %u:%g /var/lib/machines/foreign-source)" \
"$(stat -c %u:%g /var/lib/machines/foreign-clone)"
assert_eq "$(stat -c %u:%g /var/lib/machines/foreign-source/payload)" \
"$(stat -c %u:%g /var/lib/machines/foreign-clone/payload)"
(! touch /var/lib/machines/foreign-clone/new-file)
machinectl remove foreign-clone foreign-source
else
echo "Skipping foreign-UID read-only clone test: rootless nspawn prerequisites not met"
fi
fi
machinectl remove clone2
for i in {0..4}; do