]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
repart: Fix deny list logic
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Mon, 8 May 2023 16:44:01 +0000 (18:44 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 10 May 2023 16:07:47 +0000 (18:07 +0200)
Until now, we always excluded the top level directories that were
covered by child partition mount points, regardless of the source
directory and the target directory of the copy files operation.
This means that even if we were populating a XBOOTLDR partition, if
there was an EFI partition in the image, we'd exclude /boot
unconditionally, leading to the XBOOTLDR partition to be empty.

Also, because of the same cause, if we were copying a nested source
directory (e.g. /abc/def) to the root directory in the root
partition, if /abc/def/usr existed and was populated with files and
directories, the root partition would have those files under /usr,
even if a /usr partition was defined.

To fix these issues, instead of unconditionally excluding the top
level partition mount points under <source>, let's make sure that
when we're copying files from any source directory to the root
directory of a root partition, that we exclude the partition mount
point directories under the source directory instead of the top
level ones.

src/partition/repart.c
test/units/testsuite-58.sh

index d6db6ee73e1ddbd9679379d744c9077d631a08da..44b0e461f2f819f196f9d9b8c0da0f0ef95d6948 100644 (file)
@@ -96,6 +96,8 @@
 /* LUKS2 volume key size. */
 #define VOLUME_KEY_SIZE (512ULL/8ULL)
 
+#define APIVFS_TMP_DIRS_NULSTR "proc\0sys\0dev\0tmp\0run\0var/tmp\0"
+
 /* Note: When growing and placing new partitions we always align to 4K sector size. It's how newer hard disks
  * are designed, and if everything is aligned to that performance is best. And for older hard disks with 512B
  * sector size devices were generally assumed to have an even number of sectors, hence at the worst we'll
@@ -3859,7 +3861,119 @@ static int context_copy_blocks(Context *context) {
         return 0;
 }
 
-static int do_copy_files(Partition *p, const char *root, Hashmap *denylist) {
+static int add_exclude_path(const char *path, Hashmap **denylist, DenyType type) {
+        _cleanup_free_ struct stat *st = NULL;
+        int r;
+
+        assert(path);
+        assert(denylist);
+
+        st = new(struct stat, 1);
+        if (!st)
+                return log_oom();
+
+        r = chase_and_stat(path, arg_root, CHASE_PREFIX_ROOT, NULL, st);
+        if (r == -ENOENT)
+                return 0;
+        if (r < 0)
+                return log_error_errno(r, "Failed to stat source file '%s/%s': %m", strempty(arg_root), path);
+
+        r = hashmap_ensure_put(denylist, &inode_hash_ops, st, INT_TO_PTR(type));
+        if (r == -EEXIST)
+                return 0;
+        if (r < 0)
+                return log_oom();
+        if (r > 0)
+                TAKE_PTR(st);
+
+        return 0;
+}
+
+static int make_copy_files_denylist(
+                Context *context,
+                const Partition *p,
+                const char *source,
+                const char *target,
+                Hashmap **ret) {
+
+        _cleanup_hashmap_free_ Hashmap *denylist = NULL;
+        int r;
+
+        assert(context);
+        assert(p);
+        assert(source);
+        assert(target);
+        assert(ret);
+
+        /* Always exclude the top level APIVFS and temporary directories since the contents of these
+         * directories are almost certainly not intended to end up in an image. */
+
+        NULSTR_FOREACH(s, APIVFS_TMP_DIRS_NULSTR) {
+                r = add_exclude_path(s, &denylist, DENY_CONTENTS);
+                if (r < 0)
+                        return r;
+        }
+
+        /* Add the user configured excludes. */
+
+        STRV_FOREACH(e, p->exclude_files) {
+                r = add_exclude_path(*e, &denylist, endswith(*e, "/") ? DENY_CONTENTS : DENY_INODE);
+                if (r < 0)
+                        return r;
+        }
+
+        /* If we're populating a root partition, we don't want any files to end up under the APIVFS mount
+         * points. While we already exclude <source>/proc, users could still do something such as
+         * "CopyFiles=/abc:/". Now, if /abc has a proc subdirectory with files in it, those will end up in
+         * the top level proc directory in the root partition, which we want to avoid. To deal with these
+         * cases, whenever we're populating a root partition and the target of CopyFiles= is the root
+         * directory of the root partition, we exclude all directories under the source that are named after
+         * APIVFS directories or named after mount points of other partitions that are also going to be part
+         * of the image. */
+
+        if (p->type.designator == PARTITION_ROOT && empty_or_root(target)) {
+                LIST_FOREACH(partitions, q, context->partitions) {
+                        if (q->type.designator == PARTITION_ROOT)
+                                continue;
+
+                        const char *sources = gpt_partition_type_mountpoint_nulstr(q->type);
+                        if (!sources)
+                                continue;
+
+                        NULSTR_FOREACH(s, sources) {
+                                _cleanup_free_ char *path = NULL;
+
+                                /* Exclude only the children of partition mount points so that the nested
+                                 * partition mount point itself still ends up in the upper partition. */
+
+                                path = path_join(source, s);
+                                if (!path)
+                                        return -ENOMEM;
+
+                                r = add_exclude_path(path, &denylist, DENY_CONTENTS);
+                                if (r < 0)
+                                        return r;
+                        }
+                }
+
+                NULSTR_FOREACH(s, APIVFS_TMP_DIRS_NULSTR) {
+                        _cleanup_free_ char *path = NULL;
+
+                        path = path_join(source, s);
+                        if (!path)
+                                return -ENOMEM;
+
+                        r = add_exclude_path(path, &denylist, DENY_CONTENTS);
+                        if (r < 0)
+                                return r;
+                }
+        }
+
+        *ret = TAKE_PTR(denylist);
+        return 0;
+}
+
+static int do_copy_files(Context *context, Partition *p, const char *root) {
         int r;
 
         assert(p);
@@ -3891,8 +4005,13 @@ static int do_copy_files(Partition *p, const char *root, Hashmap *denylist) {
         }
 
         STRV_FOREACH_PAIR(source, target, p->copy_files) {
+                _cleanup_hashmap_free_ Hashmap *denylist = NULL;
                 _cleanup_close_ int sfd = -EBADF, pfd = -EBADF, tfd = -EBADF;
 
+                r = make_copy_files_denylist(context, p, *source, *target, &denylist);
+                if (r < 0)
+                        return r;
+
                 sfd = chase_and_open(*source, arg_root, CHASE_PREFIX_ROOT, O_CLOEXEC|O_NOCTTY, NULL);
                 if (sfd < 0)
                         return log_error_errno(sfd, "Failed to open source file '%s%s': %m", strempty(arg_root), *source);
@@ -4004,7 +4123,7 @@ static bool partition_needs_populate(Partition *p) {
         return !strv_isempty(p->copy_files) || !strv_isempty(p->make_directories);
 }
 
-static int partition_populate_directory(Partition *p, Hashmap *denylist, char **ret) {
+static int partition_populate_directory(Context *context, Partition *p, char **ret) {
         _cleanup_(rm_rf_physical_and_freep) char *root = NULL;
         const char *vt;
         int r;
@@ -4025,7 +4144,7 @@ static int partition_populate_directory(Partition *p, Hashmap *denylist, char **
         if (r < 0)
                 return log_error_errno(errno, "Failed to create temporary directory: %m");
 
-        r = do_copy_files(p, root, denylist);
+        r = do_copy_files(context, p, root);
         if (r < 0)
                 return r;
 
@@ -4039,7 +4158,7 @@ static int partition_populate_directory(Partition *p, Hashmap *denylist, char **
         return 0;
 }
 
-static int partition_populate_filesystem(Partition *p, const char *node, Hashmap *denylist) {
+static int partition_populate_filesystem(Context *context, Partition *p, const char *node) {
         int r;
 
         assert(p);
@@ -4067,7 +4186,7 @@ static int partition_populate_filesystem(Partition *p, const char *node, Hashmap
                 if (mount_nofollow_verbose(LOG_ERR, node, fs, p->format, MS_NOATIME|MS_NODEV|MS_NOEXEC|MS_NOSUID, NULL) < 0)
                         _exit(EXIT_FAILURE);
 
-                if (do_copy_files(p, fs, denylist) < 0)
+                if (do_copy_files(context, p, fs) < 0)
                         _exit(EXIT_FAILURE);
 
                 if (do_make_directories(p, fs) < 0)
@@ -4086,77 +4205,6 @@ static int partition_populate_filesystem(Partition *p, const char *node, Hashmap
         return 0;
 }
 
-static int add_exclude_path(const char *path, Hashmap **denylist, DenyType type) {
-        _cleanup_free_ struct stat *st = NULL;
-        int r;
-
-        assert(path);
-        assert(denylist);
-
-        st = new(struct stat, 1);
-        if (!st)
-                return log_oom();
-
-        r = chase_and_stat(path, arg_root, CHASE_PREFIX_ROOT, NULL, st);
-        if (r == -ENOENT)
-                return 0;
-        if (r < 0)
-                return log_error_errno(r, "Failed to stat source file '%s%s': %m",
-                                        strempty(arg_root), path);
-
-        if (hashmap_contains(*denylist, st))
-                return 0;
-
-        if (hashmap_ensure_put(denylist, &inode_hash_ops, st, INT_TO_PTR(type)) < 0)
-                return log_oom();
-
-        TAKE_PTR(st);
-
-        return 0;
-}
-
-static int make_copy_files_denylist(Context *context, const Partition *p, Hashmap **ret) {
-        _cleanup_hashmap_free_ Hashmap *denylist = NULL;
-        int r;
-
-        assert(context);
-        assert(p);
-        assert(ret);
-
-        LIST_FOREACH(partitions, q, context->partitions) {
-                if (p == q)
-                        continue;
-
-                const char *sources = gpt_partition_type_mountpoint_nulstr(q->type);
-                if (!sources)
-                        continue;
-
-                NULSTR_FOREACH(s, sources) {
-                        /* Exclude the children of partition mount points so that the nested partition mount
-                         * point itself still ends up in the upper partition. */
-
-                        r = add_exclude_path(s, &denylist, DENY_CONTENTS);
-                        if (r < 0)
-                                return r;
-                }
-        }
-
-        FOREACH_STRING(s, "proc", "sys", "dev", "tmp", "run", "var/tmp") {
-                r = add_exclude_path(s, &denylist, DENY_CONTENTS);
-                if (r < 0)
-                        return r;
-        }
-
-        STRV_FOREACH(e, p->exclude_files) {
-                r = add_exclude_path(*e, &denylist, endswith(*e, "/") ? DENY_CONTENTS : DENY_INODE);
-                if (r < 0)
-                        return r;
-        }
-
-        *ret = TAKE_PTR(denylist);
-        return 0;
-}
-
 static int context_mkfs(Context *context) {
         int r;
 
@@ -4165,7 +4213,6 @@ static int context_mkfs(Context *context) {
         /* Make a file system */
 
         LIST_FOREACH(partitions, p, context->partitions) {
-                _cleanup_hashmap_free_ Hashmap *denylist = NULL;
                 _cleanup_(rm_rf_physical_and_freep) char *root = NULL;
                 _cleanup_(partition_target_freep) PartitionTarget *t = NULL;
                 _cleanup_strv_free_ char **extra_mkfs_options = NULL;
@@ -4190,10 +4237,6 @@ static int context_mkfs(Context *context) {
                 assert(p->new_size != UINT64_MAX);
                 assert(p->new_size >= (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0));
 
-                r = make_copy_files_denylist(context, p, &denylist);
-                if (r < 0)
-                        return r;
-
                 /* If we're doing encryption, we make sure we keep free space at the end which is required
                  * for cryptsetup's offline encryption. */
                 r = partition_target_prepare(context, p,
@@ -4215,7 +4258,7 @@ static int context_mkfs(Context *context) {
                                                         "Loop device access is required to populate %s filesystems.",
                                                         p->format);
 
-                        r = partition_populate_directory(p, denylist, &root);
+                        r = partition_populate_directory(context, p, &root);
                         if (r < 0)
                                 return r;
                 }
@@ -4237,7 +4280,7 @@ static int context_mkfs(Context *context) {
                 if (partition_needs_populate(p) && !root) {
                         assert(t->loop);
 
-                        r = partition_populate_filesystem(p, t->loop->node, denylist);
+                        r = partition_populate_filesystem(context, p, t->loop->node);
                         if (r < 0)
                                 return r;
                 }
@@ -5484,7 +5527,6 @@ static int context_minimize(Context *context) {
                 return log_error_errno(r, "Could not determine temporary directory: %m");
 
         LIST_FOREACH(partitions, p, context->partitions) {
-                _cleanup_hashmap_free_ Hashmap *denylist = NULL;
                 _cleanup_(rm_rf_physical_and_freep) char *root = NULL;
                 _cleanup_(unlink_and_freep) char *temp = NULL;
                 _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
@@ -5516,10 +5558,6 @@ static int context_minimize(Context *context) {
                 log_info("Pre-populating %s filesystem of partition %s twice to calculate minimal partition size",
                          p->format, strna(hint));
 
-                r = make_copy_files_denylist(context, p, &denylist);
-                if (r < 0)
-                        return r;
-
                 r = tempfn_random_child(vt, "repart", &temp);
                 if (r < 0)
                         return log_error_errno(r, "Failed to generate temporary file path: %m");
@@ -5554,7 +5592,7 @@ static int context_minimize(Context *context) {
                                                        "Loop device access is required to populate %s filesystems",
                                                        p->format);
 
-                        r = partition_populate_directory(p, denylist, &root);
+                        r = partition_populate_directory(context, p, &root);
                         if (r < 0)
                                 return r;
                 }
@@ -5589,7 +5627,7 @@ static int context_minimize(Context *context) {
                 if (!root) {
                         assert(d);
 
-                        r = partition_populate_filesystem(p, d->node, denylist);
+                        r = partition_populate_filesystem(context, p, d->node);
                         if (r < 0)
                                 return r;
                 }
@@ -5638,7 +5676,7 @@ static int context_minimize(Context *context) {
                 if (!root) {
                         assert(d);
 
-                        r = partition_populate_filesystem(p, d->node, denylist);
+                        r = partition_populate_filesystem(context, p, d->node);
                         if (r < 0)
                                 return r;
                 }
index 7c05d8c71fe144e5833315a7e747040715a97fb1..6cc699f15177e5b3118382d4149a5c36ff6881aa 100755 (executable)
@@ -855,11 +855,19 @@ test_exclude_files() {
     runas testuser touch "$root/usr/qed"
     runas testuser mkdir "$root/tmp"
     runas testuser touch "$root/tmp/prs"
+    runas testuser mkdir "$root/proc"
+    runas testuser touch "$root/proc/prs"
+    runas testuser mkdir "$root/zzz"
+    runas testuser mkdir "$root/zzz/usr"
+    runas testuser touch "$root/zzz/usr/prs"
+    runas testuser mkdir "$root/zzz/proc"
+    runas testuser touch "$root/zzz/proc/prs"
 
     runas testuser tee "$defs/00-root.conf" <<EOF
 [Partition]
 Type=root-${architecture}
 CopyFiles=/
+CopyFiles=/zzz:/
 EOF
 
     runas testuser tee "$defs/10-usr.conf" <<EOF
@@ -893,6 +901,10 @@ EOF
     assert_rc 0 ls "$imgs/mnt/usr"
     assert_rc 2 ls "$imgs/mnt/usr/def"
 
+    # Test that /zzz/usr/prs did not end up in the root partition under /usr but did end up in /zzz/usr/prs
+    assert_rc 2 ls "$imgs/mnt/usr/prs"
+    assert_rc 0 ls "$imgs/mnt/zzz/usr/prs"
+
     # Test that /tmp/prs did not end up in the root partition but /tmp did.
     assert_rc 0 ls "$imgs/mnt/tmp"
     assert_rc 2 ls "$imgs/mnt/tmp/prs"
@@ -902,6 +914,13 @@ EOF
     assert_rc 0 ls "$imgs/mnt/usr/def"
     assert_rc 2 ls "$imgs/mnt/usr/qed"
 
+    # Test that /zzz/proc/prs did not end up in the root partition but /proc did.
+    assert_rc 0 ls "$imgs/mnt/proc"
+    assert_rc 2 ls "$imgs/mnt/proc/prs"
+
+    # Test that /zzz/usr/prs did not end up in the usr partition.
+    assert_rc 2 ls "$imgs/mnt/usr/prs"
+
     umount -R "$imgs/mnt"
     losetup -d "$loop"
 }