mirror of
https://github.com/morgan9e/systemd
synced 2026-04-14 00:14:32 +09:00
udev: new "image_dissect" builtin that can be used to generate symlinks based on partition designators (#36631)
This replaces #36209 and implements this via a new builtin, rather than by shelling out to systemd-dissect. Replaces: #36209 Replaces: #28894 Fixes: #34319 Fixes: #33453 Fixes: #27897 Fixes: #18035
This commit is contained in:
28
TODO
28
TODO
@@ -126,6 +126,8 @@ Deprecations and removals:
|
||||
* In v260: remove support for deprecated FactoryReset EFI variable in
|
||||
systemd-repart, replaced by FactoryResetRequest.
|
||||
|
||||
* Consider removing root=gpt-auto, and push people to use root=dissect instead.
|
||||
|
||||
Features:
|
||||
|
||||
* maybe replace nss-machines with logic in networkd that registers records with
|
||||
@@ -163,6 +165,8 @@ Features:
|
||||
the PCR then also reboot.
|
||||
|
||||
* cryptsetup: add boolean for disabling use of any password/recovery key slots.
|
||||
(i.e. that we can operate in a tpm-only mode, and thus protect us from rogue
|
||||
root disks)
|
||||
|
||||
* complete varlink introspection comments:
|
||||
- io.systemd.BootControl
|
||||
@@ -176,12 +180,6 @@ Features:
|
||||
- io.systemd.oom
|
||||
- io.systemd.sysext
|
||||
|
||||
* dissect: instead of searching for root and /usr partitions first, look for
|
||||
verity signature partitions first instead, then match up what we find with
|
||||
locally available keys, and then use first that works.
|
||||
|
||||
* gpt-auto-root doesn't take image policy into account.
|
||||
|
||||
* maybe define a /etc/machine-info field for the ANSI color to associate with a
|
||||
hostname. Then use it for the shell prompt to highlight the hostname. If no
|
||||
color is explicitly set, hash a color automatically from the hostname as a
|
||||
@@ -1177,10 +1175,6 @@ Features:
|
||||
* consider adding a new partition type, just for /opt/ for usage in system
|
||||
extensions
|
||||
|
||||
* gpt-auto-discovery: also use the pkcs7 signature stuff, and pass signature to
|
||||
kernel. So far we only did this for the various --image= switches, but not
|
||||
for the root fs or /usr/.
|
||||
|
||||
* dissection policy should enforce that unlocking can only take place by
|
||||
certain means, i.e. only via pw, only via tpm2, or only via fido, or a
|
||||
combination thereof.
|
||||
@@ -1345,9 +1339,6 @@ Features:
|
||||
* chase(): take inspiration from path_extract_filename() and return
|
||||
O_DIRECTORY if input path contains trailing slash.
|
||||
|
||||
* chase(): refuse resolution if trailing slash is specified on input,
|
||||
but final node is not a directory
|
||||
|
||||
* document in boot loader spec that symlinks in XBOOTLDR/ESP are not OK even if
|
||||
non-VFAT fs is used.
|
||||
|
||||
@@ -1667,12 +1658,6 @@ Features:
|
||||
data in the image, make sure the image filename actually matches this, so
|
||||
that images cannot be misused.
|
||||
|
||||
* New udev block device symlink names:
|
||||
/dev/disk/by-parttypelabel/<pttype>-<ptlabel>. Use case: if pt label is used
|
||||
as partition image version string, this is a safe way to reference a specific
|
||||
version of a specific partition type, in particular where related partitions
|
||||
are processed (e.g. verity + rootfs both named "LennartOS_0.7").
|
||||
|
||||
* sysupdate:
|
||||
- add fuzzing to the pattern parser
|
||||
- support casync as download mechanism
|
||||
@@ -1689,11 +1674,6 @@ Features:
|
||||
* systemd-sysext: optionally, run it in initrd already, before transitioning
|
||||
into host, to open up possibility for services shipped like that.
|
||||
|
||||
* introduce /dev/disk/root/* symlinks that allow referencing partitions on the
|
||||
disk the rootfs is on in a reasonably secure way. (or maybe: add
|
||||
/dev/gpt-auto-{home,srv,boot,…} similar in style to /dev/gpt-auto-root as we
|
||||
already have it.
|
||||
|
||||
* whenever we receive fds via SCM_RIGHTS make sure none got dropped due to the
|
||||
reception limit the kernel silently enforces.
|
||||
|
||||
|
||||
@@ -529,6 +529,14 @@ disk images with `--image=` or similar:
|
||||
images. Defaults to true, i.e. userspace signature validation is allowed. If
|
||||
false, authentication can be done only via the kernel's internal keyring.
|
||||
|
||||
* `$SYSTEMD_DISSECT_VERITY_GUESS` – takes a boolean. Controls whether to guess
|
||||
the Verity root hash from the partition UUIDs of a suitable pair of data
|
||||
partition and matching Verity partition: the UUIDs two are simply joined and
|
||||
used as root hash, in accordance with the recommendations in [Discoverable
|
||||
Partitions
|
||||
Specification](https://uapi-group.org/specifications/specs/discoverable_partitions_specification). Defaults
|
||||
to true.
|
||||
|
||||
`systemd-cryptsetup`:
|
||||
|
||||
* `$SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE` – takes a boolean, which controls
|
||||
|
||||
@@ -481,6 +481,18 @@
|
||||
<xi:include href="version-info.xml" xpointer="v254"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>systemd.image_filter=</varname></term>
|
||||
|
||||
<listitem><para>When GPT-based partition auto-discovery is used, configures the image dissection
|
||||
filter string to apply, as per
|
||||
<citerefentry><refentrytitle>systemd.image-filter</refentrytitle><manvolnum>7</manvolnum></citerefentry>. For
|
||||
details see
|
||||
<citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>systemd.default_timeout_start_sec=</varname></term>
|
||||
|
||||
|
||||
@@ -1188,6 +1188,7 @@ manpages = [
|
||||
['systemd.environment-generator', '7', [], 'ENABLE_ENVIRONMENT_D'],
|
||||
['systemd.exec', '5', [], ''],
|
||||
['systemd.generator', '7', [], ''],
|
||||
['systemd.image-filter', '7', [], ''],
|
||||
['systemd.image-policy', '7', [], ''],
|
||||
['systemd.journal-fields', '7', [], ''],
|
||||
['systemd.kill', '5', [], ''],
|
||||
|
||||
@@ -114,6 +114,14 @@
|
||||
in the image are used.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id='image-filter'>
|
||||
<term><option>--image-filter=<replaceable>filter</replaceable></option></term> <listitem><para>Takes an
|
||||
image filter string as argument, as per
|
||||
<citerefentry><refentrytitle>systemd.image-filter</refentrytitle><manvolnum>7</manvolnum></citerefentry>. The
|
||||
filter is taken into consideration when operating on the disk image specified via
|
||||
<option>--image=</option>, see above. If not specified no filtering is applied.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id='esp-path'>
|
||||
<term><option>--esp-path=</option></term>
|
||||
|
||||
|
||||
@@ -582,6 +582,7 @@
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="standard-options.xml" xpointer="image-policy-open" />
|
||||
<xi:include href="standard-options.xml" xpointer="image-filter" />
|
||||
<xi:include href="standard-options.xml" xpointer="no-pager" />
|
||||
<xi:include href="standard-options.xml" xpointer="no-legend" />
|
||||
<xi:include href="standard-options.xml" xpointer="json" />
|
||||
|
||||
@@ -83,10 +83,14 @@
|
||||
|
||||
<listitem><para>Configures the operating system's root filesystem to mount when running in the
|
||||
initrd. This accepts a device node path (usually <filename>/dev/disk/by-uuid/…</filename> or
|
||||
<filename>/dev/disk/by-label/…</filename> or similar), or the special values <literal>gpt-auto</literal>,
|
||||
<literal>fstab</literal>, and <literal>tmpfs</literal>.</para>
|
||||
<filename>/dev/disk/by-label/…</filename> or similar), or the special values
|
||||
<literal>gpt-auto</literal>, <literal>gpt-auto-force</literal>, <literal>dissect</literal>,
|
||||
<literal>dissect-force</literal>, <literal>fstab</literal>, <literal>fstab</literal>, and
|
||||
<literal>off</literal>.</para>
|
||||
|
||||
<para>Use <literal>gpt-auto</literal> to explicitly request automatic root file system discovery via
|
||||
<para>Set to <literal>gpt-auto</literal>, <literal>gpt-auto-force</literal>,
|
||||
<literal>dissect</literal>, <literal>dissect-force</literal> to explicitly request automatic root
|
||||
file system discovery, implemented in
|
||||
<citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
|
||||
|
||||
<para>Use <literal>fstab</literal> to explicitly request automatic root file system discovery via
|
||||
@@ -103,6 +107,11 @@
|
||||
filesystems (added in v258). Expects an absolute path name referencing an existing directory within the initrd's file
|
||||
hierarchy to boot into.</para>
|
||||
|
||||
<para>Set to <literal>off</literal> to turn off mounting of a root file system.</para>
|
||||
|
||||
<para>Note that further <varname>root=</varname> values may be supported, implemented in additional
|
||||
packages.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v217"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
@@ -133,20 +142,18 @@
|
||||
<varlistentry>
|
||||
<term><varname>mount.usr=</varname></term>
|
||||
|
||||
<listitem><para>Takes the <filename>/usr/</filename> filesystem
|
||||
to be mounted by the initrd. If
|
||||
<varname>mount.usrfstype=</varname> or
|
||||
<varname>mount.usrflags=</varname> is set, then
|
||||
<varname>mount.usr=</varname> will default to the value set in
|
||||
<listitem><para>Takes the <filename>/usr/</filename> filesystem to be mounted by the initrd. If
|
||||
<varname>mount.usrfstype=</varname> or <varname>mount.usrflags=</varname> is set, then the mount
|
||||
configured via <varname>mount.usr=</varname> will default to the the same value set in
|
||||
<varname>root=</varname>.</para>
|
||||
|
||||
<para>Otherwise, this parameter defaults to the
|
||||
<filename>/usr/</filename> entry found in
|
||||
<filename>/etc/fstab</filename> on the root filesystem.</para>
|
||||
<para>Set to <literal>dissect</literal> to explicitly request automatic <filename>/usr/</filename>
|
||||
file system discovery, implemented in
|
||||
<citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
|
||||
|
||||
<para><varname>mount.usr=</varname> is honored by the initrd.
|
||||
</para>
|
||||
<para>Set to <literal>off</literal> to turn off mounting of a separate <filename>/usr/</filename> file system.</para>
|
||||
|
||||
<para><varname>mount.usr=</varname> is honored by the initrd.</para>
|
||||
<xi:include href="version-info.xml" xpointer="v217"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
|
||||
@@ -70,11 +70,11 @@
|
||||
<citerefentry><refentrytitle>systemd-import-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
make sure to set the <literal>blockdev</literal> option and set the local name string to
|
||||
<literal>rootdisk</literal> to achieve this effect. Note that discovery of the root file system on
|
||||
loopback block devices like this is only done if <literal>root=gpt-auto</literal> is specified
|
||||
explicitly on the kernel command line, unlike the discovery based on the boot loader reported ESP which
|
||||
is also enabled if no <literal>root=</literal> parameter is specified at all. (The latter relies on
|
||||
<command>systemd-udevd.service</command>'s <filename>/dev/gpt-auto-root</filename> block device symlink
|
||||
generation).</para></listitem>
|
||||
loopback block devices like this is only done if <literal>root=gpt-auto</literal> or
|
||||
<literal>root=dissect</literal> is specified explicitly on the kernel command line, unlike the
|
||||
discovery based on the boot loader reported ESP which is also enabled if no <literal>root=</literal>
|
||||
parameter is specified at all. (The latter relies on <command>systemd-udevd.service</command>'s
|
||||
<filename>/dev/gpt-auto-root</filename> block device symlink generation).</para></listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
|
||||
@@ -317,26 +317,64 @@
|
||||
and allows enforcing a policy on dissection and use of the automatically discovered GPT partition
|
||||
table entries.</para>
|
||||
|
||||
<para>Note that the specified image policy is not taken into account for automatic root or
|
||||
<filename>/usr/</filename> file system discovery unless
|
||||
<varname>root=dissect</varname>/<varname>mount.usr=dissect</varname> (or
|
||||
<varname>root=dissect-force</varname>) are specified. (The policy will always be applied to the other
|
||||
auto-discoverable partition types.)</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v254"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>systemd.image_filter=</varname></term>
|
||||
|
||||
<listitem><para>Takes an image dissection filter string as argument (as per
|
||||
<citerefentry><refentrytitle>systemd.image-filter</refentrytitle><manvolnum>7</manvolnum></citerefentry>),
|
||||
and allows enforcing a set of globbing patterns on the partition matching of the automatically
|
||||
discovered GPT partition table entries.</para>
|
||||
|
||||
<para>Note that the specified image filter is not taken into account for automatic root or
|
||||
<filename>/usr/</filename> file system discovery unless
|
||||
<varname>root=dissect</varname>/<varname>mount.usr=dissect</varname> (or
|
||||
<varname>root=dissect-force</varname>) are specified. (The filter will always be applied to the other
|
||||
auto-discoverable partition types.)</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>root=</varname></term>
|
||||
<term><varname>rootfstype=</varname></term>
|
||||
<term><varname>rootflags=</varname></term>
|
||||
|
||||
<listitem><para>When <varname>root=</varname> is used with the special value
|
||||
<literal>gpt-auto</literal>, full automatic discovery of the root partition based on the GPT
|
||||
<literal>gpt-auto</literal>, basic automatic discovery of the root partition based on the GPT
|
||||
partition type is enabled. Use of the root partition is delayed until factory reset mode is left, in
|
||||
case it is enabled during the current boot. See <ulink url="https://systemd.io/FACTORY_RESET">Factory
|
||||
Reset</ulink> for more information on that. If <literal>gpt-auto-force</literal> is specified
|
||||
automatic discovery of the root partition is enabled, ignoring any factory reset mode. Any other
|
||||
value disables this logic.</para>
|
||||
automatic discovery of the root partition is enabled, ignoring any factory reset mode.</para>
|
||||
|
||||
<para>If <varname>root=</varname> is set to the special value <literal>dissect</literal> full
|
||||
automatic discovery of the root partition based on GPT partition information is enabled. This is a
|
||||
superset of <varname>root=gpt-auto</varname>, as it automatically configures Verity partitions
|
||||
(including signature-based setup) following the logic defined for that in the <ulink
|
||||
url="https://uapi-group.org/specifications/specs/discoverable_partitions_specification/">Discoverable
|
||||
Partitions Specification</ulink>. Moreover it takes the configured image policy and image filter into
|
||||
account for all partition types, including the root file system. <literal>root=dissect</literal> will
|
||||
wait for the factory reset phase to be completed if it is in effect before activating the root file
|
||||
system. Use <literal>root=dissect-force</literal> to ignore the factory reset phase and activate the
|
||||
root file system immediately.</para>
|
||||
|
||||
<para>Any other value (i.e. besides <literal>gpt-auto</literal>, <literal>gpt-auto-force</literal>,
|
||||
<literal>dissect</literal>, <literal>dissect-force</literal>) disables automatic root file system
|
||||
discovery.</para>
|
||||
|
||||
<para>If <varname>root=</varname> is not specified at all on the kernel command line automatic
|
||||
discovery of the root partition via the ESP reported by the boot loader is also enabled (taking
|
||||
factory reset state into account), however in this case discovery based on the loopback block device
|
||||
<literal>.lo_name</literal> field is not enabled.</para>
|
||||
factory reset state into account, i.e. equivalent to <literal>root=gpt-auto</literal>), however in
|
||||
this case discovery based on the loopback block device <literal>.lo_name</literal> field is not
|
||||
enabled.</para>
|
||||
|
||||
<para>The <varname>rootfstype=</varname> and <varname>rootflags=</varname> options are used to select
|
||||
the file system type and options when the root file system is automatically discovered.</para>
|
||||
@@ -344,6 +382,24 @@
|
||||
<xi:include href="version-info.xml" xpointer="v242"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>mount.usr=</varname></term>
|
||||
<term><varname>mount.usrfstype=</varname></term>
|
||||
<term><varname>mount.usrflags=</varname></term>
|
||||
|
||||
<listitem><para>Similar to <varname>root=</varname>, <varname>rootfstype=</varname>,
|
||||
<varname>rootflags=</varname> (see above), but applies to the <filename>/usr/</filename> partition
|
||||
instead. Note that the <literal>gpt-auto</literal>, <literal>gpt-auto-force</literal>,
|
||||
<literal>dissect-force</literal> settings that <filename>root=</filename> understands are not
|
||||
supported by <varname>mount.usr=</varname> (however <literal>dissect</literal> is).</para>
|
||||
|
||||
<para>Also note that automatic partition discovery for <filename>/usr/</filename> must be enabled
|
||||
explicitly, unlike the discovery for the root file system, which is enabled if no
|
||||
<varname>root=</varname> paramater is passed at all.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>rw</varname></term>
|
||||
<term><varname>ro</varname></term>
|
||||
|
||||
80
man/systemd.image-filter.xml
Normal file
80
man/systemd.image-filter.xml
Normal file
@@ -0,0 +1,80 @@
|
||||
<?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.image-filter">
|
||||
|
||||
<refentryinfo>
|
||||
<title>systemd.image-filter</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>systemd.image-filter</refentrytitle>
|
||||
<manvolnum>7</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>systemd.image-filter</refname>
|
||||
<refpurpose>Disk Image Dissection Filter</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para>In systemd, whenever a disk image (DDI) implementing the <ulink
|
||||
url="https://uapi-group.org/specifications/specs/discoverable_partitions_specification">Discoverable
|
||||
Partitions Specification</ulink> is activated, a filter may be specified controlling which partitions to
|
||||
consider for mounting. Such a disk image dissection filter is a string that contains per-partition-type
|
||||
patterns, separated by colons (<literal>:</literal>). The individual rules consist of a partition
|
||||
identifier, an equal sign (<literal>=</literal>), and a shell globbing pattern applied to the GPT label
|
||||
string of the partition. See <citerefentry
|
||||
project='man-pages'><refentrytitle>glob</refentrytitle><manvolnum>7</manvolnum></citerefentry> for
|
||||
details on shell globbing.</para>
|
||||
|
||||
<para>The partition identifiers currently defined are: <option>root</option>, <option>usr</option>,
|
||||
<option>home</option>, <option>srv</option>, <option>esp</option>, <option>xbootldr</option>,
|
||||
<option>swap</option>, <option>root-verity</option>, <option>root-verity-sig</option>,
|
||||
<option>usr-verity</option>, <option>usr-verity-sig</option>, <option>tmp</option>,
|
||||
<option>var</option>. These identifiers match the relevant partition types in the Discoverable Partitions
|
||||
Specification, but are agnostic to CPU architectures.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Use</title>
|
||||
|
||||
<para>Various systemd components that support operating with disk images support a
|
||||
<option>--image-filter=</option> command line option to specify the image filter to use. If no filter is
|
||||
specified all partitions in partition table are considered and no per-label filtering is applied (except
|
||||
that partitions with the <literal>_empty</literal> label are always ignored).</para>
|
||||
|
||||
<para>For the host root file system image itself
|
||||
<citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
is responsible for processing the GPT partition table and making use of the included discoverable
|
||||
partitions. It accepts an image filter via the kernel command line option
|
||||
<option>systemd.image_filter=</option>.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Examples</title>
|
||||
|
||||
<para>The following image filter string dictates that for the root file system partition only partitions
|
||||
shall be considered whose label begins with <literal>ParticleOS-</literal>. For the
|
||||
<filename>/usr/</filename> partition the precise label <literal>ParticleOS_47110815</literal> is
|
||||
required.</para>
|
||||
|
||||
<programlisting>root=ParticleOS-*:usr=ParticleOS_47110815</programlisting>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
<para><simplelist type="inline">
|
||||
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-dissect</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
|
||||
</simplelist></para>
|
||||
</refsect1>
|
||||
|
||||
</refentry>
|
||||
@@ -185,6 +185,7 @@
|
||||
<member><citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-analyze</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd.filter</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
|
||||
</simplelist></para>
|
||||
</refsect1>
|
||||
|
||||
|
||||
@@ -53,7 +53,9 @@ This is based on crypttab(5).
|
||||
<para>The third field contains a path to the underlying block hash device, or a specification of a block device via
|
||||
<varname>UUID=</varname> followed by the <replaceable>UUID</replaceable>.</para>
|
||||
|
||||
<para>The fourth field is the <replaceable>roothash</replaceable> in hexadecimal.</para>
|
||||
<para>The fourth field is the <replaceable>roothash</replaceable> in hexadecimal. If this field is
|
||||
specified as dash, it is attempted to read the root hash from the udev property
|
||||
<literal>ID_DISSECT_PART_ROOTHASH=</literal> (encoded in hexadecimal) of the data device.</para>
|
||||
|
||||
<para>The fifth field, if present, is a comma-delimited list of options. The following options are
|
||||
recognized:</para>
|
||||
@@ -213,11 +215,14 @@ This is based on crypttab(5).
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>root-hash-signature=<replaceable>PATH</replaceable>|base64:<replaceable>BASE64</replaceable></option></term>
|
||||
<term><option>root-hash-signature=<replaceable>PATH</replaceable>|base64:<replaceable>BASE64</replaceable>|auto</option></term>
|
||||
|
||||
<listitem><para>A base64 string encoding the root hash signature prefixed by
|
||||
<literal>base64:</literal> or an absolute path to a root hash signature file used to verify the root
|
||||
hash (in kernel). This feature requires Linux kernel version 5.4 or more recent.</para>
|
||||
<listitem><para>A Base64 string encoding the root hash signature prefixed by
|
||||
<literal>base64:</literal>, or an absolute path to a root hash signature file used to verify the root
|
||||
hash (in kernel). If the special string <literal>auto</literal> is specified, the root hash signature
|
||||
is attempted to be read from the udev property <literal>ID_DISSECT_PART_ROOTHASH_SIG=</literal> (in
|
||||
Base64 format) of the data device. This feature requires Linux kernel version 5.4 or more
|
||||
recent.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v248"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
@@ -30,4 +30,27 @@ LABEL="gpt_auto_root_end"
|
||||
ENV{DM_UUID}=="CRYPT-*", ENV{DM_NAME}=="root", IMPORT{builtin}="factory_reset status", SYMLINK+="gpt-auto-root"
|
||||
ENV{DM_UUID}=="CRYPT-*", ENV{DM_NAME}=="root", ENV{ID_FACTORY_RESET}=="on|complete", SYMLINK+="gpt-auto-root-ignore-factory-reset"
|
||||
|
||||
# If this is the whole disk that we booted from, then dissect it
|
||||
ENV{DEVTYPE}=="disk", ENV{ID_PART_GPT_AUTO_ROOT_DISK}=="1", IMPORT{builtin}="dissect_image probe"
|
||||
ENV{DEVTYPE}=="disk", ENV{ID_PART_GPT_AUTO_ROOT_DISK}=="1", ENV{ID_FACTORY_RESET}=="", IMPORT{builtin}="factory_reset status"
|
||||
|
||||
# If this is a partition, and we found something on the parent, then copy the
|
||||
# right properties from the parent, and rename them
|
||||
ENV{DEVTYPE}=="partition", ENV{ID_DISSECT_IMAGE}!="", IMPORT{builtin}="dissect_image copy"
|
||||
|
||||
# Create symlinks based on the designator for the partitions themselves. If we detect LUKS or Verity, suffix them with "-luks" or "-vdata"
|
||||
ENV{DEVTYPE}!="partition", GOTO="dissect_partition_symlinks_end"
|
||||
ENV{ID_DISSECT_PART_DESIGNATOR}=="", GOTO="dissect_partition_symlinks_end"
|
||||
ENV{ID_FS_TYPE}!="crypto_LUKS", ENV{ID_DISSECT_PART_HAS_VERITY}!="1", ENV{ID_FACTORY_RESET}!="on", SYMLINK+="disk/by-designator/$env{ID_DISSECT_PART_DESIGNATOR}"
|
||||
ENV{ID_FS_TYPE}!="crypto_LUKS", ENV{ID_DISSECT_PART_HAS_VERITY}!="1", ENV{ID_FACTORY_RESET}=="on|complete", SYMLINK+="disk/by-designator/$env{ID_DISSECT_PART_DESIGNATOR}-ignore-factory-reset"
|
||||
ENV{ID_FS_TYPE}=="crypto_LUKS", ENV{ID_FACTORY_RESET}!="on", SYMLINK+="disk/by-designator/$env{ID_DISSECT_PART_DESIGNATOR}-luks"
|
||||
ENV{ID_FS_TYPE}=="crypto_LUKS", ENV{ID_FACTORY_RESET}=="on|complete", SYMLINK+="disk/by-designator/$env{ID_DISSECT_PART_DESIGNATOR}-luks-ignore-factory-reset"
|
||||
ENV{ID_FS_TYPE}!="crypto_LUKS", ENV{ID_DISSECT_PART_HAS_VERITY}=="1", ENV{ID_FACTORY_RESET}!="on", SYMLINK+="disk/by-designator/$env{ID_DISSECT_PART_DESIGNATOR}-verity-data"
|
||||
ENV{ID_FS_TYPE}!="crypto_LUKS", ENV{ID_DISSECT_PART_HAS_VERITY}=="1", ENV{ID_FACTORY_RESET}=="on|complete", SYMLINK+="disk/by-designator/$env{ID_DISSECT_PART_DESIGNATOR}-verity-data-ignore-factory-reset"
|
||||
LABEL="dissect_partition_symlinks_end"
|
||||
|
||||
# For LUKS or Verity partitions we rely on the selected volume name
|
||||
ENV{DM_UUID}=="CRYPT-*", ENV{DM_NAME}=="root|usr|home|srv|swap|tmp|var", IMPORT{builtin}="factory_reset status", SYMLINK+="disk/by-designator/$env{DM_NAME}"
|
||||
ENV{DM_UUID}=="CRYPT-*", ENV{DM_NAME}=="root|usr|home|srv|swap|tmp|var", ENV{ID_FACTORY_RESET}=="on|complete", SYMLINK+="disk/by-designator/$env{DM_NAME}-ignore-factory-reset"
|
||||
|
||||
LABEL="image_dissect_end"
|
||||
|
||||
@@ -1476,9 +1476,8 @@ static int mount_image(
|
||||
const char *root_directory,
|
||||
const ImagePolicy *image_policy) {
|
||||
|
||||
_cleanup_free_ char *host_os_release_id = NULL, *host_os_release_version_id = NULL,
|
||||
*host_os_release_sysext_level = NULL, *host_os_release_confext_level = NULL,
|
||||
*extension_name = NULL;
|
||||
_cleanup_(extension_release_data_done) ExtensionReleaseData rdata = {};
|
||||
_cleanup_free_ char *extension_name = NULL;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
@@ -1490,14 +1489,14 @@ static int mount_image(
|
||||
if (m->mode == MOUNT_EXTENSION_IMAGE) {
|
||||
r = parse_os_release(
|
||||
empty_to_root(root_directory),
|
||||
"ID", &host_os_release_id,
|
||||
"VERSION_ID", &host_os_release_version_id,
|
||||
image_class_info[IMAGE_SYSEXT].level_env, &host_os_release_sysext_level,
|
||||
image_class_info[IMAGE_CONFEXT].level_env, &host_os_release_confext_level,
|
||||
"ID", &rdata.os_release_id,
|
||||
"VERSION_ID", &rdata.os_release_version_id,
|
||||
image_class_info[IMAGE_SYSEXT].level_env, &rdata.os_release_sysext_level,
|
||||
image_class_info[IMAGE_CONFEXT].level_env, &rdata.os_release_confext_level,
|
||||
NULL);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to acquire 'os-release' data of OS tree '%s': %m", empty_to_root(root_directory));
|
||||
if (isempty(host_os_release_id))
|
||||
if (isempty(rdata.os_release_id))
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "'ID' field not found or empty in 'os-release' data of OS tree '%s'.", empty_to_root(root_directory));
|
||||
}
|
||||
|
||||
@@ -1507,26 +1506,23 @@ static int mount_image(
|
||||
mount_entry_path(m),
|
||||
m->image_options_const,
|
||||
image_policy,
|
||||
host_os_release_id,
|
||||
host_os_release_version_id,
|
||||
host_os_release_sysext_level,
|
||||
host_os_release_confext_level,
|
||||
/* required_sysext_scope= */ NULL,
|
||||
/* image_filter= */ NULL,
|
||||
&rdata,
|
||||
&m->verity,
|
||||
/* ret_image= */ NULL);
|
||||
if (r == -ENOENT && m->ignore)
|
||||
return 0;
|
||||
if (r == -ESTALE && host_os_release_id)
|
||||
if (r == -ESTALE && rdata.os_release_id)
|
||||
return log_error_errno(r, // FIXME: this should not be logged ad LOG_ERR, as it will result in duplicate logging.
|
||||
"Failed to mount image %s, extension-release metadata does not match the lower layer's: ID=%s%s%s%s%s%s%s",
|
||||
mount_entry_source(m),
|
||||
host_os_release_id,
|
||||
host_os_release_version_id ? " VERSION_ID=" : "",
|
||||
strempty(host_os_release_version_id),
|
||||
host_os_release_sysext_level ? image_class_info[IMAGE_SYSEXT].level_env_print : "",
|
||||
strempty(host_os_release_sysext_level),
|
||||
host_os_release_confext_level ? image_class_info[IMAGE_CONFEXT].level_env_print : "",
|
||||
strempty(host_os_release_confext_level));
|
||||
rdata.os_release_id,
|
||||
rdata.os_release_version_id ? " VERSION_ID=" : "",
|
||||
strempty(rdata.os_release_version_id),
|
||||
rdata.os_release_sysext_level ? image_class_info[IMAGE_SYSEXT].level_env_print : "",
|
||||
strempty(rdata.os_release_sysext_level),
|
||||
rdata.os_release_confext_level ? image_class_info[IMAGE_CONFEXT].level_env_print : "",
|
||||
strempty(rdata.os_release_confext_level));
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to mount image %s on %s: %m", mount_entry_source(m), mount_entry_path(m));
|
||||
|
||||
@@ -2352,6 +2348,7 @@ int setup_namespace(const NamespaceParameters *p, char **reterr_path) {
|
||||
p->verity,
|
||||
p->root_image_options,
|
||||
p->root_image_policy,
|
||||
/* image_filter= */ NULL,
|
||||
dissect_image_flags,
|
||||
&dissected_image);
|
||||
if (r < 0)
|
||||
@@ -2364,6 +2361,12 @@ int setup_namespace(const NamespaceParameters *p, char **reterr_path) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dissected_image_guess_verity_roothash(
|
||||
dissected_image,
|
||||
p->verity);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dissected_image_decrypt(
|
||||
dissected_image,
|
||||
NULL,
|
||||
|
||||
@@ -102,6 +102,7 @@ static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID;
|
||||
static bool arg_all = false;
|
||||
static uid_t arg_uid_base = UID_INVALID;
|
||||
static bool arg_quiet = false;
|
||||
static ImageFilter *arg_image_filter = NULL;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
|
||||
@@ -110,6 +111,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_verity_settings, verity_settings_done);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_argv, strv_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_loop_ref, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_image_filter, image_filter_freep);
|
||||
|
||||
static int help(void) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
@@ -155,6 +157,8 @@ static int help(void) {
|
||||
" not embedded in IMAGE\n"
|
||||
" --image-policy=POLICY\n"
|
||||
" Specify image dissection policy\n"
|
||||
" --image-filter=FILTER\n"
|
||||
" Specify image dissection filter\n"
|
||||
" --json=pretty|short|off\n"
|
||||
" Generate JSON output\n"
|
||||
" --loop-ref=NAME Set reference string for loopback device\n"
|
||||
@@ -295,6 +299,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
ARG_SYSTEM,
|
||||
ARG_USER,
|
||||
ARG_ALL,
|
||||
ARG_IMAGE_FILTER,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
@@ -336,6 +341,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
{ "user", no_argument, NULL, ARG_USER },
|
||||
{ "all", no_argument, NULL, ARG_ALL },
|
||||
{ "quiet", no_argument, NULL, 'q' },
|
||||
{ "image-filter", required_argument, NULL, ARG_IMAGE_FILTER },
|
||||
{}
|
||||
};
|
||||
|
||||
@@ -610,6 +616,17 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
arg_quiet = true;
|
||||
break;
|
||||
|
||||
case ARG_IMAGE_FILTER: {
|
||||
_cleanup_(image_filter_freep) ImageFilter *f = NULL;
|
||||
r = image_filter_parse(optarg, &f);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse image filter expression: %s", optarg);
|
||||
|
||||
image_filter_free(arg_image_filter);
|
||||
arg_image_filter = TAKE_PTR(f);
|
||||
break;
|
||||
}
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
@@ -2106,10 +2123,11 @@ static int action_validate(void) {
|
||||
r = dissect_image_file_and_warn(
|
||||
arg_image,
|
||||
&arg_verity_settings,
|
||||
NULL,
|
||||
/* mount_options= */ NULL,
|
||||
arg_image_policy,
|
||||
arg_image_filter,
|
||||
arg_flags,
|
||||
NULL);
|
||||
/* ret= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -2177,8 +2195,10 @@ static int run(int argc, char *argv[]) {
|
||||
|
||||
if (arg_image) {
|
||||
r = verity_settings_load(
|
||||
&arg_verity_settings,
|
||||
arg_image, NULL, NULL);
|
||||
&arg_verity_settings,
|
||||
arg_image,
|
||||
/* root_hash_path= */ NULL,
|
||||
/* root_hash_sig_path= */ NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read verity artifacts for %s: %m", arg_image);
|
||||
|
||||
@@ -2229,6 +2249,7 @@ static int run(int argc, char *argv[]) {
|
||||
&arg_verity_settings,
|
||||
/* mount_options= */ NULL,
|
||||
arg_image_policy,
|
||||
arg_image_filter,
|
||||
arg_flags,
|
||||
&m);
|
||||
if (r < 0)
|
||||
@@ -2244,6 +2265,12 @@ static int run(int argc, char *argv[]) {
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to load verity signature partition: %m");
|
||||
|
||||
r = dissected_image_guess_verity_roothash(
|
||||
m,
|
||||
&arg_verity_settings);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to guess verity root hash: %m");
|
||||
|
||||
if (arg_action != ACTION_DISSECT) {
|
||||
r = dissected_image_decrypt_interactively(
|
||||
m, NULL,
|
||||
|
||||
@@ -1132,7 +1132,12 @@ static bool validate_root_or_usr_mount_source(const char *what, const char *swit
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parse_gpt_auto_root(what) > 0) {
|
||||
if (streq(what, "off")) {
|
||||
log_debug("Skipping %s directory handling, as this was explicitly turned off.", switch_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parse_gpt_auto_root(switch_name, what) > 0) {
|
||||
/* This is handled by gpt-auto-generator */
|
||||
log_debug("Skipping %s directory handling, as gpt-auto was requested.", switch_name);
|
||||
return false;
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "fstab-util.h"
|
||||
#include "generator.h"
|
||||
#include "gpt.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "image-policy.h"
|
||||
#include "initrd-util.h"
|
||||
#include "mountpoint-util.h"
|
||||
@@ -46,17 +47,24 @@ typedef enum MountPointFlags {
|
||||
} MountPointFlags;
|
||||
|
||||
static const char *arg_dest = NULL;
|
||||
static const char *arg_dest_late = NULL;
|
||||
static bool arg_enabled = true;
|
||||
static GptAutoRoot arg_auto_root = _GPT_AUTO_ROOT_INVALID;
|
||||
static GptAutoRoot arg_auto_usr = _GPT_AUTO_ROOT_INVALID;
|
||||
static bool arg_swap_enabled = true;
|
||||
static char *arg_root_fstype = NULL;
|
||||
static char *arg_root_options = NULL;
|
||||
static int arg_root_rw = -1;
|
||||
static char *arg_usr_fstype = NULL;
|
||||
static char *arg_usr_options = NULL;
|
||||
static ImagePolicy *arg_image_policy = NULL;
|
||||
static ImageFilter *arg_image_filter = NULL;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_root_fstype, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_root_options, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_usr_fstype, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_usr_options, freep);
|
||||
|
||||
#define LOADER_PARTITION_IDLE_USEC (120 * USEC_PER_SEC)
|
||||
|
||||
@@ -88,7 +96,7 @@ static int add_cryptsetup(
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to generate unit name: %m");
|
||||
|
||||
r = generator_open_unit_file(arg_dest, /* source = */ NULL, n, &f);
|
||||
r = generator_open_unit_file(arg_dest_late, /* source = */ NULL, n, &f);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -135,27 +143,27 @@ static int add_cryptsetup(
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write file %s: %m", n);
|
||||
|
||||
r = generator_write_device_timeout(arg_dest, what, mount_opts, /* filtered = */ NULL);
|
||||
r = generator_write_device_timeout(arg_dest_late, what, mount_opts, /* filtered = */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = generator_add_symlink(arg_dest, d, "wants", n);
|
||||
r = generator_add_symlink(arg_dest_late, d, "wants", n);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
const char *dmname = strjoina("dev-mapper-", e, ".device");
|
||||
|
||||
if (require) {
|
||||
r = generator_add_symlink(arg_dest, "cryptsetup.target", "requires", n);
|
||||
r = generator_add_symlink(arg_dest_late, "cryptsetup.target", "requires", n);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = generator_add_symlink(arg_dest, dmname, "requires", n);
|
||||
r = generator_add_symlink(arg_dest_late, dmname, "requires", n);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = write_drop_in_format(arg_dest, dmname, 50, "job-timeout",
|
||||
r = write_drop_in_format(arg_dest_late, dmname, 50, "job-timeout",
|
||||
"# Automatically generated by systemd-gpt-auto-generator\n\n"
|
||||
"[Unit]\n"
|
||||
"JobTimeoutSec=infinity"); /* the binary handles timeouts anyway */
|
||||
@@ -179,6 +187,106 @@ static int add_cryptsetup(
|
||||
#endif
|
||||
}
|
||||
|
||||
#if ENABLE_EFI
|
||||
static int add_veritysetup(
|
||||
const char *id,
|
||||
const char *data_what,
|
||||
const char *hash_what,
|
||||
const char *mount_opts) {
|
||||
|
||||
#if HAVE_LIBCRYPTSETUP
|
||||
int r;
|
||||
|
||||
assert(id);
|
||||
assert(data_what);
|
||||
assert(hash_what);
|
||||
|
||||
_cleanup_free_ char *dd = NULL;
|
||||
r = unit_name_from_path(data_what, ".device", &dd);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to generate data device unit name: %m");
|
||||
|
||||
_cleanup_free_ char *dh = NULL;
|
||||
r = unit_name_from_path(hash_what, ".device", &dh);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to generate hash device unit name: %m");
|
||||
|
||||
_cleanup_free_ char *e = unit_name_escape(id);
|
||||
if (!e)
|
||||
return log_oom();
|
||||
|
||||
_cleanup_free_ char *n = NULL;
|
||||
r = unit_name_build("systemd-veritysetup", e, ".service", &n);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to generate unit name: %m");
|
||||
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
r = generator_open_unit_file(arg_dest_late, /* source= */ NULL, n, &f);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = generator_write_veritysetup_unit_section(f, /* source= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
fprintf(f,
|
||||
"Before=veritysetup.target\n"
|
||||
"BindsTo=%1$s %2$s\n"
|
||||
"After=%1$s %2$s\n",
|
||||
dd, dh);
|
||||
|
||||
r = generator_write_veritysetup_service_section(
|
||||
f,
|
||||
id,
|
||||
data_what,
|
||||
hash_what,
|
||||
/* roothash= */ NULL, /* NULL means: derive root hash from udev property ID_DISSECT_PART_ROOTHASH */
|
||||
"root-hash-signature=auto"); /* auto means: derive signature from udev property ID_DISSECT_PART_ROOTHASH_SIG */
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = fflush_and_check(f);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write file %s: %m", n);
|
||||
|
||||
r = generator_write_device_timeout(arg_dest_late, data_what, mount_opts, /* filtered= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = generator_write_device_timeout(arg_dest_late, hash_what, mount_opts, /* filtered= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = generator_add_symlink(arg_dest_late, dd, "wants", n);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = generator_add_symlink(arg_dest_late, dh, "wants", n);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_free_ char *dmname = NULL;
|
||||
dmname = strjoin("dev-mapper-", e, ".device");
|
||||
if (!dmname)
|
||||
return log_oom();
|
||||
|
||||
r = write_drop_in_format(
|
||||
arg_dest_late,
|
||||
dmname, 50, "job-timeout",
|
||||
"# Automatically generated by systemd-gpt-auto-generator\n\n"
|
||||
"[Unit]\n"
|
||||
"JobTimeoutSec=infinity"); /* the binary handles timeouts anyway */
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write device timeout drop-in: %m");
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||
"Partition is Verity protected, but systemd-gpt-auto-generator was compiled without libcryptsetup support.");
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
static int add_mount(
|
||||
const char *id,
|
||||
const char *what,
|
||||
@@ -224,7 +332,7 @@ static int add_mount(
|
||||
fstype, where);
|
||||
}
|
||||
|
||||
r = generator_write_device_timeout(arg_dest, what, options, &opts_filtered);
|
||||
r = generator_write_device_timeout(arg_dest_late, what, options, &opts_filtered);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -232,7 +340,7 @@ static int add_mount(
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to generate unit name: %m");
|
||||
|
||||
r = generator_open_unit_file(arg_dest, /* source = */ NULL, unit, &f);
|
||||
r = generator_open_unit_file(arg_dest_late, /* source = */ NULL, unit, &f);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -245,6 +353,10 @@ static int add_mount(
|
||||
if (post)
|
||||
fprintf(f, "Before=%s\n", post);
|
||||
|
||||
/* NB: here we do not write to arg_dest_late, but to arg_dest! We typically leave the normal
|
||||
* generator drop-in dir for explicit configuration via systemd-fstab-generator or similar, and put
|
||||
* out automatic configuration in the arg_dest_late directory. But this one is an exception, since we
|
||||
* need to override the static version of the fsck root service file. */
|
||||
r = generator_write_fsck_deps(f, arg_dest, what, where, fstype, opts_filtered);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -275,25 +387,25 @@ static int add_mount(
|
||||
return log_error_errno(r, "Failed to write unit %s: %m", unit);
|
||||
|
||||
if (FLAGS_SET(flags, MOUNT_VALIDATEFS)) {
|
||||
r = generator_hook_up_validatefs(arg_dest, where, post);
|
||||
r = generator_hook_up_validatefs(arg_dest_late, where, post);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (FLAGS_SET(flags, MOUNT_GROWFS)) {
|
||||
r = generator_hook_up_growfs(arg_dest, where, post);
|
||||
r = generator_hook_up_growfs(arg_dest_late, where, post);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (FLAGS_SET(flags, MOUNT_MEASURE)) {
|
||||
r = generator_hook_up_pcrfs(arg_dest, where, post);
|
||||
r = generator_hook_up_pcrfs(arg_dest_late, where, post);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (post) {
|
||||
r = generator_add_symlink(arg_dest, post, "requires", unit);
|
||||
r = generator_add_symlink(arg_dest_late, post, "requires", unit);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
@@ -407,7 +519,7 @@ static int add_partition_swap(DissectedPartition *p) {
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to generate unit name: %m");
|
||||
|
||||
r = generator_open_unit_file(arg_dest, /* source = */ NULL, name, &f);
|
||||
r = generator_open_unit_file(arg_dest_late, /* source = */ NULL, name, &f);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -430,7 +542,7 @@ static int add_partition_swap(DissectedPartition *p) {
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write unit %s: %m", name);
|
||||
|
||||
return generator_add_symlink(arg_dest, SPECIAL_SWAP_TARGET, "wants", name);
|
||||
return generator_add_symlink(arg_dest_late, SPECIAL_SWAP_TARGET, "wants", name);
|
||||
}
|
||||
|
||||
static int add_automount(
|
||||
@@ -466,7 +578,7 @@ static int add_automount(
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to generate unit name: %m");
|
||||
|
||||
r = generator_open_unit_file(arg_dest, /* source = */ NULL, unit, &f);
|
||||
r = generator_open_unit_file(arg_dest_late, /* source = */ NULL, unit, &f);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -485,7 +597,7 @@ static int add_automount(
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write unit %s: %m", unit);
|
||||
|
||||
return generator_add_symlink(arg_dest, SPECIAL_LOCAL_FS_TARGET, "wants", unit);
|
||||
return generator_add_symlink(arg_dest_late, SPECIAL_LOCAL_FS_TARGET, "wants", unit);
|
||||
}
|
||||
|
||||
static int add_partition_xbootldr(DissectedPartition *p) {
|
||||
@@ -608,11 +720,11 @@ static int add_partition_root_rw(DissectedPartition *p) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = generator_enable_remount_fs_service(arg_dest);
|
||||
r = generator_enable_remount_fs_service(arg_dest_late);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
path = strjoina(arg_dest, "/systemd-remount-fs.service.d/50-remount-rw.conf");
|
||||
path = strjoina(arg_dest_late, "/systemd-remount-fs.service.d/50-remount-rw.conf");
|
||||
|
||||
r = write_string_file(path,
|
||||
"# Automatically generated by systemd-gpt-auto-generator\n\n"
|
||||
@@ -637,7 +749,7 @@ static int add_partition_root_growfs(DissectedPartition *p) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return generator_hook_up_growfs(arg_dest, "/", SPECIAL_LOCAL_FS_TARGET);
|
||||
return generator_hook_up_growfs(arg_dest_late, "/", SPECIAL_LOCAL_FS_TARGET);
|
||||
}
|
||||
|
||||
static int add_partition_root_flags(DissectedPartition *p) {
|
||||
@@ -656,22 +768,36 @@ static int add_partition_root_flags(DissectedPartition *p) {
|
||||
static int add_root_cryptsetup(void) {
|
||||
#if HAVE_LIBCRYPTSETUP
|
||||
|
||||
/* If a device /dev/gpt-auto-root-luks appears, then make it pull in systemd-cryptsetup-root.service, which
|
||||
* sets it up, and causes /dev/gpt-auto-root to appear which is all we are looking for. */
|
||||
assert(arg_auto_root != GPT_AUTO_ROOT_OFF);
|
||||
|
||||
const char *bdev = "/dev/gpt-auto-root-luks";
|
||||
/* If a device /dev/gpt-auto-root-luks or /dev/disk/by-designator/root-luks appears, then make it
|
||||
* pull in systemd-cryptsetup@root.service, which sets it up, and causes /dev/gpt-auto-root or
|
||||
* /dev/disk/by-designator/root to appear which is all we are looking for. */
|
||||
|
||||
if (arg_auto_root == GPT_AUTO_ROOT_FORCE) {
|
||||
const char *bdev =
|
||||
IN_SET(arg_auto_root, GPT_AUTO_ROOT_DISSECT, GPT_AUTO_ROOT_DISSECT_FORCE) ?
|
||||
"/dev/disk/by-designator/root-luks" : "/dev/gpt-auto-root-luks";
|
||||
|
||||
if (IN_SET(arg_auto_root, GPT_AUTO_ROOT_FORCE, GPT_AUTO_ROOT_DISSECT_FORCE)) {
|
||||
/* Similar logic as in add_root_mount(), see below */
|
||||
FactoryResetMode f = factory_reset_mode();
|
||||
if (f < 0)
|
||||
log_warning_errno(f, "Failed to determine whether we are in factory reset mode, assuming not: %m");
|
||||
|
||||
if (IN_SET(f, FACTORY_RESET_ON, FACTORY_RESET_COMPLETE))
|
||||
bdev = "/dev/gpt-auto-root-luks-ignore-factory-reset";
|
||||
bdev =
|
||||
IN_SET(arg_auto_root, GPT_AUTO_ROOT_DISSECT, GPT_AUTO_ROOT_DISSECT_FORCE) ?
|
||||
"/dev/disk/by-designator/root-luks-ignore-factory-reset" :
|
||||
"/dev/gpt-auto-root-luks-ignore-factory-reset";
|
||||
}
|
||||
|
||||
return add_cryptsetup("root", bdev, arg_root_options, MOUNT_RW|MOUNT_MEASURE, /* require= */ false, NULL);
|
||||
return add_cryptsetup(
|
||||
"root",
|
||||
bdev,
|
||||
arg_root_options,
|
||||
MOUNT_RW|MOUNT_MEASURE,
|
||||
/* require= */ false,
|
||||
/* ret_device= */ NULL);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
@@ -715,24 +841,41 @@ static int add_root_mount(void) {
|
||||
* factory reset the latter is the link to use, otherwise the former (so that we don't accidentally
|
||||
* mount a root partition too early that is about to be wiped and replaced by another one). */
|
||||
|
||||
const char *bdev = "/dev/gpt-auto-root";
|
||||
if (arg_auto_root == GPT_AUTO_ROOT_FORCE) {
|
||||
const char *bdev =
|
||||
IN_SET(arg_auto_root, GPT_AUTO_ROOT_DISSECT, GPT_AUTO_ROOT_DISSECT_FORCE) ?
|
||||
"/dev/disk/by-designator/root" : "/dev/gpt-auto-root";
|
||||
if (IN_SET(arg_auto_root, GPT_AUTO_ROOT_FORCE, GPT_AUTO_ROOT_DISSECT_FORCE)) {
|
||||
FactoryResetMode f = factory_reset_mode();
|
||||
if (f < 0)
|
||||
log_warning_errno(f, "Failed to determine whether we are in factory reset mode, assuming not: %m");
|
||||
|
||||
if (IN_SET(f, FACTORY_RESET_ON, FACTORY_RESET_COMPLETE))
|
||||
bdev = "/dev/gpt-auto-root-ignore-factory-reset";
|
||||
bdev =
|
||||
IN_SET(arg_auto_root, GPT_AUTO_ROOT_DISSECT, GPT_AUTO_ROOT_DISSECT_FORCE) ?
|
||||
"/dev/disk/by-designator/root-ignore-factory-reset" :
|
||||
"/dev/gpt-auto-root-ignore-factory-reset";
|
||||
}
|
||||
|
||||
if (in_initrd()) {
|
||||
r = generator_write_initrd_root_device_deps(arg_dest, bdev);
|
||||
r = generator_write_initrd_root_device_deps(arg_dest_late, bdev);
|
||||
if (r < 0)
|
||||
return 0;
|
||||
|
||||
r = add_root_cryptsetup();
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* If a device /dev/disk/by-designator/root-verity or
|
||||
* /dev/disk/by-designator/root-verity-data appears, then make it pull in
|
||||
* systemd-cryptsetup@root.service, which sets it up, and causes /dev/disk/by-designator/root
|
||||
* to appear. */
|
||||
r = add_veritysetup(
|
||||
"root",
|
||||
"/dev/disk/by-designator/root-verity-data",
|
||||
"/dev/disk/by-designator/root-verity",
|
||||
arg_root_options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Note that we do not need to enable systemd-remount-fs.service here. If /etc/fstab exists,
|
||||
@@ -769,6 +912,95 @@ static int add_root_mount(void) {
|
||||
#endif
|
||||
}
|
||||
|
||||
static int add_usr_mount(void) {
|
||||
#if ENABLE_EFI
|
||||
int r;
|
||||
|
||||
/* /usr/ discovery must be enabled explicitly. */
|
||||
if (arg_auto_usr == GPT_AUTO_ROOT_OFF ||
|
||||
arg_auto_usr < 0)
|
||||
return 0;
|
||||
|
||||
/* We do not support the other gpt-auto modes for /usr/, but the parser should already have checked that. */
|
||||
assert(arg_auto_usr == GPT_AUTO_ROOT_DISSECT);
|
||||
|
||||
if (arg_root_fstype && !arg_usr_fstype) {
|
||||
arg_usr_fstype = strdup(arg_root_fstype);
|
||||
if (!arg_usr_fstype)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
if (arg_root_options && !arg_usr_options) {
|
||||
arg_usr_options = strdup(arg_root_options);
|
||||
if (!arg_usr_options)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
if (in_initrd()) {
|
||||
r = add_cryptsetup(
|
||||
"usr",
|
||||
"/dev/disk/by-designator/usr-luks",
|
||||
arg_usr_options,
|
||||
MOUNT_RW|MOUNT_MEASURE,
|
||||
/* require= */ false,
|
||||
/* ret_device= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* If a device /dev/disk/by-designator/usr-verity or
|
||||
* /dev/disk/by-designator/usr-verity-data appears, then make it pull in
|
||||
* systemd-cryptsetup@usr.service, which sets it up, and causes /dev/disk/by-designator/usr
|
||||
* to appear. */
|
||||
r = add_veritysetup(
|
||||
"usr",
|
||||
"/dev/disk/by-designator/usr-verity-data",
|
||||
"/dev/disk/by-designator/usr-verity",
|
||||
arg_usr_options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
_cleanup_free_ char *options = NULL;
|
||||
r = partition_pick_mount_options(
|
||||
PARTITION_USR,
|
||||
arg_usr_fstype,
|
||||
/* rw= */ false,
|
||||
/* discard= */ true,
|
||||
&options,
|
||||
/* ret_ms_flags= */ NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to pick /usr/ mount options: %m");
|
||||
|
||||
if (arg_usr_options)
|
||||
if (!strextend_with_separator(&options, ",", arg_usr_options))
|
||||
return log_oom();
|
||||
|
||||
r = add_mount("usr",
|
||||
"/dev/disk/by-designator/usr",
|
||||
in_initrd() ? "/sysusr/usr" : "/usr",
|
||||
arg_usr_fstype,
|
||||
(in_initrd() ? MOUNT_VALIDATEFS : 0),
|
||||
options,
|
||||
"/usr/ Partition",
|
||||
in_initrd() ? SPECIAL_INITRD_USR_FS_TARGET : SPECIAL_LOCAL_FS_TARGET);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
log_debug("Synthesizing entry what=/sysusr/usr where=/sysroot/usr opts=bind");
|
||||
|
||||
return add_mount("usr-bind",
|
||||
"/sysusr/usr",
|
||||
"/sysroot/usr",
|
||||
/* fstype= */ NULL,
|
||||
/* flags= */ 0,
|
||||
"bind",
|
||||
"/usr/ Partition (Final)",
|
||||
in_initrd() ? SPECIAL_INITRD_FS_TARGET : SPECIAL_LOCAL_FS_TARGET);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int process_loader_partitions(DissectedPartition *esp, DissectedPartition *xbootldr) {
|
||||
sd_id128_t loader_uuid;
|
||||
int r;
|
||||
@@ -877,6 +1109,7 @@ static int enumerate_partitions(dev_t devnum) {
|
||||
/* verity= */ NULL,
|
||||
/* mount_options= */ NULL,
|
||||
image_policy,
|
||||
arg_image_filter,
|
||||
DISSECT_IMAGE_GPT_ONLY|
|
||||
DISSECT_IMAGE_USR_NO_ROOT|
|
||||
DISSECT_IMAGE_DISKSEQ_DEVNODE|
|
||||
@@ -960,7 +1193,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
|
||||
/* Disable root disk logic if there's a root= value specified (unless it happens to be
|
||||
* "gpt-auto" or "gpt-auto-force") */
|
||||
|
||||
arg_auto_root = parse_gpt_auto_root(value);
|
||||
arg_auto_root = parse_gpt_auto_root("root=", value);
|
||||
assert(arg_auto_root >= 0);
|
||||
|
||||
} else if (streq(key, "roothash")) {
|
||||
@@ -988,14 +1221,54 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
|
||||
if (!strextend_with_separator(&arg_root_options, ",", value))
|
||||
return log_oom();
|
||||
|
||||
} else if (streq(key, "mount.usr")) {
|
||||
|
||||
if (proc_cmdline_value_missing(key, value))
|
||||
return 0;
|
||||
|
||||
/* Disable root disk logic if there's a root= value specified (unless it happens to be
|
||||
* "gpt-auto" or "gpt-auto-force") */
|
||||
|
||||
arg_auto_usr = parse_gpt_auto_root("mount.usr=", value);
|
||||
assert(arg_auto_usr >= 0);
|
||||
|
||||
if (IN_SET(arg_auto_usr, GPT_AUTO_ROOT_ON, GPT_AUTO_ROOT_FORCE, GPT_AUTO_ROOT_DISSECT_FORCE)) {
|
||||
log_warning("'gpt-auto', 'gpt-auto-force' and 'dissect-force' are not supported for mount.usr=. Automatically resorting to mount.usr=dissect mode instead.");
|
||||
arg_auto_usr = GPT_AUTO_ROOT_DISSECT;
|
||||
}
|
||||
|
||||
} else if (streq(key, "mount.usrfstype")) {
|
||||
|
||||
if (proc_cmdline_value_missing(key, value))
|
||||
return 0;
|
||||
|
||||
return free_and_strdup_warn(&arg_usr_fstype, empty_to_null(value));
|
||||
|
||||
} else if (streq(key, "mount.usrflags")) {
|
||||
|
||||
if (proc_cmdline_value_missing(key, value))
|
||||
return 0;
|
||||
|
||||
if (!strextend_with_separator(&arg_usr_options, ",", value))
|
||||
return log_oom();
|
||||
|
||||
} else if (streq(key, "rw") && !value)
|
||||
arg_root_rw = true;
|
||||
else if (streq(key, "ro") && !value)
|
||||
arg_root_rw = false;
|
||||
else if (proc_cmdline_key_streq(key, "systemd.image_policy"))
|
||||
return parse_image_policy_argument(value, &arg_image_policy);
|
||||
else if (proc_cmdline_key_streq(key, "systemd.image_filter")) {
|
||||
_cleanup_(image_filter_freep) ImageFilter *f = NULL;
|
||||
|
||||
else if (streq(key, "systemd.swap")) {
|
||||
r = image_filter_parse(value, &f);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse image filter: %s", value);
|
||||
|
||||
image_filter_free(arg_image_filter);
|
||||
arg_image_filter = TAKE_PTR(f);
|
||||
|
||||
} else if (streq(key, "systemd.swap")) {
|
||||
|
||||
r = value ? parse_boolean(value) : 1;
|
||||
if (r < 0)
|
||||
@@ -1014,7 +1287,8 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
|
||||
static int run(const char *dest, const char *dest_early, const char *dest_late) {
|
||||
int r;
|
||||
|
||||
assert_se(arg_dest = dest_late);
|
||||
assert_se(arg_dest = dest);
|
||||
assert_se(arg_dest_late = dest_late);
|
||||
|
||||
if (detect_container() > 0) {
|
||||
log_debug("In a container, exiting.");
|
||||
@@ -1032,6 +1306,7 @@ static int run(const char *dest, const char *dest_early, const char *dest_late)
|
||||
|
||||
r = 0;
|
||||
RET_GATHER(r, add_root_mount());
|
||||
RET_GATHER(r, add_usr_mount());
|
||||
RET_GATHER(r, add_mounts());
|
||||
|
||||
return r;
|
||||
|
||||
@@ -414,6 +414,7 @@ static int vl_method_mount_image(
|
||||
&verity,
|
||||
/* mount_options= */ NULL,
|
||||
use_policy,
|
||||
/* image_filter= */ NULL,
|
||||
dissect_flags,
|
||||
&di);
|
||||
if (r == -ENOPKG)
|
||||
@@ -461,6 +462,12 @@ static int vl_method_mount_image(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dissected_image_guess_verity_roothash(
|
||||
di,
|
||||
&verity);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dissected_image_decrypt(
|
||||
di,
|
||||
p.password,
|
||||
|
||||
@@ -6370,8 +6370,9 @@ static int run(int argc, char *argv[]) {
|
||||
r = dissect_loop_device_and_warn(
|
||||
loop,
|
||||
&arg_verity_settings,
|
||||
/* mount_options=*/ NULL,
|
||||
/* mount_options= */ NULL,
|
||||
arg_image_policy ?: &image_policy_container,
|
||||
/* image_filter= */ NULL,
|
||||
dissect_image_flags,
|
||||
&dissected_image);
|
||||
if (r == -ENOPKG) {
|
||||
@@ -6391,10 +6392,20 @@ static int run(int argc, char *argv[]) {
|
||||
dissected_image,
|
||||
loop->fd,
|
||||
&arg_verity_settings);
|
||||
if (r < 0)
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to load Verity signature partition: %m");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (dissected_image->has_verity && !arg_verity_settings.root_hash && !dissected_image->has_verity_sig)
|
||||
r = dissected_image_guess_verity_roothash(
|
||||
dissected_image,
|
||||
&arg_verity_settings);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to guess Verity root hash: %m");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (dissected_image->has_verity && !arg_verity_settings.root_hash)
|
||||
log_notice("Note: image %s contains verity information, but no root hash specified and no embedded "
|
||||
"root hash signature found! Proceeding without integrity checking.", arg_image);
|
||||
|
||||
|
||||
@@ -425,6 +425,7 @@ static int portable_extract_by_path(
|
||||
/* verity= */ NULL,
|
||||
/* mount_options= */ NULL,
|
||||
image_policy,
|
||||
/* image_filter= */ NULL,
|
||||
flags,
|
||||
&m);
|
||||
if (r == -ENOPKG)
|
||||
|
||||
@@ -1726,6 +1726,7 @@ int image_read_metadata(Image *i, const ImagePolicy *image_policy) {
|
||||
/* verity= */ NULL,
|
||||
/* mount_options= */ NULL,
|
||||
image_policy,
|
||||
/* image_filter= */ NULL,
|
||||
flags,
|
||||
&m);
|
||||
if (r < 0)
|
||||
|
||||
@@ -667,6 +667,18 @@ static int compare_arch(Architecture a, Architecture b) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool image_filter_test(const ImageFilter *filter, PartitionDesignator d, const char *name) {
|
||||
assert(d < _PARTITION_DESIGNATOR_MAX);
|
||||
|
||||
if (d < 0) /* For unspecified designators we have no filter expression */
|
||||
return true;
|
||||
|
||||
if (!filter || !filter->pattern[d])
|
||||
return true;
|
||||
|
||||
return fnmatch(filter->pattern[d], strempty(name), FNM_NOESCAPE) == 0;
|
||||
}
|
||||
|
||||
static int dissect_image(
|
||||
DissectedImage *m,
|
||||
int fd,
|
||||
@@ -674,6 +686,7 @@ static int dissect_image(
|
||||
const VeritySettings *verity,
|
||||
const MountOptions *mount_options,
|
||||
const ImagePolicy *policy,
|
||||
const ImageFilter *filter,
|
||||
DissectImageFlags flags) {
|
||||
|
||||
sd_id128_t root_uuid = SD_ID128_NULL, root_verity_uuid = SD_ID128_NULL;
|
||||
@@ -792,6 +805,9 @@ static int dissect_image(
|
||||
|
||||
/* OK, we have found a file system, that's our root partition then. */
|
||||
|
||||
if (!image_filter_test(filter, PARTITION_ROOT, /* label= */ NULL)) /* do a filter check with an empty partition label */
|
||||
return -ECOMM;
|
||||
|
||||
r = image_policy_may_use(policy, PARTITION_ROOT);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -1006,6 +1022,9 @@ static int dissect_image(
|
||||
if (streq_ptr(label, "_empty"))
|
||||
continue;
|
||||
|
||||
if (!image_filter_test(filter, type.designator, label))
|
||||
continue;
|
||||
|
||||
log_debug("Dissecting %s partition with label %s and UUID %s",
|
||||
strna(partition_designator_to_string(type.designator)), strna(label), SD_ID128_TO_UUID_STRING(id));
|
||||
|
||||
@@ -1162,6 +1181,9 @@ static int dissect_image(
|
||||
/* We don't have a designator for SD_GPT_LINUX_GENERIC so check the UUID instead. */
|
||||
} else if (sd_id128_equal(type.uuid, SD_GPT_LINUX_GENERIC)) {
|
||||
|
||||
if (!image_filter_test(filter, PARTITION_ROOT, label))
|
||||
continue;
|
||||
|
||||
check_partition_flags(node, pflags,
|
||||
SD_GPT_FLAG_NO_AUTO | SD_GPT_FLAG_READ_ONLY | SD_GPT_FLAG_GROWFS);
|
||||
|
||||
@@ -1307,6 +1329,9 @@ static int dissect_image(
|
||||
if (pflags != 0x80) /* Bootable flag */
|
||||
continue;
|
||||
|
||||
if (!image_filter_test(filter, PARTITION_ROOT, /* label= */ NULL))
|
||||
continue;
|
||||
|
||||
if (generic_node)
|
||||
multiple_generic = true;
|
||||
else {
|
||||
@@ -1324,6 +1349,9 @@ static int dissect_image(
|
||||
sd_id128_t id = SD_ID128_NULL;
|
||||
const char *options = NULL;
|
||||
|
||||
if (!image_filter_test(filter, PARTITION_XBOOTLDR, /* label= */ NULL))
|
||||
continue;
|
||||
|
||||
r = image_policy_may_use(policy, PARTITION_XBOOTLDR);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -1487,12 +1515,6 @@ static int dissect_image(
|
||||
if (verity->designator >= 0 && !m->partitions[verity->designator].found)
|
||||
return -EADDRNOTAVAIL;
|
||||
|
||||
bool have_verity_sig_partition;
|
||||
if (verity->designator >= 0)
|
||||
have_verity_sig_partition = m->partitions[verity->designator == PARTITION_USR ? PARTITION_USR_VERITY_SIG : PARTITION_ROOT_VERITY_SIG].found;
|
||||
else
|
||||
have_verity_sig_partition = m->partitions[PARTITION_USR_VERITY_SIG].found || m->partitions[PARTITION_ROOT_VERITY_SIG].found;
|
||||
|
||||
if (verity->root_hash) {
|
||||
/* If we have an explicit root hash and found the partitions for it, then we are ready to use
|
||||
* Verity, set things up for it */
|
||||
@@ -1503,8 +1525,6 @@ static int dissect_image(
|
||||
|
||||
/* If we found a verity setup, then the root partition is necessarily read-only. */
|
||||
m->partitions[PARTITION_ROOT].rw = false;
|
||||
m->verity_ready = true;
|
||||
|
||||
} else {
|
||||
assert(verity->designator == PARTITION_USR);
|
||||
|
||||
@@ -1512,23 +1532,12 @@ static int dissect_image(
|
||||
return -EADDRNOTAVAIL;
|
||||
|
||||
m->partitions[PARTITION_USR].rw = false;
|
||||
m->verity_ready = true;
|
||||
}
|
||||
|
||||
if (m->verity_ready)
|
||||
m->verity_sig_ready = verity->root_hash_sig || have_verity_sig_partition;
|
||||
m->verity_ready = true;
|
||||
|
||||
} else if (have_verity_sig_partition) {
|
||||
|
||||
/* If we found an embedded signature partition, we are ready, too. */
|
||||
|
||||
m->verity_ready = m->verity_sig_ready = true;
|
||||
if (verity->designator >= 0)
|
||||
m->partitions[verity->designator == PARTITION_USR ? PARTITION_USR : PARTITION_ROOT].rw = false;
|
||||
else if (m->partitions[PARTITION_USR_VERITY_SIG].found)
|
||||
m->partitions[PARTITION_USR].rw = false;
|
||||
else if (m->partitions[PARTITION_ROOT_VERITY_SIG].found)
|
||||
m->partitions[PARTITION_ROOT].rw = false;
|
||||
if (verity->root_hash_sig)
|
||||
m->verity_sig_ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1538,20 +1547,29 @@ static int dissect_image(
|
||||
* we don't check encryption requirements here, because we haven't probed the file system yet, hence
|
||||
* don't know if this is encrypted or not) */
|
||||
for (PartitionDesignator di = 0; di < _PARTITION_DESIGNATOR_MAX; di++) {
|
||||
PartitionDesignator vi, si;
|
||||
PartitionPolicyFlags found_flags;
|
||||
|
||||
any = any || m->partitions[di].found;
|
||||
|
||||
vi = partition_verity_of(di);
|
||||
si = partition_verity_sig_of(di);
|
||||
|
||||
/* Determine the verity protection level for this partition. */
|
||||
found_flags = m->partitions[di].found ?
|
||||
(vi >= 0 && m->partitions[vi].found ?
|
||||
(si >= 0 && m->partitions[si].found ? PARTITION_POLICY_SIGNED : PARTITION_POLICY_VERITY) :
|
||||
PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED) :
|
||||
(m->partitions[di].ignored ? PARTITION_POLICY_UNUSED : PARTITION_POLICY_ABSENT);
|
||||
PartitionPolicyFlags found_flags;
|
||||
if (m->partitions[di].found) {
|
||||
found_flags = PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_UNUSED;
|
||||
|
||||
PartitionDesignator vi = partition_verity_of(di);
|
||||
if (vi >= 0 && m->partitions[vi].found) {
|
||||
found_flags |= PARTITION_POLICY_VERITY;
|
||||
|
||||
PartitionDesignator si = partition_verity_sig_of(di);
|
||||
if (si >= 0 && m->partitions[si].found)
|
||||
found_flags |= PARTITION_POLICY_SIGNED;
|
||||
}
|
||||
} else
|
||||
found_flags = m->partitions[di].ignored ? PARTITION_POLICY_UNUSED : PARTITION_POLICY_ABSENT;
|
||||
|
||||
if (DEBUG_LOGGING) {
|
||||
_cleanup_free_ char *s = NULL;
|
||||
(void) partition_policy_flags_to_string(found_flags, /* simplify= */ false, &s);
|
||||
log_debug("Found for designator %s: %s", partition_designator_to_string(di), strna(s));
|
||||
}
|
||||
|
||||
r = image_policy_check_protection(policy, di, found_flags);
|
||||
if (r < 0)
|
||||
@@ -1580,6 +1598,7 @@ int dissect_image_file(
|
||||
const VeritySettings *verity,
|
||||
const MountOptions *mount_options,
|
||||
const ImagePolicy *image_policy,
|
||||
const ImageFilter *image_filter,
|
||||
DissectImageFlags flags,
|
||||
DissectedImage **ret) {
|
||||
|
||||
@@ -1612,7 +1631,7 @@ int dissect_image_file(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dissect_image(m, fd, path, verity, mount_options, image_policy, flags);
|
||||
r = dissect_image(m, fd, path, verity, mount_options, image_policy, image_filter, flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -1682,12 +1701,13 @@ int dissect_image_file_and_warn(
|
||||
const VeritySettings *verity,
|
||||
const MountOptions *mount_options,
|
||||
const ImagePolicy *image_policy,
|
||||
const ImageFilter *image_filter,
|
||||
DissectImageFlags flags,
|
||||
DissectedImage **ret) {
|
||||
|
||||
return dissect_log_error(
|
||||
LOG_ERR,
|
||||
dissect_image_file(path, verity, mount_options, image_policy, flags, ret),
|
||||
dissect_image_file(path, verity, mount_options, image_policy, image_filter, flags, ret),
|
||||
path,
|
||||
verity);
|
||||
}
|
||||
@@ -3144,6 +3164,68 @@ int dissected_image_relinquish(DissectedImage *m) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void image_filter_done(ImageFilter *f) {
|
||||
assert(f);
|
||||
|
||||
FOREACH_ELEMENT(p, f->pattern)
|
||||
*p = mfree(*p);
|
||||
}
|
||||
|
||||
ImageFilter *image_filter_free(ImageFilter *f) {
|
||||
if (!f)
|
||||
return NULL;
|
||||
|
||||
image_filter_done(f);
|
||||
return mfree(f);
|
||||
}
|
||||
|
||||
int image_filter_parse(const char *s, ImageFilter **ret) {
|
||||
_cleanup_(image_filter_freep) ImageFilter *f = NULL;
|
||||
int r;
|
||||
|
||||
if (isempty(s)) {
|
||||
if (ret)
|
||||
*ret = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
_cleanup_free_ char *word = NULL;
|
||||
|
||||
r = extract_first_word(&s, &word, ":", EXTRACT_UNQUOTE|EXTRACT_DONT_COALESCE_SEPARATORS);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to extract word: %m");
|
||||
if (r == 0)
|
||||
break;
|
||||
|
||||
_cleanup_free_ char *designator = NULL, *pattern = NULL;
|
||||
const char *x = word;
|
||||
r = extract_many_words(&x, "=", EXTRACT_UNQUOTE|EXTRACT_DONT_COALESCE_SEPARATORS, &designator, &pattern);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to extract designator: %m");
|
||||
if (r != 2 || !isempty(x))
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to split: %m");
|
||||
|
||||
PartitionDesignator d = partition_designator_from_string(designator);
|
||||
if (d < 0)
|
||||
return log_debug_errno(d, "Failed to parse partition designator: %s", designator);
|
||||
|
||||
if (!f) {
|
||||
f = new0(ImageFilter, 1);
|
||||
if (!f)
|
||||
return log_oom_debug();
|
||||
} else if (f->pattern[d])
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Duplicate pattern for '%s', refusing.", partition_designator_to_string(d));
|
||||
|
||||
f->pattern[d] = TAKE_PTR(pattern);
|
||||
}
|
||||
|
||||
if (ret)
|
||||
*ret = TAKE_PTR(f);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *build_auxiliary_path(const char *image, const char *suffix) {
|
||||
const char *e;
|
||||
char *n;
|
||||
@@ -3384,20 +3466,52 @@ int verity_settings_load(
|
||||
return 1;
|
||||
}
|
||||
|
||||
int verity_settings_copy(VeritySettings *dest, const VeritySettings *source) {
|
||||
assert(dest);
|
||||
|
||||
if (!source) {
|
||||
*dest = VERITY_SETTINGS_DEFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
_cleanup_free_ void *rh = NULL;
|
||||
if (source->root_hash_size > 0) {
|
||||
rh = memdup(source->root_hash, source->root_hash_size);
|
||||
if (!rh)
|
||||
return log_oom_debug();
|
||||
}
|
||||
|
||||
_cleanup_free_ void *sig = NULL;
|
||||
if (source->root_hash_sig_size > 0) {
|
||||
sig = memdup(source->root_hash_sig, source->root_hash_sig_size);
|
||||
if (!sig)
|
||||
return log_oom_debug();
|
||||
}
|
||||
|
||||
_cleanup_free_ char *p = NULL;
|
||||
if (source->data_path) {
|
||||
p = strdup(source->data_path);
|
||||
if (!p)
|
||||
return log_oom_debug();
|
||||
}
|
||||
|
||||
*dest = (VeritySettings) {
|
||||
.root_hash = TAKE_PTR(rh),
|
||||
.root_hash_size = source->root_hash_size,
|
||||
.root_hash_sig = TAKE_PTR(sig),
|
||||
.root_hash_sig_size = source->root_hash_sig_size,
|
||||
.data_path = TAKE_PTR(p),
|
||||
.designator = source->designator,
|
||||
};
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int dissected_image_load_verity_sig_partition(
|
||||
DissectedImage *m,
|
||||
int fd,
|
||||
VeritySettings *verity) {
|
||||
|
||||
_cleanup_free_ void *root_hash = NULL, *root_hash_sig = NULL;
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
|
||||
size_t root_hash_size, root_hash_sig_size;
|
||||
_cleanup_free_ char *buf = NULL;
|
||||
PartitionDesignator d;
|
||||
DissectedPartition *p;
|
||||
sd_json_variant *rh, *sig;
|
||||
ssize_t n;
|
||||
char *e;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
@@ -3413,10 +3527,28 @@ int dissected_image_load_verity_sig_partition(
|
||||
if (r == 0)
|
||||
return 0;
|
||||
|
||||
d = partition_verity_sig_of(verity->designator < 0 ? PARTITION_ROOT : verity->designator);
|
||||
assert(d >= 0);
|
||||
PartitionDesignator dd = verity->designator;
|
||||
if (dd < 0) {
|
||||
if (m->partitions[PARTITION_ROOT_VERITY].found)
|
||||
dd = PARTITION_ROOT;
|
||||
else if (m->partitions[PARTITION_USR_VERITY].found)
|
||||
dd = PARTITION_USR;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
p = m->partitions + d;
|
||||
if (!m->partitions[dd].found)
|
||||
return 0;
|
||||
|
||||
PartitionDesignator dv = partition_verity_of(dd);
|
||||
assert(dv >= 0);
|
||||
if (!m->partitions[dv].found)
|
||||
return 0;
|
||||
|
||||
PartitionDesignator ds = partition_verity_sig_of(dd);
|
||||
assert(ds >= 0);
|
||||
|
||||
DissectedPartition *p = m->partitions + ds;
|
||||
if (!p->found)
|
||||
return 0;
|
||||
if (p->offset == UINT64_MAX || p->size == UINT64_MAX)
|
||||
@@ -3425,17 +3557,17 @@ int dissected_image_load_verity_sig_partition(
|
||||
if (p->size > 4*1024*1024) /* Signature data cannot possible be larger than 4M, refuse that */
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EFBIG), "Verity signature partition is larger than 4M, refusing.");
|
||||
|
||||
buf = new(char, p->size+1);
|
||||
_cleanup_free_ char *buf = new(char, p->size+1);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
n = pread(fd, buf, p->size, p->offset);
|
||||
ssize_t n = pread(fd, buf, p->size, p->offset);
|
||||
if (n < 0)
|
||||
return -ENOMEM;
|
||||
if ((uint64_t) n != p->size)
|
||||
return -EIO;
|
||||
|
||||
e = memchr(buf, 0, p->size);
|
||||
const char *e = memchr(buf, 0, p->size);
|
||||
if (e) {
|
||||
/* If we found a NUL byte then the rest of the data must be NUL too */
|
||||
if (!memeqzero(e, p->size - (e - buf)))
|
||||
@@ -3443,14 +3575,17 @@ int dissected_image_load_verity_sig_partition(
|
||||
} else
|
||||
buf[p->size] = 0;
|
||||
|
||||
r = sd_json_parse(buf, 0, &v, NULL, NULL);
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
|
||||
r = sd_json_parse(buf, 0, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to parse signature JSON data: %m");
|
||||
|
||||
rh = sd_json_variant_by_key(v, "rootHash");
|
||||
sd_json_variant *rh = sd_json_variant_by_key(v, "rootHash");
|
||||
if (!rh)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Signature JSON object lacks 'rootHash' field.");
|
||||
|
||||
_cleanup_free_ void *root_hash = NULL;
|
||||
size_t root_hash_size;
|
||||
r = sd_json_variant_unhex(rh, &root_hash, &root_hash_size);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to parse root hash field: %m");
|
||||
@@ -3466,10 +3601,12 @@ int dissected_image_load_verity_sig_partition(
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Root hash in signature JSON data (%s) doesn't match configured hash (%s).", strna(a), strna(b));
|
||||
}
|
||||
|
||||
sig = sd_json_variant_by_key(v, "signature");
|
||||
sd_json_variant *sig = sd_json_variant_by_key(v, "signature");
|
||||
if (!sig)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Signature JSON object lacks 'signature' field.");
|
||||
|
||||
_cleanup_free_ void *root_hash_sig = NULL;
|
||||
size_t root_hash_sig_size;
|
||||
r = sd_json_variant_unbase64(sig, &root_hash_sig, &root_hash_sig_size);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to parse signature field: %m");
|
||||
@@ -3480,9 +3617,78 @@ int dissected_image_load_verity_sig_partition(
|
||||
free_and_replace(verity->root_hash_sig, root_hash_sig);
|
||||
verity->root_hash_sig_size = root_hash_sig_size;
|
||||
|
||||
verity->designator = dd;
|
||||
|
||||
m->verity_ready = true;
|
||||
m->verity_sig_ready = true;
|
||||
m->partitions[dd].rw = false;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int dissected_image_guess_verity_roothash(
|
||||
DissectedImage *m,
|
||||
VeritySettings *verity) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(verity);
|
||||
|
||||
/* Guesses the Verity root hash from the partitions we found, taking into account that as per
|
||||
* https://uapi-group.org/specifications/specs/discoverable_partitions_specification/ the UUIDS of
|
||||
* the data and verity partitions are respectively the first and second halves of the dm-verity
|
||||
* roothash.
|
||||
*
|
||||
* Note of course that relying on this guesswork is mostly useful for later attestation, not so much
|
||||
* for a-priori security. */
|
||||
|
||||
if (verity->root_hash) /* Already loaded? */
|
||||
return 0;
|
||||
|
||||
r = secure_getenv_bool("SYSTEMD_DISSECT_VERITY_GUESS");
|
||||
if (r < 0 && r != -ENXIO)
|
||||
log_debug_errno(r, "Failed to parse $SYSTEMD_DISSECT_VERITY_GUESS, ignoring: %m");
|
||||
if (r == 0)
|
||||
return 0;
|
||||
|
||||
PartitionDesignator dd = verity->designator;
|
||||
if (dd < 0) {
|
||||
if (m->partitions[PARTITION_ROOT_VERITY].found)
|
||||
dd = PARTITION_ROOT;
|
||||
else if (m->partitions[PARTITION_USR_VERITY].found)
|
||||
dd = PARTITION_USR;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
DissectedPartition *d = m->partitions + dd;
|
||||
if (!d->found)
|
||||
return 0;
|
||||
|
||||
PartitionDesignator dv = partition_verity_of(dd);
|
||||
assert(dv >= 0);
|
||||
|
||||
DissectedPartition *p = m->partitions + dv;
|
||||
if (!p->found)
|
||||
return 0;
|
||||
|
||||
_cleanup_free_ uint8_t *rh = malloc(sizeof(sd_id128_t) * 2);
|
||||
if (!rh)
|
||||
return log_oom_debug();
|
||||
|
||||
memcpy(mempcpy(rh, &d->uuid, sizeof(sd_id128_t)), &p->uuid, sizeof(sd_id128_t));
|
||||
verity->root_hash = TAKE_PTR(rh);
|
||||
verity->root_hash_size = sizeof(sd_id128_t) * 2;
|
||||
|
||||
verity->designator = dd;
|
||||
|
||||
m->verity_ready = true;
|
||||
m->partitions[dd].rw = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dissected_image_acquire_metadata(
|
||||
DissectedImage *m,
|
||||
int userns_fd,
|
||||
@@ -3826,6 +4032,7 @@ int dissect_loop_device(
|
||||
const VeritySettings *verity,
|
||||
const MountOptions *mount_options,
|
||||
const ImagePolicy *image_policy,
|
||||
const ImageFilter *image_filter,
|
||||
DissectImageFlags flags,
|
||||
DissectedImage **ret) {
|
||||
|
||||
@@ -3843,7 +4050,15 @@ int dissect_loop_device(
|
||||
m->image_size = m->loop->device_size;
|
||||
m->sector_size = m->loop->sector_size;
|
||||
|
||||
r = dissect_image(m, loop->fd, loop->node, verity, mount_options, image_policy, flags);
|
||||
r = dissect_image(
|
||||
m,
|
||||
loop->fd,
|
||||
loop->node,
|
||||
verity,
|
||||
mount_options,
|
||||
image_policy,
|
||||
image_filter,
|
||||
flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -3861,6 +4076,7 @@ int dissect_loop_device_and_warn(
|
||||
const VeritySettings *verity,
|
||||
const MountOptions *mount_options,
|
||||
const ImagePolicy *image_policy,
|
||||
const ImageFilter *image_filter,
|
||||
DissectImageFlags flags,
|
||||
DissectedImage **ret) {
|
||||
|
||||
@@ -3868,7 +4084,7 @@ int dissect_loop_device_and_warn(
|
||||
|
||||
return dissect_log_error(
|
||||
LOG_ERR,
|
||||
dissect_loop_device(loop, verity, mount_options, image_policy, flags, ret),
|
||||
dissect_loop_device(loop, verity, mount_options, image_policy, image_filter, flags, ret),
|
||||
loop->backing_file ?: loop->node,
|
||||
verity);
|
||||
}
|
||||
@@ -3987,6 +4203,7 @@ int mount_image_privately_interactively(
|
||||
&verity,
|
||||
/* mount_options= */ NULL,
|
||||
image_policy,
|
||||
/* image_filter= */ NULL,
|
||||
flags,
|
||||
&dissected_image);
|
||||
if (r < 0)
|
||||
@@ -3996,6 +4213,10 @@ int mount_image_privately_interactively(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dissected_image_guess_verity_roothash(dissected_image, &verity);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dissected_image_decrypt_interactively(dissected_image, NULL, &verity, flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -4064,11 +4285,8 @@ int verity_dissect_and_mount(
|
||||
const char *dest,
|
||||
const MountOptions *options,
|
||||
const ImagePolicy *image_policy,
|
||||
const char *required_host_os_release_id,
|
||||
const char *required_host_os_release_version_id,
|
||||
const char *required_host_os_release_sysext_level,
|
||||
const char *required_host_os_release_confext_level,
|
||||
const char *required_sysext_scope,
|
||||
const ImageFilter *image_filter,
|
||||
const ExtensionReleaseData *extension_release_data,
|
||||
VeritySettings *verity,
|
||||
DissectedImage **ret_image) {
|
||||
|
||||
@@ -4082,7 +4300,7 @@ int verity_dissect_and_mount(
|
||||
assert(src);
|
||||
/* Verifying release metadata requires mounted image for now, so ensure the check is skipped when
|
||||
* opening an image without mounting it immediately (i.e.: 'dest' is NULL). */
|
||||
assert(!required_host_os_release_id || dest);
|
||||
assert(!extension_release_data || dest);
|
||||
|
||||
relax_extension_release_check = mount_options_relax_extension_release_checks(options);
|
||||
|
||||
@@ -4121,6 +4339,7 @@ int verity_dissect_and_mount(
|
||||
verity,
|
||||
options,
|
||||
image_policy,
|
||||
image_filter,
|
||||
dissect_image_flags,
|
||||
&dissected_image);
|
||||
/* No partition table? Might be a single-filesystem image, try again */
|
||||
@@ -4130,6 +4349,7 @@ int verity_dissect_and_mount(
|
||||
verity,
|
||||
options,
|
||||
image_policy,
|
||||
image_filter,
|
||||
dissect_image_flags | DISSECT_IMAGE_NO_PARTITION_TABLE,
|
||||
&dissected_image);
|
||||
if (r < 0)
|
||||
@@ -4139,6 +4359,10 @@ int verity_dissect_and_mount(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dissected_image_guess_verity_roothash(dissected_image, verity);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dissected_image_decrypt(
|
||||
dissected_image,
|
||||
NULL,
|
||||
@@ -4175,11 +4399,11 @@ int verity_dissect_and_mount(
|
||||
* First, check the distro ID. If that matches, then check the new SYSEXT_LEVEL value if
|
||||
* available, or else fallback to VERSION_ID. If neither is present (eg: rolling release),
|
||||
* then a simple match on the ID will be performed. */
|
||||
if (required_host_os_release_id) {
|
||||
if (extension_release_data && extension_release_data->os_release_id) {
|
||||
_cleanup_strv_free_ char **extension_release = NULL;
|
||||
ImageClass class = IMAGE_SYSEXT;
|
||||
|
||||
assert(!isempty(required_host_os_release_id));
|
||||
assert(!isempty(extension_release_data->os_release_id));
|
||||
|
||||
r = load_extension_release_pairs(dest, IMAGE_SYSEXT, dissected_image->image_name, relax_extension_release_check, &extension_release);
|
||||
if (r == -ENOENT) {
|
||||
@@ -4192,10 +4416,10 @@ int verity_dissect_and_mount(
|
||||
|
||||
r = extension_release_validate(
|
||||
dissected_image->image_name,
|
||||
required_host_os_release_id,
|
||||
required_host_os_release_version_id,
|
||||
class == IMAGE_SYSEXT ? required_host_os_release_sysext_level : required_host_os_release_confext_level,
|
||||
required_sysext_scope,
|
||||
extension_release_data->os_release_id,
|
||||
extension_release_data->os_release_version_id,
|
||||
class == IMAGE_SYSEXT ? extension_release_data->os_release_sysext_level : extension_release_data->os_release_confext_level,
|
||||
extension_release_data->os_release_extension_scope,
|
||||
extension_release,
|
||||
class);
|
||||
if (r == 0)
|
||||
@@ -4214,6 +4438,16 @@ int verity_dissect_and_mount(
|
||||
return 0;
|
||||
}
|
||||
|
||||
void extension_release_data_done(ExtensionReleaseData *data) {
|
||||
assert(data);
|
||||
|
||||
data->os_release_id = mfree(data->os_release_id);
|
||||
data->os_release_version_id = mfree(data->os_release_version_id);
|
||||
data->os_release_sysext_level = mfree(data->os_release_sysext_level);
|
||||
data->os_release_confext_level = mfree(data->os_release_confext_level);
|
||||
data->os_release_extension_scope = mfree(data->os_release_extension_scope);
|
||||
}
|
||||
|
||||
int get_common_dissect_directory(char **ret) {
|
||||
_cleanup_free_ char *t = NULL;
|
||||
int r;
|
||||
|
||||
@@ -19,6 +19,8 @@ typedef struct DissectedPartition DissectedPartition;
|
||||
typedef struct DecryptedImage DecryptedImage;
|
||||
typedef struct MountOptions MountOptions;
|
||||
typedef struct VeritySettings VeritySettings;
|
||||
typedef struct ImageFilter ImageFilter;
|
||||
typedef struct ExtensionReleaseData ExtensionReleaseData;
|
||||
|
||||
struct DissectedPartition {
|
||||
bool found:1;
|
||||
@@ -144,10 +146,23 @@ struct VeritySettings {
|
||||
PartitionDesignator designator;
|
||||
};
|
||||
|
||||
#define VERITY_SETTINGS_DEFAULT { \
|
||||
#define VERITY_SETTINGS_DEFAULT (VeritySettings) { \
|
||||
.designator = _PARTITION_DESIGNATOR_INVALID \
|
||||
}
|
||||
|
||||
struct ImageFilter {
|
||||
/* A per designator glob matching against the partition label */
|
||||
char *pattern[_PARTITION_DESIGNATOR_MAX];
|
||||
};
|
||||
|
||||
struct ExtensionReleaseData {
|
||||
char *os_release_id;
|
||||
char *os_release_version_id;
|
||||
char *os_release_sysext_level;
|
||||
char *os_release_confext_level;
|
||||
char *os_release_extension_scope;
|
||||
};
|
||||
|
||||
/* We include image-policy.h down here, since ImagePolicy wants a complete definition of PartitionDesignator first. */
|
||||
#include "image-policy.h"
|
||||
|
||||
@@ -161,10 +176,10 @@ static inline int probe_filesystem(const char *path, char **ret_fstype) {
|
||||
}
|
||||
|
||||
int dissect_log_error(int log_level, int r, const char *name, const VeritySettings *verity);
|
||||
int dissect_image_file(const char *path, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
|
||||
int dissect_image_file_and_warn(const char *path, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
|
||||
int dissect_loop_device(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
|
||||
int dissect_loop_device_and_warn(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
|
||||
int dissect_image_file(const char *path, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, const ImageFilter *filter, DissectImageFlags flags, DissectedImage **ret);
|
||||
int dissect_image_file_and_warn(const char *path, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, const ImageFilter *filter, DissectImageFlags flags, DissectedImage **ret);
|
||||
int dissect_loop_device(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, const ImageFilter *filter, DissectImageFlags flags, DissectedImage **ret);
|
||||
int dissect_loop_device_and_warn(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, const ImageFilter *filter, DissectImageFlags flags, DissectedImage **ret);
|
||||
|
||||
void dissected_image_close(DissectedImage *m);
|
||||
DissectedImage* dissected_image_unref(DissectedImage *m);
|
||||
@@ -201,6 +216,11 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DecryptedImage*, decrypted_image_unref);
|
||||
|
||||
int dissected_image_relinquish(DissectedImage *m);
|
||||
|
||||
void image_filter_done(ImageFilter *f);
|
||||
ImageFilter *image_filter_free(ImageFilter *f);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(ImageFilter*, image_filter_free);
|
||||
int image_filter_parse(const char *s, ImageFilter **ret);
|
||||
|
||||
int verity_settings_load(VeritySettings *verity, const char *image, const char *root_hash_path, const char *root_hash_sig_path);
|
||||
|
||||
static inline bool verity_settings_set(const VeritySettings *settings) {
|
||||
@@ -226,7 +246,10 @@ static inline bool verity_settings_data_covers(const VeritySettings *verity, Par
|
||||
verity->data_path;
|
||||
}
|
||||
|
||||
int verity_settings_copy(VeritySettings *dest, const VeritySettings *source);
|
||||
|
||||
int dissected_image_load_verity_sig_partition(DissectedImage *m, int fd, VeritySettings *verity);
|
||||
int dissected_image_guess_verity_roothash(DissectedImage *m, VeritySettings *verity);
|
||||
|
||||
bool dissected_image_verity_candidate(const DissectedImage *image, PartitionDesignator d);
|
||||
bool dissected_image_verity_ready(const DissectedImage *image, PartitionDesignator d);
|
||||
@@ -234,7 +257,7 @@ bool dissected_image_verity_sig_ready(const DissectedImage *image, PartitionDesi
|
||||
|
||||
int mount_image_privately_interactively(const char *path, const ImagePolicy *image_policy, DissectImageFlags flags, char **ret_directory, int *ret_dir_fd, LoopDevice **ret_loop_device);
|
||||
|
||||
int verity_dissect_and_mount(int src_fd, const char *src, const char *dest, const MountOptions *options, const ImagePolicy *image_policy, const char *required_host_os_release_id, const char *required_host_os_release_version_id, const char *required_host_os_release_sysext_level, const char *required_host_os_release_confext_level, const char *required_sysext_scope, VeritySettings *verity, DissectedImage **ret_image);
|
||||
int verity_dissect_and_mount(int src_fd, const char *src, const char *dest, const MountOptions *options, const ImagePolicy *image_policy, const ImageFilter *image_filter, const ExtensionReleaseData *required_release_data, VeritySettings *verity, DissectedImage **ret_image);
|
||||
|
||||
int dissect_fstype_ok(const char *fstype);
|
||||
|
||||
@@ -243,6 +266,8 @@ int probe_sector_size_prefer_ioctl(int fd, uint32_t *ret);
|
||||
|
||||
int partition_pick_mount_options(PartitionDesignator d, const char *fstype, bool rw, bool discard, char **ret_options, unsigned long *ret_ms_flags);
|
||||
|
||||
void extension_release_data_done(ExtensionReleaseData *data);
|
||||
|
||||
static inline const char* dissected_partition_fstype(const DissectedPartition *m) {
|
||||
assert(m);
|
||||
|
||||
|
||||
@@ -216,8 +216,8 @@ static int write_fsck_sysroot_service(
|
||||
|
||||
/* Writes out special versions of systemd-fsck-root.service and systemd-fsck-usr.service for use in
|
||||
* the initrd. The regular statically shipped versions of these unit files use / and /usr for as
|
||||
* paths, which doesn't match what we need for the initrd (where the dirs are /sysroot +
|
||||
* /sysusr/usr), hence we overwrite those versions here. */
|
||||
* paths, which doesn't match what we need for the initrd (where the dirs are /sysroot/ +
|
||||
* /sysusr/usr/), hence we overwrite those versions here. */
|
||||
|
||||
escaped = specifier_escape(what);
|
||||
if (!escaped)
|
||||
@@ -281,7 +281,7 @@ int generator_write_fsck_deps(
|
||||
assert(where);
|
||||
|
||||
/* Let's do an early exit if we are invoked for the root and /usr/ trees in the initrd, to avoid
|
||||
* generating confusing log messages */
|
||||
* generating confusing log messages. */
|
||||
if (in_initrd() && PATH_IN_SET(where, "/", "/usr")) {
|
||||
log_debug("Skipping fsck for %s in initrd.", where);
|
||||
return 0;
|
||||
@@ -1026,9 +1026,11 @@ int generator_write_veritysetup_service_section(
|
||||
if (!hash_what_escaped)
|
||||
return log_oom();
|
||||
|
||||
roothash_escaped = specifier_escape(roothash);
|
||||
if (!roothash_escaped)
|
||||
return log_oom();
|
||||
if (roothash) {
|
||||
roothash_escaped = specifier_escape(roothash);
|
||||
if (!roothash_escaped)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
if (options) {
|
||||
options_escaped = specifier_escape(options);
|
||||
@@ -1043,7 +1045,7 @@ int generator_write_veritysetup_service_section(
|
||||
"RemainAfterExit=yes\n"
|
||||
"ExecStart=" SYSTEMD_VERITYSETUP_PATH " attach '%s' '%s' '%s' '%s' '%s'\n"
|
||||
"ExecStop=" SYSTEMD_VERITYSETUP_PATH " detach '%s'\n",
|
||||
name_escaped, data_what_escaped, hash_what_escaped, roothash_escaped, strempty(options_escaped),
|
||||
name_escaped, data_what_escaped, hash_what_escaped, empty_to_dash(roothash_escaped), strempty(options_escaped),
|
||||
name_escaped);
|
||||
|
||||
return 0;
|
||||
@@ -1086,21 +1088,39 @@ bool generator_soft_rebooted(void) {
|
||||
return (cached = (u > 0));
|
||||
}
|
||||
|
||||
GptAutoRoot parse_gpt_auto_root(const char *value) {
|
||||
GptAutoRoot parse_gpt_auto_root(const char *switch_name, const char *value) {
|
||||
assert(switch_name);
|
||||
assert(value);
|
||||
|
||||
/* Parses the 'gpt-auto'/'gpt-auto-root' parameters to root= */
|
||||
/* Parses the 'gpt-auto'/'gpt-auto-root'/'dissect'/'dissect-force' parameters to root=
|
||||
*
|
||||
* note that we are not using a regular string table here, because the mode names don't fully match
|
||||
* the parameter names. And root= being something else is not an error. */
|
||||
|
||||
if (streq(value, "gpt-auto")) {
|
||||
log_debug("Enabling root partition auto-detection (respecting factory reset mode), root= is explicitly set to 'gpt-auto'.");
|
||||
log_debug("Enabling partition auto-detection (respecting factory reset mode), %s is explicitly set to 'gpt-auto'.", switch_name);
|
||||
return GPT_AUTO_ROOT_ON;
|
||||
}
|
||||
|
||||
if (streq(value, "gpt-auto-force")) {
|
||||
log_debug("Enabling root partition auto-detection (ignoring factory reset mode), root= is explicitly set to 'gpt-auto-force'.");
|
||||
log_debug("Enabling partition auto-detection (ignoring factory reset mode), %s is explicitly set to 'gpt-auto-force'.", switch_name);
|
||||
return GPT_AUTO_ROOT_FORCE;
|
||||
}
|
||||
|
||||
log_debug("Disabling root partition auto-detection, root= is neither unset, nor set to 'gpt-auto' or 'gpt-auto-force'.");
|
||||
if (streq(value, "dissect")) {
|
||||
log_debug("Enabling partition auto-detection via full image dissection (respecting factory reset mode), %s is explicitly set to 'dissect'.", switch_name);
|
||||
return GPT_AUTO_ROOT_DISSECT;
|
||||
}
|
||||
|
||||
if (streq(value, "dissect-force")) {
|
||||
log_debug("Enabling partition auto-detection via full image dissection (ignoring factory reset mode), %s is explicitly set to 'dissect-force'.", switch_name);
|
||||
return GPT_AUTO_ROOT_DISSECT_FORCE;
|
||||
}
|
||||
|
||||
if (streq(value, "off"))
|
||||
log_debug("Disabling partition auto-detection, %s handling is explicitly turned off.", switch_name);
|
||||
else
|
||||
log_debug("Disabling partition auto-detection, %s is neither unset, nor set to 'gpt-auto', 'gpt-auto-force', 'dissect' or 'dissect-force'.", switch_name);
|
||||
|
||||
return GPT_AUTO_ROOT_OFF;
|
||||
}
|
||||
|
||||
@@ -122,11 +122,13 @@ bool generator_soft_rebooted(void);
|
||||
exit_failure_if_negative)
|
||||
|
||||
typedef enum GptAutoRoot {
|
||||
GPT_AUTO_ROOT_OFF = 0, /* root= set to something else */
|
||||
GPT_AUTO_ROOT_ON, /* root= set explicitly to "gpt-auto" */
|
||||
GPT_AUTO_ROOT_FORCE, /* root= set explicitly to "gpt-auto-force" → ignores factory reset mode */
|
||||
GPT_AUTO_ROOT_OFF = 0, /* root= set to something else */
|
||||
GPT_AUTO_ROOT_ON, /* root= set explicitly to "gpt-auto" */
|
||||
GPT_AUTO_ROOT_FORCE, /* root= set explicitly to "gpt-auto-force" → ignores factory reset mode */
|
||||
GPT_AUTO_ROOT_DISSECT, /* root= set to "dissect" */
|
||||
GPT_AUTO_ROOT_DISSECT_FORCE, /* root= set to "dissect-force" → ignores factory reset mode */
|
||||
_GPT_AUTO_ROOT_MAX,
|
||||
_GPT_AUTO_ROOT_INVALID = -EINVAL,
|
||||
} GptAutoRoot;
|
||||
|
||||
GptAutoRoot parse_gpt_auto_root(const char *value);
|
||||
GptAutoRoot parse_gpt_auto_root(const char *switch_name, const char *value);
|
||||
|
||||
@@ -969,11 +969,8 @@ static int mount_in_namespace_legacy(
|
||||
mount_tmp,
|
||||
options,
|
||||
image_policy,
|
||||
/* required_host_os_release_id= */ NULL,
|
||||
/* required_host_os_release_version_id= */ NULL,
|
||||
/* required_host_os_release_sysext_level= */ NULL,
|
||||
/* required_host_os_release_confext_level= */ NULL,
|
||||
/* required_sysext_scope= */ NULL,
|
||||
/* image_filter= */ NULL,
|
||||
/* extension_release_data= */ NULL,
|
||||
/* verity= */ NULL,
|
||||
/* ret_image= */ NULL);
|
||||
else
|
||||
@@ -1193,11 +1190,8 @@ static int mount_in_namespace(
|
||||
/* dest= */ NULL,
|
||||
options,
|
||||
image_policy,
|
||||
/* required_host_os_release_id= */ NULL,
|
||||
/* required_host_os_release_version_id= */ NULL,
|
||||
/* required_host_os_release_sysext_level= */ NULL,
|
||||
/* required_host_os_release_confext_level= */ NULL,
|
||||
/* required_sysext_scope= */ NULL,
|
||||
/* image_filter= */ NULL,
|
||||
/* extension_release_data= */ NULL,
|
||||
/* verity= */ NULL,
|
||||
&img);
|
||||
if (r < 0)
|
||||
|
||||
@@ -1802,6 +1802,7 @@ static int merge_subprocess(
|
||||
&verity_settings,
|
||||
/* mount_options= */ NULL,
|
||||
pick_image_policy(img),
|
||||
/* image_filter= */ NULL,
|
||||
flags,
|
||||
&m);
|
||||
if (r < 0)
|
||||
@@ -1814,6 +1815,12 @@ static int merge_subprocess(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dissected_image_guess_verity_roothash(
|
||||
m,
|
||||
&verity_settings);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dissected_image_decrypt_interactively(
|
||||
m, NULL,
|
||||
&verity_settings,
|
||||
|
||||
@@ -106,6 +106,7 @@ simple_tests += files(
|
||||
'test-hostname-setup.c',
|
||||
'test-hostname-util.c',
|
||||
'test-id128.c',
|
||||
'test-image-filter.c',
|
||||
'test-image-policy.c',
|
||||
'test-import-util.c',
|
||||
'test-in-addr-prefix-util.c',
|
||||
|
||||
39
src/test/test-image-filter.c
Normal file
39
src/test/test-image-filter.c
Normal file
@@ -0,0 +1,39 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "dissect-image.h"
|
||||
#include "tests.h"
|
||||
|
||||
TEST(image_filter) {
|
||||
_cleanup_(image_filter_freep) ImageFilter *f = NULL;
|
||||
|
||||
ASSERT_OK(image_filter_parse(NULL, &f));
|
||||
ASSERT_NULL(f);
|
||||
ASSERT_OK(image_filter_parse("", &f));
|
||||
ASSERT_NULL(f);
|
||||
|
||||
ASSERT_OK(image_filter_parse("root=*", &f));
|
||||
ASSERT_NOT_NULL(f);
|
||||
ASSERT_STREQ(f->pattern[PARTITION_ROOT], "*");
|
||||
f = image_filter_free(f);
|
||||
|
||||
ASSERT_OK(image_filter_parse("usr=foox?:root=kn*arz", &f));
|
||||
ASSERT_NOT_NULL(f);
|
||||
ASSERT_STREQ(f->pattern[PARTITION_ROOT], "kn*arz");
|
||||
ASSERT_STREQ(f->pattern[PARTITION_USR], "foox?");
|
||||
f = image_filter_free(f);
|
||||
|
||||
ASSERT_OK(image_filter_parse("usr=foox?:root=kn*arz:home=wumpi", &f));
|
||||
ASSERT_NOT_NULL(f);
|
||||
ASSERT_STREQ(f->pattern[PARTITION_ROOT], "kn*arz");
|
||||
ASSERT_STREQ(f->pattern[PARTITION_USR], "foox?");
|
||||
ASSERT_STREQ(f->pattern[PARTITION_HOME], "wumpi");
|
||||
f = image_filter_free(f);
|
||||
|
||||
ASSERT_ERROR(image_filter_parse("usr=foox?:root=kn*arz:home=wumpi:schlumpf=smurf", &f), EINVAL);
|
||||
ASSERT_ERROR(image_filter_parse(":", &f), EINVAL);
|
||||
ASSERT_ERROR(image_filter_parse("::", &f), EINVAL);
|
||||
ASSERT_ERROR(image_filter_parse("-", &f), EINVAL);
|
||||
ASSERT_ERROR(image_filter_parse("root=knuff:root=knuff", &f), EINVAL);
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_INFO);
|
||||
@@ -81,7 +81,14 @@ static void* thread_func(void *ptr) {
|
||||
|
||||
log_notice("Acquired loop device %s, will mount on %s", loop->node, mounted);
|
||||
|
||||
r = dissect_loop_device(loop, NULL, NULL, NULL, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected);
|
||||
r = dissect_loop_device(
|
||||
loop,
|
||||
/* verity= */ NULL,
|
||||
/* mount_options= */ NULL,
|
||||
/* image_policy= */ NULL,
|
||||
/* image_filter= */ NULL,
|
||||
DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES,
|
||||
&dissected);
|
||||
if (r < 0)
|
||||
log_error_errno(r, "Failed dissect loopback device %s: %m", loop->node);
|
||||
assert_se(r >= 0);
|
||||
@@ -218,7 +225,14 @@ static int run(int argc, char *argv[]) {
|
||||
sfdisk = NULL;
|
||||
|
||||
#if HAVE_BLKID
|
||||
assert_se(dissect_image_file(p, NULL, NULL, NULL, 0, &dissected) >= 0);
|
||||
assert_se(dissect_image_file(
|
||||
p,
|
||||
/* verity= */ NULL,
|
||||
/* mount_options= */ NULL,
|
||||
/* image_policy= */ NULL,
|
||||
/* image_filter= */ NULL,
|
||||
/* flags= */ 0,
|
||||
&dissected) >= 0);
|
||||
verify_dissected_image(dissected);
|
||||
dissected = dissected_image_unref(dissected);
|
||||
#endif
|
||||
@@ -232,7 +246,14 @@ static int run(int argc, char *argv[]) {
|
||||
assert_se(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_EX, &loop) >= 0);
|
||||
|
||||
#if HAVE_BLKID
|
||||
assert_se(dissect_loop_device(loop, NULL, NULL, NULL, DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected) >= 0);
|
||||
assert_se(dissect_loop_device(
|
||||
loop,
|
||||
/* verity= */ NULL,
|
||||
/* mount_options= */ NULL,
|
||||
/* image_policy= */ NULL,
|
||||
/* image_filter= */ NULL,
|
||||
DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES,
|
||||
&dissected) >= 0);
|
||||
verify_dissected_image(dissected);
|
||||
|
||||
FOREACH_STRING(fs, "vfat", "ext4") {
|
||||
@@ -268,12 +289,26 @@ static int run(int argc, char *argv[]) {
|
||||
|
||||
/* Try to read once, without pinning or adding partitions, i.e. by only accessing the whole block
|
||||
* device. */
|
||||
assert_se(dissect_loop_device(loop, NULL, NULL, NULL, 0, &dissected) >= 0);
|
||||
assert_se(dissect_loop_device(
|
||||
loop,
|
||||
/* verity= */ NULL,
|
||||
/* mount_options= */ NULL,
|
||||
/* image_policy= */ NULL,
|
||||
/* image_filter= */ NULL,
|
||||
/* flags= */ 0,
|
||||
&dissected) >= 0);
|
||||
verify_dissected_image_harder(dissected);
|
||||
dissected = dissected_image_unref(dissected);
|
||||
|
||||
/* Now go via the loopback device after all, but this time add/pin, because now we want to mount it. */
|
||||
assert_se(dissect_loop_device(loop, NULL, NULL, NULL, DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected) >= 0);
|
||||
assert_se(dissect_loop_device(
|
||||
loop,
|
||||
/* verity= */ NULL,
|
||||
/* mount_options= */ NULL,
|
||||
/* image_policy= */ NULL,
|
||||
/* image_filter= */ NULL,
|
||||
DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES,
|
||||
&dissected) >= 0);
|
||||
verify_dissected_image_harder(dissected);
|
||||
|
||||
assert_se(mkdtemp_malloc(NULL, &mounted) >= 0);
|
||||
|
||||
@@ -22,6 +22,7 @@ libudevd_core_sources = files(
|
||||
'net/link-config.c',
|
||||
'udev-builtin.c',
|
||||
'udev-builtin-btrfs.c',
|
||||
'udev-builtin-dissect_image.c',
|
||||
'udev-builtin-factory_reset.c',
|
||||
'udev-builtin-hwdb.c',
|
||||
'udev-builtin-input_id.c',
|
||||
|
||||
412
src/udev/udev-builtin-dissect_image.c
Normal file
412
src/udev/udev-builtin-dissect_image.c
Normal file
@@ -0,0 +1,412 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "blockdev-util.h"
|
||||
#include "device-util.h"
|
||||
#include "dissect-image.h"
|
||||
#include "fd-util.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "image-policy.h"
|
||||
#include "initrd-util.h"
|
||||
#include "loop-util.h"
|
||||
#include "proc-cmdline.h"
|
||||
#include "udev-builtin.h"
|
||||
|
||||
static ImagePolicy *arg_image_policy = NULL;
|
||||
static VeritySettings arg_verity_settings = VERITY_SETTINGS_DEFAULT;
|
||||
static ImageFilter *arg_image_filter = NULL;
|
||||
|
||||
static int acquire_image_policy(ImagePolicy **ret) {
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
_cleanup_free_ char *value = NULL;
|
||||
r = proc_cmdline_get_key("systemd.image_policy", /* flags= */ 0, &value);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read systemd.image_policy= kernel command line switch: %m");
|
||||
if (r == 0) {
|
||||
*ret = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = image_policy_from_string(value, ret);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse image policy '%s': %m", value);
|
||||
|
||||
if (DEBUG_LOGGING) {
|
||||
_cleanup_free_ char *s = NULL;
|
||||
|
||||
(void) image_policy_to_string(*ret, /* simplify= */ true, &s);
|
||||
log_debug("Loaded image policy: %s", strna(s));
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int acquire_verity_settings(VeritySettings *ret) {
|
||||
_cleanup_(verity_settings_done) VeritySettings verity = VERITY_SETTINGS_DEFAULT;
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
_cleanup_free_ char *h = NULL;
|
||||
r = proc_cmdline_get_key("roothash", /* flags= */ 0, &h);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read roothash= kernel command line switch: %m");
|
||||
if (r > 0)
|
||||
verity.designator = PARTITION_ROOT;
|
||||
|
||||
_cleanup_free_ char *uh = NULL;
|
||||
r = proc_cmdline_get_key("usrhash", /* flags= */ 0, &uh);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read usrhash= kernel command line switch: %m");
|
||||
if (r > 0) {
|
||||
if (h)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Both roothash= and usrhash= specified, refusing.");
|
||||
|
||||
h = TAKE_PTR(uh);
|
||||
verity.designator = PARTITION_USR;
|
||||
}
|
||||
|
||||
if (h) {
|
||||
r = unhexmem(h, &verity.root_hash, &verity.root_hash_size);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse root hash from kernel command line switch: %m");
|
||||
}
|
||||
|
||||
*ret = TAKE_GENERIC(verity, VeritySettings, VERITY_SETTINGS_DEFAULT);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int acquire_image_filter(ImageFilter **ret) {
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
_cleanup_free_ char *value = NULL;
|
||||
r = proc_cmdline_get_key("systemd.image_filter", /* flags= */ 0, &value);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read systemd.image_filter= kernel command line switch: %m");
|
||||
if (r == 0) {
|
||||
*ret = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = image_filter_parse(value, ret);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse image filter '%s': %m", value);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int verb_probe(UdevEvent *event, sd_device *dev) {
|
||||
int r;
|
||||
|
||||
assert(event);
|
||||
assert(dev);
|
||||
|
||||
/* This is invoked on 'main' block devices to probe the partition table. We will generate some
|
||||
* properties with general image information, and then a bunch of properties for each partition, with
|
||||
* the partition index in the variable name. These fields will be copied into partition block devices
|
||||
* when the dissect_image builtin is later called with the "copy" verb, i.e. in verb_copy() below. */
|
||||
|
||||
const char *devnode;
|
||||
r = sd_device_get_devname(dev, &devnode);
|
||||
if (r < 0)
|
||||
return log_device_debug_errno(dev, r, "Failed to get device node: %m");
|
||||
if (block_device_is_whole_disk(dev) <= 0) {
|
||||
log_device_debug(dev, "Must be invoked on whole block device (was invoked in '%s), ignoring.", devnode);
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = blockdev_partscan_enabled(dev);
|
||||
if (r < 0)
|
||||
return log_device_debug_errno(dev, r, "Failed to determine if block device '%s' supports partitions: %m", devnode);
|
||||
if (r == 0) {
|
||||
log_device_debug(dev, "Invoked on block device '%s' that lacks partition scanning, ignoring.", devnode);
|
||||
return 0;
|
||||
}
|
||||
|
||||
_cleanup_(loop_device_unrefp) LoopDevice *loop = NULL;
|
||||
r = loop_device_open(dev, O_RDONLY, LOCK_SH, &loop);
|
||||
if (ERRNO_IS_NEG_DEVICE_ABSENT(r)) {
|
||||
log_device_debug(dev, "Device absent while opening block device '%s', ignoring.", devnode);
|
||||
return 0;
|
||||
}
|
||||
if (r < 0)
|
||||
return log_device_debug_errno(dev, r, "Failed to open block device '%s: %m", devnode);
|
||||
|
||||
const ImagePolicy *image_policy = arg_image_policy ?: &image_policy_host;
|
||||
_cleanup_(dissected_image_unrefp) DissectedImage *image = NULL;
|
||||
r = dissect_loop_device(
|
||||
loop,
|
||||
&arg_verity_settings,
|
||||
/* mount_options= */ NULL,
|
||||
image_policy,
|
||||
arg_image_filter,
|
||||
DISSECT_IMAGE_READ_ONLY|
|
||||
DISSECT_IMAGE_GPT_ONLY|
|
||||
DISSECT_IMAGE_USR_NO_ROOT|
|
||||
DISSECT_IMAGE_ALLOW_EMPTY,
|
||||
&image);
|
||||
if (r == -ERFKILL && !in_initrd()) {
|
||||
/* If we transitioned into the main system and we couldn't dissect the image with the full
|
||||
* policy, let's see if it works if we set the policies for /usr/ and the root fs out of the
|
||||
* policy. After all, we already made our choices, there's no point in insisting on the
|
||||
* policy here. */
|
||||
|
||||
static const PartitionDesignator ignore_designators[] = {
|
||||
PARTITION_ROOT,
|
||||
PARTITION_ROOT_VERITY,
|
||||
PARTITION_ROOT_VERITY_SIG,
|
||||
PARTITION_USR,
|
||||
PARTITION_USR_VERITY,
|
||||
PARTITION_USR_VERITY_SIG,
|
||||
};
|
||||
|
||||
_cleanup_(image_policy_freep) ImagePolicy *image_policy_mangled = NULL;
|
||||
r = image_policy_ignore_designators(
|
||||
image_policy,
|
||||
ignore_designators,
|
||||
ELEMENTSOF(ignore_designators),
|
||||
&image_policy_mangled);
|
||||
if (r < 0)
|
||||
return log_device_debug_errno(dev, r, "Failed to remove root/usr partitions from image policy: %m");
|
||||
|
||||
if (image_policy_equal(image_policy, image_policy_mangled))
|
||||
r = -ERFKILL; /* restore original error, if this didn't change anything */
|
||||
else {
|
||||
if (DEBUG_LOGGING) {
|
||||
_cleanup_free_ char *a = NULL, *b = NULL;
|
||||
|
||||
(void) image_policy_to_string(image_policy, /* simplify= */ false, &a);
|
||||
(void) image_policy_to_string(image_policy_mangled, /* simplify= */ false, &b);
|
||||
|
||||
log_device_debug_errno(dev, ERFKILL, "Couldn't dissect block device with regular policy '%s', retrying with policy where root/usr are set to ignore '%s'.", a, b);
|
||||
}
|
||||
|
||||
r = dissect_loop_device(
|
||||
loop,
|
||||
&arg_verity_settings,
|
||||
/* mount_options= */ NULL,
|
||||
image_policy_mangled,
|
||||
arg_image_filter,
|
||||
DISSECT_IMAGE_READ_ONLY|
|
||||
DISSECT_IMAGE_GPT_ONLY|
|
||||
DISSECT_IMAGE_USR_NO_ROOT|
|
||||
DISSECT_IMAGE_ALLOW_EMPTY,
|
||||
&image);
|
||||
}
|
||||
}
|
||||
if (IN_SET(r, -ENOPKG, -ENOMSG, -ENXIO, -ENOTUNIQ)) {
|
||||
log_device_debug_errno(dev, r, "Device does not carry a GPT disk label with suitable partitions, ignoring.");
|
||||
return 0;
|
||||
}
|
||||
if (r == -ERFKILL) {
|
||||
log_device_debug_errno(dev, r, "Device carries GPT disk label that doesn't match our image policy, ignoring.");
|
||||
return 0;
|
||||
}
|
||||
if (r < 0)
|
||||
return log_device_debug_errno(dev, r, "Failed to dissect disk image: %m");
|
||||
|
||||
/* Let's try to load verity data from the image now, so that we can attach it to the device via udev
|
||||
* properties */
|
||||
_cleanup_(verity_settings_done) VeritySettings verity = VERITY_SETTINGS_DEFAULT;
|
||||
r = verity_settings_copy(&verity, &arg_verity_settings);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dissected_image_load_verity_sig_partition(image, loop->fd, &verity);
|
||||
if (r < 0)
|
||||
return log_device_debug_errno(dev, r, "Failed to load verity signature data from image: %m");
|
||||
|
||||
r = dissected_image_guess_verity_roothash(image, &verity);
|
||||
if (r < 0)
|
||||
return log_device_debug_errno(dev, r, "Failed to guess root hash from image: %m");
|
||||
|
||||
/* Marker that we determined this to be a suitable image */
|
||||
(void) udev_builtin_add_property(event, "ID_DISSECT_IMAGE", "1");
|
||||
|
||||
/* Output the primary architecture this image is intended for */
|
||||
Architecture a = dissected_image_architecture(image);
|
||||
if (a >= 0)
|
||||
(void) udev_builtin_add_property(event, "ID_DISSECT_IMAGE_ARCHITECTURE", architecture_to_string(a));
|
||||
|
||||
/* And now output the intended designator and architecture (if it applies) for all partitions we
|
||||
* found and think belong to this system */
|
||||
FOREACH_ELEMENT(p, image->partitions) {
|
||||
PartitionDesignator d = p - image->partitions;
|
||||
if (!p->found)
|
||||
continue;
|
||||
|
||||
assert(p->partno > 0);
|
||||
|
||||
/* Indicate designator for partition */
|
||||
_cleanup_free_ char *df = NULL;
|
||||
if (asprintf(&df, "ID_DISSECT_PART%i_DESIGNATOR", p->partno) < 0)
|
||||
return log_oom_debug();
|
||||
(void) udev_builtin_add_property(event, df, partition_designator_to_string(d));
|
||||
|
||||
if (p->architecture >= 0) {
|
||||
_cleanup_free_ char *f = NULL;
|
||||
if (asprintf(&f, "ID_DISSECT_PART%i_ARCHITECTURE", p->partno) < 0)
|
||||
return log_oom_debug();
|
||||
(void) udev_builtin_add_property(event, f, architecture_to_string(p->architecture));
|
||||
}
|
||||
|
||||
/* Indicate whether this partition has verity protection */
|
||||
PartitionDesignator dv = partition_verity_of(d);
|
||||
if (dv >= 0 && image->partitions[dv].found) {
|
||||
_cleanup_free_ char *f = NULL;
|
||||
if (asprintf(&f, "ID_DISSECT_PART%i_HAS_VERITY", p->partno) < 0)
|
||||
return log_oom_debug();
|
||||
|
||||
(void) udev_builtin_add_property(event, f, "1");
|
||||
}
|
||||
|
||||
dv = partition_verity_sig_of(d);
|
||||
if (dv >= 0 && image->partitions[dv].found) {
|
||||
_cleanup_free_ char *f = NULL;
|
||||
if (asprintf(&f, "ID_DISSECT_PART%i_HAS_VERITY_SIG", p->partno) < 0)
|
||||
return log_oom_debug();
|
||||
|
||||
(void) udev_builtin_add_property(event, f, "1");
|
||||
}
|
||||
|
||||
if (d == verity.designator) {
|
||||
if (verity.root_hash_size > 0) {
|
||||
_cleanup_free_ char *f = NULL;
|
||||
if (asprintf(&f, "ID_DISSECT_PART%i_ROOTHASH", p->partno) < 0)
|
||||
return log_oom_debug();
|
||||
|
||||
_cleanup_free_ char *h = hexmem(verity.root_hash, verity.root_hash_size);
|
||||
if (!h)
|
||||
return log_oom_debug();
|
||||
|
||||
(void) udev_builtin_add_property(event, f, h);
|
||||
}
|
||||
|
||||
if (verity.root_hash_sig_size > 0) {
|
||||
_cleanup_free_ char *f = NULL;
|
||||
if (asprintf(&f, "ID_DISSECT_PART%i_ROOTHASH_SIG", p->partno) < 0)
|
||||
return log_oom_debug();
|
||||
|
||||
_cleanup_free_ char *h = NULL;
|
||||
if (base64mem(verity.root_hash_sig, verity.root_hash_sig_size, &h) < 0)
|
||||
return log_oom_debug();
|
||||
|
||||
(void) udev_builtin_add_property(event, f, h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int verb_copy(UdevEvent *event, sd_device *dev) {
|
||||
int r;
|
||||
|
||||
assert(event);
|
||||
assert(dev);
|
||||
|
||||
/* This is called for the partition block devices, and will copy the per-partition properties we
|
||||
* probed on the main block device into the partition device */
|
||||
|
||||
const char *devnode;
|
||||
r = sd_device_get_devname(dev, &devnode);
|
||||
if (r < 0)
|
||||
return log_device_debug_errno(dev, r, "Failed to get device node: %m");
|
||||
|
||||
if (!device_in_subsystem(dev, "block"))
|
||||
return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invoked on non-block device '%s', refusing: %m", devnode);
|
||||
if (!device_is_devtype(dev, "partition"))
|
||||
return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invoked on non-partition block device '%s', refusing: %m", devnode);
|
||||
|
||||
sd_device *parent;
|
||||
r = sd_device_get_parent(dev, &parent);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get parent of device '%s': %m", devnode);
|
||||
|
||||
if (!device_in_subsystem(parent, "block"))
|
||||
return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Parent of block device '%s' is not a block device, refusing: %m", devnode);
|
||||
if (!device_is_devtype(parent, "disk"))
|
||||
return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Parent of block device '%s' is not a whole block device, refusing: %m", devnode);
|
||||
|
||||
const char *partn;
|
||||
r = sd_device_get_property_value(dev, "PARTN", &partn);
|
||||
if (r < 0)
|
||||
return log_device_debug_errno(dev, r, "Failed to get partition number of partition block device '%s': %m", devnode);
|
||||
|
||||
FOREACH_STRING(f, "_DESIGNATOR", "_ARCHITECTURE", "_HAS_VERITY", "_HAS_VERITY_SIG", "_ROOTHASH", "_ROOTHASH_SIG") {
|
||||
/* The property on the parent device contains the partition number */
|
||||
_cleanup_free_ char *p = strjoin("ID_DISSECT_PART", partn, f);
|
||||
if (!p)
|
||||
return log_oom_debug();
|
||||
|
||||
const char *v;
|
||||
r = sd_device_get_property_value(parent, p, &v);
|
||||
if (r == -ENOENT)
|
||||
continue;
|
||||
if (r < 0)
|
||||
return log_device_debug_errno(dev, r, "Failed to get '%s' property of parent of '%s': %m", p, devnode);
|
||||
|
||||
/* When we copy this property to the partition we drop the partition number, so that we have
|
||||
* a constant field name */
|
||||
_cleanup_free_ char *c = strjoin("ID_DISSECT_PART", f);
|
||||
if (!c)
|
||||
return log_oom_debug();
|
||||
|
||||
(void) udev_builtin_add_property(event, c, v);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int builtin_dissect_image(UdevEvent *event, int argc, char *argv[]) {
|
||||
sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev);
|
||||
|
||||
if (argc != 2)
|
||||
return log_device_warning_errno(
|
||||
dev, SYNTHETIC_ERRNO(EINVAL), "%s: expected single argument.", argv[0]);
|
||||
|
||||
if (streq(argv[1], "probe"))
|
||||
return verb_probe(event, dev);
|
||||
if (streq(argv[1], "copy"))
|
||||
return verb_copy(event, dev);
|
||||
|
||||
return log_device_warning_errno(
|
||||
dev, SYNTHETIC_ERRNO(EINVAL), "%s: unknown verb '%s'", argv[0], argv[1]);
|
||||
}
|
||||
|
||||
static int builtin_dissect_image_init(void) {
|
||||
int r;
|
||||
|
||||
r = acquire_image_policy(&arg_image_policy);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = acquire_verity_settings(&arg_verity_settings);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = acquire_image_filter(&arg_image_filter);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void builtin_dissect_image_exit(void) {
|
||||
arg_image_policy = image_policy_free(arg_image_policy);
|
||||
verity_settings_done(&arg_verity_settings);
|
||||
arg_image_filter = image_filter_free(arg_image_filter);
|
||||
}
|
||||
|
||||
const UdevBuiltin udev_builtin_dissect_image = {
|
||||
.name = "dissect_image",
|
||||
.cmd = builtin_dissect_image,
|
||||
.init = builtin_dissect_image_init,
|
||||
.exit = builtin_dissect_image_exit,
|
||||
.help = "Dissect Disk Images",
|
||||
.run_once = true,
|
||||
};
|
||||
@@ -15,6 +15,7 @@ static const UdevBuiltin *const builtins[_UDEV_BUILTIN_MAX] = {
|
||||
[UDEV_BUILTIN_BLKID] = &udev_builtin_blkid,
|
||||
#endif
|
||||
[UDEV_BUILTIN_BTRFS] = &udev_builtin_btrfs,
|
||||
[UDEV_BUILTIN_DISSECT_IMAGE] = &udev_builtin_dissect_image,
|
||||
[UDEV_BUILTIN_FACTORY_RESET] = &udev_builtin_factory_reset,
|
||||
[UDEV_BUILTIN_HWDB] = &udev_builtin_hwdb,
|
||||
[UDEV_BUILTIN_INPUT_ID] = &udev_builtin_input_id,
|
||||
|
||||
@@ -37,6 +37,7 @@ typedef struct UdevBuiltin {
|
||||
extern const UdevBuiltin udev_builtin_blkid;
|
||||
#endif
|
||||
extern const UdevBuiltin udev_builtin_btrfs;
|
||||
extern const UdevBuiltin udev_builtin_dissect_image;
|
||||
extern const UdevBuiltin udev_builtin_factory_reset;
|
||||
extern const UdevBuiltin udev_builtin_hwdb;
|
||||
extern const UdevBuiltin udev_builtin_input_id;
|
||||
|
||||
@@ -40,6 +40,7 @@ typedef enum UdevBuiltinCommand {
|
||||
UDEV_BUILTIN_BLKID,
|
||||
#endif
|
||||
UDEV_BUILTIN_BTRFS,
|
||||
UDEV_BUILTIN_DISSECT_IMAGE,
|
||||
UDEV_BUILTIN_FACTORY_RESET,
|
||||
UDEV_BUILTIN_HWDB,
|
||||
UDEV_BUILTIN_INPUT_ID,
|
||||
@@ -64,6 +65,7 @@ typedef enum UdevReloadFlags {
|
||||
UDEV_RELOAD_BUILTIN_BLKID = 1u << UDEV_BUILTIN_BLKID,
|
||||
#endif
|
||||
UDEV_RELOAD_BUILTIN_BTRFS = 1u << UDEV_BUILTIN_BTRFS,
|
||||
UDEV_RELOAD_BUILTIN_DISSECT_IMAGE = 1u << UDEV_BUILTIN_DISSECT_IMAGE,
|
||||
UDEV_RELOAD_BUILTIN_FACTORY_RESET = 1u << UDEV_BUILTIN_FACTORY_RESET,
|
||||
UDEV_RELOAD_BUILTIN_HWDB = 1u << UDEV_BUILTIN_HWDB,
|
||||
UDEV_RELOAD_BUILTIN_INPUT_ID = 1u << UDEV_BUILTIN_INPUT_ID,
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "sd-device.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "cryptsetup-util.h"
|
||||
#include "fileio.h"
|
||||
@@ -19,7 +21,7 @@
|
||||
#include "terminal-util.h"
|
||||
#include "verbs.h"
|
||||
|
||||
static char *arg_hash = NULL;
|
||||
static char *arg_hash = NULL; /* the hash algorithm */
|
||||
static bool arg_superblock = true;
|
||||
static int arg_format = 1;
|
||||
static uint64_t arg_data_block_size = 4096;
|
||||
@@ -33,7 +35,9 @@ static uint32_t arg_activate_flags = CRYPT_ACTIVATE_READONLY;
|
||||
static char *arg_fec_what = NULL;
|
||||
static uint64_t arg_fec_offset = 0;
|
||||
static uint64_t arg_fec_roots = 2;
|
||||
static char *arg_root_hash_signature = NULL;
|
||||
static void *arg_root_hash_signature = NULL;
|
||||
static size_t arg_root_hash_signature_size = 0;
|
||||
static bool arg_root_hash_signature_auto = false;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_hash, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_salt, freep);
|
||||
@@ -60,26 +64,52 @@ static int help(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int save_roothashsig_option(const char *option, bool strict) {
|
||||
static int parse_roothashsig_option(const char *option, bool strict) {
|
||||
_cleanup_free_ void *rhs = NULL;
|
||||
size_t rhss = 0;
|
||||
bool set_auto = false;
|
||||
int r;
|
||||
|
||||
if (path_is_absolute(option) || startswith(option, "base64:")) {
|
||||
if (!HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||
"Activation of verity device with signature requested, but cryptsetup does not support crypt_activate_by_signed_key().");
|
||||
assert(option);
|
||||
|
||||
r = free_and_strdup_warn(&arg_root_hash_signature, option);
|
||||
const char *value = startswith(option, "base64:");
|
||||
if (value) {
|
||||
r = unbase64mem(value, &rhs, &rhss);
|
||||
if (r < 0)
|
||||
return r;
|
||||
return log_error_errno(r, "Failed to parse root hash signature '%s': %m", option);
|
||||
|
||||
return true;
|
||||
}
|
||||
} else if (path_is_absolute(option)) {
|
||||
r = read_full_file_full(
|
||||
AT_FDCWD,
|
||||
option,
|
||||
/* offset= */ UINT64_MAX,
|
||||
/* size= */ SIZE_MAX,
|
||||
READ_FULL_FILE_CONNECT_SOCKET,
|
||||
/* bind_name= */ NULL,
|
||||
(char**) &rhs,
|
||||
&rhss);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read root hash signature: %m");
|
||||
|
||||
if (!strict)
|
||||
} else if (streq(option, "auto"))
|
||||
/* auto → Derive signature from udev property ID_DISSECT_PART_ROOTHASH_SIG */
|
||||
set_auto = true;
|
||||
else if (strict)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"root-hash-signature= expects either full path to signature file or "
|
||||
"base64 string encoding signature prefixed by base64:.");
|
||||
else
|
||||
return false;
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"root-hash-signature= expects either full path to signature file or "
|
||||
"base64 string encoding signature prefixed by base64:.");
|
||||
|
||||
if (!HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||
"Activation of verity device with signature requested, but cryptsetup does not support crypt_activate_by_signed_key().");
|
||||
|
||||
free_and_replace(arg_root_hash_signature, rhs);
|
||||
arg_root_hash_signature_size = rhss;
|
||||
arg_root_hash_signature_auto = set_auto;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int parse_block_size(const char *t, uint64_t *size) {
|
||||
@@ -105,7 +135,7 @@ static int parse_options(const char *options) {
|
||||
int r;
|
||||
|
||||
/* backward compatibility with the obsolete ROOTHASHSIG positional argument */
|
||||
r = save_roothashsig_option(options, /* strict= */ false);
|
||||
r = parse_roothashsig_option(options, /* strict= */ false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0) {
|
||||
@@ -264,7 +294,7 @@ static int parse_options(const char *options) {
|
||||
|
||||
arg_fec_roots = u;
|
||||
} else if ((val = startswith(word, "root-hash-signature="))) {
|
||||
r = save_roothashsig_option(val, /* strict= */ true);
|
||||
r = parse_roothashsig_option(val, /* strict= */ true);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -277,10 +307,10 @@ static int parse_options(const char *options) {
|
||||
|
||||
static int verb_attach(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
|
||||
_cleanup_free_ void *m = NULL;
|
||||
_cleanup_free_ void *rh = NULL;
|
||||
struct crypt_params_verity p = {};
|
||||
crypt_status_info status;
|
||||
size_t l;
|
||||
size_t rh_size = 0;
|
||||
int r;
|
||||
|
||||
assert(argc >= 5);
|
||||
@@ -294,10 +324,48 @@ static int verb_attach(int argc, char *argv[], void *userdata) {
|
||||
if (!filename_is_valid(volume))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume);
|
||||
|
||||
r = unhexmem(root_hash, &m, &l);
|
||||
if (options) {
|
||||
r = parse_options(options);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse options: %m");
|
||||
}
|
||||
|
||||
if (empty_or_dash(root_hash) || streq_ptr(root_hash, "auto"))
|
||||
root_hash = NULL;
|
||||
|
||||
_cleanup_(sd_device_unrefp) sd_device *datadev = NULL;
|
||||
if (!root_hash || arg_root_hash_signature_auto) {
|
||||
r = sd_device_new_from_path(&datadev, data_device);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to acquire udev object for data device '%s': %m", data_device);
|
||||
}
|
||||
|
||||
if (!root_hash) {
|
||||
/* If no literal root hash is specified try to determine it automatically from the
|
||||
* ID_DISSECT_PART_ROOTHASH udev property. */
|
||||
r = sd_device_get_property_value(ASSERT_PTR(datadev), "ID_DISSECT_PART_ROOTHASH", &root_hash);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "No root hash specified, and device doesn't carry ID_DISSECT_PART_ROOTHASH property, cannot determine root hash.");
|
||||
}
|
||||
|
||||
r = unhexmem(root_hash, &rh, &rh_size);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse root hash: %m");
|
||||
|
||||
if (arg_root_hash_signature_auto) {
|
||||
assert(!arg_root_hash_signature);
|
||||
assert(arg_root_hash_signature_size == 0);
|
||||
|
||||
const char *t;
|
||||
r = sd_device_get_property_value(ASSERT_PTR(datadev), "ID_DISSECT_PART_ROOTHASH_SIG", &t);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Automatic root hash signature pick up requested, and device doesn't carry ID_DISSECT_PART_ROOTHASH_SIG property, cannot determine root hash signature.");
|
||||
|
||||
r = unbase64mem(t, &arg_root_hash_signature, &arg_root_hash_signature_size);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to decode root hash signature data from udev data device: %m");
|
||||
}
|
||||
|
||||
r = crypt_init(&cd, verity_device);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to open verity device %s: %m", verity_device);
|
||||
@@ -310,12 +378,6 @@ static int verb_attach(int argc, char *argv[], void *userdata) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (options) {
|
||||
r = parse_options(options);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse options: %m");
|
||||
}
|
||||
|
||||
if (arg_superblock) {
|
||||
p = (struct crypt_params_verity) {
|
||||
.fec_device = arg_fec_what,
|
||||
@@ -353,32 +415,23 @@ static int verb_attach(int argc, char *argv[], void *userdata) {
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to configure data device: %m");
|
||||
|
||||
if (arg_root_hash_signature) {
|
||||
if (arg_root_hash_signature_size > 0) {
|
||||
#if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
|
||||
_cleanup_free_ char *hash_sig = NULL;
|
||||
size_t hash_sig_size;
|
||||
char *value;
|
||||
r = crypt_activate_by_signed_key(cd, volume, rh, rh_size, arg_root_hash_signature, arg_root_hash_signature_size, arg_activate_flags);
|
||||
if (r < 0) {
|
||||
log_info_errno(r, "Unable to activate verity device '%s' with root hash signature (%m), retrying without.", volume);
|
||||
|
||||
if ((value = startswith(arg_root_hash_signature, "base64:"))) {
|
||||
r = unbase64mem(value, (void*) &hash_sig, &hash_sig_size);
|
||||
r = crypt_activate_by_volume_key(cd, volume, rh, rh_size, arg_activate_flags);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse root hash signature '%s': %m", arg_root_hash_signature);
|
||||
} else {
|
||||
r = read_full_file_full(
|
||||
AT_FDCWD, arg_root_hash_signature, UINT64_MAX, SIZE_MAX,
|
||||
READ_FULL_FILE_CONNECT_SOCKET,
|
||||
NULL,
|
||||
&hash_sig, &hash_sig_size);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read root hash signature: %m");
|
||||
return log_error_errno(r, "Failed to activate verity device '%s' both with and without root hash signature: %m", volume);
|
||||
|
||||
log_info("Activation of verity device '%s' succeeded without root hash signature.", volume);
|
||||
}
|
||||
|
||||
r = crypt_activate_by_signed_key(cd, volume, m, l, hash_sig, hash_sig_size, arg_activate_flags);
|
||||
#else
|
||||
assert_not_reached();
|
||||
#endif
|
||||
} else
|
||||
r = crypt_activate_by_volume_key(cd, volume, m, l, arg_activate_flags);
|
||||
r = crypt_activate_by_volume_key(cd, volume, rh, rh_size, arg_activate_flags);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set up verity device '%s': %m", volume);
|
||||
|
||||
|
||||
@@ -1194,6 +1194,7 @@ static int discover_root(char **ret) {
|
||||
/* verity= */ NULL,
|
||||
/* mount_options= */ NULL,
|
||||
/* image_policy= */ NULL,
|
||||
/* image_filter= */ NULL,
|
||||
/* flags= */ 0,
|
||||
&image);
|
||||
if (r < 0)
|
||||
|
||||
Reference in New Issue
Block a user