]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sysext: Check for /etc/initrd-release in given --root= tree 39473/head
authorKai Lueke <kailuke@microsoft.com>
Tue, 28 Oct 2025 11:56:45 +0000 (20:56 +0900)
committerKai Lueke <kailuke@microsoft.com>
Tue, 4 Nov 2025 13:10:50 +0000 (22:10 +0900)
Both sysext and confext used the host's /etc/initrd-release file even
when --root=/somewhere was specified. A workaround was the
SYSTEMD_IN_INITRD= env var but without knowing this it was quite
confusing. Aside from users validating their extensions, the primary
use case for this to matter is when the extensions are set up from the
initrd where the initrd-release file is present when running but we want
to prepare the extensions for the final system and thus should match
for the right scope.
Make systemd-sysext check for /etc/initrd-release inside the given
--root= tree. An alternative would be to always ignore the
initrd-release check when --root= is passed but this way it is more
consistent. The image policy logic for EFI-loader-passed extensions
won't take effect when --root= is used, though.

src/shared/discover-image.c
src/sysext/sysext.c
test/units/TEST-50-DISSECT.sysext.sh

index 27ca861b53b295594f3f7ba23d0097389b5462a9..7bb017bafe19e8c76e0dbbcab84d5c98e08bde8e 100644 (file)
@@ -619,6 +619,7 @@ static int image_make(
 static int pick_image_search_path(
                 RuntimeScope scope,
                 ImageClass class,
+                const char *root,
                 char ***ret) {
 
         int r;
@@ -635,11 +636,11 @@ static int pick_image_search_path(
         if (scope < 0) {
                 _cleanup_strv_free_ char **a = NULL, **b = NULL;
 
-                r = pick_image_search_path(RUNTIME_SCOPE_USER, class, &a);
+                r = pick_image_search_path(RUNTIME_SCOPE_USER, class, root, &a);
                 if (r < 0)
                         return r;
 
-                r = pick_image_search_path(RUNTIME_SCOPE_SYSTEM, class, &b);
+                r = pick_image_search_path(RUNTIME_SCOPE_SYSTEM, class, root, &b);
                 if (r < 0)
                         return r;
 
@@ -655,8 +656,15 @@ static int pick_image_search_path(
 
         case RUNTIME_SCOPE_SYSTEM: {
                 const char *ns;
+                bool is_initrd;
+
+                r = chase_and_access("/etc/initrd-release", root, CHASE_PREFIX_ROOT, F_OK, /* ret_path= */ NULL);
+                if (r < 0 && r != -ENOENT)
+                        return r;
+                is_initrd = r >= 0;
+
                 /* Use the initrd search path if there is one, otherwise use the common one */
-                ns = in_initrd() && image_search_path_initrd[class] ?
+                ns = is_initrd && image_search_path_initrd[class] ?
                         image_search_path_initrd[class] :
                         image_search_path[class];
                 if (!ns)
@@ -763,7 +771,7 @@ int image_find(RuntimeScope scope,
                 return -ENOMEM;
 
         _cleanup_strv_free_ char **search = NULL;
-        r = pick_image_search_path(scope, class, &search);
+        r = pick_image_search_path(scope, class, root, &search);
         if (r < 0)
                 return r;
 
@@ -954,7 +962,7 @@ int image_discover(
         assert(images);
 
         _cleanup_strv_free_ char **search = NULL;
-        r = pick_image_search_path(scope, class, &search);
+        r = pick_image_search_path(scope, class, root, &search);
         if (r < 0)
                 return r;
 
@@ -2101,7 +2109,7 @@ bool image_in_search_path(
         assert(image);
 
         _cleanup_strv_free_ char **search = NULL;
-        r = pick_image_search_path(scope, class, &search);
+        r = pick_image_search_path(scope, class, root, &search);
         if (r < 0)
                 return r;
 
index 99dcee44cfe02754bd6b18b5f75773f95b656316..cb21a16fce440c58d92a3b6b54ef846995629c83 100644 (file)
@@ -1707,9 +1707,11 @@ static const ImagePolicy *pick_image_policy(const Image *img) {
 
         /* If located in /.extra/ 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.  */
+         * other directories we assume the appropriate level of trust was already established.)
+         * With --root= we default to the regular policy, though. (To change that, the check would need
+         * to prepend (or cut away) arg_root.) */
 
-        if (in_initrd()) {
+        if (in_initrd() && !arg_root) {
                 if (path_startswith(img->path, "/.extra/sysext/"))
                         return &image_policy_sysext_strict;
                 if (path_startswith(img->path, "/.extra/global_sysext/"))
@@ -1905,13 +1907,19 @@ static int merge_subprocess(
                 if (force)
                         log_debug("Force mode enabled, skipping version validation.");
                 else {
+                        bool is_initrd;
+                        r = chase_and_access("/etc/initrd-release", arg_root, CHASE_PREFIX_ROOT, F_OK, /* ret_path= */ NULL);
+                        if (r < 0 && r != -ENOENT)
+                                return log_error_errno(r, "Failed to check for /etc/initrd-release: %m");
+                        is_initrd = r >= 0;
+
                         r = extension_release_validate(
                                         img->name,
                                         host_os_release_id,
                                         host_os_release_id_like,
                                         host_os_release_version_id,
                                         host_os_release_api_level,
-                                        in_initrd() ? "initrd" : "system",
+                                        is_initrd ? "initrd" : "system",
                                         image_extension_release(img, image_class),
                                         image_class);
                         if (r < 0)
index f8472f5d3012fe1c591bee25cb3ade3861ea967f..3668e66e81bcc9256869d1c733730eaa5e067b07 100755 (executable)
@@ -1147,6 +1147,29 @@ chmod 0700 "$extension_data_dir"
 (! run_systemd_sysext "$fake_root" --mutable=yes merge)
 )
 
+( init_trap
+: "Check if merging fails in case of --root= being an initrd but the extension is not for it"
+# Since this is really about whether --root= gets prepended for the /etc/initrd-release check,
+# this also tests the more interesting reverse case that we are in the initrd and prepare
+# the mounts for the final system with --root=/sysroot
+fake_root=${roots_dir:+"$roots_dir/initrd-env-with-non-initrd-extension"}
+hierarchy=/opt
+
+prepare_root "$fake_root" "$hierarchy"
+prepare_extension_image "$fake_root" "$hierarchy"
+mkdir -p "${fake_root}/etc"
+touch "${fake_root}/etc/initrd-release"
+prepare_read_only_hierarchy "$fake_root" "$hierarchy"
+
+# Should be a no-op, thus we also don't run unmerge afterwards (otherwise the test is broken)
+run_systemd_sysext "$fake_root" merge
+if run_systemd_sysext "$fake_root" status --json=pretty |  jq -r '.[].extensions' | grep -v '^none$' ; then
+    echo >&2 "Extension got loaded for an initrd structure passed as --root= while the extension does not declare itself compatible with the initrd scope"
+    exit 1
+fi
+rm "${fake_root}/etc/initrd-release"
+)
+
 } # End of run_sysext_tests