]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
validatefs: properly authenticate all subordinate devices of DM devices
authorLennart Poettering <lennart@poettering.net>
Wed, 14 May 2025 10:18:04 +0000 (12:18 +0200)
committerLennart Poettering <lennart@poettering.net>
Wed, 14 May 2025 13:44:54 +0000 (15:44 +0200)
Previously, we'd only authenticate "one" of the subordinate devices of a
DM device, and which one was somewhat undefined, it would be what we
find in slaves/ first. This is in particular a problem with dm-verity
which generally has two subordinate devices: the data device and the
hash device.

Let's fix this properly. This means two things:

1. iterate through *all* subordinate devices of a DM device (i.e.
   iterate through the sysfs slaves/ subdir), not just
   one

2. permit configuring a list of gpt labels and gpt type uuids in the
   xattrs of mount points, so that all valid combinations can be listed.

This only updates the validation like this. The generation of xattrs
that carry multiple type uuids/labels in systemd-repart will follow in a
later commit.

This extends the syntax of the two gpt-related xattrs, to allow lists of
things. This is a true extension, without breaking compat (but even if
it was, it wouldn't matter given that validatefs was added post v257,
i.e. is not included in a stable release.

Fixes: #37157
src/validatefs/validatefs.c

index fa8784cdf5d69632789b9062480f40fc9b7852cf..8140167392f0f6121a063fbf0068a0433a27271a 100644 (file)
@@ -10,6 +10,7 @@
 #include "device-util.h"
 #include "errno-util.h"
 #include "fd-util.h"
+#include "gpt.h"
 #include "initrd-util.h"
 #include "main-func.h"
 #include "mountpoint-util.h"
@@ -119,18 +120,38 @@ static int parse_argv(int argc, char *argv[]) {
 }
 
 typedef struct ValidateFields {
-        sd_id128_t gpt_type_uuid;
-        char *gpt_label;
+        sd_id128_t *gpt_type_uuid;
+        size_t n_gpt_type_uuid;
+        char **gpt_label;
         char **mount_point;
 } ValidateFields;
 
 static void validate_fields_done(ValidateFields *f) {
         assert(f);
 
-        free(f->gpt_label);
+        free(f->gpt_type_uuid);
+        strv_free(f->gpt_label);
         strv_free(f->mount_point);
 }
 
+static char* validate_fields_gpt_type_uuid_as_string(const ValidateFields *f) {
+        _cleanup_free_ char *joined = NULL;
+
+        assert(f);
+
+        FOREACH_ARRAY(u, f->gpt_type_uuid, f->n_gpt_type_uuid) {
+                if (!strextend_with_separator(&joined, ", ", SD_ID128_TO_UUID_STRING(*u)))
+                        return NULL;
+
+                const char *id = gpt_partition_type_uuid_to_string(*u);
+                if (id)
+                        (void) strextend(&joined, " (", id, ")");
+
+        }
+
+        return TAKE_PTR(joined);
+}
+
 static int validate_fields_read(int fd, ValidateFields *ret) {
         _cleanup_(validate_fields_done) ValidateFields f = {};
         int r;
@@ -138,27 +159,41 @@ static int validate_fields_read(int fd, ValidateFields *ret) {
         assert(fd >= 0);
         assert(ret);
 
-        _cleanup_free_ char *t = NULL;
-        r = fgetxattr_malloc(fd, "user.validatefs.gpt_type_uuid", &t, /* ret_size= */ NULL);
+        _cleanup_strv_free_ char **l = NULL;
+        r = getxattr_at_strv(fd, /* path= */ NULL, "user.validatefs.gpt_type_uuid", AT_EMPTY_PATH, &l);
         if (r < 0) {
                 if (r != -ENODATA && !ERRNO_IS_NOT_SUPPORTED(r))
                         return log_error_errno(r, "Failed to read 'user.validatefs.gpt_type_uuid' xattr: %m");
         } else {
-                r = sd_id128_from_string(t, &f.gpt_type_uuid);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to parse 'user.validatefs.gpt_type_uuid' xattr: %s", t);
+                STRV_FOREACH(i, l) {
+                        if (!GREEDY_REALLOC(f.gpt_type_uuid, f.n_gpt_type_uuid+1))
+                                return log_oom();
+
+                        r = sd_id128_from_string(*i, f.gpt_type_uuid + f.n_gpt_type_uuid);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse 'user.validatefs.gpt_type_uuid' xattr: %s", *i);
+
+                        f.n_gpt_type_uuid++;
+                }
         }
 
-        r = fgetxattr_malloc(fd, "user.validatefs.gpt_label", &f.gpt_label, /* ret_size= */ NULL);
+        l = strv_free(l);
+        r = getxattr_at_strv(fd, /* path= */ NULL, "user.validatefs.gpt_label", AT_EMPTY_PATH, &l);
         if (r < 0) {
                 if (r != -ENODATA && !ERRNO_IS_NOT_SUPPORTED(r))
                         return log_error_errno(r, "Failed to read 'user.validatefs.gpt_label' xattr: %m");
-        } else if (!utf8_is_valid(f.gpt_label) || string_has_cc(f.gpt_label, /* ok= */ NULL))
-                return log_error_errno(
-                                SYNTHETIC_ERRNO(EINVAL),
-                                "Extended attribute 'user.validatefs.gpt_label' contains invalid characters, refusing.");
+        } else {
+                STRV_FOREACH(i, l)
+                        if (!utf8_is_valid(*i) ||
+                            string_has_cc(*i, /* ok= */ NULL))
+                                return log_error_errno(
+                                                SYNTHETIC_ERRNO(EINVAL),
+                                                "Extended attribute 'user.validatefs.gpt_label' contains invalid characters, refusing: %s", *i);
 
-        _cleanup_strv_free_ char **l = NULL;
+                f.gpt_label = TAKE_PTR(l);
+        }
+
+        l = strv_free(l);
         r = getxattr_at_strv(fd, /* path= */ NULL, "user.validatefs.mount_point", AT_EMPTY_PATH, &l);
         if (r < 0) {
                 if (r != -ENODATA && !ERRNO_IS_NOT_SUPPORTED(r))
@@ -176,7 +211,7 @@ static int validate_fields_read(int fd, ValidateFields *ret) {
                 f.mount_point = TAKE_PTR(l);
         }
 
-        r = !sd_id128_is_null(f.gpt_type_uuid) || f.gpt_label || !strv_isempty(f.mount_point);
+        r = f.n_gpt_type_uuid > 0 || !strv_isempty(f.gpt_label) || !strv_isempty(f.mount_point);
         *ret = TAKE_STRUCT(f);
         return r;
 }
@@ -188,8 +223,6 @@ static int validate_mount_point(const char *path, const ValidateFields *f) {
         if (strv_isempty(f->mount_point))
                 return 0;
 
-        bool good = false;
-
         STRV_FOREACH(i, f->mount_point) {
                 _cleanup_free_ char *jj = NULL;
                 const char *j;
@@ -203,39 +236,24 @@ static int validate_mount_point(const char *path, const ValidateFields *f) {
                 } else
                         j = *i;
 
-                if (path_equal(path, j)) {
-                        good = true;
-                        break;
-                }
+                if (path_equal(path, j))
+                        return 0;
         }
 
-        if (!good) {
-                _cleanup_free_ char *joined = strv_join(f->mount_point, ", ");
+        _cleanup_free_ char *joined = strv_join(f->mount_point, ", ");
 
-                return log_error_errno(
-                                SYNTHETIC_ERRNO(EPERM),
-                                "File system is supposed to be mounted on one of %s only, but is mounted on %s, refusing.",
-                                strna(joined), path);
-        }
-
-        return 0;
+        return log_error_errno(
+                        SYNTHETIC_ERRNO(EPERM),
+                        "File system is supposed to be mounted on one of %s only, but is mounted on %s, refusing.",
+                        strna(joined), path);
 }
 
-static int validate_gpt_metadata(int fd, const char *path, const ValidateFields *f) {
+static int validate_gpt_metadata_one(sd_device *d, const char *path, const ValidateFields *f) {
         int r;
 
-        assert(fd >= 0);
-        assert(path);
+        assert(d);
         assert(f);
 
-        if (!f->gpt_label && sd_id128_is_null(f->gpt_type_uuid))
-                return 0;
-
-        _cleanup_(sd_device_unrefp) sd_device *d = NULL;
-        r = block_device_new_from_fd(fd, BLOCK_DEVICE_LOOKUP_ORIGINATING|BLOCK_DEVICE_LOOKUP_BACKING, &d);
-        if (r < 0)
-                return log_error_errno(r, "Failed to find block device backing '%s': %m", path);
-
         _cleanup_close_ int block_fd = sd_device_open(d, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
         if (block_fd < 0)
                 return log_error_errno(block_fd, "Failed to open block device backing '%s': %m", path);
@@ -270,33 +288,88 @@ static int validate_gpt_metadata(int fd, const char *path, const ValidateFields
         if (!streq_ptr(v, "gpt"))
                 return log_error_errno(SYNTHETIC_ERRNO(EPERM), "File system is supposed to be on a GPT partition table, but is not, refusing.");
 
-        if (f->gpt_label) {
+        if (!strv_isempty(f->gpt_label)) {
                 v = NULL;
                 (void) blkid_probe_lookup_value(b, "PART_ENTRY_NAME", &v, /* ret_len= */ NULL);
 
-                if (!streq(f->gpt_label, strempty(v)))
+                if (!strv_contains(f->gpt_label, strempty(v))) {
+                        _cleanup_free_ char *joined = strv_join(f->gpt_label, "', '");
+
                         return log_error_errno(
                                         SYNTHETIC_ERRNO(EPERM),
-                                        "File system is supposed to be placed in a partition with label '%s' only, but is placed in one labelled '%s', refusing.",
-                                        f->gpt_label, strempty(v));
+                                        "File system is supposed to be placed in a partition with labels '%s' only, but is placed in one labelled '%s', refusing.",
+                                        strna(joined), strempty(v));
+                }
         }
 
-        if (!sd_id128_is_null(f->gpt_type_uuid)) {
+        if (f->n_gpt_type_uuid > 0) {
                 v = NULL;
                 (void) blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, /* ret_len= */ NULL);
 
-                sd_id128_t id = SD_ID128_NULL;
-                if (!v || sd_id128_from_string(v, &id) < 0)
+                sd_id128_t id;
+                if (!v || sd_id128_from_string(v, &id) < 0) {
+                        _cleanup_free_ char *joined = validate_fields_gpt_type_uuid_as_string(f);
+
                         return log_error_errno(
                                         SYNTHETIC_ERRNO(EPERM),
-                                        "File system is supposed to be placed in a partition of type UUID '%s' only, but has no type, refusing.",
-                                        SD_ID128_TO_UUID_STRING(f->gpt_type_uuid));
+                                        "File system is supposed to be placed in a partition of type UUIDs %s only, but has no type, refusing.",
+                                        strna(joined));
+                }
+
+                bool found = false;
+                FOREACH_ARRAY(u, f->gpt_type_uuid, f->n_gpt_type_uuid)
+                        if (sd_id128_equal(*u, id)) {
+                                found = true;
+                                break;
+                        }
+
+                if (!found) {
+                        _cleanup_free_ char *joined = validate_fields_gpt_type_uuid_as_string(f);
 
-                if (!sd_id128_equal(f->gpt_type_uuid, id))
                         return log_error_errno(
                                         SYNTHETIC_ERRNO(EPERM),
-                                        "File system is supposed to be placed in a partition of type UUID '%s' only, but has type '%s', refusing.",
-                                        SD_ID128_TO_UUID_STRING(f->gpt_type_uuid), SD_ID128_TO_UUID_STRING(id));
+                                        "File system is supposed to be placed in a partition of type UUIDs %s only, but has type '%s', refusing.",
+                                        strna(joined), SD_ID128_TO_UUID_STRING(id));
+                }
+        }
+
+        return 0;
+}
+
+static int validate_gpt_metadata(int fd, const char *path, const ValidateFields *f) {
+        int r;
+
+        assert(fd >= 0);
+        assert(path);
+        assert(f);
+
+        if (strv_isempty(f->gpt_label) && f->n_gpt_type_uuid == 0)
+                return 0;
+
+        _cleanup_(sd_device_unrefp) sd_device *d = NULL;
+        r = block_device_new_from_fd(fd, BLOCK_DEVICE_LOOKUP_BACKING, &d);
+        if (r < 0)
+                return log_error_errno(r, "Failed to find block device backing '%s': %m", path);
+
+        /* Now validate all subordinate devices individually. */
+        bool have_slaves = false;
+        const char *suffix;
+        FOREACH_DEVICE_CHILD_WITH_SUFFIX(d, child, suffix) {
+                if (!path_startswith(suffix, "slaves"))
+                        continue;
+
+                have_slaves = true;
+
+                r = validate_gpt_metadata_one(child, path, f);
+                if (r < 0)
+                        return r;
+        }
+
+        /* If this device has no subordinate devices, then validate the device itself instead */
+        if (!have_slaves) {
+                r = validate_gpt_metadata_one(d, path, f);
+                if (r < 0)
+                        return r;
         }
 
         return 0;