]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core/exec-invoke: chdir("/") after chroot in apply_root_directory
authorChris Mason <clm@meta.com>
Fri, 22 May 2026 15:49:27 +0000 (08:49 -0700)
committerLennart Poettering <lennart@amutable.com>
Fri, 22 May 2026 20:18:29 +0000 (22:18 +0200)
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>
src/core/exec-invoke.c

index 4f935c1439b954a5d83ee7ce8124ddf62fa26633..183442aef3c07d6ff0276c213c942586da052306 100644 (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;
 }