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