From 570eae5007cbf2852f7c314f80224ecf3c828b25 Mon Sep 17 00:00:00 2001 From: Kai Lueke Date: Tue, 28 Oct 2025 20:56:45 +0900 Subject: [PATCH] sysext: Check for /etc/initrd-release in given --root= tree 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 | 20 ++++++++++++++------ src/sysext/sysext.c | 14 +++++++++++--- test/units/TEST-50-DISSECT.sysext.sh | 23 +++++++++++++++++++++++ 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index 27ca861b53b..7bb017bafe1 100644 --- a/src/shared/discover-image.c +++ b/src/shared/discover-image.c @@ -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; diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 99dcee44cfe..cb21a16fce4 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -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) diff --git a/test/units/TEST-50-DISSECT.sysext.sh b/test/units/TEST-50-DISSECT.sysext.sh index f8472f5d301..3668e66e81b 100755 --- a/test/units/TEST-50-DISSECT.sysext.sh +++ b/test/units/TEST-50-DISSECT.sysext.sh @@ -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 -- 2.47.3