summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2023-04-12 13:46:08 +0200
committerGitHub <noreply@github.com>2023-04-12 13:46:08 +0200
commit3af48a86d99b3117a44bc22258ab4d34d0ba7655 (patch)
tree594af3dadad0f5a0febfb73137689eea7503fe83
parent068943453f94c7e44a7b09972ae0cde09080aa95 (diff)
parent3bcf564530bfa7e001354dd94e653905523c418d (diff)
downloadsystemd-3af48a86d99b3117a44bc22258ab4d34d0ba7655.tar.gz
Merge pull request #25608 from poettering/dissect-moar
dissect: add dissection policies
-rw-r--r--TODO29
-rw-r--r--man/bootctl.xml2
-rw-r--r--man/coredumpctl.xml2
-rw-r--r--man/journalctl.xml2
-rw-r--r--man/kernel-command-line.xml14
-rw-r--r--man/org.freedesktop.systemd1.xml72
-rw-r--r--man/rules/meson.build1
-rw-r--r--man/standard-options.xml11
-rw-r--r--man/systemctl.xml2
-rw-r--r--man/systemd-analyze.xml41
-rw-r--r--man/systemd-dissect.xml15
-rw-r--r--man/systemd-gpt-auto-generator.xml10
-rw-r--r--man/systemd-machine-id-setup.xml2
-rw-r--r--man/systemd-nspawn.xml11
-rw-r--r--man/systemd-repart.xml2
-rw-r--r--man/systemd-sysext.xml23
-rw-r--r--man/systemd-sysupdate.xml2
-rw-r--r--man/systemd-sysusers.xml2
-rw-r--r--man/systemd-tmpfiles.xml2
-rw-r--r--man/systemd.exec.xml24
-rw-r--r--man/systemd.image-policy.xml191
-rw-r--r--src/analyze/analyze-image-policy.c154
-rw-r--r--src/analyze/analyze-image-policy.h3
-rw-r--r--src/analyze/analyze.c20
-rw-r--r--src/analyze/analyze.h1
-rw-r--r--src/analyze/meson.build1
-rw-r--r--src/boot/bootctl.c19
-rw-r--r--src/boot/bootctl.h2
-rw-r--r--src/core/dbus-execute.c61
-rw-r--r--src/core/dbus-service.c24
-rw-r--r--src/core/execute.c71
-rw-r--r--src/core/execute.h2
-rw-r--r--src/core/load-fragment-gperf.gperf.in3
-rw-r--r--src/core/load-fragment.c39
-rw-r--r--src/core/load-fragment.h1
-rw-r--r--src/core/namespace.c36
-rw-r--r--src/core/namespace.h3
-rw-r--r--src/coredump/coredumpctl.c54
-rw-r--r--src/dissect/dissect.c71
-rw-r--r--src/firstboot/firstboot.c19
-rw-r--r--src/gpt-auto-generator/gpt-auto-generator.c22
-rw-r--r--src/journal/journalctl.c20
-rw-r--r--src/machine-id-setup/machine-id-setup-main.c41
-rw-r--r--src/machine/image-dbus.c8
-rw-r--r--src/nspawn/nspawn.c20
-rw-r--r--src/partition/repart.c20
-rw-r--r--src/portable/portable.c30
-rw-r--r--src/portable/portable.h5
-rw-r--r--src/portable/portabled-image-bus.c5
-rw-r--r--src/shared/bus-unit-util.c5
-rw-r--r--src/shared/discover-image.c34
-rw-r--r--src/shared/discover-image.h3
-rw-r--r--src/shared/dissect-image.c428
-rw-r--r--src/shared/dissect-image.h22
-rw-r--r--src/shared/image-policy.c689
-rw-r--r--src/shared/image-policy.h97
-rw-r--r--src/shared/meson.build1
-rw-r--r--src/shared/mount-util.c10
-rw-r--r--src/shared/mount-util.h2
-rw-r--r--src/sysext/sysext.c54
-rw-r--r--src/systemctl/systemctl.c21
-rw-r--r--src/systemctl/systemctl.h2
-rw-r--r--src/sysupdate/sysupdate.c24
-rw-r--r--src/sysusers/sysusers.c35
-rw-r--r--src/test/meson.build1
-rw-r--r--src/test/test-image-policy.c122
-rw-r--r--src/test/test-loop-block.c10
-rw-r--r--src/test/test-namespace.c3
-rw-r--r--src/test/test-ns.c3
-rw-r--r--src/tmpfiles/tmpfiles.c17
-rwxr-xr-xtest/units/testsuite-50.sh27
-rwxr-xr-xtest/units/testsuite-65.sh12
-rw-r--r--units/systemd-sysext.service1
73 files changed, 2569 insertions, 269 deletions
diff --git a/TODO b/TODO
index 73edc66d78..29b1ac6601 100644
--- a/TODO
+++ b/TODO
@@ -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.
diff --git a/man/bootctl.xml b/man/bootctl.xml
index a6f1fc1c4c..5f98486343 100644
--- a/man/bootctl.xml
+++ b/man/bootctl.xml
@@ -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
diff --git a/man/coredumpctl.xml b/man/coredumpctl.xml
index 79632eb2d4..0f4a2e83e6 100644
--- a/man/coredumpctl.xml
+++ b/man/coredumpctl.xml
@@ -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>
diff --git a/man/journalctl.xml b/man/journalctl.xml
index ae86c50d62..aa124dd98f 100644
--- a/man/journalctl.xml
+++ b/man/journalctl.xml
@@ -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>
diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml
index 6f026318d8..27ef72da36 100644
--- a/man/kernel-command-line.xml
+++ b/man/kernel-command-line.xml
@@ -396,13 +396,23 @@
<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>
<listitem>
diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml
index 490c83bb96..f39893f647 100644
--- a/man/org.freedesktop.systemd1.xml
+++ b/man/org.freedesktop.systemd1.xml
@@ -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"/>
diff --git a/man/rules/meson.build b/man/rules/meson.build
index b6c88db390..cdf98eaaf0 100644
--- a/man/rules/meson.build
+++ b/man/rules/meson.build
@@ -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', [], ''],
diff --git a/man/standard-options.xml b/man/standard-options.xml
index d42f3296ca..71c84958ab 100644
--- a/man/standard-options.xml
+++ b/man/standard-options.xml
@@ -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>
diff --git a/man/systemctl.xml b/man/systemctl.xml
index f930034cb1..1a881d1049 100644
--- a/man/systemctl.xml
+++ b/man/systemctl.xml
@@ -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>
diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml
index 9fd28e6f45..7176e3c046 100644
--- a/man/systemd-analyze.xml
+++ b/man/systemd-analyze.xml
@@ -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>
diff --git a/man/systemd-dissect.xml b/man/systemd-dissect.xml
index eac50bffde..06ee0717f8 100644
--- a/man/systemd-dissect.xml
+++ b/man/systemd-dissect.xml
@@ -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" />
diff --git a/man/systemd-gpt-auto-generator.xml b/man/systemd-gpt-auto-generator.xml
index bd542cb7f7..1730039b62 100644
--- a/man/systemd-gpt-auto-generator.xml
+++ b/man/systemd-gpt-auto-generator.xml
@@ -250,6 +250,16 @@
</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>
<term><varname>rootflags=</varname></term>
diff --git a/man/systemd-machine-id-setup.xml b/man/systemd-machine-id-setup.xml
index f1695b6ddb..c07a853418 100644
--- a/man/systemd-machine-id-setup.xml
+++ b/man/systemd-machine-id-setup.xml
@@ -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
diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml
index e2c751692f..39a6febb3c 100644
--- a/man/systemd-nspawn.xml
+++ b/man/systemd-nspawn.xml
@@ -311,6 +311,17 @@
</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>
<listitem><para>Takes the path to an OCI runtime bundle to invoke, as specified in the <ulink
diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml
index 9033ef76d6..98ca1c431a 100644
--- a/man/systemd-repart.xml
+++ b/man/systemd-repart.xml
@@ -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>
diff --git a/man/systemd-sysext.xml b/man/systemd-sysext.xml
index f3a12e0a1c..a257fa73bc 100644
--- a/man/systemd-sysext.xml
+++ b/man/systemd-sysext.xml
@@ -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>
diff --git a/man/systemd-sysupdate.xml b/man/systemd-sysupdate.xml
index 77c1635b9d..409281c19f 100644
--- a/man/systemd-sysupdate.xml
+++ b/man/systemd-sysupdate.xml
@@ -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>
diff --git a/man/systemd-sysusers.xml b/man/systemd-sysusers.xml
index aba275024f..f7ee5e79d9 100644
--- a/man/systemd-sysusers.xml
+++ b/man/systemd-sysusers.xml
@@ -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
diff --git a/man/systemd-tmpfiles.xml b/man/systemd-tmpfiles.xml
index 49eda985b4..5612b4803d 100644
--- a/man/systemd-tmpfiles.xml
+++ b/man/systemd-tmpfiles.xml
@@ -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
diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
index 3a4af9cabe..795e26e792 100644
--- a/man/systemd.exec.xml
+++ b/man/systemd.exec.xml
@@ -261,6 +261,30 @@
</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>
<listitem><para>Takes a boolean argument. If on, a private mount namespace for the unit's processes is created
diff --git a/man/systemd.image-policy.xml b/man/systemd.image-policy.xml
new file mode 100644
index 0000000000..4f7b0986b6
--- /dev/null
+++ b/man/systemd.image-policy.xml
@@ -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>
diff --git a/src/analyze/analyze-image-policy.c b/src/analyze/analyze-image-policy.c
new file mode 100644
index 0000000000..026216629c
--- /dev/null
+++ b/src/analyze/analyze-image-policy.c
@@ -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;
+}
diff --git a/src/analyze/analyze-image-policy.h b/src/analyze/analyze-image-policy.h
new file mode 100644
index 0000000000..fa08447822
--- /dev/null
+++ b/src/analyze/analyze-image-policy.h
@@ -0,0 +1,3 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+int verb_image_policy(int argc, char *argv[], void *userdata);
diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c
index 0246da4b45..ddc71b98b0 100644
--- a/src/analyze/analyze.c
+++ b/src/analyze/analyze.c
@@ -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,
diff --git a/src/analyze/analyze.h b/src/analyze/analyze.h
index 2f623e3201..84575cd9a9 100644
--- a/src/analyze/analyze.h
+++ b/src/analyze/analyze.h
@@ -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);
diff --git a/src/analyze/meson.build b/src/analyze/meson.build
index 695089a0be..c50c35f09f 100644
--- a/src/analyze/meson.build
+++ b/src/analyze/meson.build
@@ -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',
diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c
index 82c7e498ba..b9d034d550 100644
--- a/src/boot/bootctl.c
+++ b/src/boot/bootctl.c
@@ -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,
diff --git a/src/boot/bootctl.h b/src/boot/bootctl.h
index c87d43694f..dd98b959c2 100644
--- a/src/boot/bootctl.h
+++ b/src/boot/bootctl.h
@@ -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 */
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
index d5ef796e52..d77842bdab 100644
--- a/src/core/dbus-execute.c
+++ b/src/core/dbus-execute.c
@@ -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;
diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c
index 0f6e315233..6041a37e56 100644
--- a/src/core/dbus-service.c
+++ b/src/core/dbus-service.c
@@ -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);
diff --git a/src/core/execute.c b/src/core/execute.c
index 93024b1ce4..8b09794089 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -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) {
diff --git a/src/core/execute.h b/src/core/execute.h
index 254a1ee2d1..123fc1ec60 100644
--- a/src/core/execute.h
+++ b/src/core/execute.h
@@ -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) {
diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in
index 50ff57a9f8..f35c743655 100644
--- a/src/core/load-fragment-gperf.gperf.in
+++ b/src/core/load-fragment-gperf.gperf.in
@@ -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)
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index 533c09f72e..99d40b7490 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -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,
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
index 91dc917458..ab682ee23e 100644
--- a/src/core/load-fragment.h
+++ b/src/core/load-fragment.h
@@ -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);
diff --git a/src/core/namespace.c b/src/core/namespace.c
index a71beeb18b..1d19685d2e 100644
--- a/src/core/namespace.c
+++ b/src/core/namespace.c
@@ -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;
diff --git a/src/core/namespace.h b/src/core/namespace.h
index 1cd4fdd921..39b510f41d 100644
--- a/src/core/namespace.h
+++ b/src/core/namespace.h
@@ -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,
diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c
index 60da536d4e..076b35f098 100644
--- a/src/coredump/coredumpctl.c
+++ b/src/coredump/coredumpctl.c
@@ -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 |
diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c
index ff00c4f567..b53c2cc357 100644
--- a/src/dissect/dissect.c
+++ b/src/dissect/dissect.c
@@ -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)
diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c
index 4eb4847ad0..7cd13a51e9 100644
--- a/src/firstboot/firstboot.c
+++ b/src/firstboot/firstboot.c
@@ -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 |
diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c
index 9ccd78af65..09c63a31b2 100644
--- a/src/gpt-auto-generator/gpt-auto-generator.c
+++ b/src/gpt-auto-generator/gpt-auto-generator.c
@@ -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;
}
diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c
index 97e9c1aafc..abacbb0306 100644
--- a/src/journal/journalctl.c
+++ b/src/journal/journalctl.c
@@ -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 |
diff --git a/src/machine-id-setup/machine-id-setup-main.c b/src/machine-id-setup/machine-id-setup-main.c
index 223164ea1b..c5b22d5d76 100644
--- a/src/machine-id-setup/machine-id-setup-main.c
+++ b/src/machine-id-setup/machine-id-setup-main.c
@@ -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 |
diff --git a/src/machine/image-dbus.c b/src/machine/image-dbus.c
index bf65eecfdd..336b42b7e5 100644
--- a/src/machine/image-dbus.c
+++ b/src/machine/image-dbus.c
@@ -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");
}
diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c
index ce95b10b1c..3882676216 100644
--- a/src/nspawn/nspawn.c
+++ b/src/nspawn/nspawn.c
@@ -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) {
diff --git a/src/partition/repart.c b/src/partition/repart.c
index 71c6956956..725cad1f4a 100644
--- a/src/partition/repart.c
+++ b/src/partition/repart.c
@@ -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 |
diff --git a/src/portable/portable.c b/src/portable/portable.c
index f3fc06f6fd..e5f2f38a21 100644
--- a/src/portable/portable.c
+++ b/src/portable/portable.c
@@ -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,
diff --git a/src/portable/portable.h b/src/portable/portable.h
index 1a33f30944..c61d65fed3 100644
--- a/src/portable/portable.h
+++ b/src/portable/portable.h
@@ -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);
diff --git a/src/portable/portabled-image-bus.c b/src/portable/portabled-image-bus.c
index 3c5833c653..5e46d86b08 100644
--- a/src/portable/portabled-image-bus.c
+++ b/src/portable/portabled-image-bus.c
@@ -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,
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index 3307691f28..1c991ae54f 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -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",
diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c
index d0b3245a27..ac6a8033dd 100644
--- a/src/shared/discover-image.c
+++ b/src/shared/discover-image.c
@@ -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;
diff --git a/src/shared/discover-image.h b/src/shared/discover-image.h
index 342b161577..edfb1412a4 100644
--- a/src/shared/discover-image.h
+++ b/src/shared/discover-image.h
@@ -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);
diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c
index a68610b80e..46c42cfc95 100644
--- a/src/shared/dissect-image.c
+++ b/src/shared/dissect-image.c
@@ -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;
+ }
- 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,
- };
+ 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,
+ };
+ }
}
}
@@ -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);
-
- 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);
+ return dissect_log_error(
+ dissect_loop_device(loop, verity, mount_options, image_policy, flags, ret),
+ loop->backing_file ?: loop->node,
+ verity);
- 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)
diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h
index 2e741e8267..a55ad63d2d 100644
--- a/src/shared/dissect-image.h
+++ b/src/shared/dissect-image.h
@@ -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);
diff --git a/src/shared/image-policy.c b/src/shared/image-policy.c
new file mode 100644
index 0000000000..5baeac4c5d
--- /dev/null
+++ b/src/shared/image-policy.c
@@ -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,
+};
diff --git a/src/shared/image-policy.h b/src/shared/image-policy.h
new file mode 100644
index 0000000000..a5e37642af
--- /dev/null
+++ b/src/shared/image-policy.h
@@ -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);
diff --git a/src/shared/meson.build b/src/shared/meson.build
index 0f2e2d1a67..df82778f9d 100644
--- a/src/shared/meson.build
+++ b/src/shared/meson.build
@@ -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',
diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c
index 1eac51b81e..edf01fe092 100644
--- a/src/shared/mount-util.c
+++ b/src/shared/mount-util.c
@@ -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) {
diff --git a/src/shared/mount-util.h b/src/shared/mount-util.h
index 84ea4b6392..f52687828a 100644
--- a/src/shared/mount-util.h
+++ b/src/shared/mount-util.h
@@ -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);
diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c
index d235073743..3fc6b910c4 100644
--- a/src/sysext/sysext.c
+++ b/src/sysext/sysext.c
@@ -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;
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
index 1a5beabc72..df4cf68435 100644
--- a/src/systemctl/systemctl.c
+++ b/src/systemctl/systemctl.c
@@ -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 |
diff --git a/src/systemctl/systemctl.h b/src/systemctl/systemctl.h
index 8c96ee0b4f..03645624ee 100644
--- a/src/systemctl/systemctl.h
+++ b/src/systemctl/systemctl.h
@@ -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";
diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c
index 6cb09dae50..f62e193056 100644
--- a/src/sysupdate/sysupdate.c
+++ b/src/sysupdate/sysupdate.c
@@ -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;
diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c
index f9f694bd54..a2d62121e0 100644
--- a/src/sysusers/sysusers.c
+++ b/src/sysusers/sysusers.c
@@ -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 |
diff --git a/src/test/meson.build b/src/test/meson.build
index d20c911e2b..85c3115e14 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -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',
diff --git a/src/test/test-image-policy.c b/src/test/test-image-policy.c
new file mode 100644
index 0000000000..41941704d4
--- /dev/null
+++ b/src/test/test-image-policy.c
@@ -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);
diff --git a/src/test/test-loop-block.c b/src/test/test-loop-block.c
index 97c2f66ac9..d8f48798cb 100644
--- a/src/test/test-loop-block.c
+++ b/src/test/test-loop-block.c
@@ -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);
diff --git a/src/test/test-namespace.c b/src/test/test-namespace.c
index 72155127c1..82be09dd6a 100644
--- a/src/test/test-namespace.c
+++ b/src/test/test-namespace.c
@@ -178,6 +178,7 @@ TEST(protect_kernel_logs) {
r = setup_namespace(NULL,
NULL,
NULL,
+ NULL,
&ns_info,
NULL,
NULL,
@@ -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);
diff --git a/src/test/test-ns.c b/src/test/test-ns.c
index 7eb29d109d..485069670b 100644
--- a/src/test/test-ns.c
+++ b/src/test/test-ns.c
@@ -79,6 +79,7 @@ int main(int argc, char *argv[]) {
r = setup_namespace(root_directory,
NULL,
NULL,
+ NULL,
&ns_info,
(char **) writable,
(char **) readonly,
@@ -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");
diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c
index 6cd76e8df8..fdabd7d2c5 100644
--- a/src/tmpfiles/tmpfiles.c
+++ b/src/tmpfiles/tmpfiles.c
@@ -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 |
diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh
index 3af90a69c6..52634c64b2 100755
--- a/test/units/testsuite-50.sh
+++ b/test/units/testsuite-50.sh
@@ -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"
diff --git a/test/units/testsuite-65.sh b/test/units/testsuite-65.sh
index 0ee56dfc00..49e6b87a13 100755
--- a/test/units/testsuite-65.sh
+++ b/test/units/testsuite-65.sh
@@ -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
diff --git a/units/systemd-sysext.service b/units/systemd-sysext.service
index 117591e897..5c11eba7c9 100644
--- a/units/systemd-sysext.service
+++ b/units/systemd-sysext.service
@@ -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