Merge pull request #25608 from poettering/dissect-moar

dissect: add dissection policies
This commit is contained in:
Lennart Poettering 2023-04-12 13:46:08 +02:00 committed by GitHub
commit 3af48a86d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
73 changed files with 2569 additions and 269 deletions

29
TODO
View File

@ -293,9 +293,6 @@ Features:
userspace to allow ordering boots (for example in journalctl). The counter
would be monotonically increased on every boot.
* systemd-sysext: for sysext DDIs picked up via EFI stub, set much stricter
image policy by default
* pam_systemd_home: add module parameter to control whether to only accept
only password or only pcks11/fido2 auth, and then use this to hook nicely
into two of the three PAM stacks gdm provides.
@ -836,9 +833,6 @@ Features:
virtio-fs.
* for vendor-built signed initrds:
- make sysext run in the initrd
- sysext should pick up sysext images from /.extra/ in the initrd, and insist
on verification if in secureboot mode
- kernel-install should be able to install pre-built unified kernel images in
type #2 drop-in dir in the ESP.
- kernel-install should be able install encrypted creds automatically for
@ -1040,9 +1034,6 @@ Features:
CapabilityQuintet we already have. (This likely allows us to drop libcap
dep in the base OS image)
* sysext: automatically activate sysext images dropped in via new sd-stub
sysext pickup logic. (must insist on verity + signature on those though)
* add concept for "exitrd" as inverse of "initrd", that we can transition to at
shutdown, and has similar security semantics. This should then take the place
of dracut's shutdown logic. Should probably support sysexts too. Care needs
@ -1072,22 +1063,6 @@ Features:
keys of /etc/crypttab. That way people can store/provide the roothash
externally and provide to us on demand only.
* add high-level lockdown level for GPT dissection logic: e.g. an enum that can
be ANY (to mount anything), TRUSTED (to require that /usr is on signed
verity, but rest doesn't matter), LOCKEDDOWN (to require that everything is
on signed verity, except for ESP), SUPERLOCKDOWN (like LOCKEDDOWN but ESP not
allowed). And then maybe some flavours of that that declare what is expected
from home/srv/var… Then, add a new cmdline flag to all tools that parse such
images, to configure this. Also, add a kernel cmdline option for this, to be
honoured by the gpt auto generator.
Alternative idea: add "systemd.gpt_auto_policy=rhvs" to allow gpt-auto to
only mount root dir, /home/ dir, /var/ and /srv/, but nothing else. And then
minor extension to this, insisting on encryption, for example
"systemd.gpt_auto_policy=r+v+h" to require encryption for root and var but not
for /home/, and similar. Similar add --image-dissect-policy= to tools that
take --image= that take the same short string.
* we probably should extend the root verity hash of the root fs into some PCR
on boot. (i.e. maybe add a veritytab option tpm2-measure=12 or so to measure
it into PCR 12); Similar: we probably should extend the LUKS volume key of
@ -1100,10 +1075,6 @@ Features:
(i.e. sysext, root verity) from those inherently local (i.e. encryption key),
which is useful if they shall be signed separately.
* add a "policy" to the dissection logic. i.e. a bit mask what is OK to mount,
what must be read-only, what requires encryption, and what requires
authentication.
* in uefi stub: query firmware regarding which PCR banks are being used, store
that in EFI var. then use this when enrolling TPM2 in cryptsetup to verify
that the selected PCRs actually are used by firmware.

View File

@ -305,6 +305,8 @@
switch of the same name.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="image-policy-open" />
<varlistentry>
<term><option>--install-source=</option></term>
<listitem><para>When installing binaries with <option>--root=</option> or

View File

@ -268,6 +268,8 @@
switch of the same name.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="image-policy-open" />
<varlistentry>
<term><option>-q</option></term>
<term><option>--quiet</option></term>

View File

@ -182,6 +182,8 @@
switch of the same name.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="image-policy-open" />
<varlistentry>
<term><option>--namespace=<replaceable>NAMESPACE</replaceable></option></term>

View File

@ -396,12 +396,22 @@
<term><varname>rd.systemd.gpt_auto=</varname></term>
<listitem>
<para>Configures whether GPT based partition auto-discovery
shall be attempted. For details, see
<para>Configures whether GPT-based partition auto-discovery shall be attempted. For details, see
<citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.image_policy=</varname></term>
<term><varname>rd.systemd.image_policy=</varname></term>
<listitem><para>When GPT-based partition auto-discovery is used, configures the image dissection
policy string to apply, as per
<citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>. For
details see
<citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.default_timeout_start_sec=</varname></term>

View File

@ -3167,6 +3167,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s IPCNamespacePath = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s RootImagePolicy = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s MountImagePolicy = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s ExtensionImagePolicy = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s KillMode = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly i KillSignal = ...;
@ -3724,6 +3730,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<!--property IPCNamespacePath is not documented!-->
<!--property RootImagePolicy is not documented!-->
<!--property MountImagePolicy is not documented!-->
<!--property ExtensionImagePolicy is not documented!-->
<!--property KillMode is not documented!-->
<!--property KillSignal is not documented!-->
@ -4380,6 +4392,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<variablelist class="dbus-property" generated="True" extra-ref="IPCNamespacePath"/>
<variablelist class="dbus-property" generated="True" extra-ref="RootImagePolicy"/>
<variablelist class="dbus-property" generated="True" extra-ref="MountImagePolicy"/>
<variablelist class="dbus-property" generated="True" extra-ref="ExtensionImagePolicy"/>
<variablelist class="dbus-property" generated="True" extra-ref="KillMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="KillSignal"/>
@ -5147,6 +5165,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s IPCNamespacePath = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s RootImagePolicy = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s MountImagePolicy = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s ExtensionImagePolicy = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s KillMode = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly i KillSignal = ...;
@ -5718,6 +5742,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<!--property IPCNamespacePath is not documented!-->
<!--property RootImagePolicy is not documented!-->
<!--property MountImagePolicy is not documented!-->
<!--property ExtensionImagePolicy is not documented!-->
<!--property KillMode is not documented!-->
<!--property KillSignal is not documented!-->
@ -6356,6 +6386,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<variablelist class="dbus-property" generated="True" extra-ref="IPCNamespacePath"/>
<variablelist class="dbus-property" generated="True" extra-ref="RootImagePolicy"/>
<variablelist class="dbus-property" generated="True" extra-ref="MountImagePolicy"/>
<variablelist class="dbus-property" generated="True" extra-ref="ExtensionImagePolicy"/>
<variablelist class="dbus-property" generated="True" extra-ref="KillMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="KillSignal"/>
@ -7002,6 +7038,12 @@ node /org/freedesktop/systemd1/unit/home_2emount {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s IPCNamespacePath = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s RootImagePolicy = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s MountImagePolicy = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s ExtensionImagePolicy = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s KillMode = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly i KillSignal = ...;
@ -7501,6 +7543,12 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<!--property IPCNamespacePath is not documented!-->
<!--property RootImagePolicy is not documented!-->
<!--property MountImagePolicy is not documented!-->
<!--property ExtensionImagePolicy is not documented!-->
<!--property KillMode is not documented!-->
<!--property KillSignal is not documented!-->
@ -8057,6 +8105,12 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<variablelist class="dbus-property" generated="True" extra-ref="IPCNamespacePath"/>
<variablelist class="dbus-property" generated="True" extra-ref="RootImagePolicy"/>
<variablelist class="dbus-property" generated="True" extra-ref="MountImagePolicy"/>
<variablelist class="dbus-property" generated="True" extra-ref="ExtensionImagePolicy"/>
<variablelist class="dbus-property" generated="True" extra-ref="KillMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="KillSignal"/>
@ -8830,6 +8884,12 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s IPCNamespacePath = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s RootImagePolicy = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s MountImagePolicy = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s ExtensionImagePolicy = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s KillMode = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly i KillSignal = ...;
@ -9315,6 +9375,12 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
<!--property IPCNamespacePath is not documented!-->
<!--property RootImagePolicy is not documented!-->
<!--property MountImagePolicy is not documented!-->
<!--property ExtensionImagePolicy is not documented!-->
<!--property KillMode is not documented!-->
<!--property KillSignal is not documented!-->
@ -9857,6 +9923,12 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
<variablelist class="dbus-property" generated="True" extra-ref="IPCNamespacePath"/>
<variablelist class="dbus-property" generated="True" extra-ref="RootImagePolicy"/>
<variablelist class="dbus-property" generated="True" extra-ref="MountImagePolicy"/>
<variablelist class="dbus-property" generated="True" extra-ref="ExtensionImagePolicy"/>
<variablelist class="dbus-property" generated="True" extra-ref="KillMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="KillSignal"/>

View File

@ -1107,6 +1107,7 @@ manpages = [
['systemd.environment-generator', '7', [], 'ENABLE_ENVIRONMENT_D'],
['systemd.exec', '5', [], ''],
['systemd.generator', '7', [], ''],
['systemd.image-policy', '7', [], ''],
['systemd.journal-fields', '7', [], ''],
['systemd.kill', '5', [], ''],
['systemd.link', '5', [], ''],

View File

@ -86,4 +86,15 @@
numerical signal numbers and the program will exit immediately.</para>
</listitem>
</varlistentry>
<varlistentry id='image-policy-open'>
<term><option>--image-policy=<replaceable>policy</replaceable></option></term>
<listitem><para>Takes an image policy string as argument, as per
<citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>. The
policy is enforced when operating on the disk image specified via <option>--image=</option>, see
above. If not specified defaults to the <literal>*</literal> policy, i.e. all recognized file systems
in the image are used.</para></listitem>
</varlistentry>
</variablelist>

View File

@ -2276,6 +2276,8 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
switch of the same name.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="image-policy-open" />
<varlistentry>
<term><option>--runtime</option></term>

View File

@ -162,6 +162,12 @@
<arg choice="plain">fdstore</arg>
<arg choice="opt" rep="repeat"><replaceable>UNIT</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-analyze</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="plain">image-policy</arg>
<arg choice="plain" rep="repeat"><replaceable>POLICY</replaceable></arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
@ -840,6 +846,39 @@ stored sock 0:8 4213190 - socket:[4213190] ro
"DEVNO".</para>
</refsect2>
<refsect2>
<title><command>systemd-analyze image-policy <optional><replaceable>POLICY</replaceable></optional></command></title>
<para>This command analyzes the specified image policy string, as per
<citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>. The
policy is normalized and simplified. For each currently defined partition identifier (as per the <ulink
url="https://uapi-group.org/specifications/specs/discoverable_partitions_specification">Discoverable
Partitions Specification</ulink> the effect of the image policy string is shown in tabular form.</para>
<example>
<title>Example Output</title>
<programlisting>$ systemd-analyze image-policy swap=encrypted:usr=read-only-on+verity:root=encrypted
Analyzing policy: root=encrypted:usr=verity+read-only-on:swap=encrypted
Long form: root=encrypted:usr=verity+read-only-on:swap=encrypted:=unused+absent
PARTITION MODE READ-ONLY GROWFS
root encrypted - -
usr verity yes -
home ignore - -
srv ignore - -
esp ignore - -
xbootldr ignore - -
swap encrypted - -
root-verity ignore - -
usr-verity unprotected yes -
root-verity-sig ignore - -
usr-verity-sig ignore - -
tmp ignore - -
var ignore - -
default ignore - -</programlisting>
</example>
</refsect2>
</refsect1>
<refsect1>
@ -967,6 +1006,8 @@ stored sock 0:8 4213190 - socket:[4213190] ro
operate on files inside the specified image path <replaceable>PATH</replaceable>.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="image-policy-open" />
<varlistentry>
<term><option>--offline=<replaceable>BOOL</replaceable></option></term>

View File

@ -281,6 +281,20 @@
on.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--validate</option></term>
<listitem><para>Validates the partition arrangement of a disk image (DDI), and ensures it matches the
image policy specified via <option>--image-policy=</option>, if one is specified. This parses the
partition table and probes the file systems in the image, but does not attempt to mount them (nor to
set up disk encryption/authentication via LUKS/Verity). It does this taking the configured image
dissection policy into account. Since this operation does not mount file systems, this command
unlike all other commands implemented by this tool requires no privileges other than the ability to
access the specified file. Prints "OK" and returns zero if the image appears to be in order and
matches the specified image dissection policy. Otherwise prints an error message and returns
non-zero.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
@ -405,6 +419,7 @@
<command>cfdisk /dev/loop/by-ref/quux</command>.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="image-policy-open" />
<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" />

View File

@ -249,6 +249,16 @@
</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.image_policy=</varname></term>
<term><varname>rd.systemd.image_policy=</varname></term>
<listitem><para>Takes an image dissection policy string as argument (as per
<citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>),
and allows enforcing a policy on dissection and use of the automatically discovered GPT partition
table entries.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>root=</varname></term>
<term><varname>rootfstype=</varname></term>

View File

@ -95,6 +95,8 @@
tree.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="image-policy-open" />
<varlistentry>
<term><option>--commit</option></term>
<listitem><para>Commit a transient machine ID to disk. This

View File

@ -310,6 +310,17 @@
together with <option>--directory=</option>, <option>--template=</option>.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--image-policy=<replaceable>policy</replaceable></option></term>
<listitem><para>Takes an image policy string as argument, as per
<citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>. The
policy is enforced when operating on the disk image specified via <option>--image=</option>, see
above. If not specified defaults to
<literal>root=verity+signed+encrypted+unprotected+absent:usr=verity+signed+encrypted+unprotected+absent:home=encrypted+unprotected+absent:srv=encrypted+unprotected+absent:esp=unprotected+absent:xbootldr=unprotected+absent:tmp=encrypted+unprotected+absent:var=encrypted+unprotected+absent</literal>,
i.e. all recognized file systems in the image are used, but not the swap partition.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--oci-bundle=</option></term>

View File

@ -269,6 +269,8 @@
<option>--root=</option>, see above.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="image-policy-open" />
<varlistentry>
<term><option>--seed=</option></term>

View File

@ -99,7 +99,12 @@
carrying large binary images, however are still useful for carrying symlinks to them. The primary place
for installing system extensions is <filename>/var/lib/extensions/</filename>. Any directories found in
these search directories are considered directory based extension images; any files with the
<filename>.raw</filename> suffix are considered disk image based extension images.</para>
<filename>.raw</filename> suffix are considered disk image based extension images. When invoked in the
initrd, the additional directory <filename>/.extra/sysext/</filename> is included in the directories that
are searched for extension images. Note however, that by default a tighter image policy applies to images
found there, though, see below. This directory is populated by
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> with
extension images found in the system's EFI System Partition.</para>
<para>During boot OS extension images are activated automatically, if the
<filename>systemd-sysext.service</filename> is enabled. Note that this service runs only after the
@ -270,6 +275,19 @@
whether the version information included in the images matches the host or not.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--image-policy=<replaceable>policy</replaceable></option></term>
<listitem><para>Takes an image policy string as argument, as per
<citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>. The
policy is enforced when operating on system extension disk images. If not specified defaults to
<literal>root=verity+signed+encrypted+unprotected+absent:usr=verity+signed+encrypted+unprotected+absent</literal>,
i.e. only the root and <filename>/usr/</filename> file systems in the image are used. When run in the
initrd and operating on a system extension image stored in the <filename>/.extra/sysext/</filename>
directory a slightly stricter policy is used by default:
<literal>root=signed+absent:usr=signed+absent</literal>, see above for details.</para></listitem>
</varlistentry>
<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" />
@ -286,7 +304,8 @@
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>
<citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>
</para>
</refsect1>

View File

@ -229,6 +229,8 @@
inside the specified disk image.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="image-policy-open" />
<varlistentry>
<term><option>--instances-max=</option></term>
<term><option>-m</option></term>

View File

@ -80,6 +80,8 @@
switch of the same name.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="image-policy-open" />
<varlistentry>
<term><option>--replace=<replaceable>PATH</replaceable></option></term>
<listitem><para>When this option is given, one or more positional arguments

View File

@ -202,6 +202,8 @@
<para>Implies <option>-E</option>.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="image-policy-open" />
<varlistentry>
<term><option>--replace=<replaceable>PATH</replaceable></option></term>
<listitem><para>When this option is given, one or more positional arguments

View File

@ -260,6 +260,30 @@
<xi:include href="system-only.xml" xpointer="singular"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>RootImagePolicy=</varname></term>
<term><varname>MountImagePolicy=</varname></term>
<term><varname>ExtensionImagePolicy=</varname></term>
<listitem><para>Takes an image policy string as per
<citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>
to use when mounting the disk images (DDI) specified in <varname>RootImage=</varname>,
<varname>MountImage=</varname>, <varname>ExtensionImage=</varname>, respectively. If not specified
the following policy string is the default for <varname>RootImagePolicy=</varname> and <varname>MountImagePolicy</varname>:</para>
<programlisting>root=verity+signed+encrypted+unprotected+absent: \
usr=verity+signed+encrypted+unprotected+absent: \
home=encrypted+unprotected+absent: \
srv=encrypted+unprotected+absent: \
tmp=encrypted+unprotected+absent: \
var=encrypted+unprotected+absent</programlisting>
<para>The default policy for <varname>ExtensionImagePolicy=</varname> is:</para>
<programlisting>root=verity+signed+encrypted+unprotected+absent: \
usr=verity+signed+encrypted+unprotected+absent</programlisting></listitem>
</varlistentry>
<varlistentry>
<term><varname>MountAPIVFS=</varname></term>

View File

@ -0,0 +1,191 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="systemd.image-policy">
<refentryinfo>
<title>systemd.image-policy</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd.image-policy</refentrytitle>
<manvolnum>7</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd.image-policy</refname>
<refpurpose>Disk Image Dissection Policy</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 policy may be specified controlling which partitions to
mount and what kind of cryptographic protection to require. Such a disk image dissection policy is a
string that contains per-partition-type rules, separated by colons (<literal>:</literal>). The individual
rules consist of a partition identifier, an equal sign (<literal>=</literal>), and one or more flags
which may be set per partition. If multiple flags are specified per partition they are separated by a
plus sign (<literal>+</literal>).</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. If the partition identifier is left empty it
defines the <emphasis>default</emphasis> policy for partitions defined in the Discoverable Parition
Specification for which no policy flags are explicitly listed in the policy string.</para>
<para>The following partition policy flags are defined that dictate the existence/absence, the use, and
the protection level of partitions:</para>
<itemizedlist>
<listitem><para><option>unprotected</option> for partitions that shall exist and be used, but shall
come without cryptographic protection, lacking both Verity authentication and LUKS
encryption.</para></listitem>
<listitem><para><option>verity</option> for partitions that shall exist and be used, with Verity
authentication. (Note: if a DDI image carries a data partition, along with a Verity partition and a
signature partition for it, and only the <option>verity</option> flag is set and
<option>signed</option> is not , then the image will be set up with Verity, but the signature data will
not be used. Or in other words: any DDI with a set of partitions that qualify for
<option>signature</option> also implicitly qualifies for <option>verity</option>, and in fact
<option>unprotected</option>).</para></listitem>
<listitem><para><option>signed</option> for partitions that shall exist and be used, with Verity
authentication, which are also accompanied by a PKCS#7 signature of the Verity root
hash.</para></listitem>
<listitem><para><option>encrypted</option> for partitions which shall exist and be used and are
encrypted with LUKS.</para></listitem>
<listitem><para><option>unused</option> for partitions that shall exist but shall not be
used.</para></listitem>
<listitem><para><option>absent</option> for partitions that shall not exist on the
image.</para></listitem>
</itemizedlist>
<para>By setting a combination of the flags above, alternatives can be declared. For example the
combination <literal>unused+absent</literal> means: the partition may exist (in which case it shall not
be used) or may be absent. The combination of
<literal>unprotected+verity+signed+encrypted+unused+absent</literal> may be specified via the special
shortcut <literal>open</literal>, and indicates that the partition may exist or may be absent, but if it
exists is used, regardless of the protection level.</para>
<para>As special rule: if none of the flags above are set for a listed partition identifier, the default
policy of <option>open</option> is implied, i.e. setting none of these flags listed above means
effectively all flags listed above will be set.</para>
<para>The following partition policy flags are defined that dictate the state of specific GPT partition
flags:</para>
<itemizedlist>
<listitem><para><option>read-only-off</option>, <option>read-only-on</option> to require that the
partitions have the read-only partition flag off or on.</para></listitem>
<listitem><para><option>growfs-off</option>, <option>growfs-on</option> to require that the
partitions have the growfs partition flag off or on.</para></listitem>
</itemizedlist>
<para>If both <option>read-only-off</option> and <option>read-only-on</option> are set for a partition,
then the state of the read-only flag on the partition is not dictated by the policy. Setting neither flag
is equivalent to setting both, i.e. setting neither of these two flags means effectively both will be
set. A similar logic applies to <option>growfs-off</option>/<option>growfs-on</option>.</para>
<para>If partitions are not listed within an image policy string, the default policy flags are applied
(configurable via an empty partition identifier, see above). If no default policy flags are configured in
the policy string, it is implied to be <literal>absent+unused</literal>, except for the Verity partition
and their signature partitions where the policy is automatically derived from minimal protection level of
the data partition they protect, as encoded in the policy.</para>
</refsect1>
<refsect1>
<title>Special Policies</title>
<para>The special image policy string <literal>*</literal> is short for "use everything", i.e. is
equivalent to:</para>
<programlisting>=verity+signed+encrypted+unprotected+unused+absent</programlisting>
<para>The special image policy string <literal>-</literal> is short for "use nothing", i.e. is equivalent
to:</para>
<programlisting>=unused+absent</programlisting>
<para>The special image policy string <literal>~</literal> is short for "everything must be absent",
i.e. is equivalent to:</para>
<programlisting>=absent</programlisting>
</refsect1>
<refsect1>
<title>Use</title>
<para>Most systemd components that support operating with disk images support a
<option>--image-policy=</option> command line option to specify the image policy to use, and default to
relatively open policies by default (typically the <literal>*</literal> policy, as described above),
under the assumption that trust in disk images is established before the images are passed to the program
in question.</para>
<para>For the host 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 policy via the kernel command line option
<option>systemd.image-policy=</option>.</para>
<para>Note that image policies do not dictate how the components will mount and use disk images — they
only dictate which parts to avoid and which protection level and arrangement to require while
mounting/using them. For example,
<citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry> only
cares for the <filename>/usr/</filename> and <filename>/opt/</filename> trees inside a disk image, and
thus ignores any <filename>/home/</filename> partitions (and similar) in all cases, which might be
included in the image, regardless whether the configured image policy would allow access to it or
not. Similar,
<citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry> is not
going to make use of any discovered swap device, regardless if the policy would allow that or not.</para>
<para>Use the <command>image-policy</command> command of the
<citerefentry><refentrytitle>systemd-analyze</refentrytitle><manvolnum>8</manvolnum></citerefentry> tool
to analyze image policy strings, and determine what a specific policy string means for a specific
partition.</para>
</refsect1>
<refsect1>
<title>Examples</title>
<para>The following image policy string dictates one read-only Verity-enabled <filename>/usr/</filename>
partition must exist, plus encrypted root and swap partitions. All other partitions are ignored:</para>
<programlisting>usr=verity+read-only-on:root=encrypted:swap=encrypted</programlisting>
<para>The following image policy string dictates an encrypted, writable root file system, and optional
<filename>/srv/</filename> file system that must be encrypted if it exists and no swap partition may
exist:</para>
<programlisting>root=encrypted+read-only-off:srv=encrypted+absent:swap=absent</programlisting>
<para>The following image policy string dictates a single root partition that may be encrypted, but
doesn't have to be, and ignores swap partitions, and uses all other partitions if they are available, possibly with encryption.</para>
<programlisting>root=unprotected+encrypted:swap=absent+unused:=unprotected+encrypted+absent</programlisting>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-dissect</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-analyze</refentrytitle><manvolnum>8</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

View File

@ -0,0 +1,154 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze-image-policy.h"
#include "analyze.h"
#include "format-table.h"
#include "terminal-util.h"
static int table_add_designator_line(Table *table, PartitionDesignator d, PartitionPolicyFlags f) {
_cleanup_free_ char *q = NULL;
const char *color;
int r;
assert(table);
assert(f >= 0);
if (partition_policy_flags_to_string(f & _PARTITION_POLICY_USE_MASK, /* simplify= */ true, &q) < 0)
return log_oom();
color = (f & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_IGNORE ? ansi_grey() :
((f & (PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT)) ==
(PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT)) ? ansi_highlight_yellow() :
(f & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_ABSENT ? ansi_highlight_red() :
!(f & PARTITION_POLICY_UNPROTECTED) ? ansi_highlight_green() : NULL;
if (d < 0)
r = table_add_many(table,
TABLE_STRING, "default",
TABLE_SET_COLOR, ansi_highlight_green(),
TABLE_STRING, q,
TABLE_SET_COLOR, color);
else
r = table_add_many(table,
TABLE_STRING, partition_designator_to_string(d),
TABLE_SET_COLOR, ansi_normal(),
TABLE_STRING, q,
TABLE_SET_COLOR, color);
if (r < 0)
return table_log_add_error(r);
switch (f & _PARTITION_POLICY_READ_ONLY_MASK) {
case PARTITION_POLICY_READ_ONLY_ON:
r = table_add_many(table, TABLE_BOOLEAN, true);
break;
case PARTITION_POLICY_READ_ONLY_OFF:
r = table_add_many(table, TABLE_BOOLEAN, false);
break;
default:
r = table_add_many(table, TABLE_EMPTY);
break;
}
if (r < 0)
return table_log_add_error(r);
switch (f & _PARTITION_POLICY_GROWFS_MASK) {
case PARTITION_POLICY_GROWFS_ON:
r = table_add_many(table, TABLE_BOOLEAN, true);
break;
case PARTITION_POLICY_GROWFS_OFF:
r = table_add_many(table, TABLE_BOOLEAN, false);
break;
default:
r = table_add_many(table, TABLE_EMPTY);
break;
}
if (r < 0)
return table_log_add_error(r);
return 0;
}
int verb_image_policy(int argc, char *argv[], void *userdata) {
int r;
for (int i = 1; i < argc; i++) {
_cleanup_(table_unrefp) Table *table = NULL;
_cleanup_(image_policy_freep) ImagePolicy *pbuf = NULL;
_cleanup_free_ char *as_string = NULL, *as_string_simplified = NULL;
const ImagePolicy *p;
/* NB: The magic '@' strings are not officially documented for now, since we might change
* around defaults (and in particular where precisely to reuse policy). We should document
* them once the dust has settled a bit. For now it's just useful for debugging and
* introspect our own defaults without guaranteeing API safety. */
if (streq(argv[i], "@sysext"))
p = &image_policy_sysext;
else if (streq(argv[i], "@sysext-strict"))
p = &image_policy_sysext_strict;
else if (streq(argv[i], "@container"))
p = &image_policy_container;
else if (streq(argv[i], "@service"))
p = &image_policy_service;
else if (streq(argv[i], "@host"))
p = &image_policy_host;
else {
r = image_policy_from_string(argv[i], &pbuf);
if (r < 0)
return log_error_errno(r, "Failed to parse image policy '%s': %m", argv[i]);
p = pbuf;
}
r = image_policy_to_string(p, /* simplify= */ false, &as_string);
if (r < 0)
return log_error_errno(r, "Failed to format policy '%s' as string: %m", argv[i]);
r = image_policy_to_string(p, /* simplify= */ true, &as_string_simplified);
if (r < 0)
return log_error_errno(r, "Failed to format policy '%s' as string: %m", argv[i]);
pager_open(arg_pager_flags);
if (streq(as_string, as_string_simplified))
printf("Analyzing policy: %s%s%s\n", ansi_highlight_magenta_underline(), as_string, ansi_normal());
else
printf("Analyzing policy: %s%s%s\n"
" Long form: %s%s%s\n",
ansi_highlight(), as_string_simplified, ansi_normal(),
ansi_grey(), as_string, ansi_normal());
table = table_new("partition", "mode", "read-only", "growfs");
if (!table)
return log_oom();
(void) table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
PartitionPolicyFlags f = image_policy_get_exhaustively(p, d);
assert(f >= 0);
r = table_add_designator_line(table, d, f);
if (r < 0)
return r;
}
r = table_add_designator_line(table, _PARTITION_DESIGNATOR_INVALID, image_policy_default(p));
if (r < 0)
return r;
putc('\n', stdout);
r = table_print(table, NULL);
if (r < 0)
return r;
}
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,3 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
int verb_image_policy(int argc, char *argv[], void *userdata);

View File

@ -39,6 +39,7 @@
#include "analyze-unit-files.h"
#include "analyze-unit-paths.h"
#include "analyze-verify.h"
#include "analyze-image-policy.h"
#include "build.h"
#include "bus-error.h"
#include "bus-locator.h"
@ -109,6 +110,7 @@ bool arg_quiet = false;
char *arg_profile = NULL;
bool arg_legend = true;
bool arg_table = false;
ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_dot_from_patterns, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_dot_to_patterns, strv_freep);
@ -117,6 +119,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_security_policy, freep);
STATIC_DESTRUCTOR_REGISTER(arg_unit, freep);
STATIC_DESTRUCTOR_REGISTER(arg_profile, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
int acquire_bus(sd_bus **bus, bool *use_full_bus) {
int r;
@ -268,6 +271,7 @@ static int help(int argc, char *argv[], void *userdata) {
" -q --quiet Do not emit hints\n"
" --root=PATH Operate on an alternate filesystem root\n"
" --image=PATH Operate on disk image as filesystem root\n"
" --image-policy=POLICY Specify disk image dissection policy\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
@ -307,6 +311,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_PROFILE,
ARG_TABLE,
ARG_NO_LEGEND,
ARG_IMAGE_POLICY,
};
static const struct option options[] = {
@ -339,6 +344,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "profile", required_argument, NULL, ARG_PROFILE },
{ "table", optional_argument, NULL, ARG_TABLE },
{ "no-legend", optional_argument, NULL, ARG_NO_LEGEND },
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{}
};
@ -522,6 +528,18 @@ static int parse_argv(int argc, char *argv[]) {
arg_legend = false;
break;
case ARG_IMAGE_POLICY: {
_cleanup_(image_policy_freep) ImagePolicy *p = NULL;
r = image_policy_from_string(optarg, &p);
if (r < 0)
return log_error_errno(r, "Failed to parse image policy: %s", optarg);
image_policy_free(arg_image_policy);
arg_image_policy = TAKE_PTR(p);
break;
}
case '?':
return -EINVAL;
@ -623,6 +641,7 @@ static int run(int argc, char *argv[]) {
{ "inspect-elf", 2, VERB_ANY, 0, verb_elf_inspection },
{ "malloc", VERB_ANY, VERB_ANY, 0, verb_malloc },
{ "fdstore", 2, VERB_ANY, 0, verb_fdstore },
{ "image-policy", 2, 2, 0, verb_image_policy },
{}
};
@ -643,6 +662,7 @@ static int run(int argc, char *argv[]) {
r = mount_image_privately_interactively(
arg_image,
arg_image_policy,
DISSECT_IMAGE_GENERIC_ROOT |
DISSECT_IMAGE_RELAX_VAR_CHECK |
DISSECT_IMAGE_READ_ONLY,

View File

@ -38,6 +38,7 @@ extern bool arg_quiet;
extern char *arg_profile;
extern bool arg_legend;
extern bool arg_table;
extern ImagePolicy *arg_image_policy;
int acquire_bus(sd_bus **bus, bool *use_full_bus);

View File

@ -13,6 +13,7 @@ systemd_analyze_sources = files(
'analyze-exit-status.c',
'analyze-fdstore.c',
'analyze-filesystems.c',
'analyze-image-policy.c',
'analyze-inspect-elf.c',
'analyze-log-control.c',
'analyze-malloc.c',

View File

@ -52,6 +52,7 @@ char *arg_image = NULL;
InstallSource arg_install_source = ARG_INSTALL_SOURCE_AUTO;
char *arg_efi_boot_option_description = NULL;
bool arg_dry_run = false;
ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep);
STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep);
@ -60,6 +61,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_entry_token, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_efi_boot_option_description, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
int acquire_esp(
bool unprivileged_mode,
@ -168,6 +170,8 @@ static int help(int argc, char *argv[], void *userdata) {
" --boot-path=PATH Path to the $BOOT partition\n"
" --root=PATH Operate on an alternate filesystem root\n"
" --image=PATH Operate on disk image as filesystem root\n"
" --image-policy=POLICY\n"
" Specify disk image dissection policy\n"
" --install-source=auto|image|host\n"
" Where to pick files when using --root=/--image=\n"
" -p --print-esp-path Print path to the EFI System Partition mount point\n"
@ -218,6 +222,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_ARCH_ALL,
ARG_EFI_BOOT_OPTION_DESCRIPTION,
ARG_DRY_RUN,
ARG_IMAGE_POLICY,
};
static const struct option options[] = {
@ -244,6 +249,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "all-architectures", no_argument, NULL, ARG_ARCH_ALL },
{ "efi-boot-option-description", required_argument, NULL, ARG_EFI_BOOT_OPTION_DESCRIPTION },
{ "dry-run", no_argument, NULL, ARG_DRY_RUN },
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{}
};
@ -376,6 +382,18 @@ static int parse_argv(int argc, char *argv[]) {
arg_dry_run = true;
break;
case ARG_IMAGE_POLICY: {
_cleanup_(image_policy_freep) ImagePolicy *p = NULL;
r = image_policy_from_string(optarg, &p);
if (r < 0)
return log_error_errno(r, "Failed to parse image policy: %s", optarg);
image_policy_free(arg_image_policy);
arg_image_policy = TAKE_PTR(p);
break;
}
case '?':
return -EINVAL;
@ -478,6 +496,7 @@ static int run(int argc, char *argv[]) {
r = mount_image_privately_interactively(
arg_image,
arg_image_policy,
DISSECT_IMAGE_GENERIC_ROOT |
DISSECT_IMAGE_RELAX_VAR_CHECK,
&unlink_dir,

View File

@ -4,6 +4,7 @@
#include "sd-id128.h"
#include "boot-entry.h"
#include "image-policy.h"
#include "json.h"
#include "pager.h"
@ -34,6 +35,7 @@ extern char *arg_image;
extern InstallSource arg_install_source;
extern char *arg_efi_boot_option_description;
extern bool arg_dry_run;
extern ImagePolicy *arg_image_policy;
static inline const char *arg_dollar_boot_path(void) {
/* $BOOT shall be the XBOOTLDR partition if it exists, and otherwise the ESP */

View File

@ -1156,6 +1156,30 @@ static int bus_property_get_exec_dir_symlink(
return sd_bus_message_close_container(reply);
}
static int property_get_image_policy(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
ImagePolicy **pp = ASSERT_PTR(userdata);
_cleanup_free_ char *s = NULL;
int r;
assert(bus);
assert(property);
assert(reply);
r = image_policy_to_string(*pp ?: &image_policy_service, /* simplify= */ true, &s);
if (r < 0)
return r;
return sd_bus_message_append(reply, "s", s);
}
const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST),
@ -1324,6 +1348,9 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("ProtectHostname", "b", bus_property_get_bool, offsetof(ExecContext, protect_hostname), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("NetworkNamespacePath", "s", NULL, offsetof(ExecContext, network_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("IPCNamespacePath", "s", NULL, offsetof(ExecContext, ipc_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RootImagePolicy", "s", property_get_image_policy, offsetof(ExecContext, root_image_policy), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("MountImagePolicy", "s", property_get_image_policy, offsetof(ExecContext, mount_image_policy), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ExtensionImagePolicy", "s", property_get_image_policy, offsetof(ExecContext, extension_image_policy), SD_BUS_VTABLE_PROPERTY_CONST),
/* Obsolete/redundant properties: */
SD_BUS_PROPERTY("Capabilities", "s", property_get_empty_string, 0, SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
@ -3900,6 +3927,40 @@ int bus_exec_context_set_transient_property(
return 1;
} else if (STR_IN_SET(name, "RootImagePolicy", "MountImagePolicy", "ExtensionImagePolicy")) {
_cleanup_(image_policy_freep) ImagePolicy *p = NULL;
const char *s;
r = sd_bus_message_read(message, "s", &s);
if (r < 0)
return r;
r = image_policy_from_string(s, &p);
if (r < 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to parse image policy string: %s", s);
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
_cleanup_free_ char *t = NULL;
ImagePolicy **pp =
streq(name, "RootImagePolicy") ? &c->root_image_policy :
streq(name, "MountImagePolicy") ? &c->mount_image_policy :
&c->extension_image_policy;
r = image_policy_to_string(p, /* simplify= */ true, &t);
if (r < 0)
return r;
image_policy_free(*pp);
*pp = TAKE_PTR(p);
unit_write_settingf(
u, flags, name,
"%s=%s",
name,
t); /* no escaping necessary */
}
return 1;
}
return 0;

View File

@ -197,15 +197,23 @@ static int bus_service_method_mount(sd_bus_message *message, void *userdata, sd_
propagate_directory = strjoina("/run/systemd/propagate/", u->id);
if (is_image)
r = mount_image_in_namespace(unit_pid,
propagate_directory,
"/run/systemd/incoming/",
src, dest, read_only, make_file_or_directory, options);
r = mount_image_in_namespace(
unit_pid,
propagate_directory,
"/run/systemd/incoming/",
src, dest,
read_only,
make_file_or_directory,
options,
c->mount_image_policy ?: &image_policy_service);
else
r = bind_mount_in_namespace(unit_pid,
propagate_directory,
"/run/systemd/incoming/",
src, dest, read_only, make_file_or_directory);
r = bind_mount_in_namespace(
unit_pid,
propagate_directory,
"/run/systemd/incoming/",
src, dest,
read_only,
make_file_or_directory);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to mount %s on %s in unit's namespace: %m", src, dest);

View File

@ -3799,36 +3799,43 @@ static int apply_mount_namespace(
if (asprintf(&extension_dir, "/run/user/" UID_FMT "/systemd/unit-extensions", geteuid()) < 0)
return -ENOMEM;
r = setup_namespace(root_dir, root_image, context->root_image_options,
&ns_info, read_write_paths,
needs_sandboxing ? context->read_only_paths : NULL,
needs_sandboxing ? context->inaccessible_paths : NULL,
needs_sandboxing ? context->exec_paths : NULL,
needs_sandboxing ? context->no_exec_paths : NULL,
empty_directories,
symlinks,
bind_mounts,
n_bind_mounts,
context->temporary_filesystems,
context->n_temporary_filesystems,
context->mount_images,
context->n_mount_images,
tmp_dir,
var_tmp_dir,
creds_path,
context->log_namespace,
context->mount_propagation_flag,
context->root_hash, context->root_hash_size, context->root_hash_path,
context->root_hash_sig, context->root_hash_sig_size, context->root_hash_sig_path,
context->root_verity,
context->extension_images,
context->n_extension_images,
context->extension_directories,
propagate_dir,
incoming_dir,
extension_dir,
root_dir || root_image ? params->notify_socket : NULL,
error_path);
r = setup_namespace(
root_dir,
root_image,
context->root_image_options,
context->root_image_policy ?: &image_policy_service,
&ns_info,
read_write_paths,
needs_sandboxing ? context->read_only_paths : NULL,
needs_sandboxing ? context->inaccessible_paths : NULL,
needs_sandboxing ? context->exec_paths : NULL,
needs_sandboxing ? context->no_exec_paths : NULL,
empty_directories,
symlinks,
bind_mounts,
n_bind_mounts,
context->temporary_filesystems,
context->n_temporary_filesystems,
context->mount_images,
context->n_mount_images,
context->mount_image_policy ?: &image_policy_service,
tmp_dir,
var_tmp_dir,
creds_path,
context->log_namespace,
context->mount_propagation_flag,
context->root_hash, context->root_hash_size, context->root_hash_path,
context->root_hash_sig, context->root_hash_sig_size, context->root_hash_sig_path,
context->root_verity,
context->extension_images,
context->n_extension_images,
context->extension_image_policy ?: &image_policy_sysext,
context->extension_directories,
propagate_dir,
incoming_dir,
extension_dir,
root_dir || root_image ? params->notify_socket : NULL,
error_path);
/* If we couldn't set up the namespace this is probably due to a missing capability. setup_namespace() reports
* that with a special, recognizable error ENOANO. In this case, silently proceed, but only if exclusively
@ -5767,6 +5774,10 @@ void exec_context_done(ExecContext *c) {
c->load_credentials = hashmap_free(c->load_credentials);
c->set_credentials = hashmap_free(c->set_credentials);
c->root_image_policy = image_policy_free(c->root_image_policy);
c->mount_image_policy = image_policy_free(c->mount_image_policy);
c->extension_image_policy = image_policy_free(c->extension_image_policy);
}
int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_prefix) {

View File

@ -359,6 +359,8 @@ struct ExecContext {
Hashmap *set_credentials; /* output id → ExecSetCredential */
Hashmap *load_credentials; /* output id → ExecLoadCredential */
ImagePolicy *root_image_policy, *mount_image_policy, *extension_image_policy;
};
static inline bool exec_context_restrict_namespaces_set(const ExecContext *c) {

View File

@ -6,12 +6,15 @@
{{type}}.RootDirectory, config_parse_unit_path_printf, true, offsetof({{type}}, exec_context.root_directory)
{{type}}.RootImage, config_parse_unit_path_printf, true, offsetof({{type}}, exec_context.root_image)
{{type}}.RootImageOptions, config_parse_root_image_options, 0, offsetof({{type}}, exec_context)
{{type}}.RootImagePolicy, config_parse_image_policy, 0, offsetof({{type}}, exec_context.root_image_policy)
{{type}}.RootHash, config_parse_exec_root_hash, 0, offsetof({{type}}, exec_context)
{{type}}.RootHashSignature, config_parse_exec_root_hash_sig, 0, offsetof({{type}}, exec_context)
{{type}}.RootVerity, config_parse_unit_path_printf, true, offsetof({{type}}, exec_context.root_verity)
{{type}}.ExtensionDirectories, config_parse_namespace_path_strv, 0, offsetof({{type}}, exec_context.extension_directories)
{{type}}.ExtensionImages, config_parse_extension_images, 0, offsetof({{type}}, exec_context)
{{type}}.ExtensionImagePolicy, config_parse_image_policy, 0, offsetof({{type}}, exec_context.extension_image_policy)
{{type}}.MountImages, config_parse_mount_images, 0, offsetof({{type}}, exec_context)
{{type}}.MountImagePolicy, config_parse_image_policy, 0, offsetof({{type}}, exec_context.mount_image_policy)
{{type}}.User, config_parse_user_group_compat, 0, offsetof({{type}}, exec_context.user)
{{type}}.Group, config_parse_user_group_compat, 0, offsetof({{type}}, exec_context.group)
{{type}}.SupplementaryGroups, config_parse_user_group_strv_compat, 0, offsetof({{type}}, exec_context.supplementary_groups)

View File

@ -1705,6 +1705,45 @@ int config_parse_root_image_options(
return 0;
}
int config_parse_image_policy(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
_cleanup_(image_policy_freep) ImagePolicy *np = NULL;
ImagePolicy **p = ASSERT_PTR(data);
int r;
assert(rvalue);
if (isempty(rvalue)) {
*p = image_policy_free(*p);
return 0;
}
r = image_policy_from_string(rvalue, &np);
if (r == -ENOTUNIQ)
return log_syntax(unit, LOG_ERR, filename, line, r, "Duplicate rule in image policy, refusing: %s", rvalue);
if (r == -EBADSLT)
return log_syntax(unit, LOG_ERR, filename, line, r, "Unknown partition type in image policy, refusing: %s", rvalue);
if (r == -EBADRQC)
return log_syntax(unit, LOG_ERR, filename, line, r, "Unknown partition policy flag in image policy, refusing: %s", rvalue);
if (r < 0)
return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse image policy, refusing: %s", rvalue);
image_policy_free(*p);
*p = TAKE_PTR(np);
return 0;
}
int config_parse_exec_root_hash(
const char *unit,
const char *filename,

View File

@ -52,6 +52,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_exec_cpu_affinity);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_mount_apivfs);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_secure_bits);
CONFIG_PARSER_PROTOTYPE(config_parse_root_image_options);
CONFIG_PARSER_PROTOTYPE(config_parse_image_policy);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_root_hash);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_root_hash_sig);
CONFIG_PARSER_PROTOTYPE(config_parse_capability_set);

View File

@ -1240,7 +1240,10 @@ static int mount_mqueuefs(const MountEntry *m) {
return 0;
}
static int mount_image(const MountEntry *m, const char *root_directory) {
static int mount_image(
const MountEntry *m,
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;
@ -1262,8 +1265,15 @@ static int mount_image(const MountEntry *m, const char *root_directory) {
}
r = verity_dissect_and_mount(
/* src_fd= */ -1, mount_entry_source(m), mount_entry_path(m), m->image_options,
host_os_release_id, host_os_release_version_id, host_os_release_sysext_level, NULL);
/* src_fd= */ -1,
mount_entry_source(m),
mount_entry_path(m),
m->image_options,
image_policy,
host_os_release_id,
host_os_release_version_id,
host_os_release_sysext_level,
NULL);
if (r == -ENOENT && m->ignore)
return 0;
if (r == -ESTALE && host_os_release_id)
@ -1336,6 +1346,8 @@ static int follow_symlink(
static int apply_one_mount(
const char *root_directory,
MountEntry *m,
const ImagePolicy *mount_image_policy,
const ImagePolicy *extension_image_policy,
const NamespaceInfo *ns_info) {
_cleanup_free_ char *inaccessible = NULL;
@ -1506,10 +1518,10 @@ static int apply_one_mount(
return mount_mqueuefs(m);
case MOUNT_IMAGES:
return mount_image(m, NULL);
return mount_image(m, NULL, mount_image_policy);
case EXTENSION_IMAGES:
return mount_image(m, root_directory);
return mount_image(m, root_directory, extension_image_policy);
case OVERLAY_MOUNT:
return mount_overlay(m);
@ -1779,6 +1791,8 @@ static int create_symlinks_from_tuples(const char *root, char **strv_symlinks) {
static int apply_mounts(
const char *root,
const ImagePolicy *mount_image_policy,
const ImagePolicy *extension_image_policy,
const NamespaceInfo *ns_info,
MountEntry *mounts,
size_t *n_mounts,
@ -1833,7 +1847,7 @@ static int apply_mounts(
break;
}
r = apply_one_mount(root, m, ns_info);
r = apply_one_mount(root, m, mount_image_policy, extension_image_policy, ns_info);
if (r < 0) {
if (error_path && mount_entry_path(m))
*error_path = strdup(mount_entry_path(m));
@ -2012,7 +2026,8 @@ static int verity_settings_prepare(
int setup_namespace(
const char* root_directory,
const char* root_image,
const MountOptions *root_image_options,
const MountOptions *root_image_mount_options,
const ImagePolicy *root_image_policy,
const NamespaceInfo *ns_info,
char** read_write_paths,
char** read_only_paths,
@ -2027,6 +2042,7 @@ int setup_namespace(
size_t n_temporary_filesystems,
const MountImage *mount_images,
size_t n_mount_images,
const ImagePolicy *mount_image_policy,
const char* tmp_dir,
const char* var_tmp_dir,
const char *creds_path,
@ -2041,6 +2057,7 @@ int setup_namespace(
const char *verity_data_path,
const MountImage *extension_images,
size_t n_extension_images,
const ImagePolicy *extension_image_policy,
char **extension_directories,
const char *propagate_dir,
const char *incoming_dir,
@ -2114,7 +2131,8 @@ int setup_namespace(
r = dissect_loop_device(
loop_device,
&verity,
root_image_options,
root_image_mount_options,
root_image_policy,
dissect_image_flags,
&dissected_image);
if (r < 0)
@ -2502,7 +2520,7 @@ int setup_namespace(
(void) base_filesystem_create(root, UID_INVALID, GID_INVALID);
/* Now make the magic happen */
r = apply_mounts(root, ns_info, mounts, &n_mounts, exec_dir_symlinks, error_path);
r = apply_mounts(root, mount_image_policy, extension_image_policy, ns_info, mounts, &n_mounts, exec_dir_symlinks, error_path);
if (r < 0)
goto finish;

View File

@ -103,6 +103,7 @@ int setup_namespace(
const char *root_directory,
const char *root_image,
const MountOptions *root_image_options,
const ImagePolicy *root_image_policy,
const NamespaceInfo *ns_info,
char **read_write_paths,
char **read_only_paths,
@ -117,6 +118,7 @@ int setup_namespace(
size_t n_temporary_filesystems,
const MountImage *mount_images,
size_t n_mount_images,
const ImagePolicy *mount_image_policy,
const char *tmp_dir,
const char *var_tmp_dir,
const char *creds_path,
@ -131,6 +133,7 @@ int setup_namespace(
const char *root_verity,
const MountImage *extension_images,
size_t n_extension_images,
const ImagePolicy *extension_image_policy,
char **extension_directories,
const char *propagate_dir,
const char *incoming_dir,

View File

@ -64,9 +64,11 @@ static const char* arg_output = NULL;
static bool arg_reverse = false;
static bool arg_quiet = false;
static bool arg_all = false;
static ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_debugger_args, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_file, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
static int add_match(sd_journal *j, const char *match) {
_cleanup_free_ char *p = NULL;
@ -198,6 +200,7 @@ static int verb_help(int argc, char **argv, void *userdata) {
" --all Look at all journal files instead of local ones\n"
" --root=PATH Operate on an alternate filesystem root\n"
" --image=PATH Operate on disk image as filesystem root\n"
" --image-policy=POLICY Specify disk image dissection policy\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
@ -220,29 +223,31 @@ static int parse_argv(int argc, char *argv[]) {
ARG_ROOT,
ARG_IMAGE,
ARG_ALL,
ARG_IMAGE_POLICY,
};
int c, r;
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version" , no_argument, NULL, ARG_VERSION },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "debugger", required_argument, NULL, ARG_DEBUGGER },
{ "debugger-arguments", required_argument, NULL, 'A' },
{ "output", required_argument, NULL, 'o' },
{ "field", required_argument, NULL, 'F' },
{ "file", required_argument, NULL, ARG_FILE },
{ "directory", required_argument, NULL, 'D' },
{ "reverse", no_argument, NULL, 'r' },
{ "since", required_argument, NULL, 'S' },
{ "until", required_argument, NULL, 'U' },
{ "quiet", no_argument, NULL, 'q' },
{ "json", required_argument, NULL, ARG_JSON },
{ "root", required_argument, NULL, ARG_ROOT },
{ "image", required_argument, NULL, ARG_IMAGE },
{ "all", no_argument, NULL, ARG_ALL },
{ "help", no_argument, NULL, 'h' },
{ "version" , no_argument, NULL, ARG_VERSION },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "debugger", required_argument, NULL, ARG_DEBUGGER },
{ "debugger-arguments", required_argument, NULL, 'A' },
{ "output", required_argument, NULL, 'o' },
{ "field", required_argument, NULL, 'F' },
{ "file", required_argument, NULL, ARG_FILE },
{ "directory", required_argument, NULL, 'D' },
{ "reverse", no_argument, NULL, 'r' },
{ "since", required_argument, NULL, 'S' },
{ "until", required_argument, NULL, 'U' },
{ "quiet", no_argument, NULL, 'q' },
{ "json", required_argument, NULL, ARG_JSON },
{ "root", required_argument, NULL, ARG_ROOT },
{ "image", required_argument, NULL, ARG_IMAGE },
{ "all", no_argument, NULL, ARG_ALL },
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{}
};
@ -363,6 +368,18 @@ static int parse_argv(int argc, char *argv[]) {
arg_all = true;
break;
case ARG_IMAGE_POLICY: {
_cleanup_(image_policy_freep) ImagePolicy *p = NULL;
r = image_policy_from_string(optarg, &p);
if (r < 0)
return log_error_errno(r, "Failed to parse image policy: %s", optarg);
image_policy_free(arg_image_policy);
arg_image_policy = TAKE_PTR(p);
break;
}
case '?':
return -EINVAL;
@ -1361,6 +1378,7 @@ static int run(int argc, char *argv[]) {
r = mount_image_privately_interactively(
arg_image,
arg_image_policy,
DISSECT_IMAGE_GENERIC_ROOT |
DISSECT_IMAGE_REQUIRE_ROOT |
DISSECT_IMAGE_RELAX_VAR_CHECK |

View File

@ -61,6 +61,7 @@ static enum {
ACTION_COPY_FROM,
ACTION_COPY_TO,
ACTION_DISCOVER,
ACTION_VALIDATE,
} arg_action = ACTION_DISSECT;
static char *arg_image = NULL;
static char *arg_path = NULL;
@ -83,6 +84,7 @@ static bool arg_rmdir = false;
static bool arg_in_memory = false;
static char **arg_argv = NULL;
static char *arg_loop_ref = NULL;
static ImagePolicy* arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_path, freep);
@ -126,6 +128,8 @@ static int help(void) {
" 'base64:'\n"
" --verity-data=PATH Specify data file with hash tree for verity if it is\n"
" not embedded in IMAGE\n"
" --image-policy=POLICY\n"
" Specify image dissection policy\n"
" --json=pretty|short|off\n"
" Generate JSON output\n"
" --loop-ref=NAME Set reference string for loopback device\n"
@ -145,6 +149,7 @@ static int help(void) {
" -x --copy-from Copy files from image to host\n"
" -a --copy-to Copy files from host to image\n"
" --discover Discover DDIs in well known directories\n"
" --validate Validate image and image policy\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
@ -221,6 +226,8 @@ static int parse_argv(int argc, char *argv[]) {
ARG_ATTACH,
ARG_DETACH,
ARG_LOOP_REF,
ARG_IMAGE_POLICY,
ARG_VALIDATE,
};
static const struct option options[] = {
@ -250,6 +257,8 @@ static int parse_argv(int argc, char *argv[]) {
{ "json", required_argument, NULL, ARG_JSON },
{ "discover", no_argument, NULL, ARG_DISCOVER },
{ "loop-ref", required_argument, NULL, ARG_LOOP_REF },
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{ "validate", no_argument, NULL, ARG_VALIDATE },
{}
};
@ -457,6 +466,22 @@ static int parse_argv(int argc, char *argv[]) {
return r;
break;
case ARG_IMAGE_POLICY: {
_cleanup_(image_policy_freep) ImagePolicy *p = NULL;
r = image_policy_from_string(optarg, &p);
if (r < 0)
return log_error_errno(r, "Failed to parse image policy: %s", optarg);
image_policy_free(arg_image_policy);
arg_image_policy = TAKE_PTR(p);
break;
}
case ARG_VALIDATE:
arg_action = ACTION_VALIDATE;
break;
case '?':
return -EINVAL;
@ -476,7 +501,8 @@ static int parse_argv(int argc, char *argv[]) {
if (r < 0)
return r;
arg_flags |= DISSECT_IMAGE_READ_ONLY;
/* when dumping image info be even more liberal than otherwise, do not even require a single valid partition */
arg_flags |= DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_ALLOW_EMPTY;
break;
case ACTION_MOUNT:
@ -593,7 +619,19 @@ static int parse_argv(int argc, char *argv[]) {
if (optind != argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Expected no argument.");
break;
case ACTION_VALIDATE:
if (optind + 1 != argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Expected an image file path as only argument.");
r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_image);
if (r < 0)
return r;
arg_flags |= DISSECT_IMAGE_READ_ONLY;
arg_flags &= ~(DISSECT_IMAGE_PIN_PARTITION_DEVICES|DISSECT_IMAGE_ADD_PARTITION_DEVICES);
break;
default:
@ -1689,6 +1727,31 @@ static int action_detach(const char *path) {
return 0;
}
static int action_validate(void) {
int r;
r = dissect_image_file_and_warn(
arg_image,
&arg_verity_settings,
NULL,
arg_image_policy,
arg_flags,
NULL);
if (r < 0)
return r;
if (isatty(STDOUT_FILENO) && emoji_enabled())
printf("%s ", special_glyph(SPECIAL_GLYPH_SPARKLES));
printf("%sOK%s", ansi_highlight_green(), ansi_normal());
if (isatty(STDOUT_FILENO) && emoji_enabled())
printf(" %s", special_glyph(SPECIAL_GLYPH_SPARKLES));
putc('\n', stdout);
return 0;
}
static int run(int argc, char *argv[]) {
_cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
_cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
@ -1731,6 +1794,9 @@ static int run(int argc, char *argv[]) {
* available we turn off partition table
* support */
if (arg_action == ACTION_VALIDATE)
return action_validate();
open_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : O_RDWR;
loop_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN;
@ -1750,7 +1816,8 @@ static int run(int argc, char *argv[]) {
r = dissect_loop_device_and_warn(
d,
&arg_verity_settings,
NULL,
/* mount_options= */ NULL,
arg_image_policy,
arg_flags,
&m);
if (r < 0)

View File

@ -73,6 +73,7 @@ static bool arg_delete_root_password = false;
static bool arg_root_password_is_hashed = false;
static bool arg_welcome = true;
static bool arg_reset = false;
static ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
@ -82,6 +83,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_keymap, freep);
STATIC_DESTRUCTOR_REGISTER(arg_timezone, freep);
STATIC_DESTRUCTOR_REGISTER(arg_hostname, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root_password, erase_and_freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
static bool press_any_key(void) {
char k = 0;
@ -1163,7 +1165,8 @@ static int help(void) {
" -h --help Show this help\n"
" --version Show package version\n"
" --root=PATH Operate on an alternate filesystem root\n"
" --image=PATH Operate on an alternate filesystem image\n"
" --image=PATH Operate on disk image as filesystem root\n"
" --image-policy=POLICY Specify disk image dissection policy\n"
" --locale=LOCALE Set primary locale (LANG=)\n"
" --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n"
" --keymap=KEYMAP Set keymap\n"
@ -1234,6 +1237,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_DELETE_ROOT_PASSWORD,
ARG_WELCOME,
ARG_RESET,
ARG_IMAGE_POLICY,
};
static const struct option options[] = {
@ -1270,6 +1274,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "delete-root-password", no_argument, NULL, ARG_DELETE_ROOT_PASSWORD },
{ "welcome", required_argument, NULL, ARG_WELCOME },
{ "reset", no_argument, NULL, ARG_RESET },
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{}
};
@ -1476,6 +1481,17 @@ static int parse_argv(int argc, char *argv[]) {
arg_reset = true;
break;
case ARG_IMAGE_POLICY: {
_cleanup_(image_policy_freep) ImagePolicy *p = NULL;
r = image_policy_from_string(optarg, &p);
if (r < 0)
return log_error_errno(r, "Failed to parse image policy: %s", optarg);
image_policy_free(arg_image_policy);
arg_image_policy = TAKE_PTR(p);
break;
}
case '?':
return -EINVAL;
@ -1528,6 +1544,7 @@ static int run(int argc, char *argv[]) {
r = mount_image_privately_interactively(
arg_image,
arg_image_policy,
DISSECT_IMAGE_GENERIC_ROOT |
DISSECT_IMAGE_REQUIRE_ROOT |
DISSECT_IMAGE_VALIDATE_OS |

View File

@ -23,6 +23,7 @@
#include "fstab-util.h"
#include "generator.h"
#include "gpt.h"
#include "image-policy.h"
#include "initrd-util.h"
#include "mkdir.h"
#include "mountpoint-util.h"
@ -43,6 +44,9 @@ static bool arg_root_enabled = true;
static char *arg_root_fstype = NULL;
static char *arg_root_options = NULL;
static int arg_root_rw = -1;
static ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
STATIC_DESTRUCTOR_REGISTER(arg_root_fstype, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root_options, freep);
@ -744,7 +748,9 @@ static int enumerate_partitions(dev_t devnum) {
r = dissect_loop_device(
loop,
NULL, NULL,
/* verity= */ NULL,
/* mount_options= */ NULL,
arg_image_policy ?: &image_policy_host,
DISSECT_IMAGE_GPT_ONLY|
DISSECT_IMAGE_USR_NO_ROOT|
DISSECT_IMAGE_DISKSEQ_DEVNODE,
@ -882,6 +888,20 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
arg_root_rw = true;
else if (proc_cmdline_key_streq(key, "ro") && !value)
arg_root_rw = false;
else if (proc_cmdline_key_streq(key, "systemd.image_policy")) {
_cleanup_(image_policy_freep) ImagePolicy *p = NULL;
if (proc_cmdline_value_missing(key, value))
return 0;
r = image_policy_from_string(value, &p);
if (r < 0)
return log_error_errno(r, "Failed to parse image policy: %s", value);
image_policy_free(arg_image_policy);
arg_image_policy = TAKE_PTR(p);
return 0;
}
return 0;
}

View File

@ -134,6 +134,7 @@ static Set *arg_output_fields = NULL;
static const char *arg_pattern = NULL;
static pcre2_code *arg_compiled_pattern = NULL;
static PatternCompileCase arg_case = PATTERN_COMPILE_CASE_AUTO;
ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_file, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_facilities, set_freep);
@ -145,6 +146,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_output_fields, set_freep);
STATIC_DESTRUCTOR_REGISTER(arg_compiled_pattern, pattern_freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
static enum {
ACTION_SHOW,
@ -326,8 +328,9 @@ static int help(void) {
" -m --merge Show entries from all available journals\n"
" -D --directory=PATH Show journal files from directory\n"
" --file=PATH Show journal file\n"
" --root=ROOT Operate on files below a root directory\n"
" --image=IMAGE Operate on files in filesystem image\n"
" --root=PATH Operate on an alternate filesystem root\n"
" --image=PATH Operate on disk image as filesystem root\n"
" --image-policy=POLICY Specify disk image dissection policy\n"
" --namespace=NAMESPACE Show journal data from specified journal namespace\n"
"\n%3$sFiltering Options:%4$s\n"
" -S --since=DATE Show entries not older than the specified date\n"
@ -444,6 +447,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_NO_HOSTNAME,
ARG_OUTPUT_FIELDS,
ARG_NAMESPACE,
ARG_IMAGE_POLICY,
};
static const struct option options[] = {
@ -511,6 +515,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "no-hostname", no_argument, NULL, ARG_NO_HOSTNAME },
{ "output-fields", required_argument, NULL, ARG_OUTPUT_FIELDS },
{ "namespace", required_argument, NULL, ARG_NAMESPACE },
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{}
};
@ -1033,7 +1038,17 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
case ARG_IMAGE_POLICY: {
_cleanup_(image_policy_freep) ImagePolicy *p = NULL;
r = image_policy_from_string(optarg, &p);
if (r < 0)
return log_error_errno(r, "Failed to parse image policy: %s", optarg);
image_policy_free(arg_image_policy);
arg_image_policy = TAKE_PTR(p);
break;
}
case '?':
return -EINVAL;
@ -2126,6 +2141,7 @@ static int run(int argc, char *argv[]) {
r = mount_image_privately_interactively(
arg_image,
arg_image_policy,
DISSECT_IMAGE_GENERIC_ROOT |
DISSECT_IMAGE_REQUIRE_ROOT |
DISSECT_IMAGE_VALIDATE_OS |

View File

@ -22,9 +22,11 @@ static char *arg_root = NULL;
static char *arg_image = NULL;
static bool arg_commit = false;
static bool arg_print = false;
static ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
@ -36,12 +38,13 @@ static int help(void) {
printf("%s [OPTIONS...]\n"
"\n%sInitialize /etc/machine-id from a random source.%s\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --root=PATH Operate relative to root path\n"
" --image=PATH Operate relative to image file\n"
" --commit Commit transient ID\n"
" --print Print used machine ID\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --root=PATH Operate on an alternate filesystem root\n"
" --image=PATH Operate on disk image as filesystem root\n"
" --image-policy=POLICY Specify disk image dissection policy\n"
" --commit Commit transient ID\n"
" --print Print used machine ID\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
@ -59,15 +62,17 @@ static int parse_argv(int argc, char *argv[]) {
ARG_IMAGE,
ARG_COMMIT,
ARG_PRINT,
ARG_IMAGE_POLICY,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "root", required_argument, NULL, ARG_ROOT },
{ "image", required_argument, NULL, ARG_IMAGE },
{ "commit", no_argument, NULL, ARG_COMMIT },
{ "print", no_argument, NULL, ARG_PRINT },
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "root", required_argument, NULL, ARG_ROOT },
{ "image", required_argument, NULL, ARG_IMAGE },
{ "commit", no_argument, NULL, ARG_COMMIT },
{ "print", no_argument, NULL, ARG_PRINT },
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{}
};
@ -106,6 +111,17 @@ static int parse_argv(int argc, char *argv[]) {
arg_print = true;
break;
case ARG_IMAGE_POLICY: {
_cleanup_(image_policy_freep) ImagePolicy *p = NULL;
r = image_policy_from_string(optarg, &p);
if (r < 0)
return log_error_errno(r, "Failed to parse image policy: %s", optarg);
image_policy_free(arg_image_policy);
arg_image_policy = TAKE_PTR(p);
break;
}
case '?':
return -EINVAL;
@ -141,6 +157,7 @@ static int run(int argc, char *argv[]) {
r = mount_image_privately_interactively(
arg_image,
arg_image_policy,
DISSECT_IMAGE_REQUIRE_ROOT |
DISSECT_IMAGE_VALIDATE_OS |
DISSECT_IMAGE_RELAX_VAR_CHECK |

View File

@ -313,7 +313,7 @@ int bus_image_method_get_hostname(
int r;
if (!image->metadata_valid) {
r = image_read_metadata(image);
r = image_read_metadata(image, &image_policy_container);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
}
@ -331,7 +331,7 @@ int bus_image_method_get_machine_id(
int r;
if (!image->metadata_valid) {
r = image_read_metadata(image);
r = image_read_metadata(image, &image_policy_container);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
}
@ -359,7 +359,7 @@ int bus_image_method_get_machine_info(
int r;
if (!image->metadata_valid) {
r = image_read_metadata(image);
r = image_read_metadata(image, &image_policy_container);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
}
@ -376,7 +376,7 @@ int bus_image_method_get_os_release(
int r;
if (!image->metadata_valid) {
r = image_read_metadata(image);
r = image_read_metadata(image, &image_policy_container);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
}

View File

@ -234,6 +234,7 @@ static char **arg_bind_user = NULL;
static bool arg_suppress_sync = false;
static char *arg_settings_filename = NULL;
static Architecture arg_architecture = _ARCHITECTURE_INVALID;
static ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_directory, freep);
STATIC_DESTRUCTOR_REGISTER(arg_template, freep);
@ -268,6 +269,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_cpu_set, cpu_set_reset);
STATIC_DESTRUCTOR_REGISTER(arg_sysctl, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_bind_user, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_settings_filename, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
static int handle_arg_console(const char *arg) {
if (streq(arg, "help")) {
@ -330,6 +332,7 @@ static int help(void) {
" remove it after exit\n"
" -i --image=PATH Root file system disk image (or device node) for\n"
" the container\n"
" --image-policy=POLICY Specify disk image dissection policy\n"
" --oci-bundle=PATH OCI bundle directory\n"
" --read-only Mount the root directory read-only\n"
" --volatile[=MODE] Run the system in volatile mode\n"
@ -732,6 +735,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_LOAD_CREDENTIAL,
ARG_BIND_USER,
ARG_SUPPRESS_SYNC,
ARG_IMAGE_POLICY,
};
static const struct option options[] = {
@ -805,6 +809,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL },
{ "bind-user", required_argument, NULL, ARG_BIND_USER },
{ "suppress-sync", required_argument, NULL, ARG_SUPPRESS_SYNC },
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{}
};
@ -1699,6 +1704,18 @@ static int parse_argv(int argc, char *argv[]) {
arg_settings_mask |= SETTING_SUPPRESS_SYNC;
break;
case ARG_IMAGE_POLICY: {
_cleanup_(image_policy_freep) ImagePolicy *p = NULL;
r = image_policy_from_string(optarg, &p);
if (r < 0)
return log_error_errno(r, "Failed to parse image policy: %s", optarg);
image_policy_free(arg_image_policy);
arg_image_policy = TAKE_PTR(p);
break;
}
case '?':
return -EINVAL;
@ -5758,7 +5775,8 @@ static int run(int argc, char *argv[]) {
r = dissect_loop_device_and_warn(
loop,
&arg_verity_settings,
NULL,
/* mount_options=*/ NULL,
arg_image_policy ?: &image_policy_container,
dissect_image_flags,
&dissected_image);
if (r == -ENOPKG) {

View File

@ -148,6 +148,7 @@ static FilterPartitionsType arg_filter_partitions_type = FILTER_PARTITIONS_NONE;
static sd_id128_t *arg_defer_partitions = NULL;
static size_t arg_n_defer_partitions = 0;
static uint64_t arg_sector_size = 0;
static ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
@ -158,6 +159,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_certificate, X509_freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep);
STATIC_DESTRUCTOR_REGISTER(arg_filter_partitions, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
typedef struct FreeArea FreeArea;
@ -5653,6 +5655,8 @@ static int help(void) {
" --can-factory-reset Test whether factory reset is defined\n"
" --root=PATH Operate relative to root path\n"
" --image=PATH Operate relative to image file\n"
" --image-policy=POLICY\n"
" Specify disk image dissection policy\n"
" --definitions=DIR Find partition definitions in specified directory\n"
" --key-file=PATH Key to use when encrypting partitions\n"
" --private-key=PATH Private key to use when generating verity roothash\n"
@ -5718,6 +5722,8 @@ static int parse_argv(int argc, char *argv[]) {
ARG_EXCLUDE_PARTITIONS,
ARG_DEFER_PARTITIONS,
ARG_SECTOR_SIZE,
ARG_SKIP_PARTITIONS,
ARG_IMAGE_POLICY,
};
static const struct option options[] = {
@ -5749,6 +5755,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "exclude-partitions", required_argument, NULL, ARG_EXCLUDE_PARTITIONS },
{ "defer-partitions", required_argument, NULL, ARG_DEFER_PARTITIONS },
{ "sector-size", required_argument, NULL, ARG_SECTOR_SIZE },
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{}
};
@ -6043,6 +6050,18 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_IMAGE_POLICY: {
_cleanup_(image_policy_freep) ImagePolicy *p = NULL;
r = image_policy_from_string(optarg, &p);
if (r < 0)
return log_error_errno(r, "Failed to parse image policy: %s", optarg);
image_policy_free(arg_image_policy);
arg_image_policy = TAKE_PTR(p);
break;
}
case '?':
return -EINVAL;
@ -6543,6 +6562,7 @@ static int run(int argc, char *argv[]) {
* systems */
r = mount_image_privately_interactively(
arg_image,
arg_image_policy,
DISSECT_IMAGE_MOUNT_READ_ONLY |
(arg_node ? DISSECT_IMAGE_DEVICE_READ_ONLY : 0) | /* If a different node to make changes to is specified let's open the device in read-only mode) */
DISSECT_IMAGE_GPT_ONLY |

View File

@ -324,6 +324,7 @@ static int portable_extract_by_path(
bool path_is_extension,
bool relax_extension_release_check,
char **matches,
const ImagePolicy *image_policy,
PortableMetadata **ret_os_release,
Hashmap **ret_unit_files,
sd_bus_error *error) {
@ -369,7 +370,9 @@ static int portable_extract_by_path(
r = dissect_loop_device(
d,
NULL, NULL,
/* verity= */ NULL,
/* mount_options= */ NULL,
image_policy,
DISSECT_IMAGE_READ_ONLY |
DISSECT_IMAGE_GENERIC_ROOT |
DISSECT_IMAGE_REQUIRE_ROOT |
@ -510,6 +513,7 @@ static int extract_image_and_extensions(
char **extension_image_paths,
bool validate_sysext,
bool relax_extension_release_check,
const ImagePolicy *image_policy,
Image **ret_image,
OrderedHashmap **ret_extension_images,
OrderedHashmap **ret_extension_releases,
@ -558,7 +562,15 @@ static int extract_image_and_extensions(
}
}
r = portable_extract_by_path(image->path, /* path_is_extension= */ false, /* relax_extension_release_check= */ false, matches, &os_release, &unit_files, error);
r = portable_extract_by_path(
image->path,
/* path_is_extension= */ false,
/* relax_extension_release_check= */ false,
matches,
image_policy,
&os_release,
&unit_files,
error);
if (r < 0)
return r;
@ -591,7 +603,15 @@ static int extract_image_and_extensions(
_cleanup_strv_free_ char **extension_release = NULL;
const char *e;
r = portable_extract_by_path(ext->path, /* path_is_extension= */ true, relax_extension_release_check, matches, &extension_release_meta, &extra_unit_files, error);
r = portable_extract_by_path(
ext->path,
/* path_is_extension= */ true,
relax_extension_release_check,
matches,
image_policy,
&extension_release_meta,
&extra_unit_files,
error);
if (r < 0)
return r;
@ -657,6 +677,7 @@ int portable_extract(
const char *name_or_path,
char **matches,
char **extension_image_paths,
const ImagePolicy *image_policy,
PortableFlags flags,
PortableMetadata **ret_os_release,
OrderedHashmap **ret_extension_releases,
@ -679,6 +700,7 @@ int portable_extract(
extension_image_paths,
/* validate_sysext= */ false,
/* relax_extension_release_check= */ FLAGS_SET(flags, PORTABLE_FORCE_SYSEXT),
image_policy,
&image,
&extension_images,
&extension_releases,
@ -1392,6 +1414,7 @@ int portable_attach(
char **matches,
const char *profile,
char **extension_image_paths,
const ImagePolicy *image_policy,
PortableFlags flags,
PortableChange **changes,
size_t *n_changes,
@ -1412,6 +1435,7 @@ int portable_attach(
extension_image_paths,
/* validate_sysext= */ true,
/* relax_extension_release_check= */ FLAGS_SET(flags, PORTABLE_FORCE_SYSEXT),
image_policy,
&image,
&extension_images,
&extension_releases,

View File

@ -3,6 +3,7 @@
#include "sd-bus.h"
#include "dissect-image.h"
#include "hashmap.h"
#include "macro.h"
#include "set.h"
@ -67,9 +68,9 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(PortableMetadata*, portable_metadata_unref);
int portable_metadata_hashmap_to_sorted_array(Hashmap *unit_files, PortableMetadata ***ret);
int portable_extract(const char *image, char **matches, char **extension_image_paths, PortableFlags flags, PortableMetadata **ret_os_release, OrderedHashmap **ret_extension_releases, Hashmap **ret_unit_files, char ***ret_valid_prefixes, sd_bus_error *error);
int portable_extract(const char *image, char **matches, char **extension_image_paths, const ImagePolicy *image_policy, PortableFlags flags, PortableMetadata **ret_os_release, OrderedHashmap **ret_extension_releases, Hashmap **ret_unit_files, char ***ret_valid_prefixes, sd_bus_error *error);
int portable_attach(sd_bus *bus, const char *name_or_path, char **matches, const char *profile, char **extension_images, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);
int portable_attach(sd_bus *bus, const char *name_or_path, char **matches, const char *profile, char **extension_images, const ImagePolicy* image_policy, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);
int portable_detach(sd_bus *bus, const char *name_or_path, char **extension_image_paths, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);
int portable_get_state(sd_bus *bus, const char *name_or_path, char **extension_image_paths, PortableFlags flags, PortableState *ret, sd_bus_error *error);

View File

@ -60,7 +60,7 @@ int bus_image_common_get_os_release(
return 1;
if (!image->metadata_valid) {
r = image_read_metadata(image);
r = image_read_metadata(image, &image_policy_service);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
}
@ -163,6 +163,7 @@ int bus_image_common_get_metadata(
image->path,
matches,
extension_images,
/* image_policy= */ NULL,
flags,
&os_release,
&extension_releases,
@ -387,6 +388,7 @@ int bus_image_common_attach(
matches,
profile,
extension_images,
/* image_policy= */ NULL,
flags,
&changes,
&n_changes,
@ -729,6 +731,7 @@ int bus_image_common_reattach(
matches,
profile,
extension_images,
/* image_policy= */ NULL,
flags,
&changes_attached,
&n_changes_attached,

View File

@ -959,7 +959,10 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
"ProcSubset",
"NetworkNamespacePath",
"IPCNamespacePath",
"LogNamespace"))
"LogNamespace",
"RootImagePolicy",
"MountImagePolicy",
"ExtensionImagePolicy"))
return bus_append_string(m, field, eq);
if (STR_IN_SET(field, "IgnoreSIGPIPE",

View File

@ -28,6 +28,7 @@
#include "hashmap.h"
#include "hostname-setup.h"
#include "id128-util.h"
#include "initrd-util.h"
#include "lock-util.h"
#include "log.h"
#include "loop-util.h"
@ -73,6 +74,19 @@ static const char* const image_search_path[_IMAGE_CLASS_MAX] = {
"/usr/lib/confexts\0",
};
/* Inside the initrd, use a slightly different set of search path (i.e. include .extra/sysext in extension
* search dir) */
static const char* const image_search_path_initrd[_IMAGE_CLASS_MAX] = {
/* (entries that aren't listed here will get the same search path as for the non initrd-case) */
[IMAGE_EXTENSION] = "/etc/extensions\0" /* only place symlinks here */
"/run/extensions\0" /* and here too */
"/var/lib/extensions\0" /* the main place for images */
"/usr/local/lib/extensions\0"
"/usr/lib/extensions\0"
"/.extra/sysext\0" /* put sysext picked up by systemd-stub last, since not trusted */
};
static Image *image_free(Image *i) {
assert(i);
@ -446,6 +460,14 @@ static int image_make(
return -EMEDIUMTYPE;
}
static const char *pick_image_search_path(ImageClass class) {
if (class < 0 || class >= _IMAGE_CLASS_MAX)
return NULL;
/* Use the initrd search path if there is one, otherwise use the common one */
return in_initrd() && image_search_path_initrd[class] ? image_search_path_initrd[class] : image_search_path[class];
}
int image_find(ImageClass class,
const char *name,
const char *root,
@ -461,7 +483,7 @@ int image_find(ImageClass class,
if (!image_name_is_valid(name))
return -ENOENT;
NULSTR_FOREACH(path, image_search_path[class]) {
NULSTR_FOREACH(path, pick_image_search_path(class)) {
_cleanup_free_ char *resolved = NULL;
_cleanup_closedir_ DIR *d = NULL;
struct stat st;
@ -560,7 +582,7 @@ int image_discover(
assert(class < _IMAGE_CLASS_MAX);
assert(h);
NULSTR_FOREACH(path, image_search_path[class]) {
NULSTR_FOREACH(path, pick_image_search_path(class)) {
_cleanup_free_ char *resolved = NULL;
_cleanup_closedir_ DIR *d = NULL;
@ -1138,7 +1160,7 @@ int image_set_limit(Image *i, uint64_t referenced_max) {
return btrfs_subvol_set_subtree_quota_limit(i->path, 0, referenced_max);
}
int image_read_metadata(Image *i) {
int image_read_metadata(Image *i, const ImagePolicy *image_policy) {
_cleanup_(release_lock_file) LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT;
int r;
@ -1219,7 +1241,9 @@ int image_read_metadata(Image *i) {
r = dissect_loop_device(
d,
NULL, NULL,
/* verity= */ NULL,
/* mount_options= */ NULL,
image_policy,
DISSECT_IMAGE_GENERIC_ROOT |
DISSECT_IMAGE_REQUIRE_ROOT |
DISSECT_IMAGE_RELAX_VAR_CHECK |
@ -1287,7 +1311,7 @@ bool image_in_search_path(
assert(image);
NULSTR_FOREACH(path, image_search_path[class]) {
NULSTR_FOREACH(path, pick_image_search_path(class)) {
const char *p, *q;
size_t k;

View File

@ -7,6 +7,7 @@
#include "sd-id128.h"
#include "hashmap.h"
#include "image-policy.h"
#include "lock-util.h"
#include "macro.h"
#include "os-util.h"
@ -75,7 +76,7 @@ int image_name_lock(const char *name, int operation, LockFile *ret);
int image_set_limit(Image *i, uint64_t referenced_max);
int image_read_metadata(Image *i);
int image_read_metadata(Image *i, const ImagePolicy *image_policy);
bool image_in_search_path(ImageClass class, const char *root, const char *image);

View File

@ -301,7 +301,99 @@ not_found:
}
#if HAVE_BLKID
static int dissected_image_probe_filesystems(DissectedImage *m, int fd) {
static int image_policy_may_use(
const ImagePolicy *policy,
PartitionDesignator designator) {
PartitionPolicyFlags f;
/* For each partition we find in the partition table do a first check if it may exist at all given
* the policy, or if it shall be ignored. */
f = image_policy_get_exhaustively(policy, designator);
if (f < 0)
return f;
if ((f & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_ABSENT)
/* only flag set in policy is "absent"? then this partition may not exist at all */
return log_debug_errno(
SYNTHETIC_ERRNO(ERFKILL),
"Partition of designator '%s' exists, but not allowed by policy, refusing.",
partition_designator_to_string(designator));
if ((f & _PARTITION_POLICY_USE_MASK & ~PARTITION_POLICY_ABSENT) == PARTITION_POLICY_UNUSED) {
/* only "unused" or "unused" + "absent" are set? then don't use it */
log_debug("Partition of designator '%s' exists, and policy dictates to ignore it, doing so.",
partition_designator_to_string(designator));
return false; /* ignore! */
}
return true; /* use! */
}
static int image_policy_check_protection(
const ImagePolicy *policy,
PartitionDesignator designator,
PartitionPolicyFlags found_flags) {
PartitionPolicyFlags policy_flags;
/* Checks if the flags in the policy for the designated partition overlap the flags of what we found */
if (found_flags < 0)
return found_flags;
policy_flags = image_policy_get_exhaustively(policy, designator);
if (policy_flags < 0)
return policy_flags;
if ((found_flags & policy_flags) == 0) {
_cleanup_free_ char *found_flags_string = NULL, *policy_flags_string = NULL;
(void) partition_policy_flags_to_string(found_flags, /* simplify= */ true, &found_flags_string);
(void) partition_policy_flags_to_string(policy_flags, /* simplify= */ true, &policy_flags_string);
return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), "Partition %s discovered with policy '%s' but '%s' was required, refusing.",
partition_designator_to_string(designator),
strnull(found_flags_string), strnull(policy_flags_string));
}
return 0;
}
static int image_policy_check_partition_flags(
const ImagePolicy *policy,
PartitionDesignator designator,
uint64_t gpt_flags) {
PartitionPolicyFlags policy_flags;
bool b;
/* Checks if the partition flags in the policy match reality */
policy_flags = image_policy_get_exhaustively(policy, designator);
if (policy_flags < 0)
return policy_flags;
b = FLAGS_SET(gpt_flags, SD_GPT_FLAG_READ_ONLY);
if ((policy_flags & _PARTITION_POLICY_READ_ONLY_MASK) == (b ? PARTITION_POLICY_READ_ONLY_OFF : PARTITION_POLICY_READ_ONLY_ON))
return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), "Partition %s has 'read-only' flag incorrectly set (must be %s, is %s), refusing.",
partition_designator_to_string(designator),
one_zero(!b), one_zero(b));
b = FLAGS_SET(gpt_flags, SD_GPT_FLAG_GROWFS);
if ((policy_flags & _PARTITION_POLICY_GROWFS_MASK) == (b ? PARTITION_POLICY_GROWFS_OFF : PARTITION_POLICY_GROWFS_ON))
return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), "Partition %s has 'growfs' flag incorrectly set (must be %s, is %s), refusing.",
partition_designator_to_string(designator),
one_zero(!b), one_zero(b));
return 0;
}
static int dissected_image_probe_filesystems(
DissectedImage *m,
int fd,
const ImagePolicy *policy) {
int r;
assert(m);
@ -310,6 +402,7 @@ static int dissected_image_probe_filesystems(DissectedImage *m, int fd) {
for (PartitionDesignator i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) {
DissectedPartition *p = m->partitions + i;
PartitionPolicyFlags found_flags;
if (!p->found)
continue;
@ -325,14 +418,34 @@ static int dissected_image_probe_filesystems(DissectedImage *m, int fd) {
return r;
}
if (streq_ptr(p->fstype, "crypto_LUKS"))
if (streq_ptr(p->fstype, "crypto_LUKS")) {
m->encrypted = true;
found_flags = PARTITION_POLICY_ENCRYPTED; /* found this one, and its definitely encrypted */
} else
/* found it, but it's definitely not encrypted, hence mask the encrypted flag, but
* set all other ways that indicate "present". */
found_flags = PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED;
if (p->fstype && fstype_is_ro(p->fstype))
p->rw = false;
if (!p->rw)
p->growfs = false;
/* We might have learnt more about the file system now (i.e. whether it is encrypted or not),
* hence we need to validate this against policy again, to see if the policy still matches
* with this new information. Note that image_policy_check_protection() will check for
* overlap between what's allowed in the policy and what we pass as 'found_policy' here. In
* the unencrypted case we thus might pass an overly unspecific mask here (i.e. unprotected
* OR verity OR signed), but that's fine since the earlier policy check already checked more
* specific which of those three cases where OK. Keep in mind that this function here only
* looks at specific partitions (and thus can only deduce encryption or not) but not the
* overall partition table (and thus cannot deduce verity or not). The earlier dissection
* checks already did the relevant checks that look at the whole partition table, and
* enforced policy there as needed. */
r = image_policy_check_protection(policy, i, found_flags);
if (r < 0)
return r;
}
return 0;
@ -363,9 +476,7 @@ static void check_partition_flags(
log_debug("Unexpected partition flag %llu set on %s!", bit, node);
}
}
#endif
#if HAVE_BLKID
static int dissected_image_new(const char *path, DissectedImage **ret) {
_cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
_cleanup_free_ char *name = NULL;
@ -543,6 +654,7 @@ static int dissect_image(
const char *devname,
const VeritySettings *verity,
const MountOptions *mount_options,
const ImagePolicy *policy,
DissectImageFlags flags) {
sd_id128_t root_uuid = SD_ID128_NULL, root_verity_uuid = SD_ID128_NULL;
@ -572,7 +684,11 @@ static int dissect_image(
* Returns -ENOPKG if no suitable partition table or file system could be found.
* Returns -EADDRNOTAVAIL if a root hash was specified but no matching root/verity partitions found.
* Returns -ENXIO if we couldn't find any partition suitable as root or /usr partition
* Returns -ENOTUNIQ if we only found multiple generic partitions and thus don't know what to do with that */
* Returns -ENOTUNIQ if we only found multiple generic partitions and thus don't know what to do with that
* Returns -ERFKILL if image doesn't match image policy
* Returns -EBADR if verity data was provided externally for an image that has a GPT partition table (i.e. is not just a naked fs)
* Returns -EPROTONOSUPPORT if DISSECT_IMAGE_ADD_PARTITION_DEVICES is set but the block device does not have partition logic enabled
* Returns -ENOMSG if we didn't find a single usable partition (and DISSECT_IMAGE_REFUSE_EMPTY is set) */
uint64_t diskseq = m->loop ? m->loop->diskseq : 0;
@ -650,6 +766,34 @@ static int dissect_image(
const char *fstype = NULL, *options = NULL, *suuid = NULL;
_cleanup_close_ int mount_node_fd = -EBADF;
sd_id128_t uuid = SD_ID128_NULL;
PartitionPolicyFlags found_flags;
bool encrypted;
/* OK, we have found a file system, that's our root partition then. */
r = image_policy_may_use(policy, PARTITION_ROOT);
if (r < 0)
return r;
if (r == 0) /* policy says ignore this, so we ignore it */
return -ENOPKG;
(void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
(void) blkid_probe_lookup_value(b, "UUID", &suuid, NULL);
encrypted = streq_ptr(fstype, "crypto_LUKS");
if (verity_settings_data_covers(verity, PARTITION_ROOT))
found_flags = verity->root_hash_sig ? PARTITION_POLICY_SIGNED : PARTITION_POLICY_VERITY;
else
found_flags = encrypted ? PARTITION_POLICY_ENCRYPTED : PARTITION_POLICY_UNPROTECTED;
r = image_policy_check_protection(policy, PARTITION_ROOT, found_flags);
if (r < 0)
return r;
r = image_policy_check_partition_flags(policy, PARTITION_ROOT, 0); /* we have no gpt partition flags, hence check against all bits off */
if (r < 0)
return r;
if (FLAGS_SET(flags, DISSECT_IMAGE_PIN_PARTITION_DEVICES)) {
mount_node_fd = open_partition(devname, /* is_partition = */ false, m->loop);
@ -657,10 +801,6 @@ static int dissect_image(
return mount_node_fd;
}
/* OK, we have found a file system, that's our root partition then. */
(void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
(void) blkid_probe_lookup_value(b, "UUID", &suuid, NULL);
if (fstype) {
t = strdup(fstype);
if (!t)
@ -681,7 +821,7 @@ static int dissect_image(
return r;
m->single_file_system = true;
m->encrypted = streq_ptr(fstype, "crypto_LUKS");
m->encrypted = encrypted;
m->has_verity = verity && verity->data_path;
m->verity_ready = verity_settings_data_covers(verity, PARTITION_ROOT);
@ -1049,6 +1189,18 @@ static int dissect_image(
_cleanup_close_ int mount_node_fd = -EBADF;
const char *options = NULL;
r = image_policy_may_use(policy, type.designator);
if (r < 0)
return r;
if (r == 0) {
/* Policy says: ignore; Remember this fact, so that we later can distinguish between "found but ignored" and "not found at all" */
if (!m->partitions[type.designator].found)
m->partitions[type.designator].ignored = true;
continue;
}
if (m->partitions[type.designator].found) {
/* For most partition types the first one we see wins. Except for the
* rootfs and /usr, where we do a version compare of the label, and
@ -1139,6 +1291,16 @@ static int dissect_image(
sd_id128_t id = SD_ID128_NULL;
const char *options = NULL;
r = image_policy_may_use(policy, PARTITION_XBOOTLDR);
if (r < 0)
return r;
if (r == 0) { /* policy says: ignore */
if (!m->partitions[PARTITION_XBOOTLDR].found)
m->partitions[PARTITION_XBOOTLDR].ignored = true;
continue;
}
/* First one wins */
if (m->partitions[PARTITION_XBOOTLDR].found)
continue;
@ -1223,41 +1385,49 @@ static int dissect_image(
/* If we didn't find a generic node, then we can't fix this up either */
if (generic_node) {
_cleanup_close_ int mount_node_fd = -EBADF;
_cleanup_free_ char *o = NULL, *n = NULL;
const char *options;
if (FLAGS_SET(flags, DISSECT_IMAGE_PIN_PARTITION_DEVICES)) {
mount_node_fd = open_partition(generic_node, /* is_partition = */ true, m->loop);
if (mount_node_fd < 0)
return mount_node_fd;
}
r = make_partition_devname(devname, diskseq, generic_nr, flags, &n);
r = image_policy_may_use(policy, PARTITION_ROOT);
if (r < 0)
return r;
if (r == 0)
/* Policy says: ignore; remember that we did */
m->partitions[PARTITION_ROOT].ignored = true;
else {
_cleanup_close_ int mount_node_fd = -EBADF;
_cleanup_free_ char *o = NULL, *n = NULL;
const char *options;
options = mount_options_from_designator(mount_options, PARTITION_ROOT);
if (options) {
o = strdup(options);
if (!o)
return -ENOMEM;
if (FLAGS_SET(flags, DISSECT_IMAGE_PIN_PARTITION_DEVICES)) {
mount_node_fd = open_partition(generic_node, /* is_partition = */ true, m->loop);
if (mount_node_fd < 0)
return mount_node_fd;
}
r = make_partition_devname(devname, diskseq, generic_nr, flags, &n);
if (r < 0)
return r;
options = mount_options_from_designator(mount_options, PARTITION_ROOT);
if (options) {
o = strdup(options);
if (!o)
return -ENOMEM;
}
assert(generic_nr >= 0);
m->partitions[PARTITION_ROOT] = (DissectedPartition) {
.found = true,
.rw = generic_rw,
.growfs = generic_growfs,
.partno = generic_nr,
.architecture = _ARCHITECTURE_INVALID,
.node = TAKE_PTR(n),
.uuid = generic_uuid,
.mount_options = TAKE_PTR(o),
.mount_node_fd = TAKE_FD(mount_node_fd),
.offset = UINT64_MAX,
.size = UINT64_MAX,
};
}
assert(generic_nr >= 0);
m->partitions[PARTITION_ROOT] = (DissectedPartition) {
.found = true,
.rw = generic_rw,
.growfs = generic_growfs,
.partno = generic_nr,
.architecture = _ARCHITECTURE_INVALID,
.node = TAKE_PTR(n),
.uuid = generic_uuid,
.mount_options = TAKE_PTR(o),
.mount_node_fd = TAKE_FD(mount_node_fd),
.offset = UINT64_MAX,
.size = UINT64_MAX,
};
}
}
@ -1319,7 +1489,42 @@ static int dissect_image(
}
}
r = dissected_image_probe_filesystems(m, fd);
bool any = false;
/* After we discovered all partitions let's see if the verity requirements match the policy. (Note:
* 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);
r = image_policy_check_protection(policy, di, found_flags);
if (r < 0)
return r;
if (m->partitions[di].found) {
r = image_policy_check_partition_flags(policy, di, m->partitions[di].gpt_flags);
if (r < 0)
return r;
}
}
if (!any && !FLAGS_SET(flags, DISSECT_IMAGE_ALLOW_EMPTY))
return -ENOMSG;
r = dissected_image_probe_filesystems(m, fd, policy);
if (r < 0)
return r;
@ -1331,6 +1536,7 @@ int dissect_image_file(
const char *path,
const VeritySettings *verity,
const MountOptions *mount_options,
const ImagePolicy *image_policy,
DissectImageFlags flags,
DissectedImage **ret) {
@ -1340,7 +1546,6 @@ int dissect_image_file(
int r;
assert(path);
assert(ret);
fd = open(path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
if (fd < 0)
@ -1358,17 +1563,81 @@ int dissect_image_file(
if (r < 0)
return r;
r = dissect_image(m, fd, path, verity, mount_options, flags);
r = dissect_image(m, fd, path, verity, mount_options, image_policy, flags);
if (r < 0)
return r;
*ret = TAKE_PTR(m);
if (ret)
*ret = TAKE_PTR(m);
return 0;
#else
return -EOPNOTSUPP;
#endif
}
static int dissect_log_error(int r, const char *name, const VeritySettings *verity) {
assert(name);
switch (r) {
case 0 ... INT_MAX: /* success! */
return r;
case -EOPNOTSUPP:
return log_error_errno(r, "Dissecting images is not supported, compiled without blkid support.");
case -ENOPKG:
return log_error_errno(r, "%s: Couldn't identify a suitable partition table or file system.", name);
case -ENOMEDIUM:
return log_error_errno(r, "%s: The image does not pass os-release/extension-release validation.", name);
case -EADDRNOTAVAIL:
return log_error_errno(r, "%s: No root partition for specified root hash found.", name);
case -ENOTUNIQ:
return log_error_errno(r, "%s: Multiple suitable root partitions found in image.", name);
case -ENXIO:
return log_error_errno(r, "%s: No suitable root partition found in image.", name);
case -EPROTONOSUPPORT:
return log_error_errno(r, "Device '%s' is a loopback block device with partition scanning turned off, please turn it on.", name);
case -ENOTBLK:
return log_error_errno(r, "%s: Image is not a block device.", name);
case -EBADR:
return log_error_errno(r,
"Combining partitioned images (such as '%s') with external Verity data (such as '%s') not supported. "
"(Consider setting $SYSTEMD_DISSECT_VERITY_SIDECAR=0 to disable automatic discovery of external Verity data.)",
name, strna(verity ? verity->data_path : NULL));
case -ERFKILL:
return log_error_errno(r, "%s: image does not match image policy.", name);
case -ENOMSG:
return log_error_errno(r, "%s: no suitable partitions found.", name);
default:
return log_error_errno(r, "Failed to dissect image '%s': %m", name);
}
}
int dissect_image_file_and_warn(
const char *path,
const VeritySettings *verity,
const MountOptions *mount_options,
const ImagePolicy *image_policy,
DissectImageFlags flags,
DissectedImage **ret) {
return dissect_log_error(
dissect_image_file(path, verity, mount_options, image_policy, flags, ret),
path,
verity);
}
DissectedImage* dissected_image_unref(DissectedImage *m) {
if (!m)
return NULL;
@ -3250,6 +3519,7 @@ int dissect_loop_device(
LoopDevice *loop,
const VeritySettings *verity,
const MountOptions *mount_options,
const ImagePolicy *image_policy,
DissectImageFlags flags,
DissectedImage **ret) {
@ -3258,7 +3528,6 @@ int dissect_loop_device(
int r;
assert(loop);
assert(ret);
r = dissected_image_new(loop->backing_file ?: loop->node, &m);
if (r < 0)
@ -3267,11 +3536,13 @@ int dissect_loop_device(
m->loop = loop_device_ref(loop);
m->sector_size = m->loop->sector_size;
r = dissect_image(m, loop->fd, loop->node, verity, mount_options, flags);
r = dissect_image(m, loop->fd, loop->node, verity, mount_options, image_policy, flags);
if (r < 0)
return r;
*ret = TAKE_PTR(m);
if (ret)
*ret = TAKE_PTR(m);
return 0;
#else
return -EOPNOTSUPP;
@ -3282,56 +3553,17 @@ int dissect_loop_device_and_warn(
LoopDevice *loop,
const VeritySettings *verity,
const MountOptions *mount_options,
const ImagePolicy *image_policy,
DissectImageFlags flags,
DissectedImage **ret) {
const char *name;
int r;
assert(loop);
assert(loop->fd >= 0);
name = ASSERT_PTR(loop->backing_file ?: loop->node);
return dissect_log_error(
dissect_loop_device(loop, verity, mount_options, image_policy, flags, ret),
loop->backing_file ?: loop->node,
verity);
r = dissect_loop_device(loop, verity, mount_options, flags, ret);
switch (r) {
case -EOPNOTSUPP:
return log_error_errno(r, "Dissecting images is not supported, compiled without blkid support.");
case -ENOPKG:
return log_error_errno(r, "%s: Couldn't identify a suitable partition table or file system.", name);
case -ENOMEDIUM:
return log_error_errno(r, "%s: The image does not pass validation.", name);
case -EADDRNOTAVAIL:
return log_error_errno(r, "%s: No root partition for specified root hash found.", name);
case -ENOTUNIQ:
return log_error_errno(r, "%s: Multiple suitable root partitions found in image.", name);
case -ENXIO:
return log_error_errno(r, "%s: No suitable root partition found in image.", name);
case -EPROTONOSUPPORT:
return log_error_errno(r, "Device '%s' is loopback block device with partition scanning turned off, please turn it on.", name);
case -ENOTBLK:
return log_error_errno(r, "%s: Image is not a block device.", name);
case -EBADR:
return log_error_errno(r,
"Combining partitioned images (such as '%s') with external Verity data (such as '%s') not supported. "
"(Consider setting $SYSTEMD_DISSECT_VERITY_SIDECAR=0 to disable automatic discovery of external Verity data.)",
name, strna(verity ? verity->data_path : NULL));
default:
if (r < 0)
return log_error_errno(r, "Failed to dissect image '%s': %m", name);
return r;
}
}
bool dissected_image_verity_candidate(const DissectedImage *image, PartitionDesignator partition_designator) {
@ -3407,6 +3639,7 @@ const char* mount_options_from_designator(const MountOptions *options, Partition
int mount_image_privately_interactively(
const char *image,
const ImagePolicy *image_policy,
DissectImageFlags flags,
char **ret_directory,
int *ret_dir_fd,
@ -3449,7 +3682,13 @@ int mount_image_privately_interactively(
if (r < 0)
return log_error_errno(r, "Failed to set up loopback device for %s: %m", image);
r = dissect_loop_device_and_warn(d, &verity, NULL, flags, &dissected_image);
r = dissect_loop_device_and_warn(
d,
&verity,
/* mount_options= */ NULL,
image_policy,
flags,
&dissected_image);
if (r < 0)
return r;
@ -3513,6 +3752,7 @@ int verity_dissect_and_mount(
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,
@ -3556,6 +3796,7 @@ int verity_dissect_and_mount(
loop_device,
&verity,
options,
image_policy,
dissect_image_flags,
&dissected_image);
/* No partition table? Might be a single-filesystem image, try again */
@ -3564,6 +3805,7 @@ int verity_dissect_and_mount(
loop_device,
&verity,
options,
image_policy,
dissect_image_flags | DISSECT_IMAGE_NO_PARTITION_TABLE,
&dissected_image);
if (r < 0)

View File

@ -19,6 +19,7 @@ typedef struct VeritySettings VeritySettings;
struct DissectedPartition {
bool found:1;
bool ignored:1;
bool rw:1;
bool growfs:1;
int partno; /* -1 if there was no partition and the images contains a file system directly */
@ -79,6 +80,7 @@ typedef enum DissectImageFlags {
DISSECT_IMAGE_PIN_PARTITION_DEVICES = 1 << 21, /* Open dissected partitions and decrypted partitions and pin them by fd */
DISSECT_IMAGE_RELAX_SYSEXT_CHECK = 1 << 22, /* Don't insist that the extension-release file name matches the image name */
DISSECT_IMAGE_DISKSEQ_DEVNODE = 1 << 23, /* Prefer /dev/disk/by-diskseq/… device nodes */
DISSECT_IMAGE_ALLOW_EMPTY = 1 << 24, /* Allow that no usable partitions is present */
} DissectImageFlags;
struct DissectedImage {
@ -133,6 +135,9 @@ struct VeritySettings {
.designator = _PARTITION_DESIGNATOR_INVALID \
}
/* We include image-policy.h down here, since ImagePolicy wants a complete definition of PartitionDesignator first. */
#include "image-policy.h"
MountOptions* mount_options_free_all(MountOptions *options);
DEFINE_TRIVIAL_CLEANUP_FUNC(MountOptions*, mount_options_free_all);
const char* mount_options_from_designator(const MountOptions *options, PartitionDesignator designator);
@ -141,14 +146,11 @@ int probe_filesystem_full(int fd, const char *path, uint64_t offset, uint64_t si
static inline int probe_filesystem(const char *path, char **ret_fstype) {
return probe_filesystem_full(-1, path, 0, UINT64_MAX, ret_fstype);
}
int dissect_image_file(
const char *path,
const VeritySettings *verity,
const MountOptions *mount_options,
DissectImageFlags flags,
DissectedImage **ret);
int dissect_loop_device(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, DissectImageFlags flags, DissectedImage **ret);
int dissect_loop_device_and_warn(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, DissectImageFlags flags, DissectedImage **ret);
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);
DissectedImage* dissected_image_unref(DissectedImage *m);
DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref);
@ -185,9 +187,9 @@ bool dissected_image_verity_candidate(const DissectedImage *image, PartitionDesi
bool dissected_image_verity_ready(const DissectedImage *image, PartitionDesignator d);
bool dissected_image_verity_sig_ready(const DissectedImage *image, PartitionDesignator d);
int mount_image_privately_interactively(const char *path, DissectImageFlags flags, char **ret_directory, int *ret_dir_fd, LoopDevice **ret_loop_device);
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 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_sysext_scope);
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_sysext_scope);
int dissect_fstype_ok(const char *fstype);

689
src/shared/image-policy.c Normal file
View File

@ -0,0 +1,689 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
#include "extract-word.h"
#include "image-policy.h"
#include "logarithm.h"
#include "sort-util.h"
#include "string-util.h"
#include "strv.h"
/* Rationale for the chosen syntax:
*
* one line, so that it can be reasonably added to a shell command line, for example via `systemd-dissect
* --image-policy=` or to the kernel command line via `systemd.image_policy=`.
*
* no use of "," or ";" as separators, so that it can be included in mount/fstab-style option strings and
* doesn't require escaping. Instead, separators are ":", "=", "+" which should be fine both in shell
* command lines and in mount/fstab style option strings.
*/
static int partition_policy_compare(const PartitionPolicy *a, const PartitionPolicy *b) {
return CMP(ASSERT_PTR(a)->designator, ASSERT_PTR(b)->designator);
}
static PartitionPolicy* image_policy_bsearch(const ImagePolicy *policy, PartitionDesignator designator) {
if (!policy)
return NULL;
return typesafe_bsearch(
&(PartitionPolicy) { .designator = designator },
ASSERT_PTR(policy)->policies,
ASSERT_PTR(policy)->n_policies,
partition_policy_compare);
}
static PartitionPolicyFlags partition_policy_normalized_flags(const PartitionPolicy *policy) {
PartitionPolicyFlags flags = ASSERT_PTR(policy)->flags;
/* This normalizes the per-partition policy flags. This means if the user left some things
* unspecified, we'll fill in the appropriate "dontcare" policy instead. We'll also mask out bits
* that do not make any sense for specific partition types. */
/* If no protection flag is set, then this means all are set */
if ((flags & _PARTITION_POLICY_USE_MASK) == 0)
flags |= PARTITION_POLICY_OPEN;
/* If this is a verity or verity signature designator, then mask off all protection bits, this after
* all needs no protection, because it *is* the protection */
if (partition_verity_to_data(policy->designator) >= 0 ||
partition_verity_sig_to_data(policy->designator) >= 0)
flags &= ~(PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED);
/* if this designator has no verity concept, then mask off verity protection flags */
if (partition_verity_of(policy->designator) < 0)
flags &= ~(PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED);
if ((flags & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_ABSENT)
/* If the partition must be absent, then the gpt flags don't matter */
flags &= ~(_PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK);
else {
/* If the gpt flags bits are not specified, set both options for each */
if ((flags & _PARTITION_POLICY_READ_ONLY_MASK) == 0)
flags |= PARTITION_POLICY_READ_ONLY_ON|PARTITION_POLICY_READ_ONLY_OFF;
if ((flags & _PARTITION_POLICY_GROWFS_MASK) == 0)
flags |= PARTITION_POLICY_GROWFS_ON|PARTITION_POLICY_GROWFS_OFF;
}
return flags;
}
PartitionPolicyFlags image_policy_get(const ImagePolicy *policy, PartitionDesignator designator) {
PartitionDesignator data_designator = _PARTITION_DESIGNATOR_INVALID;
PartitionPolicy *pp;
/* No policy means: everything may be used in any mode */
if (!policy)
return partition_policy_normalized_flags(
&(const PartitionPolicy) {
.flags = PARTITION_POLICY_OPEN,
.designator = designator,
});
pp = image_policy_bsearch(policy, designator);
if (pp)
return partition_policy_normalized_flags(pp);
/* Hmm, so this didn't work, then let's see if we can derive some policy from the underlying data
* partition in case of verity/signature partitions */
data_designator = partition_verity_to_data(designator);
if (data_designator >= 0) {
PartitionPolicyFlags data_flags;
/* So we are asked for the policy for a verity partition, and there's no explicit policy for
* that case. Let's synthesize a policy from the protection setting for the underlying data
* partition. */
data_flags = image_policy_get(policy, data_designator);
if (data_flags < 0)
return data_flags;
/* We need verity if verity or verity with sig is requested */
if (!(data_flags & (PARTITION_POLICY_SIGNED|PARTITION_POLICY_VERITY)))
return _PARTITION_POLICY_FLAGS_INVALID;
/* If the data partition may be unused or absent, then the verity partition may too. Also, inherit the partition flags policy */
return partition_policy_normalized_flags(
&(const PartitionPolicy) {
.flags = PARTITION_POLICY_UNPROTECTED | (data_flags & (PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT)) |
(data_flags & _PARTITION_POLICY_PFLAGS_MASK),
.designator = designator,
});
}
data_designator = partition_verity_sig_to_data(designator);
if (data_designator >= 0) {
PartitionPolicyFlags data_flags;
/* Similar case as for verity partitions, but slightly more strict rules */
data_flags = image_policy_get(policy, data_designator);
if (data_flags < 0)
return data_flags;
if (!(data_flags & PARTITION_POLICY_SIGNED))
return _PARTITION_POLICY_FLAGS_INVALID;
return partition_policy_normalized_flags(
&(const PartitionPolicy) {
.flags = PARTITION_POLICY_UNPROTECTED | (data_flags & (PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT)) |
(data_flags & _PARTITION_POLICY_PFLAGS_MASK),
.designator = designator,
});
}
return _PARTITION_POLICY_FLAGS_INVALID; /* got nothing */
}
PartitionPolicyFlags image_policy_get_exhaustively(const ImagePolicy *policy, PartitionDesignator designator) {
PartitionPolicyFlags flags;
/* This is just like image_policy_get() but whenever there is no policy for a specific designator, we
* return the default policy. */
flags = image_policy_get(policy, designator);
if (flags < 0)
return partition_policy_normalized_flags(
&(const PartitionPolicy) {
.flags = image_policy_default(policy),
.designator = designator,
});
return flags;
}
static PartitionPolicyFlags policy_flag_from_string_one(const char *s) {
assert(s);
/* This is a bitmask (i.e. not dense), hence we don't use the "string-table.h" stuff here. */
if (streq(s, "verity"))
return PARTITION_POLICY_VERITY;
if (streq(s, "signed"))
return PARTITION_POLICY_SIGNED;
if (streq(s, "encrypted"))
return PARTITION_POLICY_ENCRYPTED;
if (streq(s, "unprotected"))
return PARTITION_POLICY_UNPROTECTED;
if (streq(s, "unused"))
return PARTITION_POLICY_UNUSED;
if (streq(s, "absent"))
return PARTITION_POLICY_ABSENT;
if (streq(s, "open")) /* shortcut alias */
return PARTITION_POLICY_OPEN;
if (streq(s, "ignore")) /* ditto */
return PARTITION_POLICY_IGNORE;
if (streq(s, "read-only-on"))
return PARTITION_POLICY_READ_ONLY_ON;
if (streq(s, "read-only-off"))
return PARTITION_POLICY_READ_ONLY_OFF;
if (streq(s, "growfs-on"))
return PARTITION_POLICY_GROWFS_ON;
if (streq(s, "growfs-off"))
return PARTITION_POLICY_GROWFS_OFF;
return _PARTITION_POLICY_FLAGS_INVALID;
}
PartitionPolicyFlags partition_policy_flags_from_string(const char *s) {
PartitionPolicyFlags flags = 0;
int r;
assert(s);
if (empty_or_dash(s))
return 0;
for (;;) {
_cleanup_free_ char *f = NULL;
PartitionPolicyFlags ff;
r = extract_first_word(&s, &f, "+", EXTRACT_DONT_COALESCE_SEPARATORS);
if (r < 0)
return r;
if (r == 0)
break;
ff = policy_flag_from_string_one(strstrip(f));
if (ff < 0)
return -EBADRQC; /* recognizable error */
flags |= ff;
}
return flags;
}
static ImagePolicy* image_policy_new(size_t n_policies) {
ImagePolicy *p;
if (n_policies > (SIZE_MAX - offsetof(ImagePolicy, policies)) / sizeof(PartitionPolicy)) /* overflow check */
return NULL;
p = malloc(offsetof(ImagePolicy, policies) + sizeof(PartitionPolicy) * n_policies);
if (!p)
return NULL;
*p = (ImagePolicy) {
.default_flags = PARTITION_POLICY_IGNORE,
};
return p;
}
int image_policy_from_string(const char *s, ImagePolicy **ret) {
_cleanup_free_ ImagePolicy *p = NULL;
uint64_t dmask = 0;
ImagePolicy *t;
PartitionPolicyFlags symbolic_policy;
int r;
assert(s);
assert_cc(sizeof(dmask) * 8 >= _PARTITION_DESIGNATOR_MAX);
/* Recognizable errors:
*
* ENOTUNIQ Two or more rules for the same partition
* EBADSLT Unknown partition designator
* EBADRQC Unknown policy flags
*/
/* First, let's handle "symbolic" policies, i.e. "-", "*", "~" */
if (empty_or_dash(s))
/* ignore policy: everything may exist, but nothing used */
symbolic_policy = PARTITION_POLICY_IGNORE;
else if (streq(s, "*"))
/* allow policy: everything is allowed */
symbolic_policy = PARTITION_POLICY_OPEN;
else if (streq(s, "~"))
/* deny policy: nothing may exist */
symbolic_policy = PARTITION_POLICY_ABSENT;
else
symbolic_policy = _PARTITION_POLICY_FLAGS_INVALID;
if (symbolic_policy >= 0) {
if (!ret)
return 0;
p = image_policy_new(0);
if (!p)
return -ENOMEM;
p->default_flags = symbolic_policy;
*ret = TAKE_PTR(p);
return 0;
}
/* Allocate the policy at maximum size, i.e. for all designators. We might overshoot a bit, but the
* items are cheap, and we can return unused space to libc once we know we don't need it */
p = image_policy_new(_PARTITION_DESIGNATOR_MAX);
if (!p)
return -ENOMEM;
const char *q = s;
bool default_specified = false;
for (;;) {
_cleanup_free_ char *e = NULL, *d = NULL;
PartitionDesignator designator;
PartitionPolicyFlags flags;
char *f, *ds, *fs;
r = extract_first_word(&q, &e, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
if (r < 0)
return r;
if (r == 0)
break;
f = e;
r = extract_first_word((const char**) &f, &d, "=", EXTRACT_DONT_COALESCE_SEPARATORS);
if (r < 0)
return r;
if (r == 0)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Expected designator name followed by '='; got instead: %s", e);
if (!f) /* no separator? */
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing '=' in policy expression: %s", e);
ds = strstrip(d);
if (isempty(ds)) {
/* Not partition name? then it's the default policy */
if (default_specified)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "Default partition policy flags specified more than once.");
designator = _PARTITION_DESIGNATOR_INVALID;
default_specified = true;
} else {
designator = partition_designator_from_string(ds);
if (designator < 0)
return log_debug_errno(SYNTHETIC_ERRNO(EBADSLT), "Unknown partition designator: %s", ds); /* recognizable error */
if (dmask & (UINT64_C(1) << designator))
return log_debug_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "Partition designator specified more than once: %s", ds);
dmask |= UINT64_C(1) << designator;
}
fs = strstrip(f);
flags = partition_policy_flags_from_string(fs);
if (flags == -EBADRQC)
return log_debug_errno(flags, "Unknown partition policy flag: %s", fs);
if (flags < 0)
return log_debug_errno(flags, "Failed to parse partition policy flags '%s': %m", fs);
if (designator < 0)
p->default_flags = flags;
else {
p->policies[p->n_policies++] = (PartitionPolicy) {
.designator = designator,
.flags = flags,
};
}
};
assert(p->n_policies <= _PARTITION_DESIGNATOR_MAX);
/* Return unused space to libc */
t = realloc(p, offsetof(ImagePolicy, policies) + sizeof(PartitionPolicy) * p->n_policies);
if (t)
p = t;
typesafe_qsort(p->policies, p->n_policies, partition_policy_compare);
if (ret)
*ret = TAKE_PTR(p);
return 0;
}
int partition_policy_flags_to_string(PartitionPolicyFlags flags, bool simplify, char **ret) {
_cleanup_free_ char *buf = NULL;
const char *l[CONST_LOG2U(_PARTITION_POLICY_MASK) + 1]; /* one string per known flag at most */
size_t m = 0;
assert(ret);
if (flags < 0)
return -EINVAL;
/* If 'simplify' is false we'll output the precise value of every single flag.
*
* If 'simplify' is true we'll try to make the output shorter, by doing the following:
*
* we'll spell the long form "verity+signed+encrypted+unprotected+unused+absent" via its
* equivalent shortcut form "open" (which we happily parse btw, see above)
*
* we'll spell the long form "unused+absent" via its shortcut "ignore" (which we are also happy
* to parse)
*
* if the read-only/growfs policy flags are both set, we suppress them. this thus removes the
* distinction between "user explicitly declared don't care" and "we implied don't care because
* user didn't say anything".
*
* net result: the resulting string is shorter, but the effective policy declared that way will have
* the same results as the long form. */
if (simplify && (flags & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_OPEN)
l[m++] = "open";
else if (simplify && (flags & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_IGNORE)
l[m++] = "ignore";
else {
if (flags & PARTITION_POLICY_VERITY)
l[m++] = "verity";
if (flags & PARTITION_POLICY_SIGNED)
l[m++] = "signed";
if (flags & PARTITION_POLICY_ENCRYPTED)
l[m++] = "encrypted";
if (flags & PARTITION_POLICY_UNPROTECTED)
l[m++] = "unprotected";
if (flags & PARTITION_POLICY_UNUSED)
l[m++] = "unused";
if (flags & PARTITION_POLICY_ABSENT)
l[m++] = "absent";
}
if (!simplify || (!(flags & PARTITION_POLICY_READ_ONLY_ON) != !(flags & PARTITION_POLICY_READ_ONLY_OFF))) {
if (flags & PARTITION_POLICY_READ_ONLY_ON)
l[m++] = "read-only-on";
if (flags & PARTITION_POLICY_READ_ONLY_OFF)
l[m++] = "read-only-off";
}
if (!simplify || (!(flags & PARTITION_POLICY_GROWFS_ON) != !(flags & PARTITION_POLICY_GROWFS_OFF))) {
if (flags & PARTITION_POLICY_GROWFS_OFF)
l[m++] = "growfs-off";
if (flags & PARTITION_POLICY_GROWFS_ON)
l[m++] = "growfs-on";
}
if (m == 0)
buf = strdup("-");
else {
assert(m+1 < ELEMENTSOF(l));
l[m] = NULL;
buf = strv_join((char**) l, "+");
}
if (!buf)
return -ENOMEM;
*ret = TAKE_PTR(buf);
return 0;
}
static int image_policy_flags_all_match(const ImagePolicy *policy, PartitionPolicyFlags expected) {
if (expected < 0)
return -EINVAL;
if (image_policy_default(policy) != expected)
return false;
for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
PartitionPolicyFlags f, w;
f = image_policy_get_exhaustively(policy, d);
if (f < 0)
return f;
w = partition_policy_normalized_flags(
&(const PartitionPolicy) {
.flags = expected,
.designator = d,
});
if (w < 0)
return w;
if (f != w)
return false;
}
return true;
}
bool image_policy_equiv_ignore(const ImagePolicy *policy) {
/* Checks if this is the ignore policy (or equivalent to it), i.e. everything is ignored, aka '-', aka '' */
return image_policy_flags_all_match(policy, PARTITION_POLICY_IGNORE);
}
bool image_policy_equiv_allow(const ImagePolicy *policy) {
/* Checks if this is the allow policy (or equivalent to it), i.e. everything is allowed, aka '*' */
return image_policy_flags_all_match(policy, PARTITION_POLICY_OPEN);
}
bool image_policy_equiv_deny(const ImagePolicy *policy) {
/* Checks if this is the deny policy (or equivalent to it), i.e. everything must be absent, aka '~' */
return image_policy_flags_all_match(policy, PARTITION_POLICY_ABSENT);
}
int image_policy_to_string(const ImagePolicy *policy, bool simplify, char **ret) {
_cleanup_free_ char *s = NULL;
int r;
assert(ret);
if (simplify) {
const char *fixed;
if (image_policy_equiv_allow(policy))
fixed = "*";
else if (image_policy_equiv_ignore(policy))
fixed = "-";
else if (image_policy_equiv_deny(policy))
fixed = "~";
else
fixed = NULL;
if (fixed) {
s = strdup(fixed);
if (!s)
return -ENOMEM;
*ret = TAKE_PTR(s);
return 0;
}
}
for (size_t i = 0; i < image_policy_n_entries(policy); i++) {
const PartitionPolicy *p = policy->policies + i;
_cleanup_free_ char *f = NULL;
const char *t;
assert(i == 0 || p->designator > policy->policies[i-1].designator); /* Validate perfect ordering */
assert_se(t = partition_designator_to_string(p->designator));
if (simplify) {
/* Skip policy entries that match the default anyway */
PartitionPolicyFlags df;
df = partition_policy_normalized_flags(
&(const PartitionPolicy) {
.flags = image_policy_default(policy),
.designator = p->designator,
});
if (df < 0)
return df;
if (df == p->flags)
continue;
}
r = partition_policy_flags_to_string(p->flags, simplify, &f);
if (r < 0)
return r;
if (!strextend(&s, isempty(s) ? "" : ":", t, "=", f))
return -ENOMEM;
}
if (!simplify || image_policy_default(policy) != PARTITION_POLICY_IGNORE) {
_cleanup_free_ char *df = NULL;
r = partition_policy_flags_to_string(image_policy_default(policy), simplify, &df);
if (r < 0)
return r;
if (!strextend(&s, isempty(s) ? "" : ":", "=", df))
return -ENOMEM;
}
if (isempty(s)) { /* no rule and default policy? then let's return "-" */
s = strdup("-");
if (!s)
return -ENOMEM;
}
*ret = TAKE_PTR(s);
return 0;
}
bool image_policy_equal(const ImagePolicy *a, const ImagePolicy *b) {
if (a == b)
return true;
if (image_policy_n_entries(a) != image_policy_n_entries(b))
return false;
if (image_policy_default(a) != image_policy_default(b))
return false;
for (size_t i = 0; i < image_policy_n_entries(a); i++) {
if (a->policies[i].designator != b->policies[i].designator)
return false;
if (a->policies[i].flags != b->policies[i].flags)
return false;
}
return true;
}
int image_policy_equivalent(const ImagePolicy *a, const ImagePolicy *b) {
/* The image_policy_equal() function checks if the policy is defined the exact same way. This
* function here instead looks at the outcome of the two policies instead. Where does this come to
* different results you ask? We imply some logic regarding Verity/Encryption: when no rule is
* defined for a verity partition we can synthesize it from the protection level of the data
* partition it protects. Or: any per-partition rule that is identical to the default rule is
* redundant, and will be recognized as such by image_policy_equivalent() but not by
* image_policy_equal()- */
if (image_policy_default(a) != image_policy_default(b))
return false;
for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
PartitionPolicyFlags f, w;
f = image_policy_get_exhaustively(a, d);
if (f < 0)
return f;
w = image_policy_get_exhaustively(b, d);
if (w < 0)
return w;
if (f != w)
return false;
}
return true;
}
const ImagePolicy image_policy_allow = {
/* Allow policy */
.n_policies = 0,
.default_flags = PARTITION_POLICY_OPEN,
};
const ImagePolicy image_policy_deny = {
/* Allow policy */
.n_policies = 0,
.default_flags = PARTITION_POLICY_ABSENT,
};
const ImagePolicy image_policy_ignore = {
/* Allow policy */
.n_policies = 0,
.default_flags = PARTITION_POLICY_IGNORE,
};
const ImagePolicy image_policy_sysext = {
/* For system extensions, honour root file system, and /usr/ and ignore everything else. After all,
* we are only interested in /usr/ + /opt/ trees anyway, and that's really the only place they can
* be. */
.n_policies = 2,
.policies = {
{ PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
},
.default_flags = PARTITION_POLICY_IGNORE,
};
const ImagePolicy image_policy_sysext_strict = {
/* For system extensions, requiring signing */
.n_policies = 2,
.policies = {
{ PARTITION_ROOT, PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT },
{ PARTITION_USR, PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT },
},
.default_flags = PARTITION_POLICY_IGNORE,
};
const ImagePolicy image_policy_container = {
/* For systemd-nspawn containers we use all partitions, with the exception of swap */
.n_policies = 8,
.policies = {
{ PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_ESP, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_XBOOTLDR, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
},
.default_flags = PARTITION_POLICY_IGNORE,
};
const ImagePolicy image_policy_host = {
/* For the host policy we basically use everything */
.n_policies = 9,
.policies = {
{ PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_ESP, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_XBOOTLDR, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_SWAP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
},
.default_flags = PARTITION_POLICY_IGNORE,
};
const ImagePolicy image_policy_service = {
/* For RootImage= in services we skip ESP/XBOOTLDR and swap */
.n_policies = 6,
.policies = {
{ PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
{ PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
},
.default_flags = PARTITION_POLICY_IGNORE,
};

97
src/shared/image-policy.h Normal file
View File

@ -0,0 +1,97 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
typedef struct ImagePolicy ImagePolicy;
#include "dissect-image.h"
#include "errno-list.h"
typedef enum PartitionPolicyFlags {
/* Not all policy flags really make sense on all partition types, see comments. But even if they
* don't make sense we'll parse them anyway, because maybe one day we'll add them for more partition
* types, too. Moreover, we allow configuring a "default" policy for all partition types for which no
* explicit policy is specified. It's useful if we can use policy flags in there and apply this
* default policy gracefully even to partition types where they don't really make too much sense
* on. Example: a default policy of "verity+encrypted" certainly makes sense, but for /home/
* partitions this gracefully degrades to "encrypted" (as we do not have a concept of verity for
* /home/), and so on. */
PARTITION_POLICY_VERITY = 1 << 0, /* must exist, activate with verity (only applies to root/usr partitions) */
PARTITION_POLICY_SIGNED = 1 << 1, /* must exist, activate with signed verity (only applies to root/usr partitions) */
PARTITION_POLICY_ENCRYPTED = 1 << 2, /* must exist, activate with LUKS encryption (applies to any data partition, but not to verity/signature partitions */
PARTITION_POLICY_UNPROTECTED = 1 << 3, /* must exist, activate without encryption/verity */
PARTITION_POLICY_UNUSED = 1 << 4, /* must exist, don't use */
PARTITION_POLICY_ABSENT = 1 << 5, /* must not exist */
PARTITION_POLICY_OPEN = PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|
PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT,
PARTITION_POLICY_IGNORE = PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT,
_PARTITION_POLICY_USE_MASK = PARTITION_POLICY_OPEN,
PARTITION_POLICY_READ_ONLY_OFF = 1 << 6, /* State of GPT partition flag "read-only" must be on */
PARTITION_POLICY_READ_ONLY_ON = 1 << 7,
_PARTITION_POLICY_READ_ONLY_MASK = PARTITION_POLICY_READ_ONLY_OFF|PARTITION_POLICY_READ_ONLY_ON,
PARTITION_POLICY_GROWFS_OFF = 1 << 8, /* State of GPT partition flag "growfs" must be on */
PARTITION_POLICY_GROWFS_ON = 1 << 9,
_PARTITION_POLICY_GROWFS_MASK = PARTITION_POLICY_GROWFS_OFF|PARTITION_POLICY_GROWFS_ON,
_PARTITION_POLICY_PFLAGS_MASK = _PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK,
_PARTITION_POLICY_MASK = _PARTITION_POLICY_USE_MASK|_PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK,
_PARTITION_POLICY_FLAGS_INVALID = -EINVAL,
_PARTITION_POLICY_FLAGS_ERRNO_MAX = -ERRNO_MAX, /* Ensure the whole errno range fits into this enum */
} PartitionPolicyFlags;
assert_cc((_PARTITION_POLICY_USE_MASK | _PARTITION_POLICY_PFLAGS_MASK) >= 0); /* ensure flags don't collide with errno range */
typedef struct PartitionPolicy {
PartitionDesignator designator;
PartitionPolicyFlags flags;
} PartitionPolicy;
struct ImagePolicy {
PartitionPolicyFlags default_flags; /* for any designator not listed in the list below */
size_t n_policies;
PartitionPolicy policies[]; /* sorted by designator, hence suitable for binary search */
};
/* Default policies for various usecases */
extern const ImagePolicy image_policy_allow;
extern const ImagePolicy image_policy_deny;
extern const ImagePolicy image_policy_ignore;
extern const ImagePolicy image_policy_sysext; /* No verity required */
extern const ImagePolicy image_policy_sysext_strict; /* Signed verity required */
extern const ImagePolicy image_policy_container;
extern const ImagePolicy image_policy_service;
extern const ImagePolicy image_policy_host;
PartitionPolicyFlags image_policy_get(const ImagePolicy *policy, PartitionDesignator designator);
PartitionPolicyFlags image_policy_get_exhaustively(const ImagePolicy *policy, PartitionDesignator designator);
/* We want that the NULL image policy means "everything" allowed, hence use these simple accessors to make
* NULL policies work reasonably */
static inline PartitionPolicyFlags image_policy_default(const ImagePolicy *policy) {
return policy ? policy->default_flags : PARTITION_POLICY_OPEN;
}
static inline size_t image_policy_n_entries(const ImagePolicy *policy) {
return policy ? policy->n_policies : 0;
}
PartitionPolicyFlags partition_policy_flags_from_string(const char *s);
int partition_policy_flags_to_string(PartitionPolicyFlags flags, bool simplify, char **ret);
int image_policy_from_string(const char *s, ImagePolicy **ret);
int image_policy_to_string(const ImagePolicy *policy, bool simplify, char **ret);
/* Recognizes three special policies by equivalence */
bool image_policy_equiv_ignore(const ImagePolicy *policy);
bool image_policy_equiv_allow(const ImagePolicy *policy);
bool image_policy_equiv_deny(const ImagePolicy *policy);
bool image_policy_equal(const ImagePolicy *a, const ImagePolicy *b); /* checks if defined the same way, i.e. has literally the same ruleset */
int image_policy_equivalent(const ImagePolicy *a, const ImagePolicy *b); /* checks if the outcome is the same, i.e. for all partitions results in the same decisions. */
static inline ImagePolicy* image_policy_free(ImagePolicy *p) {
return mfree(p);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(ImagePolicy*, image_policy_free);

View File

@ -81,6 +81,7 @@ shared_sources = files(
'id128-print.c',
'idn-util.c',
'ima-util.c',
'image-policy.c',
'import-util.c',
'in-addr-prefix-util.c',
'install-file.c',

View File

@ -805,6 +805,7 @@ static int mount_in_namespace(
bool read_only,
bool make_file_or_directory,
const MountOptions *options,
const ImagePolicy *image_policy,
bool is_image) {
_cleanup_close_pair_ int errno_pipe_fd[2] = PIPE_EBADF;
@ -892,7 +893,7 @@ static int mount_in_namespace(
mount_tmp_created = true;
if (is_image)
r = verity_dissect_and_mount(chased_src_fd, chased_src_path, mount_tmp, options, NULL, NULL, NULL, NULL);
r = verity_dissect_and_mount(chased_src_fd, chased_src_path, mount_tmp, options, image_policy, NULL, NULL, NULL, NULL);
else
r = mount_follow_verbose(LOG_DEBUG, FORMAT_PROC_FD_PATH(chased_src_fd), mount_tmp, NULL, MS_BIND, NULL);
if (r < 0)
@ -1042,7 +1043,7 @@ int bind_mount_in_namespace(
bool read_only,
bool make_file_or_directory) {
return mount_in_namespace(target, propagate_path, incoming_path, src, dest, read_only, make_file_or_directory, NULL, false);
return mount_in_namespace(target, propagate_path, incoming_path, src, dest, read_only, make_file_or_directory, /* options= */ NULL, /* image_policy= */ NULL, /* is_image= */ false);
}
int mount_image_in_namespace(
@ -1053,9 +1054,10 @@ int mount_image_in_namespace(
const char *dest,
bool read_only,
bool make_file_or_directory,
const MountOptions *options) {
const MountOptions *options,
const ImagePolicy *image_policy) {
return mount_in_namespace(target, propagate_path, incoming_path, src, dest, read_only, make_file_or_directory, options, true);
return mount_in_namespace(target, propagate_path, incoming_path, src, dest, read_only, make_file_or_directory, options, image_policy, /* is_image=*/ true);
}
int make_mount_point(const char *path) {

View File

@ -81,7 +81,7 @@ static inline char* umount_and_rmdir_and_free(char *p) {
DEFINE_TRIVIAL_CLEANUP_FUNC(char*, umount_and_rmdir_and_free);
int bind_mount_in_namespace(pid_t target, const char *propagate_path, const char *incoming_path, const char *src, const char *dest, bool read_only, bool make_file_or_directory);
int mount_image_in_namespace(pid_t target, const char *propagate_path, const char *incoming_path, const char *src, const char *dest, bool read_only, bool make_file_or_directory, const MountOptions *options);
int mount_image_in_namespace(pid_t target, const char *propagate_path, const char *incoming_path, const char *src, const char *dest, bool read_only, bool make_file_or_directory, const MountOptions *options, const ImagePolicy *image_policy);
int make_mount_point(const char *path);

View File

@ -45,12 +45,14 @@ static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
static PagerFlags arg_pager_flags = 0;
static bool arg_legend = true;
static bool arg_force = false;
static ImagePolicy *arg_image_policy = NULL;
/* Is set to IMAGE_CONFEXT when systemd is called with the confext functionality instead of the default */
static ImageClass arg_image_class = IMAGE_SYSEXT;
STATIC_DESTRUCTOR_REGISTER(arg_hierarchies, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
/* Helper struct for naming simplicity and reusability */
static const struct {
@ -441,6 +443,24 @@ static int strverscmp_improvedp(char *const* a, char *const* b) {
return strverscmp_improved(*a, *b);
}
static const ImagePolicy *pick_image_policy(const Image *img) {
assert(img);
assert(img->path);
/* Explicitly specified policy always wins */
if (arg_image_policy)
return arg_image_policy;
/* If located in /.extra/sysext/ in the initrd, then it was placed there by systemd-stub, and was
* picked up from an untrusted ESP. Thus, require a stricter policy by default for them. (For the
* other directories we assume the appropriate level of trust was already established already. */
if (in_initrd() && path_startswith(img->path, "/.extra/sysext/"))
return &image_policy_sysext_strict;
return &image_policy_sysext;
}
static int merge_subprocess(Hashmap *images, const char *workspace) {
_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, *buf = NULL;
@ -558,7 +578,8 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
r = dissect_loop_device_and_warn(
d,
&verity_settings,
NULL,
/* mount_options= */ NULL,
pick_image_policy(img),
flags,
&m);
if (r < 0)
@ -770,7 +791,7 @@ static int image_discover_and_read_metadata(Hashmap **ret_images) {
return log_error_errno(r, "Failed to discover images: %m");
HASHMAP_FOREACH(img, images) {
r = image_read_metadata(img);
r = image_read_metadata(img, &image_policy_sysext);
if (r < 0)
return log_error_errno(r, "Failed to read metadata for image %s: %m", img->name);
}
@ -922,6 +943,8 @@ static int verb_help(int argc, char **argv, void *userdata) {
" --json=pretty|short|off\n"
" Generate JSON output\n"
" --force Ignore version incompatibilities\n"
" --image-policy=POLICY\n"
" Specify disk image dissection policy\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
@ -942,16 +965,18 @@ static int parse_argv(int argc, char *argv[]) {
ARG_ROOT,
ARG_JSON,
ARG_FORCE,
ARG_IMAGE_POLICY,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "root", required_argument, NULL, ARG_ROOT },
{ "json", required_argument, NULL, ARG_JSON },
{ "force", no_argument, NULL, ARG_FORCE },
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "root", required_argument, NULL, ARG_ROOT },
{ "json", required_argument, NULL, ARG_JSON },
{ "force", no_argument, NULL, ARG_FORCE },
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{}
};
@ -995,6 +1020,17 @@ static int parse_argv(int argc, char *argv[]) {
arg_force = true;
break;
case ARG_IMAGE_POLICY: {
_cleanup_(image_policy_freep) ImagePolicy *p = NULL;
r = image_policy_from_string(optarg, &p);
if (r < 0)
return log_error_errno(r, "Failed to parse image policy: %s", optarg);
image_policy_free(arg_image_policy);
arg_image_policy = TAKE_PTR(p);
break;
}
case '?':
return -EINVAL;

View File

@ -121,6 +121,7 @@ bool arg_read_only = false;
bool arg_mkdir = false;
bool arg_marked = false;
const char *arg_drop_in = NULL;
ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_types, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_states, strv_freep);
@ -135,6 +136,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_host, unsetp);
STATIC_DESTRUCTOR_REGISTER(arg_boot_loader_entry, unsetp);
STATIC_DESTRUCTOR_REGISTER(arg_clean_what, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_drop_in, unsetp);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
static int systemctl_help(void) {
_cleanup_free_ char *link = NULL;
@ -305,7 +307,9 @@ static int systemctl_help(void) {
" --root=PATH Edit/enable/disable/mask unit files in the specified\n"
" root directory\n"
" --image=PATH Edit/enable/disable/mask unit files in the specified\n"
" image\n"
" disk image\n"
" --image-policy=POLICY\n"
" Specify disk image dissection policy\n"
" -n --lines=INTEGER Number of journal entries to show\n"
" -o --output=STRING Change journal output mode (short, short-precise,\n"
" short-iso, short-iso-precise, short-full,\n"
@ -450,6 +454,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
ARG_NO_WARN,
ARG_DROP_IN,
ARG_WHEN,
ARG_IMAGE_POLICY,
};
static const struct option options[] = {
@ -515,6 +520,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
{ "marked", no_argument, NULL, ARG_MARKED },
{ "drop-in", required_argument, NULL, ARG_DROP_IN },
{ "when", required_argument, NULL, ARG_WHEN },
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{}
};
@ -1003,6 +1009,18 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
break;
case ARG_IMAGE_POLICY: {
_cleanup_(image_policy_freep) ImagePolicy *p = NULL;
r = image_policy_from_string(optarg, &p);
if (r < 0)
return log_error_errno(r, "Failed to parse image policy: %s", optarg);
image_policy_free(arg_image_policy);
arg_image_policy = TAKE_PTR(p);
break;
}
case '.':
/* Output an error mimicking getopt, and print a hint afterwards */
log_error("%s: invalid option -- '.'", program_invocation_name);
@ -1248,6 +1266,7 @@ static int run(int argc, char *argv[]) {
r = mount_image_privately_interactively(
arg_image,
arg_image_policy,
DISSECT_IMAGE_GENERIC_ROOT |
DISSECT_IMAGE_REQUIRE_ROOT |
DISSECT_IMAGE_RELAX_VAR_CHECK |

View File

@ -5,6 +5,7 @@
#include "bus-print-properties.h"
#include "bus-util.h"
#include "image-policy.h"
#include "install.h"
#include "output-mode.h"
#include "pager.h"
@ -100,6 +101,7 @@ extern bool arg_read_only;
extern bool arg_mkdir;
extern bool arg_marked;
extern const char *arg_drop_in;
extern ImagePolicy *arg_image_policy;
static inline const char* arg_job_mode(void) {
return _arg_job_mode ?: "replace";

View File

@ -46,11 +46,13 @@ static char *arg_image = NULL;
static bool arg_reboot = false;
static char *arg_component = NULL;
static int arg_verify = -1;
static ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_component, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
typedef struct Context {
Transfer **transfers;
@ -872,6 +874,7 @@ static int process_image(
r = mount_image_privately_interactively(
arg_image,
arg_image_policy,
(ro ? DISSECT_IMAGE_READ_ONLY : 0) |
DISSECT_IMAGE_FSCK |
DISSECT_IMAGE_MKDIR |
@ -1022,7 +1025,7 @@ static int verb_pending_or_reboot(int argc, char **argv, void *userdata) {
if (arg_image || arg_root)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"The --root=/--image switches may not be combined with the '%s' operation.", argv[0]);
"The --root=/--image= switches may not be combined with the '%s' operation.", argv[0]);
r = context_make_offline(&context, NULL);
if (r < 0)
@ -1205,8 +1208,10 @@ static int verb_help(int argc, char **argv, void *userdata) {
"\n%3$sOptions:%4$s\n"
" -C --component=NAME Select component to update\n"
" --definitions=DIR Find transfer definitions in specified directory\n"
" --root=PATH Operate relative to root path\n"
" --image=PATH Operate relative to image file\n"
" --root=PATH Operate on an alternate filesystem root\n"
" --image=PATH Operate on disk image as filesystem root\n"
" --image-policy=POLICY\n"
" Specify disk image dissection policy\n"
" -m --instances-max=INT How many instances to maintain\n"
" --sync=BOOL Controls whether to sync data to disk\n"
" --verify=BOOL Force signature verification on or off\n"
@ -1238,6 +1243,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_IMAGE,
ARG_REBOOT,
ARG_VERIFY,
ARG_IMAGE_POLICY,
};
static const struct option options[] = {
@ -1254,6 +1260,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "reboot", no_argument, NULL, ARG_REBOOT },
{ "component", required_argument, NULL, 'C' },
{ "verify", required_argument, NULL, ARG_VERIFY },
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{}
};
@ -1351,6 +1358,17 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
case ARG_IMAGE_POLICY: {
_cleanup_(image_policy_freep) ImagePolicy *p = NULL;
r = image_policy_from_string(optarg, &p);
if (r < 0)
return log_error_errno(r, "Failed to parse image policy: %s", optarg);
image_policy_free(arg_image_policy);
arg_image_policy = TAKE_PTR(p);
break;
}
case '?':
return -EINVAL;

View File

@ -99,6 +99,7 @@ static const char *arg_replace = NULL;
static bool arg_dry_run = false;
static bool arg_inline = false;
static PagerFlags arg_pager_flags = 0;
static ImagePolicy *arg_image_policy = NULL;
static OrderedHashmap *users = NULL, *groups = NULL;
static OrderedHashmap *todo_uids = NULL, *todo_gids = NULL;
@ -128,6 +129,7 @@ STATIC_DESTRUCTOR_REGISTER(database_groups, set_free_freep);
STATIC_DESTRUCTOR_REGISTER(uid_range, uid_range_freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
static int errno_is_not_exists(int code) {
/* See getpwnam(3) and getgrnam(3): those codes and others can be returned if the user or group are
@ -1964,6 +1966,7 @@ static int help(void) {
" --cat-config Show configuration files\n"
" --root=PATH Operate on an alternate filesystem root\n"
" --image=PATH Operate on disk image as filesystem root\n"
" --image-policy=POLICY Specify disk image dissection policy\n"
" --replace=PATH Treat arguments as replacement for PATH\n"
" --dry-run Just print what would be done\n"
" --inline Treat arguments as configuration lines\n"
@ -1986,18 +1989,20 @@ static int parse_argv(int argc, char *argv[]) {
ARG_DRY_RUN,
ARG_INLINE,
ARG_NO_PAGER,
ARG_IMAGE_POLICY,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "cat-config", no_argument, NULL, ARG_CAT_CONFIG },
{ "root", required_argument, NULL, ARG_ROOT },
{ "image", required_argument, NULL, ARG_IMAGE },
{ "replace", required_argument, NULL, ARG_REPLACE },
{ "dry-run", no_argument, NULL, ARG_DRY_RUN },
{ "inline", no_argument, NULL, ARG_INLINE },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "cat-config", no_argument, NULL, ARG_CAT_CONFIG },
{ "root", required_argument, NULL, ARG_ROOT },
{ "image", required_argument, NULL, ARG_IMAGE },
{ "replace", required_argument, NULL, ARG_REPLACE },
{ "dry-run", no_argument, NULL, ARG_DRY_RUN },
{ "inline", no_argument, NULL, ARG_INLINE },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{}
};
@ -2058,6 +2063,17 @@ static int parse_argv(int argc, char *argv[]) {
arg_pager_flags |= PAGER_DISABLE;
break;
case ARG_IMAGE_POLICY: {
_cleanup_(image_policy_freep) ImagePolicy *p = NULL;
r = image_policy_from_string(optarg, &p);
if (r < 0)
return log_error_errno(r, "Failed to parse image policy: %s", optarg);
image_policy_free(arg_image_policy);
arg_image_policy = TAKE_PTR(p);
break;
}
case '?':
return -EINVAL;
@ -2173,6 +2189,7 @@ static int run(int argc, char *argv[]) {
r = mount_image_privately_interactively(
arg_image,
arg_image_policy,
DISSECT_IMAGE_GENERIC_ROOT |
DISSECT_IMAGE_REQUIRE_ROOT |
DISSECT_IMAGE_VALIDATE_OS |

View File

@ -95,6 +95,7 @@ simple_tests += files(
'test-hostname-setup.c',
'test-hostname-util.c',
'test-id128.c',
'test-image-policy.c',
'test-import-util.c',
'test-in-addr-prefix-util.c',
'test-in-addr-util.c',

View File

@ -0,0 +1,122 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "image-policy.h"
#include "pretty-print.h"
#include "string-util.h"
#include "tests.h"
#include "pager.h"
static void test_policy(const ImagePolicy *p, const char *name) {
_cleanup_free_ char *as_string = NULL, *as_string_simplified = NULL;
_cleanup_free_ ImagePolicy *parsed = NULL;
assert_se(image_policy_to_string(p, /* simplified= */ false, &as_string) >= 0);
assert_se(image_policy_to_string(p, /* simplified= */ true, &as_string_simplified) >= 0);
printf("%s%s", ansi_underline(), name);
if (!streq(as_string_simplified, name)) {
printf(" → %s", as_string_simplified);
if (!streq(as_string, as_string_simplified))
printf(" (aka %s)", as_string);
}
printf("%s\n", ansi_normal());
assert_se(image_policy_from_string(as_string, &parsed) >= 0);
assert_se(image_policy_equal(p, parsed));
parsed = image_policy_free(parsed);
assert_se(image_policy_from_string(as_string_simplified, &parsed) >= 0);
assert_se(image_policy_equivalent(p, parsed));
parsed = image_policy_free(parsed);
for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
_cleanup_free_ char *k = NULL;
PartitionPolicyFlags f;
f = image_policy_get(p, d);
if (f < 0) {
f = image_policy_get_exhaustively(p, d);
assert_se(f >= 0);
assert_se(partition_policy_flags_to_string(f, /* simplified= */ true, &k) >= 0);
printf("%s\t%s → n/a (exhaustively: %s)%s\n", ansi_grey(), partition_designator_to_string(d), k, ansi_normal());
} else {
assert_se(partition_policy_flags_to_string(f, /* simplified= */ true, &k) >= 0);
printf("\t%s → %s\n", partition_designator_to_string(d), k);
}
}
_cleanup_free_ char *w = NULL;
assert_se(partition_policy_flags_to_string(image_policy_default(p), /* simplified= */ true, &w) >= 0);
printf("\tdefault → %s\n", w);
}
static void test_policy_string(const char *t) {
_cleanup_free_ ImagePolicy *parsed = NULL;
assert_se(image_policy_from_string(t, &parsed) >= 0);
test_policy(parsed, t);
}
static void test_policy_equiv(const char *s, bool (*func)(const ImagePolicy *p)) {
_cleanup_(image_policy_freep) ImagePolicy *p = NULL;
assert_se(image_policy_from_string(s, &p) >= 0);
assert_se(func(p));
assert_se(func == image_policy_equiv_ignore || !image_policy_equiv_ignore(p));
assert_se(func == image_policy_equiv_allow || !image_policy_equiv_allow(p));
assert_se(func == image_policy_equiv_deny || !image_policy_equiv_deny(p));
}
TEST_RET(test_image_policy_to_string) {
test_policy(&image_policy_allow, "*");
test_policy(&image_policy_ignore, "-");
test_policy(&image_policy_deny, "~");
test_policy(&image_policy_sysext, "sysext");
test_policy(&image_policy_sysext_strict, "sysext-strict");
test_policy(&image_policy_container, "container");
test_policy(&image_policy_host, "host");
test_policy(&image_policy_service, "service");
test_policy(NULL, "null");
test_policy_string("");
test_policy_string("-");
test_policy_string("*");
test_policy_string("~");
test_policy_string("swap=open");
test_policy_string("swap=open:root=signed");
test_policy_string("swap=open:root=signed+read-only-on+growfs-off:=absent");
test_policy_string("=-");
test_policy_string("=");
test_policy_equiv("", image_policy_equiv_ignore);
test_policy_equiv("-", image_policy_equiv_ignore);
test_policy_equiv("*", image_policy_equiv_allow);
test_policy_equiv("~", image_policy_equiv_deny);
test_policy_equiv("=absent", image_policy_equiv_deny);
test_policy_equiv("=open", image_policy_equiv_allow);
test_policy_equiv("=verity+signed+encrypted+unprotected+unused+absent", image_policy_equiv_allow);
test_policy_equiv("=signed+verity+encrypted+unused+unprotected+absent", image_policy_equiv_allow);
test_policy_equiv("=ignore", image_policy_equiv_ignore);
test_policy_equiv("=absent+unused", image_policy_equiv_ignore);
test_policy_equiv("=unused+absent", image_policy_equiv_ignore);
test_policy_equiv("root=ignore:=ignore", image_policy_equiv_ignore);
assert_se(image_policy_from_string("pfft", NULL) == -EINVAL);
assert_se(image_policy_from_string("öäüß", NULL) == -EINVAL);
assert_se(image_policy_from_string(":", NULL) == -EINVAL);
assert_se(image_policy_from_string("a=", NULL) == -EBADSLT);
assert_se(image_policy_from_string("=a", NULL) == -EBADRQC);
assert_se(image_policy_from_string("==", NULL) == -EBADRQC);
assert_se(image_policy_from_string("root=verity:root=encrypted", NULL) == -ENOTUNIQ);
assert_se(image_policy_from_string("root=grbl", NULL) == -EBADRQC);
assert_se(image_policy_from_string("wowza=grbl", NULL) == -EBADSLT);
return 0;
}
DEFINE_TEST_MAIN(LOG_INFO);

View File

@ -82,7 +82,7 @@ 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, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected);
r = dissect_loop_device(loop, NULL, NULL, 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);
@ -213,7 +213,7 @@ static int run(int argc, char *argv[]) {
sfdisk = NULL;
#if HAVE_BLKID
assert_se(dissect_image_file(p, NULL, NULL, 0, &dissected) >= 0);
assert_se(dissect_image_file(p, NULL, NULL, NULL, 0, &dissected) >= 0);
verify_dissected_image(dissected);
dissected = dissected_image_unref(dissected);
#endif
@ -231,7 +231,7 @@ 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, DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected) >= 0);
assert_se(dissect_loop_device(loop, NULL, NULL, NULL, DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected) >= 0);
verify_dissected_image(dissected);
FOREACH_STRING(fs, "vfat", "ext4") {
@ -267,12 +267,12 @@ 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, 0, &dissected) >= 0);
assert_se(dissect_loop_device(loop, NULL, NULL, NULL, 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, DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected) >= 0);
assert_se(dissect_loop_device(loop, NULL, NULL, 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);

View File

@ -176,6 +176,7 @@ TEST(protect_kernel_logs) {
assert_se(fd > 0);
r = setup_namespace(NULL,
NULL,
NULL,
NULL,
&ns_info,
@ -193,6 +194,7 @@ TEST(protect_kernel_logs) {
NULL,
NULL,
NULL,
NULL,
0,
NULL,
0,
@ -208,6 +210,7 @@ TEST(protect_kernel_logs) {
NULL,
NULL,
NULL,
NULL,
NULL);
assert_se(r == 0);

View File

@ -77,6 +77,7 @@ int main(int argc, char *argv[]) {
log_info("Not chrooted");
r = setup_namespace(root_directory,
NULL,
NULL,
NULL,
&ns_info,
@ -91,6 +92,7 @@ int main(int argc, char *argv[]) {
&(TemporaryFileSystem) { .path = (char*) "/var", .options = (char*) "ro" }, 1,
NULL,
0,
NULL,
tmp_dir,
var_tmp_dir,
NULL,
@ -110,6 +112,7 @@ int main(int argc, char *argv[]) {
NULL,
NULL,
NULL,
NULL,
NULL);
if (r < 0) {
log_error_errno(r, "Failed to set up namespace: %m");

View File

@ -206,6 +206,7 @@ static char **arg_exclude_prefixes = NULL;
static char *arg_root = NULL;
static char *arg_image = NULL;
static char *arg_replace = NULL;
static ImagePolicy *arg_image_policy = NULL;
#define MAX_DEPTH 256
@ -219,6 +220,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_include_prefixes, freep);
STATIC_DESTRUCTOR_REGISTER(arg_exclude_prefixes, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
static const char *const creation_mode_verb_table[_CREATION_MODE_MAX] = {
[CREATION_NORMAL] = "Created",
@ -3699,6 +3701,7 @@ static int help(void) {
" -E Ignore rules prefixed with /dev, /proc, /run, /sys\n"
" --root=PATH Operate on an alternate filesystem root\n"
" --image=PATH Operate on disk image as filesystem root\n"
" --image-policy=POLICY Specify disk image dissection policy\n"
" --replace=PATH Treat arguments as replacement for PATH\n"
" --no-pager Do not pipe output into a pager\n"
"\nSee the %s for details.\n",
@ -3726,6 +3729,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_IMAGE,
ARG_REPLACE,
ARG_NO_PAGER,
ARG_IMAGE_POLICY,
};
static const struct option options[] = {
@ -3743,6 +3747,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "image", required_argument, NULL, ARG_IMAGE },
{ "replace", required_argument, NULL, ARG_REPLACE },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{}
};
@ -3833,6 +3838,17 @@ static int parse_argv(int argc, char *argv[]) {
arg_pager_flags |= PAGER_DISABLE;
break;
case ARG_IMAGE_POLICY: {
_cleanup_(image_policy_freep) ImagePolicy *p = NULL;
r = image_policy_from_string(optarg, &p);
if (r < 0)
return log_error_errno(r, "Failed to parse image policy: %s", optarg);
image_policy_free(arg_image_policy);
arg_image_policy = TAKE_PTR(p);
break;
}
case '?':
return -EINVAL;
@ -4153,6 +4169,7 @@ static int run(int argc, char *argv[]) {
r = mount_image_privately_interactively(
arg_image,
arg_image_policy,
DISSECT_IMAGE_GENERIC_ROOT |
DISSECT_IMAGE_REQUIRE_ROOT |
DISSECT_IMAGE_VALIDATE_OS |

View File

@ -231,6 +231,33 @@ fi
systemd-dissect --root-hash "${roothash}" "${image}.gpt" | grep -q -F "MARKER=1"
systemd-dissect --root-hash "${roothash}" "${image}.gpt" | grep -q -F -f <(sed 's/"//g' "$os_release")
# Test image policies
systemd-dissect --validate "${image}.gpt"
systemd-dissect --validate "${image}.gpt" --image-policy='*'
(! systemd-dissect --validate "${image}.gpt" --image-policy='~')
(! systemd-dissect --validate "${image}.gpt" --image-policy='-')
(! systemd-dissect --validate "${image}.gpt" --image-policy=root=absent)
(! systemd-dissect --validate "${image}.gpt" --image-policy=swap=unprotected+encrypted+verity)
systemd-dissect --validate "${image}.gpt" --image-policy=root=unprotected
systemd-dissect --validate "${image}.gpt" --image-policy=root=verity
systemd-dissect --validate "${image}.gpt" --image-policy=root=verity:root-verity-sig=unused+absent
systemd-dissect --validate "${image}.gpt" --image-policy=root=verity:swap=absent
systemd-dissect --validate "${image}.gpt" --image-policy=root=verity:swap=absent+unprotected
(! systemd-dissect --validate "${image}.gpt" --image-policy=root=verity:root-verity=unused+absent)
systemd-dissect --validate "${image}.gpt" --image-policy=root=signed
(! systemd-dissect --validate "${image}.gpt" --image-policy=root=signed:root-verity-sig=unused+absent)
(! systemd-dissect --validate "${image}.gpt" --image-policy=root=signed:root-verity=unused+absent)
# Test RootImagePolicy= unit file setting
systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1"
systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='*' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1"
(! systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='~' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1")
(! systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='-' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1")
(! systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='root=absent' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1")
systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='root=verity' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1"
systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='root=signed' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1"
(! systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='root=encrypted' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1")
systemd-dissect --root-hash "${roothash}" --mount "${image}.gpt" "${image_dir}/mount"
grep -q -F -f "$os_release" "${image_dir}/mount/usr/lib/os-release"
grep -q -F -f "$os_release" "${image_dir}/mount/etc/os-release"

View File

@ -818,6 +818,18 @@ name=$(echo "$output" | awk '{ print $4 }')
check deny yes /run/systemd/transient/"$name"
check deny no "$name"
# Let's also test the "image-policy" verb
systemd-analyze image-policy '*' 2>&1 | grep -q -F "Long form: =verity+signed+encrypted+unprotected+unused+absent"
systemd-analyze image-policy '-' 2>&1 | grep -q -F "Long form: =unused+absent"
systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -F "Long form: usr=verity:home=encrypted:=unused+absent"
systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -e '^home \+encrypted \+'
systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -e '^usr \+verity \+'
systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -e '^root \+ignore \+'
systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -e '^usr-verity \+unprotected \+'
(! systemd-analyze image-policy 'doedel' )
systemd-analyze log-level info
echo OK >/testok

View File

@ -15,6 +15,7 @@ ConditionCapability=CAP_SYS_ADMIN
ConditionDirectoryNotEmpty=|/etc/extensions
ConditionDirectoryNotEmpty=|/run/extensions
ConditionDirectoryNotEmpty=|/var/lib/extensions
ConditionDirectoryNotEmpty=|/.extra/sysext
DefaultDependencies=no
After=local-fs.target