core/exec-invoke: chdir("/") after chroot in apply_root_directory

apply_root_directory() calls chroot() but never pairs it with a
chdir("/"). Moving cwd into the new root is left to the subsequent
apply_working_directory() call. That delegation breaks when the unit
sets WorkingDirectory=-/path (working_directory_missing_ok):

    apply_root_directory()              apply_working_directory()
      chroot(new_root)        -->         chdir(wd) fails (ENOENT)
      return 0                            return 0  /* missing_ok */

chroot(2) changes only the process root, not cwd. With cwd still
resolving to the pre-chroot host dentry, the process runs with the
kernel root pointing at the new root while relative-path accesses
and /proc/self/cwd reach paths outside RootDirectory=.

Only the chroot-only branch (exec_needs_mount_namespace() == false)
is affected. The mount-namespace branch uses pivot_root(), which
restructures the mount tree and makes the pre-chroot dentry
unreachable.

Fix by issuing chdir("/") immediately after the successful chroot()
in apply_root_directory() and propagating any failure with
EXIT_CHROOT, matching the standard chroot+chdir idiom and making
the chroot self-contained regardless of how
apply_working_directory() later handles a missing directory.

Assisted-by: kres (claude-opus-4-7)
Signed-off-by: Chris Mason <clm@meta.com>
This commit is contained in:
Chris Mason
2026-05-22 22:18:29 +02:00
committed by Lennart Poettering
parent 700d8e73d4
commit 4e02162a6e
+11 -1
View File
@@ -4133,12 +4133,22 @@ static int apply_root_directory(
assert(exit_status);
if (params->flags & EXEC_APPLY_CHROOT)
if (!needs_mount_ns && context->root_directory)
if (!needs_mount_ns && context->root_directory) {
if (chroot(runtime->ephemeral_copy ?: context->root_directory) < 0) {
*exit_status = EXIT_CHROOT;
return -errno;
}
/* chroot(2) changes only the process root, not cwd. Move cwd into the new root
* immediately so that a subsequent apply_working_directory() failure that is
* silently ignored via working_directory_missing_ok cannot leave cwd pointing
* at a pre-chroot dentry outside the new root. */
if (chdir("/") < 0) {
*exit_status = EXIT_CHROOT;
return -errno;
}
}
return 0;
}