]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
repart: automatically generate validatefs xattrs
authorLennart Poettering <lennart@poettering.net>
Wed, 12 Mar 2025 08:50:20 +0000 (09:50 +0100)
committerLennart Poettering <lennart@poettering.net>
Mon, 31 Mar 2025 13:14:45 +0000 (15:14 +0200)
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.

man/repart.d.xml
man/systemd-validatefs@.service.xml
src/repart/repart.c

index 2f933efdae79105f8855c0d9a608ae293b68ec83..27c78f63dad2b4b95c69f1339cf964e7af29b568 100644 (file)
 
         <xi:include href="version-info.xml" xpointer="v257"/></listitem>
       </varlistentry>
+
+      <varlistentry>
+        <term><varname>AddValidateFS=</varname></term>
+
+        <listitem><para>Takes a boolean argument. If enabled will set the <varname>user.validatefs.gpt_label</varname>,
+        <varname>user.validatefs.gpt_type_uuid</varname> and <varname>user.validatefs.mount_point</varname>
+        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
+        <varname>Format=</varname> is used and the specified argument is neither <literal>swap</literal> nor
+        <literal>vfat</literal>.</para>
+
+        <para>These extended attributes are read by
+        <citerefentry><refentrytitle>systemd-validatefs@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+        and may encode constraints on mounted file systems that must be fulfilled for the system to
+        successfully boot. This is particular important in
+        <citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+        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.</para>
+
+        <xi:include href="version-info.xml" xpointer="v258"/></listitem>
+      </varlistentry>
+
     </variablelist>
   </refsect1>
 
index b7c8adf43f329b6ef89e2ea2c47c20f3f1321fa1..397bb0d6690aa8efb88a3095c5c1c74de385fe79 100644 (file)
     for. <citerefentry><refentrytitle>systemd-fstab-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
     will do this for all mounts with the <option>x-systemd.validatefs</option> mount option in
     <filename>/etc/fstab</filename>.</para>
+
+    <para>The
+    <citerefentry><refentrytitle>systemd-repart</refentrytitle><manvolnum>8</manvolnum></citerefentry> tool
+    generates these extended attributes automatically for the file systems it puts together, which may be
+    controlled with the <varname>AddValidateFS=</varname> configuration option.</para>
   </refsect1>
 
   <refsect1>
       <member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
       <member><citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
       <member><citerefentry><refentrytitle>systemd-fstab-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
+      <member><citerefentry><refentrytitle>systemd-repart</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
     </simplelist></para>
   </refsect1>
 
index 0526c4c803bdf4e053d8d517f4462527769f8bdd..a1236943cad79c2ba251a5e62c974b8198b89056 100644 (file)
@@ -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))) {