From: Lennart Poettering Date: Wed, 12 Mar 2025 08:50:20 +0000 (+0100) Subject: repart: automatically generate validatefs xattrs X-Git-Tag: v258-rc1~983^2~3 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=85afe4760baf7e5c77fce414327933f6e6c2a2af;p=thirdparty%2Fsystemd.git repart: automatically generate validatefs xattrs Let's automatically generate validatefs xattrs by default, that encode the intended use of partitions. This defaults to on, since the structure of repart definition files tells us enough on use for this to be safe. There's an option however, to turn this off. --- diff --git a/man/repart.d.xml b/man/repart.d.xml index 2f933efdae7..27c78f63dad 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -958,6 +958,30 @@ + + + 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. + + These extended attributes are read by + systemd-validatefs@.service8 + and may encode constraints on mounted file systems that must be fulfilled for the system to + successfully boot. This is particular important in + systemd-gpt-auto-generator8 + scenarios, which puts together the mount hierarchy from untrusted data from the GPT partition + table. As these extended attributes are stored inside the file system, they are typically + authenticated as part of the file system (assuming it is contained in protected volume; i.e. LUKS or + dm-verity), and hence may be used to securely validate the matching partition table fields. + + + + diff --git a/man/systemd-validatefs@.service.xml b/man/systemd-validatefs@.service.xml index b7c8adf43f3..397bb0d6690 100644 --- a/man/systemd-validatefs@.service.xml +++ b/man/systemd-validatefs@.service.xml @@ -66,6 +66,11 @@ for. systemd-fstab-generator8 will do this for all mounts with the mount option in /etc/fstab. + + The + systemd-repart8 tool + generates these extended attributes automatically for the file systems it puts together, which may be + controlled with the AddValidateFS= configuration option. @@ -99,6 +104,7 @@ systemd1 systemd-gpt-auto-generator8 systemd-fstab-generator8 + systemd-repart8 diff --git a/src/repart/repart.c b/src/repart/repart.c index 0526c4c803b..a1236943cad 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -80,6 +80,7 @@ #include "tpm2-util.h" #include "user-util.h" #include "utf8.h" +#include "xattr-util.h" /* If not configured otherwise use a minimal partition size of 10M */ #define DEFAULT_MIN_SIZE (10ULL*1024ULL*1024ULL) @@ -396,6 +397,8 @@ typedef struct Partition { char *compression; char *compression_level; + int add_validatefs; + uint64_t gpt_flags; int no_auto; int read_only; @@ -578,6 +581,7 @@ static Partition *partition_new(void) { .growfs = -1, .verity_data_block_size = UINT64_MAX, .verity_hash_block_size = UINT64_MAX, + .add_validatefs = -1, }; return p; @@ -689,6 +693,7 @@ static void partition_foreignize(Partition *p) { p->read_only = -1; p->growfs = -1; p->verity = VERITY_OFF; + p->add_validatefs = false; partition_mountpoint_free_many(p->mountpoints, p->n_mountpoints); p->mountpoints = NULL; @@ -2337,10 +2342,23 @@ static int partition_finalize_fstype(Partition *p, const char *path) { return free_and_strdup_warn(&p->format, v); } +static bool partition_add_validatefs(const Partition *p) { + assert(p); + + if (p->add_validatefs >= 0) + return p->add_validatefs; + + return p->format && !STR_IN_SET(p->format, "swap", "vfat"); +} + static bool partition_needs_populate(const Partition *p) { assert(p); assert(!p->supplement_for || !p->suppressing); /* Avoid infinite recursion */ - return !strv_isempty(p->copy_files) || !strv_isempty(p->make_directories) || !strv_isempty(p->make_symlinks) || + + return !strv_isempty(p->copy_files) || + !strv_isempty(p->make_directories) || + !strv_isempty(p->make_symlinks) || + partition_add_validatefs(p) || (p->suppressing && partition_needs_populate(p->suppressing)); } @@ -2383,6 +2401,7 @@ static int partition_read_definition(Partition *p, const char *path, const char { "Partition", "Compression", config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, &p->compression }, { "Partition", "CompressionLevel", config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, &p->compression_level }, { "Partition", "SupplementFor", config_parse_string, 0, &p->supplement_for_name }, + { "Partition", "AddValidateFS", config_parse_tristate, 0, &p->add_validatefs }, {} }; _cleanup_free_ char *filename = NULL; @@ -5866,6 +5885,53 @@ static int set_default_subvolume(Partition *p, const char *root) { return 0; } +static int do_make_validatefs_xattrs(const Partition *p, const char *root) { + int r; + + assert(p); + assert(root); + + if (!partition_add_validatefs(p)) + return 0; + + _cleanup_close_ int fd = open(root, O_DIRECTORY|O_CLOEXEC); + 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); + if (r < 0) + return log_error_errno(r, "Failed to set 'user.validatefs.gpt_label' extended attribute: %m"); + } + + r = xsetxattr(fd, /* path= */ NULL, AT_EMPTY_PATH, "user.validatefs.gpt_type_uuid", SD_ID128_TO_UUID_STRING(p->type.uuid)); + if (r < 0) + return log_error_errno(r, "Failed to set 'user.validatefs.gpt_type_uuid' extended attribute: %m"); + + /* 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) + return log_oom(); + } else { + const char *m = gpt_partition_type_mountpoint_nulstr(p->type); + if (m) { + l = strv_split_nulstr(m); + if (!l) + return log_oom(); + } + } + + if (!strv_isempty(l)) { + r = xsetxattr_strv(fd, /* path= */ NULL, AT_EMPTY_PATH, "user.validatefs.mount_point", l); + if (r < 0) + return log_error_errno(r, "Failed to set 'user.validatefs.mount_point' extended attribute: %m"); + } + + return 0; +} + static int partition_populate_directory(Context *context, Partition *p, char **ret) { _cleanup_(rm_rf_physical_and_freep) char *root = NULL; const char *vt; @@ -5899,6 +5965,10 @@ static int partition_populate_directory(Context *context, Partition *p, char **r if (r < 0) return r; + r = do_make_validatefs_xattrs(p, root); + if (r < 0) + return r; + log_info("Ready to populate %s filesystem.", p->format); *ret = TAKE_PTR(root); @@ -5942,6 +6012,9 @@ static int partition_populate_filesystem(Context *context, Partition *p, const c if (do_make_symlinks(p, fs) < 0) _exit(EXIT_FAILURE); + if (do_make_validatefs_xattrs(p, fs) < 0) + _exit(EXIT_FAILURE); + if (make_subvolumes_read_only(p, fs) < 0) _exit(EXIT_FAILURE); @@ -6080,7 +6153,7 @@ static int context_mkfs(Context *context) { log_info("Formatting future partition %" PRIu64 ".", p->partno); /* If we're not writing to a loop device or if we're populating a read-only filesystem, we - * have to populate using the filesystem's mkfs's --root (or equivalent) option. To do that, + * have to populate using the filesystem's mkfs's --root= (or equivalent) option. To do that, * we need to set up the final directory tree beforehand. */ if (partition_needs_populate(p) && (!t->loop || fstype_is_ro(p->format))) {