diff --git a/man/os-release.xml b/man/os-release.xml
index fc880c4765..168c1675a9 100644
--- a/man/os-release.xml
+++ b/man/os-release.xml
@@ -411,6 +411,18 @@
determines the fallback hostname.
+
+ ARCHITECTURE=
+ A string that specifies which CPU architecture the userspace binaries require.
+ The architecture identifiers are the same as for ConditionArchitecture=
+ described in systemd.unit5.
+ The field is optional and should only be used when just single architecture is supported.
+ It may provide redundant information when used in a GPT partition with a GUID type that already
+ encodes the architecture. If this is not the case, the architecture should be specified in
+ e.g., an extension image, to prevent an incompatible host from loading it.
+
+
+
SYSEXT_LEVEL=
diff --git a/man/systemd-sysext.xml b/man/systemd-sysext.xml
index 42e8e12df9..8da6bd26ad 100644
--- a/man/systemd-sysext.xml
+++ b/man/systemd-sysext.xml
@@ -115,12 +115,17 @@
A simple mechanism for version compatibility is enforced: a system extension image must carry a
/usr/lib/extension-release.d/extension-release.$name
file, which must match its image name, that is compared with the host os-release
- file: the contained ID= fields have to match, as well as the
- SYSEXT_LEVEL= field (if defined). If the latter is not defined, the
- VERSION_ID= field has to match instead. System extensions should not ship a
- /usr/lib/os-release file (as that would be merged into the host
- /usr/ tree, overriding the host OS version data, which is not desirable). The
- extension-release file follows the same format and semantics, and carries the same
+ file: the contained ID= fields have to match unless _any is set
+ for the extension. If the extension ID= is not _any, the
+ SYSEXT_LEVEL= field (if defined) has to match. If the latter is not defined, the
+ VERSION_ID= field has to match instead. If the extension defines the
+ ARCHITECTURE= field and the value is not _any it has to match the kernel's
+ architecture reported by uname2
+ but the used architecture identifiers are the same as for ConditionArchitecture=
+ described in systemd.unit5.
+ System extensions should not ship a /usr/lib/os-release file (as that would be merged
+ into the host /usr/ tree, overriding the host OS version data, which is not desirable).
+ The extension-release file follows the same format and semantics, and carries the same
content, as the os-release file of the OS, but it describes the resources carried
in the extension image.
diff --git a/src/shared/extension-release.c b/src/shared/extension-release.c
index db87b23a71..2da8e7ea94 100644
--- a/src/shared/extension-release.c
+++ b/src/shared/extension-release.c
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
+#include "architecture.h"
#include "env-util.h"
#include "extension-release.h"
#include "log.h"
@@ -15,7 +16,7 @@ int extension_release_validate(
const char *host_sysext_scope,
char **extension_release) {
- const char *extension_release_id = NULL, *extension_release_sysext_level = NULL;
+ const char *extension_release_id = NULL, *extension_release_sysext_level = NULL, *extension_architecture = NULL;
assert(name);
assert(!isempty(host_os_release_id));
@@ -48,13 +49,30 @@ int extension_release_validate(
}
}
+ /* When the architecture field is present and not '_any' it must match the host - for now just look at uname but in
+ * the future we could check if the kernel also supports 32 bit or binfmt has a translator set up for the architecture */
+ extension_architecture = strv_env_pairs_get(extension_release, "ARCHITECTURE");
+ if (!isempty(extension_architecture) && !streq(extension_architecture, "_any") &&
+ !streq(architecture_to_string(uname_architecture()), extension_architecture)) {
+ log_debug("Extension '%s' is for architecture '%s', but deployed on top of '%s'.",
+ name, extension_architecture, architecture_to_string(uname_architecture()));
+ return 0;
+ }
+
extension_release_id = strv_env_pairs_get(extension_release, "ID");
if (isempty(extension_release_id)) {
- log_debug("Extension '%s' does not contain ID in extension-release but requested to match '%s'",
+ log_debug("Extension '%s' does not contain ID in extension-release but requested to match '%s' or be '_any'",
name, host_os_release_id);
return 0;
}
+ /* A sysext with no host OS dependency (static binaries or scripts) can match
+ * '_any' host OS, and VERSION_ID or SYSEXT_LEVEL are not required anywhere */
+ if (streq(extension_release_id, "_any")) {
+ log_debug("Extension '%s' matches '_any' OS.", name);
+ return 1;
+ }
+
if (!streq(host_os_release_id, extension_release_id)) {
log_debug("Extension '%s' is for OS '%s', but deployed on top of '%s'.",
name, extension_release_id, host_os_release_id);
diff --git a/test/test-functions b/test/test-functions
index b208825e67..5dec5e28de 100644
--- a/test/test-functions
+++ b/test/test-functions
@@ -712,6 +712,13 @@ EOF
chmod +x "$initdir/opt/script1.sh"
echo MARKER=1 >"$initdir/usr/lib/systemd/system/other_file"
mksquashfs "$initdir" "$oldinitdir/usr/share/app1.raw" -noappend
+
+ export initdir="$TESTDIR/app-nodistro"
+ mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/usr/lib/systemd/system"
+ ( echo "ID=_any"
+ echo "ARCHITECTURE=_any" ) >"$initdir/usr/lib/extension-release.d/extension-release.app-nodistro"
+ echo MARKER=1 >"$initdir/usr/lib/systemd/system/some_file"
+ mksquashfs "$initdir" "$oldinitdir/usr/share/app-nodistro.raw" -noappend
)
}
diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh
index 31cb52064e..ae6dd0b3d7 100755
--- a/test/units/testsuite-50.sh
+++ b/test/units/testsuite-50.sh
@@ -305,6 +305,7 @@ systemd-run -P --property ExtensionImages="/usr/share/app0.raw /usr/share/app1.r
systemd-run -P --property ExtensionImages="/usr/share/app0.raw /usr/share/app1.raw" --property RootImage="${image}.raw" cat /usr/lib/systemd/system/some_file | grep -q -F "MARKER=1"
systemd-run -P --property ExtensionImages="/usr/share/app0.raw /usr/share/app1.raw" --property RootImage="${image}.raw" cat /opt/script1.sh | grep -q -F "extension-release.app2"
systemd-run -P --property ExtensionImages="/usr/share/app0.raw /usr/share/app1.raw" --property RootImage="${image}.raw" cat /usr/lib/systemd/system/other_file | grep -q -F "MARKER=1"
+systemd-run -P --property ExtensionImages=/usr/share/app-nodistro.raw --property RootImage="${image}.raw" cat /usr/lib/systemd/system/some_file | grep -q -F "MARKER=1"
cat >/run/systemd/system/testservice-50e.service </run/systemd/system/testservice-50f.service <