Use systemd-confext instead of custom /etc overlay mount

For A/B-updated /etc contents we used a custom overlay mount that
provides the default files through a lowerdir loaded from /usr. Since
then we upstreamed mutable systemd-confext support and now we can switch
to it.
This pulls in https://github.com/flatcar/init/pull/138 and
https://github.com/flatcar/bootengine/pull/115 together with backported
systemd patches that have opened or merged upstream PRs to fix --root=
issues and add a refresh skip check to prevent boot disruptions due to
the multiple daemon reloads and - more important - the missing atomic
remount that would mean /etc is gone for a few milliseconds during boot.
The skip logic works best with verity hashes and thus the default
confext must be a verity extension image.
User-provided confext don't work well yet unless they use verity due to
the missing atomic remount and reliance on the skipping logic. We also
need to look into stacking order and other mutabiliy settings.

The backported systemd patches relate to the following upstream PRs:

https://github.com/systemd/systemd/pull/39843 for
vpick-Don-t-use-openat-directly-but-resolve-symlinks
discover-image-Follow-symlinks-in-a-given-root
sysext-Use-correct-image-name-for-extension-release
test-Add-tests-for-handling-symlinks-with-systemd-sy
Note that the patch in the PR relies on
0859fe3f32774f1e0c787974cc252ff922a1b868 but the backport patch not.

https://github.com/systemd/systemd/pull/39980 for
sysext-Create-mutable-directory-with-the-right-mode
sysext-Skip-refresh-if-no-changes-are-found

https://github.com/systemd/systemd/pull/39991 for
sysext-Get-verity-user-certs-from-given-root

https://github.com/systemd/systemd/pull/40063 for
sysext-Fix-config-file-support-with-root
which relies on https://github.com/systemd/systemd/pull/38250 for
man-sysext.conf-add-systemd-sysext-config-files
sysext-introduce-global-config-file
sysext-support-ImagePolicy-global-config-option

Signed-off-by: Kai Lueke <kailuke@microsoft.com>
This commit is contained in:
Kai Lueke 2025-10-23 23:39:44 +09:00 committed by Kai Lüke
parent bd76846ad8
commit 34859bd21c
16 changed files with 2688 additions and 18 deletions

View File

@ -743,12 +743,35 @@ EOF
if [[ $(sudo find "${root_fs_dir}/usr/share/flatcar/etc" -size +0 ! -type d 2>/dev/null | wc -l) -gt 0 ]]; then
die "Unexpected non-empty files in ${root_fs_dir}/usr/share/flatcar/etc"
fi
# Some backwards-compat symlinks still use this folder as target,
# we can't remove it yet
sudo rm -rf "${root_fs_dir}/usr/share/flatcar/etc"
sudo cp -a "${root_fs_dir}/etc" "${root_fs_dir}/usr/share/flatcar/etc"
# Now set up a default confext and enable it.
# It's important to use dm-verity not only for stricter image policies
# but also because it allows us the refresh to identify this image and
# skip setting it up again in the final boot, which not only saves us
# a daemon-reload during boot but also from /etc contents shortly
# disappearing until systemd-sysext uses mount beneath for an atomic
# remount. Instead of a temporary directory we first prepare it as
# folder and then convert it to a DDI and remove the folder.
sudo rm -rf "${root_fs_dir}/usr/lib/confexts/00-flatcar-default"
sudo mkdir -p "${root_fs_dir}/usr/lib/confexts/00-flatcar-default"
# Do a copy because we keep /etc for the flatcar (.tar) container and the developer container
sudo cp -a "${root_fs_dir}/etc" "${root_fs_dir}/usr/lib/confexts/00-flatcar-default/etc"
sudo mkdir -p "${root_fs_dir}/usr/lib/confexts/00-flatcar-default/etc/extension-release.d/"
echo ID=_any | sudo tee "${root_fs_dir}/usr/lib/confexts/00-flatcar-default/etc/extension-release.d/extension-release.00-flatcar-default" > /dev/null
sudo systemd-repart \
--private-key="${SYSEXT_SIGNING_KEY_DIR}/sysexts.key" \
--certificate="${SYSEXT_SIGNING_KEY_DIR}/sysexts.crt" \
--make-ddi=confext \
--copy-source="${root_fs_dir}/usr/lib/confexts/00-flatcar-default" \
"${root_fs_dir}/usr/lib/confexts/00-flatcar-default.raw"
sudo rm -rf "${root_fs_dir}/usr/lib/confexts/00-flatcar-default"
# Remove the rootfs state as it should be recreated through the
# tmpfiles and may not be present on updating machines. This
# makes sure our tests cover the case of missing files in the
# Remove the rootfs state as it should be recreated through tmpfiles
# (and for /etc we use a confext) and may not be present on updating machines.
# This makes sure our tests cover the case of missing files in the
# rootfs and don't rely on the new image. Not done for the developer
# container.
if [[ -n "${image_kernel}" ]]; then

View File

@ -0,0 +1,2 @@
- Switched `/etc/` from a custom overlayfs for A/B updates to using a systemd-confext extension providing the default contents by using systemd-confext in the mutable mode where `/etc/` gets used as upperdir [scripts#3555](https://github.com/flatcar/scripts/pull/3555)
- Moved systemd-sysext image mounting into the initrd, so that system extensions can better define the behavior of the final system at boot without workarounds to apply settings late at boot. This means `.wants` symlinks for systemd units work as expected now and, therefore, we dropped the `ensure-sysext.service` workaround. We still recommend extensions to keep their workarounds, e.g., using `.upholds` instead of `.wants`, to better support live reloading. A skipping logic prevents an extension refresh late at boot but only if no changes were found. For extensions that are not stored on a custom filesystem, such as a separate `/var` partition, the new extension mounting from the initrd won't be able to load them early but they will be picked up late at boot through the extension refresh. This is another case where it's good if extensions keep workarounds for late loading.

View File

@ -14,7 +14,7 @@ if [[ ${PV} == 9999 ]]; then
EGIT_REPO_URI="https://github.com/flatcar/init.git"
inherit git-r3
else
EGIT_VERSION="860090d932a0bcdf71a73619f270845a06b64af0" # flatcar-master
EGIT_VERSION="ca7b51b85e303d95a38bc6d20f459aac07439eb0" # TODO: flatcar-master
SRC_URI="https://github.com/flatcar/init/archive/${EGIT_VERSION}.tar.gz -> ${PN}-${EGIT_VERSION}.tar.gz"
S="${WORKDIR}/init-${EGIT_VERSION}"
KEYWORDS="amd64 arm arm64 x86"

View File

@ -160,19 +160,6 @@ EOF
-e '/^C!* \/etc\/pam\.d/d' \
-e '/^C!* \/etc\/issue/d' || die
(
# Some OEMs prefer chronyd, so allow them to replace
# systemd-timesyncd with it.
insinto "$(systemd_get_systemunitdir)/systemd-timesyncd.service.d"
newins - flatcar.conf <<'EOF'
# Allow sysexts to ship timesyncd replacements which can have
# a Conflicts=systemd-timesyncd directive that would result
# in systemd-timesyncd not being started.
[Unit]
After=ensure-sysext.service
EOF
)
(
# Allow @mount syscalls for systemd-udevd.service
insinto "$(systemd_get_systemunitdir)/systemd-udevd.service.d"

View File

@ -0,0 +1,33 @@
From 6f4b065b626edd8a06ff0c8028173e060b5e444b Mon Sep 17 00:00:00 2001
From: Kai Lueke <kailuke@microsoft.com>
Date: Thu, 20 Nov 2025 23:43:55 +0900
Subject: [PATCH 03/10] vpick: Don't use openat directly but resolve symlinks
in given root
With systemd-sysext --root= all symlinks should be followed relative to
the given root and direct openat usage doesn't work.
Change the openat call to use the chase helper function to resolve the
symlink in the given root.
---
src/shared/vpick.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/shared/vpick.c b/src/shared/vpick.c
index b1b2d93054..dfe58cafa5 100644
--- a/src/shared/vpick.c
+++ b/src/shared/vpick.c
@@ -471,9 +471,9 @@ static int make_choice(
if (!p)
return log_oom_debug();
- object_fd = openat(dir_fd, best_filename, O_CLOEXEC|O_PATH);
+ object_fd = chase_and_openat(toplevel_fd, p, CHASE_AT_RESOLVE_IN_ROOT, O_PATH|O_CLOEXEC, NULL);
if (object_fd < 0)
- return log_debug_errno(errno, "Failed to open '%s/%s': %m",
+ return log_debug_errno(object_fd, "Failed to open '%s/%s': %m",
empty_to_root(toplevel_path), skip_leading_slash(inode_path));
return pin_choice(
--
2.52.0

View File

@ -0,0 +1,343 @@
From 9b6f1b1d8e1066a513a2939c613b36c9e887512c Mon Sep 17 00:00:00 2001
From: Kai Lueke <kailuke@microsoft.com>
Date: Thu, 20 Nov 2025 23:43:55 +0900
Subject: [PATCH 04/10] discover-image: Follow symlinks in a given root
So far systemd-sysext with --root= specified didn't follow extension
symlinks (such as the "current" symlinks managed by systemd-sysupdate).
The main use case is running systemd-sysext --root=/sysroot for setting
up the overlay mounts already from the initrd.
Resolve symlinks correctly but don't defend against later symlink races
that would access a path outside of the given root. Malicous live
modifications are not a realistic threat model and anyway for that one
would need to rework how the image entry is passed over up to the point
when the loop device is set up. This change here does not introduce this
weakness nor does it expose it more than before. Thus, make it explicit
that setting up the extensions for a given --root= implies a certain
trust into this given root tree that it does not try do race conditions
with symlinks to trick systemd-sysext to mount a file outside --root=.
Without a strict --image-policy= set we would anyway mount filesystems
right away which is another attack vector but, again, the main use case
is to do this for the final system which is trusted at this stage.
---
src/shared/discover-image.c | 162 +++++++++++++++++++++++++++---------
1 file changed, 122 insertions(+), 40 deletions(-)
diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c
index 1402303a8e..97c4284eca 100644
--- a/src/shared/discover-image.c
+++ b/src/shared/discover-image.c
@@ -356,6 +356,8 @@ static int image_make(
/* We explicitly *do* follow symlinks here, since we want to allow symlinking trees, raw files and block
* devices into /var/lib/machines/, and treat them normally.
+ * Note that if the caller does not want to follow symlinks (and does not care about symlink races)
+ * then the caller should pass in a resolved dir_path and filename and an fd.
*
* This function returns -ENOENT if we can't find the image after all, and -EMEDIUMTYPE if it's not a file we
* recognize. */
@@ -700,10 +702,7 @@ int image_find(RuntimeScope scope,
const char *root,
Image **ret) {
- /* As mentioned above, we follow symlinks on this fstatat(), because we want to permit people to
- * symlink block devices into the search path. (For now, we disable that when operating relative to
- * some root directory.) */
- int open_flags = root ? O_NOFOLLOW : 0, r;
+ int r;
assert(scope < _RUNTIME_SCOPE_MAX && scope != RUNTIME_SCOPE_GLOBAL);
assert(class >= 0);
@@ -718,16 +717,24 @@ int image_find(RuntimeScope scope,
if (!names)
return -ENOMEM;
+ _cleanup_close_ int root_fd = AT_FDCWD;
+ if (root) {
+ root_fd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH);
+ if (root_fd < 0) {
+ log_debug_errno(errno, "Failed to open root directory '%s': %m", root);
+ return -errno;
+ }
+ }
+
_cleanup_strv_free_ char **search = NULL;
r = pick_image_search_path(scope, class, root, &search);
if (r < 0)
return r;
STRV_FOREACH(path, search) {
- _cleanup_free_ char *resolved = NULL;
_cleanup_closedir_ DIR *d = NULL;
- r = chase_and_opendir(*path, root, CHASE_PREFIX_ROOT, &resolved, &d);
+ r = chase_and_opendir(*path, root, CHASE_PREFIX_ROOT, NULL, &d);
if (r == -ENOENT)
continue;
if (r < 0)
@@ -736,11 +743,20 @@ int image_find(RuntimeScope scope,
STRV_FOREACH(n, names) {
_cleanup_free_ char *fname_buf = NULL;
const char *fname = *n;
+ _cleanup_free_ char *fname_path = NULL;
+ _cleanup_free_ char *resolved_file = NULL;
+ _cleanup_close_ int fd = -EBADF;
+ _cleanup_close_ int subdirfd = -EBADF;
- _cleanup_close_ int fd = openat(dirfd(d), fname, O_PATH|O_CLOEXEC|open_flags);
+ fname_path = path_join(*path, fname);
+ if (!fname_path)
+ return -ENOMEM;
+
+ /* Follow symlinks only inside given root */
+ fd = chase_and_open(fname_path, root, CHASE_PREFIX_ROOT, O_PATH|O_CLOEXEC, &resolved_file);
if (fd < 0) {
- if (errno != ENOENT)
- return -errno;
+ if (fd != -ENOENT)
+ return fd;
continue;
}
@@ -769,10 +785,6 @@ int image_find(RuntimeScope scope,
*ASSERT_PTR(endswith(suffix, ".v")) = 0;
- _cleanup_free_ char *vp = path_join(resolved, fname);
- if (!vp)
- return -ENOMEM;
-
PickFilter filter = {
.type_mask = endswith(suffix, ".raw") ? (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK) : (UINT32_C(1) << DT_DIR),
.basename = name,
@@ -782,23 +794,27 @@ int image_find(RuntimeScope scope,
_cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
r = path_pick(root,
- /* toplevel_fd= */ AT_FDCWD,
- vp,
+ root_fd,
+ fname_path,
&filter,
- PICK_ARCHITECTURE|PICK_TRIES,
+ PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
&result);
if (r < 0) {
- log_debug_errno(r, "Failed to pick versioned image on '%s', skipping: %m", vp);
+ log_debug_errno(r, "Failed to pick versioned image on '%s/%s', skipping: %m", strempty(root), skip_leading_slash(fname_path));
continue;
}
if (!result.path) {
- log_debug("Found versioned directory '%s', without matching entry, skipping: %m", vp);
+ log_debug("Found versioned directory '%s/%s', without matching entry, skipping.", strempty(root), skip_leading_slash(fname_path));
continue;
}
/* Refresh the stat data for the discovered target */
st = result.st;
- fd = safe_close(fd);
+ /* Set subdirfd to indicate it should be used instead of dirfd(d).
+ * We reuse the O_PATH fd because it's only used in openat style in image_make().
+ * This subdirfd can be deleted with https://github.com/systemd/systemd/pull/39970 */
+ subdirfd = TAKE_FD(fd);
+ close_and_replace(fd, result.fd);
_cleanup_free_ char *bn = NULL;
r = path_extract_filename(result.path, &bn);
@@ -812,13 +828,38 @@ int image_find(RuntimeScope scope,
return log_oom();
fname = fname_buf;
+ fname_path = mfree(fname_path);
+ fname_path = path_join(*path, fname);
+ if (!fname_path)
+ return log_oom();
+ resolved_file = mfree(resolved_file);
+ resolved_file = path_join(root, result.path);
+ if (!resolved_file)
+ return log_oom();
} else if (!S_ISDIR(st.st_mode) && !S_ISBLK(st.st_mode)) {
log_debug("Ignoring non-directory and non-block device file '%s' without suffix.", fname);
continue;
}
- r = image_make(class, name, dirfd(d), resolved, fname, fd, &st, ret);
+ /* Only put resolved paths into the image entry.
+ * Defending against symlink races is out of scope
+ * and we trust a given root in that regard. */
+ _cleanup_free_ char *resolved_dir = NULL;
+ _cleanup_free_ char *resolved_fname = NULL;
+
+ r = path_extract_directory(resolved_file, &resolved_dir);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to extract directory name of image path '%s', skipping: %m", resolved_file);
+ continue;
+ }
+ r = path_extract_filename(resolved_file, &resolved_fname);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to extract basename of image path '%s', skipping: %m", resolved_file);
+ continue;
+ }
+
+ r = image_make(class, name, subdirfd == -EBADF ? dirfd(d) : subdirfd, resolved_dir, resolved_fname, fd, &st, ret);
if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
continue;
if (r < 0)
@@ -899,26 +940,31 @@ int image_discover(
const char *root,
Hashmap **images) {
- /* As mentioned above, we follow symlinks on this fstatat(), because we want to permit people to
- * symlink block devices into the search path. (For now, we disable that when operating relative to
- * some root directory.) */
- int open_flags = root ? O_NOFOLLOW : 0, r;
+ int r;
assert(scope < _RUNTIME_SCOPE_MAX && scope != RUNTIME_SCOPE_GLOBAL);
assert(class >= 0);
assert(class < _IMAGE_CLASS_MAX);
assert(images);
+ _cleanup_close_ int root_fd = AT_FDCWD;
+ if (root) {
+ root_fd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH);
+ if (root_fd < 0) {
+ log_debug_errno(errno, "Failed to open root directory '%s': %m", root);
+ return -errno;
+ }
+ }
+
_cleanup_strv_free_ char **search = NULL;
r = pick_image_search_path(scope, class, root, &search);
if (r < 0)
return r;
STRV_FOREACH(path, search) {
- _cleanup_free_ char *resolved = NULL;
_cleanup_closedir_ DIR *d = NULL;
- r = chase_and_opendir(*path, root, CHASE_PREFIX_ROOT, &resolved, &d);
+ r = chase_and_opendir(*path, root, CHASE_PREFIX_ROOT, NULL, &d);
if (r == -ENOENT)
continue;
if (r < 0)
@@ -928,14 +974,23 @@ int image_discover(
_cleanup_free_ char *pretty = NULL, *fname_buf = NULL;
_cleanup_(image_unrefp) Image *image = NULL;
const char *fname = de->d_name;
+ _cleanup_free_ char *fname_path = NULL;
+ _cleanup_free_ char *resolved_file = NULL;
+ _cleanup_close_ int fd = -EBADF;
+ _cleanup_close_ int subdirfd = -EBADF;
if (dot_or_dot_dot(fname))
continue;
- _cleanup_close_ int fd = openat(dirfd(d), fname, O_PATH|O_CLOEXEC|open_flags);
+ fname_path = path_join(*path, fname);
+ if (!fname_path)
+ return -ENOMEM;
+
+ /* Follow symlinks only inside given root */
+ fd = chase_and_open(fname_path, root, CHASE_PREFIX_ROOT, O_PATH|O_CLOEXEC, &resolved_file);
if (fd < 0) {
- if (errno != ENOENT)
- return -errno;
+ if (fd != -ENOENT)
+ return fd;
continue; /* Vanished while we were looking at it */
}
@@ -977,10 +1032,6 @@ int image_discover(
continue;
}
- _cleanup_free_ char *vp = path_join(resolved, fname);
- if (!vp)
- return -ENOMEM;
-
PickFilter filter = {
.type_mask = endswith(suffix, ".raw") ? (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK) : (UINT32_C(1) << DT_DIR),
.basename = pretty,
@@ -990,23 +1041,27 @@ int image_discover(
_cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
r = path_pick(root,
- /* toplevel_fd= */ AT_FDCWD,
- vp,
+ root_fd,
+ fname_path,
&filter,
- PICK_ARCHITECTURE|PICK_TRIES,
+ PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE,
&result);
if (r < 0) {
- log_debug_errno(r, "Failed to pick versioned image on '%s', skipping: %m", vp);
+ log_debug_errno(r, "Failed to pick versioned image on '%s/%s', skipping: %m", strempty(root), skip_leading_slash(fname_path));
continue;
}
if (!result.path) {
- log_debug("Found versioned directory '%s', without matching entry, skipping: %m", vp);
+ log_debug("Found versioned directory '%s/%s', without matching entry, skipping.", strempty(root), skip_leading_slash(fname_path));
continue;
}
/* Refresh the stat data for the discovered target */
st = result.st;
- fd = safe_close(fd);
+ /* Set subdirfd to indicate it should be used instead of dirfd(d).
+ * We reuse the O_PATH fd because it's only used in openat style in image_make().
+ * This subdirfd can be deleted with https://github.com/systemd/systemd/pull/39970 */
+ subdirfd = TAKE_FD(fd);
+ close_and_replace(fd, result.fd);
_cleanup_free_ char *bn = NULL;
r = path_extract_filename(result.path, &bn);
@@ -1020,6 +1075,15 @@ int image_discover(
return log_oom();
fname = fname_buf;
+ fname_path = mfree(fname_path);
+ fname_path = path_join(*path, fname);
+ if (!fname_path)
+ return log_oom();
+
+ resolved_file = mfree(resolved_file);
+ resolved_file = path_join(root, result.path);
+ if (!resolved_file)
+ return log_oom();
} else {
r = extract_image_basename(
fname,
@@ -1052,7 +1116,25 @@ int image_discover(
if (hashmap_contains(*images, pretty))
continue;
- r = image_make(class, pretty, dirfd(d), resolved, fname, fd, &st, &image);
+ /* Only put resolved paths into the image entry.
+ * Defending against symlink races is out of scope
+ * and we trust a given root in that regard. */
+ _cleanup_free_ char *resolved_dir = NULL;
+ _cleanup_free_ char *resolved_fname = NULL;
+
+ r = path_extract_directory(resolved_file, &resolved_dir);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to extract directory name of image path '%s', skipping: %m", resolved_file);
+ continue;
+ }
+ r = path_extract_filename(resolved_file, &resolved_fname);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to extract basename of image path '%s', skipping: %m", resolved_file);
+ continue;
+ }
+
+ r = image_make(class, pretty, subdirfd == -EBADF ? dirfd(d) : subdirfd, resolved_dir, resolved_fname, fd, &st, &image);
+
if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
continue;
if (r < 0)
--
2.52.0

View File

@ -0,0 +1,57 @@
From 5480f56002399069f74f30ce3ef620ec44ecf527 Mon Sep 17 00:00:00 2001
From: Kai Lueke <kailuke@microsoft.com>
Date: Thu, 20 Nov 2025 23:43:55 +0900
Subject: [PATCH 3/7] sysext: Use correct image name for extension release
checks
For the extension release check the image name is needed and was derived
from the backing file of the loop device. However, this can have a
different name when symlinks were resolved. The surprising behavior was
that it worked when the target name started with the extension name and
_ because that's what's supported to chop off version suffixes. However,
we should not have such strict requirements for the target name and also
allow - as version separator and entirely different names/prefixes, the
same way as we also do for directories instead of raw images.
Do not use the image name derived from the backing file of the loop
device but directly the extension name we have at hand.
---
src/shared/discover-image.c | 5 +++++
src/sysext/sysext.c | 5 +++++
2 files changed, 10 insertions(+)
diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c
index 91f4407b0e..480ffd221c 100644
--- a/src/shared/discover-image.c
+++ b/src/shared/discover-image.c
@@ -1822,6 +1822,11 @@ int image_read_metadata(Image *i, const ImagePolicy *image_policy) {
if (r < 0)
return r;
+ /* Do not use the image name derived from the backing file of the loop device */
+ r = free_and_strdup(&m->image_name, i->name);
+ if (r < 0)
+ return r;
+
r = dissected_image_acquire_metadata(
m,
/* userns_fd= */ -EBADF,
diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c
index 5d432b42da..72da02cd89 100644
--- a/src/sysext/sysext.c
+++ b/src/sysext/sysext.c
@@ -1819,6 +1819,11 @@ static int merge_subprocess(
if (r < 0)
return r;
+ /* Do not use the image name derived from the backing file of the loop device */
+ r = free_and_strdup(&m->image_name, img->name);
+ if (r < 0)
+ return r;
+
r = dissected_image_load_verity_sig_partition(
m,
d->fd,
--
2.51.1

View File

@ -0,0 +1,334 @@
From f2e3cd402e64528454d3825681ccf242ff1b46af Mon Sep 17 00:00:00 2001
From: Kai Lueke <kailuke@microsoft.com>
Date: Thu, 20 Nov 2025 23:43:55 +0900
Subject: [PATCH 4/7] test: Add tests for handling symlinks with systemd-sysext
When we now allow following symlinks inside a --root= we should also
test that it works in various cases from simple relative and absolute
symlinks to .v being a symlink itself or its contents, both for
directory and for .raw image extensions. While at it, also add a simple
test for .v without symlinks which wasn't there for direct usage of
systemd-sysext.
---
test/units/TEST-50-DISSECT.sysext.sh | 298 +++++++++++++++++++++++++++
1 file changed, 298 insertions(+)
diff --git a/test/units/TEST-50-DISSECT.sysext.sh b/test/units/TEST-50-DISSECT.sysext.sh
index ecf0b83b1d..3eec224eb6 100755
--- a/test/units/TEST-50-DISSECT.sysext.sh
+++ b/test/units/TEST-50-DISSECT.sysext.sh
@@ -163,6 +163,24 @@ prepare_extension_image_with_matching_id_like() {
prepend_trap "rm -rf ${ext_dir@Q}"
}
+prepare_extension_image_raw() {
+ local root=${1:-}
+ local hierarchy=${2:?}
+ local ext_dir ext_release name
+
+ name="test-extension"
+ ext_dir="$root/var/lib/extensions/$name"
+ ext_release="$ext_dir/usr/lib/extension-release.d/extension-release.$name"
+ mkdir -p "${ext_release%/*}"
+ echo "ID=_any" >"$ext_release"
+ mkdir -p "$ext_dir/$hierarchy"
+ touch "$ext_dir$hierarchy/preexisting-file-in-extension-image"
+ mksquashfs "$ext_dir" "$ext_dir.raw" -all-root -noappend -quiet
+ rm -rf "$ext_dir"
+
+ prepend_trap "rm -rf ${ext_dir@Q}.raw"
+}
+
prepare_extension_mutable_dir() {
local dir=${1:?}
@@ -1104,6 +1122,286 @@ fi
rm "${fake_root}/etc/initrd-release"
)
+# A couple of symlink tests follow below
+
+( init_trap
+: "Check if following a relative extension directory symlink works with and without --root="
+fake_root=${roots_dir:+"$roots_dir/follow-relative-dir-symlink"}
+hierarchy=/opt
+
+prepare_root "$fake_root" "$hierarchy"
+prepare_extension_image "$fake_root" "$hierarchy"
+mv -T "$fake_root/var/lib/extensions/test-extension" "$fake_root/var/othername-extension"
+ln -s "../../othername-extension" "$fake_root/var/lib/extensions/test-extension"
+prepare_read_only_hierarchy "$fake_root" "$hierarchy"
+
+run_systemd_sysext "$fake_root" merge
+extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
+
+run_systemd_sysext "$fake_root" unmerge
+extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+rm -rf "$fake_root/var/othername-extension"
+)
+
+( init_trap
+: "Check if following an absolute extension directory symlink works with and without --root="
+fake_root=${roots_dir:+"$roots_dir/follow-absolute-dir-symlink"}
+hierarchy=/opt
+
+prepare_root "$fake_root" "$hierarchy"
+prepare_extension_image "$fake_root" "$hierarchy"
+mv -T "$fake_root/var/lib/extensions/test-extension" "$fake_root/var/othername-extension"
+ln -s "/var/othername-extension" "$fake_root/var/lib/extensions/test-extension"
+prepare_read_only_hierarchy "$fake_root" "$hierarchy"
+
+run_systemd_sysext "$fake_root" merge
+extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
+
+run_systemd_sysext "$fake_root" unmerge
+extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+rm -rf "$fake_root/var/othername-extension"
+)
+
+( init_trap
+: "Check if following a relative extension image symlink works with and without --root="
+fake_root=${roots_dir:+"$roots_dir/follow-relative-image-symlink"}
+hierarchy=/opt
+
+prepare_root "$fake_root" "$hierarchy"
+prepare_extension_image_raw "$fake_root" "$hierarchy"
+mv "$fake_root/var/lib/extensions/test-extension.raw" "$fake_root/var/othername-extension.raw"
+ln -s "../../othername-extension.raw" "$fake_root/var/lib/extensions/test-extension.raw"
+prepare_read_only_hierarchy "$fake_root" "$hierarchy"
+
+run_systemd_sysext "$fake_root" merge
+extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
+
+run_systemd_sysext "$fake_root" unmerge
+extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+rm -rf "$fake_root/var/othername-extension.raw"
+)
+
+( init_trap
+: "Check if following an absolute extension image symlink works with and without --root="
+fake_root=${roots_dir:+"$roots_dir/follow-absolute-image-symlink"}
+hierarchy=/opt
+
+prepare_root "$fake_root" "$hierarchy"
+prepare_extension_image_raw "$fake_root" "$hierarchy"
+mv "$fake_root/var/lib/extensions/test-extension.raw" "$fake_root/var/othername-extension.raw"
+ln -s "/var/othername-extension.raw" "$fake_root/var/lib/extensions/test-extension.raw"
+prepare_read_only_hierarchy "$fake_root" "$hierarchy"
+
+run_systemd_sysext "$fake_root" merge
+extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
+
+run_systemd_sysext "$fake_root" unmerge
+extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+rm -rf "$fake_root/var/othername-extension.raw"
+)
+
+# And now a couple of vpick tests, including following symlinks
+
+( init_trap
+: "Check if vpick works for directory extensions"
+fake_root=${roots_dir:+"$roots_dir/vpick-dir"}
+hierarchy=/opt
+
+prepare_root "$fake_root" "$hierarchy"
+prepare_extension_image "$fake_root" "$hierarchy"
+mkdir -p "$fake_root/var/lib/extensions/test-extension.v"
+mv -T "$fake_root/var/lib/extensions/test-extension" "$fake_root/var/lib/extensions/test-extension.v/test-extension_1.0"
+prepare_read_only_hierarchy "$fake_root" "$hierarchy"
+
+run_systemd_sysext "$fake_root" merge
+extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
+
+run_systemd_sysext "$fake_root" unmerge
+extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+rm -rf "$fake_root/var/lib/extensions/test-extension.v"
+)
+
+( init_trap
+: "Check if vpick works for image extensions"
+fake_root=${roots_dir:+"$roots_dir/vpick-image"}
+hierarchy=/opt
+
+prepare_root "$fake_root" "$hierarchy"
+prepare_extension_image_raw "$fake_root" "$hierarchy"
+mkdir -p "$fake_root/var/lib/extensions/test-extension.raw.v"
+mv "$fake_root/var/lib/extensions/test-extension.raw" "$fake_root/var/lib/extensions/test-extension.raw.v/test-extension_1.0.raw"
+prepare_read_only_hierarchy "$fake_root" "$hierarchy"
+
+run_systemd_sysext "$fake_root" merge
+extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
+
+run_systemd_sysext "$fake_root" unmerge
+extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+rm -rf "$fake_root/var/lib/extensions/test-extension.raw.v"
+)
+
+( init_trap
+: "Check if vpick works for directory extensions if .v is a relative symlink"
+fake_root=${roots_dir:+"$roots_dir/vpick-dir-relative-symlink"}
+hierarchy=/opt
+
+prepare_root "$fake_root" "$hierarchy"
+prepare_extension_image "$fake_root" "$hierarchy"
+mkdir -p "$fake_root/var/test-extension-vpick"
+mv -T "$fake_root/var/lib/extensions/test-extension" "$fake_root/var/test-extension-vpick/test-extension_1.0"
+ln -s "../../test-extension-vpick" "$fake_root/var/lib/extensions/test-extension.v"
+prepare_read_only_hierarchy "$fake_root" "$hierarchy"
+
+run_systemd_sysext "$fake_root" merge
+extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
+
+run_systemd_sysext "$fake_root" unmerge
+extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+rm -rf "$fake_root/var/lib/extensions/test-extension.v" "$fake_root/var/test-extension-vpick"
+)
+
+( init_trap
+: "Check if vpick works for directory extensions if .v is an absolute symlink"
+fake_root=${roots_dir:+"$roots_dir/vpick-dir-absolute-symlink"}
+hierarchy=/opt
+
+prepare_root "$fake_root" "$hierarchy"
+prepare_extension_image "$fake_root" "$hierarchy"
+mkdir -p "$fake_root/var/test-extension-vpick"
+mv -T "$fake_root/var/lib/extensions/test-extension" "$fake_root/var/test-extension-vpick/test-extension_1.0"
+ln -s "/var/test-extension-vpick" "$fake_root/var/lib/extensions/test-extension.v"
+prepare_read_only_hierarchy "$fake_root" "$hierarchy"
+
+run_systemd_sysext "$fake_root" merge
+extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
+
+run_systemd_sysext "$fake_root" unmerge
+extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+rm -rf "$fake_root/var/lib/extensions/test-extension.v" "$fake_root/var/test-extension-vpick"
+)
+
+( init_trap
+: "Check if vpick works for image extensions if .v is a relative symlink"
+fake_root=${roots_dir:+"$roots_dir/vpick-image-relative-symlink"}
+hierarchy=/opt
+
+prepare_root "$fake_root" "$hierarchy"
+prepare_extension_image_raw "$fake_root" "$hierarchy"
+mkdir -p "$fake_root/var/test-extension-vpick"
+mv "$fake_root/var/lib/extensions/test-extension.raw" "$fake_root/var/test-extension-vpick/test-extension_1.0.raw"
+ln -s "../../test-extension-vpick" "$fake_root/var/lib/extensions/test-extension.raw.v"
+prepare_read_only_hierarchy "$fake_root" "$hierarchy"
+
+run_systemd_sysext "$fake_root" merge
+extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
+
+run_systemd_sysext "$fake_root" unmerge
+extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+rm -rf "$fake_root/var/lib/extensions/test-extension.raw.v" "$fake_root/var/test-extension-vpick"
+)
+
+( init_trap
+: "Check if vpick works for image extensions if .v is an absolute symlink"
+fake_root=${roots_dir:+"$roots_dir/vpick-image-absolute-symlink"}
+hierarchy=/opt
+
+prepare_root "$fake_root" "$hierarchy"
+prepare_extension_image_raw "$fake_root" "$hierarchy"
+mkdir -p "$fake_root/var/test-extension-vpick"
+mv "$fake_root/var/lib/extensions/test-extension.raw" "$fake_root/var/test-extension-vpick/test-extension_1.0.raw"
+ln -s "/var/test-extension-vpick" "$fake_root/var/lib/extensions/test-extension.raw.v"
+prepare_read_only_hierarchy "$fake_root" "$hierarchy"
+
+run_systemd_sysext "$fake_root" merge
+extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
+
+run_systemd_sysext "$fake_root" unmerge
+extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+rm -rf "$fake_root/var/lib/extensions/test-extension.raw.v" "$fake_root/var/test-extension-vpick"
+)
+
+( init_trap
+: "Check if vpick works for directory extensions if inside a .v there is a relative symlink"
+fake_root=${roots_dir:+"$roots_dir/vpick-dir-relative-symlink-inside"}
+hierarchy=/opt
+
+prepare_root "$fake_root" "$hierarchy"
+prepare_extension_image "$fake_root" "$hierarchy"
+mv -T "$fake_root/var/lib/extensions/test-extension" "$fake_root/var/othername-extension"
+mkdir -p "$fake_root/var/lib/extensions/test-extension.v"
+ln -s "../../../othername-extension" "$fake_root/var/lib/extensions/test-extension.v/test-extension_1.0"
+prepare_read_only_hierarchy "$fake_root" "$hierarchy"
+
+run_systemd_sysext "$fake_root" merge
+extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
+
+run_systemd_sysext "$fake_root" unmerge
+extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+rm -rf "$fake_root/var/lib/extensions/test-extension.v" "$fake_root/var/othername-extension"
+)
+
+( init_trap
+: "Check if vpick works for directory extensions if inside a .v there is an absolute symlink"
+fake_root=${roots_dir:+"$roots_dir/vpick-dir-absolute-symlink-inside"}
+hierarchy=/opt
+
+prepare_root "$fake_root" "$hierarchy"
+prepare_extension_image "$fake_root" "$hierarchy"
+mv -T "$fake_root/var/lib/extensions/test-extension" "$fake_root/var/othername-extension"
+mkdir -p "$fake_root/var/lib/extensions/test-extension.v"
+ln -s "/var/othername-extension" "$fake_root/var/lib/extensions/test-extension.v/test-extension_1.0"
+prepare_read_only_hierarchy "$fake_root" "$hierarchy"
+
+run_systemd_sysext "$fake_root" merge
+extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
+
+run_systemd_sysext "$fake_root" unmerge
+extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+rm -rf "$fake_root/var/lib/extensions/test-extension.v" "$fake_root/var/othername-extension"
+)
+
+( init_trap
+: "Check if vpick works for image extensions if inside a .v there is a relative symlink"
+fake_root=${roots_dir:+"$roots_dir/vpick-image-relative-symlink-inside"}
+hierarchy=/opt
+
+prepare_root "$fake_root" "$hierarchy"
+prepare_extension_image_raw "$fake_root" "$hierarchy"
+mv "$fake_root/var/lib/extensions/test-extension.raw" "$fake_root/var/othername-extension.raw"
+mkdir -p "$fake_root/var/lib/extensions/test-extension.raw.v"
+ln -s "../../../othername-extension.raw" "$fake_root/var/lib/extensions/test-extension.raw.v/test-extension_1.0.raw"
+prepare_read_only_hierarchy "$fake_root" "$hierarchy"
+
+run_systemd_sysext "$fake_root" merge
+extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
+
+run_systemd_sysext "$fake_root" unmerge
+extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+rm -rf "$fake_root/var/lib/extensions/test-extension.raw.v" "$fake_root/var/othername-extension.raw"
+)
+
+( init_trap
+: "Check if vpick works for image extensions if inside a .v there is an absolute symlink"
+fake_root=${roots_dir:+"$roots_dir/vpick-image-absolute-symlink-inside"}
+hierarchy=/opt
+
+prepare_root "$fake_root" "$hierarchy"
+prepare_extension_image_raw "$fake_root" "$hierarchy"
+mv "$fake_root/var/lib/extensions/test-extension.raw" "$fake_root/var/othername-extension.raw"
+mkdir -p "$fake_root/var/lib/extensions/test-extension.raw.v"
+ln -s "/var/othername-extension.raw" "$fake_root/var/lib/extensions/test-extension.raw.v/test-extension_1.0.raw"
+prepare_read_only_hierarchy "$fake_root" "$hierarchy"
+
+run_systemd_sysext "$fake_root" merge
+extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
+
+run_systemd_sysext "$fake_root" unmerge
+extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+rm -rf "$fake_root/var/lib/extensions/test-extension.raw.v" "$fake_root/var/othername-extension.raw"
+)
+
+# Done with the above vpick symlink tests for --root= and without
+
} # End of run_sysext_tests
--
2.51.1

View File

@ -0,0 +1,45 @@
From cf36f845e6a806161e008def40a271e9e9746c4f Mon Sep 17 00:00:00 2001
From: Kai Lueke <kailuke@microsoft.com>
Date: Wed, 3 Dec 2025 00:02:32 +0900
Subject: [PATCH 5/7] sysext: Create mutable directory with the right mode
When the mutable directory didn't exist but gets created with
--mutable=yes then it used to get mode 700 and later it got patched by
a chmod because it is the top layer and must match the target hierarchy.
This meant one could not call the function to resolve the mutable
directory twice before the mount because it has a check for a proper
mode when the directory exists which is the case for the second call.
Also, this resulted in /var/lib/extensions.mutable getting created with
mode 700 which is not really required.
Don't rely on the chmod for the upper dir but directly create the
directory with the right mode by first creating all missing directories
with 755 as a sane default and then changing the mode as needed for the
mutable directory.
---
src/sysext/sysext.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c
index 72da02cd89..d63cf39fbb 100644
--- a/src/sysext/sysext.c
+++ b/src/sysext/sysext.c
@@ -952,10 +952,14 @@ static int resolve_mutable_directory(
if (!path_in_root)
return log_oom();
- r = mkdir_p(path_in_root, 0700);
+ /* This also creates /var/lib/extensions.mutable if needed */
+ r = mkdir_p(path_in_root, 0755);
if (r < 0)
return log_error_errno(r, "Failed to create a directory '%s': %m", path_in_root);
+ if (chmod(path_in_root, hierarchy_mode) < 0)
+ return log_error_errno(errno, "Failed to chmod directory '%s': %m", path_in_root);
+
_cleanup_close_ int atfd = open(path_in_root, O_DIRECTORY|O_CLOEXEC);
if (atfd < 0)
return log_error_errno(errno, "Failed to open directory '%s': %m", path_in_root);
--
2.51.1

View File

@ -0,0 +1,361 @@
From 439fb373b7360ba3759b8978d0354d4fe760c8f2 Mon Sep 17 00:00:00 2001
From: Kai Lueke <kailuke@microsoft.com>
Date: Thu, 27 Nov 2025 17:49:15 +0900
Subject: [PATCH 2/3] sysext: Get verity user certs from given --root=
The verity user certs weren't looked up in the given --root= for
systemd-sysext which made it fail to set up extensions with a strict
image policy.
Look up verity user certs from inside the --root= when we operate on
images in it. The main use case where this matters is when the initrd
sets up the extensions for the final system and thus systemd-sysext
should do the same thing as it would do in the final system.
Signed-off-by: Kai Lueke <kailuke@microsoft.com>
---
src/core/namespace.c | 1 +
src/machine/image-dbus.c | 8 ++--
src/machine/machined-varlink.c | 2 +-
src/mountfsd/mountwork.c | 1 +
src/portable/portabled-image-bus.c | 2 +-
src/shared/discover-image.c | 2 +-
src/shared/discover-image.h | 2 +-
src/shared/dissect-image.c | 22 ++++++-----
src/shared/dissect-image.h | 2 +-
src/sysext/sysext.c | 4 +-
test/units/TEST-50-DISSECT.sysext.sh | 58 ++++++++++++++++++++++++++++
11 files changed, 84 insertions(+), 20 deletions(-)
diff --git a/src/core/namespace.c b/src/core/namespace.c
index 283a1108ce..97cf008194 100644
--- a/src/core/namespace.c
+++ b/src/core/namespace.c
@@ -2593,6 +2593,7 @@ int setup_namespace(const NamespaceParameters *p, char **reterr_path) {
r = dissected_image_decrypt(
dissected_image,
NULL,
+ NULL,
p->verity,
dissect_image_flags);
if (r < 0)
diff --git a/src/machine/image-dbus.c b/src/machine/image-dbus.c
index 8bc6565079..2857cd18be 100644
--- a/src/machine/image-dbus.c
+++ b/src/machine/image-dbus.c
@@ -284,7 +284,7 @@ int bus_image_method_get_hostname(
int r;
if (!image->metadata_valid) {
- r = image_read_metadata(image, &image_policy_container);
+ r = image_read_metadata(image, NULL, &image_policy_container);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
}
@@ -302,7 +302,7 @@ int bus_image_method_get_machine_id(
int r;
if (!image->metadata_valid) {
- r = image_read_metadata(image, &image_policy_container);
+ r = image_read_metadata(image, NULL, &image_policy_container);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
}
@@ -330,7 +330,7 @@ int bus_image_method_get_machine_info(
int r;
if (!image->metadata_valid) {
- r = image_read_metadata(image, &image_policy_container);
+ r = image_read_metadata(image, NULL, &image_policy_container);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
}
@@ -347,7 +347,7 @@ int bus_image_method_get_os_release(
int r;
if (!image->metadata_valid) {
- r = image_read_metadata(image, &image_policy_container);
+ r = image_read_metadata(image, NULL, &image_policy_container);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
}
diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c
index 52b1fc12d2..1e8f4ce9a8 100644
--- a/src/machine/machined-varlink.c
+++ b/src/machine/machined-varlink.c
@@ -621,7 +621,7 @@ static int list_image_one_and_maybe_read_metadata(sd_varlink *link, Image *image
assert(image);
if (should_acquire_metadata(am) && !image->metadata_valid) {
- r = image_read_metadata(image, &image_policy_container);
+ r = image_read_metadata(image, NULL, &image_policy_container);
if (r < 0 && am != ACQUIRE_METADATA_GRACEFUL)
return log_debug_errno(r, "Failed to read image metadata: %m");
if (r < 0)
diff --git a/src/mountfsd/mountwork.c b/src/mountfsd/mountwork.c
index bfb8c05c22..260a668525 100644
--- a/src/mountfsd/mountwork.c
+++ b/src/mountfsd/mountwork.c
@@ -495,6 +495,7 @@ static int vl_method_mount_image(
r = dissected_image_decrypt(
di,
+ NULL,
p.password,
&verity,
dissect_flags);
diff --git a/src/portable/portabled-image-bus.c b/src/portable/portabled-image-bus.c
index e8bcb900ef..380a6d5d45 100644
--- a/src/portable/portabled-image-bus.c
+++ b/src/portable/portabled-image-bus.c
@@ -61,7 +61,7 @@ int bus_image_common_get_os_release(
return 1;
if (!image->metadata_valid) {
- r = image_read_metadata(image, &image_policy_service);
+ r = image_read_metadata(image, NULL, &image_policy_service);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
}
diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c
index 9ce5f028fc..822ea2bd24 100644
--- a/src/shared/discover-image.c
+++ b/src/shared/discover-image.c
@@ -1766,7 +1766,7 @@ int image_set_pool_limit(ImageClass class, uint64_t referenced_max) {
return 0;
}
-int image_read_metadata(Image *i, const ImagePolicy *image_policy) {
+int image_read_metadata(Image *i, const char *root, const ImagePolicy *image_policy) {
_cleanup_(release_lock_file) LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT;
int r;
diff --git a/src/shared/discover-image.h b/src/shared/discover-image.h
index 7b5593f08d..4d64a306c8 100644
--- a/src/shared/discover-image.h
+++ b/src/shared/discover-image.h
@@ -73,7 +73,7 @@ int image_name_lock(const char *name, int operation, LockFile *ret);
int image_set_limit(Image *i, uint64_t referenced_max);
int image_set_pool_limit(ImageClass class, uint64_t referenced_max);
-int image_read_metadata(Image *i, const ImagePolicy *image_policy);
+int image_read_metadata(Image *i, const char *root, const ImagePolicy *image_policy);
bool image_in_search_path(RuntimeScope scope, ImageClass class, const char *root, const char *image);
diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c
index 715afc8882..8ffb63e1d3 100644
--- a/src/shared/dissect-image.c
+++ b/src/shared/dissect-image.c
@@ -2611,7 +2611,7 @@ static char* dm_deferred_remove_clean(char *name) {
}
DEFINE_TRIVIAL_CLEANUP_FUNC(char *, dm_deferred_remove_clean);
-static int validate_signature_userspace(const VeritySettings *verity, DissectImageFlags flags) {
+static int validate_signature_userspace(const VeritySettings *verity, const char *root, DissectImageFlags flags) {
int r;
if (!FLAGS_SET(flags, DISSECT_IMAGE_ALLOW_USERSPACE_VERITY)) {
@@ -2656,7 +2656,7 @@ static int validate_signature_userspace(const VeritySettings *verity, DissectIma
/* Because installing a signature certificate into the kernel chain is so messy, let's optionally do
* userspace validation. */
- r = conf_files_list_nulstr(&certs, ".crt", NULL, CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, CONF_PATHS_NULSTR("verity.d"));
+ r = conf_files_list_nulstr(&certs, ".crt", root, CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, CONF_PATHS_NULSTR("verity.d"));
if (r < 0)
return log_debug_errno(r, "Failed to enumerate certificates: %m");
if (strv_isempty(certs)) {
@@ -2718,6 +2718,7 @@ static int validate_signature_userspace(const VeritySettings *verity, DissectIma
static int do_crypt_activate_verity(
struct crypt_device *cd,
+ const char *root,
const char *name,
const VeritySettings *verity,
DissectImageFlags flags) {
@@ -2765,7 +2766,7 @@ static int do_crypt_activate_verity(
/* Preferably propagate the original kernel error, so that the fallback logic can work,
* as the device-mapper is finicky around concurrent activations of the same volume */
- k = validate_signature_userspace(verity, flags);
+ k = validate_signature_userspace(verity, root, flags);
if (k < 0)
return r < 0 ? r : k;
if (k == 0)
@@ -2805,8 +2806,9 @@ static usec_t verity_timeout(void) {
static int verity_partition(
PartitionDesignator designator,
- DissectedPartition *m,
- DissectedPartition *v,
+ DissectedPartition *m, /* data partition */
+ DissectedPartition *v, /* verity partition */
+ const char *root, /* The root to get user verity certs from (for a sysext) */
const VeritySettings *verity,
DissectImageFlags flags,
DecryptedImage *d) {
@@ -2886,7 +2888,7 @@ static int verity_partition(
goto check; /* The device already exists. Let's check it. */
/* The symlink to the device node does not exist yet. Assume not activated, and let's activate it. */
- r = do_crypt_activate_verity(cd, name, verity, flags);
+ r = do_crypt_activate_verity(cd, root, name, verity, flags);
if (r >= 0)
goto try_open; /* The device is activated. Let's open it. */
/* libdevmapper can return EINVAL when the device is already in the activation stage.
@@ -2980,7 +2982,7 @@ static int verity_partition(
*/
sym_crypt_free(cd);
cd = NULL;
- return verity_partition(designator, m, v, verity, flags & ~DISSECT_IMAGE_VERITY_SHARE, d);
+ return verity_partition(designator, m, v, root, verity, flags & ~DISSECT_IMAGE_VERITY_SHARE, d);
}
return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "All attempts to activate verity device %s failed.", name);
@@ -3000,6 +3002,7 @@ success:
int dissected_image_decrypt(
DissectedImage *m,
+ const char *root, /* The root to get user verity certs from (for a sysext) */
const char *passphrase,
const VeritySettings *verity,
DissectImageFlags flags) {
@@ -3047,7 +3050,7 @@ int dissected_image_decrypt(
if (k >= 0) {
flags |= getenv_bool("SYSTEMD_VERITY_SHARING") != 0 ? DISSECT_IMAGE_VERITY_SHARE : 0;
- r = verity_partition(i, p, m->partitions + k, verity, flags, d);
+ r = verity_partition(i, p, m->partitions + k, root, verity, flags, d);
if (r < 0)
return r;
}
@@ -3080,7 +3083,7 @@ int dissected_image_decrypt_interactively(
n--;
for (;;) {
- r = dissected_image_decrypt(m, passphrase, verity, flags);
+ r = dissected_image_decrypt(m, NULL, passphrase, verity, flags);
if (r >= 0)
return r;
if (r == -EKEYREJECTED)
@@ -4367,6 +4370,7 @@ int verity_dissect_and_mount(
r = dissected_image_decrypt(
dissected_image,
NULL,
+ NULL,
verity,
dissect_image_flags);
if (r < 0)
diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h
index 97431bca67..004dc46dc3 100644
--- a/src/shared/dissect-image.h
+++ b/src/shared/dissect-image.h
@@ -171,7 +171,7 @@ void dissected_image_close(DissectedImage *m);
DissectedImage* dissected_image_unref(DissectedImage *m);
DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref);
-int dissected_image_decrypt(DissectedImage *m, const char *passphrase, const VeritySettings *verity, DissectImageFlags flags);
+int dissected_image_decrypt(DissectedImage *m, const char *root, const char *passphrase, const VeritySettings *verity, DissectImageFlags flags);
int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphrase, const VeritySettings *verity, DissectImageFlags flags);
int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift, uid_t uid_range, int userns_fd, DissectImageFlags flags);
int dissected_image_mount_and_warn(DissectedImage *m, const char *where, uid_t uid_shift, uid_t uid_range, int userns_fd, DissectImageFlags flags);
diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c
index c33ce0d0a4..dbd6df63b4 100644
--- a/src/sysext/sysext.c
+++ b/src/sysext/sysext.c
@@ -1888,7 +1888,7 @@ static int merge_subprocess(
return log_error_errno(r, "Failed to create origin verity entry for '%s': %m", img->name);
}
- r = dissected_image_decrypt(m, /* passphrase= */ NULL, &verity_settings, flags);
+ r = dissected_image_decrypt(m, arg_root, /* passphrase= */ NULL, &verity_settings, flags);
if (r < 0)
return r;
@@ -2308,7 +2308,7 @@ static int image_discover_and_read_metadata(ImageClass image_class, Hashmap **re
return log_error_errno(r, "Failed to discover images: %m");
HASHMAP_FOREACH(img, images) {
- r = image_read_metadata(img, image_class_info[image_class].default_image_policy);
+ r = image_read_metadata(img, arg_root, image_class_info[image_class].default_image_policy);
if (r < 0)
return log_error_errno(r, "Failed to read metadata for image %s: %m", img->name);
}
diff --git a/test/units/TEST-50-DISSECT.sysext.sh b/test/units/TEST-50-DISSECT.sysext.sh
index 05f691b457..6e64eea492 100755
--- a/test/units/TEST-50-DISSECT.sysext.sh
+++ b/test/units/TEST-50-DISSECT.sysext.sh
@@ -181,6 +181,52 @@ prepare_extension_image_raw() {
prepend_trap "rm -rf ${ext_dir@Q}.raw"
}
+prepare_extension_image_raw_verity() {
+ local root=${1:-}
+ local hierarchy=${2:?}
+ local ext_dir ext_release name tmpcrt
+
+ name="test-extension"
+ ext_dir="$root/var/lib/extensions/$name"
+ ext_release="$ext_dir/usr/lib/extension-release.d/extension-release.$name"
+ tmpcrt=$(mktemp --directory "/tmp/test-sysext.crt.XXXXXXXXXX")
+
+ prepend_trap "rm -rf ${ext_dir@Q} ${ext_dir@Q}.raw '$root/etc/verity.d/test-ext.crt' '$tmpcrt'"
+
+ mkdir -p "${ext_release%/*}"
+ echo "ID=_any" >"$ext_release"
+ mkdir -p "$ext_dir/$hierarchy"
+ touch "$ext_dir$hierarchy/preexisting-file-in-extension-image"
+ tee >"$tmpcrt/verity.openssl.cnf" <<EOF
+[ req ]
+prompt = no
+distinguished_name = req_distinguished_name
+[ req_distinguished_name ]
+C = DE
+ST = Test State
+L = Test Locality
+O = Org Name
+OU = Org Unit Name
+CN = Common Name
+emailAddress = test@email.com
+EOF
+ openssl req \
+ -config "$tmpcrt/verity.openssl.cnf" \
+ -new -x509 \
+ -newkey rsa:1024 \
+ -keyout "$tmpcrt/test-ext.key" \
+ -out "$tmpcrt/test-ext.crt" \
+ -days 365 \
+ -nodes
+ systemd-repart --make-ddi=sysext \
+ --private-key="$tmpcrt/test-ext.key" --certificate="$tmpcrt/test-ext.crt" \
+ --copy-source="$ext_dir" "$ext_dir.raw"
+ rm -rf "$ext_dir"
+ mkdir -p "$root/etc/verity.d"
+ mv "$tmpcrt/test-ext.crt" "$root/etc/verity.d/"
+ rm -rf "$tmpcrt"
+}
+
prepare_extension_mutable_dir() {
local dir=${1:?}
@@ -1439,6 +1485,18 @@ if [ "$MOUNTID2" = "$MOUNTID3" ]; then
exit 1
fi
+( init_trap
+: "Check if verity user certs get loaded from --root="
+fake_root=${roots_dir:+"$roots_dir/verity-user-cert-from-root"}
+hierarchy=/opt
+
+prepare_root "$fake_root" "$hierarchy"
+prepare_extension_image_raw_verity "$fake_root" "$hierarchy"
+prepare_read_only_hierarchy "$fake_root" "$hierarchy"
+
+run_systemd_sysext "$fake_root" merge --image-policy=root=signed+absent:usr=signed+absent
+extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
+
run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
)
--
2.52.0

View File

@ -0,0 +1,89 @@
From d711880914fe0e32f3fbc946d8b8ee54031727b1 Mon Sep 17 00:00:00 2001
From: Emanuele Giuseppe Esposito <eesposit@redhat.com>
Date: Thu, 17 Jul 2025 05:03:54 -0400
Subject: [PATCH 1/4] sysext: introduce global config file
Introduce systemd/{sysext/confext}.conf and systemd/{sysext/confext}.conf.d to provide an
alternative way of setting the cmdline options in systemd-sysext.
The config file has to have a [Sysext] or [Confext] option respectively,
which will be overridden by the cmdline.
As an example of supported config, add Mutable= option.
---
src/sysext/sysext.c | 38 ++++++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c
index 20acc60724..332fc55bb3 100644
--- a/src/sysext/sysext.c
+++ b/src/sysext/sysext.c
@@ -18,6 +18,7 @@
#include "bus-util.h"
#include "capability-util.h"
#include "chase.h"
+#include "conf-parser.h"
#include "devnum-util.h"
#include "discover-image.h"
#include "dissect-image.h"
@@ -150,6 +151,36 @@ static int parse_mutable_mode(const char *p) {
return mutable_mode_from_string(p);
}
+static DEFINE_CONFIG_PARSE_ENUM(config_parse_mutable_mode, mutable_mode, MutableMode);
+
+static int parse_config_file(ImageClass image_class) {
+ const char *section = image_class == IMAGE_SYSEXT ? "SysExt" : "ConfExt";
+ const ConfigTableItem items[] = {
+ { section, "Mutable", config_parse_mutable_mode, 0, &arg_mutable },
+ {}
+ };
+ _cleanup_free_ char *config_file = NULL;
+ int r;
+
+ config_file = strjoin("systemd/", image_class_info[image_class].short_identifier, ".conf");
+ if (!config_file)
+ return log_oom();
+
+ r = config_parse_standard_file_with_dropins_full(
+ arg_root,
+ config_file,
+ image_class == IMAGE_SYSEXT ? "SysExt\0" : "ConfExt\0",
+ config_item_table_lookup, items,
+ CONFIG_PARSE_WARN,
+ /* userdata = */ NULL,
+ /* ret_stats_by_path = */ NULL,
+ /* ret_dropin_files = */ NULL);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
static int is_our_mount_point(
ImageClass image_class,
const char *p) {
@@ -2828,6 +2859,7 @@ static int run(int argc, char *argv[]) {
arg_image_class = invoked_as(argv, "systemd-confext") ? IMAGE_CONFEXT : IMAGE_SYSEXT;
+ /* Parse environment variable first */
env_var = getenv(image_class_info[arg_image_class].mode_env);
if (env_var) {
r = parse_mutable_mode(env_var);
@@ -2838,6 +2870,12 @@ static int run(int argc, char *argv[]) {
arg_mutable = r;
}
+ /* Parse configuration file */
+ r = parse_config_file(arg_image_class);
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse global config file, ignoring: %m");
+
+ /* Parse command line */
r = parse_argv(argc, argv);
if (r <= 0)
return r;
--
2.51.0

View File

@ -0,0 +1,156 @@
From 88943429fbf80cf55fc7307ea34b5942524c2f45 Mon Sep 17 00:00:00 2001
From: Emanuele Giuseppe Esposito <eesposit@redhat.com>
Date: Thu, 17 Jul 2025 05:28:21 -0400
Subject: [PATCH 2/4] man/sysext.conf: add systemd-sysext config files
Add sysext.conf, which similar to other configs like coredump, will be
searched in:
/{etc run usr/lib}/systemd/{sysext/confext}.conf
but also
/{etc run usr/lib}/systemd/{sysext/confext}.conf.d/*
This config is an alternative to command line options, especially useful
if we want to extend the service units without modifying them.
---
man/rules/meson.build | 1 +
man/sysext.conf.xml | 89 ++++++++++++++++++++++++++++++++++++++++++
man/systemd-sysext.xml | 8 +++-
3 files changed, 97 insertions(+), 1 deletion(-)
create mode 100644 man/sysext.conf.xml
diff --git a/man/rules/meson.build b/man/rules/meson.build
index 33f44b0659..6284183756 100644
--- a/man/rules/meson.build
+++ b/man/rules/meson.build
@@ -1138,6 +1138,7 @@ manpages = [
'systemd-sysext-initrd.service',
'systemd-sysext.service'],
'ENABLE_SYSEXT'],
+ ['sysext.conf', '5', ['confext.conf'], 'ENABLE_SYSEXT'],
['systemd-system-update-generator', '8', [], ''],
['systemd-system.conf',
'5',
diff --git a/man/sysext.conf.xml b/man/sysext.conf.xml
new file mode 100644
index 0000000000..cdd88f2447
--- /dev/null
+++ b/man/sysext.conf.xml
@@ -0,0 +1,89 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
+
+<refentry id="sysext.conf" conditional='ENABLE_SYSEXT'
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>sysext.conf</title>
+ <productname>systemd</productname>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>sysext.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>sysext.conf</refname>
+ <refname>confext.conf</refname>
+ <refname>sysext.conf.d</refname>
+ <refname>confext.conf.d</refname>
+ <refpurpose>Configuration files for systemd-sysext</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <para><filename>/etc/systemd/sysext.conf</filename></para>
+ <para><filename>/etc/systemd/sysext.conf.d/*.conf</filename></para>
+ <para><filename>/run/systemd/sysext.conf</filename></para>
+ <para><filename>/run/systemd/sysext.conf.d/*.conf</filename></para>
+ <para><filename>/usr/lib/systemd/sysext.conf</filename></para>
+ <para><filename>/usr/lib/systemd/sysext.conf.d/*.conf</filename></para>
+ <para><filename>/etc/systemd/confext.conf</filename></para>
+ <para><filename>/etc/systemd/confext.conf.d/*.conf</filename></para>
+ <para><filename>/run/systemd/confext.conf</filename></para>
+ <para><filename>/run/systemd/confext.conf.d/*.conf</filename></para>
+ <para><filename>/usr/lib/systemd/confext.conf</filename></para>
+ <para><filename>/usr/lib/systemd/confext.conf.d/*.conf</filename></para>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>These configuration files control the behavior of
+ <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry> and
+ <citerefentry><refentrytitle>systemd-confext</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+ They are especially useful when needing to customize the behavior of the
+ respective extension service units.</para>
+ </refsect1>
+
+ <xi:include href="standard-conf.xml" xpointer="main-conf" />
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>The following options are understood in both the <literal>[Sysext]</literal> and
+ <literal>[Confext]</literal> sections:</para>
+
+ <refsect2>
+ <title>Section Options</title>
+
+ <variablelist class='config-directives'>
+ <varlistentry>
+ <term><varname>Mutable=</varname></term>
+ <listitem><para>Set the mutable mode for system extensions. Takes one of <literal>no</literal>,
+ <literal>yes</literal>, <literal>auto</literal>, <literal>import</literal>,
+ <literal>ephemeral</literal>, or <literal>ephemeral-import</literal>. For details about the modes,
+ see the <option>--mutable=</option> option in
+ <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+ Defaults to <literal>no</literal>.</para>
+
+ <xi:include href="version-info.xml" xpointer="v259"/>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para><simplelist type="inline">
+ <member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
+ <member><citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
+ <member><citerefentry><refentrytitle>systemd.syntax</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
+ </simplelist></para>
+ </refsect1>
+
+</refentry>
diff --git a/man/systemd-sysext.xml b/man/systemd-sysext.xml
index 3f60c85dba..6df2d94e9f 100644
--- a/man/systemd-sysext.xml
+++ b/man/systemd-sysext.xml
@@ -74,7 +74,12 @@
<para>System extension images are strictly read-only by default. On mutable host file systems,
<filename>/usr/</filename> and <filename>/opt/</filename> hierarchies become read-only while extensions
are merged, unless mutability is enabled. Mutability may be enabled via the <option>--mutable=</option>
- option; see "Mutability" below for more information.</para>
+ option and the <varname>Mutable=</varname> option in the configuration file;
+ see "Mutability" below for more information.</para>
+
+ <para>Various command options can be configured globally via configuration files. See
+ <citerefentry><refentrytitle>sysext.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for details.</para>
<para>System extensions are supposed to be purely additive, i.e. they are supposed to include only files
that do not exist in the underlying basic OS image. However, the underlying mechanism (overlayfs) also
@@ -491,6 +496,7 @@
<title>See Also</title>
<para><simplelist type="inline">
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
+ <member><citerefentry><refentrytitle>sysext.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>importctl</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
--
2.51.0

View File

@ -0,0 +1,50 @@
From 363c849b4faed27449a0e3ee41c302709aec0807 Mon Sep 17 00:00:00 2001
From: Emanuele Giuseppe Esposito <eesposit@redhat.com>
Date: Thu, 17 Jul 2025 10:16:24 -0400
Subject: [PATCH 3/4] sysext: support ImagePolicy global config option
Just as Mutable=, support ImagePolicy in systemd/{sysext/confext}.conf and
dropins in systemd/{sysext.confext}.conf.d/* configs.
---
man/sysext.conf.xml | 12 ++++++++++++
src/sysext/sysext.c | 1 +
2 files changed, 13 insertions(+)
diff --git a/man/sysext.conf.xml b/man/sysext.conf.xml
index cdd88f2447..f717b74426 100644
--- a/man/sysext.conf.xml
+++ b/man/sysext.conf.xml
@@ -73,6 +73,18 @@
<xi:include href="version-info.xml" xpointer="v259"/>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><varname>ImagePolicy=</varname></term>
+ <listitem><para>Set the image policy. Takes an image policy string as argument, as per
+ <citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
+ For details, see the <option>--image-policy=</option> option in
+ <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+ </para>
+
+ <xi:include href="version-info.xml" xpointer="v259"/>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect2>
</refsect1>
diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c
index 332fc55bb3..9656e975c4 100644
--- a/src/sysext/sysext.c
+++ b/src/sysext/sysext.c
@@ -157,6 +157,7 @@ static int parse_config_file(ImageClass image_class) {
const char *section = image_class == IMAGE_SYSEXT ? "SysExt" : "ConfExt";
const ConfigTableItem items[] = {
{ section, "Mutable", config_parse_mutable_mode, 0, &arg_mutable },
+ { section, "ImagePolicy", config_parse_image_policy, 0, &arg_image_policy },
{}
};
_cleanup_free_ char *config_file = NULL;
--
2.51.0

View File

@ -0,0 +1,183 @@
From 3498a462f517b024b3125e0bb79c8c6c54bb62c9 Mon Sep 17 00:00:00 2001
From: Kai Lueke <kailuke@microsoft.com>
Date: Thu, 11 Dec 2025 19:49:20 +0900
Subject: [PATCH] sysext: Fix config file support with --root=
Config files for --root= weren't picked up as expected because the
--root= flag got parsed after the config file.
Switch the order of config file and CLI flag parsing while letting the
CLI flags overwrite things set by the config files by tracking state
during parsing.
---
src/sysext/sysext.c | 38 +++++++++++++++-----
test/units/TEST-50-DISSECT.sysext.sh | 52 ++++++++++++++++++++++++++++
2 files changed, 82 insertions(+), 8 deletions(-)
diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c
index 9656e975c4..1fb0c72e48 100644
--- a/src/sysext/sysext.c
+++ b/src/sysext/sysext.c
@@ -92,8 +92,10 @@ static bool arg_no_reload = false;
static bool arg_always_refresh = false;
static int arg_noexec = -1;
static ImagePolicy *arg_image_policy = NULL;
+static bool arg_image_policy_set = false; /* Tracks initialization */
static bool arg_varlink = false;
static MutableMode arg_mutable = MUTABLE_NO;
+static bool arg_mutable_set = false; /* Tracks initialization */
/* Is set to IMAGE_CONFEXT when systemd is called with the confext functionality instead of the default */
static ImageClass arg_image_class = IMAGE_SYSEXT;
@@ -154,10 +156,13 @@ static int parse_mutable_mode(const char *p) {
static DEFINE_CONFIG_PARSE_ENUM(config_parse_mutable_mode, mutable_mode, MutableMode);
static int parse_config_file(ImageClass image_class) {
+ _cleanup_(image_policy_freep) ImagePolicy *config_image_policy = NULL;
+ MutableMode config_mutable = MUTABLE_NO;
const char *section = image_class == IMAGE_SYSEXT ? "SysExt" : "ConfExt";
+ const char *sections = image_class == IMAGE_SYSEXT ? "SysExt\0" : "ConfExt\0";
const ConfigTableItem items[] = {
- { section, "Mutable", config_parse_mutable_mode, 0, &arg_mutable },
- { section, "ImagePolicy", config_parse_image_policy, 0, &arg_image_policy },
+ { section, "Mutable", config_parse_mutable_mode, 0, &config_mutable },
+ { section, "ImagePolicy", config_parse_image_policy, 0, &config_image_policy },
{}
};
_cleanup_free_ char *config_file = NULL;
@@ -170,7 +175,7 @@ static int parse_config_file(ImageClass image_class) {
r = config_parse_standard_file_with_dropins_full(
arg_root,
config_file,
- image_class == IMAGE_SYSEXT ? "SysExt\0" : "ConfExt\0",
+ sections,
config_item_table_lookup, items,
CONFIG_PARSE_WARN,
/* userdata = */ NULL,
@@ -179,6 +184,17 @@ static int parse_config_file(ImageClass image_class) {
if (r < 0)
return r;
+ /* Because this runs after parse_argv we only overwrite when things aren't set yet. */
+ if (!arg_mutable_set) {
+ arg_mutable = config_mutable;
+ arg_mutable_set = true;
+ }
+
+ if (!arg_image_policy_set) {
+ arg_image_policy = TAKE_PTR(config_image_policy);
+ arg_image_policy_set = true;
+ }
+
return 0;
}
@@ -2794,6 +2810,9 @@ static int parse_argv(int argc, char *argv[]) {
r = parse_image_policy_argument(optarg, &arg_image_policy);
if (r < 0)
return r;
+ /* When the CLI flag is given we initialize even if NULL
+ * so that the config file entry won't overwrite it */
+ arg_image_policy_set = true;
break;
case ARG_NOEXEC:
@@ -2819,6 +2838,7 @@ static int parse_argv(int argc, char *argv[]) {
if (r < 0)
return log_error_errno(r, "Failed to parse argument to --mutable=: %s", optarg);
arg_mutable = r;
+ arg_mutable_set = true;
break;
case '?':
@@ -2871,11 +2891,6 @@ static int run(int argc, char *argv[]) {
arg_mutable = r;
}
- /* Parse configuration file */
- r = parse_config_file(arg_image_class);
- if (r < 0)
- log_warning_errno(r, "Failed to parse global config file, ignoring: %m");
-
/* Parse command line */
r = parse_argv(argc, argv);
if (r <= 0)
@@ -2888,6 +2903,13 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return log_error_errno(r, "Failed to parse environment variable: %m");
+ /* Parse configuration file after argv because it needs --root=.
+ * The config entries will not overwrite values set already by
+ * env/argv because we track initialization. */
+ r = parse_config_file(arg_image_class);
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse global config file, ignoring: %m");
+
if (arg_varlink) {
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL;
diff --git a/test/units/TEST-50-DISSECT.sysext.sh b/test/units/TEST-50-DISSECT.sysext.sh
index 6e64eea492..7a3146a0e7 100755
--- a/test/units/TEST-50-DISSECT.sysext.sh
+++ b/test/units/TEST-50-DISSECT.sysext.sh
@@ -1501,6 +1501,58 @@ run_systemd_sysext "$fake_root" unmerge
extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
)
+
+( init_trap
+: "Check config file support for --root="
+fake_root=${roots_dir:+"$roots_dir/config-file"}
+hierarchy=/opt
+extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy"
+
+[[ "$FSTYPE" == "fuseblk" ]] && exit 0
+if [ "$roots_dir" = "" ]; then
+ echo >&2 "Skipping test when --root= is not used"
+ exit 0
+fi
+
+prepare_root "$fake_root" "$hierarchy"
+prepare_extension_image_raw "$fake_root" "$hierarchy"
+prepare_extension_mutable_dir "$extension_data_dir"
+prepare_read_only_hierarchy "$fake_root" "$hierarchy"
+
+mkdir -p "$fake_root/etc/systemd/"
+{ echo "[SysExt]" ; echo "Mutable=auto" ; } > "$fake_root/etc/systemd/sysext.conf"
+# Config file should be picked up with --root= set
+run_systemd_sysext "$fake_root" merge
+MNTOPT=$(findmnt "$fake_root$hierarchy" --first-only --direction backward --raw --noheadings -o VFS-OPTIONS | grep -o rw || true)
+if [ "$MNTOPT" != "rw" ]; then
+ echo >&2 "Merge did not pick up mutable setting from config file"
+ exit 1
+fi
+extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -u
+run_systemd_sysext "$fake_root" unmerge
+
+# CLI arg should be able to overwrite config file
+run_systemd_sysext "$fake_root" merge --mutable=no
+MNTOPT=$(findmnt "$fake_root$hierarchy" --first-only --direction backward --raw --noheadings -o VFS-OPTIONS | grep -o ro || true)
+if [ "$MNTOPT" != "ro" ]; then
+ echo >&2 "Merge did not pick up CLI arg to overwrite mutable setting from config file"
+ exit 1
+fi
+extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
+run_systemd_sysext "$fake_root" unmerge
+
+{ echo "[SysExt]" ; echo "ImagePolicy=root=signed+absent:usr=signed+absent" ; } > "$fake_root/etc/systemd/sysext.conf"
+# Config file should be picked up with --root= set
+if run_systemd_sysext "$fake_root" merge; then
+ echo >&2 "Merge did not fail with strict image policy in config file"
+ exit 1
+fi
+# CLI arg should be able to overwrite config file
+run_systemd_sysext "$fake_root" merge --image-policy="*"
+extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
+run_systemd_sysext "$fake_root" unmerge
+)
+
} # End of run_sysext_tests
--
2.52.0

View File

@ -10,7 +10,7 @@ if [[ ${PV} == 9999 ]]; then
EGIT_REPO_URI="https://github.com/flatcar/bootengine.git"
inherit git-r3
else
EGIT_VERSION="7d9895ce55617b18a78294975197975ac17b5bc3" # flatcar-master
EGIT_VERSION="626047d0af61a9351e91d885b945b0f0f49d0e55" # TODO: flatcar-master
SRC_URI="https://github.com/flatcar/bootengine/archive/${EGIT_VERSION}.tar.gz -> ${PN}-${EGIT_VERSION}.tar.gz"
S="${WORKDIR}/${PN}-${EGIT_VERSION}"
KEYWORDS="amd64 arm arm64 x86"