From 510e9666777e90131d451ae13f38fa9c91e93ec9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 14 May 2025 11:30:52 +0200 Subject: [PATCH 1/5] validatefs: split out validate_fields_check() into three functions Just some basic refactoring, no actual code changes --- src/validatefs/validatefs.c | 228 ++++++++++++++++++++---------------- 1 file changed, 127 insertions(+), 101 deletions(-) diff --git a/src/validatefs/validatefs.c b/src/validatefs/validatefs.c index 8cc595932e..fa8784cdf5 100644 --- a/src/validatefs/validatefs.c +++ b/src/validatefs/validatefs.c @@ -181,6 +181,127 @@ static int validate_fields_read(int fd, ValidateFields *ret) { return r; } +static int validate_mount_point(const char *path, const ValidateFields *f) { + assert(path); + assert(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; + + if (arg_root) { + jj = path_join(arg_root, *i); + if (!jj) + return log_oom(); + + j = jj; + } else + j = *i; + + if (path_equal(path, j)) { + good = true; + break; + } + } + + if (!good) { + _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; +} + +static int validate_gpt_metadata(int fd, const char *path, const ValidateFields *f) { + int r; + + assert(fd >= 0); + assert(path); + 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); + + _cleanup_(blkid_free_probep) blkid_probe b = blkid_new_probe(); + if (!b) + return log_oom(); + + errno = 0; + r = blkid_probe_set_device(b, block_fd, 0, 0); + if (r != 0) + return log_error_errno(errno_or_else(ENOMEM), "Failed to set up block device prober for '%s': %m", path); + + (void) blkid_probe_enable_superblocks(b, 1); + (void) blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE|BLKID_SUBLKS_LABEL); + (void) blkid_probe_enable_partitions(b, 1); + (void) blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); + + errno = 0; + r = blkid_do_safeprobe(b); + if (r == _BLKID_SAFEPROBE_ERROR) + return log_error_errno(errno_or_else(EIO), "Failed to probe block device of '%s': %m", path); + if (r == _BLKID_SAFEPROBE_AMBIGUOUS) + return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "Found multiple file system labels on block device '%s'.", path); + if (r == _BLKID_SAFEPROBE_NOT_FOUND) + return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "Found no file system label on block device '%s'.", path); + + assert(r == _BLKID_SAFEPROBE_FOUND); + + const char *v = NULL; + (void) blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, /* ret_len= */ NULL); + 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) { + v = NULL; + (void) blkid_probe_lookup_value(b, "PART_ENTRY_NAME", &v, /* ret_len= */ NULL); + + if (!streq(f->gpt_label, strempty(v))) + 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)); + } + + if (!sd_id128_is_null(f->gpt_type_uuid)) { + 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) + 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)); + + 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)); + } + + return 0; +} + static int validate_fields_check(int fd, const char *path, const ValidateFields *f) { int r; @@ -188,108 +309,13 @@ static int validate_fields_check(int fd, const char *path, const ValidateFields assert(path); assert(f); - if (!strv_isempty(f->mount_point)) { - bool good = false; + r = validate_mount_point(path, f); + if (r < 0) + return r; - STRV_FOREACH(i, f->mount_point) { - _cleanup_free_ char *jj = NULL; - const char *j; - - if (arg_root) { - jj = path_join(arg_root, *i); - if (!jj) - return log_oom(); - - j = jj; - } else - j = *i; - - if (path_equal(path, j)) { - good = true; - break; - } - } - - if (!good) { - _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); - } - } - - if (f->gpt_label || !sd_id128_is_null(f->gpt_type_uuid)) { - _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); - - _cleanup_(blkid_free_probep) blkid_probe b = blkid_new_probe(); - if (!b) - return log_oom(); - - errno = 0; - r = blkid_probe_set_device(b, block_fd, 0, 0); - if (r != 0) - return log_error_errno(errno_or_else(ENOMEM), "Failed to set up block device prober for '%s': %m", path); - - (void) blkid_probe_enable_superblocks(b, 1); - (void) blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE|BLKID_SUBLKS_LABEL); - (void) blkid_probe_enable_partitions(b, 1); - (void) blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); - - errno = 0; - r = blkid_do_safeprobe(b); - if (r == _BLKID_SAFEPROBE_ERROR) - return log_error_errno(errno_or_else(EIO), "Failed to probe block device of '%s': %m", path); - if (r == _BLKID_SAFEPROBE_AMBIGUOUS) - return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "Found multiple file system labels on block device '%s'.", path); - if (r == _BLKID_SAFEPROBE_NOT_FOUND) - return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "Found no file system label on block device '%s'.", path); - - assert(r == _BLKID_SAFEPROBE_FOUND); - - const char *v = NULL; - (void) blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, /* ret_len= */ NULL); - 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) { - v = NULL; - (void) blkid_probe_lookup_value(b, "PART_ENTRY_NAME", &v, /* ret_len= */ NULL); - - if (!streq(f->gpt_label, strempty(v))) - 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)); - } - - if (!sd_id128_is_null(f->gpt_type_uuid)) { - 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) - 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)); - - 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)); - } - } + r = validate_gpt_metadata(fd, path, f); + if (r < 0) + return r; log_info("File system '%s' passed validation constraints, proceeding.", path); return 0; From f7d326284410c197147301a471fd01472ca48847 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 14 May 2025 12:18:04 +0200 Subject: [PATCH 2/5] validatefs: properly authenticate all subordinate devices of DM devices 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 | 183 +++++++++++++++++++++++++----------- 1 file changed, 128 insertions(+), 55 deletions(-) diff --git a/src/validatefs/validatefs.c b/src/validatefs/validatefs.c index fa8784cdf5..8140167392 100644 --- a/src/validatefs/validatefs.c +++ b/src/validatefs/validatefs.c @@ -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) - 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)); + 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); - 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 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); + + return log_error_errno( + SYNTHETIC_ERRNO(EPERM), + "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; From 02347d24e4f961cd4d06e8244d31286856bfa59e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 14 May 2025 12:35:52 +0200 Subject: [PATCH 3/5] repart: initialize validatefs xattrs to list *all* verity subordinate partitions Now that we can actually list multiple gpt labels/type uuids in xattrs, let's start doing so. Fixes: #37157 --- src/repart/repart.c | 73 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index 8eaf4308f9..b7efa88e6f 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -5894,6 +5894,63 @@ static int set_default_subvolume(Partition *p, const char *root) { return 0; } +static int partition_acquire_sibling_labels(const Partition *p, char ***ret) { + assert(p); + assert(ret); + + _cleanup_strv_free_ char **l = NULL; + if (p->new_label) { + l = strv_new(p->new_label); + if (!l) + return log_oom(); + } + + FOREACH_ELEMENT(sibling, p->siblings) { + Partition *s = *sibling; + + if (!s || s == p || !s->new_label || strv_contains(l, s->new_label)) + continue; + + if (strv_extend(&l, s->new_label) < 0) + return log_oom(); + } + + strv_sort(l); /* bring into a systematic order to make things reproducible */ + + *ret = TAKE_PTR(l); + return 0; +} + +static int partition_acquire_sibling_uuids(const Partition *p, char ***ret) { + assert(p); + assert(ret); + + _cleanup_strv_free_ char **l = NULL; + l = strv_new(SD_ID128_TO_UUID_STRING(p->type.uuid)); + if (!l) + return log_oom(); + + FOREACH_ELEMENT(sibling, p->siblings) { + Partition *s = *sibling; + + if (!s || s == p) + continue; + + const char *u = SD_ID128_TO_UUID_STRING(s->type.uuid); + if (strv_contains(l, u)) + continue; + + if (strv_extend(&l, u) < 0) + return log_oom(); + } + + strv_sort(l); /* bring into a systematic order to make things reproducible */ + + *ret = TAKE_PTR(l); + return 0; +} + + static int do_make_validatefs_xattrs(const Partition *p, const char *root) { int r; @@ -5907,18 +5964,26 @@ static int do_make_validatefs_xattrs(const Partition *p, const char *root) { if (fd < 0) return log_error_errno(errno, "Failed to open root inode '%s': %m", root); - if (p->new_label) { - r = xsetxattr(fd, /* path= */ NULL, AT_EMPTY_PATH, "user.validatefs.gpt_label", p->new_label); + _cleanup_strv_free_ char **l = NULL; + r = partition_acquire_sibling_labels(p, &l); + if (r < 0) + return r; + if (!strv_isempty(l)) { + r = xsetxattr_strv(fd, /* path= */ NULL, AT_EMPTY_PATH, "user.validatefs.gpt_label", l); if (r < 0) return log_error_errno(r, "Failed to set 'user.validatefs.gpt_label' extended attribute: %m"); } + l = strv_free(l); - r = xsetxattr(fd, /* path= */ NULL, AT_EMPTY_PATH, "user.validatefs.gpt_type_uuid", SD_ID128_TO_UUID_STRING(p->type.uuid)); + r = partition_acquire_sibling_uuids(p, &l); + if (r < 0) + return r; + r = xsetxattr_strv(fd, /* path= */ NULL, AT_EMPTY_PATH, "user.validatefs.gpt_type_uuid", l); if (r < 0) return log_error_errno(r, "Failed to set 'user.validatefs.gpt_type_uuid' extended attribute: %m"); + l = strv_free(l); /* Prefer the data from MountPoint= if specified, otherwise use data we derive from the partition type */ - _cleanup_strv_free_ char **l = NULL; if (p->n_mountpoints > 0) { FOREACH_ARRAY(m, p->mountpoints, p->n_mountpoints) if (strv_extend(&l, m->where) < 0) From 778cfac5fba45b7ac828571704945b722b2fb718 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 14 May 2025 14:36:09 +0200 Subject: [PATCH 4/5] man: document that gpt-label/gpt-type uuid xattrs are now lists --- man/repart.d.xml | 12 ++++++------ man/systemd-validatefs@.service.xml | 15 +++++++++------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/man/repart.d.xml b/man/repart.d.xml index 27c78f63da..fde83b0978 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -962,12 +962,12 @@ AddValidateFS= - Takes a boolean argument. If enabled will set the user.validatefs.gpt_label, - user.validatefs.gpt_type_uuid and user.validatefs.mount_point - extended attributes on the root inode of the formatted file system to the partition label, partition - type UUID and the intended mount point for the partition. Defaults to on if - Format= is used and the specified argument is neither swap nor - vfat. + Takes a boolean argument. If enabled will set the + user.validatefs.gpt_label, user.validatefs.gpt_type_uuid and + user.validatefs.mount_point extended attributes on the root inode of the formatted + file system to the partition labels, partition type UUIDs and the intended mount point for the file + system. Defaults to on if Format= is used and the specified argument is neither + swap nor vfat. These extended attributes are read by systemd-validatefs@.service8 diff --git a/man/systemd-validatefs@.service.xml b/man/systemd-validatefs@.service.xml index f0c7aabc6e..17f3da987d 100644 --- a/man/systemd-validatefs@.service.xml +++ b/man/systemd-validatefs@.service.xml @@ -50,13 +50,16 @@ mounted to a location not matching any of the listed paths the validation check will fail. - user.validatefs.gpt_label: this extended attribute may contain a - free-form string. It is compared with the partition label string of the partition this file system is - located on, and if different the validation will fail. + user.validatefs.gpt_label: this extended attribute may contain a one + or more free-form strings, separated by NUL bytes. If set, all backing partitions of the file system + are checked against this list, and if any backing partition's label is not listed, the validation will + fail. Note that there may be multiple backing partition in case of Verity setups, which combines a data + and a hash partition. - user.validatefs.gpt_type_uuid: this extended attribute may contain a - GPT partition type UUID formatted as string. It is compared with the partition type UUID of the - partition this file system is located on, and if different the validation will fail. + user.validatefs.gpt_type_uuid: this extended attribute may contain + one or more GPT partition type UUIDs, formatted as string, separated by NUL bytes. As above, all + backing partitions of the file system are checked against this list, and if none is matching the + validation will fail. The systemd-validatefs@.service unit is automatically pulled into the initial From 61b15b78e5f3904491d2a023883d4823fd2c5393 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 14 May 2025 14:24:51 +0200 Subject: [PATCH 5/5] ci: extend validatefs testcase to validate verity partitions --- test/units/TEST-87-AUX-UTILS-VM.validatefs.sh | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test/units/TEST-87-AUX-UTILS-VM.validatefs.sh b/test/units/TEST-87-AUX-UTILS-VM.validatefs.sh index bee835d221..ede0d71019 100755 --- a/test/units/TEST-87-AUX-UTILS-VM.validatefs.sh +++ b/test/units/TEST-87-AUX-UTILS-VM.validatefs.sh @@ -40,6 +40,16 @@ cat > /tmp/validatefs-test/validatefs-usr.conf < /tmp/validatefs-test/validatefs-usr-verity.conf < /tmp/validatefs-test/validatefs-home.conf <