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:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
@@ -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))) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
},
|
||||
]
|
||||
@@ -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);
|
||||
@@ -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
@@ -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
|
||||
@@ -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'],
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user