diff --git a/man/repart.d.xml b/man/repart.d.xml index 2f933efdae..27c78f63da 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 b7c8adf43f..397bb0d669 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 0526c4c803..a1236943ca 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))) {