Skip to content

discover-image: Follow symlinks in a given root#39843

Merged
yuwata merged 3 commits intosystemd:mainfrom
pothos:discover-image-symlinks
Jan 30, 2026
Merged

discover-image: Follow symlinks in a given root#39843
yuwata merged 3 commits intosystemd:mainfrom
pothos:discover-image-symlinks

Conversation

@pothos
Copy link
Copy Markdown
Contributor

@pothos pothos commented Nov 21, 2025

This is needed to set up extension images from the initrd with systemd-sysext --root=/sysroot/ merge.

  • 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.
    Remove the openat call and let pin_choice do the work with the chase
    helper function to resolve the symlink in the given root.

  • 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.

  • 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.

  • 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.

@github-actions github-actions bot added util-lib tests sysext please-review PR is ready for (re-)review by a maintainer labels Nov 21, 2025
@pothos pothos force-pushed the discover-image-symlinks branch from a756479 to 0fe08a8 Compare December 10, 2025 16:02
@pothos
Copy link
Copy Markdown
Contributor Author

pothos commented Dec 11, 2025

This also changes the reported path in systemd-sysext list to be resolved. This is a change in behavior that I think makes sense because, e.g., for a sysupdate-managed "current" symlink this will then show which image version this is. But we could also change the image struct to have two have two paths, the real one which is resolved and the display ("entry"?) path which is the /etc/extensions/ext.raw entry or wherever the image entry was picked up from.

@pothos pothos force-pushed the discover-image-symlinks branch 2 times, most recently from 12643ff to 5a6cc4e Compare December 13, 2025 15:31
@yuwata yuwata added needs-rebase and removed please-review PR is ready for (re-)review by a maintainer labels Dec 15, 2025
pothos added a commit to flatcar/scripts that referenced this pull request Dec 16, 2025
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 flatcar/init#138 and
flatcar/bootengine#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:

systemd/systemd#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.

systemd/systemd#39980 for
sysext-Create-mutable-directory-with-the-right-mode
sysext-Skip-refresh-if-no-changes-are-found

systemd/systemd#39991 for
sysext-Get-verity-user-certs-from-given-root

systemd/systemd#40063 for
sysext-Fix-config-file-support-with-root
which relies on systemd/systemd#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>
@pothos pothos force-pushed the discover-image-symlinks branch from 5a6cc4e to 620e8e8 Compare December 16, 2025 15:10
@github-actions github-actions bot added please-review PR is ready for (re-)review by a maintainer and removed needs-rebase labels Dec 16, 2025
pothos added a commit to flatcar/scripts that referenced this pull request Dec 17, 2025
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 flatcar/init#138 and
flatcar/bootengine#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:

systemd/systemd#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.

systemd/systemd#39980 for
sysext-Create-mutable-directory-with-the-right-mode
sysext-Skip-refresh-if-no-changes-are-found

systemd/systemd#39991 for
sysext-Get-verity-user-certs-from-given-root

systemd/systemd#40063 for
sysext-Fix-config-file-support-with-root
which relies on systemd/systemd#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>
pothos added a commit to flatcar/scripts that referenced this pull request Dec 18, 2025
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 flatcar/init#138 and
flatcar/bootengine#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:

systemd/systemd#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.

systemd/systemd#39980 for
sysext-Create-mutable-directory-with-the-right-mode
sysext-Skip-refresh-if-no-changes-are-found

systemd/systemd#39991 for
sysext-Get-verity-user-certs-from-given-root

systemd/systemd#40063 for
sysext-Fix-config-file-support-with-root
which relies on systemd/systemd#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>
pothos added a commit to flatcar/scripts that referenced this pull request Dec 18, 2025
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 flatcar/init#138 and
flatcar/bootengine#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:

systemd/systemd#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.

systemd/systemd#39980 for
sysext-Create-mutable-directory-with-the-right-mode
sysext-Skip-refresh-if-no-changes-are-found

systemd/systemd#39991 for
sysext-Get-verity-user-certs-from-given-root

systemd/systemd#40063 for
sysext-Fix-config-file-support-with-root
which relies on systemd/systemd#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>
@pothos pothos force-pushed the discover-image-symlinks branch from 620e8e8 to 51a1ab5 Compare December 23, 2025 07:24
@yuwata yuwata added reviewed/needs-rework 🔨 PR has been reviewed and needs another round of reworks and removed please-review PR is ready for (re-)review by a maintainer labels Jan 4, 2026
@pothos pothos force-pushed the discover-image-symlinks branch from 51a1ab5 to 557e97a Compare January 11, 2026 14:44
@github-actions github-actions bot added please-review PR is ready for (re-)review by a maintainer and removed reviewed/needs-rework 🔨 PR has been reviewed and needs another round of reworks labels Jan 11, 2026
@pothos pothos force-pushed the discover-image-symlinks branch from 8f1e542 to 5dafa63 Compare January 19, 2026 07:15
@github-actions github-actions bot added please-review PR is ready for (re-)review by a maintainer and removed reviewed/needs-rework 🔨 PR has been reviewed and needs another round of reworks labels Jan 19, 2026
@pothos pothos force-pushed the discover-image-symlinks branch from 5dafa63 to f40a4a9 Compare January 25, 2026 20:26
@daandemeyer daandemeyer added good-to-merge/waiting-for-ci 👍 PR is good to merge, but CI hasn't passed at time of review. Please merge if you see CI has passed and removed please-review PR is ready for (re-)review by a maintainer labels Jan 25, 2026
@pothos pothos force-pushed the discover-image-symlinks branch 2 times, most recently from a03d8e6 to 90983a2 Compare January 25, 2026 20:50
_cleanup_free_ char *search_path = NULL;

r = chase_and_opendir(*s, root, CHASE_PREFIX_ROOT, &resolved, &d);
r = chase_and_opendirat(rfd, *s, CHASE_AT_RESOLVE_IN_ROOT, &search_path, &d);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm? are you sure this actually works? chaseat() needs to be invoked with a path under root. It won't prefix root automatically. only the symlinks are resolved under rootfd...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chase() needs to be invoked with a path under root. chaseat() doesn't ;)

Copy link
Copy Markdown
Member

@yuwata yuwata left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.
(Still I'd like to drop unnecessary free, but OK.)

@yuwata yuwata added good-to-merge/with-minor-suggestions and removed good-to-merge/waiting-for-ci 👍 PR is good to merge, but CI hasn't passed at time of review. Please merge if you see CI has passed labels Jan 28, 2026
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.
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.
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.
@pothos pothos force-pushed the discover-image-symlinks branch from 90983a2 to da6d189 Compare January 28, 2026 11:25
@yuwata yuwata added good-to-merge/waiting-for-ci 👍 PR is good to merge, but CI hasn't passed at time of review. Please merge if you see CI has passed and removed good-to-merge/with-minor-suggestions labels Jan 28, 2026
@yuwata yuwata merged commit 43b6eb0 into systemd:main Jan 30, 2026
51 of 57 checks passed
@github-actions github-actions bot removed the good-to-merge/waiting-for-ci 👍 PR is good to merge, but CI hasn't passed at time of review. Please merge if you see CI has passed label Jan 30, 2026
@pothos
Copy link
Copy Markdown
Contributor Author

pothos commented Jan 31, 2026

Thanks everybody :)

@pothos pothos deleted the discover-image-symlinks branch January 31, 2026 22:12
pothos added a commit to flatcar/scripts that referenced this pull request Feb 2, 2026
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 flatcar/init#138 and
flatcar/bootengine#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:

systemd/systemd#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.

systemd/systemd#39980 for
sysext-Create-mutable-directory-with-the-right-mode
sysext-Skip-refresh-if-no-changes-are-found

systemd/systemd#39991 for
sysext-Get-verity-user-certs-from-given-root

systemd/systemd#40063 for
sysext-Fix-config-file-support-with-root
which relies on systemd/systemd#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>
pothos added a commit to flatcar/scripts that referenced this pull request Feb 28, 2026
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 flatcar/init#138 and
flatcar/bootengine#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:

systemd/systemd#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.

systemd/systemd#39980 for
sysext-Create-mutable-directory-with-the-right-mode
sysext-Skip-refresh-if-no-changes-are-found

systemd/systemd#39991 for
sysext-Get-verity-user-certs-from-given-root

systemd/systemd#40063 for
sysext-Fix-config-file-support-with-root
which relies on systemd/systemd#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>
pothos added a commit to flatcar/scripts that referenced this pull request Feb 28, 2026
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 flatcar/init#138 and
flatcar/bootengine#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:

systemd/systemd#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.

systemd/systemd#39980 for
sysext-Create-mutable-directory-with-the-right-mode
sysext-Skip-refresh-if-no-changes-are-found

systemd/systemd#39991 for
sysext-Get-verity-user-certs-from-given-root

systemd/systemd#40063 for
sysext-Fix-config-file-support-with-root
which relies on systemd/systemd#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>
pothos added a commit to flatcar/scripts that referenced this pull request Mar 3, 2026
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 flatcar/init#138 and
flatcar/bootengine#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:

systemd/systemd#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.

systemd/systemd#39980 for
sysext-Create-mutable-directory-with-the-right-mode
sysext-Skip-refresh-if-no-changes-are-found

systemd/systemd#39991 for
sysext-Get-verity-user-certs-from-given-root

systemd/systemd#40063 for
sysext-Fix-config-file-support-with-root
which relies on systemd/systemd#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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

5 participants