diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index c88fa84016..fa0fd1928f 100644 --- a/src/shared/discover-image.c +++ b/src/shared/discover-image.c @@ -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, diff --git a/test/units/TEST-13-NSPAWN.machined.sh b/test/units/TEST-13-NSPAWN.machined.sh index 2774072699..86e701fe49 100755 --- a/test/units/TEST-13-NSPAWN.machined.sh +++ b/test/units/TEST-13-NSPAWN.machined.sh @@ -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