]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sysext: Check for /etc/initrd-release in given --root= tree
authorKai Lueke <kailuke@microsoft.com>
Tue, 28 Oct 2025 11:56:45 +0000 (20:56 +0900)
committerLuca Boccassi <luca.boccassi@gmail.com>
Thu, 6 Nov 2025 21:26:42 +0000 (21:26 +0000)
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.

(cherry picked from commit 570eae5007cbf2852f7c314f80224ecf3c828b25)

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

index 58777c683b2f90c04282a96e357678323c78d1ab..1402303a8e19efcf38881853f47ca21ec4814b72 100644 (file)
@@ -567,6 +567,7 @@ static int image_make(
 static int pick_image_search_path(
                 RuntimeScope scope,
                 ImageClass class,
+                const char *root,
                 char ***ret) {
 
         int r;
@@ -583,11 +584,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;
 
@@ -603,8 +604,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)
@@ -711,7 +719,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;
 
@@ -902,7 +910,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;
 
@@ -1797,7 +1805,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 d849ee261061934728b8dcc8ae5b5d0812229caf..5d432b42da17eb5c73034892b993678655c91872 100644 (file)
@@ -1662,9 +1662,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/"))
@@ -1860,13 +1862,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 2fa2951b61275b3389017a58c7a8a0896491cbd0..ecf0b83b1de56eb3e6bcf02a134947a29b9416e3 100755 (executable)
@@ -1081,6 +1081,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