@@ -30,6 +30,7 @@ use crate::{
3030 prepare_mount_propagation_locked, propagate_umount_sources,
3131 propagation_umount_busy, MountPropagation ,
3232 } ,
33+ user_namespace:: UserNamespace ,
3334 } ,
3435 ProcessManager ,
3536 } ,
@@ -45,7 +46,7 @@ use core::{
4546 cell:: RefCell ,
4647 fmt:: Debug ,
4748 hash:: Hash ,
48- sync:: atomic:: { compiler_fence, AtomicBool , AtomicUsize , Ordering } ,
49+ sync:: atomic:: { compiler_fence, AtomicBool , AtomicU32 , AtomicUsize , Ordering } ,
4950} ;
5051use hashbrown:: HashMap ;
5152use ida:: IdAllocator ;
@@ -282,6 +283,23 @@ impl MountFlags {
282283 }
283284}
284285
286+ bitflags ! {
287+ /// Internal per-mount locks corresponding to Linux `MNT_LOCK_*` flags.
288+ ///
289+ /// These are deliberately separate from userspace-visible `MS_*` flags:
290+ /// topology locking and attribute locking have different lifetimes. In
291+ /// particular, a mount propagated across a user-namespace boundary may
292+ /// have its topology lock cleared while retaining all attribute locks.
293+ struct MountLockFlags : u32 {
294+ const TOPOLOGY = 1 << 0 ;
295+ const ATIME = 1 << 1 ;
296+ const READONLY = 1 << 2 ;
297+ const NODEV = 1 << 3 ;
298+ const NOSUID = 1 << 4 ;
299+ const NOEXEC = 1 << 5 ;
300+ }
301+ }
302+
285303pub ( crate ) fn append_comma_options ( base : & mut String , extra : String ) {
286304 if extra. is_empty ( ) {
287305 return ;
@@ -357,8 +375,8 @@ pub struct MountFS {
357375 mount_flags : RwSem < MountFlags > ,
358376 super_block_state : Arc < SuperBlockState > ,
359377 mount_source : RwSem < Option < String > > ,
360- /// Internal MNT_LOCKED equivalent ; never exposed as a userspace MS_* bit .
361- locked : AtomicBool ,
378+ /// Internal `MNT_LOCK_*` state ; never exposed as userspace ` MS_*` bits .
379+ mount_locks : AtomicU32 ,
362380 lifecycle : Mutex < MountLifecycle > ,
363381}
364382
@@ -520,6 +538,9 @@ unsafe impl Sync for MountExternalGuard {}
520538
521539#[ derive( Debug ) ]
522540pub struct SuperBlockState {
541+ /// User namespace that owns this superblock, matching Linux `s_user_ns`.
542+ /// Bind mounts and mount-namespace copies retain the same owner.
543+ owner_user_ns : Arc < UserNamespace > ,
523544 flags : RwSem < MountFlags > ,
524545 write_count : AtomicUsize ,
525546 wb_error : ErrSeq ,
@@ -696,6 +717,7 @@ struct MountStateInit {
696717impl SuperBlockState {
697718 pub fn new ( flags : MountFlags ) -> Self {
698719 Self {
720+ owner_user_ns : ProcessManager :: current_user_ns ( ) ,
699721 flags : RwSem :: new ( flags & MountFlags :: SB_SETTABLE_MASK ) ,
700722 write_count : AtomicUsize :: new ( 0 ) ,
701723 wb_error : ErrSeq :: new ( ) ,
@@ -712,6 +734,10 @@ impl SuperBlockState {
712734 }
713735 }
714736
737+ pub fn owner_user_ns ( & self ) -> & Arc < UserNamespace > {
738+ & self . owner_user_ns
739+ }
740+
715741 fn activate_mount ( & self , construction_reserved : bool ) -> Result < ( ) , SystemError > {
716742 let mut lifecycle = self . lifecycle . lock ( ) ;
717743 if lifecycle. state != SuperBlockLifecycleState :: Active {
@@ -1117,7 +1143,7 @@ impl MountFS {
11171143 mount_flags : RwSem :: new ( mount_flags) ,
11181144 super_block_state : state_init. super_block_state ,
11191145 mount_source : RwSem :: new ( state_init. mount_source ) ,
1120- locked : AtomicBool :: new ( false ) ,
1146+ mount_locks : AtomicU32 :: new ( 0 ) ,
11211147 lifecycle : Mutex :: new ( MountLifecycle {
11221148 state : MountLifecycleState :: Constructing ,
11231149 external_pins : 0 ,
@@ -1162,7 +1188,7 @@ impl MountFS {
11621188 mount_flags : RwSem :: new ( self . mount_flags ( ) ) ,
11631189 super_block_state : self . super_block_state . clone ( ) ,
11641190 mount_source : RwSem :: new ( mount_source) ,
1165- locked : AtomicBool :: new ( self . locked . load ( Ordering :: Acquire ) ) ,
1191+ mount_locks : AtomicU32 :: new ( self . mount_locks . load ( Ordering :: Acquire ) ) ,
11661192 lifecycle : Mutex :: new ( MountLifecycle {
11671193 state : MountLifecycleState :: Constructing ,
11681194 external_pins : 0 ,
@@ -1793,15 +1819,61 @@ impl MountFS {
17931819 }
17941820
17951821 pub ( crate ) fn lock_mount ( & self ) {
1796- self . locked . store ( true , Ordering :: Release ) ;
1822+ self . mount_locks
1823+ . fetch_or ( MountLockFlags :: TOPOLOGY . bits ( ) , Ordering :: Release ) ;
17971824 }
17981825
17991826 pub ( crate ) fn unlock_mount ( & self ) {
1800- self . locked . store ( false , Ordering :: Release ) ;
1827+ self . mount_locks
1828+ . fetch_and ( !MountLockFlags :: TOPOLOGY . bits ( ) , Ordering :: Release ) ;
18011829 }
18021830
18031831 pub ( crate ) fn is_locked ( & self ) -> bool {
1804- self . locked . load ( Ordering :: Acquire )
1832+ self . mount_locks . load ( Ordering :: Acquire ) & MountLockFlags :: TOPOLOGY . bits ( ) != 0
1833+ }
1834+
1835+ /// Linux `lock_mnt_tree()` semantics for mounts copied across a user
1836+ /// namespace boundary. Attribute locks snapshot per-mount flags only;
1837+ /// superblock flags have separate ownership and reconfiguration rules.
1838+ pub ( crate ) fn lock_cross_user_mount ( & self ) {
1839+ let mount_flags = self . mount_flags ( ) ;
1840+ let mut locks = MountLockFlags :: TOPOLOGY | MountLockFlags :: ATIME ;
1841+ if mount_flags. contains ( MountFlags :: RDONLY ) {
1842+ locks. insert ( MountLockFlags :: READONLY ) ;
1843+ }
1844+ if mount_flags. contains ( MountFlags :: NODEV ) {
1845+ locks. insert ( MountLockFlags :: NODEV ) ;
1846+ }
1847+ if mount_flags. contains ( MountFlags :: NOSUID ) {
1848+ locks. insert ( MountLockFlags :: NOSUID ) ;
1849+ }
1850+ if mount_flags. contains ( MountFlags :: NOEXEC ) {
1851+ locks. insert ( MountLockFlags :: NOEXEC ) ;
1852+ }
1853+ self . mount_locks . fetch_or ( locks. bits ( ) , Ordering :: Release ) ;
1854+ }
1855+
1856+ /// Linux `can_change_locked_flags()` equivalent for remount admission.
1857+ /// Locks prevent relaxing attributes that were present at lock time and
1858+ /// prevent changing the atime policy, while still allowing stricter flags
1859+ /// that were not locked to be added.
1860+ pub ( crate ) fn can_reconfigure_mount_flags ( & self , requested : MountFlags ) -> bool {
1861+ let locks = MountLockFlags :: from_bits_truncate ( self . mount_locks . load ( Ordering :: Acquire ) ) ;
1862+ if locks. contains ( MountLockFlags :: READONLY ) && !requested. contains ( MountFlags :: RDONLY ) {
1863+ return false ;
1864+ }
1865+ if locks. contains ( MountLockFlags :: NODEV ) && !requested. contains ( MountFlags :: NODEV ) {
1866+ return false ;
1867+ }
1868+ if locks. contains ( MountLockFlags :: NOSUID ) && !requested. contains ( MountFlags :: NOSUID ) {
1869+ return false ;
1870+ }
1871+ if locks. contains ( MountLockFlags :: NOEXEC ) && !requested. contains ( MountFlags :: NOEXEC ) {
1872+ return false ;
1873+ }
1874+ !locks. contains ( MountLockFlags :: ATIME )
1875+ || ( self . mount_flags ( ) & MountFlags :: MNT_ATIME_MASK )
1876+ == ( requested & MountFlags :: MNT_ATIME_MASK )
18051877 }
18061878
18071879 #[ inline( never) ]
@@ -2021,6 +2093,11 @@ impl MountFS {
20212093 self . lifecycle . lock ( ) . external_pins != 0
20222094 }
20232095
2096+ #[ cfg( test) ]
2097+ pub ( crate ) fn superblock_external_pin_count ( & self ) -> usize {
2098+ self . super_block_state . lifecycle . lock ( ) . external_pins
2099+ }
2100+
20242101 pub ( crate ) fn subtree_has_external_pins ( & self ) -> bool {
20252102 let mut pending = vec ! [ self . self_ref( ) ] ;
20262103 while let Some ( mount) = pending. pop ( ) {
0 commit comments