]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/shared/discover-image.c
tree-wide: hook up image dissection policy logic everywhere
[thirdparty/systemd.git] / src / shared / discover-image.c
index b3b59fc0bb46bf908f6d6acdca664aec71e1e3de..86ff5d6d93a3eec0b2c91cd26406b1c05ad6e9ba 100644 (file)
@@ -14,7 +14,7 @@
 
 #include "alloc-util.h"
 #include "btrfs-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "chattr-util.h"
 #include "copy.h"
 #include "dirent-util.h"
 #include "dissect-image.h"
 #include "env-file.h"
 #include "env-util.h"
+#include "extension-util.h"
 #include "fd-util.h"
 #include "fs-util.h"
 #include "hashmap.h"
 #include "hostname-setup.h"
 #include "id128-util.h"
-#include "lockfile-util.h"
+#include "lock-util.h"
 #include "log.h"
 #include "loop-util.h"
 #include "macro.h"
@@ -58,11 +59,13 @@ static const char* const image_search_path[_IMAGE_CLASS_MAX] = {
                             "/usr/local/lib/portables\0"
                             "/usr/lib/portables\0",
 
+        /* Note that we don't allow storing extensions under /usr/, unlike with other image types. That's
+         * because extension images are supposed to extend /usr/, so you get into recursive races, especially
+         * with directory-based extensions, as the kernel's OverlayFS explicitly checks for this and errors
+         * out with -ELOOP if it finds that a lowerdir= is a child of another lowerdir=. */
         [IMAGE_EXTENSION] = "/etc/extensions\0"             /* only place symlinks here */
                             "/run/extensions\0"             /* and here too */
-                            "/var/lib/extensions\0"         /* the main place for images */
-                            "/usr/local/lib/extensions\0"
-                            "/usr/lib/extensions\0",
+                            "/var/lib/extensions\0",        /* the main place for images */
 };
 
 static Image *image_free(Image *i) {
@@ -85,8 +88,9 @@ DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(image_hash_ops, char, string_hash_func, st
 
 static char **image_settings_path(Image *image) {
         _cleanup_strv_free_ char **l = NULL;
-        const char *fn;
-        unsigned i = 0;
+        _cleanup_free_ char *fn = NULL;
+        size_t i = 0;
+        int r;
 
         assert(image);
 
@@ -94,7 +98,9 @@ static char **image_settings_path(Image *image) {
         if (!l)
                 return NULL;
 
-        fn = strjoina(image->name, ".nspawn");
+        fn = strjoin(image->name, ".nspawn");
+        if (!fn)
+                return NULL;
 
         FOREACH_STRING(s, "/etc/systemd/nspawn", "/run/systemd/nspawn") {
                 l[i] = path_join(s, fn);
@@ -104,25 +110,32 @@ static char **image_settings_path(Image *image) {
                 i++;
         }
 
-        l[i] = file_in_same_dir(image->path, fn);
-        if (!l[i])
+        r = file_in_same_dir(image->path, fn, l + i);
+        if (r == -ENOMEM)
                 return NULL;
+        if (r < 0)
+                log_debug_errno(r, "Failed to generate .nspawn settings path from image path, ignoring: %m");
+
+        strv_uniq(l);
 
         return TAKE_PTR(l);
 }
 
-static char *image_roothash_path(Image *image) {
-        const char *fn;
+static int image_roothash_path(Image *image, char **ret) {
+        _cleanup_free_ char *fn = NULL;
 
         assert(image);
 
-        fn = strjoina(image->name, ".roothash");
+        fn = strjoin(image->name, ".roothash");
+        if (!fn)
+                return -ENOMEM;
 
-        return file_in_same_dir(image->path, fn);
+        return file_in_same_dir(image->path, fn, ret);
 }
 
 static int image_new(
                 ImageType t,
+                ImageClass c,
                 const char *pretty,
                 const char *path,
                 const char *filename,
@@ -146,6 +159,7 @@ static int image_new(
         *i = (Image) {
                 .n_ref = 1,
                 .type = t,
+                .class = c,
                 .read_only = read_only,
                 .crtime = crtime,
                 .mtime = mtime,
@@ -173,15 +187,13 @@ static int image_new(
 static int extract_pretty(const char *path, const char *suffix, char **ret) {
         _cleanup_free_ char *name = NULL;
         const char *p;
-        size_t n;
 
         assert(path);
         assert(ret);
 
         p = last_path_component(path);
-        n = strcspn(p, "/");
 
-        name = strndup(p, n);
+        name = strdupcspn(p, "/");
         if (!name)
                 return -ENOMEM;
 
@@ -203,6 +215,7 @@ static int extract_pretty(const char *path, const char *suffix, char **ret) {
 }
 
 static int image_make(
+                ImageClass c,
                 const char *pretty,
                 int dfd,
                 const char *path,
@@ -244,7 +257,7 @@ static int image_make(
                 (faccessat(dfd, filename, W_OK, AT_EACCESS) < 0 && errno == EROFS);
 
         if (S_ISDIR(st->st_mode)) {
-                _cleanup_close_ int fd = -1;
+                _cleanup_close_ int fd = -EBADF;
                 unsigned file_attr = 0;
                 usec_t crtime = 0;
 
@@ -278,6 +291,7 @@ static int image_make(
                                         return r;
 
                                 r = image_new(IMAGE_SUBVOLUME,
+                                              c,
                                               pretty,
                                               path,
                                               filename,
@@ -314,6 +328,7 @@ static int image_make(
 
                 /* It's just a normal directory. */
                 r = image_new(IMAGE_DIRECTORY,
+                              c,
                               pretty,
                               path,
                               filename,
@@ -345,6 +360,7 @@ static int image_make(
                 }
 
                 r = image_new(IMAGE_RAW,
+                              c,
                               pretty,
                               path,
                               filename,
@@ -361,7 +377,7 @@ static int image_make(
                 return 0;
 
         } else if (S_ISBLK(st->st_mode)) {
-                _cleanup_close_ int block_fd = -1;
+                _cleanup_close_ int block_fd = -EBADF;
                 uint64_t size = UINT64_MAX;
 
                 /* A block device */
@@ -405,6 +421,7 @@ static int image_make(
                 }
 
                 r = image_new(IMAGE_BLOCK,
+                              c,
                               pretty,
                               path,
                               filename,
@@ -429,7 +446,6 @@ int image_find(ImageClass class,
                const char *root,
                Image **ret) {
 
-        const char *path;
         int r;
 
         assert(class >= 0);
@@ -446,7 +462,7 @@ int image_find(ImageClass class,
                 struct stat st;
                 int flags;
 
-                r = chase_symlinks_and_opendir(path, root, CHASE_PREFIX_ROOT, &resolved, &d);
+                r = chase_and_opendir(path, root, CHASE_PREFIX_ROOT, &resolved, &d);
                 if (r == -ENOENT)
                         continue;
                 if (r < 0)
@@ -476,13 +492,13 @@ int image_find(ImageClass class,
                         if (!S_ISREG(st.st_mode))
                                 continue;
 
-                        r = image_make(name, dirfd(d), resolved, raw, &st, ret);
+                        r = image_make(class, name, dirfd(d), resolved, raw, &st, ret);
 
                 } else {
                         if (!S_ISDIR(st.st_mode) && !S_ISBLK(st.st_mode))
                                 continue;
 
-                        r = image_make(name, dirfd(d), resolved, name, &st, ret);
+                        r = image_make(class, name, dirfd(d), resolved, name, &st, ret);
                 }
                 if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
                         continue;
@@ -496,7 +512,7 @@ int image_find(ImageClass class,
         }
 
         if (class == IMAGE_MACHINE && streq(name, ".host")) {
-                r = image_make(".host", AT_FDCWD, NULL, empty_to_root(root), NULL, ret);
+                r = image_make(class, ".host", AT_FDCWD, NULL, empty_to_root(root), NULL, ret);
                 if (r < 0)
                         return r;
 
@@ -516,9 +532,9 @@ int image_from_path(const char *path, Image **ret) {
          * overridden by another, different image earlier in the search path */
 
         if (path_equal(path, "/"))
-                return image_make(".host", AT_FDCWD, NULL, "/", NULL, ret);
+                return image_make(IMAGE_MACHINE, ".host", AT_FDCWD, NULL, "/", NULL, ret);
 
-        return image_make(NULL, AT_FDCWD, NULL, path, NULL, ret);
+        return image_make(_IMAGE_CLASS_INVALID, NULL, AT_FDCWD, NULL, path, NULL, ret);
 }
 
 int image_find_harder(ImageClass class, const char *name_or_path, const char *root, Image **ret) {
@@ -533,7 +549,6 @@ int image_discover(
                 const char *root,
                 Hashmap *h) {
 
-        const char *path;
         int r;
 
         assert(class >= 0);
@@ -544,7 +559,7 @@ int image_discover(
                 _cleanup_free_ char *resolved = NULL;
                 _cleanup_closedir_ DIR *d = NULL;
 
-                r = chase_symlinks_and_opendir(path, root, CHASE_PREFIX_ROOT, &resolved, &d);
+                r = chase_and_opendir(path, root, CHASE_PREFIX_ROOT, &resolved, &d);
                 if (r == -ENOENT)
                         continue;
                 if (r < 0)
@@ -593,7 +608,7 @@ int image_discover(
                         if (hashmap_contains(h, pretty))
                                 continue;
 
-                        r = image_make(pretty, dirfd(d), resolved, de->d_name, &st, &image);
+                        r = image_make(class, pretty, dirfd(d), resolved, de->d_name, &st, &image);
                         if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
                                 continue;
                         if (r < 0)
@@ -612,7 +627,7 @@ int image_discover(
         if (class == IMAGE_MACHINE && !hashmap_contains(h, ".host")) {
                 _cleanup_(image_unrefp) Image *image = NULL;
 
-                r = image_make(".host", AT_FDCWD, NULL, empty_to_root("/"), NULL, &image);
+                r = image_make(IMAGE_MACHINE, ".host", AT_FDCWD, NULL, empty_to_root("/"), NULL, &image);
                 if (r < 0)
                         return r;
 
@@ -643,9 +658,9 @@ int image_remove(Image *i) {
         if (!settings)
                 return -ENOMEM;
 
-        roothash = image_roothash_path(i);
-        if (!roothash)
-                return -ENOMEM;
+        r = image_roothash_path(i, &roothash);
+        if (r < 0)
+                return r;
 
         /* Make sure we don't interfere with a running nspawn */
         r = image_path_lock(i->path, LOCK_EX|LOCK_NB, &global_lock, &local_lock);
@@ -705,15 +720,17 @@ int image_remove(Image *i) {
 }
 
 static int rename_auxiliary_file(const char *path, const char *new_name, const char *suffix) {
-        _cleanup_free_ char *rs = NULL;
-        const char *fn;
-
-        fn = strjoina(new_name, suffix);
+        _cleanup_free_ char *fn = NULL, *rs = NULL;
+        int r;
 
-        rs = file_in_same_dir(path, fn);
-        if (!rs)
+        fn = strjoin(new_name, suffix);
+        if (!fn)
                 return -ENOMEM;
 
+        r = file_in_same_dir(path, fn, &rs);
+        if (r < 0)
+                return r;
+
         return rename_noreplace(AT_FDCWD, path, AT_FDCWD, rs);
 }
 
@@ -736,9 +753,9 @@ int image_rename(Image *i, const char *new_name) {
         if (!settings)
                 return -ENOMEM;
 
-        roothash = image_roothash_path(i);
-        if (!roothash)
-                return -ENOMEM;
+        r = image_roothash_path(i, &roothash);
+        if (r < 0)
+                return r;
 
         /* Make sure we don't interfere with a running nspawn */
         r = image_path_lock(i->path, LOCK_EX|LOCK_NB, &global_lock, &local_lock);
@@ -769,7 +786,7 @@ int image_rename(Image *i, const char *new_name) {
 
                 _fallthrough_;
         case IMAGE_SUBVOLUME:
-                new_path = file_in_same_dir(i->path, new_name);
+                r = file_in_same_dir(i->path, new_name, &new_path);
                 break;
 
         case IMAGE_BLOCK:
@@ -778,23 +795,23 @@ int image_rename(Image *i, const char *new_name) {
                 if (path_startswith(i->path, "/dev"))
                         return -EROFS;
 
-                new_path = file_in_same_dir(i->path, new_name);
+                r = file_in_same_dir(i->path, new_name, &new_path);
                 break;
 
         case IMAGE_RAW: {
                 const char *fn;
 
                 fn = strjoina(new_name, ".raw");
-                new_path = file_in_same_dir(i->path, fn);
+
+                r = file_in_same_dir(i->path, fn, &new_path);
                 break;
         }
 
         default:
                 return -EOPNOTSUPP;
         }
-
-        if (!new_path)
-                return -ENOMEM;
+        if (r < 0)
+                return r;
 
         nn = strdup(new_name);
         if (!nn)
@@ -825,16 +842,18 @@ int image_rename(Image *i, const char *new_name) {
 }
 
 static int clone_auxiliary_file(const char *path, const char *new_name, const char *suffix) {
-        _cleanup_free_ char *rs = NULL;
-        const char *fn;
-
-        fn = strjoina(new_name, suffix);
+        _cleanup_free_ char *fn = NULL, *rs = NULL;
+        int r;
 
-        rs = file_in_same_dir(path, fn);
-        if (!rs)
+        fn = strjoin(new_name, suffix);
+        if (!fn)
                 return -ENOMEM;
 
-        return copy_file_atomic(path, rs, 0664, 0, 0, COPY_REFLINK);
+        r = file_in_same_dir(path, fn, &rs);
+        if (r < 0)
+                return r;
+
+        return copy_file_atomic(path, rs, 0664, COPY_REFLINK);
 }
 
 int image_clone(Image *i, const char *new_name, bool read_only) {
@@ -853,9 +872,9 @@ int image_clone(Image *i, const char *new_name, bool read_only) {
         if (!settings)
                 return -ENOMEM;
 
-        roothash = image_roothash_path(i);
-        if (!roothash)
-                return -ENOMEM;
+        r = image_roothash_path(i, &roothash);
+        if (r < 0)
+                return r;
 
         /* Make sure nobody takes the new name, between the time we
          * checked it is currently unused in all search paths, and the
@@ -895,7 +914,8 @@ int image_clone(Image *i, const char *new_name, bool read_only) {
         case IMAGE_RAW:
                 new_path = strjoina("/var/lib/machines/", new_name, ".raw");
 
-                r = copy_file_atomic(i->path, new_path, read_only ? 0444 : 0644, FS_NOCOW_FL, FS_NOCOW_FL, COPY_REFLINK|COPY_CRTIME);
+                r = copy_file_atomic_full(i->path, new_path, read_only ? 0444 : 0644, FS_NOCOW_FL, FS_NOCOW_FL,
+                                          COPY_REFLINK|COPY_CRTIME, NULL, NULL);
                 break;
 
         case IMAGE_BLOCK:
@@ -979,7 +999,7 @@ int image_read_only(Image *i, bool b) {
         }
 
         case IMAGE_BLOCK: {
-                _cleanup_close_ int fd = -1;
+                _cleanup_close_ int fd = -EBADF;
                 struct stat st;
                 int state = b;
 
@@ -1113,7 +1133,7 @@ int image_set_limit(Image *i, uint64_t referenced_max) {
         return btrfs_subvol_set_subtree_quota_limit(i->path, 0, referenced_max);
 }
 
-int image_read_metadata(Image *i) {
+int image_read_metadata(Image *i, const ImagePolicy *image_policy) {
         _cleanup_(release_lock_file) LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT;
         int r;
 
@@ -1132,7 +1152,17 @@ int image_read_metadata(Image *i) {
                 _cleanup_free_ char *hostname = NULL;
                 _cleanup_free_ char *path = NULL;
 
-                r = chase_symlinks("/etc/hostname", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path, NULL);
+                if (i->class == IMAGE_EXTENSION) {
+                        r = extension_has_forbidden_content(i->path);
+                        if (r < 0)
+                                return r;
+                        if (r > 0)
+                                return log_debug_errno(SYNTHETIC_ERRNO(ENOMEDIUM),
+                                                       "Conflicting content found in image %s, refusing.",
+                                                       i->name);
+                }
+
+                r = chase("/etc/hostname", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path, NULL);
                 if (r < 0 && r != -ENOENT)
                         log_debug_errno(r, "Failed to chase /etc/hostname in image %s: %m", i->name);
                 else if (r >= 0) {
@@ -1143,25 +1173,11 @@ int image_read_metadata(Image *i) {
 
                 path = mfree(path);
 
-                r = chase_symlinks("/etc/machine-id", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path, NULL);
-                if (r < 0 && r != -ENOENT)
-                        log_debug_errno(r, "Failed to chase /etc/machine-id in image %s: %m", i->name);
-                else if (r >= 0) {
-                        _cleanup_close_ int fd = -1;
-
-                        fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY);
-                        if (fd < 0)
-                                log_debug_errno(errno, "Failed to open %s: %m", path);
-                        else {
-                                r = id128_read_fd(fd, ID128_PLAIN, &machine_id);
-                                if (r < 0)
-                                        log_debug_errno(r, "Image %s contains invalid machine ID.", i->name);
-                        }
-                }
-
-                path = mfree(path);
+                r = id128_get_machine(i->path, &machine_id);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to read machine ID in image %s, ignoring: %m", i->name);
 
-                r = chase_symlinks("/etc/machine-info", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path, NULL);
+                r = chase("/etc/machine-info", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path, NULL);
                 if (r < 0 && r != -ENOENT)
                         log_debug_errno(r, "Failed to chase /etc/machine-info in image %s: %m", i->name);
                 else if (r >= 0) {
@@ -1174,7 +1190,7 @@ int image_read_metadata(Image *i) {
                 if (r < 0)
                         log_debug_errno(r, "Failed to read os-release in image, ignoring: %m");
 
-                r = load_extension_release_pairs(i->path, i->name, &extension_release);
+                r = load_extension_release_pairs(i->path, i->name, /* relax_extension_release_check= */ false, &extension_release);
                 if (r < 0)
                         log_debug_errno(r, "Failed to read extension-release in image, ignoring: %m");
 
@@ -1192,18 +1208,22 @@ int image_read_metadata(Image *i) {
                 _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
                 _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
 
-                r = loop_device_make_by_path(i->path, O_RDONLY, LO_FLAGS_PARTSCAN, LOCK_SH, &d);
+                r = loop_device_make_by_path(i->path, O_RDONLY, /* sector_size= */ UINT32_MAX, LO_FLAGS_PARTSCAN, LOCK_SH, &d);
                 if (r < 0)
                         return r;
 
                 r = dissect_loop_device(
                                 d,
-                                NULL, NULL,
+                                /* verity= */ NULL,
+                                /* mount_options= */ NULL,
+                                image_policy,
                                 DISSECT_IMAGE_GENERIC_ROOT |
                                 DISSECT_IMAGE_REQUIRE_ROOT |
                                 DISSECT_IMAGE_RELAX_VAR_CHECK |
                                 DISSECT_IMAGE_READ_ONLY |
-                                DISSECT_IMAGE_USR_NO_ROOT,
+                                DISSECT_IMAGE_USR_NO_ROOT |
+                                DISSECT_IMAGE_ADD_PARTITION_DEVICES |
+                                DISSECT_IMAGE_PIN_PARTITION_DEVICES,
                                 &m);
                 if (r < 0)
                         return r;
@@ -1262,8 +1282,6 @@ bool image_in_search_path(
                 const char *root,
                 const char *image) {
 
-        const char *path;
-
         assert(image);
 
         NULSTR_FOREACH(path, image_search_path[class]) {
@@ -1305,3 +1323,11 @@ static const char* const image_type_table[_IMAGE_TYPE_MAX] = {
 };
 
 DEFINE_STRING_TABLE_LOOKUP(image_type, ImageType);
+
+static const char* const image_class_table[_IMAGE_CLASS_MAX] = {
+        [IMAGE_MACHINE] = "machine",
+        [IMAGE_PORTABLE] = "portable",
+        [IMAGE_EXTENSION] = "extension",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(image_class, ImageClass);