From 76a782978aa276369f3aee1bae23d85a45b4d23a Mon Sep 17 00:00:00 2001 From: Yuming He Date: Thu, 25 Jun 2026 01:09:36 +0800 Subject: [PATCH] fix(container): preserve submount propagation across pivot_root MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In do_pivot_root, the post-pivot call do_propagation_mount(old_root, MS_REC | MS_PRIVATE) used old_root.current_path() (a readlink of /proc/self/fd/) as the mount target. old_root is opened on the host root mount *before* pivot_root(".", "."); after the pivot that readlink resolves to "/" (i.e. the *new* root X), not to the old root. The recursive MS_PRIVATE therefore landed on X and its entire subtree, clobbering the propagation type that /run/media, /sys, etc. had just inherited from their host sources during do_mounts. Root cause: pivot_root(".", ".") makes the new rootfs X the namespace root and parks the old root at the cwd. A subsequent readlink of the pre-pivot old_root fd returns "/" because the detached old root mount is reported through the new root, so the MS_REC|MS_PRIVATE recursively re-privatized X's subtree instead of the old root's. Fix: drop the fd-based target and use the cwd literal "." — fchdir(old_root) has just set cwd to the old root, so "." unambiguously refers to it. The recursive private now only touches the old root's subtree (host-side mounts), leaving the new root and its inherited-slave submounts untouched. Also open new_root with O_PATH instead of O_RDONLY: fchdir accepts O_PATH descriptors since Linux 3.5 and pivot_root(".", ".") operates on the cwd, so a read-write descriptor is unnecessary. Signed-off-by: Yuming He --- src/linyaps_box/container.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/linyaps_box/container.cpp b/src/linyaps_box/container.cpp index 8e3e86e..1f5e0ea 100644 --- a/src/linyaps_box/container.cpp +++ b/src/linyaps_box/container.cpp @@ -1428,7 +1428,7 @@ void do_pivot_root(const linyaps_box::container &container, LINYAPS_BOX_DEBUG() << "start pivot root"; LINYAPS_BOX_DEBUG() << linyaps_box::utils::inspect_fds(); auto old_root = linyaps_box::utils::open("/", O_DIRECTORY | O_PATH | O_CLOEXEC); - auto new_root = linyaps_box::utils::open(rootfs.c_str(), O_DIRECTORY | O_RDONLY | O_CLOEXEC); + auto new_root = linyaps_box::utils::open(rootfs.c_str(), O_DIRECTORY | O_PATH | O_CLOEXEC); auto old_root_stat = linyaps_box::utils::statfs(old_root); LINYAPS_BOX_DEBUG() << "Pivot root old root: " << dump(old_root_stat.f_flags); @@ -1471,8 +1471,12 @@ void do_pivot_root(const linyaps_box::container &container, throw std::system_error(errno, std::system_category(), "fchdir"); } - // make sure that umount event couldn't propagate to host - container_ns::do_propagation_mount(old_root, MS_REC | MS_PRIVATE); + // make sure that umount event couldn't propagate to host. + // target the old root via cwd "." (set by fchdir(old_root) above); do NOT use + // old_root.current_path(); after pivot_root(".", ".") the old_root fd's readlink + // resolves to "/" (= new root X), which would recursively privatize X and all + // submounts (e.g. /run/media, /sys), overriding their inherited propagation. + syscall_mount(nullptr, ".", nullptr, MS_REC | MS_PRIVATE, nullptr); // umount old root ret = umount2(".", MNT_DETACH);