introduce systemd-validatefs@.service that ensures file systems can only be used in the way they were intended (#36714)

If we have multiple trusted fs (i.e. luks or dm-verity) we generate via
repart at boot, we must make sure they cannot be "misappropriated", i.e.
used for a different mount they were intended for.

Hence, let's introduce "mount constraint" data (encoded in xattrs on the
root inode of the fs) that tells us where a file system has to be
mounted, and what the gpt partition metadata has to be for the fs to be
valid.

Inspired by this thread:
https://lists.freedesktop.org/archives/systemd-devel/2025-March/051244.html
This commit is contained in:
Yu Watanabe
2025-04-01 01:14:33 +09:00
committed by GitHub
22 changed files with 870 additions and 49 deletions
+7 -5
View File
@@ -143,10 +143,17 @@ Features:
waits and then reboots. Then use OnFailure=bsod.target from various jobs that
should result in system reboots, such as TPM tamper detection cases.
* honour validatefs xattrs in dissect-image.c too
* pcrextend: maybe add option to disable measurements entirely via kernel cmdline
* tpm2-setup: reboot if we detect SRK changed
* validatefs: validate more things: check if image id + os id of initrd match
target mount, so that we refuse early any attempts to boot into different
images with the wrong kernels. check min/max kernel version too. all encoded
via xattrs in the target fs.
* pcrextend: when we fail to measure, reboot the system (at least optionally).
important because certain measurements are supposed to "destroy" tpm object
access.
@@ -157,11 +164,6 @@ Features:
* cryptsetup: add boolean for disabling use of any password/recovery key slots.
* dissect: when mounting a file system, look into certain xattrs on / in them, and
if that exists, check if gpt partition flags + type uuid + uuid match the
data encoded therein, so that attackers cannot make us misuse our file
systems
* complete varlink introspection comments:
- io.systemd.BootControl
- io.systemd.Hostname
+24
View File
@@ -958,6 +958,30 @@
<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>
+1
View File
@@ -1164,6 +1164,7 @@ manpages = [
'ENABLE_UTMP'],
['systemd-user-sessions.service', '8', ['systemd-user-sessions'], 'HAVE_PAM'],
['systemd-userdbd.service', '8', ['systemd-userdbd'], 'ENABLE_USERDB'],
['systemd-validatefs@.service', '8', [], 'HAVE_BLKID'],
['systemd-vconsole-setup.service',
'8',
['systemd-vconsole-setup'],
+5
View File
@@ -281,6 +281,10 @@
automatically measured into PCR 15 on activation, via
<citerefentry><refentrytitle>systemd-pcrfs@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
<para>Mount constraint metadata contained in the file systems is validated by pulling in
<citerefentry><refentrytitle>systemd-validatefs@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
for generated mounts.</para>
<para><filename>systemd-gpt-auto-generator</filename> implements
<citerefentry><refentrytitle>systemd.generator</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
</refsect1>
@@ -375,6 +379,7 @@
<member><citerefentry><refentrytitle>systemd-fstab-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-cryptsetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-pcrfs@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-validatefs@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
<member><citerefentry project='die-net'><refentrytitle>cryptsetup</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
+111
View File
@@ -0,0 +1,111 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="systemd-validatefs_.service" conditional='HAVE_BLKID'
xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>systemd-validatefs@.service</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-validatefs@.service</refentrytitle>
<manvolnum>8</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-validatefs@.service</refname>
<refpurpose>Validate File System Mount Constraint Data</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><filename>systemd-validatefs@.service</filename></para>
<para><filename>/usr/lib/systemd/systemd-validatefs</filename> <optional><replaceable>DEVICE</replaceable></optional></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><filename>systemd-validatefs@.service</filename> is a system service template that can be
instantiated for newly established mount points. It reads file system mount constraint data from the file
system, and ensures the mount runtime setup matches it. If it doesn't the service fails, which effects an
immediate reboot.</para>
<para>This functionality is supposed to ensure that trusted file systems cannot be used in a different
context then what they were intended for. More specifically: in an
<citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
based environment the file systems to mount are largely auto-discovered based on (unprotected) GPT
partition table data. The mount constraint information can be used to validate the GPT partition data,
based on the (protected) file system contents.</para>
<para>Specifically, the mount constraints are encoded in the following extended attributes on the root
inode of the file systems:</para>
<orderedlist>
<listitem><para><varname>user.validatefs.mount_point</varname>: this extended attribute shall contain
one or more absolute, normalized paths, separated by NUL bytes. If set and the specified file system is
mounted to a location not matching any of the listed paths the validation check will
fail.</para></listitem>
<listitem><para><varname>user.validatefs.gpt_label</varname>: 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.</para></listitem>
<listitem><para><varname>user.validatefs.gpt_type_uuid</varname>: 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.</para></listitem>
</orderedlist>
<para>The <filename>systemd-validatefs@.service</filename> unit is automatically pulled into the initial
transaction by
<citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
for all file systems it discovers and generates mounts
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>
<title>Options</title>
<para>The <filename>/usr/lib/systemd/system-validatefs</filename> executable may also be invoked from the
command line, where it expects a path to a mount and the following options:</para>
<variablelist>
<varlistentry>
<term><option>--root=</option></term>
<listitem><para>Takes an absolute path or the special string <literal>auto</literal>. The specified
path if removed as prefix from the specified mount point argument before the validation. If set to
<literal>auto</literal> defaults to unspecified on the host and <filename>/sysroot/</filename> when
run in initrd context, in order to validate the mount constraint data relative to the future file
system root.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
</refsect1>
<refsect1>
<title>See Also</title>
<para><simplelist type="inline">
<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>
</refentry>
+16
View File
@@ -432,6 +432,22 @@
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
</varlistentry>
<varlistentry>
<term><option>x-systemd.validatefs</option></term>
<listitem><para>Validates mount constraint metadata of the mounted file system after mounting
it. This ensures the
<citerefentry><refentrytitle>systemd-validatefs@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
service is pulled in by the mount unit.</para>
<para>Note that this option can only be used in <filename>/etc/fstab</filename>, and will be ignored
when part of the <varname>Options=</varname> setting in a unit file. It is also implied for all
partitions discovered by
<citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term><option>x-systemd.rw-only</option></term>
+1
View File
@@ -2392,6 +2392,7 @@ subdir('src/update-done')
subdir('src/update-utmp')
subdir('src/user-sessions')
subdir('src/userdb')
subdir('src/validatefs')
subdir('src/varlinkctl')
subdir('src/vconsole')
subdir('src/veritysetup')
+1
View File
@@ -95,6 +95,7 @@
#define SPECIAL_GROWFS_ROOT_SERVICE "systemd-growfs-root.service"
#define SPECIAL_PCRFS_SERVICE "systemd-pcrfs@.service"
#define SPECIAL_PCRFS_ROOT_SERVICE "systemd-pcrfs-root.service"
#define SPECIAL_VALIDATEFS_SERVICE "systemd-validatefs@.service"
#define SPECIAL_HIBERNATE_RESUME_SERVICE "systemd-hibernate-resume.service"
/* Services systemd relies on */
+32
View File
@@ -13,11 +13,13 @@
#include "fd-util.h"
#include "macro.h"
#include "missing_syscall.h"
#include "nulstr-util.h"
#include "parse-util.h"
#include "sparse-endian.h"
#include "stat-util.h"
#include "stdio-util.h"
#include "string-util.h"
#include "strv.h"
#include "time-util.h"
#include "xattr-util.h"
@@ -189,6 +191,24 @@ int getxattr_at_bool(int fd, const char *path, const char *name, int at_flags) {
return parse_boolean(v);
}
int getxattr_at_strv(int fd, const char *path, const char *name, int at_flags, char ***ret_strv) {
int r;
_cleanup_free_ char *nulstr = NULL;
size_t nulstr_size = 0;
r = getxattr_at_malloc(fd, path, name, at_flags, &nulstr, &nulstr_size);
if (r < 0)
return r;
_cleanup_strv_free_ char **l = strv_parse_nulstr(nulstr, nulstr_size);
if (!l)
return -ENOMEM;
*ret_strv = TAKE_PTR(l);
return 0;
}
static int listxattr_pinned_internal(
int fd,
const char *path,
@@ -323,6 +343,18 @@ int xsetxattr_full(
return 0;
}
int xsetxattr_strv(int fd, const char *path, int at_flags, const char *name, char * const* l) {
int r;
_cleanup_free_ char *nulstr = NULL;
size_t size = 0;
r = strv_make_nulstr(l, &nulstr, &size);
if (r < 0)
return r;
return xsetxattr_full(fd, path, at_flags, name, nulstr, size, /* xattr_flags= */ 0);
}
int xremovexattr(int fd, const char *path, int at_flags, const char *name) {
int r;
+3
View File
@@ -20,6 +20,7 @@ static inline int fgetxattr_malloc(int fd, const char *name, char **ret, size_t
}
int getxattr_at_bool(int fd, const char *path, const char *name, int at_flags);
int getxattr_at_strv(int fd, const char *path, const char *name, int at_flags, char ***ret_strv);
int listxattr_at_malloc(int fd, const char *path, int at_flags, char **ret);
static inline int listxattr_malloc(const char *path, char **ret) {
@@ -49,6 +50,8 @@ static inline int xsetxattr(
return xsetxattr_full(fd, path, at_flags, name, value, SIZE_MAX, 0);
}
int xsetxattr_strv(int fd, const char *path, int at_flags, const char *name, char *const*l);
int xremovexattr(int fd, const char *path, int at_flags, const char *name);
int fd_setcrtime(int fd, usec_t usec);
+25 -12
View File
@@ -39,14 +39,15 @@
#include "volatile-util.h"
typedef enum MountPointFlags {
MOUNT_NOAUTO = 1 << 0,
MOUNT_NOFAIL = 1 << 1,
MOUNT_AUTOMOUNT = 1 << 2,
MOUNT_MAKEFS = 1 << 3,
MOUNT_GROWFS = 1 << 4,
MOUNT_RW_ONLY = 1 << 5,
MOUNT_PCRFS = 1 << 6,
MOUNT_QUOTA = 1 << 7,
MOUNT_NOAUTO = 1 << 0,
MOUNT_NOFAIL = 1 << 1,
MOUNT_AUTOMOUNT = 1 << 2,
MOUNT_MAKEFS = 1 << 3,
MOUNT_GROWFS = 1 << 4,
MOUNT_RW_ONLY = 1 << 5,
MOUNT_PCRFS = 1 << 6,
MOUNT_QUOTA = 1 << 7,
MOUNT_VALIDATEFS = 1 << 8,
} MountPointFlags;
typedef struct Mount {
@@ -249,9 +250,10 @@ static int add_swap(
return true;
}
log_debug("Found swap entry what=%s makefs=%s growfs=%s pcrfs=%s noauto=%s nofail=%s",
log_debug("Found swap entry what=%s makefs=%s growfs=%s pcrfs=%s validatefs=%s noauto=%s nofail=%s",
what,
yes_no(flags & MOUNT_MAKEFS), yes_no(flags & MOUNT_GROWFS), yes_no(flags & MOUNT_PCRFS),
yes_no(flags & MOUNT_MAKEFS), yes_no(flags & MOUNT_GROWFS),
yes_no(flags & MOUNT_PCRFS), yes_no(flags & MOUNT_VALIDATEFS),
yes_no(flags & MOUNT_NOAUTO), yes_no(flags & MOUNT_NOFAIL));
r = unit_name_from_path(what, ".swap", &name);
@@ -304,6 +306,8 @@ static int add_swap(
log_warning("%s: growing swap devices is currently unsupported.", what);
if (flags & MOUNT_PCRFS)
log_warning("%s: measuring swap devices is currently unsupported.", what);
if (flags & MOUNT_VALIDATEFS)
log_warning("%s: validating swap devices is currently unsupported.", what);
if (!(flags & MOUNT_NOAUTO)) {
r = generator_add_symlink(arg_dest, SPECIAL_SWAP_TARGET,
@@ -688,6 +692,12 @@ static int add_mount(
}
}
if (flags & MOUNT_VALIDATEFS) {
r = generator_hook_up_validatefs(dest, where, target_unit);
if (r < 0)
return r;
}
if (flags & MOUNT_QUOTA) {
r = generator_hook_up_quotacheck(dest, what, where, target_unit, fstype);
if (r < 0) {
@@ -842,6 +852,8 @@ static MountPointFlags fstab_options_to_flags(const char *options, bool is_swap)
flags |= MOUNT_GROWFS;
if (fstab_test_option(options, "x-systemd.pcrfs\0"))
flags |= MOUNT_PCRFS;
if (fstab_test_option(options, "x-systemd.validatefs\0"))
flags |= MOUNT_VALIDATEFS;
if (fstab_test_option(options, "usrquota\0" "grpquota\0" "quota\0" "usrjquota\0" "grpjquota\0" "prjquota\0"))
flags |= MOUNT_QUOTA;
if (fstab_test_yes_no_option(options, "noauto\0" "auto\0"))
@@ -970,9 +982,10 @@ static int parse_fstab_one(
free_and_replace(what, p);
}
log_debug("Found entry what=%s where=%s type=%s makefs=%s growfs=%s pcrfs=%s noauto=%s nofail=%s",
log_debug("Found entry what=%s where=%s type=%s makefs=%s growfs=%s pcrfs=%s validatefs=%s noauto=%s nofail=%s",
what, where, strna(fstype),
yes_no(flags & MOUNT_MAKEFS), yes_no(flags & MOUNT_GROWFS), yes_no(flags & MOUNT_PCRFS),
yes_no(flags & MOUNT_MAKEFS), yes_no(flags & MOUNT_GROWFS),
yes_no(flags & MOUNT_PCRFS), yes_no(flags & MOUNT_VALIDATEFS),
yes_no(flags & MOUNT_NOAUTO), yes_no(flags & MOUNT_NOFAIL));
bool is_sysroot = in_initrd() && path_equal(where, "/sysroot");
+34 -28
View File
@@ -38,6 +38,13 @@
#include "unit-name.h"
#include "virt.h"
typedef enum MountPointFlags {
MOUNT_RW = 1 << 0,
MOUNT_GROWFS = 1 << 1,
MOUNT_MEASURE = 1 << 2,
MOUNT_VALIDATEFS = 1 << 3,
} MountPointFlags;
static const char *arg_dest = NULL;
static bool arg_enabled = true;
static GptAutoRoot arg_auto_root = _GPT_AUTO_ROOT_INVALID;
@@ -57,9 +64,8 @@ static int add_cryptsetup(
const char *id,
const char *what,
const char *mount_opts,
bool rw,
MountPointFlags flags,
bool require,
bool measure,
char **ret_device) {
#if HAVE_LIBCRYPTSETUP
@@ -97,7 +103,7 @@ static int add_cryptsetup(
"After=%s\n",
d, d);
if (!rw) {
if (!FLAGS_SET(flags, MOUNT_RW)) {
options = strdup("read-only");
if (!options)
return log_oom();
@@ -109,7 +115,7 @@ static int add_cryptsetup(
if (!strextend_with_separator(&options, ",", "tpm2-device=auto"))
return log_oom();
if (measure) {
if (FLAGS_SET(flags, MOUNT_MEASURE)) {
/* We only measure the root volume key into PCR 15 if we are booted with sd-stub (i.e. in a
* UKI), and sd-stub measured the UKI. We do this in order not to step into people's own PCR
* assignment, under the assumption that people who are fine to use sd-stub with its PCR
@@ -178,9 +184,7 @@ static int add_mount(
const char *what,
const char *where,
const char *fstype,
bool rw,
bool growfs,
bool measure,
MountPointFlags flags,
const char *options,
const char *description,
const char *post) {
@@ -203,7 +207,7 @@ static int add_mount(
if (streq_ptr(fstype, "crypto_LUKS")) {
/* Mount options passed are determined by partition_pick_mount_options(), whose result
* is known to not contain timeout options. */
r = add_cryptsetup(id, what, /* mount_opts = */ NULL, rw, /* require= */ true, measure, &crypto_what);
r = add_cryptsetup(id, what, /* mount_opts = */ NULL, flags, /* require= */ true, &crypto_what);
if (r < 0)
return r;
@@ -270,13 +274,19 @@ static int add_mount(
if (r < 0)
return log_error_errno(r, "Failed to write unit %s: %m", unit);
if (growfs) {
if (FLAGS_SET(flags, MOUNT_VALIDATEFS)) {
r = generator_hook_up_validatefs(arg_dest, where, post);
if (r < 0)
return r;
}
if (FLAGS_SET(flags, MOUNT_GROWFS)) {
r = generator_hook_up_growfs(arg_dest, where, post);
if (r < 0)
return r;
}
if (measure) {
if (FLAGS_SET(flags, MOUNT_MEASURE)) {
r = generator_hook_up_pcrfs(arg_dest, where, post);
if (r < 0)
return r;
@@ -353,9 +363,10 @@ static int add_partition_mount(
p->node,
where,
p->fstype,
p->rw,
p->growfs,
/* measure= */ STR_IN_SET(id, "root", "var"), /* by default measure rootfs and /var, since they contain the "identity" of the system */
(p->rw ? MOUNT_RW : 0) |
MOUNT_VALIDATEFS |
(p->growfs ? MOUNT_GROWFS : 0) |
(STR_IN_SET(id, "root", "var") ? MOUNT_MEASURE : 0), /* by default measure rootfs and /var, since they contain the "identity" of the system */
options,
description,
SPECIAL_LOCAL_FS_TARGET);
@@ -383,7 +394,7 @@ static int add_partition_swap(DissectedPartition *p) {
}
if (streq_ptr(p->fstype, "crypto_LUKS")) {
r = add_cryptsetup("swap", p->node, /* mount_opts = */ NULL, /* rw= */ true, /* require= */ true, /* measure= */ false, &crypto_what);
r = add_cryptsetup("swap", p->node, /* mount_opts = */ NULL, MOUNT_RW, /* require= */ true, &crypto_what);
if (r < 0)
return r;
what = crypto_what;
@@ -427,8 +438,7 @@ static int add_automount(
const char *what,
const char *where,
const char *fstype,
bool rw,
bool growfs,
MountPointFlags flags,
const char *options,
const char *description,
usec_t timeout) {
@@ -445,12 +455,10 @@ static int add_automount(
what,
where,
fstype,
rw,
growfs,
/* measure= */ false,
flags,
options,
description,
NULL);
/* post= */ NULL);
if (r < 0)
return r;
@@ -508,8 +516,7 @@ static int add_partition_xbootldr(DissectedPartition *p) {
p->node,
"/boot",
p->fstype,
/* rw= */ true,
/* growfs= */ false,
MOUNT_RW,
options,
"Boot Loader Partition",
LOADER_PARTITION_IDLE_USEC);
@@ -571,8 +578,7 @@ static int add_partition_esp(DissectedPartition *p, bool has_xbootldr) {
p->node,
esp_path,
p->fstype,
/* rw= */ true,
/* growfs= */ false,
MOUNT_RW,
options,
"EFI System Partition Automount",
LOADER_PARTITION_IDLE_USEC);
@@ -665,7 +671,7 @@ static int add_root_cryptsetup(void) {
bdev = "/dev/gpt-auto-root-luks-ignore-factory-reset";
}
return add_cryptsetup("root", bdev, arg_root_options, /* rw= */ true, /* require= */ false, /* measure= */ true, NULL);
return add_cryptsetup("root", bdev, arg_root_options, MOUNT_RW|MOUNT_MEASURE, /* require= */ false, NULL);
#else
return 0;
#endif
@@ -752,9 +758,9 @@ static int add_root_mount(void) {
bdev,
in_initrd() ? "/sysroot" : "/",
arg_root_fstype,
/* rw= */ arg_root_rw > 0,
/* growfs= */ false,
/* measure= */ true,
(arg_root_rw > 0 ? MOUNT_RW : 0) |
(in_initrd() ? MOUNT_VALIDATEFS : 0) |
MOUNT_MEASURE,
options,
"Root Partition",
in_initrd() ? SPECIAL_INITRD_ROOT_FS_TARGET : SPECIAL_LOCAL_FS_TARGET);
+75 -2
View 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))) {
+37
View File
@@ -764,6 +764,43 @@ int generator_hook_up_pcrfs(
return generator_add_symlink_full(dir, where_unit, "wants", pcrfs_unit_path, instance);
}
int generator_hook_up_validatefs(
const char *dir,
const char *where,
const char *target) {
_cleanup_free_ char *where_unit = NULL, *instance = NULL;
const char *validatefs_unit, *validatefs_unit_path;
int r;
assert(dir);
assert(where);
/* never hook this in for the actual root fs, because it's too late then, we already are running from
* the root fs, it makes no sense to validate it anymore */
if (empty_or_root(where))
return 0;
r = unit_name_from_path(where, ".mount", &where_unit);
if (r < 0)
return log_error_errno(r, "Failed to make unit name from path '%s': %m", where);
validatefs_unit = SPECIAL_VALIDATEFS_SERVICE;
validatefs_unit_path = SYSTEM_DATA_UNIT_DIR "/" SPECIAL_VALIDATEFS_SERVICE;
r = unit_name_path_escape(where, &instance);
if (r < 0)
return log_error_errno(r, "Failed to escape path '%s': %m", where);
if (target) {
r = generator_add_ordering(dir, target, "After", validatefs_unit, instance);
if (r < 0)
return r;
}
return generator_add_symlink_full(dir, where_unit, "wants", validatefs_unit_path, instance);
}
int generator_hook_up_quotacheck(
const char *dir,
const char *what,
+4
View File
@@ -67,6 +67,10 @@ int generator_hook_up_pcrfs(
const char *dir,
const char *where,
const char *target);
int generator_hook_up_validatefs(
const char *dir,
const char *where,
const char *target);
int generator_hook_up_quotacheck(
const char *dir,
const char *what,
+14
View File
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
executables += [
libexec_template + {
'name' : 'systemd-validatefs',
'conditions' : [
'HAVE_BLKID',
],
'sources' : files('validatefs.c'),
'dependencies' : [
libblkid,
],
},
]
+331
View File
@@ -0,0 +1,331 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <getopt.h>
#include "blkid-util.h"
#include "blockdev-util.h"
#include "build.h"
#include "chase.h"
#include "device-util.h"
#include "fd-util.h"
#include "initrd-util.h"
#include "main-func.h"
#include "mountpoint-util.h"
#include "parse-argument.h"
#include "path-util.h"
#include "pretty-print.h"
#include "string-util.h"
#include "utf8.h"
#include "xattr-util.h"
static char *arg_target = NULL;
static char *arg_root = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_target, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
static int help(void) {
int r;
_cleanup_free_ char *link = NULL;
r = terminal_urlify_man("systemd-validatefs@.service", "8", &link);
if (r < 0)
return log_oom();
printf("%1$s [OPTIONS...] /path/to/mountpoint\n"
"\n%3$sCheck file system validation constraints.%4$s\n"
" -h --help Show this help and exit\n"
" --version Print version string and exit\n"
" --root=PATH|auto Operate relative to the specified path\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
ansi_highlight(),
ansi_normal());
return 0;
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_ROOT,
};
int c, r;
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version" , no_argument, NULL, ARG_VERSION },
{ "root", required_argument, NULL, ARG_ROOT },
{}
};
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
switch (c) {
case 'h':
return help();
case ARG_VERSION:
return version();
case ARG_ROOT:
if (streq(optarg, "auto")) {
arg_root = mfree(arg_root);
if (in_initrd()) {
arg_root = strdup("/sysroot");
if (!arg_root)
return log_oom();
}
break;
}
if (!path_is_absolute(optarg))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--root= argument must be 'auto' or absolute path, got: %s", optarg);
r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root);
if (r < 0)
return r;
break;
case '?':
return -EINVAL;
default:
assert_not_reached();
}
if (optind + 1 != argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"%s excepts exactly one argument (the mount point).",
program_invocation_short_name);
arg_target = strdup(argv[optind]);
if (!arg_target)
return log_oom();
if (arg_root && !path_startswith(arg_target, arg_root))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path '%s' does not start with specified root '%s', refusing.", arg_target, arg_root);
return 1;
}
typedef struct ValidateFields {
sd_id128_t gpt_type_uuid;
char *gpt_label;
char **mount_point;
} ValidateFields;
static void validate_fields_done(ValidateFields *f) {
assert(f);
free(f->gpt_label);
strv_free(f->mount_point);
}
static int validate_fields_read(int fd, ValidateFields *ret) {
_cleanup_(validate_fields_done) ValidateFields f = {};
int r;
assert(fd >= 0);
assert(ret);
_cleanup_free_ char *t = NULL;
r = getxattr_at_malloc(fd, /* path= */ NULL, "user.validatefs.gpt_type_uuid", AT_EMPTY_PATH, &t, /* ret_size= */ NULL);
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);
}
r = getxattr_at_malloc(fd, /* path= */ NULL, "user.validatefs.gpt_label", AT_EMPTY_PATH, &f.gpt_label, /* ret_size= */ NULL);
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.");
_cleanup_strv_free_ char **l = NULL;
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))
return log_error_errno(r, "Failed to read 'user.validatefs.mount_point' xattr: %m");
} else {
STRV_FOREACH(i, l)
if (!utf8_is_valid(*i) ||
string_has_cc(*i, /* ok= */ NULL) ||
!path_is_absolute(*i) ||
!path_is_normalized(*i))
return log_error_errno(
SYNTHETIC_ERRNO(EINVAL),
"Path listed in extended attribute 'user.validatefs.mount_point' is not a valid, normalized, absolute path or contains invalid characters, refusing: %s", *i);
f.mount_point = TAKE_PTR(l);
}
r = !sd_id128_is_null(f.gpt_type_uuid) || f.gpt_label || !strv_isempty(f.mount_point);
*ret = TAKE_STRUCT(f);
return r;
}
static int validate_fields_check(int fd, const char *path, const ValidateFields *f) {
int r;
assert(fd >= 0);
assert(path);
assert(f);
if (!strv_isempty(f->mount_point)) {
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);
}
}
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));
}
}
log_info("File system '%s' passed validation constraints, proceeding.", path);
return 0;
}
static int run(int argc, char *argv[]) {
int r;
log_setup();
r = parse_argv(argc, argv);
if (r <= 0)
return r;
_cleanup_free_ char *resolved = NULL;
_cleanup_close_ int target_fd = chase_and_open(arg_target, arg_root, CHASE_MUST_BE_DIRECTORY, O_DIRECTORY|O_CLOEXEC, &resolved);
if (target_fd < 0)
return log_error_errno(target_fd, "Failed to open directory '%s': %m", arg_target);
r = is_mount_point_at(target_fd, /* filename= */ NULL, /* flags= */ 0);
if (r < 0)
return log_error_errno(r, "Failed to determine whether '%s' is a mount point: %m", resolved);
if (!r)
return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Directory '%s' is not a mount point.", resolved);
_cleanup_(validate_fields_done) ValidateFields f = {};
r = validate_fields_read(target_fd, &f);
if (r < 0)
return r;
if (r == 0) {
log_info("File system '%s' has no validation constraints set, not validating.", resolved);
return EXIT_SUCCESS;
}
r = validate_fields_check(target_fd, resolved, &f);
if (r < 0)
return r;
return EXIT_SUCCESS;
}
DEFINE_MAIN_FUNCTION(run);
+2 -2
View File
@@ -17,8 +17,8 @@ if [[ -v ASAN_OPTIONS || -v UBSAN_OPTIONS ]]; then
ARGS+=(--profile=trusted)
fi
unsquashfs -dest /tmp/minimal_0 /usr/share/minimal_0.raw
unsquashfs -dest /tmp/minimal_1 /usr/share/minimal_1.raw
unsquashfs -no-xattrs -d /tmp/minimal_0 /usr/share/minimal_0.raw
unsquashfs -no-xattrs -d /tmp/minimal_1 /usr/share/minimal_1.raw
portablectl "${ARGS[@]}" attach --copy=symlink --now --runtime /tmp/minimal_0 minimal-app0
@@ -46,6 +46,7 @@ FSTAB_GENERAL=(
"/dev/test22 /initrd/mount ext2 defaults,x-systemd.rw-only,x-initrd.mount 0 1"
"/dev/test23 /initrd/mount/nofail ext3 defaults,nofail,x-initrd.mount 0 1"
"/dev/test24 /initrd/mount/deps ext4 x-initrd.mount,x-systemd.before=early.service,x-systemd.after=late.service 0 1"
"/dev/test25 /x-systemd.validatefs xfs x-systemd.validatefs 0 0"
# Incomplete, but valid entries
"/dev/incomplete1 /incomplete1"
@@ -282,6 +283,9 @@ check_fstab_mount_units() {
elif [[ "$opt" == x-systemd.growfs ]]; then
service="$(systemd-escape --template=systemd-growfs@.service --path "$where")"
link_endswith "$out_dir/${unit}.wants/$service" "/lib/systemd/system/systemd-growfs@.service"
elif [[ "$opt" == x-systemd.validatefs ]]; then
service="$(systemd-escape --template=systemd-validatefs@.service --path "$where")"
link_endswith "$out_dir/${unit}.wants/$service" "/lib/systemd/system/systemd-validatefs@.service"
elif [[ "$opt" == bg ]] && [[ "$fstype" =~ ^(nfs|nfs4)$ ]]; then
# We "convert" nfs bg mounts to fg, so we can do the job-control
# ourselves
+116
View File
@@ -0,0 +1,116 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -eux
set -o pipefail
if ! command -v systemd-repart >/dev/null; then
echo "no systemd-repart"
return
fi
if ! test -x /usr/lib/systemd/systemd-validatefs ; then
echo "no systemd-validatefs"
return
fi
export SYSTEMD_LOG_LEVEL=debug
export PAGER=cat
at_exit() {
set +e
rm -rf /tmp/validatefs-test/
rm -f /var/tmp/validatefs-test.raw
systemd-dissect --umount --rmdir /tmp/validatefs-test.mount
umount /tmp/validatefs-test.fake
rmdir /tmp/validatefs-test.fake
}
trap at_exit EXIT
mkdir /tmp/validatefs-test
cat > /tmp/validatefs-test/validatefs-root.conf <<EOF
[Partition]
Type=root
Label=kromm
Format=ext4
EOF
cat > /tmp/validatefs-test/validatefs-usr.conf <<EOF
[Partition]
Type=usr
Label=plisch
Format=ext4
EOF
cat > /tmp/validatefs-test/validatefs-home.conf <<EOF
[Partition]
Type=home
Label=rupft
Format=ext4
EOF
cat > /tmp/validatefs-test/validatefs-esp.conf <<EOF
[Partition]
Type=esp
Label=fumm
Format=vfat
EOF
cat > /tmp/validatefs-test/validatefs-generic.conf <<EOF
[Partition]
Label=qnurx
Type=linux-generic
MountPoint=/somewhere/else
Format=ext4
EOF
systemd-repart --dry-run=no --empty=create --size=256M --definitions=/tmp/validatefs-test /var/tmp/validatefs-test.raw
systemd-dissect --mount --mkdir /var/tmp/validatefs-test.raw /tmp/validatefs-test.mount
getfattr --dump /tmp/validatefs-test.mount/
getfattr --dump /tmp/validatefs-test.mount/ | grep -q user.validatefs.gpt_type_uuid=
getfattr --dump /tmp/validatefs-test.mount/ | grep -q user.validatefs.gpt_label=\"kromm\"
getfattr --dump /tmp/validatefs-test.mount/ | grep -q user.validatefs.mount_point=\"/\"
/usr/lib/systemd/systemd-validatefs --root=/tmp/validatefs-test.mount /tmp/validatefs-test.mount/
(! /usr/lib/systemd/systemd-validatefs /tmp/validatefs-test.mount/ )
getfattr --dump /tmp/validatefs-test.mount/usr
getfattr --dump /tmp/validatefs-test.mount/usr | grep -q user.validatefs.gpt_type_uuid=
getfattr --dump /tmp/validatefs-test.mount/usr | grep -q user.validatefs.gpt_label=\"plisch\"
getfattr --dump /tmp/validatefs-test.mount/usr | grep -q user.validatefs.mount_point=\"/usr\"
/usr/lib/systemd/systemd-validatefs --root=/tmp/validatefs-test.mount /tmp/validatefs-test.mount/usr
(! /usr/lib/systemd/systemd-validatefs /tmp/validatefs-test.mount/usr )
getfattr --dump /tmp/validatefs-test.mount/home
getfattr --dump /tmp/validatefs-test.mount/home | grep -q user.validatefs.gpt_type_uuid=
getfattr --dump /tmp/validatefs-test.mount/home | grep -q user.validatefs.gpt_label=\"rupft\"
getfattr --dump /tmp/validatefs-test.mount/home | grep -q user.validatefs.mount_point=\"/home\"
/usr/lib/systemd/systemd-validatefs --root=/tmp/validatefs-test.mount /tmp/validatefs-test.mount/home
(! /usr/lib/systemd/systemd-validatefs /tmp/validatefs-test.mount/home )
getfattr --dump /tmp/validatefs-test.mount/efi
(! getfattr --dump /tmp/validatefs-test.mount/efi | grep -q user.validatefs.gpt_type_uuid= )
(! getfattr --dump /tmp/validatefs-test.mount/efi | grep -q user.validatefs.gpt_label= )
(! getfattr --dump /tmp/validatefs-test.mount/efi | grep -q user.validatefs.mount_point= )
/usr/lib/systemd/systemd-validatefs --root=/tmp/validatefs-test.mount /tmp/validatefs-test.mount/efi
/usr/lib/systemd/systemd-validatefs /tmp/validatefs-test.mount/efi
# the generic one we must mount by hand
mkdir -p /tmp/validatefs-test.mount/somewhere/else
mount /dev/disk/by-label/qnurx /tmp/validatefs-test.mount/somewhere/else
getfattr --dump /tmp/validatefs-test.mount/somewhere/else
/usr/lib/systemd/systemd-validatefs --root=/tmp/validatefs-test.mount /tmp/validatefs-test.mount/somewhere/else
# Set up a fake mount point with incorrect data to validate a failure
mkdir /tmp/validatefs-test.fake
mount --bind /tmp/validatefs-test.fake /tmp/validatefs-test.fake
/usr/lib/systemd/systemd-validatefs /tmp/validatefs-test.fake
if setfattr -n "user.validatefs.mount_point" -v "/foo\000/bar\000/tmp/validatefs-test.fake\000/waldo" /tmp/validatefs-test.fake ; then
# xattrs on tmpfs are only available starting with kernel 6.6, hence handle setfattr failures gracefully
/usr/lib/systemd/systemd-validatefs /tmp/validatefs-test.fake
setfattr -n "user.validatefs.mount_point" -v "/knurz/schnurz\000/foo/bar/mor\000/end" /tmp/validatefs-test.fake
(! /usr/lib/systemd/systemd-validatefs /tmp/validatefs-test.fake )
fi
+4
View File
@@ -849,6 +849,10 @@ units = [
'file' : 'systemd-nsresourced.socket',
'conditions' : ['ENABLE_NSRESOURCED'],
},
{
'file' : 'systemd-validatefs@.service.in',
'conditions' : ['HAVE_BLKID'],
},
{
'file' : 'systemd-vconsole-setup.service.in',
'conditions' : ['ENABLE_VCONSOLE'],
+23
View File
@@ -0,0 +1,23 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Validate File System Mount Constraints of %f
Documentation=man:systemd-validatefs@.service(8)
DefaultDependencies=no
BindsTo=%i.mount
Conflicts=shutdown.target
After=%i.mount
Before=shutdown.target systemd-pcrfs@%i.service systemd-quotacheck@%i.service systemd-growfs@%i.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart={{LIBEXECDIR}}/systemd-validatefs --root=auto %f
FailureAction=reboot-immediate