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.
This commit is contained in:
Kai Lueke
2025-10-28 20:56:45 +09:00
parent 6649562924
commit 570eae5007
3 changed files with 48 additions and 9 deletions

View 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;

View 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)

View File

@@ -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