Skip to content

Commit fcfc8eb

Browse files
authored
fix(vfs): snapshot recursive bind topology (#2123)
Hold the mount lifecycle and dentry topology snapshot across source validation, detached root preparation, and recursive child cloning. This aligns the operation with Linux copy_tree semantics and prevents root and descendants from observing different mount states. Keep filesystem metadata checks outside the topology critical section so FUSE requests cannot block global mount and rename progress. Add regression coverage for unbindable subtree filtering and colliding parent-side inode numbers across independent FUSE instances. Fixes: #2101 Signed-off-by: longjin <longjin@dragonos.org>
1 parent 72ebe13 commit fcfc8eb

4 files changed

Lines changed: 296 additions & 95 deletions

File tree

kernel/src/filesystem/vfs/mount/mod.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2798,7 +2798,7 @@ impl MountFSInode {
27982798
super_block_state: Option<Arc<SuperBlockState>>,
27992799
bind_source: Option<&Arc<MountFS>>,
28002800
) -> Result<Arc<MountFS>, SystemError> {
2801-
// Linux do_add_mount: the parent mount point must belong to the current mount namespace.
2801+
// Preserve do_add_mount validation order before invoking a filesystem.
28022802
let current_mntns = ProcessManager::current_mntns();
28032803
if !self.mount_fs.is_belongs_to_mntns(&current_mntns) {
28042804
return Err(SystemError::EINVAL);
@@ -2812,6 +2812,38 @@ impl MountFSInode {
28122812
return Err(SystemError::ENOTDIR);
28132813
}
28142814

2815+
self.prepare_subtree_with_root_dentry_prevalidated(
2816+
inner_fs,
2817+
root_inner_inode,
2818+
root_dentry,
2819+
mount_flags,
2820+
super_block_state,
2821+
bind_source,
2822+
)
2823+
}
2824+
2825+
/// Prepare a detached mount after the caller has validated that the source
2826+
/// root and destination mountpoint have compatible, immutable inode types.
2827+
///
2828+
/// Unlike [`Self::prepare_subtree_with_root_dentry`], this entry point does
2829+
/// not call into the underlying filesystem. It is suitable for bind-mount
2830+
/// topology snapshot sections, where a FUSE `metadata()` request could
2831+
/// otherwise wait on a daemon that needs the dentry topology write lock.
2832+
pub(crate) fn prepare_subtree_with_root_dentry_prevalidated(
2833+
&self,
2834+
inner_fs: Arc<dyn FileSystem>,
2835+
root_inner_inode: Arc<dyn IndexNode>,
2836+
root_dentry: Option<Arc<VfsDentry>>,
2837+
mount_flags: MountFlags,
2838+
super_block_state: Option<Arc<SuperBlockState>>,
2839+
bind_source: Option<&Arc<MountFS>>,
2840+
) -> Result<Arc<MountFS>, SystemError> {
2841+
// Linux do_add_mount: the parent mount point must belong to the current mount namespace.
2842+
let current_mntns = ProcessManager::current_mntns();
2843+
if !self.mount_fs.is_belongs_to_mntns(&current_mntns) {
2844+
return Err(SystemError::EINVAL);
2845+
}
2846+
28152847
// Keep detached construction private. The destination parent's shared
28162848
// state is revalidated and any new group is allocated atomically at
28172849
// publication; a bind source may install an existing group below.

kernel/src/filesystem/vfs/syscall/sys_mount.rs

Lines changed: 62 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -593,92 +593,64 @@ fn do_bind_mount(
593593
.downcast_arc::<MountFSInode>()
594594
.ok_or(SystemError::EINVAL)?;
595595

596-
// Get the source's filesystem
597-
let source_fs = source_inode.fs();
596+
let source_mfs = source_inode
597+
.fs()
598+
.downcast_arc::<MountFS>()
599+
.ok_or(SystemError::EINVAL)?;
598600

599-
// Check if source is on a MountFS
600-
let source_mfs = source_fs.clone().downcast_arc::<MountFS>();
601+
let target_mountpoint = target_inode
602+
.clone()
603+
.downcast_arc::<crate::filesystem::vfs::mount::MountFSInode>()
604+
.ok_or(SystemError::EINVAL)?;
605+
let target_mount_fs = target_mountpoint.mount_fs();
606+
let current_mntns = ProcessManager::current_mntns();
601607

602-
// The source mount must belong to the current mount namespace.
603-
if let Some(ref mfs) = source_mfs {
604-
let current_mntns = ProcessManager::current_mntns();
605-
if !mfs.is_belongs_to_mntns(&current_mntns) {
608+
// Linux holds namespace_lock across check_mnt(), clone_mnt(root), and
609+
// copy_tree(children). Take one mount+dentry snapshot for the equivalent
610+
// source validation and complete detached clone so root and descendants
611+
// cannot observe different lifecycle or propagation states.
612+
let target_mfs = with_topology_snapshot(|| {
613+
if !source_mfs.is_live()
614+
|| !source_mfs.is_belongs_to_mntns(&current_mntns)
615+
|| !target_mount_fs.is_live()
616+
|| !target_mount_fs.is_belongs_to_mntns(&current_mntns)
617+
{
606618
return Err(SystemError::EINVAL);
607619
}
608-
}
609-
610-
// Check if source is unbindable - if so, reject the bind mount
611-
if let Some(ref mfs) = source_mfs {
612-
if mfs.propagation().is_unbindable() {
620+
if source_mfs.propagation().is_unbindable() {
613621
return Err(SystemError::EINVAL);
614622
}
615623
if !flags.contains(MountFlags::REC)
616-
&& has_locked_children_in_view(mfs, &source_mount_inode)?
624+
&& has_locked_children_in_view_locked(&source_mfs, &source_mount_inode)?
617625
{
618626
return Err(SystemError::EINVAL);
619627
}
620-
}
621-
622-
// Clone source_mfs for recursive bind mount (need to keep it for later use)
623-
let source_mfs_for_recursive = source_mfs.clone();
624-
625-
let root_inner_inode = if is_mountpoint_root(&source_inode) {
626-
source_mfs
627-
.as_ref()
628-
.map(|mfs| mfs.root_inner_inode())
629-
.unwrap_or_else(|| source_inode.clone())
630-
} else {
631-
source_inode
632-
.clone()
633-
.downcast_arc::<MountFSInode>()
634-
.map(|inode| inode.underlying_inode())
635-
.unwrap_or_else(|| source_inode.clone())
636-
};
637-
638-
// Get the inner filesystem for mounting while preserving the source subtree root.
639-
let inner_fs = source_mfs
640-
.map(|mfs| mfs.inner_filesystem())
641-
.unwrap_or(source_fs);
642-
643-
// do_loopback: the target mount point must belong to the current mount namespace.
644-
let current_mntns = ProcessManager::current_mntns();
645-
let target_mount_fs = target_inode
646-
.fs()
647-
.downcast_arc::<MountFS>()
648-
.ok_or(SystemError::EINVAL)?;
649-
if !target_mount_fs.is_belongs_to_mntns(&current_mntns) {
650-
return Err(SystemError::EINVAL);
651-
}
652628

653-
let target_mountpoint = target_inode
654-
.clone()
655-
.downcast_arc::<crate::filesystem::vfs::mount::MountFSInode>()
656-
.ok_or(SystemError::EINVAL)?;
657-
let target_mfs = target_mountpoint.prepare_subtree_with_root_dentry(
658-
inner_fs,
659-
root_inner_inode,
660-
Some(source_mount_inode.shared_dentry()),
661-
source_mfs_for_recursive
662-
.as_ref()
663-
.map(|mount| mount.mount_flags())
664-
.unwrap_or_else(MountFlags::empty),
665-
source_mfs_for_recursive
666-
.as_ref()
667-
.map(|mfs| mfs.super_block_state()),
668-
source_mfs_for_recursive.as_ref(),
669-
)?;
670-
target_mfs.set_mount_source(Some(source_path.clone()));
671-
672-
// Build the complete recursive clone while detached. The root edge is the
673-
// publication point, so lookup never observes a half-copied bind tree.
674-
if flags.contains(MountFlags::REC) {
675-
if let Some(ref mfs) = source_mfs_for_recursive {
676-
if let Err(e) = do_recursive_bind_mount(mfs, &target_mfs) {
629+
let root_inner_inode = if is_mountpoint_root(&source_inode) {
630+
source_mfs.root_inner_inode()
631+
} else {
632+
source_mount_inode.underlying_inode()
633+
};
634+
let target_mfs = target_mountpoint.prepare_subtree_with_root_dentry_prevalidated(
635+
source_mfs.inner_filesystem(),
636+
root_inner_inode,
637+
Some(source_mount_inode.shared_dentry()),
638+
source_mfs.mount_flags(),
639+
Some(source_mfs.super_block_state()),
640+
Some(&source_mfs),
641+
)?;
642+
target_mfs.set_mount_source(Some(source_path.clone()));
643+
644+
// The root edge is the publication point, so lookup never observes a
645+
// half-copied recursive bind tree.
646+
if flags.contains(MountFlags::REC) {
647+
if let Err(error) = do_recursive_bind_mount_locked(&source_mfs, &target_mfs) {
677648
MountFS::deactivate_disconnected_subtree(&target_mfs);
678-
return Err(e);
649+
return Err(error);
679650
}
680651
}
681-
}
652+
Ok(target_mfs)
653+
})?;
682654

683655
if let Err(error) = target_mountpoint.publish_prepared_subtree(&target_mfs) {
684656
MountFS::deactivate_disconnected_subtree(&target_mfs);
@@ -690,26 +662,25 @@ fn do_bind_mount(
690662

691663
/// Linux rejects a non-recursive bind when it would uncover locked child
692664
/// mounts below the selected source dentry (has_locked_children()).
693-
fn has_locked_children_in_view(
665+
/// Caller holds the mount+dentry topology snapshot.
666+
fn has_locked_children_in_view_locked(
694667
source_mount: &Arc<MountFS>,
695668
source_root: &Arc<MountFSInode>,
696669
) -> Result<bool, SystemError> {
697-
with_topology_snapshot(|| {
698-
let mut pending = source_mount.mount_children();
699-
while let Some(child) = pending.pop() {
700-
let mountpoint = child.self_mountpoint().ok_or(SystemError::EINVAL)?;
701-
if mountpoint
702-
.relative_path_from_snapshot(source_root)?
703-
.is_none()
704-
{
705-
continue;
706-
}
707-
if child.is_locked() {
708-
return Ok(true);
709-
}
670+
let mut pending = source_mount.mount_children();
671+
while let Some(child) = pending.pop() {
672+
let mountpoint = child.self_mountpoint().ok_or(SystemError::EINVAL)?;
673+
if mountpoint
674+
.relative_path_from_snapshot(source_root)?
675+
.is_none()
676+
{
677+
continue;
678+
}
679+
if child.is_locked() {
680+
return Ok(true);
710681
}
711-
Ok(false)
712-
})
682+
}
683+
Ok(false)
713684
}
714685

715686
/// Change the propagation type of a mount point.
@@ -829,13 +800,12 @@ fn do_move_mount(
829800
/// # Returns
830801
/// * `Ok(())` on success
831802
/// * `Err(SystemError)` on failure
832-
fn do_recursive_bind_mount(
803+
///
804+
/// Caller holds the mount+dentry topology snapshot for the complete detached copy.
805+
fn do_recursive_bind_mount_locked(
833806
source_mfs: &Arc<MountFS>,
834807
target_mfs: &Arc<MountFS>,
835808
) -> Result<(), SystemError> {
836-
// Mount edge mutations use the same lock. Holding it for the complete
837-
// detached copy gives MS_BIND|MS_REC one coherent source-tree snapshot.
838-
let _topology = MOUNT_LIFECYCLE_LOCK.lock();
839809
let mut pending = vec![(source_mfs.clone(), target_mfs.clone())];
840810
while let Some((source_parent, target_parent)) = pending.pop() {
841811
for source_child in source_parent.mount_children() {

0 commit comments

Comments
 (0)