Skip to contentSkip to conversation

Fix a race in put_mountpoint.

PATCH In review Krister Johansen 6 participants 63 messages Dec 31, 2016 – Jun 7, 2017

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 Fix a race in put_mountpoint.

This can cause a panic when simultaneous callers of put_mountpoint attempt to free the same mountpoint. This occurs because some callers hold the mount_hash_lock, while others hold the namespace lock. Some even hold both.

In this submitter's case, the panic manifested itself as a GP fault in put_mountpoint() when it called hlist_del() and attempted to dereference a m_hash.pprev that had been poisioned by another thread.

Instead of trying to force all mountpoint hash users back under the namespace lock, add locks that cover just the mountpoint hash. This uses hlist_bl to protect against simlultaneous additions and removals, and RCU for lookups.

---
 fs/mount.h     |  6 ++++--
 fs/namespace.c | 62 ++++++++++++++++++++++++++++++++++++++++------------------
 2 files changed, 47 insertions(+), 21 deletions(-)
fs/mount.h +4 -2
index 2c856fc..1a2f41a 100644
@@ -3,6 +3,7 @@
 #include <linux/poll.h>
 #include <linux/ns_common.h>
 #include <linux/fs_pin.h>
+#include <linux/rculist_bl.h>
 
 struct mnt_namespace {
 	atomic_t		count;
@@ -24,10 +25,11 @@ struct mnt_pcp {
 };
 
 struct mountpoint {
-	struct hlist_node m_hash;
+	struct hlist_bl_node m_hash;
 	struct dentry *m_dentry;
 	struct hlist_head m_list;
-	int m_count;
+	atomic_t m_count;
+	struct rcu_head m_rcu;
 };
 
 struct mount {
fs/namespace.c +43 -19

134 lines not on this page · raw message

Signature · 1 line
2.7.4
maintainer
#1 lore raw
Quoted 5 lines · Krister Johansen

This can cause a panic when simultaneous callers of put_mountpoint attempt to free the same mountpoint. This occurs because some callers hold the mount_hash_lock, while others hold the namespace lock. Some even hold both.

Quoted 8 lines

In this submitter's case, the panic manifested itself as a GP fault in put_mountpoint() when it called hlist_del() and attempted to dereference a m_hash.pprev that had been poisioned by another thread.

Instead of trying to force all mountpoint hash users back under the namespace lock, add locks that cover just the mountpoint hash. This uses hlist_bl to protect against simlultaneous additions and removals, and RCU for lookups.

Too complicated, IMO. Look at the call graph for that sucker:
put_mountpoint
<- unhash_mnt
<- detach_mnt
<- attach_recursive_mnt [under mount_lock] <- pivot_root [under mount_lock]
<- umount_mnt
<- mntput_no_expire [under mount_lock]
<- umount_tree
<- do_umount [under mount_lock]
<- __detach_mounts [under mount_lock] <- copy_tree [under mount_lock]
<- drop_collected_mounts [under mount_lock] <- attach_recursive_mnt [under mount_lock] <- do_loopback [under mount_lock] <- mark_mounts_for_expiry [under mount_lock] <- shrink_submounts
<- do_umount [under mount_lock]
<- __detach_mounts [under mount_lock]
<- __detach_mounts [right after dropping mount_lock]
<- unlock_mount
<- pivot_root
Now, __detach_mounts() thing is trivially fixed - we just move that call one line up. unhash_mnt() is all covered. Which leaves us with unlock_mount() and pivot_root() and both are _not_ hot paths - not by any stretch of imagination. We also have lookup_mountpoint(), which is not hot either. Note that hash insertions and lookups are serialized on namespace lock; it's only removal from final mntput() that can be triggered outside. So playing with bitlocks is absolutely pointless.

Let's do this: make sure that all callers of lookup_mountpoint() have lock_mount (at least read_seqlock_excl), pull the call of put_mountpoint() in __detach_mounts() under mount_lock and slap read_seqlock_excl() around the calls in unlock_mount() and pivot_root(). Note that read_seqlock_excl() *is* exclusive - the "read" part in it is about "don't bump the seqcount part of mount_lock". I.e. the patch below ought to fix that and it's much simpler than your variant. Do you see any holes in the above?

fs/namespace.c +10 -3
index b5b1259e064f..ca98a8ff2732 100644
@@ -1595,11 +1595,11 @@ void __detach_mounts(struct dentry *dentry)
 	struct mount *mnt;
 
 	namespace_lock();
+	lock_mount_hash();
 	mp = lookup_mountpoint(dentry);
 	if (IS_ERR_OR_NULL(mp))
 		goto out_unlock;
 
-	lock_mount_hash();
 	event++;
 	while (!hlist_empty(&mp->m_list)) {
 		mnt = hlist_entry(mp->m_list.first, struct mount, mnt_mp_list);
@@ -1609,9 +1609,9 @@ void __detach_mounts(struct dentry *dentry)
 		}
 		else umount_tree(mnt, UMOUNT_CONNECTED);
 	}
-	unlock_mount_hash();
 	put_mountpoint(mp);
 out_unlock:
+	unlock_mount_hash();
 	namespace_unlock();
 }
 
@@ -2038,7 +2038,10 @@ static struct mountpoint *lock_mount(struct path *path)
 	namespace_lock();
 	mnt = lookup_mnt(path);
 	if (likely(!mnt)) {
-		struct mountpoint *mp = lookup_mountpoint(dentry);
+		struct mountpoint *mp;
+		read_seqlock_excl(&mount_lock);
+		mp = lookup_mountpoint(dentry);
+		read_sequnlock_excl(&mount_lock);
 		if (!mp)
 			mp = new_mountpoint(dentry);
 		if (IS_ERR(mp)) {
@@ -2059,7 +2062,9 @@ static struct mountpoint *lock_mount(struct path *path)
 static void unlock_mount(struct mountpoint *where)
 {
 	struct dentry *dentry = where->m_dentry;
+	read_seqlock_excl(&mount_lock);
 	put_mountpoint(where);
+	read_sequnlock_excl(&mount_lock);
 	namespace_unlock();
 	inode_unlock(dentry->d_inode);
 }
@@ -3137,7 +3142,9 @@ SYSCALL_DEFINE2(pivot_root, const char __user *, new_root,
 	list_del_init(&new_mnt->mnt_expire);
 	unlock_mount_hash();
 	chroot_fs_refs(&root, &new);
+	read_seqlock_excl(&mount_lock);
 	put_mountpoint(root_mp);
+	read_sequnlock_excl(&mount_lock);
 	error = 0;
 out4:
 	unlock_mount(old_mp);
#2 lore raw
Quoted 45 lines · Al Viro
Quoted 5 lines · Krister Johansen

This can cause a panic when simultaneous callers of put_mountpoint attempt to free the same mountpoint. This occurs because some callers hold the mount_hash_lock, while others hold the namespace lock. Some even hold both.

Quoted 8 lines

In this submitter's case, the panic manifested itself as a GP fault in put_mountpoint() when it called hlist_del() and attempted to dereference a m_hash.pprev that had been poisioned by another thread.

Instead of trying to force all mountpoint hash users back under the namespace lock, add locks that cover just the mountpoint hash. This uses hlist_bl to protect against simlultaneous additions and removals, and RCU for lookups.

Too complicated, IMO. Look at the call graph for that sucker:
put_mountpoint
<- unhash_mnt
<- detach_mnt
<- attach_recursive_mnt [under mount_lock] <- pivot_root [under mount_lock]
<- umount_mnt
<- mntput_no_expire [under mount_lock]
<- umount_tree
<- do_umount [under mount_lock]
<- __detach_mounts [under mount_lock] <- copy_tree [under mount_lock]
<- drop_collected_mounts [under mount_lock] <- attach_recursive_mnt [under mount_lock] <- do_loopback [under mount_lock] <- mark_mounts_for_expiry [under mount_lock] <- shrink_submounts
<- do_umount [under mount_lock]
<- __detach_mounts [under mount_lock]
<- __detach_mounts [right after dropping mount_lock]
<- unlock_mount
<- pivot_root
Now, __detach_mounts() thing is trivially fixed - we just move that call one line up. unhash_mnt() is all covered. Which leaves us with unlock_mount() and pivot_root() and both are _not_ hot paths - not by any stretch of imagination. We also have lookup_mountpoint(), which is not hot either. Note that hash insertions and lookups are serialized on namespace lock; it's only removal from final mntput() that can be triggered outside. So playing with bitlocks is absolutely pointless.

Quoted 7 lines

Let's do this: make sure that all callers of lookup_mountpoint() have lock_mount (at least read_seqlock_excl), pull the call of put_mountpoint() in __detach_mounts() under mount_lock and slap read_seqlock_excl() around the calls in unlock_mount() and pivot_root(). Note that read_seqlock_excl() *is* exclusive - the "read" part in it is about "don't bump the seqcount part of mount_lock". I.e. the patch below ought to fix that and it's much simpler than your variant. Do you see any holes in the above?

The only significant thing I see is that you have not taken the mount_lock on the path where new_mountpoint adds the new struct mountpoint into the mountpoint hash table.

Just for managing this fix we need a: "Cc: [email protected]" and a "Fixes: ce07d891a089 ("mnt: Honor MNT_LOCKED when detaching mounts")" As the bug is 2 years old at this point.

I will start with your patch and see about adding handling the missing locking in new_mountpoint and see where it gets me.

Eric

Quoted 60 lines
fs/namespace.c +10 -3
index b5b1259e064f..ca98a8ff2732 100644
@@ -1595,11 +1595,11 @@ void __detach_mounts(struct dentry *dentry)
 	struct mount *mnt;
 
 	namespace_lock();
+	lock_mount_hash();
 	mp = lookup_mountpoint(dentry);
 	if (IS_ERR_OR_NULL(mp))
 		goto out_unlock;
 
-	lock_mount_hash();
 	event++;
 	while (!hlist_empty(&mp->m_list)) {
 		mnt = hlist_entry(mp->m_list.first, struct mount, mnt_mp_list);
@@ -1609,9 +1609,9 @@ void __detach_mounts(struct dentry *dentry)
 		}
 		else umount_tree(mnt, UMOUNT_CONNECTED);
 	}
-	unlock_mount_hash();
 	put_mountpoint(mp);
 out_unlock:
+	unlock_mount_hash();
 	namespace_unlock();
 }
 
@@ -2038,7 +2038,10 @@ static struct mountpoint *lock_mount(struct path *path)
 	namespace_lock();
 	mnt = lookup_mnt(path);
 	if (likely(!mnt)) {
-		struct mountpoint *mp = lookup_mountpoint(dentry);
+		struct mountpoint *mp;
+		read_seqlock_excl(&mount_lock);
+		mp = lookup_mountpoint(dentry);
+		read_sequnlock_excl(&mount_lock);
 		if (!mp)
 			mp = new_mountpoint(dentry);
 		if (IS_ERR(mp)) {
@@ -2059,7 +2062,9 @@ static struct mountpoint *lock_mount(struct path *path)
 static void unlock_mount(struct mountpoint *where)
 {
 	struct dentry *dentry = where->m_dentry;
+	read_seqlock_excl(&mount_lock);
 	put_mountpoint(where);
+	read_sequnlock_excl(&mount_lock);
 	namespace_unlock();
 	inode_unlock(dentry->d_inode);
 }
@@ -3137,7 +3142,9 @@ SYSCALL_DEFINE2(pivot_root, const char __user *, new_root,
 	list_del_init(&new_mnt->mnt_expire);
 	unlock_mount_hash();
 	chroot_fs_refs(&root, &new);
+	read_seqlock_excl(&mount_lock);
 	put_mountpoint(root_mp);
+	read_sequnlock_excl(&mount_lock);
 	error = 0;
 out4:
 	unlock_mount(old_mp);
maintainer
#3 lore raw
Quoted 3 lines · Eric W. Biederman

The only significant thing I see is that you have not taken the mount_lock on the path where new_mountpoint adds the new struct mountpoint into the mountpoint hash table.

Umm... Point, but I really don't like that bouncing mount_lock up and down there. It's not going to cause any serious overhead, but it just looks ugly... ;-/

Let me think for a while...

#4 lore raw
Quoted 11 lines · Al Viro
Quoted 3 lines · Eric W. Biederman

The only significant thing I see is that you have not taken the mount_lock on the path where new_mountpoint adds the new struct mountpoint into the mountpoint hash table.

Umm... Point, but I really don't like that bouncing mount_lock up and down there. It's not going to cause any serious overhead, but it just looks ugly... ;-/

Let me think for a while...

The other possibility is to grab namespace_sem in mntput_no_expire around the call of umount_mnt. That is the only path where put_mountpoint can be called where we are not holding namespace_sem. That works in the small but I haven't traced the callers of mntput and mntput_no_expire yet to see if it works in practice.

Eric

maintainer
#5 lore raw
Quoted 12 lines · Eric W. Biederman
Quoted 10 lines · Al Viro
Quoted 3 lines · Eric W. Biederman

The only significant thing I see is that you have not taken the mount_lock on the path where new_mountpoint adds the new struct mountpoint into the mountpoint hash table.

Umm... Point, but I really don't like that bouncing mount_lock up and down there. It's not going to cause any serious overhead, but it just looks ugly... ;-/

Quoted 7 lines
Quoted 1 line

Let me think for a while...

The other possibility is to grab namespace_sem in mntput_no_expire around the call of umount_mnt. That is the only path where put_mountpoint can be called where we are not holding namespace_sem. That works in the small but I haven't traced the callers of mntput and mntput_no_expire yet to see if it works in practice.

No, that's a really bad idea. Final mntput should _not_ happen under namespace_lock, but I don't want grabbing it in that place.

How about this instead:

fs/namespace.c +51 -38
index b5b1259e064f..20fc797277f8 100644
@@ -742,29 +742,6 @@ static struct mountpoint *lookup_mountpoint(struct dentry *dentry)
 	return NULL;
 }
 
-static struct mountpoint *new_mountpoint(struct dentry *dentry)
-{
-	struct hlist_head *chain = mp_hash(dentry);
-	struct mountpoint *mp;
-	int ret;
-
-	mp = kmalloc(sizeof(struct mountpoint), GFP_KERNEL);
-	if (!mp)
-		return ERR_PTR(-ENOMEM);
-
-	ret = d_set_mounted(dentry);
-	if (ret) {
-		kfree(mp);
-		return ERR_PTR(ret);
-	}
-
-	mp->m_dentry = dentry;
-	mp->m_count = 1;
-	hlist_add_head(&mp->m_hash, chain);
-	INIT_HLIST_HEAD(&mp->m_list);
-	return mp;
-}
-
 static void put_mountpoint(struct mountpoint *mp)
 {
 	if (!--mp->m_count) {
@@ -1595,11 +1572,11 @@ void __detach_mounts(struct dentry *dentry)
 	struct mount *mnt;
 
 	namespace_lock();
+	lock_mount_hash();
 	mp = lookup_mountpoint(dentry);
 	if (IS_ERR_OR_NULL(mp))
 		goto out_unlock;
 
-	lock_mount_hash();
 	event++;
 	while (!hlist_empty(&mp->m_list)) {
 		mnt = hlist_entry(mp->m_list.first, struct mount, mnt_mp_list);
@@ -1609,9 +1586,9 @@ void __detach_mounts(struct dentry *dentry)
 		}
 		else umount_tree(mnt, UMOUNT_CONNECTED);
 	}
-	unlock_mount_hash();
 	put_mountpoint(mp);
 out_unlock:
+	unlock_mount_hash();
 	namespace_unlock();
 }
 
@@ -2027,8 +2004,11 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 
 static struct mountpoint *lock_mount(struct path *path)
 {
+	struct mountpoint *mp = NULL;
 	struct vfsmount *mnt;
 	struct dentry *dentry = path->dentry;
+	int err;
+
 retry:
 	inode_lock(dentry->d_inode);
 	if (unlikely(cant_mount(dentry))) {
@@ -2037,29 +2017,60 @@ static struct mountpoint *lock_mount(struct path *path)
 	}
 	namespace_lock();
 	mnt = lookup_mnt(path);
-	if (likely(!mnt)) {
-		struct mountpoint *mp = lookup_mountpoint(dentry);
-		if (!mp)
-			mp = new_mountpoint(dentry);
-		if (IS_ERR(mp)) {
+	if (unlikely(mnt)) {
+		namespace_unlock();
+		inode_unlock(path->dentry->d_inode);
+		path_put(path);
+		path->mnt = mnt;
+		dentry = path->dentry = dget(mnt->mnt_root);
+		goto retry;
+	}
+
+	/*
+	 * OK, we have namespace_lock held, nothing is overmounting
+	 * *path and inode of mountpoint to be is locked.
+	 */
+	if (likely(!d_mountpoint(dentry)))
+		mp = kmalloc(sizeof(struct mountpoint), GFP_KERNEL);
+	read_seqlock_excl(&mount_lock);
+	if (!mp && !d_mountpoint(dentry)) {
+		read_sequnlock_excl(&mount_lock);
+		mp = kmalloc(sizeof(struct mountpoint), GFP_KERNEL);
+		read_seqlock_excl(&mount_lock);
+	}
+	if (d_mountpoint(dentry)) {
+		kfree(mp);
+		mp = lookup_mountpoint(dentry);
+	} else {
+		if (unlikely(!mp)) {
+			read_sequnlock_excl(&mount_lock);
 			namespace_unlock();
 			inode_unlock(dentry->d_inode);
-			return mp;
+			return ERR_PTR(-ENOMEM);
 		}
-		return mp;
+		err = d_set_mounted(dentry);
+		if (unlikely(err)) {
+			kfree(mp);
+			read_sequnlock_excl(&mount_lock);
+			namespace_unlock();
+			inode_unlock(dentry->d_inode);
+			return ERR_PTR(err);
+		}
+		mp->m_dentry = dentry;
+		mp->m_count = 1;
+		hlist_add_head(&mp->m_hash, mp_hash(dentry));
+		INIT_HLIST_HEAD(&mp->m_list);
 	}
-	namespace_unlock();
-	inode_unlock(path->dentry->d_inode);
-	path_put(path);
-	path->mnt = mnt;
-	dentry = path->dentry = dget(mnt->mnt_root);
-	goto retry;
+	read_sequnlock_excl(&mount_lock);
+	return mp;
 }
 
 static void unlock_mount(struct mountpoint *where)
 {
 	struct dentry *dentry = where->m_dentry;
+	read_seqlock_excl(&mount_lock);
 	put_mountpoint(where);
+	read_sequnlock_excl(&mount_lock);
 	namespace_unlock();
 	inode_unlock(dentry->d_inode);
 }
@@ -3137,7 +3148,9 @@ SYSCALL_DEFINE2(pivot_root, const char __user *, new_root,
 	list_del_init(&new_mnt->mnt_expire);
 	unlock_mount_hash();
 	chroot_fs_refs(&root, &new);
+	read_seqlock_excl(&mount_lock);
 	put_mountpoint(root_mp);
+	read_sequnlock_excl(&mount_lock);
 	error = 0;
 out4:
 	unlock_mount(old_mp);
#6 lore raw
Quoted 15 lines · Al Viro
Quoted 14 lines · Eric W. Biederman
Quoted 11 lines · Al Viro
Quoted 3 lines · Eric W. Biederman

The only significant thing I see is that you have not taken the mount_lock on the path where new_mountpoint adds the new struct mountpoint into the mountpoint hash table.

Umm... Point, but I really don't like that bouncing mount_lock up and down there. It's not going to cause any serious overhead, but it just looks ugly... ;-/

Let me think for a while...

Quoted 8 lines
Quoted 5 lines

The other possibility is to grab namespace_sem in mntput_no_expire around the call of umount_mnt. That is the only path where put_mountpoint can be called where we are not holding namespace_sem. That works in the small but I haven't traced the callers of mntput and mntput_no_expire yet to see if it works in practice.

No, that's a really bad idea. Final mntput should _not_ happen under namespace_lock, but I don't want grabbing it in that place.

Agreed. That just makes the code harder to maintain later on.

Quoted 1 line

How about this instead:

I really don't like the logic inlined as my patch to kill shadow mounts needs to call acquire a mountpoint which may not already have been allocated as well.

Beyond that we can make the logic simpler by causing d_set_mounted to fail if the flag is already set and syncrhonize on that. Which means we don't have to verify the ordering between mount_lock and rename_lock (from d_set_mounted) is not a problem, which makes backports easier to verify.

Patch follows.

Eric

#7 lore raw

PATCH 1/1 mnt: Protect the mountpoint hashtable with mount_lock

Protecting the mountpoint hashtable with namespace_sem was sufficient until a call to umount_mnt was added to mntput_no_expire. At which point it became possible for multiple calls of put_mountpoint on the same hash chain to happen on the same time.

Kristen Johansen <[email protected]> reported:

Quoted 8 lines

This can cause a panic when simultaneous callers of put_mountpoint attempt to free the same mountpoint. This occurs because some callers hold the mount_hash_lock, while others hold the namespace lock. Some even hold both.

In this submitter's case, the panic manifested itself as a GP fault in put_mountpoint() when it called hlist_del() and attempted to dereference a m_hash.pprev that had been poisioned by another thread.

Al Viro observed that the simple fix is to switch from using the namespace_sem to the mount_lock to protect the mountpoint hash table.

I have taken Al's suggested patch moved put_mountpoint in pivot_root (instead of taking mount_lock an additional time), and have replaced new_mountpoint with get_mountpoint a function that does the hash table
lookup and addition under the mount_lock. The introduction of get_mounptoint
ensures that only the mount_lock is needed to manipulate the mountpoint hashtable.

d_set_mounted is modified to only set DCACHE_MOUNTED if it is not already set. This allows get_mountpoint to use the setting of DCACHE_MOUNTED to ensure adding a struct mountpoint for a dentry happens exactly once.

Fixes ce07d891a089 ("mnt: Honor MNT_LOCKED when detaching mounts")
---
 fs/dcache.c    |  7 +++++--
 fs/namespace.c | 64 +++++++++++++++++++++++++++++++++++++++++-----------------
 2 files changed, 50 insertions(+), 21 deletions(-)
fs/dcache.c +5 -2
index 769903dbc19d..95d71eda8142 100644
@@ -1336,8 +1336,11 @@ int d_set_mounted(struct dentry *dentry)
 	}
 	spin_lock(&dentry->d_lock);
 	if (!d_unlinked(dentry)) {
-		dentry->d_flags |= DCACHE_MOUNTED;
-		ret = 0;
+		ret = -EBUSY;
+		if (!d_mountpoint(dentry)) {
+			dentry->d_flags |= DCACHE_MOUNTED;
+			ret = 0;
+		}
 	}
  	spin_unlock(&dentry->d_lock);
 out:
fs/namespace.c +45 -19

116 lines not on this page · raw message

Signature · 1 line
2.10.1
#8 lore raw

PATCH 1/1 mnt: Tuck mounts under others instead of creating shadow/side mounts.

Ever since mount propagation was introduced in cases where a mount in propagated to parent mount mountpoint pair that is already in use the code has placed the new mount behind the old mount in the mount hash table.

This implementation detail is problematic as it allows creating arbitrary length mount hash chains.

Furthermore it invalidates the constraint maintained elsewhere in the mount code that a parent mount and a mountpoint pair will have exactly one mount upon them. Making it hard to deal with and to talk about this special case in the mount code.

Modify mount propagation to notice when there is already a mount at the parent mount and mountpoint where a new mount is propagating to and place that preexisting mount on top of the new mount.

Modify unmount propagation to notice when a mount that is being unmounted has another mount on top of it (and no other children), and to replace the unmounted mount with the mount on top of it.

Move the MNT_UMUONT test from __lookup_mnt_last into __propagate_umount as that is the only call of __lookup_mnt_last where MNT_UMOUNT may be set on any mount visible in the mount hash table.

These modifications allow:
- __lookup_mnt_last to be removed.
- attach_shadows to be renamed __attach_mnt and the it's shadow handling to be removed.
- commit_tree to be simplified
- copy_tree to be simplified

The result is an easier to understand tree of mounts that does not allow creation of arbitrary length hash chains in the mount hash table.

v2: Updated to mnt_change_mountpoint to not call dput or mntput and instead to decrement the counts directly. It is guaranteed that there will be other references when mnt_change_mountpoint is called so this is safe.

Fixes b90fa9ae8f51 ("[PATCH] shared mount handling: bind and rbind")

---

Since the last version some of you may have seen I have modified my implementation of mnt_change_mountpoint so that it no longer calls mntput or dput but instead relies on the knowledge that it can not possibly have the last reference to the mnt and dentry of interest. This avoids code checking tools from complaining bitterly.

This is on top of my previous patch that sorts out locking of the mountpoint hash table. After time giving ample time for review I intend to push this and the previous bug fix to Linus.

 fs/mount.h     |   1 -
 fs/namespace.c | 110 +++++++++++++++++++++++++++++++--------------------------
 fs/pnode.c     |  27 ++++++++++----
 fs/pnode.h     |   2 ++
 4 files changed, 82 insertions(+), 58 deletions(-)
fs/mount.h +0 -1
index 2c856fc47ae3..2826543a131d 100644
@@ -89,7 +89,6 @@ static inline int is_mounted(struct vfsmount *mnt)
 }
 
 extern struct mount *__lookup_mnt(struct vfsmount *, struct dentry *);
-extern struct mount *__lookup_mnt_last(struct vfsmount *, struct dentry *);
 
 extern int __legitimize_mnt(struct vfsmount *, unsigned);
 extern bool legitimize_mnt(struct vfsmount *, unsigned);
fs/namespace.c +59 -51

182 lines not on this page · raw message

fs/pnode.c +21 -6
index 06a793f4ae38..eb4331240fd1 100644
@@ -327,6 +327,9 @@ int propagate_mnt(struct mount *dest_mnt, struct mountpoint *dest_mp,
  */
 static inline int do_refcount_check(struct mount *mnt, int count)
 {
+	struct mount *topper = __lookup_mnt(&mnt->mnt, mnt->mnt.mnt_root);
+	if (topper)
+		count++;
 	return mnt_get_count(mnt) > count;
 }
 
@@ -359,7 +362,7 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 
 	for (m = propagation_next(parent, parent); m;
 	     		m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
 		if (child && list_empty(&child->mnt_mounts) &&
 		    (ret = do_refcount_check(child, 1)))
 			break;
@@ -381,7 +384,7 @@ void propagate_mount_unlock(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
 		if (child)
 			child->mnt.mnt_flags &= ~MNT_LOCKED;
 	}
@@ -399,9 +402,11 @@ static void mark_umount_candidates(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-		struct mount *child = __lookup_mnt_last(&m->mnt,
+		struct mount *child = __lookup_mnt(&m->mnt,
 						mnt->mnt_mountpoint);
-		if (child && (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m))) {
+		if (!child || (child->mnt.mnt_flags & MNT_UMOUNT))
+			continue;
+		if (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m)) {
 			SET_MNT_MARK(child);
 		}
 	}
@@ -420,8 +425,8 @@ static void __propagate_umount(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-
-		struct mount *child = __lookup_mnt_last(&m->mnt,
+		struct mount *topper;
+		struct mount *child = __lookup_mnt(&m->mnt,
 						mnt->mnt_mountpoint);
 		/*
 		 * umount the child only if the child has no children
@@ -430,6 +435,16 @@ static void __propagate_umount(struct mount *mnt)
 		if (!child || !IS_MNT_MARKED(child))
 			continue;
 		CLEAR_MNT_MARK(child);
+
+		/* If there is exactly one mount covering all of child
+		 * replace child with that mount.
+		 */
+		topper = __lookup_mnt(&child->mnt, child->mnt.mnt_root);
+		if (topper &&
+		    (child->mnt_mounts.next == &topper->mnt_child) &&
+		    (topper->mnt_child.next == &child->mnt_mounts))
+			mnt_change_mountpoint(child->mnt_parent, child->mnt_mp, topper);
+
 		if (list_empty(&child->mnt_mounts)) {
 			list_del_init(&child->mnt_child);
 			child->mnt.mnt_flags |= MNT_UMOUNT;
fs/pnode.h +2 -0
index 550f5a8b4fcf..dc87e65becd2 100644
@@ -49,6 +49,8 @@ int get_dominating_id(struct mount *mnt, const struct path *root);
 unsigned int mnt_get_count(struct mount *mnt);
 void mnt_set_mountpoint(struct mount *, struct mountpoint *,
 			struct mount *);
+void mnt_change_mountpoint(struct mount *parent, struct mountpoint *mp,
+			   struct mount *mnt);
 struct mount *copy_tree(struct mount *, struct dentry *, int);
 bool is_path_reachable(struct mount *, struct dentry *,
 			 const struct path *root);
Signature · 1 line
2.10.1
maintainer
#9 lore raw

Re: [REVIEW][PATCH] mnt: Tuck mounts under others instead of creating shadow/side mounts.

Quoted 2 lines · Eric W. Biederman

- attach_shadows to be renamed __attach_mnt and the it's shadow handling to be removed.

Er... s/the it's/its/, presumably? Or am I misparsing that?

Quoted 7 lines

v2: Updated to mnt_change_mountpoint to not call dput or mntput and instead to decrement the counts directly. It is guaranteed that there will be other references when mnt_change_mountpoint is called so this is safe.

+void mnt_change_mountpoint(struct mount *parent, struct mountpoint *mp, struct mount *mnt)
{

Too generic name, IMO, and I really wonder if "mount" (== interpose) and "umount" (== excise?) cases would be better off separately.

Quoted 8 lines
fs/namespace.c +8 -0
+	struct mountpoint *old_mp = mnt->mnt_mp;
+	struct dentry *old_mountpoint = mnt->mnt_mountpoint;
+	struct mount *old_parent = mnt->mnt_parent;
+
+	list_del_init(&mnt->mnt_child);
+	hlist_del_init(&mnt->mnt_mp_list);
+	hlist_del_init_rcu(&mnt->mnt_hash);
+

fs/namespace.c static void attach_mnt(struct mount *mnt,

Quoted 10 lines
fs/namespace.c +9 -0
+	attach_mnt(mnt, parent, mp);
+
+	put_mountpoint(old_mp);
 
+	 *
+	 * During mounting, another mount will continue to use the old
+	 * mountpoint and during unmounting, the old mountpoint will
+	 * continue to exist until namespace_unlock which happens well
+	 * after mnt_change_mountpoint.
+	 */

Umm... AFAICS, in the former case "another mount" is simply parent, right?

fs/namespace.c static void attach_mnt(struct mount *mnt,

Quoted 7 lines
fs/namespace.c +5 -0
+	spin_lock(&old_mountpoint->d_lock);
+	old_mountpoint->d_lockref.count--;
+	spin_unlock(&old_mountpoint->d_lock);
+	mnt_add_count(old_parent, -1);
 
 
+		if (child->mnt.mnt_root == smp->m_dentry) {

Explain, please. In which case is that condition _not_ satisfied, and what should happen i

fs/namespace.c static int attach_recursive_mnt(struct mount *source_mnt,

Quoted 9 lines
fs/namespace.c +6 -0
+			struct mount *q;
+			q = __lookup_mnt(&child->mnt_parent->mnt,
+					 child->mnt_mountpoint);
+			if (q)
+				mnt_change_mountpoint(child, smp, q);
 
 
 	unlock_mount_hash();
+	put_mountpoint(smp);

Wrong order...

fs/namespace.c static int attach_recursive_mnt(struct mount *source_mnt, · line 2056

Quoted 2 lines
fs/namespace.c +1 -0
 	ns->pending_mounts = 0;
+	put_mountpoint(smp);

... and worse yet here.

Quoted 9 lines

static inline int do_refcount_check(struct mount *mnt, int count) {

fs/pnode.c +3 -0
+	struct mount *topper = __lookup_mnt(&mnt->mnt, mnt->mnt.mnt_root);
+	if (topper)
+		count++;
 	return mnt_get_count(mnt) > count;
 }

fs/pnode.c int propagate_mount_busy(struct mount *mnt, int refcnt) · line 368

Quoted 8 lines
fs/pnode.c +1 -1
@@ -359,7 +362,7 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 
 	for (m = propagation_next(parent, parent); m;
 	     		m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
 		if (child && list_empty(&child->mnt_mounts) &&
 		    (ret = do_refcount_check(child, 1)))
 			break;

Er... You do realize that you can end up with more that one such propagation, right? IOW, there might be more than one thing slipped in.

fs/pnode.c static void mark_umount_candidates(struct mount *mnt) · line 410

Quoted 2 lines
fs/pnode.c +1 -0
+		if (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m)) {
 			SET_MNT_MARK(child);

Reread the condition, please... And yes, I realize that original is also rather odd; at a guess it was meant to be !(, not (!, but that's just a guess - it's your code, IIRC.

Quoted 14 lines
fs/pnode.c +2 -2
@@ -420,8 +425,8 @@ static void __propagate_umount(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-
-		struct mount *child = __lookup_mnt_last(&m->mnt,
+		struct mount *topper;
+		struct mount *child = __lookup_mnt(&m->mnt,
 						mnt->mnt_mountpoint);
 		/*
 		 * umount the child only if the child has no children
@@ -430,6 +435,16 @@ static void __propagate_umount(struct mount *mnt)
 		if (!child || !IS_MNT_MARKED(child))
 			continue;

fs/pnode.c static void __propagate_umount(struct mount *mnt) · line 446

Quoted 10 lines
fs/pnode.c +8 -0
 		CLEAR_MNT_MARK(child);
+
+		/* If there is exactly one mount covering all of child
+		 * replace child with that mount.
+		 */
+		topper = __lookup_mnt(&child->mnt, child->mnt.mnt_root);
+		if (topper &&
 
+		    (child->mnt_mounts.next == &topper->mnt_child) &&
+		    (topper->mnt_child.next == &child->mnt_mounts))

Weird way to spell child->mnt_mounts.next == child->mnt_mounts.prev, that... Or, perhaps, the entire thing ought to be

		if (list_is_singular(&child->mnt_mounts)) {
			topper = list_first_entry(&child->mnt_mounts,
						  struct mount, mnt_child);
			if (topper->mnt_parent == child &&
			    topped->mnt_mountpoint == child->mnt.mnt_root)

to avoid hash lookups.

fs/pnode.c static void __propagate_umount(struct mount *mnt) · line 446

Quoted 1 line
fs/pnode.c +1 -0
+			mnt_change_mountpoint(child->mnt_parent, child->mnt_mp, topper);

FWIW, the my main worry here is your handling of the umount. For example, what happens if
* something is mounted on A (m1)
* something else is mounted on A/bar (m2)
* D is a slave of C
* something has been mounted on D/foo (n)
* you do mount --rbind A C/foo (m1' on C/foo, m2' on m1/bar, m1'' interposed on D/foo under n, m2'' on m1''/bar,
m1'' slave of m1', m2'' slave of m2)
* you make C/foo and C/foo/bar private (m1'' and m2'' are not getting propagation from m1' and m2' anymore)
* you umount C/foo/bar (m2' is unmounted)
* you umount C/foo
m1' gets unmounted, all right, but what of m1''? D is a slave of C, so we get propagation of umount from C/foo to D/foo; m1'' still has m2'' attached to it. AFAICS, your logics will happily slip m1'' from under n (assuming that n itself is not busy), and leak both m1'' and m2''.

OTOH, the case of multiple slip-under (Z is slave of Y, which is a slave of X, mount on Z, then mount of Y, then mount on X) the check for being busy would do very odd things.

Something's fishy on the umount side...

#10 lore raw

Re: [REVIEW][PATCH] mnt: Tuck mounts under others instead of creating shadow/side mounts.

Quoted 30 lines · Al Viro
Quoted 2 lines · Eric W. Biederman

- attach_shadows to be renamed __attach_mnt and the it's shadow handling to be removed.

Er... s/the it's/its/, presumably? Or am I misparsing that?

Quoted 7 lines

v2: Updated to mnt_change_mountpoint to not call dput or mntput and instead to decrement the counts directly. It is guaranteed that there will be other references when mnt_change_mountpoint is called so this is safe.

+void mnt_change_mountpoint(struct mount *parent, struct mountpoint *mp, struct mount *mnt)
{

Too generic name, IMO, and I really wonder if "mount" (== interpose) and "umount" (== excise?) cases would be better off separately.

Quoted 11 lines
fs/namespace.c +11 -0
+	struct mountpoint *old_mp = mnt->mnt_mp;
+	struct dentry *old_mountpoint = mnt->mnt_mountpoint;
+	struct mount *old_parent = mnt->mnt_parent;
+
+	list_del_init(&mnt->mnt_child);
+	hlist_del_init(&mnt->mnt_mp_list);
+	hlist_del_init_rcu(&mnt->mnt_hash);
+
+	attach_mnt(mnt, parent, mp);
+
+	put_mountpoint(old_mp);
Quoted 9 lines

fs/namespace.c static void attach_mnt(struct mount *mnt, · line 901

Quoted 6 lines
fs/namespace.c +6 -0
+	 *
+	 * During mounting, another mount will continue to use the old
+	 * mountpoint and during unmounting, the old mountpoint will
+	 * continue to exist until namespace_unlock which happens well
+	 * after mnt_change_mountpoint.
+	 */

Umm... AFAICS, in the former case "another mount" is simply parent, right?

Yes it is the new parent mountpoint. I was looking at it from a different perspective.

Quoted 10 lines

fs/namespace.c static void attach_mnt(struct mount *mnt,

Quoted 7 lines
fs/namespace.c +5 -0
+	spin_lock(&old_mountpoint->d_lock);
+	old_mountpoint->d_lockref.count--;
+	spin_unlock(&old_mountpoint->d_lock);
+	mnt_add_count(old_parent, -1);
 
 
+		if (child->mnt.mnt_root == smp->m_dentry) {

Explain, please. In which case is that condition _not_ satisfied, and what should happen i

When a tree is grafted in that condition does not apply to the lower leaves of the tree. At the same time nothing needs to be done for those leaves. Only the primary mountpoint needs to worry about tucking.

Quoted 12 lines

fs/namespace.c static int attach_recursive_mnt(struct mount *source_mnt,

Quoted 9 lines
fs/namespace.c +6 -0
+			struct mount *q;
+			q = __lookup_mnt(&child->mnt_parent->mnt,
+					 child->mnt_mountpoint);
+			if (q)
+				mnt_change_mountpoint(child, smp, q);
 
 
 	unlock_mount_hash();
+	put_mountpoint(smp);

Wrong order...

Quoted 4 lines

fs/namespace.c static int attach_recursive_mnt(struct mount *source_mnt, · line 2056

Quoted 2 lines
fs/namespace.c +1 -0
 	ns->pending_mounts = 0;
+	put_mountpoint(smp);

... and worse yet here.

Definitely. I totally spaced on propagating the locking changes to this patch when I rebased it.

Quoted 10 lines
Quoted 10 lines

static inline int do_refcount_check(struct mount *mnt, int count) {

fs/pnode.c +3 -0
+	struct mount *topper = __lookup_mnt(&mnt->mnt, mnt->mnt.mnt_root);
+	if (topper)
+		count++;
 	return mnt_get_count(mnt) > count;
 }
@@ -359,7 +362,7 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 
Quoted 11 lines

fs/pnode.c int propagate_mount_busy(struct mount *mnt, int refcnt) · line 368

Quoted 7 lines
fs/pnode.c +1 -1
 	for (m = propagation_next(parent, parent); m;
 	     		m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
 		if (child && list_empty(&child->mnt_mounts) &&
 		    (ret = do_refcount_check(child, 1)))
 			break;

Er... You do realize that you can end up with more that one such propagation, right? IOW, there might be more than one thing slipped in.

So I have stared at this a lot and I don't see what you seem to be seeing here. I do however see that propagate_mount_busy has been buggy since the beginning, as it only fails in the propagation case if list of children is empty.

I also see my modification to the code is buggy since the list empty precludes my changes to do_refcount_check from being effective.

I have looked hard and your point with multiple propagations elludes me.

I am going to add a patch to fix propagate_mount_busy, and then rebase this patch on top of that, and post it all for review.

Quoted 6 lines

fs/pnode.c static void mark_umount_candidates(struct mount *mnt) · line 410

Quoted 2 lines
fs/pnode.c +1 -0
+		if (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m)) {
 			SET_MNT_MARK(child);

Reread the condition, please... And yes, I realize that original is also rather odd; at a guess it was meant to be !(, not (!, but that's just a guess - it's your code, IIRC.

The intent is to find all trees that we can unmount where the point at which the tree meets the rest of the mounts is not locked.

The mark is later used to see if it ok to unmount a mount or if we will reveal information to userspace (by breaking a lock).

Therefore the mark needs to be set if the mount is unlocked, and recursively the mark needs to be set for every child of that mount where the mark is set (the second condition).

Which makes the code essentially correct.

Unfortunately the code does not handle multiple unmounts from the same parent mount point. Which means shadow/side mount support and untucking of mounts fails to handle multiple unmounts from the same parent mount.

The way the mark is used fundamentally assumes only one operation on each mountpoint, and that is broken.

I intend to work on propagute_umount some more and fix that brokenness and hopefully fix the performance issues as well. But I am leaving that work for another change as it is going to require stripping out and replacing algorithms, and so far I don't have good solutions.

Quoted 22 lines
Quoted 21 lines
fs/pnode.c +8 -2
@@ -420,8 +425,8 @@ static void __propagate_umount(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-
-		struct mount *child = __lookup_mnt_last(&m->mnt,
+		struct mount *topper;
+		struct mount *child = __lookup_mnt(&m->mnt,
 						mnt->mnt_mountpoint);
 		/*
 		 * umount the child only if the child has no children
@@ -430,6 +435,16 @@ static void __propagate_umount(struct mount *mnt)
 		if (!child || !IS_MNT_MARKED(child))
 			continue;
 		CLEAR_MNT_MARK(child);
+
+		/* If there is exactly one mount covering all of child
+		 * replace child with that mount.
+		 */
+		topper = __lookup_mnt(&child->mnt, child->mnt.mnt_root);
+		if (topper &&
Quoted 5 lines

fs/pnode.c static void __propagate_umount(struct mount *mnt) · line 445

Quoted 2 lines
fs/pnode.c +2 -0
+		    (child->mnt_mounts.next == &topper->mnt_child) &&
+		    (topper->mnt_child.next == &child->mnt_mounts))

Weird way to spell child->mnt_mounts.next == child->mnt_mounts.prev, that... Or, perhaps, the entire thing ought to be

Except it is clearer than that. It verifies not just that there is one list item but that topper is that one list item.

Further it is the exact same logic as is used in do_check_refcnt and at this stage in development I figured it was more important to have a recognizable pattern than to have the absolute most performant code. Especially as the basic complexity of the code is the same either way.

Quoted 7 lines
		if (list_is_singular(&child->mnt_mounts)) {
			topper = list_first_entry(&child->mnt_mounts,
						  struct mount, mnt_child);
			if (topper->mnt_parent == child &&
			    topped->mnt_mountpoint == child->mnt.mnt_root)

to avoid hash lookups.

That it would and now that I see that list_is_singular exists it looks like a reasonable option.

Quoted 13 lines

fs/pnode.c static void __propagate_umount(struct mount *mnt) · line 446

Quoted 1 line
fs/pnode.c +1 -0
+			mnt_change_mountpoint(child->mnt_parent, child->mnt_mp, topper);

FWIW, the my main worry here is your handling of the umount. For example, what happens if
* something is mounted on A (m1)
* something else is mounted on A/bar (m2)
* D is a slave of C
* something has been mounted on D/foo (n)
* you do mount --rbind A C/foo (m1' on C/foo, m2' on m1'/bar, m1'' interposed on D/foo under n, m2'' on m1''/bar,
m1'' slave of m1', m2'' slave of m2)

Quoted 8 lines

* you make C/foo and C/foo/bar private (m1'' and m2'' are not getting propagation from m1' and m2' anymore)
* you umount C/foo/bar (m2' is unmounted)
* you umount C/foo
m1' gets unmounted, all right, but what of m1''? D is a slave of C, so we get propagation of umount from C/foo to D/foo; m1'' still has m2'' attached to it. AFAICS, your logics will happily slip m1'' from under n (assuming that n itself is not busy), and leak both m1'' and m2''.

Yes. This is exactly the same behavior we have today without my patch. The only difference is who is the parent mount.

$ cat > viro1.sh << EOF
#!/bin/sh
set -e
set -x

mount -t tmpfs base /mnt
mkdir -p /mnt/A
mount -t tmpfs m1 /mnt/A
mkdir -p /mnt/A/bar
mount -t tmpfs m2 /mnt/A/bar

mkdir -p /mnt/D
mkdir -p /mnt/C
mount -t tmpfs mC /mnt/C
mkdir -p /mnt/C/foo
mount --make-shared /mnt/C
mount --bind /mnt/C /mnt/D
mount --make-slave /mnt/D
mount -t tmpfs n /mnt/D/foo
mount --rbind /mnt/A /mnt/C/foo

echo
cat /proc/self/mountinfo

mount --make-private /mnt/C/foo
mount --make-private /mnt/C/foo/bar

echo
cat /proc/self/mountinfo

umount /mnt/C/foo/bar

echo
cat /proc/self/mountinfo

umount /mnt/C/foo

echo
cat /proc/self/mountinfo
EOF

$ chmod +x ./viro1.sh
$ unshare -Urm ./viro1.sh

At least when I run the above on a kernel with and without my patch under discussion all I see different is mnt_id of the parent. Which is exactly what we should expect from this change.

Did I make a mistake in creating my script?

Or are you referring to the fact that mount_propagation_busy is just plain buggy?

Quoted 3 lines

OTOH, the case of multiple slip-under (Z is slave of Y, which is a slave of X, mount on Z, then mount of Y, then mount on X) the check for being busy would do very odd things.

I don't see what you are referring to. Reposting shortly.

Eric

maintainer
#11 lore raw

Re: [REVIEW][PATCH] mnt: Tuck mounts under others instead of creating shadow/side mounts.

Quoted 8 lines · Eric W. Biederman
Quoted 4 lines

fs/namespace.c static int attach_recursive_mnt(struct mount *source_mnt, · line 2032

Quoted 1 line
fs/namespace.c +1 -0
+		if (child->mnt.mnt_root == smp->m_dentry) {

Explain, please. In which case is that condition _not_ satisfied, and what should happen i

When a tree is grafted in that condition does not apply to the lower leaves of the tree. At the same time nothing needs to be done for those leaves. Only the primary mountpoint needs to worry about tucking.

How in hell would those lower leaves end up on the list in
attach_recursive_mnt()? IDGI...

Quoted 10 lines
Quoted 6 lines

fs/pnode.c static void mark_umount_candidates(struct mount *mnt) · line 410

Quoted 2 lines
fs/pnode.c +1 -0
+		if (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m)) {
 			SET_MNT_MARK(child);

Reread the condition, please... And yes, I realize that original is also rather odd; at a guess it was meant to be !(, not (!, but that's just a guess - it's your code, IIRC.

The intent is to find all trees that we can unmount where the point at which the tree meets the rest of the mounts is not locked.

Quoted 6 lines

The mark is later used to see if it ok to unmount a mount or if we will reveal information to userspace (by breaking a lock).

Therefore the mark needs to be set if the mount is unlocked, and recursively the mark needs to be set for every child of that mount where the mark is set (the second condition).

*blink*

You have "if mount is not locked - mark it; if mount is already marked - mark it again". The latter part (|| IS_MNT_MARKED(mnt), that is) looks very odd, won't you agree? What the hell was that (its counterpart in the earlier code) about?

I could understand something along the lines "mark it unless it's locked or already marked", but your code is "mark it if it's not locked *or* if it's already marked". Makes no sense in that form.

Quoted 19 lines
Quoted 10 lines

FWIW, the my main worry here is your handling of the umount. For example, what happens if
* something is mounted on A (m1)
* something else is mounted on A/bar (m2)
* D is a slave of C
* something has been mounted on D/foo (n)
* you do mount --rbind A C/foo (m1' on C/foo, m2' on m1'/bar, m1'' interposed on D/foo under n, m2'' on m1''/bar,
m1'' slave of m1', m2'' slave of m2)

Quoted 8 lines

* you make C/foo and C/foo/bar private (m1'' and m2'' are not getting propagation from m1' and m2' anymore)
* you umount C/foo/bar (m2' is unmounted)
* you umount C/foo
m1' gets unmounted, all right, but what of m1''? D is a slave of C, so we get propagation of umount from C/foo to D/foo; m1'' still has m2'' attached to it. AFAICS, your logics will happily slip m1'' from under n (assuming that n itself is not busy), and leak both m1'' and m2''.

Quoted 2 lines

Yes. This is exactly the same behavior we have today without my patch. The only difference is who is the parent mount.

Not quite. In the current tree m1'' should get stuck there (and be exposed when n gets unmounted); AFAICS, your change will have it kicked out, with m2'' still attached and still contributing to refcount of m1''.

I might be missing something (and I hadn't checked your script - right now I'm at 16 hours of uptime after only 4 hours of sleep). Will take a look at that after I grab some sleep...

#12 lore raw

Re: [REVIEW][PATCH] mnt: Tuck mounts under others instead of creating shadow/side mounts.

Quoted 6 lines · Al Viro
Quoted 5 lines · Eric W. Biederman
Quoted 4 lines

fs/namespace.c static int attach_recursive_mnt(struct mount *source_mnt, · line 2032

Quoted 1 line
fs/namespace.c +1 -0
+		if (child->mnt.mnt_root == smp->m_dentry) {

Explain, please. In which case is that condition _not_ satisfied, and what should happen i

Quoted 6 lines
Quoted 3 lines

When a tree is grafted in that condition does not apply to the lower leaves of the tree. At the same time nothing needs to be done for those leaves. Only the primary mountpoint needs to worry about tucking.

How in hell would those lower leaves end up on the list in
attach_recursive_mnt()? IDGI...

The submounts of a mount tree that is being attached need to have commit_tree called on them to attach them to a mount namespace.

Quoted 18 lines
Quoted 11 lines
Quoted 7 lines

fs/pnode.c static void mark_umount_candidates(struct mount *mnt) · line 410

Quoted 3 lines
fs/pnode.c +1 -0
+		if (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m)) {
 			SET_MNT_MARK(child);

Reread the condition, please... And yes, I realize that original is also rather odd; at a guess it was meant to be !(, not (!, but that's just a guess - it's your code, IIRC.

The intent is to find all trees that we can unmount where the point at which the tree meets the rest of the mounts is not locked.

Quoted 6 lines

The mark is later used to see if it ok to unmount a mount or if we will reveal information to userspace (by breaking a lock).

Therefore the mark needs to be set if the mount is unlocked, and recursively the mark needs to be set for every child of that mount where the mark is set (the second condition).

Quoted 6 lines

*blink*

You have "if mount is not locked - mark it; if mount is already marked - mark it again". The latter part (|| IS_MNT_MARKED(mnt), that is) looks very odd, won't you agree? What the hell was that (its counterpart in the earlier code) about?

Not mark it again. If the parent is marked mark the child.

This is about finding subtrees where the root of the subree is unlocked but the children may be locked to that root. Still we can safely unmount the entire subtree without revealing anything to userspace.

The walk is designed to happen from parent to the child mounts.

Quoted 26 lines

I could understand something along the lines "mark it unless it's locked or already marked", but your code is "mark it if it's not locked *or* if it's already marked". Makes no sense in that form.

Quoted 19 lines
Quoted 10 lines

FWIW, the my main worry here is your handling of the umount. For example, what happens if
* something is mounted on A (m1)
* something else is mounted on A/bar (m2)
* D is a slave of C
* something has been mounted on D/foo (n)
* you do mount --rbind A C/foo (m1' on C/foo, m2' on m1'/bar, m1'' interposed on D/foo under n, m2'' on m1''/bar,
m1'' slave of m1', m2'' slave of m2)

Quoted 8 lines

* you make C/foo and C/foo/bar private (m1'' and m2'' are not getting propagation from m1' and m2' anymore)
* you umount C/foo/bar (m2' is unmounted)
* you umount C/foo
m1' gets unmounted, all right, but what of m1''? D is a slave of C, so we get propagation of umount from C/foo to D/foo; m1'' still has m2'' attached to it. AFAICS, your logics will happily slip m1'' from under n (assuming that n itself is not busy), and leak both m1'' and m2''.

Quoted 2 lines

Yes. This is exactly the same behavior we have today without my patch. The only difference is who is the parent mount.

Quoted 7 lines

Not quite. In the current tree m1'' should get stuck there (and be exposed when n gets unmounted); AFAICS, your change will have it kicked out, with m2'' still attached and still contributing to refcount of m1''.

I might be missing something (and I hadn't checked your script - right now I'm at 16 hours of uptime after only 4 hours of sleep). Will take a look at that after I grab some sleep...

Please look with fresh rested eyes. I will see about posting a new version of the patch shortly.

Eric

#13 lore raw

PATCH 1/2 mnt: Fix propagate_mount_busy to notice all cases of busy mounts.

When I look at what propagate_mount_busy is trying to do and I look at the code closely I discover there is a great disconnect between the two. In the ordinary non-propagation case propagate_mount_busy has been verifying that there are no submounts and that there are no extraneous references on the mount.

For mounts that the unmount would propagate to propagate_mount_busy has been verifying that there are no extraneous references only if there are no submounts. Which is nonsense.

Thefore rework the logic in propgate_mount_busy so that for each mount it examines it considers that mount busy if that mount has children or if there are extraneous references to that mount.

While this check was incorrect we could leak mounts instead of simply failing umount.

Fixes a05964f3917c ("[PATCH] shared mounts handling: umount")

---

If you don't figure this fix is worth it after all of this time please let me know. This feels like the proper thing to do, and I don't expect it will break anyone to fix this.

 fs/pnode.c | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)
fs/pnode.c +6 -5
index 06a793f4ae38..12fafa711114 100644
@@ -344,7 +344,6 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 {
 	struct mount *m, *child;
 	struct mount *parent = mnt->mnt_parent;
-	int ret = 0;
 
 	if (mnt == parent)
 		return do_refcount_check(mnt, refcnt);
@@ -360,11 +359,13 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 	for (m = propagation_next(parent, parent); m;
 	     		m = propagation_next(m, parent)) {
 		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
-		if (child && list_empty(&child->mnt_mounts) &&
-		    (ret = do_refcount_check(child, 1)))
-			break;
+		if (!child)
+			continue;
+		if (!list_empty(&child->mnt_mounts) ||
+		    do_refcount_check(child, 1))
+			return 1;
 	}
-	return ret;
+	return 0;
 }
 
 /*
Signature · 1 line
2.10.1
#14 lore raw

PATCH 2/2 mnt: Tuck mounts under others instead of creating shadow/side mounts.

Ever since mount propagation was introduced in cases where a mount in propagated to parent mount mountpoint pair that is already in use the code has placed the new mount behind the old mount in the mount hash table.

This implementation detail is problematic as it allows creating arbitrary length mount hash chains.

Furthermore it invalidates the constraint maintained elsewhere in the mount code that a parent mount and a mountpoint pair will have exactly one mount upon them. Making it hard to deal with and to talk about this special case in the mount code.

Modify mount propagation to notice when there is already a mount at the parent mount and mountpoint where a new mount is propagating to and place that preexisting mount on top of the new mount.

Modify unmount propagation to notice when a mount that is being unmounted has another mount on top of it (and no other children), and to replace the unmounted mount with the mount on top of it.

Move the MNT_UMUONT test from __lookup_mnt_last into __propagate_umount as that is the only call of __lookup_mnt_last where MNT_UMOUNT may be set on any mount visible in the mount hash table.

These modifications allow:
- __lookup_mnt_last to be removed.
- attach_shadows to be renamed __attach_mnt and its shadow handling to be removed.
- commit_tree to be simplified
- copy_tree to be simplified

The result is an easier to understand tree of mounts that does not allow creation of arbitrary length hash chains in the mount hash table.

v2: Updated to mnt_change_mountpoint to not call dput or mntput and instead to decrement the counts directly. It is guaranteed that there will be other references when mnt_change_mountpoint is called so this is safe.

v3: Moved put_mountpoint under mount_lock in attach_recursive_mnt
As the locking in fs/namespace.c changed between v2 and v3.

v4: Reworked the logic in propagate_mount_busy and __propagate_umount
that detects when a mount completely covers another mount.

Fixes b90fa9ae8f51 ("[PATCH] shared mount handling: bind and rbind")
---
 fs/mount.h     |   1 -
 fs/namespace.c | 114 +++++++++++++++++++++++++++++++--------------------------
 fs/pnode.c     |  55 +++++++++++++++++++++++-----
 fs/pnode.h     |   2 +
 4 files changed, 111 insertions(+), 61 deletions(-)
fs/mount.h +0 -1
index 2c856fc47ae3..2826543a131d 100644
@@ -89,7 +89,6 @@ static inline int is_mounted(struct vfsmount *mnt)
 }
 
 extern struct mount *__lookup_mnt(struct vfsmount *, struct dentry *);
-extern struct mount *__lookup_mnt_last(struct vfsmount *, struct dentry *);
 
 extern int __legitimize_mnt(struct vfsmount *, unsigned);
 extern bool legitimize_mnt(struct vfsmount *, unsigned);
fs/namespace.c +63 -51

185 lines not on this page · raw message

fs/pnode.c +46 -9

100 lines not on this page · raw message

fs/pnode.h +2 -0
index 550f5a8b4fcf..dc87e65becd2 100644
@@ -49,6 +49,8 @@ int get_dominating_id(struct mount *mnt, const struct path *root);
 unsigned int mnt_get_count(struct mount *mnt);
 void mnt_set_mountpoint(struct mount *, struct mountpoint *,
 			struct mount *);
+void mnt_change_mountpoint(struct mount *parent, struct mountpoint *mp,
+			   struct mount *mnt);
 struct mount *copy_tree(struct mount *, struct dentry *, int);
 bool is_path_reachable(struct mount *, struct dentry *,
 			 const struct path *root);
Signature · 1 line
2.10.1
maintainer
#15 lore raw

Re: [REVIEW][PATCH 2/2] mnt: Tuck mounts under others instead of creating shadow/side mounts.

fs/namespace.c static int attach_recursive_mnt(struct mount *source_mnt, · line 2038

Quoted 7 lines · Eric W. Biederman
fs/namespace.c +7 -0
+		if (child->mnt.mnt_root == smp->m_dentry) {
+			struct mount *q;
+			q = __lookup_mnt(&child->mnt_parent->mnt,
+					 child->mnt_mountpoint);
+			if (q)
+				mnt_change_mountpoint(child, smp, q);
+		}

This is wrong; condition will be true for *all* mounts seen by that loop. Feel free to add else WARN_ON(1); to the line above and try to trigger it. You are misinterpreting what propagate_mnt() and commit_tree() are doing - the loop in commit_tree() goes through the submounts and sets ->mnt_ns on those. The of the fields is already set up by that point. For roots of those copies we need to set ->mnt_hash/->mnt_child as well, but for all submounts it's already been done by copy_tree(). Again, commit_tree() is called once per secondary copy of source tree, not once per created mount.

fs/pnode.c int propagate_mnt(struct mount *dest_mnt, struct mountpoint *dest_mp, · line 334

Quoted 10 lines
fs/pnode.c +10 -0
+static struct mount *find_topper(struct mount *mnt)
+{
+	/* If there is exactly one mount covering mnt completely return it. */
+	struct mount *child;
+
+	if (!list_is_singular(&mnt->mnt_mounts))
+		return NULL;
+
+	child = list_first_entry(&mnt->mnt_mounts, struct mount, mnt_child);
+	if (child->mnt_parent != mnt ||

The first part can't happen. Turn that into WARN_ON(child->mnt_parent != mnt) if you wish, but that never occurs unless the data structures are corrupted.

Quoted 25 lines
fs/pnode.c +7 -4
+	    child->mnt_mountpoint != mnt->mnt.mnt_root)
+		return NULL;
@@ -342,7 +358,7 @@ static inline int do_refcount_check(struct mount *mnt, int count)
  */
 int propagate_mount_busy(struct mount *mnt, int refcnt)
 {
-	struct mount *m, *child;
+	struct mount *m, *child, *topper;
 	struct mount *parent = mnt->mnt_parent;
 
 	if (mnt == parent)
@@ -358,11 +374,21 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 
 	for (m = propagation_next(parent, parent); m;
 	     		m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
+		int count = 1;
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
 		if (!child)
 			continue;
-		if (!list_empty(&child->mnt_mounts) ||
-		    do_refcount_check(child, 1))
+
+		/* Is there exactly one mount on the child that covers

fs/pnode.c int propagate_mount_busy(struct mount *mnt, int refcnt) · line 392

Quoted 10 lines
fs/pnode.c +9 -0
+		 * it completely whose reference should be ignored?
+		 */
+		topper = find_topper(child);
+		if (topper)
+			count += 1;
+		else if (!list_empty(&child->mnt_mounts))
+			return 1;
+
+		if (do_refcount_check(child, count))
 			return 1;

Again, subject to the comments re semantics change (see the reply to previous patch).

Quoted 6 lines
fs/pnode.c +2 -0
@@ -431,6 +459,15 @@ static void __propagate_umount(struct mount *mnt)
 		if (!child || !IS_MNT_MARKED(child))
 			continue;
 		CLEAR_MNT_MARK(child);
+
+		/* If there is exactly one mount covering all of child

fs/pnode.c static void __propagate_umount(struct mount *mnt) · line 473

Quoted 10 lines
fs/pnode.c +7 -0
+		 * replace child with that mount.
+		 */
+		topper = find_topper(child);
+		if (topper)
+			mnt_change_mountpoint(child->mnt_parent, child->mnt_mp,
+					      topper);
+
 		if (list_empty(&child->mnt_mounts)) {
 			list_del_init(&child->mnt_child);
 			child->mnt.mnt_flags |= MNT_UMOUNT;

Umm... With fallthrough from "is completely overmounted" case? And I'm not sure I understand what that list_empty() is doing there after your previous semantics change - how _can_ we reach that point with non-empty ->mnt_mounts now?

#16 lore raw

Re: [REVIEW][PATCH 2/2] mnt: Tuck mounts under others instead of creating shadow/side mounts.

Quoted 11 lines · Al Viro

fs/namespace.c static int attach_recursive_mnt(struct mount *source_mnt, · line 2038

Quoted 7 lines · Eric W. Biederman
fs/namespace.c +7 -0
+		if (child->mnt.mnt_root == smp->m_dentry) {
+			struct mount *q;
+			q = __lookup_mnt(&child->mnt_parent->mnt,
+					 child->mnt_mountpoint);
+			if (q)
+				mnt_change_mountpoint(child, smp, q);
+		}

This is wrong; condition will be true for *all* mounts seen by that loop.

Quoted 8 lines

Feel free to add else WARN_ON(1); to the line above and try to trigger it. You are misinterpreting what propagate_mnt() and commit_tree() are doing - the loop in commit_tree() goes through the submounts and sets ->mnt_ns on those. The of the fields is already set up by that point. For roots of those copies we need to set ->mnt_hash/->mnt_child as well, but for all submounts it's already been done by copy_tree(). Again, commit_tree() is called once per secondary copy of source tree, not once per created mount.

*doh*

Quoted 6 lines
Quoted 6 lines
fs/pnode.c +6 -0
+static struct mount *find_topper(struct mount *mnt)
+{
+	/* If there is exactly one mount covering mnt completely return it. */
+	struct mount *child;
+
+	if (!list_is_singular(&mnt->mnt_mounts))
Quoted 8 lines

fs/pnode.c int propagate_mnt(struct mount *dest_mnt, struct mountpoint *dest_mp, · line 334

Quoted 4 lines
fs/pnode.c +4 -0
+		return NULL;
+
+	child = list_first_entry(&mnt->mnt_mounts, struct mount, mnt_child);
+	if (child->mnt_parent != mnt ||

The first part can't happen. Turn that into WARN_ON(child->mnt_parent != mnt) if you wish, but that never occurs unless the data structures are corrupted.

Agreed.

Quoted 15 lines
Quoted 15 lines
fs/pnode.c +11 -0
+	    child->mnt_mountpoint != mnt->mnt.mnt_root)
+		return NULL;
@@ -431,6 +459,15 @@ static void __propagate_umount(struct mount *mnt)
 		if (!child || !IS_MNT_MARKED(child))
 			continue;
 		CLEAR_MNT_MARK(child);
+
+		/* If there is exactly one mount covering all of child
+		 * replace child with that mount.
+		 */
+		topper = find_topper(child);
+		if (topper)
+			mnt_change_mountpoint(child->mnt_parent, child->mnt_mp,
+					      topper);
+
Quoted 8 lines

fs/pnode.c static void __propagate_umount(struct mount *mnt) · line 473

Quoted 3 lines
fs/pnode.c +0 -0
 		if (list_empty(&child->mnt_mounts)) {
 			list_del_init(&child->mnt_child);
 			child->mnt.mnt_flags |= MNT_UMOUNT;

Umm... With fallthrough from "is completely overmounted" case? And I'm not sure I understand what that list_empty() is doing there after your previous semantics change - how _can_ we reach that point with non-empty ->mnt_mounts now?

With the semantic change to propagate_mnt_busy, to reach the list_empty with a non-empty mnt_mounts requires the a umount(MNT_DETACH) as that skips the propagate_mnt_busy call.

Without the semantic change it is even easier to get there.

A respin of this patch without the semantic change in a moment.

Eric

#17 lore raw

PATCH v5 1/1 mnt: Tuck mounts under others instead of creating shadow/side mounts.

In reply to Al Viro

Ever since mount propagation was introduced in cases where a mount in propagated to parent mount mountpoint pair that is already in use the code has placed the new mount behind the old mount in the mount hash table.

This implementation detail is problematic as it allows creating arbitrary length mount hash chains.

Furthermore it invalidates the constraint maintained elsewhere in the mount code that a parent mount and a mountpoint pair will have exactly one mount upon them. Making it hard to deal with and to talk about this special case in the mount code.

Modify mount propagation to notice when there is already a mount at the parent mount and mountpoint where a new mount is propagating to and place that preexisting mount on top of the new mount.

Modify unmount propagation to notice when a mount that is being unmounted has another mount on top of it (and no other children), and to replace the unmounted mount with the mount on top of it.

Move the MNT_UMUONT test from __lookup_mnt_last into __propagate_umount as that is the only call of __lookup_mnt_last where MNT_UMOUNT may be set on any mount visible in the mount hash table.

These modifications allow:
- __lookup_mnt_last to be removed.
- attach_shadows to be renamed __attach_mnt and its shadow handling to be removed.
- commit_tree to be simplified
- copy_tree to be simplified

The result is an easier to understand tree of mounts that does not allow creation of arbitrary length hash chains in the mount hash table.

v2: Updated to mnt_change_mountpoint to not call dput or mntput and instead to decrement the counts directly. It is guaranteed that there will be other references when mnt_change_mountpoint is called so this is safe.

v3: Moved put_mountpoint under mount_lock in attach_recursive_mnt
As the locking in fs/namespace.c changed between v2 and v3.

v4: Reworked the logic in propagate_mount_busy and __propagate_umount
that detects when a mount completely covers another mount.

v5: Removed unnecessary tests whose result is always true in
find_topper and attach_recursive_mnt.

Fixes b90fa9ae8f51 ("[PATCH] shared mount handling: bind and rbind")
---
 fs/mount.h     |   1 -
 fs/namespace.c | 110 +++++++++++++++++++++++++++++++--------------------------
 fs/pnode.c     |  61 +++++++++++++++++++++++++-------
 fs/pnode.h     |   2 ++
 4 files changed, 111 insertions(+), 63 deletions(-)
fs/mount.h +0 -1
index 2c856fc47ae3..2826543a131d 100644
@@ -89,7 +89,6 @@ static inline int is_mounted(struct vfsmount *mnt)
 }
 
 extern struct mount *__lookup_mnt(struct vfsmount *, struct dentry *);
-extern struct mount *__lookup_mnt_last(struct vfsmount *, struct dentry *);
 
 extern int __legitimize_mnt(struct vfsmount *, unsigned);
 extern bool legitimize_mnt(struct vfsmount *, unsigned);
fs/namespace.c +60 -50

182 lines not on this page · raw message

fs/pnode.c +49 -12

106 lines not on this page · raw message

fs/pnode.h +2 -0
index 550f5a8b4fcf..dc87e65becd2 100644
@@ -49,6 +49,8 @@ int get_dominating_id(struct mount *mnt, const struct path *root);
 unsigned int mnt_get_count(struct mount *mnt);
 void mnt_set_mountpoint(struct mount *, struct mountpoint *,
 			struct mount *);
+void mnt_change_mountpoint(struct mount *parent, struct mountpoint *mp,
+			   struct mount *mnt);
 struct mount *copy_tree(struct mount *, struct dentry *, int);
 bool is_path_reachable(struct mount *, struct dentry *,
 			 const struct path *root);
Signature · 1 line
2.10.1
#18 lore raw

Re: [PATCH v5] mnt: Tuck mounts under others instead of creating shadow/side mounts.

Quoted 312 lines · Eric W. Biederman

Ever since mount propagation was introduced in cases where a mount in propagated to parent mount mountpoint pair that is already in use the code has placed the new mount behind the old mount in the mount hash table.

This implementation detail is problematic as it allows creating arbitrary length mount hash chains.

Furthermore it invalidates the constraint maintained elsewhere in the mount code that a parent mount and a mountpoint pair will have exactly one mount upon them. Making it hard to deal with and to talk about this special case in the mount code.

Modify mount propagation to notice when there is already a mount at the parent mount and mountpoint where a new mount is propagating to and place that preexisting mount on top of the new mount.

Modify unmount propagation to notice when a mount that is being unmounted has another mount on top of it (and no other children), and to replace the unmounted mount with the mount on top of it.

Move the MNT_UMUONT test from __lookup_mnt_last into __propagate_umount as that is the only call of __lookup_mnt_last where MNT_UMOUNT may be set on any mount visible in the mount hash table.

These modifications allow:
- __lookup_mnt_last to be removed.
- attach_shadows to be renamed __attach_mnt and its shadow handling to be removed.
- commit_tree to be simplified
- copy_tree to be simplified

The result is an easier to understand tree of mounts that does not allow creation of arbitrary length hash chains in the mount hash table.

v2: Updated to mnt_change_mountpoint to not call dput or mntput and instead to decrement the counts directly. It is guaranteed that there will be other references when mnt_change_mountpoint is called so this is safe.

v3: Moved put_mountpoint under mount_lock in attach_recursive_mnt
As the locking in fs/namespace.c changed between v2 and v3.

v4: Reworked the logic in propagate_mount_busy and __propagate_umount
that detects when a mount completely covers another mount.

v5: Removed unnecessary tests whose result is always true in
find_topper and attach_recursive_mnt.

Fixes b90fa9ae8f51 ("[PATCH] shared mount handling: bind and rbind")
---
 fs/mount.h     |   1 -
 fs/namespace.c | 110 +++++++++++++++++++++++++++++++--------------------------
 fs/pnode.c     |  61 +++++++++++++++++++++++++-------
 fs/pnode.h     |   2 ++
 4 files changed, 111 insertions(+), 63 deletions(-)
fs/mount.h +0 -1
index 2c856fc47ae3..2826543a131d 100644
@@ -89,7 +89,6 @@ static inline int is_mounted(struct vfsmount *mnt)
 }
 
 extern struct mount *__lookup_mnt(struct vfsmount *, struct dentry *);
-extern struct mount *__lookup_mnt_last(struct vfsmount *, struct dentry *);
 
 extern int __legitimize_mnt(struct vfsmount *, unsigned);
 extern bool legitimize_mnt(struct vfsmount *, unsigned);
fs/namespace.c +60 -50
index 487ba30bb5c6..8a3b6c1b16ff 100644
@@ -637,28 +637,6 @@ struct mount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)
 }
 
 /*
- * find the last mount at @dentry on vfsmount @mnt.
- * mount_lock must be held.
- */
-struct mount *__lookup_mnt_last(struct vfsmount *mnt, struct dentry *dentry)
-{
-	struct mount *p, *res = NULL;
-	p = __lookup_mnt(mnt, dentry);
-	if (!p)
-		goto out;
-	if (!(p->mnt.mnt_flags & MNT_UMOUNT))
-		res = p;
-	hlist_for_each_entry_continue(p, mnt_hash) {
-		if (&p->mnt_parent->mnt != mnt || p->mnt_mountpoint != dentry)
-			break;
-		if (!(p->mnt.mnt_flags & MNT_UMOUNT))
-			res = p;
-	}
-out:
-	return res;
-}
-
-/*
  * lookup_mnt - Return the first child mount mounted at path
  *
  * "First" means first mounted chronologically.  If you create the
@@ -878,6 +856,13 @@ void mnt_set_mountpoint(struct mount *mnt,
 	hlist_add_head(&child_mnt->mnt_mp_list, &mp->m_list);
 }
 
+static void __attach_mnt(struct mount *mnt, struct mount *parent)
+{
+	hlist_add_head_rcu(&mnt->mnt_hash,
+			   m_hash(&parent->mnt, mnt->mnt_mountpoint));
+	list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
+}
+
 /*
  * vfsmount lock must be held for write
  */
@@ -886,28 +871,45 @@ static void attach_mnt(struct mount *mnt,
 			struct mountpoint *mp)
 {
 	mnt_set_mountpoint(parent, mp, mnt);
-	hlist_add_head_rcu(&mnt->mnt_hash, m_hash(&parent->mnt, mp->m_dentry));
-	list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
+	__attach_mnt(mnt, parent);
 }
 
-static void attach_shadowed(struct mount *mnt,
-			struct mount *parent,
-			struct mount *shadows)
+void mnt_change_mountpoint(struct mount *parent, struct mountpoint *mp, struct mount *mnt)
 {
-	if (shadows) {
-		hlist_add_behind_rcu(&mnt->mnt_hash, &shadows->mnt_hash);
-		list_add(&mnt->mnt_child, &shadows->mnt_child);
-	} else {
-		hlist_add_head_rcu(&mnt->mnt_hash,
-				m_hash(&parent->mnt, mnt->mnt_mountpoint));
-		list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
-	}
+	struct mountpoint *old_mp = mnt->mnt_mp;
+	struct dentry *old_mountpoint = mnt->mnt_mountpoint;
+	struct mount *old_parent = mnt->mnt_parent;
+
+	list_del_init(&mnt->mnt_child);
+	hlist_del_init(&mnt->mnt_mp_list);
+	hlist_del_init_rcu(&mnt->mnt_hash);
+
+	attach_mnt(mnt, parent, mp);
+
+	put_mountpoint(old_mp);
+
+	/*
+	 * Safely avoid even the suggestion this code might sleep or
+	 * lock the mount hash by taking advantage of the knowledge that
+	 * mnt_change_mountpoint will not release the final reference
+	 * to a mountpoint.
+	 *
+	 * During mounting, the mount passed in as the parent mount will
+	 * continue to use the old mountpoint and during unmounting, the
+	 * old mountpoint will continue to exist until namespace_unlock,
+	 * which happens well after mnt_change_mountpoint.
+	 */
+	spin_lock(&old_mountpoint->d_lock);
+	old_mountpoint->d_lockref.count--;
+	spin_unlock(&old_mountpoint->d_lock);
+
+	mnt_add_count(old_parent, -1);
 }
 
 /*
  * vfsmount lock must be held for write
  */
-static void commit_tree(struct mount *mnt, struct mount *shadows)
+static void commit_tree(struct mount *mnt)
 {
 	struct mount *parent = mnt->mnt_parent;
 	struct mount *m;
@@ -925,7 +927,7 @@ static void commit_tree(struct mount *mnt, struct mount *shadows)
 	n->mounts += n->pending_mounts;
 	n->pending_mounts = 0;
 
-	attach_shadowed(mnt, parent, shadows);
+	__attach_mnt(mnt, parent);
 	touch_mnt_namespace(n);
 }
 
@@ -1764,7 +1766,6 @@ struct mount *copy_tree(struct mount *mnt, struct dentry *dentry,
 			continue;
 
 		for (s = r; s; s = next_mnt(s, r)) {
-			struct mount *t = NULL;
 			if (!(flag & CL_COPY_UNBINDABLE) &&
 			    IS_MNT_UNBINDABLE(s)) {
 				s = skip_mnt_tree(s);
@@ -1786,14 +1787,7 @@ struct mount *copy_tree(struct mount *mnt, struct dentry *dentry,
 				goto out;
 			lock_mount_hash();
 			list_add_tail(&q->mnt_list, &res->mnt_list);
-			mnt_set_mountpoint(parent, p->mnt_mp, q);
-			if (!list_empty(&parent->mnt_mounts)) {
-				t = list_last_entry(&parent->mnt_mounts,
-					struct mount, mnt_child);
-				if (t->mnt_mp != p->mnt_mp)
-					t = NULL;
-			}
-			attach_shadowed(q, parent, t);
+			attach_mnt(q, parent, p->mnt_mp);
 			unlock_mount_hash();
 		}
 	}
@@ -1992,10 +1986,18 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 {
 	HLIST_HEAD(tree_list);
 	struct mnt_namespace *ns = dest_mnt->mnt_ns;
+	struct mountpoint *smp;
 	struct mount *child, *p;
 	struct hlist_node *n;
 	int err;
 
+	/* Preallocate a mountpoint in case the new mounts need
+	 * to be tucked under other mounts.
+	 */
+	smp = get_mountpoint(source_mnt->mnt.mnt_root);
+	if (IS_ERR(smp))
+		return PTR_ERR(smp);
+
 	/* Is there space to add these mounts to the mount namespace? */
 	if (!parent_path) {
 		err = count_mounts(ns, source_mnt);
@@ -2022,16 +2024,19 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 		touch_mnt_namespace(source_mnt->mnt_ns);
 	} else {
 		mnt_set_mountpoint(dest_mnt, dest_mp, source_mnt);
-		commit_tree(source_mnt, NULL);
+		commit_tree(source_mnt);
 	}
 
 	hlist_for_each_entry_safe(child, n, &tree_list, mnt_hash) {
 		struct mount *q;
 		hlist_del_init(&child->mnt_hash);
-		q = __lookup_mnt_last(&child->mnt_parent->mnt,
-				      child->mnt_mountpoint);
-		commit_tree(child, q);
+		q = __lookup_mnt(&child->mnt_parent->mnt,
+				 child->mnt_mountpoint);
+		if (q)
+			mnt_change_mountpoint(child, smp, q);
+		commit_tree(child);
 	}
+	put_mountpoint(smp);
 	unlock_mount_hash();
 
 	return 0;
@@ -2046,6 +2051,11 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 	cleanup_group_ids(source_mnt, NULL);
  out:
 	ns->pending_mounts = 0;
+
+	read_seqlock_excl(&mount_lock);
+	put_mountpoint(smp);
+	read_sequnlock_excl(&mount_lock);
+
 	return err;
 }
 
fs/pnode.c +16 -5
index 06a793f4ae38..5bc7896d122a 100644
@@ -322,6 +322,21 @@ int propagate_mnt(struct mount *dest_mnt, struct mountpoint *dest_mp,
 	return ret;
 }
 
+static struct mount *find_topper(struct mount *mnt)
+{
+	/* If there is exactly one mount covering mnt completely return it. */
+	struct mount *child;
+
+	if (!list_is_singular(&mnt->mnt_mounts))
+		return NULL;
+
+	child = list_first_entry(&mnt->mnt_mounts, struct mount, mnt_child);
+	if (child->mnt_mountpoint != mnt->mnt.mnt_root)
+		return NULL;
+
+	return child;
+}
+
 /*
  * return true if the refcount is greater than count
  */
@@ -342,9 +357,8 @@ static inline int do_refcount_check(struct mount *mnt, int count)
  */
 int propagate_mount_busy(struct mount *mnt, int refcnt)
 {
-	struct mount *m, *child;
+	struct mount *m, *child, *topper;
 	struct mount *parent = mnt->mnt_parent;
-	int ret = 0;
 
 	if (mnt == parent)
 		return do_refcount_check(mnt, refcnt);
@@ -359,12 +373,24 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 
 	for (m = propagation_next(parent, parent); m;
 	     		m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
-		if (child && list_empty(&child->mnt_mounts) &&
-		    (ret = do_refcount_check(child, 1)))

fs/pnode.c int propagate_mount_busy(struct mount *mnt, int refcnt) · line 384

Quoted 10 lines
fs/pnode.c +9 -1
-			break;
+		int count = 1;
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
+		if (!child)
+			continue;
+
+		/* Is there exactly one mount on the child that covers
+		 * it completely whose reference should be ignored?
+		 */
+		topper = find_topper(child);

This is tricky. I understand it is trying to identify the case where a mount got tucked-in because of propagation. But this will not distinguish the case where a mount got over-mounted genuinely, not because of propagation, but because of explicit user action.

example:

case 1: (explicit user action)
B is a slave of A
mount something on A/a , it will propagate to B/a and than mount something on B/a

case 2: (tucked mount)
B is a slave of A
mount something on B/a
and than mount something on A/a

Both case 1 and case 2 lead to the same mount configuration.

however 'umount A/a' in case 1 should fail. and 'umount A/a' in case 2 should pass.

Right? in other words, umounts of 'tucked mounts' should pass(case 2).

	whereas umounts of mounts on which overmounts exist should
		fail.(case 1)

maybe we need a flag to identify tucked mounts?

RP

Quoted 78 lines
fs/pnode.c +24 -6
+		if (topper)
+			count += 1;
+		else if (!list_empty(&child->mnt_mounts))
+			continue;
+
+		if (do_refcount_check(child, count))
+			return 1;
 	}
-	return ret;
+	return 0;
 }
 
 /*
@@ -381,7 +407,7 @@ void propagate_mount_unlock(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
 		if (child)
 			child->mnt.mnt_flags &= ~MNT_LOCKED;
 	}
@@ -399,9 +425,11 @@ static void mark_umount_candidates(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-		struct mount *child = __lookup_mnt_last(&m->mnt,
+		struct mount *child = __lookup_mnt(&m->mnt,
 						mnt->mnt_mountpoint);
-		if (child && (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m))) {
+		if (!child || (child->mnt.mnt_flags & MNT_UMOUNT))
+			continue;
+		if (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m)) {
 			SET_MNT_MARK(child);
 		}
 	}
@@ -420,8 +448,8 @@ static void __propagate_umount(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-
-		struct mount *child = __lookup_mnt_last(&m->mnt,
+		struct mount *topper;
+		struct mount *child = __lookup_mnt(&m->mnt,
 						mnt->mnt_mountpoint);
 		/*
 		 * umount the child only if the child has no children
@@ -430,6 +458,15 @@ static void __propagate_umount(struct mount *mnt)
 		if (!child || !IS_MNT_MARKED(child))
 			continue;
 		CLEAR_MNT_MARK(child);
+
+		/* If there is exactly one mount covering all of child
+		 * replace child with that mount.
+		 */
+		topper = find_topper(child);
+		if (topper)
+			mnt_change_mountpoint(child->mnt_parent, child->mnt_mp,
+					      topper);
+
 		if (list_empty(&child->mnt_mounts)) {
 			list_del_init(&child->mnt_child);
 			child->mnt.mnt_flags |= MNT_UMOUNT;
fs/pnode.h +2 -0
index 550f5a8b4fcf..dc87e65becd2 100644
@@ -49,6 +49,8 @@ int get_dominating_id(struct mount *mnt, const struct path *root);
 unsigned int mnt_get_count(struct mount *mnt);
 void mnt_set_mountpoint(struct mount *, struct mountpoint *,
 			struct mount *);
+void mnt_change_mountpoint(struct mount *parent, struct mountpoint *mp,
+			   struct mount *mnt);
 struct mount *copy_tree(struct mount *, struct dentry *, int);
 bool is_path_reachable(struct mount *, struct dentry *,
 			 const struct path *root);
Signature · 1 line
2.10.1
Signature · 1 line
Ram Pai
#19 lore raw

Re: [PATCH v5] mnt: Tuck mounts under others instead of creating shadow/side mounts.

Quoted 39 lines · Ram Pai
Quoted 7 lines
fs/pnode.c +0 -3
@@ -359,12 +373,24 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 
 	for (m = propagation_next(parent, parent); m;
 	     		m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
-		if (child && list_empty(&child->mnt_mounts) &&
-		    (ret = do_refcount_check(child, 1)))

fs/pnode.c int propagate_mount_busy(struct mount *mnt, int refcnt) · line 384

Quoted 10 lines
fs/pnode.c +9 -1
-			break;
+		int count = 1;
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
+		if (!child)
+			continue;
+
+		/* Is there exactly one mount on the child that covers
+		 * it completely whose reference should be ignored?
+		 */
+		topper = find_topper(child);

This is tricky. I understand it is trying to identify the case where a mount got tucked-in because of propagation. But this will not distinguish the case where a mount got over-mounted genuinely, not because of propagation, but because of explicit user action.

example:

case 1: (explicit user action)
B is a slave of A
mount something on A/a , it will propagate to B/a and than mount something on B/a

case 2: (tucked mount)
B is a slave of A
mount something on B/a
and than mount something on A/a

Both case 1 and case 2 lead to the same mount configuration.

Quoted 6 lines

however 'umount A/a' in case 1 should fail. and 'umount A/a' in case 2 should pass.

Right? in other words, umounts of 'tucked mounts' should pass(case 2).

	whereas umounts of mounts on which overmounts exist should
		fail.(case 1)

Looking at your example. I agree that case 1 will fail today. However my actual expectation would be for both mount configurations to behave the same. In both cases something has been explicitly mounted on B/a and something has propagated to B/a. In both cases the mount on top is what was explicitly mounted, and the mount below is what was propagated to B/a.

I don't see why the order of operations should matter.

Quoted 1 line

maybe we need a flag to identify tucked mounts?

To preserve our exact current semantics yes.

The mount configurations that are delibearately constructed that I am aware of are comparatively simple. I don't think anyone has even taken advantage of the shadow/side mounts at this point. I made a reasonable effort to find out and no one was even aware they existed. Much less what they were. And certainly no one I talked to could find code that used them.

So I think we are fine with a very modest semantic change here. Especially one that appears to make the semantics more consistent and predictable.

I also expect the checkpoint/restart folks will appreciate the change as by giving them options it will make it easier to reconstruct complicated mount trees.

Eric

Quoted 74 lines
Quoted 74 lines
fs/pnode.c +20 -6
+
+		if (do_refcount_check(child, count))
+			return 1;
 	}
-	return ret;
+	return 0;
 }
 
 /*
@@ -381,7 +407,7 @@ void propagate_mount_unlock(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
 		if (child)
 			child->mnt.mnt_flags &= ~MNT_LOCKED;
 	}
@@ -399,9 +425,11 @@ static void mark_umount_candidates(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-		struct mount *child = __lookup_mnt_last(&m->mnt,
+		struct mount *child = __lookup_mnt(&m->mnt,
 						mnt->mnt_mountpoint);
-		if (child && (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m))) {
+		if (!child || (child->mnt.mnt_flags & MNT_UMOUNT))
+			continue;
+		if (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m)) {
 			SET_MNT_MARK(child);
 		}
 	}
@@ -420,8 +448,8 @@ static void __propagate_umount(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-
-		struct mount *child = __lookup_mnt_last(&m->mnt,
+		struct mount *topper;
+		struct mount *child = __lookup_mnt(&m->mnt,
 						mnt->mnt_mountpoint);
 		/*
 		 * umount the child only if the child has no children
@@ -430,6 +458,15 @@ static void __propagate_umount(struct mount *mnt)
 		if (!child || !IS_MNT_MARKED(child))
 			continue;
 		CLEAR_MNT_MARK(child);
+
+		/* If there is exactly one mount covering all of child
+		 * replace child with that mount.
+		 */
+		topper = find_topper(child);
+		if (topper)
+			mnt_change_mountpoint(child->mnt_parent, child->mnt_mp,
+					      topper);
+
 		if (list_empty(&child->mnt_mounts)) {
 			list_del_init(&child->mnt_child);
 			child->mnt.mnt_flags |= MNT_UMOUNT;
fs/pnode.h +2 -0
index 550f5a8b4fcf..dc87e65becd2 100644
@@ -49,6 +49,8 @@ int get_dominating_id(struct mount *mnt, const struct path *root);
 unsigned int mnt_get_count(struct mount *mnt);
 void mnt_set_mountpoint(struct mount *, struct mountpoint *,
 			struct mount *);
+void mnt_change_mountpoint(struct mount *parent, struct mountpoint *mp,
+			   struct mount *mnt);
 struct mount *copy_tree(struct mount *, struct dentry *, int);
 bool is_path_reachable(struct mount *, struct dentry *,
 			 const struct path *root);
Signature · 1 line
2.10.1
#20 lore raw

Re: [PATCH v5] mnt: Tuck mounts under others instead of creating shadow/side mounts.

Quoted 41 lines · Eric W. Biederman
Quoted 39 lines · Ram Pai
Quoted 7 lines
fs/pnode.c +0 -3
@@ -359,12 +373,24 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 
 	for (m = propagation_next(parent, parent); m;
 	     		m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
-		if (child && list_empty(&child->mnt_mounts) &&
-		    (ret = do_refcount_check(child, 1)))

fs/pnode.c int propagate_mount_busy(struct mount *mnt, int refcnt) · line 384

Quoted 10 lines
fs/pnode.c +9 -1
-			break;
+		int count = 1;
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
+		if (!child)
+			continue;
+
+		/* Is there exactly one mount on the child that covers
+		 * it completely whose reference should be ignored?
+		 */
+		topper = find_topper(child);

This is tricky. I understand it is trying to identify the case where a mount got tucked-in because of propagation. But this will not distinguish the case where a mount got over-mounted genuinely, not because of propagation, but because of explicit user action.

example:

case 1: (explicit user action)
B is a slave of A
mount something on A/a , it will propagate to B/a and than mount something on B/a

case 2: (tucked mount)
B is a slave of A
mount something on B/a
and than mount something on A/a

Both case 1 and case 2 lead to the same mount configuration.

Quoted 8 lines
Quoted 6 lines

however 'umount A/a' in case 1 should fail. and 'umount A/a' in case 2 should pass.

Right? in other words, umounts of 'tucked mounts' should pass(case 2).

	whereas umounts of mounts on which overmounts exist should
		fail.(case 1)

Looking at your example. I agree that case 1 will fail today.

And should continue to fail. right? Your semantics change will pass it.

Quoted 7 lines

However my actual expectation would be for both mount configurations to behave the same. In both cases something has been explicitly mounted on B/a and something has propagated to B/a. In both cases the mount on top is what was explicitly mounted, and the mount below is what was propagated to B/a.

I don't see why the order of operations should matter.

One of the subtle expectation is reversibility.

Mount followed immediately by unmount has always passed and that is the standard expectation always. Your proposed code will ensure that.

However there is one other subtle expectaton.

A mount cannot disappear if a user has explicitly mounted on top of it.

your proposed code will not meet that expectation.

In other words, these two expectations make it behave differently even when; arguably, they feel like the same configuration.

Quoted 11 lines
Quoted 2 lines

maybe we need a flag to identify tucked mounts?

To preserve our exact current semantics yes.

The mount configurations that are delibearately constructed that I am aware of are comparatively simple. I don't think anyone has even taken advantage of the shadow/side mounts at this point. I made a reasonable effort to find out and no one was even aware they existed. Much less what they were. And certainly no one I talked to could find code that used them.

But someday; even if its after a decade, someone ;) will stumble into this semantics and wonder 'why?'. Its better to get it right sooner. Sorry, I am blaming myself; for keeping some of the problems open thinking no one will bump into them.

RP

#21 lore raw

Re: [PATCH v5] mnt: Tuck mounts under others instead of creating shadow/side mounts.

Quoted 45 lines · Ram Pai
Quoted 44 lines · Eric W. Biederman
Quoted 42 lines · Ram Pai
Quoted 7 lines
fs/pnode.c +0 -3
@@ -359,12 +373,24 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 
 	for (m = propagation_next(parent, parent); m;
 	     		m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
-		if (child && list_empty(&child->mnt_mounts) &&
-		    (ret = do_refcount_check(child, 1)))

fs/pnode.c int propagate_mount_busy(struct mount *mnt, int refcnt) · line 384

Quoted 10 lines
fs/pnode.c +9 -1
-			break;
+		int count = 1;
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
+		if (!child)
+			continue;
+
+		/* Is there exactly one mount on the child that covers
+		 * it completely whose reference should be ignored?
+		 */
+		topper = find_topper(child);

This is tricky. I understand it is trying to identify the case where a mount got tucked-in because of propagation. But this will not distinguish the case where a mount got over-mounted genuinely, not because of propagation, but because of explicit user action.

example:

case 1: (explicit user action)
B is a slave of A
mount something on A/a , it will propagate to B/a and than mount something on B/a

case 2: (tucked mount)
B is a slave of A
mount something on B/a
and than mount something on A/a

Both case 1 and case 2 lead to the same mount configuration.

however 'umount A/a' in case 1 should fail. and 'umount A/a' in case 2 should pass.

Quoted 7 lines
Quoted 5 lines
Quoted 3 lines

Right? in other words, umounts of 'tucked mounts' should pass(case 2).

	whereas umounts of mounts on which overmounts exist should
		fail.(case 1)

Looking at your example. I agree that case 1 will fail today.

And should continue to fail. right? Your semantics change will pass it.

I don't see why it should continue to fail.

Quoted 13 lines
Quoted 7 lines

However my actual expectation would be for both mount configurations to behave the same. In both cases something has been explicitly mounted on B/a and something has propagated to B/a. In both cases the mount on top is what was explicitly mounted, and the mount below is what was propagated to B/a.

I don't see why the order of operations should matter.

One of the subtle expectation is reversibility.

Mount followed immediately by unmount has always passed and that is the standard expectation always. Your proposed code will ensure that.

Quoted 8 lines

However there is one other subtle expectaton.

A mount cannot disappear if a user has explicitly mounted on top of it.

your proposed code will not meet that expectation.

In other words, these two expectations make it behave differently even when; arguably, they feel like the same configuration.

I am not seeing that.

Quoted 12 lines
Quoted 11 lines
Quoted 2 lines

maybe we need a flag to identify tucked mounts?

To preserve our exact current semantics yes.

The mount configurations that are delibearately constructed that I am aware of are comparatively simple. I don't think anyone has even taken advantage of the shadow/side mounts at this point. I made a reasonable effort to find out and no one was even aware they existed. Much less what they were. And certainly no one I talked to could find code that used them.

Quoted 4 lines

But someday; even if its after a decade, someone ;) will stumble into this semantics and wonder 'why?'. Its better to get it right sooner. Sorry, I am blaming myself; for keeping some of the problems open thinking no one will bump into them.

Oh definitely. If we have people ready to talk it through I am happy to dot as many i's and cross as many t's as we productively can.

I was just pointing out that I don't have any reason to expect that any one depends on the subtle details of the implementation today so we still have some wiggle room to fix them. Even if they are visible to user space.

Then I see Andrei Vagin's patch for checkpoint/restore and the mount namespace and I start suspecting that will be the point where all of the subtle details get locked in stone because checkpont/restore will have to preserve every possible configuration of mount namespaces.

My main concern at this point is to get the code to a point where a malicious user in a user namespace can not cause problems for root in the primary mount namespace. Even if root did open himself for all kinds of trouble by running "mount --make-rshared /". As that is essentially required to use mount propagation at all.

Eric

#22 lore raw

Re: [PATCH v5] mnt: Tuck mounts under others instead of creating shadow/side mounts.

Quoted 100 lines · [email protected] (Eric W. Biederman) writes:
Quoted 45 lines · Ram Pai
Quoted 44 lines · Eric W. Biederman
Quoted 42 lines · Ram Pai
Quoted 7 lines
fs/pnode.c +0 -3
@@ -359,12 +373,24 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 
 	for (m = propagation_next(parent, parent); m;
 	     		m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
-		if (child && list_empty(&child->mnt_mounts) &&
-		    (ret = do_refcount_check(child, 1)))

fs/pnode.c int propagate_mount_busy(struct mount *mnt, int refcnt) · line 384

Quoted 10 lines
fs/pnode.c +9 -1
-			break;
+		int count = 1;
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
+		if (!child)
+			continue;
+
+		/* Is there exactly one mount on the child that covers
+		 * it completely whose reference should be ignored?
+		 */
+		topper = find_topper(child);

This is tricky. I understand it is trying to identify the case where a mount got tucked-in because of propagation. But this will not distinguish the case where a mount got over-mounted genuinely, not because of propagation, but because of explicit user action.

example:

case 1: (explicit user action)
B is a slave of A
mount something on A/a , it will propagate to B/a and than mount something on B/a

case 2: (tucked mount)
B is a slave of A
mount something on B/a
and than mount something on A/a

Both case 1 and case 2 lead to the same mount configuration.

however 'umount A/a' in case 1 should fail. and 'umount A/a' in case 2 should pass.

Quoted 7 lines
Quoted 5 lines
Quoted 3 lines

Right? in other words, umounts of 'tucked mounts' should pass(case 2).

	whereas umounts of mounts on which overmounts exist should
		fail.(case 1)

Looking at your example. I agree that case 1 will fail today.

And should continue to fail. right? Your semantics change will pass it.

I don't see why it should continue to fail.

Quoted 13 lines
Quoted 7 lines

However my actual expectation would be for both mount configurations to behave the same. In both cases something has been explicitly mounted on B/a and something has propagated to B/a. In both cases the mount on top is what was explicitly mounted, and the mount below is what was propagated to B/a.

I don't see why the order of operations should matter.

One of the subtle expectation is reversibility.

Mount followed immediately by unmount has always passed and that is the standard expectation always. Your proposed code will ensure that.

Quoted 8 lines

However there is one other subtle expectaton.

A mount cannot disappear if a user has explicitly mounted on top of it.

your proposed code will not meet that expectation.

In other words, these two expectations make it behave differently even when; arguably, they feel like the same configuration.

I am not seeing that.

Quoted 12 lines
Quoted 11 lines
Quoted 2 lines

maybe we need a flag to identify tucked mounts?

To preserve our exact current semantics yes.

The mount configurations that are delibearately constructed that I am aware of are comparatively simple. I don't think anyone has even taken advantage of the shadow/side mounts at this point. I made a reasonable effort to find out and no one was even aware they existed. Much less what they were. And certainly no one I talked to could find code that used them.

Quoted 4 lines

But someday; even if its after a decade, someone ;) will stumble into this semantics and wonder 'why?'. Its better to get it right sooner. Sorry, I am blaming myself; for keeping some of the problems open thinking no one will bump into them.

Quoted 7 lines

Oh definitely. If we have people ready to talk it through I am happy to dot as many i's and cross as many t's as we productively can.

I was just pointing out that I don't have any reason to expect that any one depends on the subtle details of the implementation today so we still have some wiggle room to fix them. Even if they are visible to user space.

So I haven't seen a reply, and we are getting awfully close to the merge window. Is there anything concrete we can do to ease concerns?

Right now I am thinking my last version of the patch is the likely the best we have time and energy to manage and it would be good to merge it before the code bit rots.

Eric

#23 lore raw

Re: [PATCH v5] mnt: Tuck mounts under others instead of creating shadow/side mounts.

Quoted 110 lines · Eric W. Biederman
Quoted 100 lines · [email protected] (Eric W. Biederman) writes:
Quoted 45 lines · Ram Pai
Quoted 44 lines · Eric W. Biederman
Quoted 42 lines · Ram Pai
Quoted 7 lines
fs/pnode.c +0 -3
@@ -359,12 +373,24 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 
 	for (m = propagation_next(parent, parent); m;
 	     		m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
-		if (child && list_empty(&child->mnt_mounts) &&
-		    (ret = do_refcount_check(child, 1)))

fs/pnode.c int propagate_mount_busy(struct mount *mnt, int refcnt) · line 384

Quoted 10 lines
fs/pnode.c +9 -1
-			break;
+		int count = 1;
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
+		if (!child)
+			continue;
+
+		/* Is there exactly one mount on the child that covers
+		 * it completely whose reference should be ignored?
+		 */
+		topper = find_topper(child);

This is tricky. I understand it is trying to identify the case where a mount got tucked-in because of propagation. But this will not distinguish the case where a mount got over-mounted genuinely, not because of propagation, but because of explicit user action.

example:

case 1: (explicit user action)
B is a slave of A
mount something on A/a , it will propagate to B/a and than mount something on B/a

case 2: (tucked mount)
B is a slave of A
mount something on B/a
and than mount something on A/a

Both case 1 and case 2 lead to the same mount configuration.

however 'umount A/a' in case 1 should fail. and 'umount A/a' in case 2 should pass.

Quoted 7 lines
Quoted 5 lines
Quoted 3 lines

Right? in other words, umounts of 'tucked mounts' should pass(case 2).

	whereas umounts of mounts on which overmounts exist should
		fail.(case 1)

Looking at your example. I agree that case 1 will fail today.

And should continue to fail. right? Your semantics change will pass it.

I don't see why it should continue to fail.

Quoted 13 lines
Quoted 7 lines

However my actual expectation would be for both mount configurations to behave the same. In both cases something has been explicitly mounted on B/a and something has propagated to B/a. In both cases the mount on top is what was explicitly mounted, and the mount below is what was propagated to B/a.

I don't see why the order of operations should matter.

One of the subtle expectation is reversibility.

Mount followed immediately by unmount has always passed and that is the standard expectation always. Your proposed code will ensure that.

Quoted 8 lines

However there is one other subtle expectaton.

A mount cannot disappear if a user has explicitly mounted on top of it.

your proposed code will not meet that expectation.

In other words, these two expectations make it behave differently even when; arguably, they feel like the same configuration.

I am not seeing that.

Quoted 12 lines
Quoted 11 lines
Quoted 2 lines

maybe we need a flag to identify tucked mounts?

To preserve our exact current semantics yes.

The mount configurations that are delibearately constructed that I am aware of are comparatively simple. I don't think anyone has even taken advantage of the shadow/side mounts at this point. I made a reasonable effort to find out and no one was even aware they existed. Much less what they were. And certainly no one I talked to could find code that used them.

Quoted 4 lines

But someday; even if its after a decade, someone ;) will stumble into this semantics and wonder 'why?'. Its better to get it right sooner. Sorry, I am blaming myself; for keeping some of the problems open thinking no one will bump into them.

Quoted 7 lines

Oh definitely. If we have people ready to talk it through I am happy to dot as many i's and cross as many t's as we productively can.

I was just pointing out that I don't have any reason to expect that any one depends on the subtle details of the implementation today so we still have some wiggle room to fix them. Even if they are visible to user space.

Quoted 6 lines

So I haven't seen a reply, and we are getting awfully close to the merge window. Is there anything concrete we can do to ease concerns?

Right now I am thinking my last version of the patch is the likely the best we have time and energy to manage and it would be good to merge it before the code bit rots.

I was waiting for some other opinions on the behavior, since I continue to think that 'one should not be able to unmount mounts on which a user has explicitly mounted upon'. I am happy to be overruled, since your patch significantly improves the rest of the semantics.

Viro?

RP

#24 lore raw

Re: [PATCH v5] mnt: Tuck mounts under others instead of creating shadow/side mounts.

Quoted 118 lines · Ram Pai
Quoted 110 lines · Eric W. Biederman
Quoted 100 lines · [email protected] (Eric W. Biederman) writes:
Quoted 45 lines · Ram Pai
Quoted 44 lines · Eric W. Biederman
Quoted 42 lines · Ram Pai
Quoted 7 lines
(quoted diff) +0 -3
@@ -359,12 +373,24 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 
 	for (m = propagation_next(parent, parent); m;
 	     		m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
-		if (child && list_empty(&child->mnt_mounts) &&
-		    (ret = do_refcount_check(child, 1)))

Review int propagate_mount_busy(struct mount *mnt, int refcnt) · line 384

Quoted 10 lines
(quoted diff) +9 -1
-			break;
+		int count = 1;
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
+		if (!child)
+			continue;
+
+		/* Is there exactly one mount on the child that covers
+		 * it completely whose reference should be ignored?
+		 */
+		topper = find_topper(child);

This is tricky. I understand it is trying to identify the case where a mount got tucked-in because of propagation. But this will not distinguish the case where a mount got over-mounted genuinely, not because of propagation, but because of explicit user action.

example:

case 1: (explicit user action)
B is a slave of A
mount something on A/a , it will propagate to B/a and than mount something on B/a

case 2: (tucked mount)
B is a slave of A
mount something on B/a
and than mount something on A/a

Both case 1 and case 2 lead to the same mount configuration.

however 'umount A/a' in case 1 should fail. and 'umount A/a' in case 2 should pass.

Quoted 7 lines
Quoted 5 lines
Quoted 3 lines

Right? in other words, umounts of 'tucked mounts' should pass(case 2).

	whereas umounts of mounts on which overmounts exist should
		fail.(case 1)

Looking at your example. I agree that case 1 will fail today.

And should continue to fail. right? Your semantics change will pass it.

I don't see why it should continue to fail.

Quoted 13 lines
Quoted 7 lines

However my actual expectation would be for both mount configurations to behave the same. In both cases something has been explicitly mounted on B/a and something has propagated to B/a. In both cases the mount on top is what was explicitly mounted, and the mount below is what was propagated to B/a.

I don't see why the order of operations should matter.

One of the subtle expectation is reversibility.

Mount followed immediately by unmount has always passed and that is the standard expectation always. Your proposed code will ensure that.

Quoted 8 lines

However there is one other subtle expectaton.

A mount cannot disappear if a user has explicitly mounted on top of it.

your proposed code will not meet that expectation.

In other words, these two expectations make it behave differently even when; arguably, they feel like the same configuration.

I am not seeing that.

Quoted 12 lines
Quoted 11 lines
Quoted 2 lines

maybe we need a flag to identify tucked mounts?

To preserve our exact current semantics yes.

The mount configurations that are delibearately constructed that I am aware of are comparatively simple. I don't think anyone has even taken advantage of the shadow/side mounts at this point. I made a reasonable effort to find out and no one was even aware they existed. Much less what they were. And certainly no one I talked to could find code that used them.

Quoted 4 lines

But someday; even if its after a decade, someone ;) will stumble into this semantics and wonder 'why?'. Its better to get it right sooner. Sorry, I am blaming myself; for keeping some of the problems open thinking no one will bump into them.

Quoted 7 lines

Oh definitely. If we have people ready to talk it through I am happy to dot as many i's and cross as many t's as we productively can.

I was just pointing out that I don't have any reason to expect that any one depends on the subtle details of the implementation today so we still have some wiggle room to fix them. Even if they are visible to user space.

Quoted 6 lines

So I haven't seen a reply, and we are getting awfully close to the merge window. Is there anything concrete we can do to ease concerns?

Right now I am thinking my last version of the patch is the likely the best we have time and energy to manage and it would be good to merge it before the code bit rots.

Quoted 6 lines

I was waiting for some other opinions on the behavior, since I continue to think that 'one should not be able to unmount mounts on which a user has explicitly mounted upon'. I am happy to be overruled, since your patch significantly improves the rest of the semantics.

Viro?

Ram Pai, just to be clear you were hoping to add the logic below to my patch?

My objections to the snippet below are:

- It makes it hard for the CRIU folks (yet more state they have to find and restore).

- It feels subjectively worse to me.

- We already have cases where mounts are unmounted transparently (umount on rmdir).

- Al Viro claims that the side/shadow mounts are ordinary mounts and maintaining this extra logic that remembers if we tucked one mount under another seems to make this them less ordinary.

- The symmetry for unmounting exists for a tucked mount. We can unmount it via propagation or we can unmount the mount above it, and then we can unmount the new underlying mount. So I don't see why we don't want symmetry in the other case just because we mounted on top of the mount and rather than had the mount tucked under us.

fs/namespace.c +3 -1
index 8bfad42c1ccf..8b00e0548438 100644
@@ -2047,8 +2047,10 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 		hlist_del_init(&child->mnt_hash);
 		q = __lookup_mnt(&child->mnt_parent->mnt,
 				 child->mnt_mountpoint);
-		if (q)
+		if (q) {
 			mnt_change_mountpoint(child, smp, q);
+			child->mnt.mnt_flags |= MNT_TUCKED;
+		}
 		commit_tree(child);
 	}
 	put_mountpoint(smp);
fs/pnode.c +3 -0
index 5bc7896d122a..e2a6ac68feb9 100644
@@ -327,6 +327,9 @@ static struct mount *find_topper(struct mount *mnt)
 	/* If there is exactly one mount covering mnt completely return it. */
 	struct mount *child;
 
+	if (!(mnt->mnt.mnt_flags & MNT_TUCKED))
+		return NULL;
+	
 	if (!list_is_singular(&mnt->mnt_mounts))
 		return NULL;
 
include/linux/mount.h +1 -0
index 8e0352af06b7..25ca398b19b3 100644
@@ -52,6 +52,7 @@ struct mnt_namespace;
 
 #define MNT_INTERNAL	0x4000
 
+#define MNT_TUCKED		0x020000
 #define MNT_LOCK_ATIME		0x040000
 #define MNT_LOCK_NOEXEC		0x080000
 #define MNT_LOCK_NOSUID		0x100000

Eric

#25 lore raw

Re: [PATCH v5] mnt: Tuck mounts under others instead of creating shadow/side mounts.

Quoted 120 lines · Eric W. Biederman
Quoted 118 lines · Ram Pai
Quoted 116 lines · Eric W. Biederman
Quoted 100 lines · [email protected] (Eric W. Biederman) writes:
Quoted 45 lines · Ram Pai
Quoted 44 lines · Eric W. Biederman
Quoted 42 lines · Ram Pai
Quoted 7 lines
(quoted diff) +0 -3
@@ -359,12 +373,24 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 
 	for (m = propagation_next(parent, parent); m;
 	     		m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
-		if (child && list_empty(&child->mnt_mounts) &&
-		    (ret = do_refcount_check(child, 1)))

Review int propagate_mount_busy(struct mount *mnt, int refcnt) · line 384

Quoted 10 lines
(quoted diff) +9 -1
-			break;
+		int count = 1;
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
+		if (!child)
+			continue;
+
+		/* Is there exactly one mount on the child that covers
+		 * it completely whose reference should be ignored?
+		 */
+		topper = find_topper(child);

This is tricky. I understand it is trying to identify the case where a mount got tucked-in because of propagation. But this will not distinguish the case where a mount got over-mounted genuinely, not because of propagation, but because of explicit user action.

example:

case 1: (explicit user action)
B is a slave of A
mount something on A/a , it will propagate to B/a and than mount something on B/a

case 2: (tucked mount)
B is a slave of A
mount something on B/a
and than mount something on A/a

Both case 1 and case 2 lead to the same mount configuration.

however 'umount A/a' in case 1 should fail. and 'umount A/a' in case 2 should pass.

Quoted 7 lines
Quoted 5 lines
Quoted 3 lines

Right? in other words, umounts of 'tucked mounts' should pass(case 2).

	whereas umounts of mounts on which overmounts exist should
		fail.(case 1)

Looking at your example. I agree that case 1 will fail today.

And should continue to fail. right? Your semantics change will pass it.

I don't see why it should continue to fail.

Quoted 13 lines
Quoted 7 lines

However my actual expectation would be for both mount configurations to behave the same. In both cases something has been explicitly mounted on B/a and something has propagated to B/a. In both cases the mount on top is what was explicitly mounted, and the mount below is what was propagated to B/a.

I don't see why the order of operations should matter.

One of the subtle expectation is reversibility.

Mount followed immediately by unmount has always passed and that is the standard expectation always. Your proposed code will ensure that.

Quoted 8 lines

However there is one other subtle expectaton.

A mount cannot disappear if a user has explicitly mounted on top of it.

your proposed code will not meet that expectation.

In other words, these two expectations make it behave differently even when; arguably, they feel like the same configuration.

I am not seeing that.

Quoted 12 lines
Quoted 11 lines
Quoted 2 lines

maybe we need a flag to identify tucked mounts?

To preserve our exact current semantics yes.

The mount configurations that are delibearately constructed that I am aware of are comparatively simple. I don't think anyone has even taken advantage of the shadow/side mounts at this point. I made a reasonable effort to find out and no one was even aware they existed. Much less what they were. And certainly no one I talked to could find code that used them.

Quoted 4 lines

But someday; even if its after a decade, someone ;) will stumble into this semantics and wonder 'why?'. Its better to get it right sooner. Sorry, I am blaming myself; for keeping some of the problems open thinking no one will bump into them.

Quoted 7 lines

Oh definitely. If we have people ready to talk it through I am happy to dot as many i's and cross as many t's as we productively can.

I was just pointing out that I don't have any reason to expect that any one depends on the subtle details of the implementation today so we still have some wiggle room to fix them. Even if they are visible to user space.

So I haven't seen a reply, and we are getting awfully close to the merge window. Is there anything concrete we can do to ease concerns?

Right now I am thinking my last version of the patch is the likely the best we have time and energy to manage and it would be good to merge it before the code bit rots.

Quoted 8 lines
Quoted 6 lines

I was waiting for some other opinions on the behavior, since I continue to think that 'one should not be able to unmount mounts on which a user has explicitly mounted upon'. I am happy to be overruled, since your patch significantly improves the rest of the semantics.

Viro?

Ram Pai, just to be clear you were hoping to add the logic below to my patch?

Yes. the behavior of your patch below is what I was proposing.

Quoted 5 lines

My objections to the snippet below are:

- It makes it hard for the CRIU folks (yet more state they have to find and restore).

true. unfortunately one more subtle detail to be aware off.

Quoted 4 lines

- It feels subjectively worse to me.

- We already have cases where mounts are unmounted transparently (umount on rmdir).

sorry. i am not aware of this case. some details will help.

Quoted 4 lines

- Al Viro claims that the side/shadow mounts are ordinary mounts and maintaining this extra logic that remembers if we tucked one mount under another seems to make this them less ordinary.

I tend to argue that they are a bit more than ordinary, for they have the ability to tuck.

Quoted 4 lines

- The symmetry for unmounting exists for a tucked mount. We can unmount it via propagation or we can unmount the mount above it, and then we can unmount the new underlying mount.

this is fine with me.

Quoted 3 lines

So I don't see why we don't
want symmetry in the other case just because we mounted on top of the mount and rather than had the mount tucked under us.

A tucked mount should be un-tuckable. I agree. But a non-tucked mount cannot pretend to be tucked and this is where I disagree.

Quoted 45 lines
fs/namespace.c +3 -1
index 8bfad42c1ccf..8b00e0548438 100644
@@ -2047,8 +2047,10 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 		hlist_del_init(&child->mnt_hash);
 		q = __lookup_mnt(&child->mnt_parent->mnt,
 				 child->mnt_mountpoint);
-		if (q)
+		if (q) {
 			mnt_change_mountpoint(child, smp, q);
+			child->mnt.mnt_flags |= MNT_TUCKED;
+		}
 		commit_tree(child);
 	}
 	put_mountpoint(smp);
fs/pnode.c +3 -0
index 5bc7896d122a..e2a6ac68feb9 100644
@@ -327,6 +327,9 @@ static struct mount *find_topper(struct mount *mnt)
 	/* If there is exactly one mount covering mnt completely return it. */
 	struct mount *child;
 
+	if (!(mnt->mnt.mnt_flags & MNT_TUCKED))
+		return NULL;
+	
 	if (!list_is_singular(&mnt->mnt_mounts))
 		return NULL;
 
include/linux/mount.h +1 -0
index 8e0352af06b7..25ca398b19b3 100644
@@ -52,6 +52,7 @@ struct mnt_namespace;
 
 #define MNT_INTERNAL	0x4000
 
+#define MNT_TUCKED		0x020000
 #define MNT_LOCK_ATIME		0x040000
 #define MNT_LOCK_NOEXEC		0x080000
 #define MNT_LOCK_NOSUID		0x100000

Eric

Signature · 1 line
Ram Pai
#26 lore raw

Re: [PATCH v5] mnt: Tuck mounts under others instead of creating shadow/side mounts.

Quoted 133 lines · Ram Pai
Quoted 120 lines · Eric W. Biederman
Quoted 118 lines · Ram Pai
Quoted 116 lines · Eric W. Biederman
Quoted 100 lines · [email protected] (Eric W. Biederman) writes:
Quoted 45 lines · Ram Pai
Quoted 44 lines · Eric W. Biederman
Quoted 42 lines · Ram Pai
Quoted 7 lines
(quoted diff) +0 -3
@@ -359,12 +373,24 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 
 	for (m = propagation_next(parent, parent); m;
 	     		m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
-		if (child && list_empty(&child->mnt_mounts) &&
-		    (ret = do_refcount_check(child, 1)))

Review int propagate_mount_busy(struct mount *mnt, int refcnt) · line 384

Quoted 10 lines
(quoted diff) +9 -1
-			break;
+		int count = 1;
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
+		if (!child)
+			continue;
+
+		/* Is there exactly one mount on the child that covers
+		 * it completely whose reference should be ignored?
+		 */
+		topper = find_topper(child);

This is tricky. I understand it is trying to identify the case where a mount got tucked-in because of propagation. But this will not distinguish the case where a mount got over-mounted genuinely, not because of propagation, but because of explicit user action.

example:

case 1: (explicit user action)
B is a slave of A
mount something on A/a , it will propagate to B/a and than mount something on B/a

case 2: (tucked mount)
B is a slave of A
mount something on B/a
and than mount something on A/a

Both case 1 and case 2 lead to the same mount configuration.

however 'umount A/a' in case 1 should fail. and 'umount A/a' in case 2 should pass.

Quoted 7 lines
Quoted 5 lines
Quoted 3 lines

Right? in other words, umounts of 'tucked mounts' should pass(case 2).

	whereas umounts of mounts on which overmounts exist should
		fail.(case 1)

Looking at your example. I agree that case 1 will fail today.

And should continue to fail. right? Your semantics change will pass it.

I don't see why it should continue to fail.

Quoted 13 lines
Quoted 7 lines

However my actual expectation would be for both mount configurations to behave the same. In both cases something has been explicitly mounted on B/a and something has propagated to B/a. In both cases the mount on top is what was explicitly mounted, and the mount below is what was propagated to B/a.

I don't see why the order of operations should matter.

One of the subtle expectation is reversibility.

Mount followed immediately by unmount has always passed and that is the standard expectation always. Your proposed code will ensure that.

Quoted 8 lines

However there is one other subtle expectaton.

A mount cannot disappear if a user has explicitly mounted on top of it.

your proposed code will not meet that expectation.

In other words, these two expectations make it behave differently even when; arguably, they feel like the same configuration.

I am not seeing that.

Quoted 12 lines
Quoted 11 lines
Quoted 2 lines

maybe we need a flag to identify tucked mounts?

To preserve our exact current semantics yes.

The mount configurations that are delibearately constructed that I am aware of are comparatively simple. I don't think anyone has even taken advantage of the shadow/side mounts at this point. I made a reasonable effort to find out and no one was even aware they existed. Much less what they were. And certainly no one I talked to could find code that used them.

Quoted 4 lines

But someday; even if its after a decade, someone ;) will stumble into this semantics and wonder 'why?'. Its better to get it right sooner. Sorry, I am blaming myself; for keeping some of the problems open thinking no one will bump into them.

Quoted 7 lines

Oh definitely. If we have people ready to talk it through I am happy to dot as many i's and cross as many t's as we productively can.

I was just pointing out that I don't have any reason to expect that any one depends on the subtle details of the implementation today so we still have some wiggle room to fix them. Even if they are visible to user space.

So I haven't seen a reply, and we are getting awfully close to the merge window. Is there anything concrete we can do to ease concerns?

Right now I am thinking my last version of the patch is the likely the best we have time and energy to manage and it would be good to merge it before the code bit rots.

Quoted 8 lines
Quoted 6 lines

I was waiting for some other opinions on the behavior, since I continue to think that 'one should not be able to unmount mounts on which a user has explicitly mounted upon'. I am happy to be overruled, since your patch significantly improves the rest of the semantics.

Viro?

Ram Pai, just to be clear you were hoping to add the logic below to my patch?

Yes. the behavior of your patch below is what I was proposing.

Quoted 1 line
Quoted 6 lines
Quoted 4 lines

My objections to the snippet below are:

- It makes it hard for the CRIU folks (yet more state they have to find and restore).

true. unfortunately one more subtle detail to be aware off.

A bit more than that, as it means that it requires an almost exact playback of the sequence of mounts in all mount namespaces to get to the point of reproducing a mount namespace.

Quoted 5 lines
Quoted 3 lines

- It feels subjectively worse to me.

- We already have cases where mounts are unmounted transparently (umount on rmdir).

sorry. i am not aware of this case. some details will help.

The question:

What happens when we rmdir a directory that has a mount on it in another mount namespace?

What happens when someone on the nfs server deletes a directory there is a mount on?

It used to be that we returned -EBUSY, and refused the rmdir operation, and we lied in the vfs about the nfs dentry being deleted to preserve the mount.

In recent kernels I have done the work so that we transparently unmount the mounts and allow the rmdir to happen. An unprivileged user mounting over say glibc and blocking the yum update of it is a pretty serious bug.

Quoted 12 lines
Quoted 3 lines

- Al Viro claims that the side/shadow mounts are ordinary mounts and maintaining this extra logic that remembers if we tucked one mount under another seems to make this them less ordinary.

I tend to argue that they are a bit more than ordinary, for they have the ability to tuck.

Quoted 4 lines

- The symmetry for unmounting exists for a tucked mount. We can unmount it via propagation or we can unmount the mount above it, and then we can unmount the new underlying mount.

Quoted 8 lines

this is fine with me.

Quoted 3 lines

So I don't see why we don't
want symmetry in the other case just because we mounted on top of the mount and rather than had the mount tucked under us.

A tucked mount should be un-tuckable. I agree. But a non-tucked mount cannot pretend to be tucked and this is where I disagree.

I have always seen the question as: Should a mount that is propagated be unmountable via umount propagation.

Which leads me to think that allowing the umount propagation when it won't change the applications view of files and filesystems is a good thing. From my perspective it also better preserves the reversability
property that is important. The mount propgated and now the unmount
propagated.

Quoted 1 line

From a system management point of view one of the largest practical

problems with mount namespaces and mount propagation is: mounts that propagate into another mount namespaces but don't get unmounted.

Which is to say not unmounting something (especially silently) and leaving the filesystem busy when something could be unmounted is a practical problem for people.

I am going to be out for a week, and I am leaving in a few minutes. So I am going to push my patch to the my for-next branch, so there is a reasonable chance of merging things when the merge window opens.

If the feedback is to add the MNT_TUCKED annotations to make the patch suitable for merging to Linus's tree I will take care of that when I get back.

Eric

#27 lore raw

Re: [PATCH v5] mnt: Tuck mounts under others instead of creating shadow/side mounts.

Quoted 137 lines · Eric W. Biederman
Quoted 135 lines · Ram Pai
Quoted 120 lines · Eric W. Biederman
Quoted 118 lines · Ram Pai
Quoted 116 lines · Eric W. Biederman
Quoted 100 lines · [email protected] (Eric W. Biederman) writes:
Quoted 45 lines · Ram Pai
Quoted 44 lines · Eric W. Biederman
Quoted 42 lines · Ram Pai
Quoted 7 lines
(quoted diff) +0 -3
@@ -359,12 +373,24 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 
 	for (m = propagation_next(parent, parent); m;
 	     		m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
-		if (child && list_empty(&child->mnt_mounts) &&
-		    (ret = do_refcount_check(child, 1)))

Review int propagate_mount_busy(struct mount *mnt, int refcnt) · line 384

Quoted 10 lines
(quoted diff) +9 -1
-			break;
+		int count = 1;
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
+		if (!child)
+			continue;
+
+		/* Is there exactly one mount on the child that covers
+		 * it completely whose reference should be ignored?
+		 */
+		topper = find_topper(child);

This is tricky. I understand it is trying to identify the case where a mount got tucked-in because of propagation. But this will not distinguish the case where a mount got over-mounted genuinely, not because of propagation, but because of explicit user action.

example:

case 1: (explicit user action)
B is a slave of A
mount something on A/a , it will propagate to B/a and than mount something on B/a

case 2: (tucked mount)
B is a slave of A
mount something on B/a
and than mount something on A/a

Both case 1 and case 2 lead to the same mount configuration.

however 'umount A/a' in case 1 should fail. and 'umount A/a' in case 2 should pass.

Quoted 7 lines
Quoted 5 lines
Quoted 3 lines

Right? in other words, umounts of 'tucked mounts' should pass(case 2).

	whereas umounts of mounts on which overmounts exist should
		fail.(case 1)

Looking at your example. I agree that case 1 will fail today.

And should continue to fail. right? Your semantics change will pass it.

I don't see why it should continue to fail.

Quoted 13 lines
Quoted 7 lines

However my actual expectation would be for both mount configurations to behave the same. In both cases something has been explicitly mounted on B/a and something has propagated to B/a. In both cases the mount on top is what was explicitly mounted, and the mount below is what was propagated to B/a.

I don't see why the order of operations should matter.

One of the subtle expectation is reversibility.

Mount followed immediately by unmount has always passed and that is the standard expectation always. Your proposed code will ensure that.

Quoted 8 lines

However there is one other subtle expectaton.

A mount cannot disappear if a user has explicitly mounted on top of it.

your proposed code will not meet that expectation.

In other words, these two expectations make it behave differently even when; arguably, they feel like the same configuration.

I am not seeing that.

Quoted 12 lines
Quoted 11 lines
Quoted 2 lines

maybe we need a flag to identify tucked mounts?

To preserve our exact current semantics yes.

The mount configurations that are delibearately constructed that I am aware of are comparatively simple. I don't think anyone has even taken advantage of the shadow/side mounts at this point. I made a reasonable effort to find out and no one was even aware they existed. Much less what they were. And certainly no one I talked to could find code that used them.

Quoted 4 lines

But someday; even if its after a decade, someone ;) will stumble into this semantics and wonder 'why?'. Its better to get it right sooner. Sorry, I am blaming myself; for keeping some of the problems open thinking no one will bump into them.

Quoted 7 lines

Oh definitely. If we have people ready to talk it through I am happy to dot as many i's and cross as many t's as we productively can.

I was just pointing out that I don't have any reason to expect that any one depends on the subtle details of the implementation today so we still have some wiggle room to fix them. Even if they are visible to user space.

So I haven't seen a reply, and we are getting awfully close to the merge window. Is there anything concrete we can do to ease concerns?

Right now I am thinking my last version of the patch is the likely the best we have time and energy to manage and it would be good to merge it before the code bit rots.

Quoted 8 lines
Quoted 6 lines

I was waiting for some other opinions on the behavior, since I continue to think that 'one should not be able to unmount mounts on which a user has explicitly mounted upon'. I am happy to be overruled, since your patch significantly improves the rest of the semantics.

Viro?

Ram Pai, just to be clear you were hoping to add the logic below to my patch?

Yes. the behavior of your patch below is what I was proposing.

Quoted 3 lines

My objections to the snippet below are:

Quoted 8 lines
Quoted 4 lines
Quoted 2 lines

- It makes it hard for the CRIU folks (yet more state they have to find and restore).

true. unfortunately one more subtle detail to be aware off.

A bit more than that, as it means that it requires an almost exact playback of the sequence of mounts in all mount namespaces to get to the point of reproducing a mount namespace.

Currently dump and restore of mount namespaces is the most complicated part of CRIU. The main problem is that we don't know how a tree of mounts was created. Mounts have two types of relationships:
child<->parent and shared groups. Currently both this relationships can't be restored directly. We can not add a mount into an existing group, the group can be only inherited from a source mount. And we can not restore "tucked" mounts, which can be only appeared due to propagation.

Now we don't know an algorithm to dump and restore any set of mount points for a reqsonable time. The problem becomes more complex if we start thinking how to restore mount namespaces which lives in different user namespaces.

This patch from Eric together with my patch https://lkml.org/lkml/2017/1/23/712
can solve the problem of dumping and restoring mount namespaces.

Quoted 78 lines
Quoted 6 lines
Quoted 4 lines

- It feels subjectively worse to me.

- We already have cases where mounts are unmounted transparently (umount on rmdir).

sorry. i am not aware of this case. some details will help.

The question:

What happens when we rmdir a directory that has a mount on it in another mount namespace?

What happens when someone on the nfs server deletes a directory there is a mount on?

It used to be that we returned -EBUSY, and refused the rmdir operation, and we lied in the vfs about the nfs dentry being deleted to preserve the mount.

In recent kernels I have done the work so that we transparently unmount the mounts and allow the rmdir to happen. An unprivileged user mounting over say glibc and blocking the yum update of it is a pretty serious bug.

Quoted 12 lines
Quoted 3 lines

- Al Viro claims that the side/shadow mounts are ordinary mounts and maintaining this extra logic that remembers if we tucked one mount under another seems to make this them less ordinary.

I tend to argue that they are a bit more than ordinary, for they have the ability to tuck.

Quoted 4 lines

- The symmetry for unmounting exists for a tucked mount. We can unmount it via propagation or we can unmount the mount above it, and then we can unmount the new underlying mount.

Quoted 8 lines

this is fine with me.

Quoted 3 lines

So I don't see why we don't
want symmetry in the other case just because we mounted on top of the mount and rather than had the mount tucked under us.

A tucked mount should be un-tuckable. I agree. But a non-tucked mount cannot pretend to be tucked and this is where I disagree.

I have always seen the question as: Should a mount that is propagated be unmountable via umount propagation.

Which leads me to think that allowing the umount propagation when it won't change the applications view of files and filesystems is a good thing. From my perspective it also better preserves the reversability
property that is important. The mount propgated and now the unmount
propagated.

From a system management point of view one of the largest practical problems with mount namespaces and mount propagation is: mounts that propagate into another mount namespaces but don't get unmounted.

Which is to say not unmounting something (especially silently) and leaving the filesystem busy when something could be unmounted is a practical problem for people.

I am going to be out for a week, and I am leaving in a few minutes. So I am going to push my patch to the my for-next branch, so there is a reasonable chance of merging things when the merge window opens.

If the feedback is to add the MNT_TUCKED annotations to make the patch suitable for merging to Linus's tree I will take care of that when I get back.

Eric

#28 lore raw

Re: [PATCH v5] mnt: Tuck mounts under others instead of creating shadow/side mounts.

Quoted 16 lines · Andrei Vagin
Quoted 15 lines · Eric W. Biederman
Quoted 13 lines · Ram Pai
Quoted 12 lines · Eric W. Biederman
Quoted 10 lines · Ram Pai
Quoted 9 lines · Eric W. Biederman
Quoted 7 lines · [email protected] (Eric W. Biederman) writes:
Quoted 5 lines · Ram Pai
Quoted 4 lines · Eric W. Biederman
Quoted 2 lines · Ram Pai
Quoted 2 lines
(quoted diff) +0 -0
@@ -359,12 +373,24 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 

....snip....

Quoted 15 lines
Quoted 4 lines

A bit more than that, as it means that it requires an almost exact playback of the sequence of mounts in all mount namespaces to get to the point of reproducing a mount namespace.

Currently dump and restore of mount namespaces is the most complicated part of CRIU. The main problem is that we don't know how a tree of mounts was created. Mounts have two types of relationships:
child<->parent and shared groups. Currently both this relationships can't be restored directly. We can not add a mount into an existing group, the group can be only inherited from a source mount. And we can not restore "tucked" mounts, which can be only appeared due to propagation.

Quoted 8 lines

Now we don't know an algorithm to dump and restore any set of mount points for a reqsonable time. The problem becomes more complex if we start thinking how to restore mount namespaces which lives in different user namespaces.

This patch from Eric together with my patch https://lkml.org/lkml/2017/1/23/712
can solve the problem of dumping and restoring mount namespaces.

Lets say we exposed the tucked flag for the mount in the /proc/*/mountinfo, than there will be no need to know the history. Its just a matter or setting that flag as part of
your new MS_SET_GROUP mount() call. right?

Looking at the patch at https://lkml.org/lkml/2017/1/23/712, I am guessing the CRUI dumps information from mountinfo to restore it later.

RP

#29 lore raw

Re: [PATCH v5] mnt: Tuck mounts under others instead of creating shadow/side mounts.

Quoted 24 lines · Ram Pai
Quoted 16 lines · Andrei Vagin
Quoted 15 lines · Eric W. Biederman
Quoted 13 lines · Ram Pai
Quoted 12 lines · Eric W. Biederman
Quoted 10 lines · Ram Pai
Quoted 9 lines · Eric W. Biederman
Quoted 7 lines · [email protected] (Eric W. Biederman) writes:
Quoted 5 lines · Ram Pai
Quoted 4 lines · Eric W. Biederman
Quoted 2 lines · Ram Pai
Quoted 2 lines
(quoted diff) +0 -0
@@ -359,12 +373,24 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 

....snip....

Quoted 6 lines
Quoted 4 lines

A bit more than that, as it means that it requires an almost exact playback of the sequence of mounts in all mount namespaces to get to the point of reproducing a mount namespace.

Quoted 7 lines
Quoted 7 lines

Currently dump and restore of mount namespaces is the most complicated part of CRIU. The main problem is that we don't know how a tree of mounts was created. Mounts have two types of relationships:
child<->parent and shared groups. Currently both this relationships can't be restored directly. We can not add a mount into an existing group, the group can be only inherited from a source mount. And we can not restore "tucked" mounts, which can be only appeared due to

Sorry, I used a wrong term. I mean "shadow" mounts.

Quoted 8 lines
Quoted 8 lines

propagation.

Now we don't know an algorithm to dump and restore any set of mount points for a reqsonable time. The problem becomes more complex if we start thinking how to restore mount namespaces which lives in different user namespaces.

Quoted 7 lines
Quoted 2 lines

This patch from Eric together with my patch https://lkml.org/lkml/2017/1/23/712
can solve the problem of dumping and restoring mount namespaces.

Lets say we exposed the tucked flag for the mount in the /proc/*/mountinfo, than there will be no need to know the history. Its just a matter or setting that flag as part of
your new MS_SET_GROUP mount() call. right?

This is right.

Quoted 7 lines

Looking at the patch at https://lkml.org/lkml/2017/1/23/712, I am guessing the CRUI dumps information from mountinfo to restore it later.

RP

maintainer
#30 lore raw

Re: [REVIEW][PATCH 1/2] mnt: Fix propagate_mount_busy to notice all cases of busy mounts.

In reply to Eric W. Biederman
Quoted 10 lines · Eric W. Biederman

When I look at what propagate_mount_busy is trying to do and I look at the code closely I discover there is a great disconnect between the two. In the ordinary non-propagation case propagate_mount_busy has been verifying that there are no submounts and that there are no extraneous references on the mount.

For mounts that the unmount would propagate to propagate_mount_busy has been verifying that there are no extraneous references only if there are no submounts. Which is nonsense.

... because?

Quoted 6 lines

Thefore rework the logic in propgate_mount_busy so that for each mount it examines it considers that mount busy if that mount has children or if there are extraneous references to that mount.

While this check was incorrect we could leak mounts instead of simply failing umount.

What do you mean, leak? We ended up not unmounting them, and they
stayed around until umount of whatever they'd been shadowed by/slipped under had exposed them and they got explicitly unmounted.

This is not a leak in a sense of "data structure is unreachable and
will never be freed", unlike the one your previous version had introduced.

Your change might very well be a nicer behaviour - or a DoS in making. But it really deserves more detailed rationale than that and yes, it is a user-visible change. With rather insane userland setups in that area (*cough* systemd *cough* docker), it's _not_ obviously correct.

#31 lore raw

Re: [REVIEW][PATCH 1/2] mnt: Fix propagate_mount_busy to notice all cases of busy mounts.

Quoted 18 lines · Al Viro
Quoted 10 lines · Eric W. Biederman

When I look at what propagate_mount_busy is trying to do and I look at the code closely I discover there is a great disconnect between the two. In the ordinary non-propagation case propagate_mount_busy has been verifying that there are no submounts and that there are no extraneous references on the mount.

For mounts that the unmount would propagate to propagate_mount_busy has been verifying that there are no extraneous references only if there are no submounts. Which is nonsense.

... because?

Quoted 4 lines

Thefore rework the logic in propgate_mount_busy so that for each mount it examines it considers that mount busy if that mount has children or if there are extraneous references to that mount.

Quoted 6 lines
Quoted 2 lines

While this check was incorrect we could leak mounts instead of simply failing umount.

What do you mean, leak? We ended up not unmounting them, and they
stayed around until umount of whatever they'd been shadowed by/slipped under had exposed them and they got explicitly unmounted.

Leak in the sense of userspace expecting everything to be cleaned up and it was not. My concerns exist in the presence of a slave mount with something mounted on it. Nothing exotic needs to exist.

Quoted 7 lines

This is not a leak in a sense of "data structure is unreachable and
will never be freed", unlike the one your previous version had introduced.

Your change might very well be a nicer behaviour - or a DoS in making. But it really deserves more detailed rationale than that and yes, it is a user-visible change. With rather insane userland setups in that area (*cough* systemd *cough* docker), it's _not_ obviously correct.

I wrote this patch primarily because I looked at what the code was doing and saw semantics that make no obvious sense to me given my experience with how unmount ordinarily works, I needed to point that out so we can have this conversation independently. Having just looked and doubled checked I can say this is how umount is documented to behave in Documentation/filesystems/shared-subtrees.

My experience with umount in other contexts is either umount succeeds, umount fails because something is making the mount busy, or a lazy umount is requested and users of the mount are ignored.

The way umount propagation works adds another case into my understanding of umount behavior. Namely that the umount will be propagated to some places but not to other places depending on the presence of submounts. For me at least that violates the principle of least surprise. I do not understand if we are going to give up in some cases if the mount is busy but not in other cases why we even bother looking at propgated mounts.

Given this behavior has existed for a decade and we have some very creative pieces of userspace code I completely agree that my case for making this change is insufficiently strong. To actually make this change would require extensive testing to verify I don't introduce any regressions in userspace applications.

This patch was my way of pointing out the very strange (to my eyes) behaviour of umount propagation ignoring some cases of busy mounts, and asking if that was what you were concerned about in propagate_mount_busy.

As my case is insufficient for this change. And my concerns about what you were concerned about with propagate_mount_busy have been addressed I am going to drop this patch.

Eric

#32 lore raw

Re: [REVIEW][PATCH 1/2] mnt: Fix propagate_mount_busy to notice all cases of busy mounts.

In reply to Eric W. Biederman

Hi Eric,

Something wrong is in this patch. Pls, take a look at this script:

[root@fc24 ~]# unshare -m bash -x xxx.sh
+ set -x -e -m
+ mount --make-rprivate /
+ mount --make-shared /
+ mount -t tmpfs xxx /mnt
+ mount --make-private /mnt
+ mkdir /mnt/yyy
+ mount -t tmpfs xxx /mnt/yyy
+ sleep 1
+ unshare --propagation unchanged -m sleep 1000
+ pid=452
+ umount /mnt/yyy
+ umount /mnt/
umount: /mnt/: target is busy
(In some cases useful info about processes that
use the device is found by lsof(8) or fuser(1).)
+ echo FAIL
FAIL
+ kill 452
+ wait
xxx.sh: line 15: 452 Terminated unshare --propagation
unchanged -m sleep 1000

[root@fc24 ~]# cat xxx.sh
set -x -e -m

mount --make-rprivate /
mount --make-shared /
mount -t tmpfs xxx /mnt
mount --make-private /mnt
mkdir /mnt/yyy
mount -t tmpfs xxx /mnt/yyy
unshare --propagation unchanged -m sleep 1000 &
sleep 1
pid=$!
umount /mnt/yyy
umount /mnt/ || echo FAIL
kill $pid
wait

Quoted 63 lines · Eric W. Biederman

When I look at what propagate_mount_busy is trying to do and I look at the code closely I discover there is a great disconnect between the two. In the ordinary non-propagation case propagate_mount_busy has been verifying that there are no submounts and that there are no extraneous references on the mount.

For mounts that the unmount would propagate to propagate_mount_busy has been verifying that there are no extraneous references only if there are no submounts. Which is nonsense.

Thefore rework the logic in propgate_mount_busy so that for each mount it examines it considers that mount busy if that mount has children or if there are extraneous references to that mount.

While this check was incorrect we could leak mounts instead of simply failing umount.

Fixes a05964f3917c ("[PATCH] shared mounts handling: umount")

---

If you don't figure this fix is worth it after all of this time please let me know. This feels like the proper thing to do, and I don't expect it will break anyone to fix this.

 fs/pnode.c | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)
fs/pnode.c +6 -5
index 06a793f4ae38..12fafa711114 100644
@@ -344,7 +344,6 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 {
 	struct mount *m, *child;
 	struct mount *parent = mnt->mnt_parent;
-	int ret = 0;
 
 	if (mnt == parent)
 		return do_refcount_check(mnt, refcnt);
@@ -360,11 +359,13 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 	for (m = propagation_next(parent, parent); m;
 	     		m = propagation_next(m, parent)) {
 		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
-		if (child && list_empty(&child->mnt_mounts) &&
-		    (ret = do_refcount_check(child, 1)))
-			break;
+		if (!child)
+			continue;
+		if (!list_empty(&child->mnt_mounts) ||
+		    do_refcount_check(child, 1))
+			return 1;
 	}
-	return ret;
+	return 0;
 }
 
 /*
Signature · 1 line
2.10.1
Tested-by
#33 lore raw

Re: [REVIEW][PATCH 1/2] mnt: Fix propagate_mount_busy to notice all cases of busy mounts.

Quoted 3 lines · Andrei Vagin

Hi Eric,

Something wrong is in this patch. Pls, take a look at this script:

Actually, it works as expected. The reason of this error is what was fixed in this patch. I'm sorry for this noise.

Quoted 107 lines

[root@fc24 ~]# unshare -m bash -x xxx.sh
+ set -x -e -m
+ mount --make-rprivate /
+ mount --make-shared /
+ mount -t tmpfs xxx /mnt
+ mount --make-private /mnt
+ mkdir /mnt/yyy
+ mount -t tmpfs xxx /mnt/yyy
+ sleep 1
+ unshare --propagation unchanged -m sleep 1000
+ pid=452
+ umount /mnt/yyy
+ umount /mnt/
umount: /mnt/: target is busy
(In some cases useful info about processes that
use the device is found by lsof(8) or fuser(1).)
+ echo FAIL
FAIL
+ kill 452
+ wait
xxx.sh: line 15: 452 Terminated unshare --propagation
unchanged -m sleep 1000

[root@fc24 ~]# cat xxx.sh
set -x -e -m

mount --make-rprivate /
mount --make-shared /
mount -t tmpfs xxx /mnt
mount --make-private /mnt
mkdir /mnt/yyy
mount -t tmpfs xxx /mnt/yyy
unshare --propagation unchanged -m sleep 1000 &
sleep 1
pid=$!
umount /mnt/yyy
umount /mnt/ || echo FAIL
kill $pid
wait

Quoted 63 lines · Eric W. Biederman

When I look at what propagate_mount_busy is trying to do and I look at the code closely I discover there is a great disconnect between the two. In the ordinary non-propagation case propagate_mount_busy has been verifying that there are no submounts and that there are no extraneous references on the mount.

For mounts that the unmount would propagate to propagate_mount_busy has been verifying that there are no extraneous references only if there are no submounts. Which is nonsense.

Thefore rework the logic in propgate_mount_busy so that for each mount it examines it considers that mount busy if that mount has children or if there are extraneous references to that mount.

While this check was incorrect we could leak mounts instead of simply failing umount.

Fixes a05964f3917c ("[PATCH] shared mounts handling: umount")

---

If you don't figure this fix is worth it after all of this time please let me know. This feels like the proper thing to do, and I don't expect it will break anyone to fix this.

 fs/pnode.c | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)
fs/pnode.c +6 -5
index 06a793f4ae38..12fafa711114 100644
@@ -344,7 +344,6 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 {
 	struct mount *m, *child;
 	struct mount *parent = mnt->mnt_parent;
-	int ret = 0;
 
 	if (mnt == parent)
 		return do_refcount_check(mnt, refcnt);
@@ -360,11 +359,13 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 	for (m = propagation_next(parent, parent); m;
 	     		m = propagation_next(m, parent)) {
 		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
-		if (child && list_empty(&child->mnt_mounts) &&
-		    (ret = do_refcount_check(child, 1)))
-			break;
+		if (!child)
+			continue;
+		if (!list_empty(&child->mnt_mounts) ||
+		    do_refcount_check(child, 1))
+			return 1;
 	}
-	return ret;
+	return 0;
 }
 
 /*
Signature · 1 line
2.10.1
#34 lore raw

Re: [REVIEW][PATCH 1/2] mnt: Fix propagate_mount_busy to notice all cases of busy mounts.

In reply to Eric W. Biederman
Quoted 10 lines · Eric W. Biederman

When I look at what propagate_mount_busy is trying to do and I look at the code closely I discover there is a great disconnect between the two. In the ordinary non-propagation case propagate_mount_busy has been verifying that there are no submounts and that there are no extraneous references on the mount.

For mounts that the unmount would propagate to propagate_mount_busy has been verifying that there are no extraneous references only if there are no submounts. Which is nonsense.

the reason why we had to do it that way was because there were situations where it was impossible to umount anything...

take for example.

(1) mount --make-shared A

(2) mount --bind A A/a

The tree looks like this

A

	|
        B

(3) mount --bind A B/a
The tree looks like this
A

	|
 	B B'   (B' becomes a shadow mount)
	|
        C

(4) mount --make-slave A
At this point B and C are peers and A is a slave.

(5) umount B'
NOTE: This used to be possible a decade ago if the process doing the umount had access to its dentry.
The tree looks like this
A

	|
 	B
	|
        C

Now if you try to unmount C, it becomes impossible, reason being...

B is the parent of C.
So the umount propagates to A. But A has B mounted at the same location. But B is busy since it has got a child C.
So the entire umount has to fail. There is no way to umount it all. Kind of stuck for ever. That is the reason; in those days a decade ago, we relaxed the rule to let go propagated mounts that had children.

The above example is a simplest case that demonstrates the phenomenon.

Given that, the current code does not allow any process to reach shadow mount B' and given that we are getting rid of shadow mounts, I think we should allow the code changes you propose in this patch.

RP

Quoted 52 lines

Thefore rework the logic in propgate_mount_busy so that for each mount it examines it considers that mount busy if that mount has children or if there are extraneous references to that mount.

While this check was incorrect we could leak mounts instead of simply failing umount.

Fixes a05964f3917c ("[PATCH] shared mounts handling: umount")

---

If you don't figure this fix is worth it after all of this time please let me know. This feels like the proper thing to do, and I don't expect it will break anyone to fix this.

 fs/pnode.c | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)
fs/pnode.c +6 -5
index 06a793f4ae38..12fafa711114 100644
@@ -344,7 +344,6 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 {
 	struct mount *m, *child;
 	struct mount *parent = mnt->mnt_parent;
-	int ret = 0;
 
 	if (mnt == parent)
 		return do_refcount_check(mnt, refcnt);
@@ -360,11 +359,13 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 	for (m = propagation_next(parent, parent); m;
 	     		m = propagation_next(m, parent)) {
 		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
-		if (child && list_empty(&child->mnt_mounts) &&
-		    (ret = do_refcount_check(child, 1)))
-			break;
+		if (!child)
+			continue;
+		if (!list_empty(&child->mnt_mounts) ||
+		    do_refcount_check(child, 1))
+			return 1;
 	}
-	return ret;
+	return 0;
 }
 
 /*
Signature · 1 line
2.10.1
Signature · 1 line
Ram Pai
#35 lore raw

Re: [REVIEW][PATCH 1/2] mnt: Fix propagate_mount_busy to notice all cases of busy mounts.

Quoted 59 lines · Ram Pai
Quoted 10 lines · Eric W. Biederman

When I look at what propagate_mount_busy is trying to do and I look at the code closely I discover there is a great disconnect between the two. In the ordinary non-propagation case propagate_mount_busy has been verifying that there are no submounts and that there are no extraneous references on the mount.

For mounts that the unmount would propagate to propagate_mount_busy has been verifying that there are no extraneous references only if there are no submounts. Which is nonsense.

the reason why we had to do it that way was because there were situations where it was impossible to umount anything...

take for example.

(1) mount --make-shared A

(2) mount --bind A A/a

The tree looks like this

A

	|
        B

(3) mount --bind A B/a
The tree looks like this
A

	|
 	B B'   (B' becomes a shadow mount)
	|
        C

(4) mount --make-slave A
At this point B and C are peers and A is a slave.

(5) umount B'
NOTE: This used to be possible a decade ago if the process doing the umount had access to its dentry.
The tree looks like this
A

	|
 	B
	|
        C

Now if you try to unmount C, it becomes impossible, reason being...

B is the parent of C.
So the umount propagates to A. But A has B mounted at the same location. But B is busy since it has got a child C.
So the entire umount has to fail. There is no way to umount it all. Kind of stuck for ever. That is the reason; in those days a decade ago, we relaxed the rule to let go propagated mounts that had children.

Quoted 5 lines

The above example is a simplest case that demonstrates the phenomenon.

Given that, the current code does not allow any process to reach shadow mount B' and given that we are getting rid of shadow mounts, I think we should allow the code changes you propose in this patch.

Thank you very much for the good description of why propagate_mount_busy works the way it does.

I just finished taking a hard look at this and in fact the current code does allow reaching B' via umount propagation. My other patch changes exactly how you have to reach it but it is still possible to umount B'

At the same time those mounts have alwasy been unmountable with "umount -l" aka MOUNT_DETACH.

Have you ever encountered a non-contrived situation that leads to this kind of problem?

I expect if we can verify that docker, and systemd are similar pieces of the linux ecosystem are not depending on the exact details of the propagation of the umount busy we should be able to remove this.

Last I looked the uses of mount and umount were all quite simple, so I think it is very possible to make this change. Especially as it is now much harder to get into the situation you describe.

Eric

#36 lore raw

Re: [REVIEW][PATCH 1/2] mnt: Fix propagate_mount_busy to notice all cases of busy mounts.

Quoted 74 lines · Eric W. Biederman
Quoted 59 lines · Ram Pai
Quoted 10 lines · Eric W. Biederman

When I look at what propagate_mount_busy is trying to do and I look at the code closely I discover there is a great disconnect between the two. In the ordinary non-propagation case propagate_mount_busy has been verifying that there are no submounts and that there are no extraneous references on the mount.

For mounts that the unmount would propagate to propagate_mount_busy has been verifying that there are no extraneous references only if there are no submounts. Which is nonsense.

the reason why we had to do it that way was because there were situations where it was impossible to umount anything...

take for example.

(1) mount --make-shared A

(2) mount --bind A A/a

The tree looks like this

A

	|
        B

(3) mount --bind A B/a
The tree looks like this
A

	|
 	B B'   (B' becomes a shadow mount)
	|
        C

(4) mount --make-slave A
At this point B and C are peers and A is a slave.

(5) umount B'
NOTE: This used to be possible a decade ago if the process doing the umount had access to its dentry.
The tree looks like this
A

	|
 	B
	|
        C

Now if you try to unmount C, it becomes impossible, reason being...

B is the parent of C.
So the umount propagates to A. But A has B mounted at the same location. But B is busy since it has got a child C.
So the entire umount has to fail. There is no way to umount it all. Kind of stuck for ever. That is the reason; in those days a decade ago, we relaxed the rule to let go propagated mounts that had children.

Quoted 5 lines

The above example is a simplest case that demonstrates the phenomenon.

Given that, the current code does not allow any process to reach shadow mount B' and given that we are getting rid of shadow mounts, I think we should allow the code changes you propose in this patch.

Thank you very much for the good description of why propagate_mount_busy works the way it does.

I just finished taking a hard look at this and in fact the current code does allow reaching B' via umount propagation. My other patch changes exactly how you have to reach it but it is still possible to umount B'

Quoted 5 lines

At the same time those mounts have alwasy been unmountable with "umount -l" aka MOUNT_DETACH.

Have you ever encountered a non-contrived situation that leads to this kind of problem?

No. Simple cases dont expose any of these hidden issues. The devil is in the detail. There used to be a test suite; which I believe has been integrated into LTP, that had all kinds of contrived cases to expose as many hidden issues.

RP

maintainer
#37 lore raw

Re: [REVIEW][PATCH] mnt: Tuck mounts under others instead of creating shadow/side mounts.

In reply to Eric W. Biederman
Quoted 12 lines · Eric W. Biederman
Quoted 10 lines · Al Viro
Quoted 8 lines · Eric W. Biederman
Quoted 4 lines

fs/namespace.c static int attach_recursive_mnt(struct mount *source_mnt, · line 2032

Quoted 1 line
fs/namespace.c +1 -0
+		if (child->mnt.mnt_root == smp->m_dentry) {

Explain, please. In which case is that condition _not_ satisfied, and what should happen i

When a tree is grafted in that condition does not apply to the lower leaves of the tree. At the same time nothing needs to be done for those leaves. Only the primary mountpoint needs to worry about tucking.

Quoted 5 lines
Quoted 2 lines

How in hell would those lower leaves end up on the list in
attach_recursive_mnt()? IDGI...

The submounts of a mount tree that is being attached need to have commit_tree called on them to attach them to a mount namespace.

Huh? commit_tree() is called once for each copy of the source tree. This

        list_add_tail(&head, &mnt->mnt_list);
        list_for_each_entry(m, &head, mnt_list)
                m->mnt_ns = n;

is what goes through submounts in each of them, _not_ the loop in the caller.

What we get out of propagate_mnt() is the list of copies of source tree, one for each of the mountpoints that should get propagation from the target.

	->mnt_mountpoint/->mnt_parent is fully set for all nodes.
	Everything except the roots of those trees is hashed and
has ->mnt_child set up.
	->mnt_hash of the roots of those copies host the cyclic list,
anchored in tree_list passed to propagate_mnt().
	->mnt_list in each copy forms an unanchored cyclic list

going through all mounts in that copy.

The loop in attach_recursive_mnt() takes the tree_list apart and for each element (== each copy of source tree) we have commit_tree() called once, doing the remaining work:
* splices the ->mnt_list into the namespace's mount list
* sets ->mnt_ns for all nodes (root and submounts alike)
* sets ->mnt_child and ->mnt_hash for the root.

Again, the loop in attach_recursive_mnt() is over the set of secondary copies of the source tree; it goes *only* through their roots. Submounts are seen only by commit_tree(), in the list_for_each_entry() loop in that function. Hell, try to add else WARN_ON(1); to that if (...) of yours and see if you can trigger it if you don't believe the above...

Quoted 6 lines
Quoted 4 lines

You have "if mount is not locked - mark it; if mount is already marked - mark it again". The latter part (|| IS_MNT_MARKED(mnt), that is) looks very odd, won't you agree? What the hell was that (its counterpart in the earlier code) about?

Not mark it again. If the parent is marked mark the child.

*doh*

#38 lore raw

Re: [REVIEW][PATCH] mnt: Tuck mounts under others instead of creating shadow/side mounts.

In reply to Eric W. Biederman

Hi Eric,

I found one issue about this patch. Take a look at the next script. The idea is simple, we call mount twice and than we call umount twice, but the second umount fails.

[root@fc24 ~]# cat m.sh
#!/bin/sh

set -x -e
mount -t tmpfs xxx /mnt
mkdir -p /mnt/1
mkdir -p /mnt/2
mount --bind /mnt/1 /mnt/1
mount --make-shared /mnt/1
mount --bind /mnt/1 /mnt/2
mkdir -p /mnt/1/1
for i in seq 2; do

	mount --bind /mnt/1/1 /mnt/1/1
done
for i in `seq 2`; do
	umount /mnt/1/1 || {
		cat /proc/self/mountinfo | grep xxx
		exit 1
	}

done

[root@fc24 ~]# unshare -Urm ./m.sh
+ mount -t tmpfs xxx /mnt
+ mkdir -p /mnt/1
+ mkdir -p /mnt/2
+ mount --bind /mnt/1 /mnt/1
+ mount --make-shared /mnt/1
+ mount --bind /mnt/1 /mnt/2
+ mkdir -p /mnt/1/1
++ seq 2
+ for i in 'seq 2'
+ mount --bind /mnt/1/1 /mnt/1/1
+ for i in 'seq 2'
+ mount --bind /mnt/1/1 /mnt/1/1
++ seq 2
+ for i in 'seq 2'
+ umount /mnt/1/1
+ for i in 'seq 2'
+ umount /mnt/1/1
umount: /mnt/1/1: not mounted
+ cat /proc/self/mountinfo
+ grep xxx
147 116 0:42 / /mnt rw,relatime - tmpfs xxx rw
148 147 0:42 /1 /mnt/1 rw,relatime shared:65 - tmpfs xxx rw 149 147 0:42 /1 /mnt/2 rw,relatime shared:65 - tmpfs xxx rw 157 149 0:42 /1/1 /mnt/2/1 rw,relatime shared:65 - tmpfs xxx rw
+ exit 1

And you can see that /mnt/2 contains a mount, but it should not.

Thanks,
Andrei

Quoted 360 lines · Eric W. Biederman

Ever since mount propagation was introduced in cases where a mount in propagated to parent mount mountpoint pair that is already in use the code has placed the new mount behind the old mount in the mount hash table.

This implementation detail is problematic as it allows creating arbitrary length mount hash chains.

Furthermore it invalidates the constraint maintained elsewhere in the mount code that a parent mount and a mountpoint pair will have exactly one mount upon them. Making it hard to deal with and to talk about this special case in the mount code.

Modify mount propagation to notice when there is already a mount at the parent mount and mountpoint where a new mount is propagating to and place that preexisting mount on top of the new mount.

Modify unmount propagation to notice when a mount that is being unmounted has another mount on top of it (and no other children), and to replace the unmounted mount with the mount on top of it.

Move the MNT_UMUONT test from __lookup_mnt_last into __propagate_umount as that is the only call of __lookup_mnt_last where MNT_UMOUNT may be set on any mount visible in the mount hash table.

These modifications allow:
- __lookup_mnt_last to be removed.
- attach_shadows to be renamed __attach_mnt and the it's shadow handling to be removed.
- commit_tree to be simplified
- copy_tree to be simplified

The result is an easier to understand tree of mounts that does not allow creation of arbitrary length hash chains in the mount hash table.

v2: Updated to mnt_change_mountpoint to not call dput or mntput and instead to decrement the counts directly. It is guaranteed that there will be other references when mnt_change_mountpoint is called so this is safe.

Fixes b90fa9ae8f51 ("[PATCH] shared mount handling: bind and rbind")

---

Since the last version some of you may have seen I have modified my implementation of mnt_change_mountpoint so that it no longer calls mntput or dput but instead relies on the knowledge that it can not possibly have the last reference to the mnt and dentry of interest. This avoids code checking tools from complaining bitterly.

This is on top of my previous patch that sorts out locking of the mountpoint hash table. After time giving ample time for review I intend to push this and the previous bug fix to Linus.

 fs/mount.h     |   1 -
 fs/namespace.c | 110 +++++++++++++++++++++++++++++++--------------------------
 fs/pnode.c     |  27 ++++++++++----
 fs/pnode.h     |   2 ++
 4 files changed, 82 insertions(+), 58 deletions(-)
fs/mount.h +0 -1
index 2c856fc47ae3..2826543a131d 100644
@@ -89,7 +89,6 @@ static inline int is_mounted(struct vfsmount *mnt)
 }
 
 extern struct mount *__lookup_mnt(struct vfsmount *, struct dentry *);
-extern struct mount *__lookup_mnt_last(struct vfsmount *, struct dentry *);
 
 extern int __legitimize_mnt(struct vfsmount *, unsigned);
 extern bool legitimize_mnt(struct vfsmount *, unsigned);
fs/namespace.c +59 -51
index 487ba30bb5c6..91ccfb73f0e0 100644
@@ -637,28 +637,6 @@ struct mount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)
 }
 
 /*
- * find the last mount at @dentry on vfsmount @mnt.
- * mount_lock must be held.
- */
-struct mount *__lookup_mnt_last(struct vfsmount *mnt, struct dentry *dentry)
-{
-	struct mount *p, *res = NULL;
-	p = __lookup_mnt(mnt, dentry);
-	if (!p)
-		goto out;
-	if (!(p->mnt.mnt_flags & MNT_UMOUNT))
-		res = p;
-	hlist_for_each_entry_continue(p, mnt_hash) {
-		if (&p->mnt_parent->mnt != mnt || p->mnt_mountpoint != dentry)
-			break;
-		if (!(p->mnt.mnt_flags & MNT_UMOUNT))
-			res = p;
-	}
-out:
-	return res;
-}
-
-/*
  * lookup_mnt - Return the first child mount mounted at path
  *
  * "First" means first mounted chronologically.  If you create the
@@ -878,6 +856,13 @@ void mnt_set_mountpoint(struct mount *mnt,
 	hlist_add_head(&child_mnt->mnt_mp_list, &mp->m_list);
 }
 
+static void __attach_mnt(struct mount *mnt, struct mount *parent)
+{
+	hlist_add_head_rcu(&mnt->mnt_hash,
+			   m_hash(&parent->mnt, mnt->mnt_mountpoint));
+	list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
+}
+
 /*
  * vfsmount lock must be held for write
  */
@@ -886,28 +871,45 @@ static void attach_mnt(struct mount *mnt,
 			struct mountpoint *mp)
 {
 	mnt_set_mountpoint(parent, mp, mnt);
-	hlist_add_head_rcu(&mnt->mnt_hash, m_hash(&parent->mnt, mp->m_dentry));
-	list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
+	__attach_mnt(mnt, parent);
 }
 
-static void attach_shadowed(struct mount *mnt,
-			struct mount *parent,
-			struct mount *shadows)
+void mnt_change_mountpoint(struct mount *parent, struct mountpoint *mp, struct mount *mnt)
 {
-	if (shadows) {
-		hlist_add_behind_rcu(&mnt->mnt_hash, &shadows->mnt_hash);
-		list_add(&mnt->mnt_child, &shadows->mnt_child);
-	} else {
-		hlist_add_head_rcu(&mnt->mnt_hash,
-				m_hash(&parent->mnt, mnt->mnt_mountpoint));
-		list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
-	}
+	struct mountpoint *old_mp = mnt->mnt_mp;
+	struct dentry *old_mountpoint = mnt->mnt_mountpoint;
+	struct mount *old_parent = mnt->mnt_parent;
+
+	list_del_init(&mnt->mnt_child);
+	hlist_del_init(&mnt->mnt_mp_list);
+	hlist_del_init_rcu(&mnt->mnt_hash);
+
+	attach_mnt(mnt, parent, mp);
+
+	put_mountpoint(old_mp);
+
+	/*
+	 * Safely avoid even the suggestion this code might sleep or
+	 * lock the mount hash by taking avantage of the knowlege that
+	 * mnt_change_mounpoint will not release the final reference
+	 * to a mountpoint.
+	 *
+	 * During mounting, another mount will continue to use the old
+	 * mountpoint and during unmounting, the old mountpoint will
+	 * continue to exist until namespace_unlock which happens well
+	 * after mnt_change_mountpoint.
+	 */
+	spin_lock(&old_mountpoint->d_lock);
+	old_mountpoint->d_lockref.count--;
+	spin_unlock(&old_mountpoint->d_lock);
+
+	mnt_add_count(old_parent, -1);
 }
 
 /*
  * vfsmount lock must be held for write
  */
-static void commit_tree(struct mount *mnt, struct mount *shadows)
+static void commit_tree(struct mount *mnt)
 {
 	struct mount *parent = mnt->mnt_parent;
 	struct mount *m;
@@ -925,7 +927,7 @@ static void commit_tree(struct mount *mnt, struct mount *shadows)
 	n->mounts += n->pending_mounts;
 	n->pending_mounts = 0;
 
-	attach_shadowed(mnt, parent, shadows);
+	__attach_mnt(mnt, parent);
 	touch_mnt_namespace(n);
 }
 
@@ -1764,7 +1766,6 @@ struct mount *copy_tree(struct mount *mnt, struct dentry *dentry,
 			continue;
 
 		for (s = r; s; s = next_mnt(s, r)) {
-			struct mount *t = NULL;
 			if (!(flag & CL_COPY_UNBINDABLE) &&
 			    IS_MNT_UNBINDABLE(s)) {
 				s = skip_mnt_tree(s);
@@ -1786,14 +1787,7 @@ struct mount *copy_tree(struct mount *mnt, struct dentry *dentry,
 				goto out;
 			lock_mount_hash();
 			list_add_tail(&q->mnt_list, &res->mnt_list);
-			mnt_set_mountpoint(parent, p->mnt_mp, q);
-			if (!list_empty(&parent->mnt_mounts)) {
-				t = list_last_entry(&parent->mnt_mounts,
-					struct mount, mnt_child);
-				if (t->mnt_mp != p->mnt_mp)
-					t = NULL;
-			}
-			attach_shadowed(q, parent, t);
+			attach_mnt(q, parent, p->mnt_mp);
 			unlock_mount_hash();
 		}
 	}
@@ -1992,10 +1986,18 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 {
 	HLIST_HEAD(tree_list);
 	struct mnt_namespace *ns = dest_mnt->mnt_ns;
+	struct mountpoint *smp;
 	struct mount *child, *p;
 	struct hlist_node *n;
 	int err;
 
+	/* Preallocate a mountpoint in case the new mounts need
+	 * to be tucked under other mounts.
+	 */
+	smp = get_mountpoint(source_mnt->mnt.mnt_root);
+	if (IS_ERR(smp))
+		return PTR_ERR(smp);
+
 	/* Is there space to add these mounts to the mount namespace? */
 	if (!parent_path) {
 		err = count_mounts(ns, source_mnt);
@@ -2022,17 +2024,22 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 		touch_mnt_namespace(source_mnt->mnt_ns);
 	} else {
 		mnt_set_mountpoint(dest_mnt, dest_mp, source_mnt);
-		commit_tree(source_mnt, NULL);
+		commit_tree(source_mnt);
 	}
 
 	hlist_for_each_entry_safe(child, n, &tree_list, mnt_hash) {
-		struct mount *q;
 		hlist_del_init(&child->mnt_hash);
-		q = __lookup_mnt_last(&child->mnt_parent->mnt,
-				      child->mnt_mountpoint);
-		commit_tree(child, q);
+		if (child->mnt.mnt_root == smp->m_dentry) {
+			struct mount *q;
+			q = __lookup_mnt(&child->mnt_parent->mnt,
+					 child->mnt_mountpoint);
+			if (q)
+				mnt_change_mountpoint(child, smp, q);
+		}
+		commit_tree(child);
 	}
 	unlock_mount_hash();
+	put_mountpoint(smp);
 
 	return 0;
 
@@ -2046,6 +2053,7 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 	cleanup_group_ids(source_mnt, NULL);
  out:
 	ns->pending_mounts = 0;
+	put_mountpoint(smp);
 	return err;
 }
 
fs/pnode.c +21 -6
index 06a793f4ae38..eb4331240fd1 100644
@@ -327,6 +327,9 @@ int propagate_mnt(struct mount *dest_mnt, struct mountpoint *dest_mp,
  */
 static inline int do_refcount_check(struct mount *mnt, int count)
 {
+	struct mount *topper = __lookup_mnt(&mnt->mnt, mnt->mnt.mnt_root);
+	if (topper)
+		count++;
 	return mnt_get_count(mnt) > count;
 }
 
@@ -359,7 +362,7 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 
 	for (m = propagation_next(parent, parent); m;
 	     		m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
 		if (child && list_empty(&child->mnt_mounts) &&
 		    (ret = do_refcount_check(child, 1)))
 			break;
@@ -381,7 +384,7 @@ void propagate_mount_unlock(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
 		if (child)
 			child->mnt.mnt_flags &= ~MNT_LOCKED;
 	}
@@ -399,9 +402,11 @@ static void mark_umount_candidates(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-		struct mount *child = __lookup_mnt_last(&m->mnt,
+		struct mount *child = __lookup_mnt(&m->mnt,
 						mnt->mnt_mountpoint);
-		if (child && (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m))) {
+		if (!child || (child->mnt.mnt_flags & MNT_UMOUNT))
+			continue;
+		if (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m)) {
 			SET_MNT_MARK(child);
 		}
 	}
@@ -420,8 +425,8 @@ static void __propagate_umount(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-
-		struct mount *child = __lookup_mnt_last(&m->mnt,
+		struct mount *topper;
+		struct mount *child = __lookup_mnt(&m->mnt,
 						mnt->mnt_mountpoint);
 		/*
 		 * umount the child only if the child has no children
@@ -430,6 +435,16 @@ static void __propagate_umount(struct mount *mnt)
 		if (!child || !IS_MNT_MARKED(child))
 			continue;
 		CLEAR_MNT_MARK(child);
+
+		/* If there is exactly one mount covering all of child
+		 * replace child with that mount.
+		 */
+		topper = __lookup_mnt(&child->mnt, child->mnt.mnt_root);
+		if (topper &&
+		    (child->mnt_mounts.next == &topper->mnt_child) &&
+		    (topper->mnt_child.next == &child->mnt_mounts))
+			mnt_change_mountpoint(child->mnt_parent, child->mnt_mp, topper);
+
 		if (list_empty(&child->mnt_mounts)) {
 			list_del_init(&child->mnt_child);
 			child->mnt.mnt_flags |= MNT_UMOUNT;
fs/pnode.h +2 -0
index 550f5a8b4fcf..dc87e65becd2 100644
@@ -49,6 +49,8 @@ int get_dominating_id(struct mount *mnt, const struct path *root);
 unsigned int mnt_get_count(struct mount *mnt);
 void mnt_set_mountpoint(struct mount *, struct mountpoint *,
 			struct mount *);
+void mnt_change_mountpoint(struct mount *parent, struct mountpoint *mp,
+			   struct mount *mnt);
 struct mount *copy_tree(struct mount *, struct dentry *, int);
 bool is_path_reachable(struct mount *, struct dentry *,
 			 const struct path *root);
Signature · 1 line
2.10.1
#39 lore raw

Re: [REVIEW][PATCH] mnt: Tuck mounts under others instead of creating shadow/side mounts.

Quoted 5 lines · Andrei Vagin

Hi Eric,

I found one issue about this patch. Take a look at the next script. The idea is simple, we call mount twice and than we call umount twice, but the second umount fails.

Definitely an odd. I will take a look.

Eric

Quoted 415 lines

[root@fc24 ~]# cat m.sh
#!/bin/sh

set -x -e
mount -t tmpfs xxx /mnt
mkdir -p /mnt/1
mkdir -p /mnt/2
mount --bind /mnt/1 /mnt/1
mount --make-shared /mnt/1
mount --bind /mnt/1 /mnt/2
mkdir -p /mnt/1/1
for i in seq 2; do

	mount --bind /mnt/1/1 /mnt/1/1
done
for i in `seq 2`; do
	umount /mnt/1/1 || {
		cat /proc/self/mountinfo | grep xxx
		exit 1
	}

done

[root@fc24 ~]# unshare -Urm ./m.sh
+ mount -t tmpfs xxx /mnt
+ mkdir -p /mnt/1
+ mkdir -p /mnt/2
+ mount --bind /mnt/1 /mnt/1
+ mount --make-shared /mnt/1
+ mount --bind /mnt/1 /mnt/2
+ mkdir -p /mnt/1/1
++ seq 2
+ for i in 'seq 2'
+ mount --bind /mnt/1/1 /mnt/1/1
+ for i in 'seq 2'
+ mount --bind /mnt/1/1 /mnt/1/1
++ seq 2
+ for i in 'seq 2'
+ umount /mnt/1/1
+ for i in 'seq 2'
+ umount /mnt/1/1
umount: /mnt/1/1: not mounted
+ cat /proc/self/mountinfo
+ grep xxx
147 116 0:42 / /mnt rw,relatime - tmpfs xxx rw
148 147 0:42 /1 /mnt/1 rw,relatime shared:65 - tmpfs xxx rw 149 147 0:42 /1 /mnt/2 rw,relatime shared:65 - tmpfs xxx rw 157 149 0:42 /1/1 /mnt/2/1 rw,relatime shared:65 - tmpfs xxx rw
+ exit 1

And you can see that /mnt/2 contains a mount, but it should not.

Thanks,
Andrei

Quoted 360 lines · Eric W. Biederman

Ever since mount propagation was introduced in cases where a mount in propagated to parent mount mountpoint pair that is already in use the code has placed the new mount behind the old mount in the mount hash table.

This implementation detail is problematic as it allows creating arbitrary length mount hash chains.

Furthermore it invalidates the constraint maintained elsewhere in the mount code that a parent mount and a mountpoint pair will have exactly one mount upon them. Making it hard to deal with and to talk about this special case in the mount code.

Modify mount propagation to notice when there is already a mount at the parent mount and mountpoint where a new mount is propagating to and place that preexisting mount on top of the new mount.

Modify unmount propagation to notice when a mount that is being unmounted has another mount on top of it (and no other children), and to replace the unmounted mount with the mount on top of it.

Move the MNT_UMUONT test from __lookup_mnt_last into __propagate_umount as that is the only call of __lookup_mnt_last where MNT_UMOUNT may be set on any mount visible in the mount hash table.

These modifications allow:
- __lookup_mnt_last to be removed.
- attach_shadows to be renamed __attach_mnt and the it's shadow handling to be removed.
- commit_tree to be simplified
- copy_tree to be simplified

The result is an easier to understand tree of mounts that does not allow creation of arbitrary length hash chains in the mount hash table.

v2: Updated to mnt_change_mountpoint to not call dput or mntput and instead to decrement the counts directly. It is guaranteed that there will be other references when mnt_change_mountpoint is called so this is safe.

Fixes b90fa9ae8f51 ("[PATCH] shared mount handling: bind and rbind")

---

Since the last version some of you may have seen I have modified my implementation of mnt_change_mountpoint so that it no longer calls mntput or dput but instead relies on the knowledge that it can not possibly have the last reference to the mnt and dentry of interest. This avoids code checking tools from complaining bitterly.

This is on top of my previous patch that sorts out locking of the mountpoint hash table. After time giving ample time for review I intend to push this and the previous bug fix to Linus.

 fs/mount.h     |   1 -
 fs/namespace.c | 110 +++++++++++++++++++++++++++++++--------------------------
 fs/pnode.c     |  27 ++++++++++----
 fs/pnode.h     |   2 ++
 4 files changed, 82 insertions(+), 58 deletions(-)
fs/mount.h +0 -1
index 2c856fc47ae3..2826543a131d 100644
@@ -89,7 +89,6 @@ static inline int is_mounted(struct vfsmount *mnt)
 }
 
 extern struct mount *__lookup_mnt(struct vfsmount *, struct dentry *);
-extern struct mount *__lookup_mnt_last(struct vfsmount *, struct dentry *);
 
 extern int __legitimize_mnt(struct vfsmount *, unsigned);
 extern bool legitimize_mnt(struct vfsmount *, unsigned);
fs/namespace.c +59 -51
index 487ba30bb5c6..91ccfb73f0e0 100644
@@ -637,28 +637,6 @@ struct mount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)
 }
 
 /*
- * find the last mount at @dentry on vfsmount @mnt.
- * mount_lock must be held.
- */
-struct mount *__lookup_mnt_last(struct vfsmount *mnt, struct dentry *dentry)
-{
-	struct mount *p, *res = NULL;
-	p = __lookup_mnt(mnt, dentry);
-	if (!p)
-		goto out;
-	if (!(p->mnt.mnt_flags & MNT_UMOUNT))
-		res = p;
-	hlist_for_each_entry_continue(p, mnt_hash) {
-		if (&p->mnt_parent->mnt != mnt || p->mnt_mountpoint != dentry)
-			break;
-		if (!(p->mnt.mnt_flags & MNT_UMOUNT))
-			res = p;
-	}
-out:
-	return res;
-}
-
-/*
  * lookup_mnt - Return the first child mount mounted at path
  *
  * "First" means first mounted chronologically.  If you create the
@@ -878,6 +856,13 @@ void mnt_set_mountpoint(struct mount *mnt,
 	hlist_add_head(&child_mnt->mnt_mp_list, &mp->m_list);
 }
 
+static void __attach_mnt(struct mount *mnt, struct mount *parent)
+{
+	hlist_add_head_rcu(&mnt->mnt_hash,
+			   m_hash(&parent->mnt, mnt->mnt_mountpoint));
+	list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
+}
+
 /*
  * vfsmount lock must be held for write
  */
@@ -886,28 +871,45 @@ static void attach_mnt(struct mount *mnt,
 			struct mountpoint *mp)
 {
 	mnt_set_mountpoint(parent, mp, mnt);
-	hlist_add_head_rcu(&mnt->mnt_hash, m_hash(&parent->mnt, mp->m_dentry));
-	list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
+	__attach_mnt(mnt, parent);
 }
 
-static void attach_shadowed(struct mount *mnt,
-			struct mount *parent,
-			struct mount *shadows)
+void mnt_change_mountpoint(struct mount *parent, struct mountpoint *mp, struct mount *mnt)
 {
-	if (shadows) {
-		hlist_add_behind_rcu(&mnt->mnt_hash, &shadows->mnt_hash);
-		list_add(&mnt->mnt_child, &shadows->mnt_child);
-	} else {
-		hlist_add_head_rcu(&mnt->mnt_hash,
-				m_hash(&parent->mnt, mnt->mnt_mountpoint));
-		list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
-	}
+	struct mountpoint *old_mp = mnt->mnt_mp;
+	struct dentry *old_mountpoint = mnt->mnt_mountpoint;
+	struct mount *old_parent = mnt->mnt_parent;
+
+	list_del_init(&mnt->mnt_child);
+	hlist_del_init(&mnt->mnt_mp_list);
+	hlist_del_init_rcu(&mnt->mnt_hash);
+
+	attach_mnt(mnt, parent, mp);
+
+	put_mountpoint(old_mp);
+
+	/*
+	 * Safely avoid even the suggestion this code might sleep or
+	 * lock the mount hash by taking avantage of the knowlege that
+	 * mnt_change_mounpoint will not release the final reference
+	 * to a mountpoint.
+	 *
+	 * During mounting, another mount will continue to use the old
+	 * mountpoint and during unmounting, the old mountpoint will
+	 * continue to exist until namespace_unlock which happens well
+	 * after mnt_change_mountpoint.
+	 */
+	spin_lock(&old_mountpoint->d_lock);
+	old_mountpoint->d_lockref.count--;
+	spin_unlock(&old_mountpoint->d_lock);
+
+	mnt_add_count(old_parent, -1);
 }
 
 /*
  * vfsmount lock must be held for write
  */
-static void commit_tree(struct mount *mnt, struct mount *shadows)
+static void commit_tree(struct mount *mnt)
 {
 	struct mount *parent = mnt->mnt_parent;
 	struct mount *m;
@@ -925,7 +927,7 @@ static void commit_tree(struct mount *mnt, struct mount *shadows)
 	n->mounts += n->pending_mounts;
 	n->pending_mounts = 0;
 
-	attach_shadowed(mnt, parent, shadows);
+	__attach_mnt(mnt, parent);
 	touch_mnt_namespace(n);
 }
 
@@ -1764,7 +1766,6 @@ struct mount *copy_tree(struct mount *mnt, struct dentry *dentry,
 			continue;
 
 		for (s = r; s; s = next_mnt(s, r)) {
-			struct mount *t = NULL;
 			if (!(flag & CL_COPY_UNBINDABLE) &&
 			    IS_MNT_UNBINDABLE(s)) {
 				s = skip_mnt_tree(s);
@@ -1786,14 +1787,7 @@ struct mount *copy_tree(struct mount *mnt, struct dentry *dentry,
 				goto out;
 			lock_mount_hash();
 			list_add_tail(&q->mnt_list, &res->mnt_list);
-			mnt_set_mountpoint(parent, p->mnt_mp, q);
-			if (!list_empty(&parent->mnt_mounts)) {
-				t = list_last_entry(&parent->mnt_mounts,
-					struct mount, mnt_child);
-				if (t->mnt_mp != p->mnt_mp)
-					t = NULL;
-			}
-			attach_shadowed(q, parent, t);
+			attach_mnt(q, parent, p->mnt_mp);
 			unlock_mount_hash();
 		}
 	}
@@ -1992,10 +1986,18 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 {
 	HLIST_HEAD(tree_list);
 	struct mnt_namespace *ns = dest_mnt->mnt_ns;
+	struct mountpoint *smp;
 	struct mount *child, *p;
 	struct hlist_node *n;
 	int err;
 
+	/* Preallocate a mountpoint in case the new mounts need
+	 * to be tucked under other mounts.
+	 */
+	smp = get_mountpoint(source_mnt->mnt.mnt_root);
+	if (IS_ERR(smp))
+		return PTR_ERR(smp);
+
 	/* Is there space to add these mounts to the mount namespace? */
 	if (!parent_path) {
 		err = count_mounts(ns, source_mnt);
@@ -2022,17 +2024,22 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 		touch_mnt_namespace(source_mnt->mnt_ns);
 	} else {
 		mnt_set_mountpoint(dest_mnt, dest_mp, source_mnt);
-		commit_tree(source_mnt, NULL);
+		commit_tree(source_mnt);
 	}
 
 	hlist_for_each_entry_safe(child, n, &tree_list, mnt_hash) {
-		struct mount *q;
 		hlist_del_init(&child->mnt_hash);
-		q = __lookup_mnt_last(&child->mnt_parent->mnt,
-				      child->mnt_mountpoint);
-		commit_tree(child, q);
+		if (child->mnt.mnt_root == smp->m_dentry) {
+			struct mount *q;
+			q = __lookup_mnt(&child->mnt_parent->mnt,
+					 child->mnt_mountpoint);
+			if (q)
+				mnt_change_mountpoint(child, smp, q);
+		}
+		commit_tree(child);
 	}
 	unlock_mount_hash();
+	put_mountpoint(smp);
 
 	return 0;
 
@@ -2046,6 +2053,7 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 	cleanup_group_ids(source_mnt, NULL);
  out:
 	ns->pending_mounts = 0;
+	put_mountpoint(smp);
 	return err;
 }
 
fs/pnode.c +21 -6
index 06a793f4ae38..eb4331240fd1 100644
@@ -327,6 +327,9 @@ int propagate_mnt(struct mount *dest_mnt, struct mountpoint *dest_mp,
  */
 static inline int do_refcount_check(struct mount *mnt, int count)
 {
+	struct mount *topper = __lookup_mnt(&mnt->mnt, mnt->mnt.mnt_root);
+	if (topper)
+		count++;
 	return mnt_get_count(mnt) > count;
 }
 
@@ -359,7 +362,7 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 
 	for (m = propagation_next(parent, parent); m;
 	     		m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
 		if (child && list_empty(&child->mnt_mounts) &&
 		    (ret = do_refcount_check(child, 1)))
 			break;
@@ -381,7 +384,7 @@ void propagate_mount_unlock(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
 		if (child)
 			child->mnt.mnt_flags &= ~MNT_LOCKED;
 	}
@@ -399,9 +402,11 @@ static void mark_umount_candidates(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-		struct mount *child = __lookup_mnt_last(&m->mnt,
+		struct mount *child = __lookup_mnt(&m->mnt,
 						mnt->mnt_mountpoint);
-		if (child && (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m))) {
+		if (!child || (child->mnt.mnt_flags & MNT_UMOUNT))
+			continue;
+		if (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m)) {
 			SET_MNT_MARK(child);
 		}
 	}
@@ -420,8 +425,8 @@ static void __propagate_umount(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-
-		struct mount *child = __lookup_mnt_last(&m->mnt,
+		struct mount *topper;
+		struct mount *child = __lookup_mnt(&m->mnt,
 						mnt->mnt_mountpoint);
 		/*
 		 * umount the child only if the child has no children
@@ -430,6 +435,16 @@ static void __propagate_umount(struct mount *mnt)
 		if (!child || !IS_MNT_MARKED(child))
 			continue;
 		CLEAR_MNT_MARK(child);
+
+		/* If there is exactly one mount covering all of child
+		 * replace child with that mount.
+		 */
+		topper = __lookup_mnt(&child->mnt, child->mnt.mnt_root);
+		if (topper &&
+		    (child->mnt_mounts.next == &topper->mnt_child) &&
+		    (topper->mnt_child.next == &child->mnt_mounts))
+			mnt_change_mountpoint(child->mnt_parent, child->mnt_mp, topper);
+
 		if (list_empty(&child->mnt_mounts)) {
 			list_del_init(&child->mnt_child);
 			child->mnt.mnt_flags |= MNT_UMOUNT;
fs/pnode.h +2 -0
index 550f5a8b4fcf..dc87e65becd2 100644
@@ -49,6 +49,8 @@ int get_dominating_id(struct mount *mnt, const struct path *root);
 unsigned int mnt_get_count(struct mount *mnt);
 void mnt_set_mountpoint(struct mount *, struct mountpoint *,
 			struct mount *);
+void mnt_change_mountpoint(struct mount *parent, struct mountpoint *mp,
+			   struct mount *mnt);
 struct mount *copy_tree(struct mount *, struct dentry *, int);
 bool is_path_reachable(struct mount *, struct dentry *,
 			 const struct path *root);
Signature · 1 line
2.10.1
#40 lore raw

Re: [REVIEW][PATCH] mnt: Tuck mounts under others instead of creating shadow/side mounts.

Quoted 9 lines · [email protected] (Eric W. Biederman) writes:
Quoted 5 lines · Andrei Vagin

Hi Eric,

I found one issue about this patch. Take a look at the next script. The idea is simple, we call mount twice and than we call umount twice, but the second umount fails.

Definitely an odd. I will take a look.

After a little more looking I have to say the only thing I can see wrong in the behavior is that the first umount doesn't unmount everything.

Changing the mount propagation tree while it is being traversed apparently prevents a complete traversal of the propgation tree.

Last time I was looking at this I was playing with multipass algorithm because of these peculiarities that happen with propagation trees.

I suspect we are going to need to fix umount to behave correct before we tune it for performance. So that we actually know what correct behavior is.

Eric

Quoted 415 lines
Quoted 415 lines

[root@fc24 ~]# cat m.sh
#!/bin/sh

set -x -e
mount -t tmpfs xxx /mnt
mkdir -p /mnt/1
mkdir -p /mnt/2
mount --bind /mnt/1 /mnt/1
mount --make-shared /mnt/1
mount --bind /mnt/1 /mnt/2
mkdir -p /mnt/1/1
for i in seq 2; do

	mount --bind /mnt/1/1 /mnt/1/1
done
for i in `seq 2`; do
	umount /mnt/1/1 || {
		cat /proc/self/mountinfo | grep xxx
		exit 1
	}

done

[root@fc24 ~]# unshare -Urm ./m.sh
+ mount -t tmpfs xxx /mnt
+ mkdir -p /mnt/1
+ mkdir -p /mnt/2
+ mount --bind /mnt/1 /mnt/1
+ mount --make-shared /mnt/1
+ mount --bind /mnt/1 /mnt/2
+ mkdir -p /mnt/1/1
++ seq 2
+ for i in 'seq 2'
+ mount --bind /mnt/1/1 /mnt/1/1
+ for i in 'seq 2'
+ mount --bind /mnt/1/1 /mnt/1/1
++ seq 2
+ for i in 'seq 2'
+ umount /mnt/1/1
+ for i in 'seq 2'
+ umount /mnt/1/1
umount: /mnt/1/1: not mounted
+ cat /proc/self/mountinfo
+ grep xxx
147 116 0:42 / /mnt rw,relatime - tmpfs xxx rw
148 147 0:42 /1 /mnt/1 rw,relatime shared:65 - tmpfs xxx rw 149 147 0:42 /1 /mnt/2 rw,relatime shared:65 - tmpfs xxx rw 157 149 0:42 /1/1 /mnt/2/1 rw,relatime shared:65 - tmpfs xxx rw
+ exit 1

And you can see that /mnt/2 contains a mount, but it should not.

Thanks,
Andrei

Quoted 360 lines · Eric W. Biederman

Ever since mount propagation was introduced in cases where a mount in propagated to parent mount mountpoint pair that is already in use the code has placed the new mount behind the old mount in the mount hash table.

This implementation detail is problematic as it allows creating arbitrary length mount hash chains.

Furthermore it invalidates the constraint maintained elsewhere in the mount code that a parent mount and a mountpoint pair will have exactly one mount upon them. Making it hard to deal with and to talk about this special case in the mount code.

Modify mount propagation to notice when there is already a mount at the parent mount and mountpoint where a new mount is propagating to and place that preexisting mount on top of the new mount.

Modify unmount propagation to notice when a mount that is being unmounted has another mount on top of it (and no other children), and to replace the unmounted mount with the mount on top of it.

Move the MNT_UMUONT test from __lookup_mnt_last into __propagate_umount as that is the only call of __lookup_mnt_last where MNT_UMOUNT may be set on any mount visible in the mount hash table.

These modifications allow:
- __lookup_mnt_last to be removed.
- attach_shadows to be renamed __attach_mnt and the it's shadow handling to be removed.
- commit_tree to be simplified
- copy_tree to be simplified

The result is an easier to understand tree of mounts that does not allow creation of arbitrary length hash chains in the mount hash table.

v2: Updated to mnt_change_mountpoint to not call dput or mntput and instead to decrement the counts directly. It is guaranteed that there will be other references when mnt_change_mountpoint is called so this is safe.

Fixes b90fa9ae8f51 ("[PATCH] shared mount handling: bind and rbind")

---

Since the last version some of you may have seen I have modified my implementation of mnt_change_mountpoint so that it no longer calls mntput or dput but instead relies on the knowledge that it can not possibly have the last reference to the mnt and dentry of interest. This avoids code checking tools from complaining bitterly.

This is on top of my previous patch that sorts out locking of the mountpoint hash table. After time giving ample time for review I intend to push this and the previous bug fix to Linus.

 fs/mount.h     |   1 -
 fs/namespace.c | 110 +++++++++++++++++++++++++++++++--------------------------
 fs/pnode.c     |  27 ++++++++++----
 fs/pnode.h     |   2 ++
 4 files changed, 82 insertions(+), 58 deletions(-)
fs/mount.h +0 -1
index 2c856fc47ae3..2826543a131d 100644
@@ -89,7 +89,6 @@ static inline int is_mounted(struct vfsmount *mnt)
 }
 
 extern struct mount *__lookup_mnt(struct vfsmount *, struct dentry *);
-extern struct mount *__lookup_mnt_last(struct vfsmount *, struct dentry *);
 
 extern int __legitimize_mnt(struct vfsmount *, unsigned);
 extern bool legitimize_mnt(struct vfsmount *, unsigned);
fs/namespace.c +59 -51
index 487ba30bb5c6..91ccfb73f0e0 100644
@@ -637,28 +637,6 @@ struct mount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)
 }
 
 /*
- * find the last mount at @dentry on vfsmount @mnt.
- * mount_lock must be held.
- */
-struct mount *__lookup_mnt_last(struct vfsmount *mnt, struct dentry *dentry)
-{
-	struct mount *p, *res = NULL;
-	p = __lookup_mnt(mnt, dentry);
-	if (!p)
-		goto out;
-	if (!(p->mnt.mnt_flags & MNT_UMOUNT))
-		res = p;
-	hlist_for_each_entry_continue(p, mnt_hash) {
-		if (&p->mnt_parent->mnt != mnt || p->mnt_mountpoint != dentry)
-			break;
-		if (!(p->mnt.mnt_flags & MNT_UMOUNT))
-			res = p;
-	}
-out:
-	return res;
-}
-
-/*
  * lookup_mnt - Return the first child mount mounted at path
  *
  * "First" means first mounted chronologically.  If you create the
@@ -878,6 +856,13 @@ void mnt_set_mountpoint(struct mount *mnt,
 	hlist_add_head(&child_mnt->mnt_mp_list, &mp->m_list);
 }
 
+static void __attach_mnt(struct mount *mnt, struct mount *parent)
+{
+	hlist_add_head_rcu(&mnt->mnt_hash,
+			   m_hash(&parent->mnt, mnt->mnt_mountpoint));
+	list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
+}
+
 /*
  * vfsmount lock must be held for write
  */
@@ -886,28 +871,45 @@ static void attach_mnt(struct mount *mnt,
 			struct mountpoint *mp)
 {
 	mnt_set_mountpoint(parent, mp, mnt);
-	hlist_add_head_rcu(&mnt->mnt_hash, m_hash(&parent->mnt, mp->m_dentry));
-	list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
+	__attach_mnt(mnt, parent);
 }
 
-static void attach_shadowed(struct mount *mnt,
-			struct mount *parent,
-			struct mount *shadows)
+void mnt_change_mountpoint(struct mount *parent, struct mountpoint *mp, struct mount *mnt)
 {
-	if (shadows) {
-		hlist_add_behind_rcu(&mnt->mnt_hash, &shadows->mnt_hash);
-		list_add(&mnt->mnt_child, &shadows->mnt_child);
-	} else {
-		hlist_add_head_rcu(&mnt->mnt_hash,
-				m_hash(&parent->mnt, mnt->mnt_mountpoint));
-		list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
-	}
+	struct mountpoint *old_mp = mnt->mnt_mp;
+	struct dentry *old_mountpoint = mnt->mnt_mountpoint;
+	struct mount *old_parent = mnt->mnt_parent;
+
+	list_del_init(&mnt->mnt_child);
+	hlist_del_init(&mnt->mnt_mp_list);
+	hlist_del_init_rcu(&mnt->mnt_hash);
+
+	attach_mnt(mnt, parent, mp);
+
+	put_mountpoint(old_mp);
+
+	/*
+	 * Safely avoid even the suggestion this code might sleep or
+	 * lock the mount hash by taking avantage of the knowlege that
+	 * mnt_change_mounpoint will not release the final reference
+	 * to a mountpoint.
+	 *
+	 * During mounting, another mount will continue to use the old
+	 * mountpoint and during unmounting, the old mountpoint will
+	 * continue to exist until namespace_unlock which happens well
+	 * after mnt_change_mountpoint.
+	 */
+	spin_lock(&old_mountpoint->d_lock);
+	old_mountpoint->d_lockref.count--;
+	spin_unlock(&old_mountpoint->d_lock);
+
+	mnt_add_count(old_parent, -1);
 }
 
 /*
  * vfsmount lock must be held for write
  */
-static void commit_tree(struct mount *mnt, struct mount *shadows)
+static void commit_tree(struct mount *mnt)
 {
 	struct mount *parent = mnt->mnt_parent;
 	struct mount *m;
@@ -925,7 +927,7 @@ static void commit_tree(struct mount *mnt, struct mount *shadows)
 	n->mounts += n->pending_mounts;
 	n->pending_mounts = 0;
 
-	attach_shadowed(mnt, parent, shadows);
+	__attach_mnt(mnt, parent);
 	touch_mnt_namespace(n);
 }
 
@@ -1764,7 +1766,6 @@ struct mount *copy_tree(struct mount *mnt, struct dentry *dentry,
 			continue;
 
 		for (s = r; s; s = next_mnt(s, r)) {
-			struct mount *t = NULL;
 			if (!(flag & CL_COPY_UNBINDABLE) &&
 			    IS_MNT_UNBINDABLE(s)) {
 				s = skip_mnt_tree(s);
@@ -1786,14 +1787,7 @@ struct mount *copy_tree(struct mount *mnt, struct dentry *dentry,
 				goto out;
 			lock_mount_hash();
 			list_add_tail(&q->mnt_list, &res->mnt_list);
-			mnt_set_mountpoint(parent, p->mnt_mp, q);
-			if (!list_empty(&parent->mnt_mounts)) {
-				t = list_last_entry(&parent->mnt_mounts,
-					struct mount, mnt_child);
-				if (t->mnt_mp != p->mnt_mp)
-					t = NULL;
-			}
-			attach_shadowed(q, parent, t);
+			attach_mnt(q, parent, p->mnt_mp);
 			unlock_mount_hash();
 		}
 	}
@@ -1992,10 +1986,18 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 {
 	HLIST_HEAD(tree_list);
 	struct mnt_namespace *ns = dest_mnt->mnt_ns;
+	struct mountpoint *smp;
 	struct mount *child, *p;
 	struct hlist_node *n;
 	int err;
 
+	/* Preallocate a mountpoint in case the new mounts need
+	 * to be tucked under other mounts.
+	 */
+	smp = get_mountpoint(source_mnt->mnt.mnt_root);
+	if (IS_ERR(smp))
+		return PTR_ERR(smp);
+
 	/* Is there space to add these mounts to the mount namespace? */
 	if (!parent_path) {
 		err = count_mounts(ns, source_mnt);
@@ -2022,17 +2024,22 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 		touch_mnt_namespace(source_mnt->mnt_ns);
 	} else {
 		mnt_set_mountpoint(dest_mnt, dest_mp, source_mnt);
-		commit_tree(source_mnt, NULL);
+		commit_tree(source_mnt);
 	}
 
 	hlist_for_each_entry_safe(child, n, &tree_list, mnt_hash) {
-		struct mount *q;
 		hlist_del_init(&child->mnt_hash);
-		q = __lookup_mnt_last(&child->mnt_parent->mnt,
-				      child->mnt_mountpoint);
-		commit_tree(child, q);
+		if (child->mnt.mnt_root == smp->m_dentry) {
+			struct mount *q;
+			q = __lookup_mnt(&child->mnt_parent->mnt,
+					 child->mnt_mountpoint);
+			if (q)
+				mnt_change_mountpoint(child, smp, q);
+		}
+		commit_tree(child);
 	}
 	unlock_mount_hash();
+	put_mountpoint(smp);
 
 	return 0;
 
@@ -2046,6 +2053,7 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 	cleanup_group_ids(source_mnt, NULL);
  out:
 	ns->pending_mounts = 0;
+	put_mountpoint(smp);
 	return err;
 }
 
fs/pnode.c +21 -6
index 06a793f4ae38..eb4331240fd1 100644
@@ -327,6 +327,9 @@ int propagate_mnt(struct mount *dest_mnt, struct mountpoint *dest_mp,
  */
 static inline int do_refcount_check(struct mount *mnt, int count)
 {
+	struct mount *topper = __lookup_mnt(&mnt->mnt, mnt->mnt.mnt_root);
+	if (topper)
+		count++;
 	return mnt_get_count(mnt) > count;
 }
 
@@ -359,7 +362,7 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 
 	for (m = propagation_next(parent, parent); m;
 	     		m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
 		if (child && list_empty(&child->mnt_mounts) &&
 		    (ret = do_refcount_check(child, 1)))
 			break;
@@ -381,7 +384,7 @@ void propagate_mount_unlock(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
 		if (child)
 			child->mnt.mnt_flags &= ~MNT_LOCKED;
 	}
@@ -399,9 +402,11 @@ static void mark_umount_candidates(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-		struct mount *child = __lookup_mnt_last(&m->mnt,
+		struct mount *child = __lookup_mnt(&m->mnt,
 						mnt->mnt_mountpoint);
-		if (child && (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m))) {
+		if (!child || (child->mnt.mnt_flags & MNT_UMOUNT))
+			continue;
+		if (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m)) {
 			SET_MNT_MARK(child);
 		}
 	}
@@ -420,8 +425,8 @@ static void __propagate_umount(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-
-		struct mount *child = __lookup_mnt_last(&m->mnt,
+		struct mount *topper;
+		struct mount *child = __lookup_mnt(&m->mnt,
 						mnt->mnt_mountpoint);
 		/*
 		 * umount the child only if the child has no children
@@ -430,6 +435,16 @@ static void __propagate_umount(struct mount *mnt)
 		if (!child || !IS_MNT_MARKED(child))
 			continue;
 		CLEAR_MNT_MARK(child);
+
+		/* If there is exactly one mount covering all of child
+		 * replace child with that mount.
+		 */
+		topper = __lookup_mnt(&child->mnt, child->mnt.mnt_root);
+		if (topper &&
+		    (child->mnt_mounts.next == &topper->mnt_child) &&
+		    (topper->mnt_child.next == &child->mnt_mounts))
+			mnt_change_mountpoint(child->mnt_parent, child->mnt_mp, topper);
+
 		if (list_empty(&child->mnt_mounts)) {
 			list_del_init(&child->mnt_child);
 			child->mnt.mnt_flags |= MNT_UMOUNT;
fs/pnode.h +2 -0
index 550f5a8b4fcf..dc87e65becd2 100644
@@ -49,6 +49,8 @@ int get_dominating_id(struct mount *mnt, const struct path *root);
 unsigned int mnt_get_count(struct mount *mnt);
 void mnt_set_mountpoint(struct mount *, struct mountpoint *,
 			struct mount *);
+void mnt_change_mountpoint(struct mount *parent, struct mountpoint *mp,
+			   struct mount *mnt);
 struct mount *copy_tree(struct mount *, struct dentry *, int);
 bool is_path_reachable(struct mount *, struct dentry *,
 			 const struct path *root);
Signature · 1 line
2.10.1
#41 lore raw

Re: [REVIEW][PATCH] mnt: Tuck mounts under others instead of creating shadow/side mounts.

Quoted 10 lines · Eric W. Biederman
Quoted 8 lines · [email protected] (Eric W. Biederman) writes:
Quoted 5 lines · Andrei Vagin

Hi Eric,

I found one issue about this patch. Take a look at the next script. The idea is simple, we call mount twice and than we call umount twice, but the second umount fails.

Quoted 7 lines
Quoted 1 line

Definitely an odd. I will take a look.

After a little more looking I have to say the only thing I can see wrong in the behavior is that the first umount doesn't unmount everything.

Changing the mount propagation tree while it is being traversed apparently prevents a complete traversal of the propgation tree.

Is it be enough to find topper which will not be umounted? I attached a patch which does this. I can't find a reason why this will not work.

The idea of the patch is that we try to find a topper, check whether it is marked, and if it is marked, then we try to find the next one. All marked toppers are umounted and the first unmarked topper is attached to the parent mount.

And I think we need to add tests in tools/testing/selftests/...

Quoted 418 lines

Last time I was looking at this I was playing with multipass algorithm because of these peculiarities that happen with propagation trees.

I suspect we are going to need to fix umount to behave correct before we tune it for performance. So that we actually know what correct behavior is.

Eric

Quoted 407 lines
Quoted 407 lines

[root@fc24 ~]# cat m.sh
#!/bin/sh

set -x -e
mount -t tmpfs xxx /mnt
mkdir -p /mnt/1
mkdir -p /mnt/2
mount --bind /mnt/1 /mnt/1
mount --make-shared /mnt/1
mount --bind /mnt/1 /mnt/2
mkdir -p /mnt/1/1
for i in seq 2; do

	mount --bind /mnt/1/1 /mnt/1/1
done
for i in `seq 2`; do
	umount /mnt/1/1 || {
		cat /proc/self/mountinfo | grep xxx
		exit 1
	}

done

[root@fc24 ~]# unshare -Urm ./m.sh
+ mount -t tmpfs xxx /mnt
+ mkdir -p /mnt/1
+ mkdir -p /mnt/2
+ mount --bind /mnt/1 /mnt/1
+ mount --make-shared /mnt/1
+ mount --bind /mnt/1 /mnt/2
+ mkdir -p /mnt/1/1
++ seq 2
+ for i in 'seq 2'
+ mount --bind /mnt/1/1 /mnt/1/1
+ for i in 'seq 2'
+ mount --bind /mnt/1/1 /mnt/1/1
++ seq 2
+ for i in 'seq 2'
+ umount /mnt/1/1
+ for i in 'seq 2'
+ umount /mnt/1/1
umount: /mnt/1/1: not mounted
+ cat /proc/self/mountinfo
+ grep xxx
147 116 0:42 / /mnt rw,relatime - tmpfs xxx rw
148 147 0:42 /1 /mnt/1 rw,relatime shared:65 - tmpfs xxx rw 149 147 0:42 /1 /mnt/2 rw,relatime shared:65 - tmpfs xxx rw 157 149 0:42 /1/1 /mnt/2/1 rw,relatime shared:65 - tmpfs xxx rw
+ exit 1

And you can see that /mnt/2 contains a mount, but it should not.

Thanks,
Andrei

Quoted 352 lines · Eric W. Biederman

Ever since mount propagation was introduced in cases where a mount in propagated to parent mount mountpoint pair that is already in use the code has placed the new mount behind the old mount in the mount hash table.

This implementation detail is problematic as it allows creating arbitrary length mount hash chains.

Furthermore it invalidates the constraint maintained elsewhere in the mount code that a parent mount and a mountpoint pair will have exactly one mount upon them. Making it hard to deal with and to talk about this special case in the mount code.

Modify mount propagation to notice when there is already a mount at the parent mount and mountpoint where a new mount is propagating to and place that preexisting mount on top of the new mount.

Modify unmount propagation to notice when a mount that is being unmounted has another mount on top of it (and no other children), and to replace the unmounted mount with the mount on top of it.

Move the MNT_UMUONT test from __lookup_mnt_last into __propagate_umount as that is the only call of __lookup_mnt_last where MNT_UMOUNT may be set on any mount visible in the mount hash table.

These modifications allow:
- __lookup_mnt_last to be removed.
- attach_shadows to be renamed __attach_mnt and the it's shadow handling to be removed.
- commit_tree to be simplified
- copy_tree to be simplified

The result is an easier to understand tree of mounts that does not allow creation of arbitrary length hash chains in the mount hash table.

v2: Updated to mnt_change_mountpoint to not call dput or mntput and instead to decrement the counts directly. It is guaranteed that there will be other references when mnt_change_mountpoint is called so this is safe.

Fixes b90fa9ae8f51 ("[PATCH] shared mount handling: bind and rbind")

---

Since the last version some of you may have seen I have modified my implementation of mnt_change_mountpoint so that it no longer calls mntput or dput but instead relies on the knowledge that it can not possibly have the last reference to the mnt and dentry of interest. This avoids code checking tools from complaining bitterly.

This is on top of my previous patch that sorts out locking of the mountpoint hash table. After time giving ample time for review I intend to push this and the previous bug fix to Linus.

 fs/mount.h     |   1 -
 fs/namespace.c | 110 +++++++++++++++++++++++++++++++--------------------------
 fs/pnode.c     |  27 ++++++++++----
 fs/pnode.h     |   2 ++
 4 files changed, 82 insertions(+), 58 deletions(-)
fs/mount.h +0 -1
index 2c856fc47ae3..2826543a131d 100644
@@ -89,7 +89,6 @@ static inline int is_mounted(struct vfsmount *mnt)
 }
 
 extern struct mount *__lookup_mnt(struct vfsmount *, struct dentry *);
-extern struct mount *__lookup_mnt_last(struct vfsmount *, struct dentry *);
 
 extern int __legitimize_mnt(struct vfsmount *, unsigned);
 extern bool legitimize_mnt(struct vfsmount *, unsigned);
fs/namespace.c +59 -51
index 487ba30bb5c6..91ccfb73f0e0 100644
@@ -637,28 +637,6 @@ struct mount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)
 }
 
 /*
- * find the last mount at @dentry on vfsmount @mnt.
- * mount_lock must be held.
- */
-struct mount *__lookup_mnt_last(struct vfsmount *mnt, struct dentry *dentry)
-{
-	struct mount *p, *res = NULL;
-	p = __lookup_mnt(mnt, dentry);
-	if (!p)
-		goto out;
-	if (!(p->mnt.mnt_flags & MNT_UMOUNT))
-		res = p;
-	hlist_for_each_entry_continue(p, mnt_hash) {
-		if (&p->mnt_parent->mnt != mnt || p->mnt_mountpoint != dentry)
-			break;
-		if (!(p->mnt.mnt_flags & MNT_UMOUNT))
-			res = p;
-	}
-out:
-	return res;
-}
-
-/*
  * lookup_mnt - Return the first child mount mounted at path
  *
  * "First" means first mounted chronologically.  If you create the
@@ -878,6 +856,13 @@ void mnt_set_mountpoint(struct mount *mnt,
 	hlist_add_head(&child_mnt->mnt_mp_list, &mp->m_list);
 }
 
+static void __attach_mnt(struct mount *mnt, struct mount *parent)
+{
+	hlist_add_head_rcu(&mnt->mnt_hash,
+			   m_hash(&parent->mnt, mnt->mnt_mountpoint));
+	list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
+}
+
 /*
  * vfsmount lock must be held for write
  */
@@ -886,28 +871,45 @@ static void attach_mnt(struct mount *mnt,
 			struct mountpoint *mp)
 {
 	mnt_set_mountpoint(parent, mp, mnt);
-	hlist_add_head_rcu(&mnt->mnt_hash, m_hash(&parent->mnt, mp->m_dentry));
-	list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
+	__attach_mnt(mnt, parent);
 }
 
-static void attach_shadowed(struct mount *mnt,
-			struct mount *parent,
-			struct mount *shadows)
+void mnt_change_mountpoint(struct mount *parent, struct mountpoint *mp, struct mount *mnt)
 {
-	if (shadows) {
-		hlist_add_behind_rcu(&mnt->mnt_hash, &shadows->mnt_hash);
-		list_add(&mnt->mnt_child, &shadows->mnt_child);
-	} else {
-		hlist_add_head_rcu(&mnt->mnt_hash,
-				m_hash(&parent->mnt, mnt->mnt_mountpoint));
-		list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
-	}
+	struct mountpoint *old_mp = mnt->mnt_mp;
+	struct dentry *old_mountpoint = mnt->mnt_mountpoint;
+	struct mount *old_parent = mnt->mnt_parent;
+
+	list_del_init(&mnt->mnt_child);
+	hlist_del_init(&mnt->mnt_mp_list);
+	hlist_del_init_rcu(&mnt->mnt_hash);
+
+	attach_mnt(mnt, parent, mp);
+
+	put_mountpoint(old_mp);
+
+	/*
+	 * Safely avoid even the suggestion this code might sleep or
+	 * lock the mount hash by taking avantage of the knowlege that
+	 * mnt_change_mounpoint will not release the final reference
+	 * to a mountpoint.
+	 *
+	 * During mounting, another mount will continue to use the old
+	 * mountpoint and during unmounting, the old mountpoint will
+	 * continue to exist until namespace_unlock which happens well
+	 * after mnt_change_mountpoint.
+	 */
+	spin_lock(&old_mountpoint->d_lock);
+	old_mountpoint->d_lockref.count--;
+	spin_unlock(&old_mountpoint->d_lock);
+
+	mnt_add_count(old_parent, -1);
 }
 
 /*
  * vfsmount lock must be held for write
  */
-static void commit_tree(struct mount *mnt, struct mount *shadows)
+static void commit_tree(struct mount *mnt)
 {
 	struct mount *parent = mnt->mnt_parent;
 	struct mount *m;
@@ -925,7 +927,7 @@ static void commit_tree(struct mount *mnt, struct mount *shadows)
 	n->mounts += n->pending_mounts;
 	n->pending_mounts = 0;
 
-	attach_shadowed(mnt, parent, shadows);
+	__attach_mnt(mnt, parent);
 	touch_mnt_namespace(n);
 }
 
@@ -1764,7 +1766,6 @@ struct mount *copy_tree(struct mount *mnt, struct dentry *dentry,
 			continue;
 
 		for (s = r; s; s = next_mnt(s, r)) {
-			struct mount *t = NULL;
 			if (!(flag & CL_COPY_UNBINDABLE) &&
 			    IS_MNT_UNBINDABLE(s)) {
 				s = skip_mnt_tree(s);
@@ -1786,14 +1787,7 @@ struct mount *copy_tree(struct mount *mnt, struct dentry *dentry,
 				goto out;
 			lock_mount_hash();
 			list_add_tail(&q->mnt_list, &res->mnt_list);
-			mnt_set_mountpoint(parent, p->mnt_mp, q);
-			if (!list_empty(&parent->mnt_mounts)) {
-				t = list_last_entry(&parent->mnt_mounts,
-					struct mount, mnt_child);
-				if (t->mnt_mp != p->mnt_mp)
-					t = NULL;
-			}
-			attach_shadowed(q, parent, t);
+			attach_mnt(q, parent, p->mnt_mp);
 			unlock_mount_hash();
 		}
 	}
@@ -1992,10 +1986,18 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 {
 	HLIST_HEAD(tree_list);
 	struct mnt_namespace *ns = dest_mnt->mnt_ns;
+	struct mountpoint *smp;
 	struct mount *child, *p;
 	struct hlist_node *n;
 	int err;
 
+	/* Preallocate a mountpoint in case the new mounts need
+	 * to be tucked under other mounts.
+	 */
+	smp = get_mountpoint(source_mnt->mnt.mnt_root);
+	if (IS_ERR(smp))
+		return PTR_ERR(smp);
+
 	/* Is there space to add these mounts to the mount namespace? */
 	if (!parent_path) {
 		err = count_mounts(ns, source_mnt);
@@ -2022,17 +2024,22 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 		touch_mnt_namespace(source_mnt->mnt_ns);
 	} else {
 		mnt_set_mountpoint(dest_mnt, dest_mp, source_mnt);
-		commit_tree(source_mnt, NULL);
+		commit_tree(source_mnt);
 	}
 
 	hlist_for_each_entry_safe(child, n, &tree_list, mnt_hash) {
-		struct mount *q;
 		hlist_del_init(&child->mnt_hash);
-		q = __lookup_mnt_last(&child->mnt_parent->mnt,
-				      child->mnt_mountpoint);
-		commit_tree(child, q);
+		if (child->mnt.mnt_root == smp->m_dentry) {
+			struct mount *q;
+			q = __lookup_mnt(&child->mnt_parent->mnt,
+					 child->mnt_mountpoint);
+			if (q)
+				mnt_change_mountpoint(child, smp, q);
+		}
+		commit_tree(child);
 	}
 	unlock_mount_hash();
+	put_mountpoint(smp);
 
 	return 0;
 
@@ -2046,6 +2053,7 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 	cleanup_group_ids(source_mnt, NULL);
  out:
 	ns->pending_mounts = 0;
+	put_mountpoint(smp);
 	return err;
 }
 
fs/pnode.c +21 -6
index 06a793f4ae38..eb4331240fd1 100644
@@ -327,6 +327,9 @@ int propagate_mnt(struct mount *dest_mnt, struct mountpoint *dest_mp,
  */
 static inline int do_refcount_check(struct mount *mnt, int count)
 {
+	struct mount *topper = __lookup_mnt(&mnt->mnt, mnt->mnt.mnt_root);
+	if (topper)
+		count++;
 	return mnt_get_count(mnt) > count;
 }
 
@@ -359,7 +362,7 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 
 	for (m = propagation_next(parent, parent); m;
 	     		m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
 		if (child && list_empty(&child->mnt_mounts) &&
 		    (ret = do_refcount_check(child, 1)))
 			break;
@@ -381,7 +384,7 @@ void propagate_mount_unlock(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-		child = __lookup_mnt_last(&m->mnt, mnt->mnt_mountpoint);
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
 		if (child)
 			child->mnt.mnt_flags &= ~MNT_LOCKED;
 	}
@@ -399,9 +402,11 @@ static void mark_umount_candidates(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-		struct mount *child = __lookup_mnt_last(&m->mnt,
+		struct mount *child = __lookup_mnt(&m->mnt,
 						mnt->mnt_mountpoint);
-		if (child && (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m))) {
+		if (!child || (child->mnt.mnt_flags & MNT_UMOUNT))
+			continue;
+		if (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m)) {
 			SET_MNT_MARK(child);
 		}
 	}
@@ -420,8 +425,8 @@ static void __propagate_umount(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-
-		struct mount *child = __lookup_mnt_last(&m->mnt,
+		struct mount *topper;
+		struct mount *child = __lookup_mnt(&m->mnt,
 						mnt->mnt_mountpoint);
 		/*
 		 * umount the child only if the child has no children
@@ -430,6 +435,16 @@ static void __propagate_umount(struct mount *mnt)
 		if (!child || !IS_MNT_MARKED(child))
 			continue;
 		CLEAR_MNT_MARK(child);
+
+		/* If there is exactly one mount covering all of child
+		 * replace child with that mount.
+		 */
+		topper = __lookup_mnt(&child->mnt, child->mnt.mnt_root);
+		if (topper &&
+		    (child->mnt_mounts.next == &topper->mnt_child) &&
+		    (topper->mnt_child.next == &child->mnt_mounts))
+			mnt_change_mountpoint(child->mnt_parent, child->mnt_mp, topper);
+
 		if (list_empty(&child->mnt_mounts)) {
 			list_del_init(&child->mnt_child);
 			child->mnt.mnt_flags |= MNT_UMOUNT;
fs/pnode.h +0 -0
index 550f5a8b4fcf..dc87e65becd2 100644
@@ -49,6 +49,8 @@ int get_dominating_id(struct mount *mnt, const struct path *root);
 unsigned int mnt_get_count(struct mount *mnt);
 void mnt_set_mountpoint(struct mount *, struct mountpoint *,
 			struct mount *);
Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
fs/pnode.h +2 -0
+void mnt_change_mountpoint(struct mount *parent, struct mountpoint *mp,
+			   struct mount *mnt);
 struct mount *copy_tree(struct mount *, struct dentry *, int);
 bool is_path_reachable(struct mount *, struct dentry *,
 			 const struct path *root);
Signature · 1 line
2.10.1
fs/pnode.c +34 -11
index 5bc7896..7aefd06 100644
@@ -435,6 +435,17 @@ static void mark_umount_candidates(struct mount *mnt)
 	}
 }
 
+static void __marked_umount(struct mount *mnt, struct mount *child)
+{
+	CLEAR_MNT_MARK(child);
+
+	if (list_empty(&child->mnt_mounts)) {
+		list_del_init(&child->mnt_child);
+		child->mnt.mnt_flags |= MNT_UMOUNT;
+		list_move_tail(&child->mnt_list, &mnt->mnt_list);
+	}
+}
+
 /*
  * NOTE: unmounting 'mnt' naturally propagates to all other mounts its
  * parent propagates to.
@@ -448,8 +459,13 @@ static void __propagate_umount(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-		struct mount *topper;
-		struct mount *child = __lookup_mnt(&m->mnt,
+		struct mount *topper, *topper_to_umount;
+		struct mount *child;
+
+		if (m->mnt.mnt_flags & MNT_UMOUNT)
+			continue;
+
+		child = __lookup_mnt(&m->mnt,
 						mnt->mnt_mountpoint);
 		/*
 		 * umount the child only if the child has no children
@@ -457,21 +473,28 @@ static void __propagate_umount(struct mount *mnt)
 		 */
 		if (!child || !IS_MNT_MARKED(child))
 			continue;
-		CLEAR_MNT_MARK(child);
 
-		/* If there is exactly one mount covering all of child
+		/* If there is exactly one mount covering all of childV
 		 * replace child with that mount.
 		 */
-		topper = find_topper(child);
+		topper = topper_to_umount = child;
+		while (1) {
+			topper = find_topper(topper);
+			if (topper == NULL)
+				break;
+			if (!IS_MNT_MARKED(topper))
+				break;
+			topper_to_umount = topper;
+		}
 		if (topper)
-			mnt_change_mountpoint(child->mnt_parent, child->mnt_mp,
-					      topper);
+			mnt_change_mountpoint(child->mnt_parent, child->mnt_mp, topper);
 
-		if (list_empty(&child->mnt_mounts)) {
-			list_del_init(&child->mnt_child);
-			child->mnt.mnt_flags |= MNT_UMOUNT;
-			list_move_tail(&child->mnt_list, &mnt->mnt_list);
+		while (topper_to_umount != child) {
+			__marked_umount(mnt, topper_to_umount);
+			topper_to_umount = topper_to_umount->mnt_parent;
 		}
+
+		__marked_umount(mnt, child);
 	}
 }
 
#42 lore raw

Re: [REVIEW][PATCH] mnt: Tuck mounts under others instead of creating shadow/side mounts.

Quoted 19 lines · Andrei Vagin
Quoted 10 lines · Eric W. Biederman
Quoted 8 lines · [email protected] (Eric W. Biederman) writes:
Quoted 5 lines · Andrei Vagin

Hi Eric,

I found one issue about this patch. Take a look at the next script. The idea is simple, we call mount twice and than we call umount twice, but the second umount fails.

Quoted 7 lines
Quoted 1 line

Definitely an odd. I will take a look.

After a little more looking I have to say the only thing I can see wrong in the behavior is that the first umount doesn't unmount everything.

Changing the mount propagation tree while it is being traversed apparently prevents a complete traversal of the propgation tree.

Quoted 7 lines

Is it be enough to find topper which will not be umounted? I attached a patch which does this. I can't find a reason why this will not work.

The idea of the patch is that we try to find a topper, check whether it is marked, and if it is marked, then we try to find the next one. All marked toppers are umounted and the first unmarked topper is attached to the parent mount.

That isn't completely wrong. But I don't believe the it is the proper logic.

Fundamentally the issue is that we are reparenting while remounting. This results in mounts that should be unmounted moving before they are unmounted.

Which means that if we leave all of the mnt_change_mountpoint work for a final pass over the mounts everything works correctly.

Which is fabulous news.

That means multiple passes through a mount propagation tree for a given mountpoint can no longer change what winds up unmounted. Which means we can skip subtrees that have already seen mount propagation. Which means the optimizations I was playing with earlier fundamentally will work without changing the semantics of MNT_DETACH.

Quoted 1 line

And I think we need to add tests in tools/testing/selftests/...

Feel free.

Patch to sort this immediate issue out in a moment...

Eric

#43 lore raw

PATCH 1/1 mnt: In umount propagation reparent in a separate pass

It was observed that in some pathlogical cases that the current code does not unmount everything it should. After investigation it was determined that the issue is that mnt_change_mntpoint can can change which mounts are available to be unmounted during mount propagation which is wrong.

The trivial reproducer is:

$ cat ./pathological.sh

mount -t tmpfs test-base /mnt
cd /mnt
mkdir 1 2 1/1
mount --bind 1 1
mount --make-shared 1
mount --bind 1 2
mount --bind 1/1 1/1
mount --bind 1/1 1/1
echo
grep test-base /proc/self/mountinfo
umount 1/1
echo
grep test-base /proc/self/mountinfo

$ unshare -Urm ./pathological.sh

The expected output looks like:
46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 49 54 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 50 53 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 51 49 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 54 47 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 53 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 52 50 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

The output without the fix looks like:
46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 49 54 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 50 53 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 51 49 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 54 47 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 53 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 52 50 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 52 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

That last mount in the output was in the propgation tree to be unmounted but was missed because the mnt_change_mountpoint changed it's parent before the walk through the mount propagation tree observed it.

Fixes 1064f874abc0 ("mnt: Tuck mounts under others instead of creating shadow/side mounts.")
---
 fs/mount.h     |  1 +
 fs/namespace.c |  1 +
 fs/pnode.c     | 35 ++++++++++++++++++++++++++++++-----
 3 files changed, 32 insertions(+), 5 deletions(-)
fs/mount.h +1 -0
index bf1fda6eed8f..ede5a1d5cf99 100644
@@ -58,6 +58,7 @@ struct mount {
 	struct mnt_namespace *mnt_ns;	/* containing namespace */
 	struct mountpoint *mnt_mp;	/* where is it mounted */
 	struct hlist_node mnt_mp_list;	/* list mounts with the same mountpoint */
+	struct list_head mnt_reparent;	/* reparent list entry */
 #ifdef CONFIG_FSNOTIFY
 	struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks;
 	__u32 mnt_fsnotify_mask;
fs/namespace.c +1 -0
index 8bd3e4d448b9..51e49866e1fe 100644
@@ -236,6 +236,7 @@ static struct mount *alloc_vfsmnt(const char *name)
 		INIT_LIST_HEAD(&mnt->mnt_slave_list);
 		INIT_LIST_HEAD(&mnt->mnt_slave);
 		INIT_HLIST_NODE(&mnt->mnt_mp_list);
+		INIT_LIST_HEAD(&mnt->mnt_reparent);
 		init_fs_pin(&mnt->mnt_umount, drop_mountpoint);
 	}
 	return mnt;
fs/pnode.c +30 -5
index 5bc7896d122a..52aca0a118ff 100644
@@ -439,7 +439,7 @@ static void mark_umount_candidates(struct mount *mnt)
  * NOTE: unmounting 'mnt' naturally propagates to all other mounts its
  * parent propagates to.
  */
-static void __propagate_umount(struct mount *mnt)
+static void __propagate_umount(struct mount *mnt, struct list_head *to_reparent)
 {
 	struct mount *parent = mnt->mnt_parent;
 	struct mount *m;
@@ -464,17 +464,38 @@ static void __propagate_umount(struct mount *mnt)
 		 */
 		topper = find_topper(child);
 		if (topper)
-			mnt_change_mountpoint(child->mnt_parent, child->mnt_mp,
-					      topper);
+			list_add_tail(&topper->mnt_reparent, to_reparent);
 
-		if (list_empty(&child->mnt_mounts)) {
+		if (topper || list_empty(&child->mnt_mounts)) {
 			list_del_init(&child->mnt_child);
+			list_del_init(&child->mnt_reparent);
 			child->mnt.mnt_flags |= MNT_UMOUNT;
 			list_move_tail(&child->mnt_list, &mnt->mnt_list);
 		}
 	}
 }
 
+static void reparent_mounts(struct list_head *to_reparent)
+{
+	while (!list_empty(to_reparent)) {
+		struct mount *mnt, *parent;
+		struct mountpoint *mp;
+
+		mnt = list_first_entry(to_reparent, struct mount, mnt_reparent);
+		list_del_init(&mnt->mnt_reparent);
+
+		/* Where should this mount be reparented to? */
+		mp = mnt->mnt_mp;
+		parent = mnt->mnt_parent;
+		while (parent->mnt.mnt_flags & MNT_UMOUNT) {
+			mp = parent->mnt_mp;
+			parent = parent->mnt_parent;
+		}
+
+		mnt_change_mountpoint(parent, mp, mnt);
+	}
+}
+
 /*
  * collect all mounts that receive propagation from the mount in @list,
  * and return these additional mounts in the same list.
@@ -485,11 +506,15 @@ static void __propagate_umount(struct mount *mnt)
 int propagate_umount(struct list_head *list)
 {
 	struct mount *mnt;
+	LIST_HEAD(to_reparent);
 
 	list_for_each_entry_reverse(mnt, list, mnt_list)
 		mark_umount_candidates(mnt);
 
 	list_for_each_entry(mnt, list, mnt_list)
-		__propagate_umount(mnt);
+		__propagate_umount(mnt, &to_reparent);
+
+	reparent_mounts(&to_reparent);
+
 	return 0;
 }
Signature · 1 line
2.10.1
Acked-by on v1 1/1
#44 lore raw

Re: [REVIEW][PATCH] mnt: In umount propagation reparent in a separate pass

Quoted 52 lines · Eric W. Biederman

It was observed that in some pathlogical cases that the current code does not unmount everything it should. After investigation it was determined that the issue is that mnt_change_mntpoint can can change which mounts are available to be unmounted during mount propagation which is wrong.

The trivial reproducer is:

$ cat ./pathological.sh

mount -t tmpfs test-base /mnt
cd /mnt
mkdir 1 2 1/1
mount --bind 1 1
mount --make-shared 1
mount --bind 1 2
mount --bind 1/1 1/1
mount --bind 1/1 1/1
echo
grep test-base /proc/self/mountinfo
umount 1/1
echo
grep test-base /proc/self/mountinfo

$ unshare -Urm ./pathological.sh

The expected output looks like:
46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 49 54 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 50 53 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 51 49 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 54 47 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 53 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 52 50 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

The output without the fix looks like:
46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 49 54 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 50 53 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 51 49 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 54 47 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 53 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 52 50 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

Quoted 9 lines

46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 52 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

That last mount in the output was in the propgation tree to be unmounted but was missed because the mnt_change_mountpoint changed it's parent before the walk through the mount propagation tree observed it.

It works for me and the patch looks correct. Thanks!

Quoted 108 lines
Fixes 1064f874abc0 ("mnt: Tuck mounts under others instead of creating shadow/side mounts.")
---
 fs/mount.h     |  1 +
 fs/namespace.c |  1 +
 fs/pnode.c     | 35 ++++++++++++++++++++++++++++++-----
 3 files changed, 32 insertions(+), 5 deletions(-)
fs/mount.h +1 -0
index bf1fda6eed8f..ede5a1d5cf99 100644
@@ -58,6 +58,7 @@ struct mount {
 	struct mnt_namespace *mnt_ns;	/* containing namespace */
 	struct mountpoint *mnt_mp;	/* where is it mounted */
 	struct hlist_node mnt_mp_list;	/* list mounts with the same mountpoint */
+	struct list_head mnt_reparent;	/* reparent list entry */
 #ifdef CONFIG_FSNOTIFY
 	struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks;
 	__u32 mnt_fsnotify_mask;
fs/namespace.c +1 -0
index 8bd3e4d448b9..51e49866e1fe 100644
@@ -236,6 +236,7 @@ static struct mount *alloc_vfsmnt(const char *name)
 		INIT_LIST_HEAD(&mnt->mnt_slave_list);
 		INIT_LIST_HEAD(&mnt->mnt_slave);
 		INIT_HLIST_NODE(&mnt->mnt_mp_list);
+		INIT_LIST_HEAD(&mnt->mnt_reparent);
 		init_fs_pin(&mnt->mnt_umount, drop_mountpoint);
 	}
 	return mnt;
fs/pnode.c +30 -5
index 5bc7896d122a..52aca0a118ff 100644
@@ -439,7 +439,7 @@ static void mark_umount_candidates(struct mount *mnt)
  * NOTE: unmounting 'mnt' naturally propagates to all other mounts its
  * parent propagates to.
  */
-static void __propagate_umount(struct mount *mnt)
+static void __propagate_umount(struct mount *mnt, struct list_head *to_reparent)
 {
 	struct mount *parent = mnt->mnt_parent;
 	struct mount *m;
@@ -464,17 +464,38 @@ static void __propagate_umount(struct mount *mnt)
 		 */
 		topper = find_topper(child);
 		if (topper)
-			mnt_change_mountpoint(child->mnt_parent, child->mnt_mp,
-					      topper);
+			list_add_tail(&topper->mnt_reparent, to_reparent);
 
-		if (list_empty(&child->mnt_mounts)) {
+		if (topper || list_empty(&child->mnt_mounts)) {
 			list_del_init(&child->mnt_child);
+			list_del_init(&child->mnt_reparent);
 			child->mnt.mnt_flags |= MNT_UMOUNT;
 			list_move_tail(&child->mnt_list, &mnt->mnt_list);
 		}
 	}
 }
 
+static void reparent_mounts(struct list_head *to_reparent)
+{
+	while (!list_empty(to_reparent)) {
+		struct mount *mnt, *parent;
+		struct mountpoint *mp;
+
+		mnt = list_first_entry(to_reparent, struct mount, mnt_reparent);
+		list_del_init(&mnt->mnt_reparent);
+
+		/* Where should this mount be reparented to? */
+		mp = mnt->mnt_mp;
+		parent = mnt->mnt_parent;
+		while (parent->mnt.mnt_flags & MNT_UMOUNT) {
+			mp = parent->mnt_mp;
+			parent = parent->mnt_parent;
+		}
+
+		mnt_change_mountpoint(parent, mp, mnt);
+	}
+}
+
 /*
  * collect all mounts that receive propagation from the mount in @list,
  * and return these additional mounts in the same list.
@@ -485,11 +506,15 @@ static void __propagate_umount(struct mount *mnt)
 int propagate_umount(struct list_head *list)
 {
 	struct mount *mnt;
+	LIST_HEAD(to_reparent);
 
 	list_for_each_entry_reverse(mnt, list, mnt_list)
 		mark_umount_candidates(mnt);
 
 	list_for_each_entry(mnt, list, mnt_list)
-		__propagate_umount(mnt);
+		__propagate_umount(mnt, &to_reparent);
+
+	reparent_mounts(&to_reparent);
+
 	return 0;
 }
Signature · 1 line
2.10.1
#45 lore raw

PATCH 1/1 test: check a case when a mount is propagated between exiting mounts

In reply to Eric W. Biederman

This test checks two behaviour cases:

When a mount is propagated to a place which is already busy, the new mount is inserted between parent and old mount.

When a mount that is being unmounted due to propagation has another mount on top of it, it is replaced by the top mount.

---
 tools/testing/selftests/mount/Makefile             | 19 +++--
 tools/testing/selftests/mount/test-reparent-mounts | 92 ++++++++++++++++++++++
 2 files changed, 105 insertions(+), 6 deletions(-)
 create mode 100755 tools/testing/selftests/mount/test-reparent-mounts
tools/testing/selftests/mount/Makefile +13 -6
index 9093d7f..5927230 100644
@@ -6,11 +6,18 @@ TEST_GEN_PROGS := unprivileged-remount-test
 
 include ../lib.mk
 
-override RUN_TESTS := if [ -f /proc/self/uid_map ] ; \
-		      then	\
-				./unprivileged-remount-test ; \
-		      else	\
-				echo "WARN: No /proc/self/uid_map exist, test skipped." ; \
-		      fi
+override define RUN_TESTS
+	if [ -f /proc/self/uid_map ] ; \
+	then	\
+		./unprivileged-remount-test ; \
+	else	\
+		echo "WARN: No /proc/self/uid_map exist, test skipped." ; \
+	fi
+	unshare -Urm ./test-reparent-mounts
+	unshare -Urm ./test-reparent-mounts -c
+	unshare -Urm ./test-reparent-mounts -s
+	unshare -Urm ./test-reparent-mounts -s -S
+endef
+
 override EMIT_TESTS := echo "$(RUN_TESTS)"
 
tools/testing/selftests/mount/test-reparent-mounts new file +92 -0

92 lines not on this page · raw message

Signature · 1 line
2.9.3
#46 lore raw

PATCH 1/2 mnt: In propgate_umount handle visiting mounts in any order

In reply to Eric W. Biederman

While investigating some poor umount performance I realized that in the case of overlapping mount trees where some of the mounts are locked the code has been failing to unmount all of the mounts it should have been unmounting.

This failure to unmount all of the necessary
mounts can be reproduced with:

$ cat locked_mounts_test.sh

mount -t tmpfs test-base /mnt
mount --make-shared /mnt
mkdir -p /mnt/b

mount -t tmpfs test1 /mnt/b
mount --make-shared /mnt/b
mkdir -p /mnt/b/10

mount -t tmpfs test2 /mnt/b/10
mount --make-shared /mnt/b/10
mkdir -p /mnt/b/10/20

mount --rbind /mnt/b /mnt/b/10/20

unshare -Urm --propagation unchaged /bin/sh -c 'sleep 5; if [ $(grep test /proc/self/mountinfo | wc -l) -eq 1 ] ; then echo SUCCESS ; else echo FAILURE ; fi' sleep 1
umount -l /mnt/b
wait %%

$ unshare -Urm ./locked_mounts_test.sh

This failure is corrected by removing the prepass that marks mounts that may be umounted.

A first pass is added that umounts mounts if possible and if not sets mount mark if they could be unmounted if they weren't locked and adds them to a list to umount possibilities. This first pass reconsiders the mounts parent if it is on the list of umount possibilities, ensuring that information of umoutability will pass from child to mount parent.

A second pass then walks through all mounts that are umounted and processes their children unmounting them or marking them for reparenting.

A last pass cleans up the state on the mounts that could not be umounted and if applicable reparents them to their first parent that remained mounted.

While a bit longer than the old code this code is much more robust as it allows information to flow up from the leaves and down from the trunk making the order in which mounts are encountered in the umount propgation tree irrelevant.

Fixes 0c56fe31420c ("mnt: Don't propagate unmounts to locked mounts")
---
 fs/mount.h     |   2 +-
 fs/namespace.c |   2 +-
 fs/pnode.c     | 144 ++++++++++++++++++++++++++++++++++-----------------------
 3 files changed, 88 insertions(+), 60 deletions(-)
fs/mount.h +1 -1
index ede5a1d5cf99..de45d9e76748 100644
@@ -58,7 +58,7 @@ struct mount {
 	struct mnt_namespace *mnt_ns;	/* containing namespace */
 	struct mountpoint *mnt_mp;	/* where is it mounted */
 	struct hlist_node mnt_mp_list;	/* list mounts with the same mountpoint */
-	struct list_head mnt_reparent;	/* reparent list entry */
+	struct list_head mnt_umounting; /* list entry for umount propagation */
 #ifdef CONFIG_FSNOTIFY
 	struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks;
 	__u32 mnt_fsnotify_mask;
fs/namespace.c +1 -1
index 51e49866e1fe..5e3dcbeb1de5 100644
@@ -236,7 +236,7 @@ static struct mount *alloc_vfsmnt(const char *name)
 		INIT_LIST_HEAD(&mnt->mnt_slave_list);
 		INIT_LIST_HEAD(&mnt->mnt_slave);
 		INIT_HLIST_NODE(&mnt->mnt_mp_list);
-		INIT_LIST_HEAD(&mnt->mnt_reparent);
+		INIT_LIST_HEAD(&mnt->mnt_umounting);
 		init_fs_pin(&mnt->mnt_umount, drop_mountpoint);
 	}
 	return mnt;
fs/pnode.c +86 -58

187 lines not on this page · raw message

Signature · 1 line
2.10.1
#47 lore raw

PATCH 2/2 mnt: Make propagate_umount less slow for overlapping mount propagation trees

Andrei Vagin pointed out that time to executue propagate_umount can go non-linear (and take a ludicrious amount of time) when the mount propogation trees of the mounts to be unmunted by a lazy unmount overlap.

Make the walk of the mount propagation trees nearly linear by remembering which mounts have already been visited, allowing subsequent walks to detect when walking a mount propgation tree or a subtree of a mount propgation tree would be duplicate work and to skip them entirely.

Walk the list of mounts whose propgatation trees need to be traversed from the mount highest in the mount tree to mounts lower in the mount tree so that odds are higher that the code will walk the largest trees first, allowing later tree walks to be skipped entirely.

Add cleanup_umount_visitation to remover the code's memory of which mounts have been visited.

Add the functions last_slave and skip_propagation_subtree to allow skipping appropriate parts of the mount propagation tree without needing to change the logic of the rest of the code.

A script to generate overlapping mount propagation trees:

$ cat runs.h
set -e
mount -t tmpfs zdtm /mnt
mkdir -p /mnt/1 /mnt/2
mount -t tmpfs zdtm /mnt/1
mount --make-shared /mnt/1
mkdir /mnt/1/1

iteration=10
if [ -n "$1" ] ; then
iteration=$1
fi

for i in $(seq $iteration); do

	mount --bind /mnt/1/1 /mnt/1/1

done

mount --rbind /mnt/1 /mnt/2

TIMEFORMAT='%Rs'
nr=$(( ( 2 ** ( $iteration + 1 ) ) + 1 ))
echo -n "umount -l /mnt/1 -> $nr "
time umount -l /mnt/1

nr=$(cat /proc/self/mountinfo | grep zdtm | wc -l ) time umount -l /mnt/2

$ for i in $(seq 9 19); do echo $i; unshare -Urm bash ./run.sh $i; done

Here are the performance numbers with and without the patch:

     mhash |  8192   |  8192  | 1048576 | 1048576
    mounts | before  | after  |  before | after
    ------------------------------------------------
      1025 |  0.040s | 0.016s |  0.038s | 0.019s
      2049 |  0.094s | 0.017s |  0.080s | 0.018s
      4097 |  0.243s | 0.019s |  0.206s | 0.023s
      8193 |  1.202s | 0.028s |  1.562s | 0.032s
     16385 |  9.635s | 0.036s |  9.952s | 0.041s
     32769 | 60.928s | 0.063s | 44.321s | 0.064s
     65537 |         | 0.097s |         | 0.097s
    131073 |         | 0.233s |         | 0.176s
    262145 |         | 0.653s |         | 0.344s
    524289 |         | 2.305s |         | 0.735s

1048577 | | 7.107s | | 2.603s

Andrei Vagin reports fixing the performance problem is part of the work to fix CVE-2016-6213.

Fixes a05964f3917c ("[PATCH] shared mounts handling: umount")
---
 fs/pnode.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 62 insertions(+), 1 deletion(-)
fs/pnode.c +62 -1
index fbaca7df2eb0..53d411a371ce 100644
@@ -24,6 +24,11 @@ static inline struct mount *first_slave(struct mount *p)
 	return list_entry(p->mnt_slave_list.next, struct mount, mnt_slave);
 }
 
+static inline struct mount *last_slave(struct mount *p)
+{
+	return list_entry(p->mnt_slave_list.prev, struct mount, mnt_slave);
+}
+
 static inline struct mount *next_slave(struct mount *p)
 {
 	return list_entry(p->mnt_slave.next, struct mount, mnt_slave);
@@ -162,6 +167,19 @@ static struct mount *propagation_next(struct mount *m,
 	}
 }
 
+static struct mount *skip_propagation_subtree(struct mount *m,
+						struct mount *origin)
+{
+	/*
+	 * Advance m such that propagation_next will not return
+	 * the slaves of m.
+	 */
+	if (!IS_MNT_NEW(m) && !list_empty(&m->mnt_slave_list))
+		m = last_slave(m);
+
+	return m;
+}
+
 static struct mount *next_group(struct mount *m, struct mount *origin)
 {
 	while (1) {
@@ -505,6 +523,15 @@ static void restore_mounts(struct list_head *to_restore)
 	}
 }
 
+static void cleanup_umount_visitations(struct list_head *visited)
+{
+	while (!list_empty(visited)) {
+		struct mount *mnt =
+			list_first_entry(visited, struct mount, mnt_umounting);
+		list_del_init(&mnt->mnt_umounting);
+	}
+}
+
 /*
  * collect all mounts that receive propagation from the mount in @list,
  * and return these additional mounts in the same list.
@@ -517,11 +544,23 @@ int propagate_umount(struct list_head *list)
 	struct mount *mnt;
 	LIST_HEAD(to_restore);
 	LIST_HEAD(to_umount);
+	LIST_HEAD(visited);
 
-	list_for_each_entry(mnt, list, mnt_list) {
+	/* Find candidates for unmounting */
+	list_for_each_entry_reverse(mnt, list, mnt_list) {
 		struct mount *parent = mnt->mnt_parent;
 		struct mount *m;
 
+		/*
+		 * If this mount has already been visited it is known that it's
+		 * entire peer group and all of their slaves in the propagation
+		 * tree for the mountpoint has already been visited and there is
+		 * no need to visit them again.
+		 */
+		if (!list_empty(&mnt->mnt_umounting))
+			continue;
+
+		list_add_tail(&mnt->mnt_umounting, &visited);
 		for (m = propagation_next(parent, parent); m;
 		     m = propagation_next(m, parent)) {
 			struct mount *child = __lookup_mnt(&m->mnt,
@@ -529,6 +568,27 @@ int propagate_umount(struct list_head *list)
 			if (!child)
 				continue;
 
+			if (!list_empty(&child->mnt_umounting)) {
+				/*
+				 * If the child has already been visited it is
+				 * know that it's entire peer group and all of
+				 * their slaves in the propgation tree for the
+				 * mountpoint has already been visited and there
+				 * is no need to visit this subtree again.
+				 */
+				m = skip_propagation_subtree(m, parent);
+				continue;
+			} else if (child->mnt.mnt_flags & MNT_UMOUNT) {
+				/*
+				 * We have come accross an partially unmounted
+				 * mount in list that has not been visited yet.
+				 * Remember it has been visited and continue
+				 * about our merry way.
+				 */
+				list_add_tail(&child->mnt_umounting, &visited);
+				continue;
+			}
+
 			/* Check the child and parents while progress is made */
 			while (__propagate_umount(child,
 						  &to_umount, &to_restore)) {
@@ -542,6 +602,7 @@ int propagate_umount(struct list_head *list)
 
 	umount_list(&to_umount, &to_restore);
 	restore_mounts(&to_restore);
+	cleanup_umount_visitations(&visited);
 	list_splice_tail(&to_umount, list);
 
 	return 0;
Signature · 1 line
2.10.1
#48 lore raw

Re: [REVIEW][PATCH 2/2] mnt: Make propagate_umount less slow for overlapping mount propagation trees

Hi Eric,

I tested both patches and I haven't found any issue. Thanks.

Quoted 199 lines · Eric W. Biederman

Andrei Vagin pointed out that time to executue propagate_umount can go non-linear (and take a ludicrious amount of time) when the mount propogation trees of the mounts to be unmunted by a lazy unmount overlap.

Make the walk of the mount propagation trees nearly linear by remembering which mounts have already been visited, allowing subsequent walks to detect when walking a mount propgation tree or a subtree of a mount propgation tree would be duplicate work and to skip them entirely.

Walk the list of mounts whose propgatation trees need to be traversed from the mount highest in the mount tree to mounts lower in the mount tree so that odds are higher that the code will walk the largest trees first, allowing later tree walks to be skipped entirely.

Add cleanup_umount_visitation to remover the code's memory of which mounts have been visited.

Add the functions last_slave and skip_propagation_subtree to allow skipping appropriate parts of the mount propagation tree without needing to change the logic of the rest of the code.

A script to generate overlapping mount propagation trees:

$ cat runs.h
set -e
mount -t tmpfs zdtm /mnt
mkdir -p /mnt/1 /mnt/2
mount -t tmpfs zdtm /mnt/1
mount --make-shared /mnt/1
mkdir /mnt/1/1

iteration=10
if [ -n "$1" ] ; then
iteration=$1
fi

for i in $(seq $iteration); do

	mount --bind /mnt/1/1 /mnt/1/1

done

mount --rbind /mnt/1 /mnt/2

TIMEFORMAT='%Rs'
nr=$(( ( 2 ** ( $iteration + 1 ) ) + 1 ))
echo -n "umount -l /mnt/1 -> $nr "
time umount -l /mnt/1

nr=$(cat /proc/self/mountinfo | grep zdtm | wc -l ) time umount -l /mnt/2

$ for i in $(seq 9 19); do echo $i; unshare -Urm bash ./run.sh $i; done

Here are the performance numbers with and without the patch:

     mhash |  8192   |  8192  | 1048576 | 1048576
    mounts | before  | after  |  before | after
    ------------------------------------------------
      1025 |  0.040s | 0.016s |  0.038s | 0.019s
      2049 |  0.094s | 0.017s |  0.080s | 0.018s
      4097 |  0.243s | 0.019s |  0.206s | 0.023s
      8193 |  1.202s | 0.028s |  1.562s | 0.032s
     16385 |  9.635s | 0.036s |  9.952s | 0.041s
     32769 | 60.928s | 0.063s | 44.321s | 0.064s
     65537 |         | 0.097s |         | 0.097s
    131073 |         | 0.233s |         | 0.176s
    262145 |         | 0.653s |         | 0.344s
    524289 |         | 2.305s |         | 0.735s

1048577 | | 7.107s | | 2.603s

Andrei Vagin reports fixing the performance problem is part of the work to fix CVE-2016-6213.

Fixes a05964f3917c ("[PATCH] shared mounts handling: umount")
---
 fs/pnode.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 62 insertions(+), 1 deletion(-)
fs/pnode.c +62 -1
index fbaca7df2eb0..53d411a371ce 100644
@@ -24,6 +24,11 @@ static inline struct mount *first_slave(struct mount *p)
 	return list_entry(p->mnt_slave_list.next, struct mount, mnt_slave);
 }
 
+static inline struct mount *last_slave(struct mount *p)
+{
+	return list_entry(p->mnt_slave_list.prev, struct mount, mnt_slave);
+}
+
 static inline struct mount *next_slave(struct mount *p)
 {
 	return list_entry(p->mnt_slave.next, struct mount, mnt_slave);
@@ -162,6 +167,19 @@ static struct mount *propagation_next(struct mount *m,
 	}
 }
 
+static struct mount *skip_propagation_subtree(struct mount *m,
+						struct mount *origin)
+{
+	/*
+	 * Advance m such that propagation_next will not return
+	 * the slaves of m.
+	 */
+	if (!IS_MNT_NEW(m) && !list_empty(&m->mnt_slave_list))
+		m = last_slave(m);
+
+	return m;
+}
+
 static struct mount *next_group(struct mount *m, struct mount *origin)
 {
 	while (1) {
@@ -505,6 +523,15 @@ static void restore_mounts(struct list_head *to_restore)
 	}
 }
 
+static void cleanup_umount_visitations(struct list_head *visited)
+{
+	while (!list_empty(visited)) {
+		struct mount *mnt =
+			list_first_entry(visited, struct mount, mnt_umounting);
+		list_del_init(&mnt->mnt_umounting);
+	}
+}
+
 /*
  * collect all mounts that receive propagation from the mount in @list,
  * and return these additional mounts in the same list.
@@ -517,11 +544,23 @@ int propagate_umount(struct list_head *list)
 	struct mount *mnt;
 	LIST_HEAD(to_restore);
 	LIST_HEAD(to_umount);
+	LIST_HEAD(visited);
 
-	list_for_each_entry(mnt, list, mnt_list) {
+	/* Find candidates for unmounting */
+	list_for_each_entry_reverse(mnt, list, mnt_list) {
 		struct mount *parent = mnt->mnt_parent;
 		struct mount *m;
 
+		/*
+		 * If this mount has already been visited it is known that it's
+		 * entire peer group and all of their slaves in the propagation
+		 * tree for the mountpoint has already been visited and there is
+		 * no need to visit them again.
+		 */
+		if (!list_empty(&mnt->mnt_umounting))
+			continue;
+
+		list_add_tail(&mnt->mnt_umounting, &visited);
 		for (m = propagation_next(parent, parent); m;
 		     m = propagation_next(m, parent)) {
 			struct mount *child = __lookup_mnt(&m->mnt,
@@ -529,6 +568,27 @@ int propagate_umount(struct list_head *list)
 			if (!child)
 				continue;
 
+			if (!list_empty(&child->mnt_umounting)) {
+				/*
+				 * If the child has already been visited it is
+				 * know that it's entire peer group and all of
+				 * their slaves in the propgation tree for the
+				 * mountpoint has already been visited and there
+				 * is no need to visit this subtree again.
+				 */
+				m = skip_propagation_subtree(m, parent);
+				continue;
+			} else if (child->mnt.mnt_flags & MNT_UMOUNT) {
+				/*
+				 * We have come accross an partially unmounted
+				 * mount in list that has not been visited yet.
+				 * Remember it has been visited and continue
+				 * about our merry way.
+				 */
+				list_add_tail(&child->mnt_umounting, &visited);
+				continue;
+			}
+
 			/* Check the child and parents while progress is made */
 			while (__propagate_umount(child,
 						  &to_umount, &to_restore)) {
@@ -542,6 +602,7 @@ int propagate_umount(struct list_head *list)
 
 	umount_list(&to_umount, &to_restore);
 	restore_mounts(&to_restore);
+	cleanup_umount_visitations(&visited);
 	list_splice_tail(&to_umount, list);
 
 	return 0;
Signature · 1 line
2.10.1
#49 lore raw

Re: [REVIEW][PATCH 2/2] mnt: Make propagate_umount less slow for overlapping mount propagation trees

Quoted 3 lines · Andrei Vagin

Hi Eric,

I tested both patches and I haven't found any issue. Thanks.

Can I get a Tested-by an Acked-by or a Reviewed-by?

Apologies this took so long to get to this point until I realized that we could move mnt_change_mountpoint into a separate pass this didn't look possible.

Eric

Quoted 201 lines · Eric W. Biederman
Quoted 199 lines

Andrei Vagin pointed out that time to executue propagate_umount can go non-linear (and take a ludicrious amount of time) when the mount propogation trees of the mounts to be unmunted by a lazy unmount overlap.

Make the walk of the mount propagation trees nearly linear by remembering which mounts have already been visited, allowing subsequent walks to detect when walking a mount propgation tree or a subtree of a mount propgation tree would be duplicate work and to skip them entirely.

Walk the list of mounts whose propgatation trees need to be traversed from the mount highest in the mount tree to mounts lower in the mount tree so that odds are higher that the code will walk the largest trees first, allowing later tree walks to be skipped entirely.

Add cleanup_umount_visitation to remover the code's memory of which mounts have been visited.

Add the functions last_slave and skip_propagation_subtree to allow skipping appropriate parts of the mount propagation tree without needing to change the logic of the rest of the code.

A script to generate overlapping mount propagation trees:

$ cat runs.h
set -e
mount -t tmpfs zdtm /mnt
mkdir -p /mnt/1 /mnt/2
mount -t tmpfs zdtm /mnt/1
mount --make-shared /mnt/1
mkdir /mnt/1/1

iteration=10
if [ -n "$1" ] ; then
iteration=$1
fi

for i in $(seq $iteration); do

	mount --bind /mnt/1/1 /mnt/1/1

done

mount --rbind /mnt/1 /mnt/2

TIMEFORMAT='%Rs'
nr=$(( ( 2 ** ( $iteration + 1 ) ) + 1 ))
echo -n "umount -l /mnt/1 -> $nr "
time umount -l /mnt/1

nr=$(cat /proc/self/mountinfo | grep zdtm | wc -l ) time umount -l /mnt/2

$ for i in $(seq 9 19); do echo $i; unshare -Urm bash ./run.sh $i; done

Here are the performance numbers with and without the patch:

     mhash |  8192   |  8192  | 1048576 | 1048576
    mounts | before  | after  |  before | after
    ------------------------------------------------
      1025 |  0.040s | 0.016s |  0.038s | 0.019s
      2049 |  0.094s | 0.017s |  0.080s | 0.018s
      4097 |  0.243s | 0.019s |  0.206s | 0.023s
      8193 |  1.202s | 0.028s |  1.562s | 0.032s
     16385 |  9.635s | 0.036s |  9.952s | 0.041s
     32769 | 60.928s | 0.063s | 44.321s | 0.064s
     65537 |         | 0.097s |         | 0.097s
    131073 |         | 0.233s |         | 0.176s
    262145 |         | 0.653s |         | 0.344s
    524289 |         | 2.305s |         | 0.735s

1048577 | | 7.107s | | 2.603s

Andrei Vagin reports fixing the performance problem is part of the work to fix CVE-2016-6213.

Fixes a05964f3917c ("[PATCH] shared mounts handling: umount")
---
 fs/pnode.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 62 insertions(+), 1 deletion(-)
fs/pnode.c +62 -1
index fbaca7df2eb0..53d411a371ce 100644
@@ -24,6 +24,11 @@ static inline struct mount *first_slave(struct mount *p)
 	return list_entry(p->mnt_slave_list.next, struct mount, mnt_slave);
 }
 
+static inline struct mount *last_slave(struct mount *p)
+{
+	return list_entry(p->mnt_slave_list.prev, struct mount, mnt_slave);
+}
+
 static inline struct mount *next_slave(struct mount *p)
 {
 	return list_entry(p->mnt_slave.next, struct mount, mnt_slave);
@@ -162,6 +167,19 @@ static struct mount *propagation_next(struct mount *m,
 	}
 }
 
+static struct mount *skip_propagation_subtree(struct mount *m,
+						struct mount *origin)
+{
+	/*
+	 * Advance m such that propagation_next will not return
+	 * the slaves of m.
+	 */
+	if (!IS_MNT_NEW(m) && !list_empty(&m->mnt_slave_list))
+		m = last_slave(m);
+
+	return m;
+}
+
 static struct mount *next_group(struct mount *m, struct mount *origin)
 {
 	while (1) {
@@ -505,6 +523,15 @@ static void restore_mounts(struct list_head *to_restore)
 	}
 }
 
+static void cleanup_umount_visitations(struct list_head *visited)
+{
+	while (!list_empty(visited)) {
+		struct mount *mnt =
+			list_first_entry(visited, struct mount, mnt_umounting);
+		list_del_init(&mnt->mnt_umounting);
+	}
+}
+
 /*
  * collect all mounts that receive propagation from the mount in @list,
  * and return these additional mounts in the same list.
@@ -517,11 +544,23 @@ int propagate_umount(struct list_head *list)
 	struct mount *mnt;
 	LIST_HEAD(to_restore);
 	LIST_HEAD(to_umount);
+	LIST_HEAD(visited);
 
-	list_for_each_entry(mnt, list, mnt_list) {
+	/* Find candidates for unmounting */
+	list_for_each_entry_reverse(mnt, list, mnt_list) {
 		struct mount *parent = mnt->mnt_parent;
 		struct mount *m;
 
+		/*
+		 * If this mount has already been visited it is known that it's
+		 * entire peer group and all of their slaves in the propagation
+		 * tree for the mountpoint has already been visited and there is
+		 * no need to visit them again.
+		 */
+		if (!list_empty(&mnt->mnt_umounting))
+			continue;
+
+		list_add_tail(&mnt->mnt_umounting, &visited);
 		for (m = propagation_next(parent, parent); m;
 		     m = propagation_next(m, parent)) {
 			struct mount *child = __lookup_mnt(&m->mnt,
@@ -529,6 +568,27 @@ int propagate_umount(struct list_head *list)
 			if (!child)
 				continue;
 
+			if (!list_empty(&child->mnt_umounting)) {
+				/*
+				 * If the child has already been visited it is
+				 * know that it's entire peer group and all of
+				 * their slaves in the propgation tree for the
+				 * mountpoint has already been visited and there
+				 * is no need to visit this subtree again.
+				 */
+				m = skip_propagation_subtree(m, parent);
+				continue;
+			} else if (child->mnt.mnt_flags & MNT_UMOUNT) {
+				/*
+				 * We have come accross an partially unmounted
+				 * mount in list that has not been visited yet.
+				 * Remember it has been visited and continue
+				 * about our merry way.
+				 */
+				list_add_tail(&child->mnt_umounting, &visited);
+				continue;
+			}
+
 			/* Check the child and parents while progress is made */
 			while (__propagate_umount(child,
 						  &to_umount, &to_restore)) {
@@ -542,6 +602,7 @@ int propagate_umount(struct list_head *list)
 
 	umount_list(&to_umount, &to_restore);
 	restore_mounts(&to_restore);
+	cleanup_umount_visitations(&visited);
 	list_splice_tail(&to_umount, list);
 
 	return 0;
Signature · 1 line
2.10.1
Reviewed-by
#50 lore raw

Re: [REVIEW][PATCH 2/2] mnt: Make propagate_umount less slow for overlapping mount propagation trees

Quoted 7 lines · Eric W. Biederman
Quoted 3 lines · Andrei Vagin

Hi Eric,

I tested both patches and I haven't found any issue. Thanks.

Can I get a Tested-by an Acked-by or a Reviewed-by?

Sure. I read patches and they look good for me.

Thanks,
Andrei

Quoted 208 lines

Apologies this took so long to get to this point until I realized that we could move mnt_change_mountpoint into a separate pass this didn't look possible.

Eric

Quoted 201 lines · Eric W. Biederman
Quoted 199 lines

Andrei Vagin pointed out that time to executue propagate_umount can go non-linear (and take a ludicrious amount of time) when the mount propogation trees of the mounts to be unmunted by a lazy unmount overlap.

Make the walk of the mount propagation trees nearly linear by remembering which mounts have already been visited, allowing subsequent walks to detect when walking a mount propgation tree or a subtree of a mount propgation tree would be duplicate work and to skip them entirely.

Walk the list of mounts whose propgatation trees need to be traversed from the mount highest in the mount tree to mounts lower in the mount tree so that odds are higher that the code will walk the largest trees first, allowing later tree walks to be skipped entirely.

Add cleanup_umount_visitation to remover the code's memory of which mounts have been visited.

Add the functions last_slave and skip_propagation_subtree to allow skipping appropriate parts of the mount propagation tree without needing to change the logic of the rest of the code.

A script to generate overlapping mount propagation trees:

$ cat runs.h
set -e
mount -t tmpfs zdtm /mnt
mkdir -p /mnt/1 /mnt/2
mount -t tmpfs zdtm /mnt/1
mount --make-shared /mnt/1
mkdir /mnt/1/1

iteration=10
if [ -n "$1" ] ; then
iteration=$1
fi

for i in $(seq $iteration); do

	mount --bind /mnt/1/1 /mnt/1/1

done

mount --rbind /mnt/1 /mnt/2

TIMEFORMAT='%Rs'
nr=$(( ( 2 ** ( $iteration + 1 ) ) + 1 ))
echo -n "umount -l /mnt/1 -> $nr "
time umount -l /mnt/1

nr=$(cat /proc/self/mountinfo | grep zdtm | wc -l ) time umount -l /mnt/2

$ for i in $(seq 9 19); do echo $i; unshare -Urm bash ./run.sh $i; done

Here are the performance numbers with and without the patch:

     mhash |  8192   |  8192  | 1048576 | 1048576
    mounts | before  | after  |  before | after
    ------------------------------------------------
      1025 |  0.040s | 0.016s |  0.038s | 0.019s
      2049 |  0.094s | 0.017s |  0.080s | 0.018s
      4097 |  0.243s | 0.019s |  0.206s | 0.023s
      8193 |  1.202s | 0.028s |  1.562s | 0.032s
     16385 |  9.635s | 0.036s |  9.952s | 0.041s
     32769 | 60.928s | 0.063s | 44.321s | 0.064s
     65537 |         | 0.097s |         | 0.097s
    131073 |         | 0.233s |         | 0.176s
    262145 |         | 0.653s |         | 0.344s
    524289 |         | 2.305s |         | 0.735s

1048577 | | 7.107s | | 2.603s

Andrei Vagin reports fixing the performance problem is part of the work to fix CVE-2016-6213.

Fixes a05964f3917c ("[PATCH] shared mounts handling: umount")
---
 fs/pnode.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 62 insertions(+), 1 deletion(-)
fs/pnode.c +62 -1
index fbaca7df2eb0..53d411a371ce 100644
@@ -24,6 +24,11 @@ static inline struct mount *first_slave(struct mount *p)
 	return list_entry(p->mnt_slave_list.next, struct mount, mnt_slave);
 }
 
+static inline struct mount *last_slave(struct mount *p)
+{
+	return list_entry(p->mnt_slave_list.prev, struct mount, mnt_slave);
+}
+
 static inline struct mount *next_slave(struct mount *p)
 {
 	return list_entry(p->mnt_slave.next, struct mount, mnt_slave);
@@ -162,6 +167,19 @@ static struct mount *propagation_next(struct mount *m,
 	}
 }
 
+static struct mount *skip_propagation_subtree(struct mount *m,
+						struct mount *origin)
+{
+	/*
+	 * Advance m such that propagation_next will not return
+	 * the slaves of m.
+	 */
+	if (!IS_MNT_NEW(m) && !list_empty(&m->mnt_slave_list))
+		m = last_slave(m);
+
+	return m;
+}
+
 static struct mount *next_group(struct mount *m, struct mount *origin)
 {
 	while (1) {
@@ -505,6 +523,15 @@ static void restore_mounts(struct list_head *to_restore)
 	}
 }
 
+static void cleanup_umount_visitations(struct list_head *visited)
+{
+	while (!list_empty(visited)) {
+		struct mount *mnt =
+			list_first_entry(visited, struct mount, mnt_umounting);
+		list_del_init(&mnt->mnt_umounting);
+	}
+}
+
 /*
  * collect all mounts that receive propagation from the mount in @list,
  * and return these additional mounts in the same list.
@@ -517,11 +544,23 @@ int propagate_umount(struct list_head *list)
 	struct mount *mnt;
 	LIST_HEAD(to_restore);
 	LIST_HEAD(to_umount);
+	LIST_HEAD(visited);
 
-	list_for_each_entry(mnt, list, mnt_list) {
+	/* Find candidates for unmounting */
+	list_for_each_entry_reverse(mnt, list, mnt_list) {
 		struct mount *parent = mnt->mnt_parent;
 		struct mount *m;
 
+		/*
+		 * If this mount has already been visited it is known that it's
+		 * entire peer group and all of their slaves in the propagation
+		 * tree for the mountpoint has already been visited and there is
+		 * no need to visit them again.
+		 */
+		if (!list_empty(&mnt->mnt_umounting))
+			continue;
+
+		list_add_tail(&mnt->mnt_umounting, &visited);
 		for (m = propagation_next(parent, parent); m;
 		     m = propagation_next(m, parent)) {
 			struct mount *child = __lookup_mnt(&m->mnt,
@@ -529,6 +568,27 @@ int propagate_umount(struct list_head *list)
 			if (!child)
 				continue;
 
+			if (!list_empty(&child->mnt_umounting)) {
+				/*
+				 * If the child has already been visited it is
+				 * know that it's entire peer group and all of
+				 * their slaves in the propgation tree for the
+				 * mountpoint has already been visited and there
+				 * is no need to visit this subtree again.
+				 */
+				m = skip_propagation_subtree(m, parent);
+				continue;
+			} else if (child->mnt.mnt_flags & MNT_UMOUNT) {
+				/*
+				 * We have come accross an partially unmounted
+				 * mount in list that has not been visited yet.
+				 * Remember it has been visited and continue
+				 * about our merry way.
+				 */
+				list_add_tail(&child->mnt_umounting, &visited);
+				continue;
+			}
+
 			/* Check the child and parents while progress is made */
 			while (__propagate_umount(child,
 						  &to_umount, &to_restore)) {
@@ -542,6 +602,7 @@ int propagate_umount(struct list_head *list)
 
 	umount_list(&to_umount, &to_restore);
 	restore_mounts(&to_restore);
+	cleanup_umount_visitations(&visited);
 	list_splice_tail(&to_umount, list);
 
 	return 0;
Signature · 1 line
2.10.1
#51 lore raw

Re: [REVIEW][PATCH 1/2] mnt: In propgate_umount handle visiting mounts in any order

In reply to Eric W. Biederman
Quoted 44 lines · Eric W. Biederman

While investigating some poor umount performance I realized that in the case of overlapping mount trees where some of the mounts are locked the code has been failing to unmount all of the mounts it should have been unmounting.

This failure to unmount all of the necessary
mounts can be reproduced with:

$ cat locked_mounts_test.sh

mount -t tmpfs test-base /mnt
mount --make-shared /mnt
mkdir -p /mnt/b

mount -t tmpfs test1 /mnt/b
mount --make-shared /mnt/b
mkdir -p /mnt/b/10

mount -t tmpfs test2 /mnt/b/10
mount --make-shared /mnt/b/10
mkdir -p /mnt/b/10/20

mount --rbind /mnt/b /mnt/b/10/20

unshare -Urm --propagation unchaged /bin/sh -c 'sleep 5; if [ $(grep test /proc/self/mountinfo | wc -l) -eq 1 ] ; then echo SUCCESS ; else echo FAILURE ; fi' sleep 1
umount -l /mnt/b
wait %%

$ unshare -Urm ./locked_mounts_test.sh

This failure is corrected by removing the prepass that marks mounts that may be umounted.

A first pass is added that umounts mounts if possible and if not sets mount mark if they could be unmounted if they weren't locked and adds them to a list to umount possibilities. This first pass reconsiders the mounts parent if it is on the list of umount possibilities, ensuring that information of umoutability will pass from child to mount parent.

A second pass then walks through all mounts that are umounted and processes their children unmounting them or marking them for reparenting.

Quoted 8 lines

A last pass cleans up the state on the mounts that could not be umounted and if applicable reparents them to their first parent that remained mounted.

While a bit longer than the old code this code is much more robust as it allows information to flow up from the leaves and down from the trunk making the order in which mounts are encountered in the umount propgation tree irrelevant.

Eric,
I think we can accomplish what you want in a much simpler way. Would the patch below; UNTESTED BUT COMPILED, resolve your issue?

Its a two pass unmount. First pass marks mounts that can be unmounted, and second pass does the neccessary unlinks. It does mark TUCKED mounts, and uses that information to peel off the correct mounts. Key points are

a) a tucked mount never entertain any unmount propagation on its root dentry.

b) when the child on the root dentry of a tucked mount is unmounted, the tucked mount is not a tucked mount anymore.

c) if the child is a tucked mount, than its child is reparented to the parent.

fs/namespace.c        |    4 ++-
fs/pnode.c            |   53 +++++++++++++++++++++++++++++++++++++-------------
fs/pnode.h            |    3 ++
include/linux/mount.h |    1 
fs/namespace.c +3 -1
index cc1375ef..ff3ec90 100644
@@ -2050,8 +2050,10 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 		hlist_del_init(&child->mnt_hash);
 		q = __lookup_mnt(&child->mnt_parent->mnt,
 				 child->mnt_mountpoint);
-		if (q)
+		if (q) {
 			mnt_change_mountpoint(child, smp, q);
+			SET_MNT_TUCKED(child);
+		}
 		commit_tree(child);
 	}
 	put_mountpoint(smp);
fs/pnode.c +40 -13

71 lines not on this page · raw message

fs/pnode.h +3 -0
index dc87e65..9ebd1a8 100644
@@ -18,8 +18,11 @@
 #define IS_MNT_UNBINDABLE(m) ((m)->mnt.mnt_flags & MNT_UNBINDABLE)
 #define IS_MNT_MARKED(m) ((m)->mnt.mnt_flags & MNT_MARKED)
 #define SET_MNT_MARK(m) ((m)->mnt.mnt_flags |= MNT_MARKED)
+#define SET_MNT_TUCKED(m) ((m)->mnt.mnt_flags |= MNT_TUCKED)
 #define CLEAR_MNT_MARK(m) ((m)->mnt.mnt_flags &= ~MNT_MARKED)
+#define CLEAR_MNT_TUCKED(m) ((m)->mnt.mnt_flags &= ~MNT_TUCKED)
 #define IS_MNT_LOCKED(m) ((m)->mnt.mnt_flags & MNT_LOCKED)
+#define IS_MNT_TUCKED(m) ((m)->mnt.mnt_flags & MNT_TUCKED)
 
 #define CL_EXPIRE    		0x01
 #define CL_SLAVE     		0x02
include/linux/mount.h +1 -0
index 8e0352a..41674e7 100644
@@ -62,6 +62,7 @@
 #define MNT_SYNC_UMOUNT		0x2000000
 #define MNT_MARKED		0x4000000
 #define MNT_UMOUNT		0x8000000
+#define MNT_TUCKED		0x10000000
 
 struct vfsmount {
 	struct dentry *mnt_root;	/* root of the mounted tree */
#52 lore raw

Re: [REVIEW][PATCH 1/2] mnt: In propgate_umount handle visiting mounts in any order

Quoted 54 lines · Ram Pai
Quoted 44 lines · Eric W. Biederman

While investigating some poor umount performance I realized that in the case of overlapping mount trees where some of the mounts are locked the code has been failing to unmount all of the mounts it should have been unmounting.

This failure to unmount all of the necessary
mounts can be reproduced with:

$ cat locked_mounts_test.sh

mount -t tmpfs test-base /mnt
mount --make-shared /mnt
mkdir -p /mnt/b

mount -t tmpfs test1 /mnt/b
mount --make-shared /mnt/b
mkdir -p /mnt/b/10

mount -t tmpfs test2 /mnt/b/10
mount --make-shared /mnt/b/10
mkdir -p /mnt/b/10/20

mount --rbind /mnt/b /mnt/b/10/20

unshare -Urm --propagation unchaged /bin/sh -c 'sleep 5; if [ $(grep test /proc/self/mountinfo | wc -l) -eq 1 ] ; then echo SUCCESS ; else echo FAILURE ; fi' sleep 1
umount -l /mnt/b
wait %%

$ unshare -Urm ./locked_mounts_test.sh

This failure is corrected by removing the prepass that marks mounts that may be umounted.

A first pass is added that umounts mounts if possible and if not sets mount mark if they could be unmounted if they weren't locked and adds them to a list to umount possibilities. This first pass reconsiders the mounts parent if it is on the list of umount possibilities, ensuring that information of umoutability will pass from child to mount parent.

A second pass then walks through all mounts that are umounted and processes their children unmounting them or marking them for reparenting.

Quoted 8 lines

A last pass cleans up the state on the mounts that could not be umounted and if applicable reparents them to their first parent that remained mounted.

While a bit longer than the old code this code is much more robust as it allows information to flow up from the leaves and down from the trunk making the order in which mounts are encountered in the umount propgation tree irrelevant.

Quoted 4 lines

Eric,
I think we can accomplish what you want in a much simpler way. Would the patch below; UNTESTED BUT COMPILED, resolve your issue?

The reason I came up with an algorithm where the information flows both directions is that especially in the case of umount -l but even in some rare cases of a simple umount, the ordering of the mount propagation tree can result in parent mounts being visited before the child mounts.

This case shows in in the case of a mount or a set of mounts being mounted below itself.

So no. Irregardless of tucked mount state we can't do this.

I see this also doesn't have the change to move mnt_change_mountpoint into another pass. That one is quite important from a practical point of view as that means the way the mount tree changes in umount is the same irrespective of the number of times a mount shows up in the mount propagation trees. Which is a very important property to have for optimizing umount -l. Which in
the worst case allows reduces umount from O(N^2+) to roughly O(N).

All of what I am doing should have not effect on an implementation of MNT_TUCKED.

That said your code to deal with MNT_TUCKED seems reasonable.

Eric

Quoted 143 lines

Its a two pass unmount. First pass marks mounts that can be unmounted, and second pass does the neccessary unlinks. It does mark TUCKED mounts, and uses that information to peel off the correct mounts. Key points are

a) a tucked mount never entertain any unmount propagation on its root dentry.

b) when the child on the root dentry of a tucked mount is unmounted, the tucked mount is not a tucked mount anymore.

c) if the child is a tucked mount, than its child is reparented to the parent.

fs/namespace.c        |    4 ++-
fs/pnode.c            |   53 +++++++++++++++++++++++++++++++++++++-------------
fs/pnode.h            |    3 ++
include/linux/mount.h |    1 
fs/namespace.c +3 -1
index cc1375ef..ff3ec90 100644
@@ -2050,8 +2050,10 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 		hlist_del_init(&child->mnt_hash);
 		q = __lookup_mnt(&child->mnt_parent->mnt,
 				 child->mnt_mountpoint);
-		if (q)
+		if (q) {
 			mnt_change_mountpoint(child, smp, q);
+			SET_MNT_TUCKED(child);
+		}
 		commit_tree(child);
 	}
 	put_mountpoint(smp);
fs/pnode.c +40 -13
index 5bc7896..b44a544 100644
@@ -448,31 +448,58 @@ static void __propagate_umount(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-		struct mount *topper;
-		struct mount *child = __lookup_mnt(&m->mnt,
-						mnt->mnt_mountpoint);
-		/*
-		 * umount the child only if the child has no children
-		 * and the child is marked safe to unmount.
+		struct mount *topper, *child;
+
+		/* Tucked mount must drop umount propagation events on
+		 * its **root dentry**.
+		 * The tucked mount did not exist when that child came
+		 * into existence. It never received that mount propagation.
+		 * Hence it should never entertain the umount propagation
+		 * aswell.
 		 */
+		if (IS_MNT_TUCKED(m) && list_is_singular(&mnt->mnt_mounts))
+			continue;
+
+
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
+
 		if (!child || !IS_MNT_MARKED(child))
 			continue;
+
 		CLEAR_MNT_MARK(child);
 
-		/* If there is exactly one mount covering all of child
-		 * replace child with that mount.
-		 */
-		topper = find_topper(child);
-		if (topper)
-			mnt_change_mountpoint(child->mnt_parent, child->mnt_mp,
-					      topper);
+		if (IS_MNT_TUCKED(child) &&
+			(list_is_singular(&child->mnt_mounts))) {
+			topper = find_topper(child);
+			if (topper) {
+				mnt_change_mountpoint(child->mnt_parent,
+					child->mnt_mp, topper);
+				CLEAR_MNT_TUCKED(child); /*lets be precise*/
+			}
+		}
 
 		if (list_empty(&child->mnt_mounts)) {
 			list_del_init(&child->mnt_child);
 			child->mnt.mnt_flags |= MNT_UMOUNT;
 			list_move_tail(&child->mnt_list, &mnt->mnt_list);
 		}
+#if 0
+	       	else {
+			mntput(child); /* mark it for deletion. It will
+				       	  be deleted whenever it looses
+					  all its remaining references.
+					  TODO: some more thought
+					  needed, please validate */
+		}
+#endif
 	}
+
+	/*
+	 * This explicit umount operation is exposing the parent.
+	 * In case the parent was a 'tucked' mount, it cannot be so
+	 * anymore.
+	 */
+	CLEAR_MNT_TUCKED(parent);
 }
 
 /*
fs/pnode.h +3 -0
index dc87e65..9ebd1a8 100644
@@ -18,8 +18,11 @@
 #define IS_MNT_UNBINDABLE(m) ((m)->mnt.mnt_flags & MNT_UNBINDABLE)
 #define IS_MNT_MARKED(m) ((m)->mnt.mnt_flags & MNT_MARKED)
 #define SET_MNT_MARK(m) ((m)->mnt.mnt_flags |= MNT_MARKED)
+#define SET_MNT_TUCKED(m) ((m)->mnt.mnt_flags |= MNT_TUCKED)
 #define CLEAR_MNT_MARK(m) ((m)->mnt.mnt_flags &= ~MNT_MARKED)
+#define CLEAR_MNT_TUCKED(m) ((m)->mnt.mnt_flags &= ~MNT_TUCKED)
 #define IS_MNT_LOCKED(m) ((m)->mnt.mnt_flags & MNT_LOCKED)
+#define IS_MNT_TUCKED(m) ((m)->mnt.mnt_flags & MNT_TUCKED)
 
 #define CL_EXPIRE    		0x01
 #define CL_SLAVE     		0x02
include/linux/mount.h +1 -0
index 8e0352a..41674e7 100644
@@ -62,6 +62,7 @@
 #define MNT_SYNC_UMOUNT		0x2000000
 #define MNT_MARKED		0x4000000
 #define MNT_UMOUNT		0x8000000
+#define MNT_TUCKED		0x10000000
 
 struct vfsmount {
 	struct dentry *mnt_root;	/* root of the mounted tree */
#53 lore raw

Re: [REVIEW][PATCH 1/2] mnt: In propgate_umount handle visiting mounts in any order

Quoted 67 lines · Eric W. Biederman
Quoted 54 lines · Ram Pai
Quoted 44 lines · Eric W. Biederman

While investigating some poor umount performance I realized that in the case of overlapping mount trees where some of the mounts are locked the code has been failing to unmount all of the mounts it should have been unmounting.

This failure to unmount all of the necessary
mounts can be reproduced with:

$ cat locked_mounts_test.sh

mount -t tmpfs test-base /mnt
mount --make-shared /mnt
mkdir -p /mnt/b

mount -t tmpfs test1 /mnt/b
mount --make-shared /mnt/b
mkdir -p /mnt/b/10

mount -t tmpfs test2 /mnt/b/10
mount --make-shared /mnt/b/10
mkdir -p /mnt/b/10/20

mount --rbind /mnt/b /mnt/b/10/20

unshare -Urm --propagation unchaged /bin/sh -c 'sleep 5; if [ $(grep test /proc/self/mountinfo | wc -l) -eq 1 ] ; then echo SUCCESS ; else echo FAILURE ; fi' sleep 1
umount -l /mnt/b
wait %%

$ unshare -Urm ./locked_mounts_test.sh

This failure is corrected by removing the prepass that marks mounts that may be umounted.

A first pass is added that umounts mounts if possible and if not sets mount mark if they could be unmounted if they weren't locked and adds them to a list to umount possibilities. This first pass reconsiders the mounts parent if it is on the list of umount possibilities, ensuring that information of umoutability will pass from child to mount parent.

A second pass then walks through all mounts that are umounted and processes their children unmounting them or marking them for reparenting.

Quoted 8 lines

A last pass cleans up the state on the mounts that could not be umounted and if applicable reparents them to their first parent that remained mounted.

While a bit longer than the old code this code is much more robust as it allows information to flow up from the leaves and down from the trunk making the order in which mounts are encountered in the umount propgation tree irrelevant.

Quoted 4 lines

Eric,
I think we can accomplish what you want in a much simpler way. Would the patch below; UNTESTED BUT COMPILED, resolve your issue?

The reason I came up with an algorithm where the information flows both directions is that especially in the case of umount -l but even in some rare cases of a simple umount, the ordering of the mount propagation tree can result in parent mounts being visited before the child mounts.

Quoted 4 lines

This case shows in in the case of a mount or a set of mounts being mounted below itself.

So no. Irregardless of tucked mount state we can't do this.

Ok. I thought I had taken care, regardles of the order in which the mounts were encountered. I need to understand your patch better. Will relook at it later tonight.

RP

Quoted 159 lines

I see this also doesn't have the change to move mnt_change_mountpoint into another pass. That one is quite important from a practical point of view as that means the way the mount tree changes in umount is the same irrespective of the number of times a mount shows up in the mount propagation trees. Which is a very important property to have for optimizing umount -l. Which in
the worst case allows reduces umount from O(N^2+) to roughly O(N).

All of what I am doing should have not effect on an implementation of MNT_TUCKED.

That said your code to deal with MNT_TUCKED seems reasonable.

Eric

Quoted 143 lines

Its a two pass unmount. First pass marks mounts that can be unmounted, and second pass does the neccessary unlinks. It does mark TUCKED mounts, and uses that information to peel off the correct mounts. Key points are

a) a tucked mount never entertain any unmount propagation on its root dentry.

b) when the child on the root dentry of a tucked mount is unmounted, the tucked mount is not a tucked mount anymore.

c) if the child is a tucked mount, than its child is reparented to the parent.

fs/namespace.c        |    4 ++-
fs/pnode.c            |   53 +++++++++++++++++++++++++++++++++++++-------------
fs/pnode.h            |    3 ++
include/linux/mount.h |    1 
fs/namespace.c +3 -1
index cc1375ef..ff3ec90 100644
@@ -2050,8 +2050,10 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 		hlist_del_init(&child->mnt_hash);
 		q = __lookup_mnt(&child->mnt_parent->mnt,
 				 child->mnt_mountpoint);
-		if (q)
+		if (q) {
 			mnt_change_mountpoint(child, smp, q);
+			SET_MNT_TUCKED(child);
+		}
 		commit_tree(child);
 	}
 	put_mountpoint(smp);
fs/pnode.c +40 -13
index 5bc7896..b44a544 100644
@@ -448,31 +448,58 @@ static void __propagate_umount(struct mount *mnt)
 
 	for (m = propagation_next(parent, parent); m;
 			m = propagation_next(m, parent)) {
-		struct mount *topper;
-		struct mount *child = __lookup_mnt(&m->mnt,
-						mnt->mnt_mountpoint);
-		/*
-		 * umount the child only if the child has no children
-		 * and the child is marked safe to unmount.
+		struct mount *topper, *child;
+
+		/* Tucked mount must drop umount propagation events on
+		 * its **root dentry**.
+		 * The tucked mount did not exist when that child came
+		 * into existence. It never received that mount propagation.
+		 * Hence it should never entertain the umount propagation
+		 * aswell.
 		 */
+		if (IS_MNT_TUCKED(m) && list_is_singular(&mnt->mnt_mounts))
+			continue;
+
+
+		child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
+
 		if (!child || !IS_MNT_MARKED(child))
 			continue;
+
 		CLEAR_MNT_MARK(child);
 
-		/* If there is exactly one mount covering all of child
-		 * replace child with that mount.
-		 */
-		topper = find_topper(child);
-		if (topper)
-			mnt_change_mountpoint(child->mnt_parent, child->mnt_mp,
-					      topper);
+		if (IS_MNT_TUCKED(child) &&
+			(list_is_singular(&child->mnt_mounts))) {
+			topper = find_topper(child);
+			if (topper) {
+				mnt_change_mountpoint(child->mnt_parent,
+					child->mnt_mp, topper);
+				CLEAR_MNT_TUCKED(child); /*lets be precise*/
+			}
+		}
 
 		if (list_empty(&child->mnt_mounts)) {
 			list_del_init(&child->mnt_child);
 			child->mnt.mnt_flags |= MNT_UMOUNT;
 			list_move_tail(&child->mnt_list, &mnt->mnt_list);
 		}
+#if 0
+	       	else {
+			mntput(child); /* mark it for deletion. It will
+				       	  be deleted whenever it looses
+					  all its remaining references.
+					  TODO: some more thought
+					  needed, please validate */
+		}
+#endif
 	}
+
+	/*
+	 * This explicit umount operation is exposing the parent.
+	 * In case the parent was a 'tucked' mount, it cannot be so
+	 * anymore.
+	 */
+	CLEAR_MNT_TUCKED(parent);
 }
 
 /*
fs/pnode.h +3 -0
index dc87e65..9ebd1a8 100644
@@ -18,8 +18,11 @@
 #define IS_MNT_UNBINDABLE(m) ((m)->mnt.mnt_flags & MNT_UNBINDABLE)
 #define IS_MNT_MARKED(m) ((m)->mnt.mnt_flags & MNT_MARKED)
 #define SET_MNT_MARK(m) ((m)->mnt.mnt_flags |= MNT_MARKED)
+#define SET_MNT_TUCKED(m) ((m)->mnt.mnt_flags |= MNT_TUCKED)
 #define CLEAR_MNT_MARK(m) ((m)->mnt.mnt_flags &= ~MNT_MARKED)
+#define CLEAR_MNT_TUCKED(m) ((m)->mnt.mnt_flags &= ~MNT_TUCKED)
 #define IS_MNT_LOCKED(m) ((m)->mnt.mnt_flags & MNT_LOCKED)
+#define IS_MNT_TUCKED(m) ((m)->mnt.mnt_flags & MNT_TUCKED)
 
 #define CL_EXPIRE    		0x01
 #define CL_SLAVE     		0x02
include/linux/mount.h +1 -0
index 8e0352a..41674e7 100644
@@ -62,6 +62,7 @@
 #define MNT_SYNC_UMOUNT		0x2000000
 #define MNT_MARKED		0x4000000
 #define MNT_UMOUNT		0x8000000
+#define MNT_TUCKED		0x10000000
 
 struct vfsmount {
 	struct dentry *mnt_root;	/* root of the mounted tree */
Signature · 1 line
Ram Pai
#54 lore raw

Re: [REVIEW][PATCH 1/2] mnt: In propgate_umount handle visiting mounts in any order

In reply to Eric W. Biederman
Quoted 44 lines · Eric W. Biederman

While investigating some poor umount performance I realized that in the case of overlapping mount trees where some of the mounts are locked the code has been failing to unmount all of the mounts it should have been unmounting.

This failure to unmount all of the necessary
mounts can be reproduced with:

$ cat locked_mounts_test.sh

mount -t tmpfs test-base /mnt
mount --make-shared /mnt
mkdir -p /mnt/b

mount -t tmpfs test1 /mnt/b
mount --make-shared /mnt/b
mkdir -p /mnt/b/10

mount -t tmpfs test2 /mnt/b/10
mount --make-shared /mnt/b/10
mkdir -p /mnt/b/10/20

mount --rbind /mnt/b /mnt/b/10/20

unshare -Urm --propagation unchaged /bin/sh -c 'sleep 5; if [ $(grep test /proc/self/mountinfo | wc -l) -eq 1 ] ; then echo SUCCESS ; else echo FAILURE ; fi' sleep 1
umount -l /mnt/b
wait %%

$ unshare -Urm ./locked_mounts_test.sh

This failure is corrected by removing the prepass that marks mounts that may be umounted.

A first pass is added that umounts mounts if possible and if not sets mount mark if they could be unmounted if they weren't locked and adds them to a list to umount possibilities. This first pass reconsiders the mounts parent if it is on the list of umount possibilities, ensuring that information of umoutability will pass from child to mount parent.

A second pass then walks through all mounts that are umounted and processes their children unmounting them or marking them for reparenting.

Quoted 8 lines

A last pass cleans up the state on the mounts that could not be umounted and if applicable reparents them to their first parent that remained mounted.

While a bit longer than the old code this code is much more robust as it allows information to flow up from the leaves and down from the trunk making the order in which mounts are encountered in the umount propgation tree irrelevant.

Eric,

I tried multiple time to understand the algorithm, but failed to understand the reasoning behind each of the steps. Hence I can't tell if the algorithm is correct or wrong.

I know you are trying to optimize the current algorithm, but what is the key insight that you are trying to leverage to optimize it? That probably might help me analyze the algorithm.

You walk the propogation tree, and for each element in the propagation-tree you try to unmount its entire mount-tree. (not sure if this operation is correct, since I know, I had
given an example in the past where this can go wrong).
And later if you find that the unmount is successful, you try to walk up and see if the parent can also be unmounted(dont know why this is needed).

Sorry, but if you can help with some key insights, it will help. RP

Quoted 231 lines
Fixes 0c56fe31420c ("mnt: Don't propagate unmounts to locked mounts")
---
 fs/mount.h     |   2 +-
 fs/namespace.c |   2 +-
 fs/pnode.c     | 144 ++++++++++++++++++++++++++++++++++-----------------------
 3 files changed, 88 insertions(+), 60 deletions(-)
fs/mount.h +1 -1
index ede5a1d5cf99..de45d9e76748 100644
@@ -58,7 +58,7 @@ struct mount {
 	struct mnt_namespace *mnt_ns;	/* containing namespace */
 	struct mountpoint *mnt_mp;	/* where is it mounted */
 	struct hlist_node mnt_mp_list;	/* list mounts with the same mountpoint */
-	struct list_head mnt_reparent;	/* reparent list entry */
+	struct list_head mnt_umounting; /* list entry for umount propagation */
 #ifdef CONFIG_FSNOTIFY
 	struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks;
 	__u32 mnt_fsnotify_mask;
fs/namespace.c +1 -1
index 51e49866e1fe..5e3dcbeb1de5 100644
@@ -236,7 +236,7 @@ static struct mount *alloc_vfsmnt(const char *name)
 		INIT_LIST_HEAD(&mnt->mnt_slave_list);
 		INIT_LIST_HEAD(&mnt->mnt_slave);
 		INIT_HLIST_NODE(&mnt->mnt_mp_list);
-		INIT_LIST_HEAD(&mnt->mnt_reparent);
+		INIT_LIST_HEAD(&mnt->mnt_umounting);
 		init_fs_pin(&mnt->mnt_umount, drop_mountpoint);
 	}
 	return mnt;
fs/pnode.c +86 -58
index 52aca0a118ff..fbaca7df2eb0 100644
@@ -413,86 +413,95 @@ void propagate_mount_unlock(struct mount *mnt)
 	}
 }
 
-/*
- * Mark all mounts that the MNT_LOCKED logic will allow to be unmounted.
- */
-static void mark_umount_candidates(struct mount *mnt)
+static void umount_one(struct mount *mnt, struct list_head *to_umount)
 {
-	struct mount *parent = mnt->mnt_parent;
-	struct mount *m;
-
-	BUG_ON(parent == mnt);
-
-	for (m = propagation_next(parent, parent); m;
-			m = propagation_next(m, parent)) {
-		struct mount *child = __lookup_mnt(&m->mnt,
-						mnt->mnt_mountpoint);
-		if (!child || (child->mnt.mnt_flags & MNT_UMOUNT))
-			continue;
-		if (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m)) {
-			SET_MNT_MARK(child);
-		}
-	}
+	CLEAR_MNT_MARK(mnt);
+	mnt->mnt.mnt_flags |= MNT_UMOUNT;
+	list_del_init(&mnt->mnt_child);
+	list_del_init(&mnt->mnt_umounting);
+	list_move_tail(&mnt->mnt_list, to_umount);
 }
 
 /*
  * NOTE: unmounting 'mnt' naturally propagates to all other mounts its
  * parent propagates to.
  */
-static void __propagate_umount(struct mount *mnt, struct list_head *to_reparent)
+static bool __propagate_umount(struct mount *mnt,
+			       struct list_head *to_umount,
+			       struct list_head *to_restore)
 {
-	struct mount *parent = mnt->mnt_parent;
-	struct mount *m;
+	bool progress = false;
+	struct mount *child;
 
-	BUG_ON(parent == mnt);
+	/*
+	 * The state of the parent won't change if this mount is
+	 * already unmounted or marked as without children.
+	 */
+	if (mnt->mnt.mnt_flags & (MNT_UMOUNT | MNT_MARKED))
+		goto out;
 
-	for (m = propagation_next(parent, parent); m;
-			m = propagation_next(m, parent)) {
-		struct mount *topper;
-		struct mount *child = __lookup_mnt(&m->mnt,
-						mnt->mnt_mountpoint);
-		/*
-		 * umount the child only if the child has no children
-		 * and the child is marked safe to unmount.
-		 */
-		if (!child || !IS_MNT_MARKED(child))
+	/* Verify topper is the only grandchild that has not been
+	 * speculatively unmounted.
+	 */
+	list_for_each_entry(child, &mnt->mnt_mounts, mnt_child) {
+		if (child->mnt_mountpoint == mnt->mnt.mnt_root)
 			continue;
-		CLEAR_MNT_MARK(child);
+		if (!list_empty(&child->mnt_umounting) && IS_MNT_MARKED(child))
+			continue;
+		/* Found a mounted child */
+		goto children;
+	}
 
-		/* If there is exactly one mount covering all of child
-		 * replace child with that mount.
-		 */
-		topper = find_topper(child);
-		if (topper)
-			list_add_tail(&topper->mnt_reparent, to_reparent);
+	/* Mark mounts that can be unmounted if not locked */
+	SET_MNT_MARK(mnt);
+	progress = true;
 
-		if (topper || list_empty(&child->mnt_mounts)) {
-			list_del_init(&child->mnt_child);
-			list_del_init(&child->mnt_reparent);
-			child->mnt.mnt_flags |= MNT_UMOUNT;
-			list_move_tail(&child->mnt_list, &mnt->mnt_list);
+	/* If a mount is without children and not locked umount it. */
+	if (!IS_MNT_LOCKED(mnt)) {
+		umount_one(mnt, to_umount);
+	} else {
+children:
+		list_move_tail(&mnt->mnt_umounting, to_restore);
+	}
+out:
+	return progress;
+}
+
+static void umount_list(struct list_head *to_umount,
+			struct list_head *to_restore)
+{
+	struct mount *mnt, *child, *tmp;
+	list_for_each_entry(mnt, to_umount, mnt_list) {
+		list_for_each_entry_safe(child, tmp, &mnt->mnt_mounts, mnt_child) {
+			/* topper? */
+			if (child->mnt_mountpoint == mnt->mnt.mnt_root)
+				list_move_tail(&child->mnt_umounting, to_restore);
+			else
+				umount_one(child, to_umount);
 		}
 	}
 }
 
-static void reparent_mounts(struct list_head *to_reparent)
+static void restore_mounts(struct list_head *to_restore)
 {
-	while (!list_empty(to_reparent)) {
+	/* Restore mounts to a clean working state */
+	while (!list_empty(to_restore)) {
 		struct mount *mnt, *parent;
 		struct mountpoint *mp;
 
-		mnt = list_first_entry(to_reparent, struct mount, mnt_reparent);
-		list_del_init(&mnt->mnt_reparent);
+		mnt = list_first_entry(to_restore, struct mount, mnt_umounting);
+		CLEAR_MNT_MARK(mnt);
+		list_del_init(&mnt->mnt_umounting);
 
-		/* Where should this mount be reparented to? */
+		/* Should this mount be reparented? */
 		mp = mnt->mnt_mp;
 		parent = mnt->mnt_parent;
 		while (parent->mnt.mnt_flags & MNT_UMOUNT) {
 			mp = parent->mnt_mp;
 			parent = parent->mnt_parent;
 		}
-
-		mnt_change_mountpoint(parent, mp, mnt);
+		if (parent != mnt->mnt_parent)
+			mnt_change_mountpoint(parent, mp, mnt);
 	}
 }
 
@@ -506,15 +515,34 @@ static void reparent_mounts(struct list_head *to_reparent)
 int propagate_umount(struct list_head *list)
 {
 	struct mount *mnt;
-	LIST_HEAD(to_reparent);
+	LIST_HEAD(to_restore);
+	LIST_HEAD(to_umount);
 
-	list_for_each_entry_reverse(mnt, list, mnt_list)
-		mark_umount_candidates(mnt);
+	list_for_each_entry(mnt, list, mnt_list) {
+		struct mount *parent = mnt->mnt_parent;
+		struct mount *m;
 
-	list_for_each_entry(mnt, list, mnt_list)
-		__propagate_umount(mnt, &to_reparent);
+		for (m = propagation_next(parent, parent); m;
+		     m = propagation_next(m, parent)) {
+			struct mount *child = __lookup_mnt(&m->mnt,
+							   mnt->mnt_mountpoint);
+			if (!child)
+				continue;
+
+			/* Check the child and parents while progress is made */
+			while (__propagate_umount(child,
+						  &to_umount, &to_restore)) {
+				/* Is the parent a umount candidate? */
+				child = child->mnt_parent;
+				if (list_empty(&child->mnt_umounting))
+					break;
+			}
+		}
+	}
 
-	reparent_mounts(&to_reparent);
+	umount_list(&to_umount, &to_restore);
+	restore_mounts(&to_restore);
+	list_splice_tail(&to_umount, list);
 
 	return 0;
 }
Signature · 1 line
2.10.1
Signature · 1 line
Ram Pai
#55 lore raw

Re: [REVIEW][PATCH 1/2] mnt: In propgate_umount handle visiting mounts in any order

Quoted 66 lines · Ram Pai
Quoted 44 lines · Eric W. Biederman

While investigating some poor umount performance I realized that in the case of overlapping mount trees where some of the mounts are locked the code has been failing to unmount all of the mounts it should have been unmounting.

This failure to unmount all of the necessary
mounts can be reproduced with:

$ cat locked_mounts_test.sh

mount -t tmpfs test-base /mnt
mount --make-shared /mnt
mkdir -p /mnt/b

mount -t tmpfs test1 /mnt/b
mount --make-shared /mnt/b
mkdir -p /mnt/b/10

mount -t tmpfs test2 /mnt/b/10
mount --make-shared /mnt/b/10
mkdir -p /mnt/b/10/20

mount --rbind /mnt/b /mnt/b/10/20

unshare -Urm --propagation unchaged /bin/sh -c 'sleep 5; if [ $(grep test /proc/self/mountinfo | wc -l) -eq 1 ] ; then echo SUCCESS ; else echo FAILURE ; fi' sleep 1
umount -l /mnt/b
wait %%

$ unshare -Urm ./locked_mounts_test.sh

This failure is corrected by removing the prepass that marks mounts that may be umounted.

A first pass is added that umounts mounts if possible and if not sets mount mark if they could be unmounted if they weren't locked and adds them to a list to umount possibilities. This first pass reconsiders the mounts parent if it is on the list of umount possibilities, ensuring that information of umoutability will pass from child to mount parent.

A second pass then walks through all mounts that are umounted and processes their children unmounting them or marking them for reparenting.

Quoted 8 lines

A last pass cleans up the state on the mounts that could not be umounted and if applicable reparents them to their first parent that remained mounted.

While a bit longer than the old code this code is much more robust as it allows information to flow up from the leaves and down from the trunk making the order in which mounts are encountered in the umount propgation tree irrelevant.

Eric,

I tried multiple time to understand the algorithm, but failed to understand the reasoning behind each of the steps. Hence I can't tell if the algorithm is correct or wrong.

I know you are trying to optimize the current algorithm, but what is the key insight that you are trying to leverage to optimize it? That probably might help me analyze the algorithm.

Quoted 4 lines

You walk the propogation tree, and for each element in the propagation-tree you try to unmount its entire mount-tree. (not sure if this operation is correct, since I know, I had
given an example in the past where this can go wrong).

I think you are refering to when I tried to propgate the entire tree mount and was not following the individual propagation trees for each mount. Which left some mounts mounted that if we had followed the individual propgation trees would have been umounted.

This code does not do anything like that it simply follows the individual umount propgation trees.

Quoted 5 lines

And later if you find that the unmount is successful, you try to walk up and see if the parent can also be unmounted(dont know why this is needed).

Sorry, but if you can help with some key insights, it will help.

The first insight is that the parent child relationship that happens between mounts in the set of mounts removed by MNT_DETACH is not necessarily the same parent child relationship between the mounts the are propagated to.

Which leads to the second insight that we can not guarantee that during umount when the mount propgation tree is being walked we can not gaurantee that the leaves are being walked first.

Without tucked mounts, without locked mounts that information is enough to say the original mount propgation code for umount was incorrect as it assumed that the leaves would be walked first.

I believe the test case I have given above is an example of that. It has locked mounts in it as well which made everything doubly ugly.

To handle the case that the code may visit the parent before the child if something is mounted on top of a mount we wish to unmount it is added to the to_restore list instead of the to_umount list.

If a mount does not have children and it is unmounted __propgate_umount returns true. The code then looks at the parent mount and it is on the to_restore list it tries to unmount the parent again.

That is the leaf to root propagation.

There is also root to leaf propgation in umount_list to handle the case of locked mounts. Locked mounts come about because a more privileged user propagated them into your mount namespace as a set, and the unprivileged user is not allowed to break up the set lest they see something under a mount they should not see.

When a mount that could be unmounted if it was not locked to it's parent it is marked and placed on the to_restore list.

When examining children umount_one removes mounts from their parents mnt_mounts list.

Children that fully cover a mount (toppers) are ignored. Children that if not locked would be unmounted are ignored
as those children become unmountable if their parent is unmountable.

This allows a tree of mounts that is locked together to be unmounted if the root is unmountable.

Does that help?

Eric

Quoted 233 lines

RP

Quoted 231 lines
Fixes 0c56fe31420c ("mnt: Don't propagate unmounts to locked mounts")
---
 fs/mount.h     |   2 +-
 fs/namespace.c |   2 +-
 fs/pnode.c     | 144 ++++++++++++++++++++++++++++++++++-----------------------
 3 files changed, 88 insertions(+), 60 deletions(-)
fs/mount.h +1 -1
index ede5a1d5cf99..de45d9e76748 100644
@@ -58,7 +58,7 @@ struct mount {
 	struct mnt_namespace *mnt_ns;	/* containing namespace */
 	struct mountpoint *mnt_mp;	/* where is it mounted */
 	struct hlist_node mnt_mp_list;	/* list mounts with the same mountpoint */
-	struct list_head mnt_reparent;	/* reparent list entry */
+	struct list_head mnt_umounting; /* list entry for umount propagation */
 #ifdef CONFIG_FSNOTIFY
 	struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks;
 	__u32 mnt_fsnotify_mask;
fs/namespace.c +1 -1
index 51e49866e1fe..5e3dcbeb1de5 100644
@@ -236,7 +236,7 @@ static struct mount *alloc_vfsmnt(const char *name)
 		INIT_LIST_HEAD(&mnt->mnt_slave_list);
 		INIT_LIST_HEAD(&mnt->mnt_slave);
 		INIT_HLIST_NODE(&mnt->mnt_mp_list);
-		INIT_LIST_HEAD(&mnt->mnt_reparent);
+		INIT_LIST_HEAD(&mnt->mnt_umounting);
 		init_fs_pin(&mnt->mnt_umount, drop_mountpoint);
 	}
 	return mnt;
fs/pnode.c +86 -58
index 52aca0a118ff..fbaca7df2eb0 100644
@@ -413,86 +413,95 @@ void propagate_mount_unlock(struct mount *mnt)
 	}
 }
 
-/*
- * Mark all mounts that the MNT_LOCKED logic will allow to be unmounted.
- */
-static void mark_umount_candidates(struct mount *mnt)
+static void umount_one(struct mount *mnt, struct list_head *to_umount)
 {
-	struct mount *parent = mnt->mnt_parent;
-	struct mount *m;
-
-	BUG_ON(parent == mnt);
-
-	for (m = propagation_next(parent, parent); m;
-			m = propagation_next(m, parent)) {
-		struct mount *child = __lookup_mnt(&m->mnt,
-						mnt->mnt_mountpoint);
-		if (!child || (child->mnt.mnt_flags & MNT_UMOUNT))
-			continue;
-		if (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m)) {
-			SET_MNT_MARK(child);
-		}
-	}
+	CLEAR_MNT_MARK(mnt);
+	mnt->mnt.mnt_flags |= MNT_UMOUNT;
+	list_del_init(&mnt->mnt_child);
+	list_del_init(&mnt->mnt_umounting);
+	list_move_tail(&mnt->mnt_list, to_umount);
 }
 
 /*
  * NOTE: unmounting 'mnt' naturally propagates to all other mounts its
  * parent propagates to.
  */
-static void __propagate_umount(struct mount *mnt, struct list_head *to_reparent)
+static bool __propagate_umount(struct mount *mnt,
+			       struct list_head *to_umount,
+			       struct list_head *to_restore)
 {
-	struct mount *parent = mnt->mnt_parent;
-	struct mount *m;
+	bool progress = false;
+	struct mount *child;
 
-	BUG_ON(parent == mnt);
+	/*
+	 * The state of the parent won't change if this mount is
+	 * already unmounted or marked as without children.
+	 */
+	if (mnt->mnt.mnt_flags & (MNT_UMOUNT | MNT_MARKED))
+		goto out;
 
-	for (m = propagation_next(parent, parent); m;
-			m = propagation_next(m, parent)) {
-		struct mount *topper;
-		struct mount *child = __lookup_mnt(&m->mnt,
-						mnt->mnt_mountpoint);
-		/*
-		 * umount the child only if the child has no children
-		 * and the child is marked safe to unmount.
-		 */
-		if (!child || !IS_MNT_MARKED(child))
+	/* Verify topper is the only grandchild that has not been
+	 * speculatively unmounted.
+	 */
+	list_for_each_entry(child, &mnt->mnt_mounts, mnt_child) {
+		if (child->mnt_mountpoint == mnt->mnt.mnt_root)
 			continue;
-		CLEAR_MNT_MARK(child);
+		if (!list_empty(&child->mnt_umounting) && IS_MNT_MARKED(child))
+			continue;
+		/* Found a mounted child */
+		goto children;
+	}
 
-		/* If there is exactly one mount covering all of child
-		 * replace child with that mount.
-		 */
-		topper = find_topper(child);
-		if (topper)
-			list_add_tail(&topper->mnt_reparent, to_reparent);
+	/* Mark mounts that can be unmounted if not locked */
+	SET_MNT_MARK(mnt);
+	progress = true;
 
-		if (topper || list_empty(&child->mnt_mounts)) {
-			list_del_init(&child->mnt_child);
-			list_del_init(&child->mnt_reparent);
-			child->mnt.mnt_flags |= MNT_UMOUNT;
-			list_move_tail(&child->mnt_list, &mnt->mnt_list);
+	/* If a mount is without children and not locked umount it. */
+	if (!IS_MNT_LOCKED(mnt)) {
+		umount_one(mnt, to_umount);
+	} else {
+children:
+		list_move_tail(&mnt->mnt_umounting, to_restore);
+	}
+out:
+	return progress;
+}
+
+static void umount_list(struct list_head *to_umount,
+			struct list_head *to_restore)
+{
+	struct mount *mnt, *child, *tmp;
+	list_for_each_entry(mnt, to_umount, mnt_list) {
+		list_for_each_entry_safe(child, tmp, &mnt->mnt_mounts, mnt_child) {
+			/* topper? */
+			if (child->mnt_mountpoint == mnt->mnt.mnt_root)
+				list_move_tail(&child->mnt_umounting, to_restore);
+			else
+				umount_one(child, to_umount);
 		}
 	}
 }
 
-static void reparent_mounts(struct list_head *to_reparent)
+static void restore_mounts(struct list_head *to_restore)
 {
-	while (!list_empty(to_reparent)) {
+	/* Restore mounts to a clean working state */
+	while (!list_empty(to_restore)) {
 		struct mount *mnt, *parent;
 		struct mountpoint *mp;
 
-		mnt = list_first_entry(to_reparent, struct mount, mnt_reparent);
-		list_del_init(&mnt->mnt_reparent);
+		mnt = list_first_entry(to_restore, struct mount, mnt_umounting);
+		CLEAR_MNT_MARK(mnt);
+		list_del_init(&mnt->mnt_umounting);
 
-		/* Where should this mount be reparented to? */
+		/* Should this mount be reparented? */
 		mp = mnt->mnt_mp;
 		parent = mnt->mnt_parent;
 		while (parent->mnt.mnt_flags & MNT_UMOUNT) {
 			mp = parent->mnt_mp;
 			parent = parent->mnt_parent;
 		}
-
-		mnt_change_mountpoint(parent, mp, mnt);
+		if (parent != mnt->mnt_parent)
+			mnt_change_mountpoint(parent, mp, mnt);
 	}
 }
 
@@ -506,15 +515,34 @@ static void reparent_mounts(struct list_head *to_reparent)
 int propagate_umount(struct list_head *list)
 {
 	struct mount *mnt;
-	LIST_HEAD(to_reparent);
+	LIST_HEAD(to_restore);
+	LIST_HEAD(to_umount);
 
-	list_for_each_entry_reverse(mnt, list, mnt_list)
-		mark_umount_candidates(mnt);
+	list_for_each_entry(mnt, list, mnt_list) {
+		struct mount *parent = mnt->mnt_parent;
+		struct mount *m;
 
-	list_for_each_entry(mnt, list, mnt_list)
-		__propagate_umount(mnt, &to_reparent);
+		for (m = propagation_next(parent, parent); m;
+		     m = propagation_next(m, parent)) {
+			struct mount *child = __lookup_mnt(&m->mnt,
+							   mnt->mnt_mountpoint);
+			if (!child)
+				continue;
+
+			/* Check the child and parents while progress is made */
+			while (__propagate_umount(child,
+						  &to_umount, &to_restore)) {
+				/* Is the parent a umount candidate? */
+				child = child->mnt_parent;
+				if (list_empty(&child->mnt_umounting))
+					break;
+			}
+		}
+	}
 
-	reparent_mounts(&to_reparent);
+	umount_list(&to_umount, &to_restore);
+	restore_mounts(&to_restore);
+	list_splice_tail(&to_umount, list);
 
 	return 0;
 }
Signature · 1 line
2.10.1
#56 lore raw

Re: [REVIEW][PATCH 1/2] mnt: In propgate_umount handle visiting mounts in any order

Quoted 87 lines · Eric W. Biederman
Quoted 66 lines · Ram Pai
Quoted 44 lines · Eric W. Biederman

While investigating some poor umount performance I realized that in the case of overlapping mount trees where some of the mounts are locked the code has been failing to unmount all of the mounts it should have been unmounting.

This failure to unmount all of the necessary
mounts can be reproduced with:

$ cat locked_mounts_test.sh

mount -t tmpfs test-base /mnt
mount --make-shared /mnt
mkdir -p /mnt/b

mount -t tmpfs test1 /mnt/b
mount --make-shared /mnt/b
mkdir -p /mnt/b/10

mount -t tmpfs test2 /mnt/b/10
mount --make-shared /mnt/b/10
mkdir -p /mnt/b/10/20

mount --rbind /mnt/b /mnt/b/10/20

unshare -Urm --propagation unchaged /bin/sh -c 'sleep 5; if [ $(grep test /proc/self/mountinfo | wc -l) -eq 1 ] ; then echo SUCCESS ; else echo FAILURE ; fi' sleep 1
umount -l /mnt/b
wait %%

$ unshare -Urm ./locked_mounts_test.sh

This failure is corrected by removing the prepass that marks mounts that may be umounted.

A first pass is added that umounts mounts if possible and if not sets mount mark if they could be unmounted if they weren't locked and adds them to a list to umount possibilities. This first pass reconsiders the mounts parent if it is on the list of umount possibilities, ensuring that information of umoutability will pass from child to mount parent.

A second pass then walks through all mounts that are umounted and processes their children unmounting them or marking them for reparenting.

Quoted 8 lines

A last pass cleans up the state on the mounts that could not be umounted and if applicable reparents them to their first parent that remained mounted.

While a bit longer than the old code this code is much more robust as it allows information to flow up from the leaves and down from the trunk making the order in which mounts are encountered in the umount propgation tree irrelevant.

Eric,

I tried multiple time to understand the algorithm, but failed to understand the reasoning behind each of the steps. Hence I can't tell if the algorithm is correct or wrong.

I know you are trying to optimize the current algorithm, but what is the key insight that you are trying to leverage to optimize it? That probably might help me analyze the algorithm.

Quoted 4 lines

You walk the propogation tree, and for each element in the propagation-tree you try to unmount its entire mount-tree. (not sure if this operation is correct, since I know, I had
given an example in the past where this can go wrong).

I think you are refering to when I tried to propgate the entire tree mount and was not following the individual propagation trees for each mount. Which left some mounts mounted that if we had followed the individual propgation trees would have been umounted.

This code does not do anything like that it simply follows the individual umount propgation trees.

Quoted 5 lines

And later if you find that the unmount is successful, you try to walk up and see if the parent can also be unmounted(dont know why this is needed).

Sorry, but if you can help with some key insights, it will help.

Quoted 9 lines

The first insight is that the parent child relationship that happens between mounts in the set of mounts removed by MNT_DETACH is not necessarily the same parent child relationship between the mounts the are propagated to.

Which leads to the second insight that we can not guarantee that during umount when the mount propgation tree is being walked we can not gaurantee that the leaves are being walked first.

If you agree that " (A) tucked mounts do/should NOT receive propagation events on its root dentry ", than i strongly think; short of asserting, that the propagation tree will always reach the child first and not the parent. I will further verify my thoughts before elevating my strong-thinking to an assertion.

But if you do not agree with (A) than yes we could reach the parent or the child first and we will badly need your algorithm.

BTW: This is the same disagreement that we had earlier regarding the semantics of tucked mounts.

Assuming that you may not agree with (A), I looked through your explaination and the code/algorithm and the steps make sense.

The one thing that was not clear to me was -- is it ok to umount child whose ancestor is MNT_LOCKED? Your algorithm seems to allow that.

Quoted 29 lines

Without tucked mounts, without locked mounts that information is enough to say the original mount propgation code for umount was incorrect as it assumed that the leaves would be walked first.

I believe the test case I have given above is an example of that. It has locked mounts in it as well which made everything doubly ugly.

To handle the case that the code may visit the parent before the child if something is mounted on top of a mount we wish to unmount it is added to the to_restore list instead of the to_umount list.

If a mount does not have children and it is unmounted __propgate_umount returns true. The code then looks at the parent mount and it is on the to_restore list it tries to unmount the parent again.

That is the leaf to root propagation.

There is also root to leaf propgation in umount_list to handle the case of locked mounts. Locked mounts come about because a more privileged user propagated them into your mount namespace as a set, and the unprivileged user is not allowed to break up the set lest they see something under a mount they should not see.

When a mount that could be unmounted if it was not locked to it's parent it is marked and placed on the to_restore list.

Quoted 7 lines

When examining children umount_one removes mounts from their parents mnt_mounts list.

Children that fully cover a mount (toppers) are ignored. Children that if not locked would be unmounted are ignored
as those children become unmountable if their parent is unmountable.

I find the children can get unmounted even when one its ancestor is locked. An example case is -- if the child is a leaf and one of its ancestor is locked, but is not part of any related propagation tree.

Quoted 6 lines

This allows a tree of mounts that is locked together to be unmounted if the root is unmountable.

Does that help?

Yes your explaination helped,

Sorry it took some time to get to this,
RP

Quoted 233 lines

Eric

Quoted 231 lines
Quoted 231 lines
Fixes 0c56fe31420c ("mnt: Don't propagate unmounts to locked mounts")
---
 fs/mount.h     |   2 +-
 fs/namespace.c |   2 +-
 fs/pnode.c     | 144 ++++++++++++++++++++++++++++++++++-----------------------
 3 files changed, 88 insertions(+), 60 deletions(-)
fs/mount.h +1 -1
index ede5a1d5cf99..de45d9e76748 100644
@@ -58,7 +58,7 @@ struct mount {
 	struct mnt_namespace *mnt_ns;	/* containing namespace */
 	struct mountpoint *mnt_mp;	/* where is it mounted */
 	struct hlist_node mnt_mp_list;	/* list mounts with the same mountpoint */
-	struct list_head mnt_reparent;	/* reparent list entry */
+	struct list_head mnt_umounting; /* list entry for umount propagation */
 #ifdef CONFIG_FSNOTIFY
 	struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks;
 	__u32 mnt_fsnotify_mask;
fs/namespace.c +1 -1
index 51e49866e1fe..5e3dcbeb1de5 100644
@@ -236,7 +236,7 @@ static struct mount *alloc_vfsmnt(const char *name)
 		INIT_LIST_HEAD(&mnt->mnt_slave_list);
 		INIT_LIST_HEAD(&mnt->mnt_slave);
 		INIT_HLIST_NODE(&mnt->mnt_mp_list);
-		INIT_LIST_HEAD(&mnt->mnt_reparent);
+		INIT_LIST_HEAD(&mnt->mnt_umounting);
 		init_fs_pin(&mnt->mnt_umount, drop_mountpoint);
 	}
 	return mnt;
fs/pnode.c +86 -58
index 52aca0a118ff..fbaca7df2eb0 100644
@@ -413,86 +413,95 @@ void propagate_mount_unlock(struct mount *mnt)
 	}
 }
 
-/*
- * Mark all mounts that the MNT_LOCKED logic will allow to be unmounted.
- */
-static void mark_umount_candidates(struct mount *mnt)
+static void umount_one(struct mount *mnt, struct list_head *to_umount)
 {
-	struct mount *parent = mnt->mnt_parent;
-	struct mount *m;
-
-	BUG_ON(parent == mnt);
-
-	for (m = propagation_next(parent, parent); m;
-			m = propagation_next(m, parent)) {
-		struct mount *child = __lookup_mnt(&m->mnt,
-						mnt->mnt_mountpoint);
-		if (!child || (child->mnt.mnt_flags & MNT_UMOUNT))
-			continue;
-		if (!IS_MNT_LOCKED(child) || IS_MNT_MARKED(m)) {
-			SET_MNT_MARK(child);
-		}
-	}
+	CLEAR_MNT_MARK(mnt);
+	mnt->mnt.mnt_flags |= MNT_UMOUNT;
+	list_del_init(&mnt->mnt_child);
+	list_del_init(&mnt->mnt_umounting);
+	list_move_tail(&mnt->mnt_list, to_umount);
 }
 
 /*
  * NOTE: unmounting 'mnt' naturally propagates to all other mounts its
  * parent propagates to.
  */
-static void __propagate_umount(struct mount *mnt, struct list_head *to_reparent)
+static bool __propagate_umount(struct mount *mnt,
+			       struct list_head *to_umount,
+			       struct list_head *to_restore)
 {
-	struct mount *parent = mnt->mnt_parent;
-	struct mount *m;
+	bool progress = false;
+	struct mount *child;
 
-	BUG_ON(parent == mnt);
+	/*
+	 * The state of the parent won't change if this mount is
+	 * already unmounted or marked as without children.
+	 */
+	if (mnt->mnt.mnt_flags & (MNT_UMOUNT | MNT_MARKED))
+		goto out;
 
-	for (m = propagation_next(parent, parent); m;
-			m = propagation_next(m, parent)) {
-		struct mount *topper;
-		struct mount *child = __lookup_mnt(&m->mnt,
-						mnt->mnt_mountpoint);
-		/*
-		 * umount the child only if the child has no children
-		 * and the child is marked safe to unmount.
-		 */
-		if (!child || !IS_MNT_MARKED(child))
+	/* Verify topper is the only grandchild that has not been
+	 * speculatively unmounted.
+	 */
+	list_for_each_entry(child, &mnt->mnt_mounts, mnt_child) {
+		if (child->mnt_mountpoint == mnt->mnt.mnt_root)
 			continue;
-		CLEAR_MNT_MARK(child);
+		if (!list_empty(&child->mnt_umounting) && IS_MNT_MARKED(child))
+			continue;
+		/* Found a mounted child */
+		goto children;
+	}
 
-		/* If there is exactly one mount covering all of child
-		 * replace child with that mount.
-		 */
-		topper = find_topper(child);
-		if (topper)
-			list_add_tail(&topper->mnt_reparent, to_reparent);
+	/* Mark mounts that can be unmounted if not locked */
+	SET_MNT_MARK(mnt);
+	progress = true;
 
-		if (topper || list_empty(&child->mnt_mounts)) {
-			list_del_init(&child->mnt_child);
-			list_del_init(&child->mnt_reparent);
-			child->mnt.mnt_flags |= MNT_UMOUNT;
-			list_move_tail(&child->mnt_list, &mnt->mnt_list);
+	/* If a mount is without children and not locked umount it. */
+	if (!IS_MNT_LOCKED(mnt)) {
+		umount_one(mnt, to_umount);
+	} else {
+children:
+		list_move_tail(&mnt->mnt_umounting, to_restore);
+	}
+out:
+	return progress;
+}
+
+static void umount_list(struct list_head *to_umount,
+			struct list_head *to_restore)
+{
+	struct mount *mnt, *child, *tmp;
+	list_for_each_entry(mnt, to_umount, mnt_list) {
+		list_for_each_entry_safe(child, tmp, &mnt->mnt_mounts, mnt_child) {
+			/* topper? */
+			if (child->mnt_mountpoint == mnt->mnt.mnt_root)
+				list_move_tail(&child->mnt_umounting, to_restore);
+			else
+				umount_one(child, to_umount);
 		}
 	}
 }
 
-static void reparent_mounts(struct list_head *to_reparent)
+static void restore_mounts(struct list_head *to_restore)
 {
-	while (!list_empty(to_reparent)) {
+	/* Restore mounts to a clean working state */
+	while (!list_empty(to_restore)) {
 		struct mount *mnt, *parent;
 		struct mountpoint *mp;
 
-		mnt = list_first_entry(to_reparent, struct mount, mnt_reparent);
-		list_del_init(&mnt->mnt_reparent);
+		mnt = list_first_entry(to_restore, struct mount, mnt_umounting);
+		CLEAR_MNT_MARK(mnt);
+		list_del_init(&mnt->mnt_umounting);
 
-		/* Where should this mount be reparented to? */
+		/* Should this mount be reparented? */
 		mp = mnt->mnt_mp;
 		parent = mnt->mnt_parent;
 		while (parent->mnt.mnt_flags & MNT_UMOUNT) {
 			mp = parent->mnt_mp;
 			parent = parent->mnt_parent;
 		}
-
-		mnt_change_mountpoint(parent, mp, mnt);
+		if (parent != mnt->mnt_parent)
+			mnt_change_mountpoint(parent, mp, mnt);
 	}
 }
 
@@ -506,15 +515,34 @@ static void reparent_mounts(struct list_head *to_reparent)
 int propagate_umount(struct list_head *list)
 {
 	struct mount *mnt;
-	LIST_HEAD(to_reparent);
+	LIST_HEAD(to_restore);
+	LIST_HEAD(to_umount);
 
-	list_for_each_entry_reverse(mnt, list, mnt_list)
-		mark_umount_candidates(mnt);
+	list_for_each_entry(mnt, list, mnt_list) {
+		struct mount *parent = mnt->mnt_parent;
+		struct mount *m;
 
-	list_for_each_entry(mnt, list, mnt_list)
-		__propagate_umount(mnt, &to_reparent);
+		for (m = propagation_next(parent, parent); m;
+		     m = propagation_next(m, parent)) {
+			struct mount *child = __lookup_mnt(&m->mnt,
+							   mnt->mnt_mountpoint);
+			if (!child)
+				continue;
+
+			/* Check the child and parents while progress is made */
+			while (__propagate_umount(child,
+						  &to_umount, &to_restore)) {
+				/* Is the parent a umount candidate? */
+				child = child->mnt_parent;
+				if (list_empty(&child->mnt_umounting))
+					break;
+			}
+		}
+	}
 
-	reparent_mounts(&to_reparent);
+	umount_list(&to_umount, &to_restore);
+	restore_mounts(&to_restore);
+	list_splice_tail(&to_umount, list);
 
 	return 0;
 }
Signature · 1 line
2.10.1
Signature · 1 line
Ram Pai
#57 lore raw

Re: [REVIEW][PATCH 1/2] mnt: In propgate_umount handle visiting mounts in any order

Quoted 98 lines · Ram Pai
Quoted 87 lines · Eric W. Biederman
Quoted 66 lines · Ram Pai
Quoted 44 lines · Eric W. Biederman

While investigating some poor umount performance I realized that in the case of overlapping mount trees where some of the mounts are locked the code has been failing to unmount all of the mounts it should have been unmounting.

This failure to unmount all of the necessary
mounts can be reproduced with:

$ cat locked_mounts_test.sh

mount -t tmpfs test-base /mnt
mount --make-shared /mnt
mkdir -p /mnt/b

mount -t tmpfs test1 /mnt/b
mount --make-shared /mnt/b
mkdir -p /mnt/b/10

mount -t tmpfs test2 /mnt/b/10
mount --make-shared /mnt/b/10
mkdir -p /mnt/b/10/20

mount --rbind /mnt/b /mnt/b/10/20

unshare -Urm --propagation unchaged /bin/sh -c 'sleep 5; if [ $(grep test /proc/self/mountinfo | wc -l) -eq 1 ] ; then echo SUCCESS ; else echo FAILURE ; fi' sleep 1
umount -l /mnt/b
wait %%

$ unshare -Urm ./locked_mounts_test.sh

This failure is corrected by removing the prepass that marks mounts that may be umounted.

A first pass is added that umounts mounts if possible and if not sets mount mark if they could be unmounted if they weren't locked and adds them to a list to umount possibilities. This first pass reconsiders the mounts parent if it is on the list of umount possibilities, ensuring that information of umoutability will pass from child to mount parent.

A second pass then walks through all mounts that are umounted and processes their children unmounting them or marking them for reparenting.

Quoted 8 lines

A last pass cleans up the state on the mounts that could not be umounted and if applicable reparents them to their first parent that remained mounted.

While a bit longer than the old code this code is much more robust as it allows information to flow up from the leaves and down from the trunk making the order in which mounts are encountered in the umount propgation tree irrelevant.

Eric,

I tried multiple time to understand the algorithm, but failed to understand the reasoning behind each of the steps. Hence I can't tell if the algorithm is correct or wrong.

I know you are trying to optimize the current algorithm, but what is the key insight that you are trying to leverage to optimize it? That probably might help me analyze the algorithm.

Quoted 4 lines

You walk the propogation tree, and for each element in the propagation-tree you try to unmount its entire mount-tree. (not sure if this operation is correct, since I know, I had
given an example in the past where this can go wrong).

I think you are refering to when I tried to propgate the entire tree mount and was not following the individual propagation trees for each mount. Which left some mounts mounted that if we had followed the individual propgation trees would have been umounted.

This code does not do anything like that it simply follows the individual umount propgation trees.

Quoted 5 lines

And later if you find that the unmount is successful, you try to walk up and see if the parent can also be unmounted(dont know why this is needed).

Sorry, but if you can help with some key insights, it will help.

Quoted 9 lines

The first insight is that the parent child relationship that happens between mounts in the set of mounts removed by MNT_DETACH is not necessarily the same parent child relationship between the mounts the are propagated to.

Which leads to the second insight that we can not guarantee that during umount when the mount propgation tree is being walked we can not gaurantee that the leaves are being walked first.

Quoted 5 lines

If you agree that " (A) tucked mounts do/should NOT receive propagation events on its root dentry ", than i strongly think; short of asserting, that the propagation tree will always reach the child first and not the parent. I will further verify my thoughts before elevating my strong-thinking to an assertion.

What led to the implementation of these mounts as tucked mounts rather than side/shadow mounts is Al Viro's assertion that these are normal mounts. As normal mounts I tend to think they should be treated normally.

My experience to date suggests that except for artifically created pathlogical cases the possible ways we can treat tucked mounts does not appear to matter.

So I do not yet agree to (A).

I don't think we have ever actually implemented (A). We have implemented something very similar but the act of untucking a mount resulted in what was at the root of a dentry receving events during the same umount MNT_DETACH instance.

All that is important for the optimizations in patch 2/2 is that resolving the underying mount is a separate and final pass from dealing with mount propgation. So your (A) could be compatible with the code in patch 1/2.

Quoted 2 lines

But if you do not agree with (A) than yes we could reach the parent or the child first and we will badly need your algorithm.

Please note my example does not involve any tucked mounts. I see this issue with a very ordinary set of mounts and mount propagation.

So I think we have rough agreement that my algorithm in patch 1/2 is needed.

Quoted 9 lines

BTW: This is the same disagreement that we had earlier regarding the semantics of tucked mounts.

Assuming that you may not agree with (A), I looked through your explaination and the code/algorithm and the steps make sense.

The one thing that was not clear to me was -- is it ok to umount child whose ancestor is MNT_LOCKED? Your algorithm seems to allow that.

If you have a tree of mounts: With the root of the tree not locked onto it's mountpoint and the rest of the mounts in the tree locked onto the mountpoint. Yes it is ok to unmount the tree. (Just not the individual pieces).

While user space has access to the mounts it is necessary to keep the pieces locked together.

MNT_LOCKED guards against seeing the mountpint or any of the files or directories in the mountpoint.

MNT_LOCKED is a mechanism for dealing with privilege differences between mount namespaces.

Quoted 32 lines
Quoted 32 lines

Without tucked mounts, without locked mounts that information is enough to say the original mount propgation code for umount was incorrect as it assumed that the leaves would be walked first.

I believe the test case I have given above is an example of that. It has locked mounts in it as well which made everything doubly ugly.

To handle the case that the code may visit the parent before the child if something is mounted on top of a mount we wish to unmount it is added to the to_restore list instead of the to_umount list.

If a mount does not have children and it is unmounted __propgate_umount returns true. The code then looks at the parent mount and it is on the to_restore list it tries to unmount the parent again.

That is the leaf to root propagation.

There is also root to leaf propgation in umount_list to handle the case of locked mounts. Locked mounts come about because a more privileged user propagated them into your mount namespace as a set, and the unprivileged user is not allowed to break up the set lest they see something under a mount they should not see.

When a mount that could be unmounted if it was not locked to it's parent it is marked and placed on the to_restore list.

When examining children umount_one removes mounts from their parents mnt_mounts list.

Quoted 8 lines
Quoted 4 lines

Children that fully cover a mount (toppers) are ignored. Children that if not locked would be unmounted are ignored
as those children become unmountable if their parent is unmountable.

I find the children can get unmounted even when one its ancestor is locked. An example case is -- if the child is a leaf and one of its ancestor is locked, but is not part of any related propagation tree.

I am not quite certain what you are asserting. Locking is an operation to hide the contents of a mountpoint. So a leaf that is not locked to it's parent is perfectly fine to unmount.

Quoted 10 lines
Quoted 6 lines

This allows a tree of mounts that is locked together to be unmounted if the root is unmountable.

Does that help?

Yes your explaination helped,

Sorry it took some time to get to this,

Thank you for making the time to get to this and have the conversation. These are tough issues to dig through, and we aren't having the easiest of conversations.

Eric

Reviewed-by on v1 1/1
#58 lore raw

Re: [REVIEW][PATCH] mnt: In umount propagation reparent in a separate pass

In reply to Eric W. Biederman
Quoted 52 lines · Eric W. Biederman

It was observed that in some pathlogical cases that the current code does not unmount everything it should. After investigation it was determined that the issue is that mnt_change_mntpoint can can change which mounts are available to be unmounted during mount propagation which is wrong.

The trivial reproducer is:

$ cat ./pathological.sh

mount -t tmpfs test-base /mnt
cd /mnt
mkdir 1 2 1/1
mount --bind 1 1
mount --make-shared 1
mount --bind 1 2
mount --bind 1/1 1/1
mount --bind 1/1 1/1
echo
grep test-base /proc/self/mountinfo
umount 1/1
echo
grep test-base /proc/self/mountinfo

$ unshare -Urm ./pathological.sh

The expected output looks like:
46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 49 54 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 50 53 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 51 49 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 54 47 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 53 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 52 50 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

The output without the fix looks like:
46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 49 54 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 50 53 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 51 49 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 54 47 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 53 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 52 50 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

Quoted 9 lines

46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 52 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

That last mount in the output was in the propgation tree to be unmounted but was missed because the mnt_change_mountpoint changed it's parent before the walk through the mount propagation tree observed it.

Looks patch correct to me.

BTW: The logic of find_topper() looks not-so-accurate to me. Why dont we explicitly flag tucked mounts with MNT_TUCKED, and use that information to determine if the child is really a topper? Currently we determine the topper if it entirely is covering. How do we diambiguate that from an entirely-covering-mount that is explicitly mounted by the administrator? A topper situation is applicable only when tucked, right?

Quoted 107 lines
Fixes 1064f874abc0 ("mnt: Tuck mounts under others instead of creating shadow/side mounts.")
---
 fs/mount.h     |  1 +
 fs/namespace.c |  1 +
 fs/pnode.c     | 35 ++++++++++++++++++++++++++++++-----
 3 files changed, 32 insertions(+), 5 deletions(-)
fs/mount.h +1 -0
index bf1fda6eed8f..ede5a1d5cf99 100644
@@ -58,6 +58,7 @@ struct mount {
 	struct mnt_namespace *mnt_ns;	/* containing namespace */
 	struct mountpoint *mnt_mp;	/* where is it mounted */
 	struct hlist_node mnt_mp_list;	/* list mounts with the same mountpoint */
+	struct list_head mnt_reparent;	/* reparent list entry */
 #ifdef CONFIG_FSNOTIFY
 	struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks;
 	__u32 mnt_fsnotify_mask;
fs/namespace.c +1 -0
index 8bd3e4d448b9..51e49866e1fe 100644
@@ -236,6 +236,7 @@ static struct mount *alloc_vfsmnt(const char *name)
 		INIT_LIST_HEAD(&mnt->mnt_slave_list);
 		INIT_LIST_HEAD(&mnt->mnt_slave);
 		INIT_HLIST_NODE(&mnt->mnt_mp_list);
+		INIT_LIST_HEAD(&mnt->mnt_reparent);
 		init_fs_pin(&mnt->mnt_umount, drop_mountpoint);
 	}
 	return mnt;
fs/pnode.c +30 -5
index 5bc7896d122a..52aca0a118ff 100644
@@ -439,7 +439,7 @@ static void mark_umount_candidates(struct mount *mnt)
  * NOTE: unmounting 'mnt' naturally propagates to all other mounts its
  * parent propagates to.
  */
-static void __propagate_umount(struct mount *mnt)
+static void __propagate_umount(struct mount *mnt, struct list_head *to_reparent)
 {
 	struct mount *parent = mnt->mnt_parent;
 	struct mount *m;
@@ -464,17 +464,38 @@ static void __propagate_umount(struct mount *mnt)
 		 */
 		topper = find_topper(child);
 		if (topper)
-			mnt_change_mountpoint(child->mnt_parent, child->mnt_mp,
-					      topper);
+			list_add_tail(&topper->mnt_reparent, to_reparent);
 
-		if (list_empty(&child->mnt_mounts)) {
+		if (topper || list_empty(&child->mnt_mounts)) {
 			list_del_init(&child->mnt_child);
+			list_del_init(&child->mnt_reparent);
 			child->mnt.mnt_flags |= MNT_UMOUNT;
 			list_move_tail(&child->mnt_list, &mnt->mnt_list);
 		}
 	}
 }
 
+static void reparent_mounts(struct list_head *to_reparent)
+{
+	while (!list_empty(to_reparent)) {
+		struct mount *mnt, *parent;
+		struct mountpoint *mp;
+
+		mnt = list_first_entry(to_reparent, struct mount, mnt_reparent);
+		list_del_init(&mnt->mnt_reparent);
+
+		/* Where should this mount be reparented to? */
+		mp = mnt->mnt_mp;
+		parent = mnt->mnt_parent;
+		while (parent->mnt.mnt_flags & MNT_UMOUNT) {
+			mp = parent->mnt_mp;
+			parent = parent->mnt_parent;
+		}
+
+		mnt_change_mountpoint(parent, mp, mnt);
+	}
+}
+
 /*
  * collect all mounts that receive propagation from the mount in @list,
  * and return these additional mounts in the same list.
@@ -485,11 +506,15 @@ static void __propagate_umount(struct mount *mnt)
 int propagate_umount(struct list_head *list)
 {
 	struct mount *mnt;
+	LIST_HEAD(to_reparent);
 
 	list_for_each_entry_reverse(mnt, list, mnt_list)
 		mark_umount_candidates(mnt);
 
 	list_for_each_entry(mnt, list, mnt_list)
-		__propagate_umount(mnt);
+		__propagate_umount(mnt, &to_reparent);
+
+	reparent_mounts(&to_reparent);
+
 	return 0;
 }
Signature · 1 line
2.10.1
Signature · 1 line
Ram Pai
#59 lore raw

Re: [REVIEW][PATCH] mnt: In umount propagation reparent in a separate pass

Quoted 64 lines · Ram Pai
Quoted 52 lines · Eric W. Biederman

It was observed that in some pathlogical cases that the current code does not unmount everything it should. After investigation it was determined that the issue is that mnt_change_mntpoint can can change which mounts are available to be unmounted during mount propagation which is wrong.

The trivial reproducer is:

$ cat ./pathological.sh

mount -t tmpfs test-base /mnt
cd /mnt
mkdir 1 2 1/1
mount --bind 1 1
mount --make-shared 1
mount --bind 1 2
mount --bind 1/1 1/1
mount --bind 1/1 1/1
echo
grep test-base /proc/self/mountinfo
umount 1/1
echo
grep test-base /proc/self/mountinfo

$ unshare -Urm ./pathological.sh

The expected output looks like:
46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 49 54 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 50 53 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 51 49 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 54 47 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 53 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 52 50 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

The output without the fix looks like:
46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 49 54 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 50 53 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 51 49 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 54 47 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 53 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 52 50 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

Quoted 9 lines

46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 52 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

That last mount in the output was in the propgation tree to be unmounted but was missed because the mnt_change_mountpoint changed it's parent before the walk through the mount propagation tree observed it.

Looks patch correct to me.

Quoted 8 lines

BTW: The logic of find_topper() looks not-so-accurate to me. Why dont we explicitly flag tucked mounts with MNT_TUCKED, and use that information to determine if the child is really a topper? Currently we determine the topper if it entirely is covering. How do we diambiguate that from an entirely-covering-mount that is explicitly mounted by the administrator? A topper situation is applicable only when tucked, right?

In the current code explictly does not care about the difference. The code just restricts untucking mounts of any kind to umount propagation.

This is where we have previously disagreed.

A short summary of our previous discussions:
Eric Biederman: find_topper makes tucked mounts ordinary mounts and is simple. Eric Biederman: I don't see a compelling case for a MNT_TUCKED flag Eric Biederman: I think the change is a nice behavioral improvement Ram Pai: a MNT_TUCKED flag would perfectly preserve existing behavior Ram Pai: find_topper while not perfect is better than the previous
very special case for side/shadow mounts

With respect to backwards compatibility the set of bugs I am fixing shows that it is possible to have some very egregious bugs in this area and in practice no one cares.

Without a MNT_TUCKED flag I can readily tell what the following code should do by simply inspection of the of the mount propgation information in /proc/self/mountinfo:

$ mount -t tmpfs test-base /mnt
$ cd /mnt
$ mkdir -p 1 2 1/1
$ mount --bind 1 1
$ mount --make-shared 1
$ mount --bind 1 2
$ mount --bind 1/1 1/1
$ mount --bind 1/1 1/1
$ umount 1/1

Before the umount /proc/self/mountinfo shows:
46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=502,gid=502
47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502 49 54 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502 50 53 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502 51 49 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502 54 47 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502 53 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502 52 50 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502

So it is clear to me that umount /mnt/1/1 should just leave:
/mnt
/mnt/1
/mnt/2

I would argue that is what the code should always have done.

I believe the code with a MNT_TUCKED flag would leave:
/mnt
/mnt/1
/mnt/1/1
/mnt/2
But in truth it makes my head hurt to think about it. I don't see that MNT_TUCKED adds anything except aditional code complexity.

I don't actually see what the value is in keeping mounts that you can not use (because they are overmounted) around.

If the scenarios we were talking about were all limited to perfoming a mount and then undoing that mount I could almost see some value in a MNT_TUCKED flag. Given that one of the justications for tucking mounts in the first place is what happens when you umount something on a slave mount I really don't like it. As now I get the question what happens on a slave mount where a mount has been propagated and tucked, and then the topper is unmounted and a new topper is added. Should unmount on the parent untuck the propagated mount or leave it there? It was propagated it was tucked but it wasn't tucked under what is currenty on top.

I much prefer the current semantics where we just say mount propagation can tuck and untuck things, and the history of how the mount tree got into its current shape is not important.

Given how difficult it has been to make this code performant and correct I am not particularly eager to add complexity for unnecessary bug compatibility. But if it creates a breaking regression for something (other than a regression test) I am willing to add MNT_TUCKED.

Eric

#60 lore raw

Re: [REVIEW][PATCH] mnt: In umount propagation reparent in a separate pass

Quoted 89 lines · Eric W. Biederman
Quoted 64 lines · Ram Pai
Quoted 52 lines · Eric W. Biederman

It was observed that in some pathlogical cases that the current code does not unmount everything it should. After investigation it was determined that the issue is that mnt_change_mntpoint can can change which mounts are available to be unmounted during mount propagation which is wrong.

The trivial reproducer is:

$ cat ./pathological.sh

mount -t tmpfs test-base /mnt
cd /mnt
mkdir 1 2 1/1
mount --bind 1 1
mount --make-shared 1
mount --bind 1 2
mount --bind 1/1 1/1
mount --bind 1/1 1/1
echo
grep test-base /proc/self/mountinfo
umount 1/1
echo
grep test-base /proc/self/mountinfo

$ unshare -Urm ./pathological.sh

The expected output looks like:
46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 49 54 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 50 53 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 51 49 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 54 47 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 53 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 52 50 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

The output without the fix looks like:
46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 49 54 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 50 53 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 51 49 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 54 47 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 53 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 52 50 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

Quoted 9 lines

46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 52 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

That last mount in the output was in the propgation tree to be unmounted but was missed because the mnt_change_mountpoint changed it's parent before the walk through the mount propagation tree observed it.

Looks patch correct to me.

Quoted 8 lines

BTW: The logic of find_topper() looks not-so-accurate to me. Why dont we explicitly flag tucked mounts with MNT_TUCKED, and use that information to determine if the child is really a topper? Currently we determine the topper if it entirely is covering. How do we diambiguate that from an entirely-covering-mount that is explicitly mounted by the administrator? A topper situation is applicable only when tucked, right?

In the current code explictly does not care about the difference. The code just restricts untucking mounts of any kind to umount propagation.

This is where we have previously disagreed.

A short summary of our previous discussions:
Eric Biederman: find_topper makes tucked mounts ordinary mounts and is simple. Eric Biederman: I don't see a compelling case for a MNT_TUCKED flag Eric Biederman: I think the change is a nice behavioral improvement Ram Pai: a MNT_TUCKED flag would perfectly preserve existing behavior Ram Pai: find_topper while not perfect is better than the previous
very special case for side/shadow mounts

Quoted 9 lines

With respect to backwards compatibility the set of bugs I am fixing shows that it is possible to have some very egregious bugs in this area and in practice no one cares.

Without a MNT_TUCKED flag I can readily tell what the following code should do by simply inspection of the of the mount propgation information in /proc/self/mountinfo:

Step 1>  $ mount -t tmpfs test-base /mnt
Step 2>  $ cd /mnt
Step 3>  $ mkdir -p 1 2 1/1
Step 4>  $ mount --bind 1 1
Step 5>  $ mount --make-shared 1
Step 6>  $ mount --bind 1 2
Step 7>  $ mount --bind 1/1 1/1
Step 8>  $ mount --bind 1/1 1/1
Step 9>  $ umount 1/1
Quoted 12 lines

Before the umount /proc/self/mountinfo shows:
46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=502,gid=502
47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502 49 54 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502 50 53 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502 51 49 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502 54 47 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502 53 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502 52 50 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502

Quoted 4 lines

So it is clear to me that umount /mnt/1/1 should just leave:
/mnt
/mnt/1
/mnt/2

This is one other place where we disagree.... I expect things to peel back to the state the trees were, when the Step 7 was executed. which is
/mnt
/mnt/1
/mnt/2
/mnt/1/1
/mnt/2/1
And all tucked mounts disapper.

Dont get me wrong. I dont think we will agree because we have different expections. There is no standard on what to expect. Someone authoritative; may be Al Viro, has to define what to expect.

Quoted 9 lines

I would argue that is what the code should always have done.

I believe the code with a MNT_TUCKED flag would leave:
/mnt
/mnt/1
/mnt/1/1
/mnt/2
But in truth it makes my head hurt to think about it.

Yes it is extremely mind-bending; sometimes mind-bleeding. :-(

Quoted 5 lines

I don't see that MNT_TUCKED adds anything except aditional code complexity.

I don't actually see what the value is in keeping mounts that you can not use (because they are overmounted) around.

I argue that MNT_TUCKED leaves markers that can be used to determine what can be taken out and what needs to be kept.

I will stop here and say.. there is value in marking TUCKED mounts. Someone will run into some obscure issue in the future; probably a decade from now, and the same story will repeat.

I wish there was a mathematical formula, where you plugin the operation and a state of the trees, and the new state of the mount-trees emerge.

For now your patches look good to me.
RP

Quoted 22 lines

If the scenarios we were talking about were all limited to perfoming a mount and then undoing that mount I could almost see some value in a MNT_TUCKED flag. Given that one of the justications for tucking mounts in the first place is what happens when you umount something on a slave mount I really don't like it. As now I get the question what happens on a slave mount where a mount has been propagated and tucked, and then the topper is unmounted and a new topper is added. Should unmount on the parent untuck the propagated mount or leave it there? It was propagated it was tucked but it wasn't tucked under what is currenty on top.

I much prefer the current semantics where we just say mount propagation can tuck and untuck things, and the history of how the mount tree got into its current shape is not important.

Given how difficult it has been to make this code performant and correct I am not particularly eager to add complexity for unnecessary bug compatibility. But if it creates a breaking regression for something (other than a regression test) I am willing to add MNT_TUCKED.

Eric

Signature · 1 line
Ram Pai
#61 lore raw

Re: [REVIEW][PATCH] mnt: In umount propagation reparent in a separate pass

Quoted 160 lines · Ram Pai
Quoted 89 lines · Eric W. Biederman
Quoted 64 lines · Ram Pai
Quoted 52 lines · Eric W. Biederman

It was observed that in some pathlogical cases that the current code does not unmount everything it should. After investigation it was determined that the issue is that mnt_change_mntpoint can can change which mounts are available to be unmounted during mount propagation which is wrong.

The trivial reproducer is:

$ cat ./pathological.sh

mount -t tmpfs test-base /mnt
cd /mnt
mkdir 1 2 1/1
mount --bind 1 1
mount --make-shared 1
mount --bind 1 2
mount --bind 1/1 1/1
mount --bind 1/1 1/1
echo
grep test-base /proc/self/mountinfo
umount 1/1
echo
grep test-base /proc/self/mountinfo

$ unshare -Urm ./pathological.sh

The expected output looks like:
46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 49 54 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 50 53 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 51 49 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 54 47 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 53 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 52 50 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

The output without the fix looks like:
46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 49 54 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 50 53 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 51 49 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 54 47 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 53 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 52 50 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

Quoted 9 lines

46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=1000,gid=1000 47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000 52 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=1000,gid=1000

That last mount in the output was in the propgation tree to be unmounted but was missed because the mnt_change_mountpoint changed it's parent before the walk through the mount propagation tree observed it.

Looks patch correct to me.

Quoted 8 lines

BTW: The logic of find_topper() looks not-so-accurate to me. Why dont we explicitly flag tucked mounts with MNT_TUCKED, and use that information to determine if the child is really a topper? Currently we determine the topper if it entirely is covering. How do we diambiguate that from an entirely-covering-mount that is explicitly mounted by the administrator? A topper situation is applicable only when tucked, right?

In the current code explictly does not care about the difference. The code just restricts untucking mounts of any kind to umount propagation.

This is where we have previously disagreed.

A short summary of our previous discussions:
Eric Biederman: find_topper makes tucked mounts ordinary mounts and is simple. Eric Biederman: I don't see a compelling case for a MNT_TUCKED flag Eric Biederman: I think the change is a nice behavioral improvement Ram Pai: a MNT_TUCKED flag would perfectly preserve existing behavior Ram Pai: find_topper while not perfect is better than the previous
very special case for side/shadow mounts

Quoted 9 lines

With respect to backwards compatibility the set of bugs I am fixing shows that it is possible to have some very egregious bugs in this area and in practice no one cares.

Without a MNT_TUCKED flag I can readily tell what the following code should do by simply inspection of the of the mount propgation information in /proc/self/mountinfo:

Step 1>  $ mount -t tmpfs test-base /mnt
Step 2>  $ cd /mnt
Step 3>  $ mkdir -p 1 2 1/1
Step 4>  $ mount --bind 1 1
Step 5>  $ mount --make-shared 1
Step 6>  $ mount --bind 1 2
Step 7>  $ mount --bind 1/1 1/1
Step 8>  $ mount --bind 1/1 1/1
Step 9>  $ umount 1/1
Quoted 12 lines

Before the umount /proc/self/mountinfo shows:
46 31 0:25 / /mnt rw,relatime - tmpfs test-base rw,uid=502,gid=502
47 46 0:25 /1 /mnt/1 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502 48 46 0:25 /1 /mnt/2 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502 49 54 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502 50 53 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502 51 49 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502 54 47 0:25 /1/1 /mnt/1/1 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502 53 48 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502 52 50 0:25 /1/1 /mnt/2/1 rw,relatime shared:1 - tmpfs test-base rw,uid=502,gid=502

Quoted 4 lines

So it is clear to me that umount /mnt/1/1 should just leave:
/mnt
/mnt/1
/mnt/2

This is one other place where we disagree.... I expect things to peel back to the state the trees were, when the Step 7 was executed. which is
/mnt
/mnt/1
/mnt/2
/mnt/1/1
/mnt/2/1
And all tucked mounts disapper.

Dont get me wrong. I dont think we will agree because we have different expections. There is no standard on what to expect. Someone authoritative; may be Al Viro, has to define what to expect.

Quoted 9 lines

I would argue that is what the code should always have done.

I believe the code with a MNT_TUCKED flag would leave:
/mnt
/mnt/1
/mnt/1/1
/mnt/2
But in truth it makes my head hurt to think about it.

Yes it is extremely mind-bending; sometimes mind-bleeding. :-(

Quoted 5 lines

I don't see that MNT_TUCKED adds anything except aditional code complexity.

I don't actually see what the value is in keeping mounts that you can not use (because they are overmounted) around.

I argue that MNT_TUCKED leaves markers that can be used to determine what can be taken out and what needs to be kept.

Quoted 8 lines

I will stop here and say.. there is value in marking TUCKED mounts. Someone will run into some obscure issue in the future; probably a decade from now, and the same story will repeat.

I wish there was a mathematical formula, where you plugin the operation and a state of the trees, and the new state of the mount-trees emerge.

For now your patches look good to me.

Then let me ask you this. Please look at the two successor patches to this. I am confident in them but I also know I am human and may have missed something.

Then on top of those et's look at marking tucked mounts. If we write the code and examine motivating cases where the behavior differs we should be able to make an informed choice.

I say motivating cases as there are use cases with slave mounts that motivated the support of tucking mounts. I figure if we go back through and examine those we can at least see if there are any cases where without marking them we have a practical issue. Or perhaps we will see that for all cases that we can think of that matter there are no differences.

I am motivated to solve this because after fixing the perfomance issues we need find a way for some set of mount namespaces to recreate their
mount propgation tree on a different machine. That is needed for
CRIU and it may be needed for the plan9 case where logging into a remote system you could bring all of your filesystems with you into your own personal mount namespace.

Eric

#62 lore raw

Re: [PATCH] mnt: Protect the mountpoint hashtable with mount_lock

In reply to Eric W. Biederman
Quoted 31 lines · Eric W. Biederman

Protecting the mountpoint hashtable with namespace_sem was sufficient until a call to umount_mnt was added to mntput_no_expire. At which point it became possible for multiple calls of put_mountpoint on the same hash chain to happen on the same time.

Kristen Johansen <[email protected]> reported:

Quoted 8 lines

This can cause a panic when simultaneous callers of put_mountpoint attempt to free the same mountpoint. This occurs because some callers hold the mount_hash_lock, while others hold the namespace lock. Some even hold both.

In this submitter's case, the panic manifested itself as a GP fault in put_mountpoint() when it called hlist_del() and attempted to dereference a m_hash.pprev that had been poisioned by another thread.

Al Viro observed that the simple fix is to switch from using the namespace_sem to the mount_lock to protect the mountpoint hash table.

I have taken Al's suggested patch moved put_mountpoint in pivot_root (instead of taking mount_lock an additional time), and have replaced new_mountpoint with get_mountpoint a function that does the hash table
lookup and addition under the mount_lock. The introduction of get_mounptoint
ensures that only the mount_lock is needed to manipulate the mountpoint hashtable.

d_set_mounted is modified to only set DCACHE_MOUNTED if it is not already set. This allows get_mountpoint to use the setting of DCACHE_MOUNTED to ensure adding a struct mountpoint for a dentry happens exactly once.

Quoted 6 lines
Fixes ce07d891a089 ("mnt: Honor MNT_LOCKED when detaching mounts")

---

Sorry for the slow reply.

This looks right to me. I just pulled in the patch and went through all of the code paths in cscope. Everything is now under the mount_lock, which solves the problem from my perspective. Feel free to put me down as a reviewed-by if my vote counts.`

There's another issue with MNT_LOCKED and detached mounts that I've been investigating. I'd be curious to get your opinion before I write any code. I'll send that out in a separate e-mail, though.

-K