diff --git a/Documentation/filesystems/propagate_umount.txt b/Documentation/filesystems/propagate_umount.txt
index 9a7eb96..de58ce8 100644
--- a/Documentation/filesystems/propagate_umount.txt
+++ b/Documentation/filesystems/propagate_umount.txt
@@ -32,12 +32,12 @@ of set.  They can be reparented to the place where the bottom of
 stack is attached to a mount that will survive.  NOTE: doing that
 will violate a constraint on having no more than one mount with
 the same parent/mountpoint pair; however, the caller (umount_tree())
-will immediately remedy that - it may keep unmounted element attached
-to parent, but only if the parent itself is unmounted.  Since all
-conflicts created by reparenting have common parent *not* in the
-set and one side of the conflict (bottom of the stack of overmounts)
-is in the set, it will be resolved.  However, we rely upon umount_tree()
-doing that pretty much immediately after the call of propagate_umount().
+will immediately remedy that - it detaches every element of the set
+from its parent.  Since all conflicts created by reparenting have
+common parent *not* in the set and one side of the conflict (bottom
+of the stack of overmounts) is in the set, it will be resolved.
+However, we rely upon umount_tree() doing that pretty much immediately
+after the call of propagate_umount().
 
 Algorithm is based on two statements:
 	1) for any set S, there is a maximal non-shifting subset of S
diff --git a/Documentation/filesystems/sharedsubtree.rst b/Documentation/filesystems/sharedsubtree.rst
index 8b7dc91..8bae6d9 100644
--- a/Documentation/filesystems/sharedsubtree.rst
+++ b/Documentation/filesystems/sharedsubtree.rst
@@ -564,8 +564,8 @@ f) Unmount semantics
    where 'A' is a mount mounted on mount 'B' at dentry 'b'.
 
    If mount 'B' is shared, then all most-recently-mounted mounts at dentry
-   'b' on mounts that receive propagation from mount 'B' and does not have
-   sub-mounts within them are unmounted.
+   'b' on mounts that receive propagation from mount 'B' are unmounted as
+   well, if every mount below them is also unmounted.
 
    Example: Let's say 'B1', 'B2', 'B3' are shared mounts that propagate to
    each other.
@@ -584,10 +584,18 @@ f) Unmount semantics
 
    So all 'C1', 'C2' and 'C3' should be unmounted.
 
-   If any of 'C2' or 'C3' has some child mounts, then that mount is not
-   unmounted, but all other mounts are unmounted. However if 'C1' is told
-   to be unmounted and 'C1' has some sub-mounts, the umount operation is
-   failed entirely.
+   If any of 'C2' or 'C3' has a child mount that cannot be unmounted
+   then that mount is not unmounted. But all other mounts are unmounted.
+   A child mount that is itself unmounted by the same unmount
+   propagation does not keep its parent mounted. However if 'C1' is
+   supposed to be unmounted and 'C1' has some sub-mounts, the unmount
+   fails.
+
+   A lazy umount (MNT_DETACH) takes a whole tree. Every mount of the
+   tree then propagates its unmount from its own parent as described
+   above, so the mounts that receive propagation lose the corresponding
+   trees as well. Documentation/filesystems/propagate_umount.txt has the
+   precise rules, including the ones for locked mounts.
 
 g) Clone Namespace
 
diff --git a/fs/dcache.c b/fs/dcache.c
index a66be85..f44c7b3 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -1583,6 +1583,8 @@ EXPORT_SYMBOL(path_has_submounts);
  *
  * Only one of d_invalidate() and d_set_mounted() must succeed.  For
  * this reason take rename_lock and d_lock on dentry and ancestors.
+ * Likewise for dont_mount() which marks a dentry that is being removed
+ * under d_lock.
  */
 int d_set_mounted(struct dentry *dentry)
 {
@@ -1599,7 +1601,7 @@ int d_set_mounted(struct dentry *dentry)
 		spin_unlock(&p->d_lock);
 	}
 	spin_lock(&dentry->d_lock);
-	if (!d_unlinked(dentry)) {
+	if (!d_unlinked(dentry) && !cant_mount(dentry)) {
 		ret = -EBUSY;
 		if (!d_mountpoint(dentry)) {
 			dentry->d_flags |= DCACHE_MOUNTED;
diff --git a/fs/fcntl.c b/fs/fcntl.c
index c158f08..9b71bc9 100644
--- a/fs/fcntl.c
+++ b/fs/fcntl.c
@@ -372,6 +372,9 @@ static long fcntl_set_rw_hint(struct file *file, unsigned long arg)
 	u64 __user *argp = (u64 __user *)arg;
 	u64 hint;
 
+	/* nothing is ever written to it */
+	if (IS_IMMUTABLE(inode))
+		return -EPERM;
 	if (!inode_owner_or_capable(file_mnt_idmap(file), inode))
 		return -EPERM;
 
diff --git a/fs/fhandle.c b/fs/fhandle.c
index f882923..d22f2e0 100644
--- a/fs/fhandle.c
+++ b/fs/fhandle.c
@@ -298,6 +298,22 @@ static bool capable_wrt_mount(struct mount *mount)
 	return mnt_ns && ns_capable(mnt_ns->user_ns, CAP_SYS_ADMIN);
 }
 
+/*
+ * Does the caller have an unobstructed way to everything below @root? Only
+ * if the mount is mounted, the caller is privileged over its mount namespace
+ * and no locked child covers something below @root->dentry. One answer from
+ * under mount_lock: an umount in between clears ->mnt_ns and takes the
+ * children off the mount, the locked ones too.
+ */
+static bool subtree_unobstructed(const struct path *root)
+{
+	struct mount *mnt = real_mount(root->mnt);
+
+	guard(mount_locked_reader)();
+	return is_mounted(root->mnt) && capable_wrt_mount(mnt) &&
+	       !has_locked_children(mnt, root->dentry);
+}
+
 static inline int may_decode_fh(struct handle_to_path_ctx *ctx,
 				unsigned int o_flags)
 {
@@ -332,9 +348,7 @@ static inline int may_decode_fh(struct handle_to_path_ctx *ctx,
 
 	if (ns_capable(root->mnt->mnt_sb->s_user_ns, CAP_SYS_ADMIN))
 		ctx->flags = HANDLE_CHECK_PERMS;
-	else if (is_mounted(root->mnt) &&
-		 capable_wrt_mount(real_mount(root->mnt)) &&
-		 !has_locked_children(real_mount(root->mnt), root->dentry))
+	else if (subtree_unobstructed(root))
 		ctx->flags = HANDLE_CHECK_PERMS | HANDLE_CHECK_SUBTREE;
 	else
 		return -EPERM;
diff --git a/fs/file_table.c b/fs/file_table.c
index c68b8c0..e2665ea 100644
--- a/fs/file_table.c
+++ b/fs/file_table.c
@@ -520,7 +520,8 @@ static void __fput(struct file *file)
 	dput(dentry);
 	if (unlikely(mode & FMODE_NEED_UNMOUNT))
 		dissolve_on_fput(mnt);
-	mntput(mnt);
+	else
+		mntput(mnt);
 out:
 	file_free(file);
 }
diff --git a/fs/fs_context.c b/fs/fs_context.c
index 23ad66c..a50584d 100644
--- a/fs/fs_context.c
+++ b/fs/fs_context.c
@@ -315,6 +315,10 @@ struct fs_context *fs_context_for_reconfigure(struct dentry *dentry,
 					unsigned int sb_flags,
 					unsigned int sb_flags_mask)
 {
+	/* kernel-internal superblocks are nobody's to reconfigure */
+	if (dentry->d_sb->s_flags & SB_NOUSER)
+		return ERR_PTR(-EINVAL);
+
 	return alloc_fs_context(dentry->d_sb->s_type, dentry, sb_flags,
 				sb_flags_mask, FS_CONTEXT_FOR_RECONFIGURE);
 }
diff --git a/fs/mount.h b/fs/mount.h
index 94fcc30..4d23cad 100644
--- a/fs/mount.h
+++ b/fs/mount.h
@@ -6,6 +6,8 @@
 #include <linux/fs_pin.h>
 
 extern struct file_system_type nullfs_fs_type;
+extern struct dentry *nullfs_new_file(struct super_block *sb);
+extern struct vfsmount *knullfs;
 extern struct list_head notify_list;
 
 struct mnt_namespace {
@@ -33,16 +35,20 @@ struct mnt_namespace {
 } __randomize_layout;
 
 struct mnt_pcp {
-	int mnt_count;
+	unsigned int mnt_gets;
+	unsigned int mnt_puts;
 	int mnt_writers;
 };
 
 struct mountpoint {
 	struct hlist_node m_hash;
 	struct dentry *m_dentry;
-	struct hlist_head m_list;
+	struct hlist_head m_list;	/* mounts on it and pins */
+	struct hlist_head m_covers;	/* covers of unmounted parents */
 };
 
+struct mnt_cover;
+
 struct mount {
 	struct hlist_node mnt_hash;
 	struct mount *mnt_parent;
@@ -84,7 +90,7 @@ struct mount {
 	struct mountpoint *mnt_mp;	/* where is it mounted */
 	union {
 		struct hlist_node mnt_mp_list;	/* list mounts with the same mountpoint */
-		struct hlist_node mnt_umount;
+		struct hlist_node mnt_umount;	/* on the unmounted list */
 	};
 #ifdef CONFIG_FSNOTIFY
 	struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks;
@@ -98,7 +104,8 @@ struct mount {
 	int mnt_group_id;		/* peer group identifier */
 	int mnt_expiry_mark;		/* true if marked for expiry */
 	struct hlist_head mnt_pins;
-	struct hlist_head mnt_stuck_children;
+	struct mnt_cover *mnt_cover;	/* the one it may leave behind */
+	struct hlist_head mnt_covers;	/* left behind by its unmounted children */
 	struct hlist_node mnt_ns_visible; /* link in ns->mnt_visible_mounts */
 	struct mount *overmount;	/* mounted on ->mnt_root */
 } __randomize_layout;
@@ -233,6 +240,9 @@ static inline struct mnt_namespace *to_mnt_ns(struct ns_common *ns)
 #ifdef CONFIG_FSNOTIFY
 static inline void mnt_notify_add(struct mount *m)
 {
+	/* queued already under this namespace_sem hold */
+	if (!list_empty(&m->to_notify))
+		return;
 	/* Optimize the case where there are no watches */
 	if ((m->mnt_ns && m->mnt_ns->n_fsnotify_marks) ||
 	    (m->prev_ns && m->prev_ns->n_fsnotify_marks))
diff --git a/fs/namei.c b/fs/namei.c
index 20a6534..c87c14e 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -2087,6 +2087,33 @@ all_done: // pure jump
 }
 
 /*
+ * Be careful in case the dentry is unlinked or renamed. Any mounts
+ * stacked on top of it are going away. We need to make sure that we
+ * don't reveal the underyling dentry during refwalk. In rcuwalk we
+ * catch this via d_seq and another lookup for the name. Give the same
+ * guarantee in refwalk.
+ */
+static bool unlink_may_reveal(struct nameidata *nd, int flags,
+			      struct path *path, struct dentry *dentry)
+{
+	/* ".." and LOOKUP_DOWN may land on an unhashed directory */
+	if (flags & WALK_NOFOLLOW)
+		return false;
+	if (nd->flags & LOOKUP_REVAL)
+		return false;
+	/* We crossed onto a mount and the name led us here while it still existed */
+	if (path->mnt != nd->path.mnt)
+		return false;
+	/* only a name on its way out is flagged */
+	if (likely(!cant_mount(dentry)))
+		return false;
+	if (!d_unlinked(dentry))
+		return false;
+	dput(no_free_ptr(path->dentry));
+	return true;
+}
+
+/*
  * Do we need to follow links? We _really_ want to be able
  * to do this check without having to look at inode->i_op,
  * so we keep a cache of "no, this doesn't need follow_link"
@@ -2115,6 +2142,8 @@ static noinline const char *step_into_slowpath(struct nameidata *nd, int flags,
 			if (unlikely(!inode))
 				return ERR_PTR(-ENOENT);
 		} else {
+			if (unlikely(unlink_may_reveal(nd, flags, &path, dentry)))
+				return ERR_PTR(-ESTALE);
 			dput(nd->path.dentry);
 			if (nd->path.mnt != path.mnt)
 				mntput(nd->path.mnt);
diff --git a/fs/namespace.c b/fs/namespace.c
index 580877e..3ed3766 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -80,6 +80,8 @@ static u64 mnt_id_ctr = MNT_UNIQUE_ID_OFFSET;
 static struct hlist_head *mount_hashtable __ro_after_init;
 static struct hlist_head *mountpoint_hashtable __ro_after_init;
 static struct kmem_cache *mnt_cache __ro_after_init;
+struct vfsmount *knullfs __ro_after_init;	/* private nullfs instance */
+static struct vfsmount *knullfs_file __ro_after_init;	/* its regular file */
 static DECLARE_RWSEM(namespace_sem);
 static HLIST_HEAD(unmounted);	/* protected by namespace_sem */
 static LIST_HEAD(ex_mountpoints); /* protected by namespace_sem */
@@ -130,7 +132,6 @@ static void mnt_ns_release(struct mnt_namespace *ns)
 {
 	/* keep alive for {list,stat}mount() */
 	if (ns && refcount_dec_and_test(&ns->passive)) {
-		fsnotify_mntns_delete(ns);
 		put_user_ns(ns->user_ns);
 		kfree(ns);
 	}
@@ -210,6 +211,19 @@ static inline struct hlist_head *mp_hash(struct dentry *dentry)
 	return &mountpoint_hashtable[tmp & mp_hash_mask];
 }
 
+/*
+ * What an unmounted mount leaves behind at its unmounted parent instead of
+ * staying attached to it. A lookup on the parent at the mountpoint finds
+ * a stand-in for as long as the cover is there. The parent frees it.
+ */
+struct mnt_cover {
+	struct hlist_node node;		/* parent->mnt_covers, RCU */
+	struct hlist_node pin;		/* mp->m_covers, keeps the mountpoint */
+	struct dentry *dentry;
+	struct mountpoint *mp;
+	struct rcu_head rcu;
+};
+
 static int mnt_alloc_id(struct mount *mnt)
 {
 	int res;
@@ -249,16 +263,24 @@ void mnt_release_group_id(struct mount *mnt)
 	mnt->mnt_group_id = 0;
 }
 
-/*
- * vfsmount lock must be held for read
- */
-static inline void mnt_add_count(struct mount *mnt, int n)
+static inline void mnt_inc_count(struct mount *mnt)
+{
+#ifdef CONFIG_SMP
+	this_cpu_inc(mnt->mnt_pcp->mnt_gets);
+#else
+	preempt_disable();
+	mnt->mnt_count++;
+	preempt_enable();
+#endif
+}
+
+static inline void mnt_dec_count(struct mount *mnt)
 {
 #ifdef CONFIG_SMP
-	this_cpu_add(mnt->mnt_pcp->mnt_count, n);
+	this_cpu_inc(mnt->mnt_pcp->mnt_puts);
 #else
 	preempt_disable();
-	mnt->mnt_count += n;
+	mnt->mnt_count--;
 	preempt_enable();
 #endif
 }
@@ -269,14 +291,17 @@ static inline void mnt_add_count(struct mount *mnt, int n)
 int mnt_get_count(struct mount *mnt)
 {
 #ifdef CONFIG_SMP
-	int count = 0;
+	unsigned int gets = 0, puts = 0;
 	int cpu;
 
-	for_each_possible_cpu(cpu) {
-		count += per_cpu_ptr(mnt->mnt_pcp, cpu)->mnt_count;
-	}
+	/* puts first, so a put counted here has its get counted below */
+	for_each_possible_cpu(cpu)
+		puts += per_cpu_ptr(mnt->mnt_pcp, cpu)->mnt_puts;
+	smp_mb();	/* pairs with the smp_wmb() in mntput_no_expire() */
+	for_each_possible_cpu(cpu)
+		gets += per_cpu_ptr(mnt->mnt_pcp, cpu)->mnt_gets;
 
-	return count;
+	return gets - puts;
 #else
 	return mnt->mnt_count;
 #endif
@@ -288,6 +313,10 @@ static struct mount *alloc_vfsmnt(const char *name)
 	if (mnt) {
 		int err;
 
+		mnt->mnt_cover = kzalloc_obj(struct mnt_cover, GFP_KERNEL_ACCOUNT);
+		if (!mnt->mnt_cover)
+			goto out_free_cache;
+
 		err = mnt_alloc_id(mnt);
 		if (err)
 			goto out_free_cache;
@@ -305,7 +334,7 @@ static struct mount *alloc_vfsmnt(const char *name)
 		if (!mnt->mnt_pcp)
 			goto out_free_devname;
 
-		this_cpu_add(mnt->mnt_pcp->mnt_count, 1);
+		this_cpu_inc(mnt->mnt_pcp->mnt_gets);
 #else
 		mnt->mnt_count = 1;
 		mnt->mnt_writers = 0;
@@ -320,8 +349,11 @@ static struct mount *alloc_vfsmnt(const char *name)
 		INIT_HLIST_HEAD(&mnt->mnt_slave_list);
 		INIT_HLIST_NODE(&mnt->mnt_slave);
 		INIT_HLIST_NODE(&mnt->mnt_mp_list);
-		INIT_HLIST_HEAD(&mnt->mnt_stuck_children);
+		INIT_HLIST_HEAD(&mnt->mnt_covers);
 		INIT_HLIST_NODE(&mnt->mnt_ns_visible);
+#ifdef CONFIG_FSNOTIFY
+		INIT_LIST_HEAD(&mnt->to_notify);
+#endif
 		RB_CLEAR_NODE(&mnt->mnt_node);
 		mnt->mnt.mnt_idmap = &nop_mnt_idmap;
 	}
@@ -334,6 +366,7 @@ out_free_devname:
 out_free_id:
 	mnt_free_id(mnt);
 out_free_cache:
+	kfree(mnt->mnt_cover);
 	kmem_cache_free(mnt_cache, mnt);
 	return NULL;
 }
@@ -725,6 +758,8 @@ int sb_prepare_remount_readonly(struct super_block *sb)
 static void free_vfsmnt(struct mount *mnt)
 {
 	mnt_idmap_put(mnt_idmap(&mnt->mnt));
+	/* NULL if it left it behind */
+	kfree(mnt->mnt_cover);
 	kfree_const(mnt->mnt_devname);
 #ifdef CONFIG_SMP
 	free_percpu(mnt->mnt_pcp);
@@ -746,13 +781,13 @@ int __legitimize_mnt(struct vfsmount *bastard, unsigned seq)
 	if (bastard == NULL)
 		return 0;
 	mnt = real_mount(bastard);
-	mnt_add_count(mnt, 1);
-	smp_mb();		// see mntput_no_expire() and do_umount()
+	mnt_inc_count(mnt);
+	smp_mb();	/* see mntput_no_expire_slowpath() and do_umount() */
 	if (likely(!read_seqretry(&mount_lock, seq)))
 		return 0;
 	lock_mount_hash();
 	if (unlikely(bastard->mnt_flags & (MNT_SYNC_UMOUNT | MNT_DOOMED))) {
-		mnt_add_count(mnt, -1);
+		mnt_dec_count(mnt);
 		unlock_mount_hash();
 		return 1;
 	}
@@ -781,20 +816,31 @@ static bool legitimize_mnt(struct vfsmount *bastard, unsigned seq)
  * @dentry:	dentry of mountpoint
  *
  * If @mnt has a child mount @c mounted on @dentry find and return it.
+ * If @mnt is unmounted and a child that was unmounted with it left its
+ * cover behind at @dentry, return the stand-in for it instead: knullfs
+ * for a directory, its regular file for anything else.
  * Caller must either hold the spinlock component of @mount_lock or
  * hold rcu_read_lock(), sample the seqcount component before the call
  * and recheck it afterwards.
  *
- * Return: The child of @mnt mounted on @dentry or %NULL.
+ * Return: The child of @mnt mounted on @dentry, a stand-in or %NULL.
  */
 struct mount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)
 {
 	struct hlist_head *head = m_hash(mnt, dentry);
+	struct mnt_cover *cover;
 	struct mount *p;
 
 	hlist_for_each_entry_rcu(p, head, mnt_hash)
 		if (&p->mnt_parent->mnt == mnt && p->mnt_mountpoint == dentry)
 			return p;
+	/* an unmounted mount keeps the covers its unmounted children left */
+	/* a lockless caller rechecks mount_lock after a miss, a stale flag is harmless */
+	if (unlikely(data_race(mnt->mnt_flags) & MNT_UMOUNT)) {
+		hlist_for_each_entry_rcu(cover, &real_mount(mnt)->mnt_covers, node)
+			if (cover->dentry == dentry)
+				return real_mount(d_is_dir(dentry) ? knullfs : knullfs_file);
+	}
 	return NULL;
 }
 
@@ -910,6 +956,7 @@ mountpoint:
 	mp->m_dentry = dget(dentry);
 	hlist_add_head(&mp->m_hash, mp_hash(dentry));
 	INIT_HLIST_HEAD(&mp->m_list);
+	INIT_HLIST_HEAD(&mp->m_covers);
 	hlist_add_head(&m->node, &mp->m_list);
 	m->mp = no_free_ptr(mp);
 	read_sequnlock_excl(&mount_lock);
@@ -922,7 +969,7 @@ mountpoint:
  */
 static void maybe_free_mountpoint(struct mountpoint *mp, struct list_head *list)
 {
-	if (hlist_empty(&mp->m_list)) {
+	if (hlist_empty(&mp->m_list) && hlist_empty(&mp->m_covers)) {
 		struct dentry *dentry = mp->m_dentry;
 		spin_lock(&dentry->d_lock);
 		dentry->d_flags &= ~DCACHE_MOUNTED;
@@ -1010,6 +1057,36 @@ static void umount_mnt(struct mount *mnt)
 }
 
 /*
+ * @mnt is unmounted together with its parent and would have stayed attached
+ * to it. Leave its cover behind before it is detached so that a lookup on the
+ * parent at the mountpoint keeps finding a mount instead of what @mnt covered.
+ *
+ * locks: mount_lock[write_seqlock]
+ */
+static void leave_cover(struct mount *mnt)
+{
+	struct mnt_cover *cover = mnt->mnt_cover;
+
+	mnt->mnt_cover = NULL;
+	cover->dentry = mnt->mnt_mountpoint;
+	cover->mp = mnt->mnt_mp;
+	/* keeps the mountpoint once @mnt has let go of it */
+	hlist_add_head(&cover->pin, &cover->mp->m_covers);
+	hlist_add_head_rcu(&cover->node, &mnt->mnt_parent->mnt_covers);
+}
+
+/*
+ * locks: mount_lock[write_seqlock]
+ */
+static void drop_cover(struct mnt_cover *cover, struct list_head *shrink_list)
+{
+	hlist_del_rcu(&cover->node);
+	hlist_del(&cover->pin);
+	maybe_free_mountpoint(cover->mp, shrink_list);
+	kfree_rcu(cover, rcu);
+}
+
+/*
  * vfsmount lock must be held for write
  */
 void mnt_set_mountpoint(struct mount *mnt,
@@ -1058,7 +1135,10 @@ static void attach_mnt(struct mount *mnt, struct mount *parent,
 void mnt_change_mountpoint(struct mount *parent, struct mountpoint *mp, struct mount *mnt)
 {
 	struct mountpoint *old_mp = mnt->mnt_mp;
+	struct mount *old_parent = mnt->mnt_parent;
 
+	if (old_parent->overmount == mnt)
+		old_parent->overmount = NULL;
 	list_del_init(&mnt->mnt_child);
 	hlist_del_init(&mnt->mnt_mp_list);
 	hlist_del_init_rcu(&mnt->mnt_hash);
@@ -1254,6 +1334,7 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root,
 
 	mnt->mnt.mnt_flags = READ_ONCE(old->mnt.mnt_flags) &
 			     ~MNT_INTERNAL_FLAGS;
+	mnt->mnt_t_flags = old->mnt_t_flags & T_UNBINDABLE;
 
 	if (flag & (CL_SLAVE | CL_PRIVATE))
 		mnt->mnt_group_id = 0; /* not a peer of original */
@@ -1296,8 +1377,6 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root,
 
 static void cleanup_mnt(struct mount *mnt)
 {
-	struct hlist_node *p;
-	struct mount *m;
 	/*
 	 * The warning here probably indicates that somebody messed
 	 * up a mnt_want/drop_write() pair.  If this happens, the
@@ -1308,10 +1387,6 @@ static void cleanup_mnt(struct mount *mnt)
 	WARN_ON(mnt_get_writers(mnt));
 	if (unlikely(mnt->mnt_pins.first))
 		mnt_pin_kill(mnt);
-	hlist_for_each_entry_safe(m, p, &mnt->mnt_stuck_children, mnt_umount) {
-		hlist_del(&m->mnt_umount);
-		mntput(&m->mnt);
-	}
 	fsnotify_vfsmount_delete(&mnt->mnt);
 	dput(mnt->mnt.mnt_root);
 	deactivate_super(mnt->mnt.mnt_sb);
@@ -1337,6 +1412,8 @@ static DECLARE_DELAYED_WORK(delayed_mntput_work, delayed_mntput);
 
 static void noinline mntput_no_expire_slowpath(struct mount *mnt)
 {
+	struct mnt_cover *cover;
+	struct hlist_node *n;
 	LIST_HEAD(list);
 	int count;
 
@@ -1347,7 +1424,7 @@ static void noinline mntput_no_expire_slowpath(struct mount *mnt)
 	 * mount_lock, we'll see their refcount increment here.
 	 */
 	smp_mb();
-	mnt_add_count(mnt, -1);
+	mnt_dec_count(mnt);
 	count = mnt_get_count(mnt);
 	if (count != 0) {
 		WARN_ON(count < 0);
@@ -1367,13 +1444,10 @@ static void noinline mntput_no_expire_slowpath(struct mount *mnt)
 	if (unlikely(!list_empty(&mnt->mnt_expire)))
 		list_del(&mnt->mnt_expire);
 
-	if (unlikely(!list_empty(&mnt->mnt_mounts))) {
-		struct mount *p, *tmp;
-		list_for_each_entry_safe(p, tmp, &mnt->mnt_mounts,  mnt_child) {
-			__umount_mnt(p, &list);
-			hlist_add_head(&p->mnt_umount, &mnt->mnt_stuck_children);
-		}
-	}
+	/* nothing stays attached to an unmounted mount */
+	VFS_WARN_ON_ONCE(!list_empty(&mnt->mnt_mounts));
+	hlist_for_each_entry_safe(cover, n, &mnt->mnt_covers, node)
+		drop_cover(cover, &list);
 	unlock_mount_hash();
 	shrink_dentry_list(&list);
 
@@ -1404,7 +1478,8 @@ static void mntput_no_expire(struct mount *mnt)
 		 * non-NULL under rcu_read_lock(), the reference
 		 * we are dropping is not the final one.
 		 */
-		mnt_add_count(mnt, -1);
+		smp_wmb();	/* pairs with the smp_mb() in mnt_get_count() */
+		mnt_dec_count(mnt);
 		rcu_read_unlock();
 		return;
 	}
@@ -1426,7 +1501,7 @@ EXPORT_SYMBOL(mntput);
 struct vfsmount *mntget(struct vfsmount *mnt)
 {
 	if (mnt)
-		mnt_add_count(real_mount(mnt), 1);
+		mnt_inc_count(real_mount(mnt));
 	return mnt;
 }
 EXPORT_SYMBOL(mntget);
@@ -1737,36 +1812,17 @@ static inline void namespace_lock(void)
 enum umount_tree_flags {
 	UMOUNT_SYNC = 1,
 	UMOUNT_PROPAGATE = 2,
-	UMOUNT_CONNECTED = 4,
+	UMOUNT_COVER = 4,
 };
 
-static bool disconnect_mount(struct mount *mnt, enum umount_tree_flags how)
+/* Do we need to leave the mountpoint on the parent covered? */
+static bool needs_cover(struct mount *mnt, enum umount_tree_flags how)
 {
-	/* Leaving mounts connected is only valid for lazy umounts */
 	if (how & UMOUNT_SYNC)
-		return true;
-
-	/* A mount without a parent has nothing to be connected to */
-	if (!mnt_has_parent(mnt))
-		return true;
-
-	/* Because the reference counting rules change when mounts are
-	 * unmounted and connected, umounted mounts may not be
-	 * connected to mounted mounts.
-	 */
-	if (!(mnt->mnt_parent->mnt.mnt_flags & MNT_UMOUNT))
-		return true;
-
-	/* Has it been requested that the mount remain connected? */
-	if (how & UMOUNT_CONNECTED)
 		return false;
-
-	/* Is the mount locked such that it needs to remain connected? */
-	if (IS_MNT_LOCKED(mnt))
+	if (!(mnt->mnt_parent->mnt.mnt_flags & MNT_UMOUNT))
 		return false;
-
-	/* By default disconnect the mount */
-	return true;
+	return (how & UMOUNT_COVER) || IS_MNT_LOCKED(mnt);
 }
 
 /*
@@ -1783,6 +1839,8 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)
 
 	/* Gather the mounts to umount */
 	for (p = mnt; p; p = next_mnt(p, mnt)) {
+		/* A mount is unmounted once. */
+		VFS_WARN_ON_ONCE(p->mnt.mnt_flags & MNT_UMOUNT);
 		p->mnt.mnt_flags |= MNT_UMOUNT;
 		if (mnt_ns_attached(p))
 			move_from_ns(p);
@@ -1802,7 +1860,6 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)
 
 	while (!list_empty(&tmp_list)) {
 		struct mnt_namespace *ns;
-		bool disconnect;
 		p = list_first_entry(&tmp_list, struct mount, mnt_list);
 		list_del_init(&p->mnt_expire);
 		list_del_init(&p->mnt_list);
@@ -1815,17 +1872,12 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)
 		if (how & UMOUNT_SYNC)
 			p->mnt.mnt_flags |= MNT_SYNC_UMOUNT;
 
-		disconnect = disconnect_mount(p, how);
 		if (mnt_has_parent(p)) {
-			if (!disconnect) {
-				/* Don't forget about p */
-				list_add_tail(&p->mnt_child, &p->mnt_parent->mnt_mounts);
-			} else {
-				umount_mnt(p);
-			}
+			if (needs_cover(p, how))
+				leave_cover(p);
+			umount_mnt(p);
 		}
-		if (disconnect)
-			hlist_add_head(&p->mnt_umount, &unmounted);
+		hlist_add_head(&p->mnt_umount, &unmounted);
 
 		/*
 		 * At this point p->mnt_ns is NULL, notification will be queued
@@ -1976,11 +2028,16 @@ out:
  * detach_mounts allows lazily unmounting those mounts instead of
  * leaking them.
  *
+ * The dentry is unhashed before the mounts go so that no lookup finds
+ * what they covered. The caller removes it for good afterwards.
+ *
  * The caller may hold dentry->d_inode->i_rwsem.
  */
 void __detach_mounts(struct dentry *dentry)
 {
 	struct pinned_mountpoint mp = {};
+	struct mnt_cover *cover;
+	struct hlist_node *n;
 	struct mount *mnt;
 
 	guard(namespace_excl)();
@@ -1989,15 +2046,16 @@ void __detach_mounts(struct dentry *dentry)
 	if (!lookup_mountpoint(dentry, &mp))
 		return;
 
+	/* the name goes first, what covered it goes second */
+	d_drop(dentry);
 	event++;
 	while (mp.node.next) {
 		mnt = hlist_entry(mp.node.next, struct mount, mnt_mp_list);
-		if (mnt->mnt.mnt_flags & MNT_UMOUNT) {
-			umount_mnt(mnt);
-			hlist_add_head(&mnt->mnt_umount, &unmounted);
-		}
-		else umount_tree(mnt, UMOUNT_CONNECTED);
+		umount_tree(mnt, UMOUNT_COVER);
 	}
+	/* the dentry goes away, so do the covers left behind on it */
+	hlist_for_each_entry_safe(cover, n, &mp.mp->m_covers, pin)
+		drop_cover(cover, &ex_mountpoints);
 	unpin_mountpoint(&mp);
 }
 
@@ -2294,9 +2352,11 @@ void drop_collected_paths(const struct path *paths, const struct path *prealloc)
 
 static struct mnt_namespace *alloc_mnt_ns(struct user_namespace *, bool);
 
+/* Consumes the caller's reference to @mnt. */
 void dissolve_on_fput(struct vfsmount *mnt)
 {
-	struct mount *m = real_mount(mnt);
+	struct vfsmount *p __free(mntput) = mnt;
+	struct mount *m = real_mount(p);
 
 	/*
 	 * m used to be the root of anon namespace; if it still is one,
@@ -2319,13 +2379,14 @@ void dissolve_on_fput(struct vfsmount *mnt)
 
 		emptied_ns = m->mnt_ns;
 		lock_mount_hash();
-		umount_tree(m, UMOUNT_CONNECTED);
+		umount_tree(m, UMOUNT_COVER);
 		unlock_mount_hash();
+		mntput(no_free_ptr(p));
 	}
 }
 
 /* locks: namespace_shared && pinned(mnt) || mount_locked_reader */
-static bool __has_locked_children(struct mount *mnt, struct dentry *dentry)
+bool has_locked_children(struct mount *mnt, struct dentry *dentry)
 {
 	struct mount *child;
 
@@ -2339,10 +2400,16 @@ static bool __has_locked_children(struct mount *mnt, struct dentry *dentry)
 	return false;
 }
 
-bool has_locked_children(struct mount *mnt, struct dentry *dentry)
+/* locks: namespace_shared && pinned(mnt) || mount_locked_reader */
+static bool __has_children(struct mount *mnt, struct dentry *dentry)
 {
-	guard(mount_locked_reader)();
-	return __has_locked_children(mnt, dentry);
+	struct mount *child;
+
+	list_for_each_entry(child, &mnt->mnt_mounts, mnt_child) {
+		if (is_subdir(child->mnt_mountpoint, dentry))
+			return true;
+	}
+	return false;
 }
 
 /*
@@ -2401,7 +2468,7 @@ struct vfsmount *clone_private_mount(const struct path *path)
 	if (!ns_capable(old_mnt->mnt_ns->user_ns, CAP_SYS_ADMIN))
 		return ERR_PTR(-EPERM);
 
-	if (__has_locked_children(old_mnt, path->dentry))
+	if (has_locked_children(old_mnt, path->dentry))
 		return ERR_PTR(-EINVAL);
 
 	new_mnt = clone_mnt(old_mnt, path->dentry, CL_PRIVATE);
@@ -2564,11 +2631,11 @@ enum mnt_tree_flags_t {
 static int attach_recursive_mnt(struct mount *source_mnt,
 				const struct pinned_mountpoint *dest)
 {
-	struct user_namespace *user_ns = current->nsproxy->mnt_ns->user_ns;
 	struct mount *dest_mnt = dest->parent;
 	struct mountpoint *dest_mp = dest->mp;
 	HLIST_HEAD(tree_list);
 	struct mnt_namespace *ns = dest_mnt->mnt_ns;
+	struct user_namespace *user_ns = ns->user_ns;
 	struct pinned_mountpoint root = {};
 	struct mountpoint *shorter = NULL;
 	struct mount *child, *p;
@@ -2578,12 +2645,30 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 	bool moving = mnt_has_parent(source_mnt);
 
 	/*
+	 * A caller in an unprivileged mount namespaces may trigger an
+	 * automount and propagate locked mounts into privileged mount
+	 * namespaces. Take ownership from the target mount namespace.
+	 * It's equivalent for everything but the automount case.
+	 *
+	 * Detached trees in anonymous mount namespaces by be handed
+	 * over via SCM_RIGHTS or inherited in other ways on purpose
+	 * the attaching task's mount namespace is authoritative, not
+	 * the creator of the detached tree.
+	 */
+	if (is_anon_ns(ns))
+		user_ns = current->nsproxy->mnt_ns->user_ns;
+	else
+		user_ns = ns->user_ns;
+
+	/*
 	 * Preallocate a mountpoint in case the new mounts need to be
 	 * mounted beneath mounts on the same mountpoint.
 	 */
-	for (top = source_mnt; unlikely(top->overmount); top = top->overmount) {
+	for (top = source_mnt; ; top = top->overmount) {
 		if (!shorter && is_mnt_ns_file(top->mnt.mnt_root))
 			shorter = top->mnt_mp;
+		if (likely(!top->overmount))
+			break;
 	}
 	err = get_mountpoint(top->mnt.mnt_root, &root);
 	if (err)
@@ -2654,15 +2739,17 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 			/*
 			 * If @q was locked it was meant to hide
 			 * whatever was under it. Let @child take over
-			 * that job and lock it, then we can unlock @q.
-			 * That'll allow another namespace to shed @q
-			 * and reveal @child. Clearly, that mounter
-			 * consented to this by not severing the mount
-			 * relationship. Otherwise, what's the point.
+			 * that job and lock it. If @child is the mount
+			 * the caller placed we can then unlock @q:
+			 * nothing another namespace does removes it
+			 * again. A propagated copy goes away when the
+			 * mounter of the original unmounts it, so @q
+			 * keeps its lock.
 			 */
 			if (IS_MNT_LOCKED(q)) {
 				child->mnt.mnt_flags |= MNT_LOCKED;
-				q->mnt.mnt_flags &= ~MNT_LOCKED;
+				if (child == source_mnt)
+					q->mnt.mnt_flags &= ~MNT_LOCKED;
 			}
 			mnt_change_mountpoint(r, mp, q);
 		}
@@ -2759,6 +2846,11 @@ static void do_lock_mount(const struct path *path,
 
 		scoped_guard(mount_locked_reader) {
 			m = where_to_mount(path, &dentry, beneath);
+			/* sticky, so it takes no locks to refuse it */
+			if (unlikely(cant_mount(dentry))) {
+				res->parent = ERR_PTR(-ENOENT);
+				return;
+			}
 			if (&m->mnt != path->mnt) {
 				mntget(&m->mnt);
 				dget(dentry);
@@ -2989,7 +3081,14 @@ static struct mount *__do_loopback(const struct path *old_path,
 	if (!may_copy_tree(old_path))
 		return ERR_PTR(-EINVAL);
 
-	if (!recurse && __has_locked_children(old, old_path->dentry))
+	/* a pseudo dentry is freed without an RCU delay, no walk may find it */
+	if (old_path->dentry->d_flags & DCACHE_NORCU)
+		return ERR_PTR(-EINVAL);
+
+	if (recurse && !old->mnt_ns)
+		return ERR_PTR(-EINVAL);
+
+	if (!recurse && has_locked_children(old, old_path->dentry))
 		return ERR_PTR(-EINVAL);
 
 	if (recurse)
@@ -3028,7 +3127,11 @@ static int do_loopback(const struct path *path, const char *old_name,
 	if (IS_ERR(mnt))
 		return PTR_ERR(mnt);
 
-	err = graft_tree(mnt, &mp);
+	/* the copy may carry mount namespace files from below the source */
+	if (recurse && !check_for_nsfs_mounts(mnt))
+		err = -EINVAL;
+	else
+		err = graft_tree(mnt, &mp);
 	if (err) {
 		lock_mount_hash();
 		umount_tree(mnt, UMOUNT_SYNC);
@@ -3088,7 +3191,7 @@ static struct file *open_detached_copy(struct path *path, unsigned int flags)
 	path->mnt = mntget(&ns->root->mnt);
 	file = dentry_open(path, O_PATH, current_cred());
 	if (IS_ERR(file))
-		dissolve_on_fput(path->mnt);
+		dissolve_on_fput(no_free_ptr(path->mnt));
 	else
 		file->f_mode |= FMODE_NEED_UNMOUNT;
 	return file;
@@ -3110,12 +3213,18 @@ static struct mnt_namespace *create_new_namespace(struct path *path,
 	struct mount *mnt;
 	unsigned int copy_flags = 0;
 	bool locked = false, recurse = flags & MOUNT_COPY_RECURSIVE;
+	bool foreign = user_ns != ns->user_ns;
 
 	if (unlikely(!d_can_lookup(path->dentry)))
 		return ERR_PTR(-ENOTDIR);
 
-	if (user_ns != ns->user_ns)
-		copy_flags |= CL_SLAVE;
+	/*
+	 * Without privileges over the mount namespace the copy is made from
+	 * nothing mounted below @path may be left out. It would reveal what
+	 * it covers. That's what unshare() gives such a caller as well.
+	 */
+	if (foreign)
+		copy_flags |= CL_SLAVE | CL_COPY_UNBINDABLE;
 
 	new_ns = alloc_mnt_ns(user_ns, false);
 	if (IS_ERR(new_ns))
@@ -3149,10 +3258,14 @@ static struct mnt_namespace *create_new_namespace(struct path *path,
 	/*
 	 * We don't emulate unshare()ing a mount namespace. We stick to
 	 * the restrictions of creating detached bind-mounts. It has a
-	 * lot saner and simpler semantics.
+	 * lot saner and simpler semantics. A caller without privileges
+	 * over the mount namespace can't leave out any child though.
 	 */
 	if (flags & MOUNT_COPY_NEW)
 		mnt = clone_mnt(real_mount(path->mnt), path->dentry, copy_flags);
+	else if (foreign && !recurse &&
+		 __has_children(real_mount(path->mnt), path->dentry))
+		mnt = ERR_PTR(-EINVAL);
 	else
 		mnt = __do_loopback(path, recurse, copy_flags);
 	scoped_guard(mount_writer) {
@@ -3169,7 +3282,7 @@ static struct mnt_namespace *create_new_namespace(struct path *path,
 		 * of the real rootfs we created.
 		 */
 		attach_mnt(mnt, new_ns_root, mp.mp);
-		if (user_ns != ns->user_ns)
+		if (foreign)
 			lock_mnt_tree(new_ns_root);
 	}
 
@@ -3463,11 +3576,11 @@ static int do_set_group(const struct path *from_path, const struct path *to_path
 		return -EINVAL;
 
 	/* From mount should not have locked children in place of To's root */
-	if (__has_locked_children(from, to->mnt.mnt_root))
+	if (has_locked_children(from, to->mnt.mnt_root))
 		return -EINVAL;
 
 	/* Setting sharing groups is only allowed on private mounts */
-	if (IS_MNT_SHARED(to) || IS_MNT_SLAVE(to))
+	if (IS_MNT_SHARED(to) || IS_MNT_SLAVE(to) || IS_MNT_UNBINDABLE(to))
 		return -EINVAL;
 
 	/* From should not be private */
@@ -3743,7 +3856,7 @@ static int do_add_mount(struct mount *newmnt, const struct pinned_mountpoint *mp
 		if (!(mnt_flags & MNT_SHRINKABLE))
 			return -EINVAL;
 		/* ... and for those we'd better have mountpoint still alive */
-		if (!parent->mnt_ns)
+		if (!is_mounted(&parent->mnt))
 			return -EINVAL;
 	}
 
@@ -3953,6 +4066,8 @@ void mark_mounts_for_expiry(struct list_head *mounts)
 	list_for_each_entry_safe(mnt, next, mounts, mnt_expire) {
 		if (!is_mounted(&mnt->mnt))
 			continue;
+		/* lock_mnt_tree() leaves expirable mounts alone */
+		VFS_WARN_ON_ONCE(IS_MNT_LOCKED(mnt));
 		if (!xchg(&mnt->mnt_expiry_mark, 1) ||
 			propagate_mount_busy(mnt, 1))
 			continue;
@@ -3960,6 +4075,11 @@ void mark_mounts_for_expiry(struct list_head *mounts)
 	}
 	while (!list_empty(&graveyard)) {
 		mnt = list_first_entry(&graveyard, struct mount, mnt_expire);
+		/* an earlier umount_tree() may have moved a busy mount here */
+		if (propagate_mount_busy(mnt, 1)) {
+			list_move(&mnt->mnt_expire, mounts);
+			continue;
+		}
 		touch_mnt_namespace(mnt->mnt_ns);
 		umount_tree(mnt, UMOUNT_PROPAGATE|UMOUNT_SYNC);
 	}
@@ -3968,16 +4088,37 @@ void mark_mounts_for_expiry(struct list_head *mounts)
 EXPORT_SYMBOL_GPL(mark_mounts_for_expiry);
 
 /*
+ * Unmount @mnt if it's a shrinkable mount without children that nobody uses.
+ *
+ * mount_lock must be held for write
+ */
+static bool shrink_submount(struct mount *mnt)
+{
+	/* not the kernel's to remove either */
+	if (IS_MNT_LOCKED(mnt) || propagate_mount_busy(mnt, 1))
+		return false;
+	touch_mnt_namespace(mnt->mnt_ns);
+	umount_tree(mnt, UMOUNT_PROPAGATE|UMOUNT_SYNC);
+	return true;
+}
+
+/*
  * Ripoff of 'select_parent()'
  *
- * search the list of submounts for a given mountpoint, and move any
- * shrinkable submounts to the 'graveyard' list.
+ * unmount the shrinkable submounts of @parent that aren't busy, children
+ * before their parent, and say whether anything went
+ *
+ * The cursor into the children of @this_parent survives the umount of a
+ * child mount without child mounts. The mounts that get umounted together with
+ * it are located under receiving mounts of @this_parent and never under
+ * @this_parent itself. The one exception is @this_parent getting unmounted
+ * then the walk starts over.
  */
-static int select_submounts(struct mount *parent, struct list_head *graveyard)
+static bool __shrink_submounts(struct mount *parent)
 {
 	struct mount *this_parent = parent;
 	struct list_head *next;
-	int found = 0;
+	bool shrunk = false;
 
 repeat:
 	next = this_parent->mnt_mounts.next;
@@ -3996,42 +4137,45 @@ resume:
 			this_parent = mnt;
 			goto repeat;
 		}
-
-		if (!propagate_mount_busy(mnt, 1)) {
-			list_move_tail(&mnt->mnt_expire, graveyard);
-			found++;
-		}
+		if (!shrink_submount(mnt))
+			continue;
+		shrunk = true;
+		if (unlikely(this_parent->mnt.mnt_flags & MNT_UMOUNT))
+			return true;
 	}
 	/*
 	 * All done at this level ... ascend and resume the search
 	 */
 	if (this_parent != parent) {
-		next = this_parent->mnt_child.next;
-		this_parent = this_parent->mnt_parent;
+		struct mount *mnt = this_parent;
+
+		next = mnt->mnt_child.next;
+		this_parent = mnt->mnt_parent;
+		/* its children are gone, maybe it can go as well */
+		if (shrink_submount(mnt)) {
+			shrunk = true;
+			if (unlikely(this_parent->mnt.mnt_flags & MNT_UMOUNT))
+				return true;
+		}
 		goto resume;
 	}
-	return found;
+	return shrunk;
 }
 
 /*
- * process a list of expirable mountpoints with the intent of discarding any
- * submounts of a specific parent mountpoint
+ * unmount the shrinkable submounts of @mnt that aren't busy
+ *
+ * The busy check and the umount of a mount are adjacent. An umount can
+ * still empty or move a mount in a part of the tree that was walked
+ * already, so walk again until nothing goes.
  *
  * mount_lock must be held for write
  */
 static void shrink_submounts(struct mount *mnt)
 {
-	LIST_HEAD(graveyard);
-	struct mount *m;
-
-	/* extract submounts of 'mountpoint' from the expiration list */
-	while (select_submounts(mnt, &graveyard)) {
-		while (!list_empty(&graveyard)) {
-			m = list_first_entry(&graveyard, struct mount,
-						mnt_expire);
-			touch_mnt_namespace(m->mnt_ns);
-			umount_tree(m, UMOUNT_PROPAGATE|UMOUNT_SYNC);
-		}
+	for (;;) {
+		if (!__shrink_submounts(mnt))
+			break;
 	}
 }
 
@@ -4196,6 +4340,8 @@ static void free_mnt_ns(struct mnt_namespace *ns)
 	if (!is_anon_ns(ns))
 		ns_common_free(ns);
 	dec_mnt_namespaces(ns->ucounts);
+	/* the last active reference is gone, no mark can show up anymore */
+	fsnotify_mntns_delete(ns);
 	mnt_ns_tree_remove(ns);
 }
 
@@ -4545,7 +4691,7 @@ SYSCALL_DEFINE3(fsmount, int, fs_fd, unsigned int, flags,
 	FD_PREPARE(fdf, (flags & FSMOUNT_CLOEXEC) ? O_CLOEXEC : 0,
 		   dentry_open(&new_path, O_PATH, fc->cred));
 	if (fdf.err) {
-		dissolve_on_fput(new_path.mnt);
+		dissolve_on_fput(no_free_ptr(new_path.mnt));
 		return fdf.err;
 	}
 
@@ -4697,14 +4843,15 @@ int path_pivot_root(struct path *new, struct path *old)
 
 	new_mnt = real_mount(new->mnt);
 	root_mnt = real_mount(root.mnt);
+	/* only a mounted mount has a parent that namespace_sem pins */
+	if (!check_mnt(root_mnt) || !check_mnt(new_mnt))
+		return -EINVAL;
 	ex_parent = new_mnt->mnt_parent;
 	root_parent = root_mnt->mnt_parent;
 	if (IS_MNT_SHARED(old_mnt) ||
 		IS_MNT_SHARED(ex_parent) ||
 		IS_MNT_SHARED(root_parent))
 		return -EINVAL;
-	if (!check_mnt(root_mnt) || !check_mnt(new_mnt))
-		return -EINVAL;
 	if (new_mnt->mnt.mnt_flags & MNT_LOCKED)
 		return -EINVAL;
 	if (d_unlinked(new->dentry))
@@ -4955,6 +5102,11 @@ static int do_mount_setattr(const struct path *path, struct mount_kattr *kattr)
 		 * propagation.
 		 */
 		namespace_lock();
+		/* invent_group_ids() walks the tree, only walk a mounted one */
+		if (!anon_ns_root(mnt) && !check_mnt(mnt)) {
+			namespace_unlock();
+			return -EINVAL;
+		}
 		if (kattr->propagation == MS_SHARED) {
 			err = invent_group_ids(mnt, kattr->kflags & MOUNT_KATTR_RECURSE);
 			if (err) {
@@ -4967,6 +5119,7 @@ static int do_mount_setattr(const struct path *path, struct mount_kattr *kattr)
 	err = -EINVAL;
 	lock_mount_hash();
 
+	/* Checked under namespace_sem already if the propagation changes. */
 	if (!anon_ns_root(mnt) && !check_mnt(mnt))
 		goto out;
 
@@ -5319,15 +5472,25 @@ static void statmount_sb_basic(struct kstatmount *s)
 	s->sm.sb_flags = sb->s_flags & (SB_RDONLY|SB_SYNCHRONOUS|SB_DIRSYNC|SB_LAZYTIME);
 }
 
+static void statmount_mnt_parent(struct kstatmount *s, const struct mount *m)
+{
+	s->sm.mnt_parent_id = m->mnt_parent->mnt_id_unique;
+	s->sm.mnt_parent_id_old = m->mnt_parent->mnt_id;
+}
+
 static void statmount_mnt_basic(struct kstatmount *s)
 {
 	struct mount *m = real_mount(s->mnt);
 
 	s->sm.mask |= STATMOUNT_MNT_BASIC;
 	s->sm.mnt_id = m->mnt_id_unique;
-	s->sm.mnt_parent_id = m->mnt_parent->mnt_id_unique;
 	s->sm.mnt_id_old = m->mnt_id;
-	s->sm.mnt_parent_id_old = m->mnt_parent->mnt_id;
+	/* An unmounted mount is cut loose from its parent under mount_lock alone. */
+	if (likely(is_mounted(s->mnt)))
+		statmount_mnt_parent(s, m);
+	else
+		scoped_guard(mount_locked_reader)
+			statmount_mnt_parent(s, m);
 	s->sm.mnt_attr = mnt_to_attr_flags(&m->mnt);
 	s->sm.mnt_propagation = mnt_to_propagation_flags(m);
 	s->sm.mnt_peer_group = m->mnt_group_id;
@@ -6199,6 +6362,25 @@ static void __init mount_rootfs_on_nullfs(struct vfsmount *mnt,
 		attach_mnt(real_mount(mnt), mp.parent, mp.mp);
 }
 
+static struct vfsmount *__init knullfs_file_mount(void)
+{
+	struct dentry *file;
+	struct mount *mnt;
+
+	file = nullfs_new_file(knullfs->mnt_sb);
+	if (IS_ERR(file))
+		return ERR_CAST(file);
+	mnt = clone_mnt(real_mount(knullfs), file, CL_PRIVATE);
+	dput(file);
+	if (IS_ERR(mnt))
+		return ERR_CAST(mnt);
+	mnt->mnt_ns = MNT_NS_INTERNAL;
+	mnt->mnt.mnt_flags |= MNT_INTERNAL;
+	mnt->mnt.mnt_flags &= ~MNT_READONLY;
+	dont_mount(mnt->mnt.mnt_root);
+	return &mnt->mnt;
+}
+
 static void __init init_mount_tree(void)
 {
 	struct vfsmount *mnt, *nullfs_mnt;
@@ -6210,10 +6392,14 @@ static void __init init_mount_tree(void)
 	 *
 	 * (1) nullfs with mount id 1
 	 * (2) mutable rootfs with mount id 2
-	 * (3) private nullfs for kthreads (SB_KERNMOUNT)
+	 * (3) private nullfs for kthreads (SB_KERNMOUNT), kept in knullfs
+	 * (4) a second mount of (3) rooted on a regular file, kept in
+	 *     knullfs_file
 	 *
 	 * with (2) mounted on top of (1). The init_task's root and pwd
 	 * are pointed at (3) so all kthreads start isolated in nullfs.
+	 * A lookup at the cover an unmounted mount left behind finds (3)
+	 * or (4), see __lookup_mnt().
 	 */
 	nullfs_mnt = vfs_kern_mount(&nullfs_fs_type, 0, "nullfs", NULL);
 	if (IS_ERR(nullfs_mnt))
@@ -6245,11 +6431,18 @@ static void __init init_mount_tree(void)
 		init_mnt_ns.nr_mounts++;
 	}
 
-	nullfs_mnt = kern_mount(&nullfs_fs_type);
-	if (IS_ERR(nullfs_mnt))
+	knullfs = kern_mount(&nullfs_fs_type);
+	if (IS_ERR(knullfs))
 		panic("VFS: Failed to create private nullfs instance");
-	root.mnt	= nullfs_mnt;
-	root.dentry	= nullfs_mnt->mnt_root;
+	/* nothing is ever mounted on the root of a kernel thread */
+	dont_mount(knullfs->mnt_root);
+	/* and nothing is ever written through it */
+	knullfs->mnt_flags |= MNT_READONLY;
+	knullfs_file = knullfs_file_mount();
+	if (IS_ERR(knullfs_file))
+		panic("VFS: Failed to create the nullfs file stand-in");
+	root.mnt	= knullfs;
+	root.dentry	= knullfs->mnt_root;
 
 	init_task.nsproxy->mnt_ns = &init_mnt_ns;
 	get_mnt_ns(&init_mnt_ns);
diff --git a/fs/notify/mark.c b/fs/notify/mark.c
index b2640d8..d176285 100644
--- a/fs/notify/mark.c
+++ b/fs/notify/mark.c
@@ -903,6 +903,10 @@ static int fsnotify_add_mark_list(struct fsnotify_mark *mark, void *obj,
 	if (WARN_ON(!fsnotify_valid_obj_type(obj_type)))
 		return -EINVAL;
 
+	/* the filesystem doesn't want its objects watched */
+	if (sb && (sb->s_type->fs_flags & FS_DISALLOW_NOTIFY))
+		return -EINVAL;
+
 	/*
 	 * Attach the sb info before attaching a connector to any object on sb.
 	 * The sb info will remain attached as long as sb lives.
diff --git a/fs/nullfs.c b/fs/nullfs.c
index e06352c..e7221bc 100644
--- a/fs/nullfs.c
+++ b/fs/nullfs.c
@@ -3,6 +3,8 @@
 #include <linux/fs/super_types.h>
 #include <linux/fs_context.h>
 #include <linux/magic.h>
+#include <linux/splice.h>
+#include <linux/uio.h>
 
 #include "mount.h"
 
@@ -10,6 +12,121 @@ static const struct super_operations nullfs_super_operations = {
 	.statfs	= simple_statfs,
 };
 
+static loff_t nullfs_dir_llseek(struct file *file, loff_t offset, int whence)
+{
+	/* an empty directory has two entries . and .. at offsets 0 and 1 */
+	return generic_file_llseek_size(file, offset, whence, 2, 2);
+}
+
+static int nullfs_dir_readdir(struct file *file, struct dir_context *ctx)
+{
+	dir_emit_dots(file, ctx);
+	return 0;
+}
+
+/* the one inode of nullfs is shared by every holder, so no locks on it */
+static int nullfs_nolock(struct file *file, int cmd, struct file_lock *fl)
+{
+	return -ENOLCK;
+}
+
+/* what libfs gives an empty directory, plus the refusal of file locks */
+static const struct file_operations nullfs_dir_operations = {
+	.llseek		= nullfs_dir_llseek,
+	.read		= generic_read_dir,
+	.iterate_shared	= nullfs_dir_readdir,
+	.fsync		= noop_fsync,
+	.lock		= nullfs_nolock,
+	.flock		= nullfs_nolock,
+	.fop_flags	= FOP_IMMUTABLE,
+};
+
+static ssize_t nullfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
+{
+	return 0;
+}
+
+static ssize_t nullfs_file_splice_read(struct file *in, loff_t *ppos,
+				       struct pipe_inode_info *pipe,
+				       size_t len, unsigned int flags)
+{
+	return 0;
+}
+
+static ssize_t nullfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
+{
+	size_t count = iov_iter_count(from);
+
+	iov_iter_advance(from, count);
+	return count;
+}
+
+static int nullfs_pipe_to_null(struct pipe_inode_info *pipe,
+			       struct pipe_buffer *buf, struct splice_desc *sd)
+{
+	return sd->len;
+}
+
+static ssize_t nullfs_file_splice_write(struct pipe_inode_info *pipe,
+					struct file *out, loff_t *ppos,
+					size_t len, unsigned int flags)
+{
+	return splice_from_pipe(pipe, out, ppos, len, flags, nullfs_pipe_to_null);
+}
+
+static int nullfs_file_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
+			       struct iattr *attr)
+{
+	if (attr->ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID))
+		return -EPERM;
+	return 0;
+}
+
+static const struct inode_operations nullfs_file_inode_operations = {
+	.setattr	= nullfs_file_setattr,
+};
+
+static const struct file_operations nullfs_file_operations = {
+	.llseek		= generic_file_llseek,
+	.read_iter	= nullfs_file_read_iter,
+	.write_iter	= nullfs_file_write_iter,
+	.splice_read	= nullfs_file_splice_read,
+	.splice_write	= nullfs_file_splice_write,
+	.fsync		= noop_fsync,
+	.lock		= nullfs_nolock,
+	.flock		= nullfs_nolock,
+};
+
+/*
+ * An empty regular file on @sb as a dentry of its own, never hashed under
+ * the root so no lookup finds it.
+ */
+struct dentry *nullfs_new_file(struct super_block *sb)
+{
+	struct dentry *dentry;
+	struct inode *inode;
+
+	inode = new_inode(sb);
+	if (!inode)
+		return ERR_PTR(-ENOMEM);
+
+	/* the root directory is 1 */
+	inode->i_ino = 2;
+	inode->i_mode = S_IFREG | 0666;
+	inode->i_op = &nullfs_file_inode_operations;
+	inode->i_fop = &nullfs_file_operations;
+	simple_inode_init_ts(inode);
+	inode->i_flags |= S_NOATIME;
+
+	dentry = d_alloc_anon(sb);
+	if (!dentry) {
+		iput(inode);
+		return ERR_PTR(-ENOMEM);
+	}
+	d_instantiate(dentry, inode);
+	return dentry;
+}
+
 static int nullfs_fs_fill_super(struct super_block *s, struct fs_context *fc)
 {
 	struct inode *inode;
@@ -30,10 +147,11 @@ static int nullfs_fs_fill_super(struct super_block *s, struct fs_context *fc)
 
 	/* nullfs is permanently empty... */
 	make_empty_dir_inode(inode);
+	inode->i_fop = &nullfs_dir_operations;
 	simple_inode_init_ts(inode);
 	inode->i_ino	= 1;
-	/* ... and immutable. */
-	inode->i_flags |= S_IMMUTABLE;
+	/* ... and immutable, reading it leaves no trace either. */
+	inode->i_flags |= S_IMMUTABLE | S_NOATIME;
 
 	s->s_root = d_make_root(inode);
 	if (!s->s_root)
@@ -61,6 +179,7 @@ static int nullfs_init_fs_context(struct fs_context *fc)
 
 struct file_system_type nullfs_fs_type = {
 	.name			= "nullfs",
+	.fs_flags		= FS_DISALLOW_NOTIFY,
 	.init_fs_context	= nullfs_init_fs_context,
 	.kill_sb		= kill_anon_super,
 };
diff --git a/fs/pnode.c b/fs/pnode.c
index 5d91c3e..5f07fa1 100644
--- a/fs/pnode.c
+++ b/fs/pnode.c
@@ -410,19 +410,99 @@ bool propagation_would_overmount(const struct mount *from,
 	return false;
 }
 
+/* Does @m receive propagation from @parent? */
+static bool receives_from(struct mount *m, struct mount *parent)
+{
+	if (m == parent)
+		return false;
+	for (; m; m = m->mnt_master)
+		if (m == parent || peers(m, parent))
+			return true;
+	return false;
+}
+
+/*
+ * Does @m receive propagation from the victim's parent as well? If so, then
+ * the mount at the victim's mountpoint inside of @m is a umount candidate as
+ * well. So it's the next candidate in the chain. Otherwise the chain ends at
+ * @m.
+ */
+static struct mount *next_candidate(struct mount *m, struct mount *victim)
+{
+	if (!receives_from(m, victim->mnt_parent))
+		return NULL;
+	return __lookup_mnt(&m->mnt, victim->mnt_mountpoint);
+}
+
+/*
+ * Would propagate_umount() pull out a mount of the chain of candidates that
+ * starts at @c, and does that mount have references beyond its own?
+ *
+ * This mirrors how trim_one(), trim_ancestors() and handle_locked() handle a
+ * synchronous umount:
+ *
+ * - single victim
+ * - without children
+ * - with MNT_LOCKED already cleared on every candidate by propagate_mount_unlock()
+ *
+ * A copy of the victim gets unmounted when each of its children is
+ * the next candidate in the chain or its overmount, unless the next
+ * unmount candidate is not its overmount and some unmount candidate further
+ * down has a child outside the chain.  Keep this in sync with
+ * Documentation/filesystems/propagate_umount.txt.
+ */
+static bool chain_busy(struct mount *c, struct mount *victim)
+{
+	struct mount *m, *n, *next, *deepest = NULL;
+	bool above;
+
+	/* the deepest candidate with a child outside the chain */
+	for (m = c; m; m = next) {
+		next = next_candidate(m, victim);
+		list_for_each_entry(n, &m->mnt_mounts, mnt_child) {
+			if (n != next && n != victim) {
+				deepest = m;
+				break;
+			}
+		}
+	}
+
+	above = deepest != NULL;	/* @deepest is at or below @m */
+	for (m = c; m; m = next) {
+		bool goes = true;
+
+		next = next_candidate(m, victim);
+		list_for_each_entry(n, &m->mnt_mounts, mnt_child) {
+			if (n != next && n != m->overmount && n != victim) {
+				goes = false;
+				break;
+			}
+		}
+		if (goes && next && next != m->overmount && above && m != deepest)
+			goes = false;
+		if (m == deepest)
+			above = false;
+		if (goes && do_refcount_check(m, 1))
+			return true;
+	}
+	return false;
+}
+
 /*
  * check if the mount 'mnt' can be unmounted successfully.
  * @mnt: the mount to be checked for unmount
  * NOTE: unmounting 'mnt' would naturally propagate to all
  * other mounts its parent propagates to.
- * Check if any of these mounts that **do not have submounts**
- * have more references than 'refcnt'. If so return busy.
+ * Check if any of the mounts that propagate_umount() would pull out
+ * along with it have more references than their own. If so return busy.
  *
  * vfsmount lock must be held for write
  */
 int propagate_mount_busy(struct mount *mnt, int refcnt)
 {
 	struct mount *parent = mnt->mnt_parent;
+	struct dentry *mp = mnt->mnt_mountpoint;
+	struct mount *m;
 
 	/*
 	 * quickly check if the current mount can be unmounted.
@@ -435,24 +515,16 @@ int propagate_mount_busy(struct mount *mnt, int refcnt)
 	if (mnt == parent)
 		return 0;
 
-	for (struct mount *m = propagation_next(parent, parent); m;
-	     		m = propagation_next(m, parent)) {
-		struct list_head *head;
-		struct mount *child = __lookup_mnt(&m->mnt, mnt->mnt_mountpoint);
+	/* the candidates are the mounts at @mp below the receivers */
+	for (m = propagation_next(parent, parent); m;
+	     m = propagation_next(m, parent)) {
+		struct mount *c = __lookup_mnt(&m->mnt, mp);
 
-		if (!child)
+		/* each chain once, from its top: skip receivers that are candidates */
+		if (!c || (mnt_has_parent(m) && m->mnt_mountpoint == mp &&
+			   receives_from(m->mnt_parent, parent)))
 			continue;
-
-		head = &child->mnt_mounts;
-		if (!list_empty(head)) {
-			/*
-			 * a mount that covers child completely wouldn't prevent
-			 * it being pulled out; any other would.
-			 */
-			if (!list_is_singular(head) || !child->overmount)
-				continue;
-		}
-		if (do_refcount_check(child, 1))
+		if (chain_busy(c, mnt))
 			return 1;
 	}
 	return 0;
@@ -625,8 +697,8 @@ static void handle_locked(struct mount *m, struct list_head *to_umount)
  * the same parent and mountpoint; that will be remedied as soon as we
  * return from propagate_umount() - its caller (umount_tree()) will detach
  * the stack from the parent it (and now @m) is attached to.  umount_tree()
- * might choose to keep unmounted pieces stuck to each other, but it always
- * detaches them from the mounts that remain in the tree.
+ * detaches every unmounted mount from its parent, whether that parent
+ * remains in the tree or not.
  */
 static void reparent(struct mount *m)
 {
diff --git a/fs/readdir.c b/fs/readdir.c
index 76bb1ae..f228884 100644
--- a/fs/readdir.c
+++ b/fs/readdir.c
@@ -87,6 +87,8 @@ EXPORT_SYMBOL(wrap_directory_iterator);
 int iterate_dir(struct file *file, struct dir_context *ctx)
 {
 	struct inode *inode = file_inode(file);
+	/* never an entry, never removed: nothing for the lock to keep still */
+	bool locked = !(file->f_op->fop_flags & FOP_IMMUTABLE);
 	int res = -ENOTDIR;
 
 	if (!file->f_op->iterate_shared)
@@ -100,9 +102,11 @@ int iterate_dir(struct file *file, struct dir_context *ctx)
 	if (res)
 		goto out;
 
-	res = down_read_killable(&inode->i_rwsem);
-	if (res)
-		goto out;
+	if (locked) {
+		res = down_read_killable(&inode->i_rwsem);
+		if (res)
+			goto out;
+	}
 
 	res = -ENOENT;
 	if (!IS_DEADDIR(inode)) {
@@ -112,7 +116,8 @@ int iterate_dir(struct file *file, struct dir_context *ctx)
 		fsnotify_access(file);
 		file_accessed(file);
 	}
-	inode_unlock_shared(inode);
+	if (locked)
+		inode_unlock_shared(inode);
 out:
 	return res;
 }
diff --git a/include/linux/fs.h b/include/linux/fs.h
index f9d1e05..deb411e 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1978,6 +1978,8 @@ struct file_operations {
 #define FOP_ASYNC_LOCK		((__force fop_flags_t)(1 << 6))
 /* File system supports uncached read/write buffered IO */
 #define FOP_DONTCACHE		((__force fop_flags_t)(1 << 7))
+/* Never changes and is never removed, readdir of a directory takes no lock */
+#define FOP_IMMUTABLE		((__force fop_flags_t)(1 << 8))
 
 /* Wrap a directory iterator that needs exclusive inode access */
 int wrap_directory_iterator(struct file *, struct dir_context *,
@@ -2296,6 +2298,7 @@ struct file_system_type {
 #define FS_POWER_FREEZE		256	/* Always freeze on suspend/hibernate */
 #define FS_USERNS_MOUNT_RESTRICTED 512	/* Restrict mount in userns if not already visible */
 #define FS_USERNS_DELEGATABLE	1024	/* Can be mounted inside userns from outside */
+#define FS_DISALLOW_NOTIFY	2048	/* No fsnotify marks on its objects */
 #define FS_RENAME_DOES_D_MOVE	32768	/* FS will handle d_move() during rename() internally. */
 	int (*init_fs_context)(struct fs_context *);
 	const struct fs_parameter_spec *parameters;
diff --git a/include/linux/mount.h b/include/linux/mount.h
index acfe7ef..b16ba5d 100644
--- a/include/linux/mount.h
+++ b/include/linux/mount.h
@@ -52,7 +52,7 @@ enum mount_flags {
 	MNT_ATIME_MASK = MNT_NOATIME | MNT_NODIRATIME | MNT_RELATIME,
 
 	MNT_INTERNAL_FLAGS = MNT_INTERNAL | MNT_DOOMED |
-			     MNT_SYNC_UMOUNT | MNT_LOCKED
+			     MNT_SYNC_UMOUNT | MNT_LOCKED | MNT_UMOUNT
 };
 
 struct vfsmount {
diff --git a/include/linux/nsproxy.h b/include/linux/nsproxy.h
index 5a67648..dc2447f 100644
--- a/include/linux/nsproxy.h
+++ b/include/linux/nsproxy.h
@@ -100,6 +100,7 @@ void exit_cred_namespaces(struct task_struct *tsk);
 void switch_task_namespaces(struct task_struct *tsk, struct nsproxy *new);
 int exec_task_namespaces(void);
 void deactivate_nsproxy(struct nsproxy *ns);
+void nsproxy_free(struct nsproxy *ns);
 int unshare_nsproxy_namespaces(unsigned long, struct nsproxy **,
 	struct cred *, struct fs_struct *);
 int __init nsproxy_cache_init(void);
diff --git a/kernel/fork.c b/kernel/fork.c
index 10f2d05..eb198c8 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -3317,14 +3317,17 @@ int ksys_unshare(unsigned long unshare_flags)
 			shm_init_task(current);
 		}
 
+		if (new_fs) {
+			new_fs = switch_fs_struct(new_fs);
+			if (new_fs)
+				free_fs_struct(no_free_ptr(new_fs));
+		}
+
 		if (new_nsproxy) {
 			switch_task_namespaces(current, new_nsproxy);
 			new_nsproxy = NULL;
 		}
 
-		if (new_fs)
-			new_fs = switch_fs_struct(new_fs);
-
 		if (new_fd) {
 			guard(task_lock)(current);
 			swap(current->files, new_fd);
@@ -3340,8 +3343,9 @@ int ksys_unshare(unsigned long unshare_flags)
 	perf_event_namespaces(current);
 
 bad_unshare_cleanup_nsproxy:
+	/* never installed, so no active references to drop */
 	if (new_nsproxy)
-		put_nsproxy(new_nsproxy);
+		nsproxy_free(new_nsproxy);
 bad_unshare_cleanup_cred:
 	if (new_cred)
 		put_cred(new_cred);
diff --git a/kernel/nsproxy.c b/kernel/nsproxy.c
index d9d3d59..3fb1595 100644
--- a/kernel/nsproxy.c
+++ b/kernel/nsproxy.c
@@ -61,7 +61,7 @@ static inline struct nsproxy *create_nsproxy(void)
 	return nsproxy;
 }
 
-static inline void nsproxy_free(struct nsproxy *ns)
+void nsproxy_free(struct nsproxy *ns)
 {
 	put_mnt_ns(ns->mnt_ns);
 	put_uts_ns(ns->uts_ns);
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 2738539..bc4fdb6 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -43,6 +43,7 @@ TARGETS += filesystems/open_tree_ns
 TARGETS += filesystems/overlayfs
 TARGETS += filesystems/statmount
 TARGETS += filesystems/mount-notify
+TARGETS += filesystems/mount_cycle
 TARGETS += filesystems/nsfs
 TARGETS += filesystems/fuse
 TARGETS += filesystems/move_mount
@@ -50,6 +51,8 @@ TARGETS += filesystems/empty_mntns
 TARGETS += filesystems/fsmount_ns
 TARGETS += filesystems/fscontext_ns
 TARGETS += filesystems/xattr
+TARGETS += filesystems/mntns_unbindable
+TARGETS += filesystems/umount_propagation
 TARGETS += firmware
 TARGETS += fpu
 TARGETS += ftrace
diff --git a/tools/testing/selftests/filesystems/.gitignore b/tools/testing/selftests/filesystems/.gitignore
index 9eb185f..62f7b1c 100644
--- a/tools/testing/selftests/filesystems/.gitignore
+++ b/tools/testing/selftests/filesystems/.gitignore
@@ -7,3 +7,4 @@ anon_inode_test
 kernfs_test
 idmapped_tmpfile
 ustat_test
+rw_hint_test
diff --git a/tools/testing/selftests/filesystems/Makefile b/tools/testing/selftests/filesystems/Makefile
index 03be337..93e2cc9 100644
--- a/tools/testing/selftests/filesystems/Makefile
+++ b/tools/testing/selftests/filesystems/Makefile
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 
 CFLAGS += $(KHDR_INCLUDES)
-TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog ustat_test
+TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog ustat_test rw_hint_test
 TEST_GEN_PROGS += idmapped_tmpfile
 TEST_GEN_PROGS_EXTENDED := dnotify_test
 
diff --git a/tools/testing/selftests/filesystems/empty_mntns/.gitignore b/tools/testing/selftests/filesystems/empty_mntns/.gitignore
index 99f89d3..27bcbca 100644
--- a/tools/testing/selftests/filesystems/empty_mntns/.gitignore
+++ b/tools/testing/selftests/filesystems/empty_mntns/.gitignore
@@ -2,3 +2,6 @@
 clone3_empty_mntns_test
 empty_mntns_test
 overmount_chroot_test
+internal_sb_reconfigure_test
+nullfs_atime_test
+root_readdir_test
diff --git a/tools/testing/selftests/filesystems/empty_mntns/Makefile b/tools/testing/selftests/filesystems/empty_mntns/Makefile
index 22e3fb9..22af216 100644
--- a/tools/testing/selftests/filesystems/empty_mntns/Makefile
+++ b/tools/testing/selftests/filesystems/empty_mntns/Makefile
@@ -4,9 +4,14 @@ CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) $(TOOLS_INCLUDES)
 LDLIBS += -lcap
 
 TEST_GEN_PROGS := empty_mntns_test overmount_chroot_test clone3_empty_mntns_test
+TEST_GEN_PROGS += internal_sb_reconfigure_test nullfs_atime_test root_readdir_test
+
+LOCAL_HDRS += ../readdir_hold.h
 
 include ../../lib.mk
 
 $(OUTPUT)/empty_mntns_test: ../utils.c
 $(OUTPUT)/overmount_chroot_test: ../utils.c
 $(OUTPUT)/clone3_empty_mntns_test: ../utils.c
+$(OUTPUT)/internal_sb_reconfigure_test: ../utils.c
+$(OUTPUT)/root_readdir_test: LDLIBS += -pthread
diff --git a/tools/testing/selftests/filesystems/empty_mntns/internal_sb_reconfigure_test.c b/tools/testing/selftests/filesystems/empty_mntns/internal_sb_reconfigure_test.c
new file mode 100644
index 0000000..cb645d1
--- /dev/null
+++ b/tools/testing/selftests/filesystems/empty_mntns/internal_sb_reconfigure_test.c
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * The root of an empty mount namespace is a nullfs mount. Its superblock is
+ * kernel-internal and shared by every mount namespace. It can't be
+ * reconfigured, neither through fspick() nor through mount(MS_REMOUNT) nor
+ * through umount() of the root which remounts it read-only.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/statfs.h>
+#include <sys/statvfs.h>
+#include <sys/syscall.h>
+#include <sys/vfs.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "../utils.h"
+#include "../wrappers.h"
+#include "empty_mntns.h"
+#include "kselftest_harness.h"
+
+#ifndef __NR_fspick
+#define __NR_fspick 433
+#endif
+
+static int sys_fspick(int dfd, const char *path, unsigned int flags)
+{
+	return syscall(__NR_fspick, dfd, path, flags);
+}
+
+/* Child exit codes. */
+enum {
+	CHILD_OK,
+	CHILD_USERNS,		/* could not create the user namespace */
+	CHILD_UNSHARE,		/* could not create the empty mount namespace */
+	CHILD_FSPICK,		/* fspick() of the root was not refused with EINVAL */
+	CHILD_REMOUNT,		/* mount(MS_REMOUNT) was not refused with EINVAL */
+	CHILD_UMOUNT,		/* umount() of the root succeeded */
+	CHILD_STATFS,		/* statfs() of the root failed */
+	CHILD_RDONLY,		/* the root ended up read-only */
+};
+
+static int empty_mntns_child(void)
+{
+	struct statfs st;
+
+	if (enter_userns())
+		return CHILD_USERNS;
+	if (unshare(UNSHARE_EMPTY_MNTNS))
+		return CHILD_UNSHARE;
+
+	if (sys_fspick(AT_FDCWD, "/", 0) >= 0 || errno != EINVAL)
+		return CHILD_FSPICK;
+	if (!mount(NULL, "/", NULL, MS_REMOUNT | MS_RDONLY, NULL) ||
+	    errno != EINVAL)
+		return CHILD_REMOUNT;
+	if (!umount2("/", 0))
+		return CHILD_UMOUNT;
+	if (statfs("/", &st))
+		return CHILD_STATFS;
+	if (st.f_flags & ST_RDONLY)
+		return CHILD_RDONLY;
+	return CHILD_OK;
+}
+
+FIXTURE(internal_sb_reconfigure) {};
+
+FIXTURE_SETUP(internal_sb_reconfigure)
+{
+	pid_t pid;
+	int status;
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (pid == 0) {
+		if (enter_userns())
+			_exit(1);
+		if (unshare(UNSHARE_EMPTY_MNTNS))
+			_exit(1);
+		_exit(0);
+	}
+	ASSERT_EQ(waitpid(pid, &status, 0), pid);
+	if (!WIFEXITED(status) || WEXITSTATUS(status))
+		SKIP(return, "UNSHARE_EMPTY_MNTNS not supported");
+}
+
+FIXTURE_TEARDOWN(internal_sb_reconfigure) {}
+
+TEST_F(internal_sb_reconfigure, nullfs_root)
+{
+	pid_t pid;
+	int status;
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (pid == 0)
+		_exit(empty_mntns_child());
+	ASSERT_EQ(waitpid(pid, &status, 0), pid);
+	ASSERT_TRUE(WIFEXITED(status));
+	ASSERT_EQ(WEXITSTATUS(status), CHILD_OK);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/filesystems/empty_mntns/nullfs_atime_test.c b/tools/testing/selftests/filesystems/empty_mntns/nullfs_atime_test.c
new file mode 100644
index 0000000..51e34f3
--- /dev/null
+++ b/tools/testing/selftests/filesystems/empty_mntns/nullfs_atime_test.c
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * The root of every empty mount namespace is the same nullfs inode. A read
+ * of it by one user must not change the access time another user sees.
+ */
+#define _GNU_SOURCE
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include "../../kselftest_harness.h"
+
+#ifndef UNSHARE_EMPTY_MNTNS
+#define UNSHARE_EMPTY_MNTNS	0x00100000
+#endif
+
+enum {
+	CHILD_OK,
+	CHILD_UNSUPPORTED,
+	CHILD_SETUP,
+	CHILD_CHANGED,
+};
+
+static int wait_byte(int fd)
+{
+	char c;
+
+	return read(fd, &c, 1) == 1 ? 0 : -1;
+}
+
+static int send_byte(int fd)
+{
+	return write(fd, "x", 1) == 1 ? 0 : -1;
+}
+
+static int empty_mntns(void)
+{
+	if (!unshare(UNSHARE_EMPTY_MNTNS))
+		return 0;
+	return errno == EINVAL ? CHILD_UNSUPPORTED : CHILD_SETUP;
+}
+
+/* the watcher: stats its root before and after the reader read its own */
+static int watcher(int to_reader, int from_reader)
+{
+	struct stat before, after;
+	int ret;
+
+	ret = empty_mntns();
+	if (ret)
+		return ret;
+	if (stat("/", &before))
+		return CHILD_SETUP;
+	if (send_byte(to_reader) || wait_byte(from_reader))
+		return CHILD_SETUP;
+	if (stat("/", &after))
+		return CHILD_SETUP;
+	if (before.st_atim.tv_sec != after.st_atim.tv_sec ||
+	    before.st_atim.tv_nsec != after.st_atim.tv_nsec)
+		return CHILD_CHANGED;
+	return CHILD_OK;
+}
+
+/* the reader: lists its own root, which is the same inode */
+static int reader(int to_watcher, int from_watcher)
+{
+	struct dirent *de;
+	DIR *d;
+	int ret;
+
+	ret = empty_mntns();
+	if (ret)
+		return ret;
+	if (wait_byte(from_watcher))
+		return CHILD_SETUP;
+	d = opendir("/");
+	if (!d)
+		return CHILD_SETUP;
+	while ((de = readdir(d)))
+		;
+	closedir(d);
+	return send_byte(to_watcher) ? CHILD_SETUP : CHILD_OK;
+}
+
+static int wait_child(pid_t pid)
+{
+	int status;
+
+	if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status))
+		return -1;
+	return WEXITSTATUS(status);
+}
+
+TEST(empty_mntns_root_atime)
+{
+	int to_reader[2], to_watcher[2], w, r;
+	pid_t watcher_pid, reader_pid;
+
+	if (geteuid())
+		SKIP(return, "test requires root");
+	ASSERT_EQ(pipe(to_reader), 0);
+	ASSERT_EQ(pipe(to_watcher), 0);
+
+	watcher_pid = fork();
+	ASSERT_GE(watcher_pid, 0);
+	if (watcher_pid == 0)
+		_exit(watcher(to_reader[1], to_watcher[0]));
+	reader_pid = fork();
+	ASSERT_GE(reader_pid, 0);
+	if (reader_pid == 0)
+		_exit(reader(to_watcher[1], to_reader[0]));
+
+	r = wait_child(reader_pid);
+	w = wait_child(watcher_pid);
+	if (r == CHILD_UNSUPPORTED || w == CHILD_UNSUPPORTED)
+		SKIP(return, "UNSHARE_EMPTY_MNTNS not supported");
+	EXPECT_EQ(r, CHILD_OK);
+	EXPECT_EQ(w, CHILD_OK)
+		TH_LOG("the access time of the root changed while this namespace did nothing");
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/filesystems/empty_mntns/root_readdir_test.c b/tools/testing/selftests/filesystems/empty_mntns/root_readdir_test.c
new file mode 100644
index 0000000..0ff5442
--- /dev/null
+++ b/tools/testing/selftests/filesystems/empty_mntns/root_readdir_test.c
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Reading the root of an empty mount namespace holds nothing that anybody
+ * else waits for: the directory never has an entry, so a readdir stuck in
+ * the page fault of its buffer stalls neither a create nor a lookup in it.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "../../kselftest_harness.h"
+#include "../readdir_hold.h"
+#include "empty_mntns.h"
+
+TEST(readdir_blocks_nobody)
+{
+	struct readdir_hold hold;
+	bool stalled;
+	int dfd;
+
+	if (geteuid())
+		SKIP(return, "test requires root");
+	if (readdir_hold_init(&hold))
+		SKIP(return, "test requires userfaultfd");
+	if (unshare(UNSHARE_EMPTY_MNTNS)) {
+		readdir_hold_destroy(&hold);
+		if (errno == EINVAL)
+			SKIP(return, "UNSHARE_EMPTY_MNTNS not supported");
+		ASSERT_TRUE(false)
+			TH_LOG("unshare(UNSHARE_EMPTY_MNTNS): %m");
+	}
+	dfd = open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+	ASSERT_GE(dfd, 0);
+	ASSERT_EQ(readdir_hold_check(&hold, dfd, &stalled), 0);
+	/* the lookup came back while the readdir was stuck in its fault */
+	EXPECT_FALSE(stalled);
+	close(dfd);
+	readdir_hold_destroy(&hold);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/filesystems/mntns_unbindable/Makefile b/tools/testing/selftests/filesystems/mntns_unbindable/Makefile
new file mode 100644
index 0000000..33a311c
--- /dev/null
+++ b/tools/testing/selftests/filesystems/mntns_unbindable/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+TEST_GEN_PROGS := mntns_unbindable_test
+
+CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES)
+
+include ../../lib.mk
diff --git a/tools/testing/selftests/filesystems/mntns_unbindable/mntns_unbindable_test.c b/tools/testing/selftests/filesystems/mntns_unbindable/mntns_unbindable_test.c
new file mode 100644
index 0000000..9aebc37
--- /dev/null
+++ b/tools/testing/selftests/filesystems/mntns_unbindable/mntns_unbindable_test.c
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * An unbindable mount stays unbindable in a cloned mount namespace.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "../../kselftest_harness.h"
+
+#ifndef OPEN_TREE_CLONE
+#define OPEN_TREE_CLONE		1
+#endif
+#ifndef OPEN_TREE_CLOEXEC
+#define OPEN_TREE_CLOEXEC	O_CLOEXEC
+#endif
+
+static int sys_open_tree(int dfd, const char *filename, unsigned int flags)
+{
+	return syscall(__NR_open_tree, dfd, filename, flags);
+}
+
+/* Child exit codes. */
+enum {
+	CHILD_OK,		/* the operation failed with EINVAL as it must */
+	CHILD_ALLOWED,		/* the operation succeeded: the flag was lost */
+	CHILD_UNSHARE,		/* unshare(CLONE_NEWNS) failed */
+	CHILD_ERRNO,		/* the operation failed with some other errno */
+	CHILD_MOUNTINFO,	/* the mount was not found in mountinfo */
+};
+
+FIXTURE(mntns_unbindable)
+{
+	char base[64];
+	char src[80];
+	char dst[80];
+	bool mounted;
+};
+
+FIXTURE_SETUP(mntns_unbindable)
+{
+	self->mounted = false;
+
+	if (geteuid() != 0)
+		SKIP(return, "test requires CAP_SYS_ADMIN");
+
+	ASSERT_EQ(unshare(CLONE_NEWNS), 0);
+	ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0);
+
+	snprintf(self->base, sizeof(self->base), "/tmp/mntns_unbindable.XXXXXX");
+	ASSERT_NE(mkdtemp(self->base), NULL);
+	ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, NULL), 0);
+	self->mounted = true;
+
+	snprintf(self->src, sizeof(self->src), "%s/src", self->base);
+	snprintf(self->dst, sizeof(self->dst), "%s/dst", self->base);
+	ASSERT_EQ(mkdir(self->src, 0755), 0);
+	ASSERT_EQ(mkdir(self->dst, 0755), 0);
+
+	ASSERT_EQ(mount("tmpfs", self->src, "tmpfs", 0, NULL), 0);
+	ASSERT_EQ(mount(NULL, self->src, NULL, MS_UNBINDABLE, NULL), 0);
+}
+
+FIXTURE_TEARDOWN(mntns_unbindable)
+{
+	if (self->mounted)
+		umount2(self->base, MNT_DETACH);
+	rmdir(self->base);
+}
+
+static int classify(int ret, int err)
+{
+	if (ret >= 0)
+		return CHILD_ALLOWED;
+	return err == EINVAL ? CHILD_OK : CHILD_ERRNO;
+}
+
+/* Is the mount on @mountpoint marked unbindable in /proc/self/mountinfo? */
+static int mountinfo_unbindable(const char *mountpoint)
+{
+	char line[4096];
+	FILE *f;
+	int ret = CHILD_MOUNTINFO;
+
+	f = fopen("/proc/self/mountinfo", "re");
+	if (!f)
+		return CHILD_ERRNO;
+
+	while (fgets(line, sizeof(line), f)) {
+		char *fields[6], *p = line, *opt;
+		int i;
+
+		for (i = 0; i < 6; i++) {
+			fields[i] = strsep(&p, " ");
+			if (!fields[i])
+				break;
+		}
+		if (i < 6 || strcmp(fields[4], mountpoint))
+			continue;
+
+		/* the optional fields, up to the "-" separator */
+		ret = CHILD_ALLOWED;
+		while ((opt = strsep(&p, " ")) && strcmp(opt, "-")) {
+			if (!strcmp(opt, "unbindable"))
+				ret = CHILD_OK;
+		}
+		break;
+	}
+	fclose(f);
+	return ret;
+}
+
+static int run_in_child(int (*fn)(const char *src, const char *dst),
+			const char *src, const char *dst)
+{
+	int status;
+	pid_t pid;
+
+	pid = fork();
+	if (pid < 0)
+		return -1;
+	if (pid == 0)
+		_exit(fn(src, dst));
+	if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status))
+		return -1;
+	return WEXITSTATUS(status);
+}
+
+static int bind_after_clone(const char *src, const char *dst)
+{
+	int ret;
+
+	if (unshare(CLONE_NEWNS))
+		return CHILD_UNSHARE;
+	ret = mount(src, dst, NULL, MS_BIND, NULL);
+	return classify(ret, errno);
+}
+
+static int rbind_after_clone(const char *src, const char *dst)
+{
+	int ret;
+
+	if (unshare(CLONE_NEWNS))
+		return CHILD_UNSHARE;
+	ret = mount(src, dst, NULL, MS_BIND | MS_REC, NULL);
+	return classify(ret, errno);
+}
+
+static int open_tree_after_clone(const char *src, const char *dst)
+{
+	int ret;
+
+	if (unshare(CLONE_NEWNS))
+		return CHILD_UNSHARE;
+	ret = sys_open_tree(AT_FDCWD, src, OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
+	return classify(ret, errno);
+}
+
+static int mountinfo_after_clone(const char *src, const char *dst)
+{
+	if (unshare(CLONE_NEWNS))
+		return CHILD_UNSHARE;
+	return mountinfo_unbindable(src);
+}
+
+static int bind_after_two_clones(const char *src, const char *dst)
+{
+	int ret;
+
+	if (unshare(CLONE_NEWNS))
+		return CHILD_UNSHARE;
+	if (unshare(CLONE_NEWNS))
+		return CHILD_UNSHARE;
+	ret = mount(src, dst, NULL, MS_BIND, NULL);
+	return classify(ret, errno);
+}
+
+/* The namespace the mount was made unbindable in. */
+TEST_F(mntns_unbindable, refuses_bind)
+{
+	int ret = mount(self->src, self->dst, NULL, MS_BIND, NULL);
+
+	ASSERT_EQ(classify(ret, errno), CHILD_OK);
+	ASSERT_EQ(mountinfo_unbindable(self->src), CHILD_OK);
+}
+
+/* A copy of the namespace must not turn the mount bindable. */
+TEST_F(mntns_unbindable, refuses_bind_after_clone)
+{
+	ASSERT_EQ(run_in_child(bind_after_clone, self->src, self->dst), CHILD_OK)
+		TH_LOG("bind of an unbindable mount allowed in a copied mount namespace");
+}
+
+TEST_F(mntns_unbindable, refuses_rbind_after_clone)
+{
+	ASSERT_EQ(run_in_child(rbind_after_clone, self->src, self->dst), CHILD_OK)
+		TH_LOG("rbind of an unbindable mount allowed in a copied mount namespace");
+}
+
+TEST_F(mntns_unbindable, refuses_open_tree_after_clone)
+{
+	ASSERT_EQ(run_in_child(open_tree_after_clone, self->src, self->dst), CHILD_OK)
+		TH_LOG("OPEN_TREE_CLONE of an unbindable mount allowed in a copied mount namespace");
+}
+
+TEST_F(mntns_unbindable, mountinfo_after_clone)
+{
+	ASSERT_EQ(run_in_child(mountinfo_after_clone, self->src, self->dst), CHILD_OK)
+		TH_LOG("mountinfo does not show the mount as unbindable in a copied mount namespace");
+}
+
+TEST_F(mntns_unbindable, refuses_bind_after_two_clones)
+{
+	ASSERT_EQ(run_in_child(bind_after_two_clones, self->src, self->dst), CHILD_OK)
+		TH_LOG("bind of an unbindable mount allowed two mount namespace copies down");
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/filesystems/mount_cycle/.gitignore b/tools/testing/selftests/filesystems/mount_cycle/.gitignore
new file mode 100644
index 0000000..30f7dd0
--- /dev/null
+++ b/tools/testing/selftests/filesystems/mount_cycle/.gitignore
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-only
+loop_cycle_test
+unmounted_tree_test
+overmount_reparent_test
+mount_cover_test
+locked_handle_test
+nsfs_rbind_loop_test
+overmount_ns_file_test
diff --git a/tools/testing/selftests/filesystems/mount_cycle/Makefile b/tools/testing/selftests/filesystems/mount_cycle/Makefile
new file mode 100644
index 0000000..fbe8c5e
--- /dev/null
+++ b/tools/testing/selftests/filesystems/mount_cycle/Makefile
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0
+TEST_GEN_PROGS := loop_cycle_test unmounted_tree_test overmount_reparent_test mount_cover_test locked_handle_test
+TEST_GEN_PROGS += nsfs_rbind_loop_test overmount_ns_file_test
+
+CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES)
+
+LOCAL_HDRS += ../readdir_hold.h
+
+include ../../lib.mk
+
+$(OUTPUT)/locked_handle_test: LDLIBS += -pthread
+$(OUTPUT)/mount_cover_test: LDLIBS += -pthread
diff --git a/tools/testing/selftests/filesystems/mount_cycle/config b/tools/testing/selftests/filesystems/mount_cycle/config
new file mode 100644
index 0000000..3bd5ce4
--- /dev/null
+++ b/tools/testing/selftests/filesystems/mount_cycle/config
@@ -0,0 +1,39 @@
+CONFIG_USER_NS=y
+CONFIG_TMPFS=y
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_VFAT_FS=y
+CONFIG_MSDOS_FS=y
+CONFIG_NLS_CODEPAGE_437=y
+CONFIG_NLS_ISO8859_1=y
+CONFIG_MINIX_FS=y
+CONFIG_AUTOFS_FS=y
+CONFIG_ZRAM=y
+CONFIG_ZRAM_WRITEBACK=y
+CONFIG_KEYS=y
+CONFIG_ECRYPT_FS=y
+CONFIG_BINFMT_MISC=y
+CONFIG_FUSE_FS=y
+CONFIG_FUSE_PASSTHROUGH=y
+CONFIG_BLK_DEV_ZONED=y
+CONFIG_BLK_DEV_ZONED_LOOP=y
+CONFIG_CONFIGFS_FS=y
+CONFIG_USB_SUPPORT=y
+CONFIG_USB=y
+CONFIG_USB_STORAGE=y
+CONFIG_SCSI=y
+CONFIG_BLK_DEV_SD=y
+CONFIG_USB_GADGET=y
+CONFIG_USB_DUMMY_HCD=y
+CONFIG_USB_CONFIGFS=y
+CONFIG_USB_CONFIGFS_MASS_STORAGE=y
+CONFIG_MD=y
+CONFIG_BLK_DEV_MD=y
+CONFIG_MD_RAID1=y
+CONFIG_MD_BITMAP=y
+CONFIG_MD_BITMAP_FILE=y
+CONFIG_INOTIFY_USER=y
+CONFIG_DNOTIFY=y
+CONFIG_FANOTIFY=y
+CONFIG_FILE_LOCKING=y
+CONFIG_CRYPTO_AES=y
+CONFIG_USERFAULTFD=y
diff --git a/tools/testing/selftests/filesystems/mount_cycle/locked_handle_test.c b/tools/testing/selftests/filesystems/mount_cycle/locked_handle_test.c
new file mode 100644
index 0000000..b21f434
--- /dev/null
+++ b/tools/testing/selftests/filesystems/mount_cycle/locked_handle_test.c
@@ -0,0 +1,377 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * may_decode_fh() refuses a file handle below a mount with locked children
+ * to root in a user namespace. That has to hold while the mount is lazily
+ * unmounted and its children, the locked ones too, are taken off it.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <sched.h>
+#include <stdatomic.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mount.h>
+#include <sys/prctl.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+
+#include "../../kselftest_harness.h"
+
+#ifndef OPEN_TREE_CLONE
+#define OPEN_TREE_CLONE		1
+#endif
+#ifndef OPEN_TREE_CLOEXEC
+#define OPEN_TREE_CLOEXEC	O_CLOEXEC
+#endif
+#ifndef AT_RECURSIVE
+#define AT_RECURSIVE		0x8000
+#endif
+
+#define DIR_LEN		64
+#define PATH_LEN	128
+
+#define ROUNDS		100	/* lazy umounts raced per test */
+#define RACERS		3	/* threads in open_by_handle_at() */
+#define EXTRA_MOUNTS	64	/* make umount_tree() hold mount_lock longer */
+#define MAX_DELAY_US	3000	/* before the umount */
+#define TAIL_US		2000	/* after it */
+
+struct handle {
+	struct file_handle fh;
+	unsigned char buf[MAX_HANDLE_SZ];
+};
+
+/* exit codes of the child */
+enum {
+	CHILD_OK,
+	CHILD_SETUP,
+	CHILD_DECODED,		/* decoded past a locked child */
+	CHILD_MOUNTED,		/* not refused while mounted */
+	CHILD_UNMOUNTED,	/* not refused once unmounted */
+	CHILD_ALLOWED,		/* refused where nothing is locked */
+	CHILD_NOUSERNS,		/* no user namespace to be had */
+};
+
+static int write_file(const char *path, const char *s)
+{
+	ssize_t n = -1;
+	int fd;
+
+	fd = open(path, O_WRONLY | O_CLOEXEC);
+	if (fd >= 0) {
+		n = write(fd, s, strlen(s));
+		close(fd);
+	}
+	return n == (ssize_t)strlen(s) ? 0 : -1;
+}
+
+/* Become root in a new user namespace with a private mount namespace. */
+static int enter_userns(void)
+{
+	uid_t uid = getuid();
+	gid_t gid = getgid();
+	char map[32];
+
+	prctl(PR_SET_DUMPABLE, 1);
+	/* EINVAL: no USER_NS, ENOSPC: user.max_user_namespaces is 0, EPERM: an LSM */
+	if (unshare(CLONE_NEWUSER | CLONE_NEWNS))
+		return errno == EINVAL || errno == ENOSPC || errno == EPERM ?
+		       CHILD_NOUSERNS : CHILD_SETUP;
+	if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT)
+		return CHILD_SETUP;
+	snprintf(map, sizeof(map), "0 %d 1", uid);
+	if (write_file("/proc/self/uid_map", map))
+		return CHILD_SETUP;
+	snprintf(map, sizeof(map), "0 %d 1", gid);
+	if (write_file("/proc/self/gid_map", map))
+		return CHILD_SETUP;
+	if (setgid(0) || setuid(0))
+		return CHILD_SETUP;
+	if (mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL))
+		return CHILD_SETUP;
+	return CHILD_OK;
+}
+
+static int get_handle(const char *path, struct handle *h)
+{
+	int mntid;
+
+	h->fh.handle_bytes = MAX_HANDLE_SZ;
+	return name_to_handle_at(AT_FDCWD, path, &h->fh, &mntid, 0);
+}
+
+static int decode(int dfd, struct handle *h)
+{
+	return open_by_handle_at(dfd, &h->fh, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+}
+
+struct race {
+	int dfd;
+	struct handle *h;
+	atomic_int go;
+	atomic_int stop;
+	atomic_int won;		/* the first decoded descriptor */
+};
+
+static void *racer(void *arg)
+{
+	struct race *r = arg;
+
+	while (!atomic_load(&r->go))
+		;
+	while (!atomic_load(&r->stop)) {
+		int none = -1;
+		int fd;
+
+		fd = decode(r->dfd, r->h);
+		if (fd < 0)
+			continue;
+		if (!atomic_compare_exchange_strong(&r->won, &none, fd))
+			close(fd);
+	}
+	return NULL;
+}
+
+/* stop the @n racers started so far and wait for them, they spin on @r */
+static void stop_racers(struct race *r, pthread_t *th, int n)
+{
+	int i;
+
+	atomic_store(&r->stop, 1);
+	atomic_store(&r->go, 1);	/* one still waiting for the start sees stop next */
+	for (i = 0; i < n; i++)
+		pthread_join(th[i], NULL);
+}
+
+/*
+ * Bind @h recursively at @w, or take a detached copy of it, and race
+ * open_by_handle_at() of @inner against the lazy umount. The decoded
+ * descriptor, if there was one, is left in @won.
+ */
+static int race_round(const char *h, const char *w, struct handle *inner,
+		      bool dissolve, int *won)
+{
+	struct race r = { .h = inner, .won = -1 };
+	pthread_t th[RACERS];
+	int treefd = -1, fd, i;
+
+	if (dissolve) {
+		treefd = syscall(__NR_open_tree, AT_FDCWD, h,
+				 OPEN_TREE_CLONE | AT_RECURSIVE | OPEN_TREE_CLOEXEC);
+		if (treefd < 0)
+			return CHILD_SETUP;
+		/* an ordinary descriptor on the copy, treefd's close dissolves it */
+		r.dfd = openat(treefd, ".", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+	} else {
+		if (mount(h, w, NULL, MS_BIND | MS_REC, NULL))
+			return CHILD_SETUP;
+		r.dfd = open(w, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+	}
+	if (r.dfd < 0)
+		return CHILD_SETUP;
+
+	fd = decode(r.dfd, inner);
+	if (fd >= 0 || errno != EPERM) {
+		if (fd >= 0)
+			close(fd);
+		return CHILD_MOUNTED;
+	}
+
+	for (i = 0; i < RACERS; i++) {
+		if (pthread_create(&th[i], NULL, racer, &r)) {
+			stop_racers(&r, th, i);
+			return CHILD_SETUP;
+		}
+	}
+	atomic_store(&r.go, 1);
+	usleep(rand() % MAX_DELAY_US);
+	if (dissolve)
+		close(treefd);
+	else if (umount2(w, MNT_DETACH)) {
+		stop_racers(&r, th, RACERS);
+		return CHILD_SETUP;
+	}
+	usleep(TAIL_US);
+	stop_racers(&r, th, RACERS);
+
+	fd = decode(r.dfd, inner);
+	if (fd >= 0) {
+		close(fd);
+		return CHILD_UNMOUNTED;
+	}
+	close(r.dfd);
+	*won = atomic_load(&r.won);
+	return CHILD_OK;
+}
+
+static int race_child(const char *h, const char *w, struct handle *inner,
+		      bool dissolve)
+{
+	int ret, won, i;
+
+	srand(getpid());
+	ret = enter_userns();
+	if (ret)
+		return ret;
+	for (i = 0; i < ROUNDS; i++) {
+		ret = race_round(h, w, inner, dissolve, &won);
+		if (ret)
+			return ret;
+		if (won >= 0)
+			return CHILD_DECODED;
+	}
+	return CHILD_OK;
+}
+
+/* a bind without locked children decodes while mounted and not after */
+static int allowed_child(const char *plain, const char *w, struct handle *h)
+{
+	int dfd, fd, ret;
+
+	ret = enter_userns();
+	if (ret)
+		return ret;
+	if (mount(plain, w, NULL, MS_BIND | MS_REC, NULL))
+		return CHILD_SETUP;
+	dfd = open(w, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+	if (dfd < 0)
+		return CHILD_SETUP;
+	fd = decode(dfd, h);
+	if (fd < 0)
+		return CHILD_ALLOWED;
+	close(fd);
+	if (umount2(w, MNT_DETACH))
+		return CHILD_SETUP;
+	fd = decode(dfd, h);
+	if (fd >= 0) {
+		close(fd);
+		return CHILD_UNMOUNTED;
+	}
+	return errno == EPERM ? CHILD_OK : CHILD_UNMOUNTED;
+}
+
+FIXTURE(locked_handle) {
+	char base[DIR_LEN];
+	char h[PATH_LEN];		/* the tree with the covered directory */
+	char plain[PATH_LEN];		/* a tree with no mount in it */
+	char w[PATH_LEN];		/* where the child binds either */
+	struct handle inner;		/* h/top/secret/inner, covered */
+	struct handle uncovered;	/* plain/inner */
+};
+
+FIXTURE_SETUP(locked_handle)
+{
+	char p[PATH_LEN], e[PATH_LEN];
+	int i;
+
+	if (geteuid())
+		SKIP(return, "test requires root");
+
+	snprintf(self->base, sizeof(self->base), "/tmp/locked_handle.XXXXXX");
+	ASSERT_NE(mkdtemp(self->base), NULL);
+	ASSERT_EQ(unshare(CLONE_NEWNS), 0);
+	ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0);
+	ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, NULL), 0);
+
+	snprintf(self->h, sizeof(self->h), "%s/h", self->base);
+	snprintf(self->plain, sizeof(self->plain), "%s/plain", self->base);
+	snprintf(self->w, sizeof(self->w), "%s/w", self->base);
+	ASSERT_EQ(mkdir(self->h, 0755), 0);
+	ASSERT_EQ(mkdir(self->plain, 0755), 0);
+	ASSERT_EQ(mkdir(self->w, 0755), 0);
+	snprintf(p, sizeof(p), "%s/plain/inner", self->base);
+	ASSERT_EQ(mkdir(p, 0755), 0);
+	ASSERT_EQ(get_handle(p, &self->uncovered), 0);
+
+	/* the handle is for this filesystem */
+	ASSERT_EQ(mount("tmpfs", self->h, "tmpfs", 0, NULL), 0);
+	snprintf(p, sizeof(p), "%s/h/top", self->base);
+	ASSERT_EQ(mkdir(p, 0755), 0);
+	snprintf(p, sizeof(p), "%s/h/top/secret", self->base);
+	ASSERT_EQ(mkdir(p, 0755), 0);
+	snprintf(p, sizeof(p), "%s/h/top/secret/inner", self->base);
+	ASSERT_EQ(mkdir(p, 0755), 0);
+	ASSERT_EQ(get_handle(p, &self->inner), 0);
+	snprintf(e, sizeof(e), "%s/h/empty", self->base);
+	ASSERT_EQ(mkdir(e, 0755), 0);
+
+	/* cover it, and some more so the umount takes longer */
+	snprintf(p, sizeof(p), "%s/h/top/secret", self->base);
+	ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, NULL), 0);
+	for (i = 0; i < EXTRA_MOUNTS; i++) {
+		snprintf(p, sizeof(p), "%s/h/c%d", self->base, i);
+		ASSERT_EQ(mkdir(p, 0755), 0);
+		ASSERT_EQ(mount(e, p, NULL, MS_BIND, NULL), 0);
+	}
+}
+
+FIXTURE_TEARDOWN(locked_handle)
+{
+	umount2(self->base, MNT_DETACH);
+	rmdir(self->base);
+}
+
+static int wait_child(pid_t pid)
+{
+	int status;
+
+	if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status))
+		return -1;
+	return WEXITSTATUS(status);
+}
+
+TEST_F(locked_handle, lazy_umount)
+{
+	pid_t pid;
+	int ret;
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (pid == 0)
+		_exit(race_child(self->h, self->w, &self->inner, false));
+	ret = wait_child(pid);
+	TH_LOG("child exit code %d", ret);
+	if (ret == CHILD_NOUSERNS)
+		SKIP(return, "no user namespaces");
+	EXPECT_EQ(ret, CHILD_OK);
+}
+
+TEST_F(locked_handle, dissolve)
+{
+	pid_t pid;
+	int ret;
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (pid == 0)
+		_exit(race_child(self->h, self->w, &self->inner, true));
+	ret = wait_child(pid);
+	TH_LOG("child exit code %d", ret);
+	if (ret == CHILD_NOUSERNS)
+		SKIP(return, "no user namespaces");
+	EXPECT_EQ(ret, CHILD_OK);
+}
+
+TEST_F(locked_handle, allowed_use)
+{
+	pid_t pid;
+	int ret;
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (pid == 0)
+		_exit(allowed_child(self->plain, self->w, &self->uncovered));
+	ret = wait_child(pid);
+	TH_LOG("child exit code %d", ret);
+	if (ret == CHILD_NOUSERNS)
+		SKIP(return, "no user namespaces");
+	EXPECT_EQ(ret, CHILD_OK);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/filesystems/mount_cycle/loop_cycle_test.c b/tools/testing/selftests/filesystems/mount_cycle/loop_cycle_test.c
new file mode 100644
index 0000000..6b4f530
--- /dev/null
+++ b/tools/testing/selftests/filesystems/mount_cycle/loop_cycle_test.c
@@ -0,0 +1,1542 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A mount that another namespace's rmdir detached, or that went down with
+ * the detached tree it was in when the tree's last fd was closed, keeps
+ * its submounts connected, and a connected submount is put by its
+ * parent's final mntput(). A submount whose filesystem keeps a file open
+ * on the parent then holds the parent's count above zero for good:
+ * nothing in userspace refers to either mount any more and nothing can
+ * release them. A loop device is the simplest such filesystem, its
+ * backing file sits on the parent.
+ */
+#define _GNU_SOURCE
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <stdbool.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <linux/fuse.h>
+#include <linux/keyctl.h>
+#include <linux/major.h>
+#include <linux/raid/md_u.h>
+#include <linux/raid/md_p.h>
+#include <linux/loop.h>
+#include <linux/magic.h>
+#include <sys/syscall.h>
+#include <sys/sysmacros.h>
+#include <sys/uio.h>
+
+#include "../wrappers.h"
+#include "../../kselftest_harness.h"
+
+#define IMAGE_SIZE	(1440 * 1024)
+#define SECTOR		512
+
+/*
+ * The tmpfs of the fixture. A directory of its own from mkdtemp(), so that
+ * the test needs no writable root directory and two instances don't take
+ * each other's mounts down.
+ */
+static char base[64];
+
+/* The configfs directory of the gadget, named after the test process. */
+static char gadget[96];
+
+/* A path below the base directory. Eight of them can be in use at a time. */
+static const char *at(const char *rel)
+{
+	static char buf[8][PATH_MAX];
+	static unsigned int next;
+	char *p = buf[next++ % 8];
+
+	snprintf(p, PATH_MAX, "%s/%s", base, rel);
+	return p;
+}
+
+/* A path below the gadget's configfs directory. */
+static const char *gat(const char *rel)
+{
+	static char buf[4][PATH_MAX];
+	static unsigned int next;
+	char *p = buf[next++ % 4];
+
+	snprintf(p, PATH_MAX, "%s%s", gadget, rel);
+	return p;
+}
+
+/* The loop devices this process has bound, to release them on every path. */
+#define MAX_LOOPS	4
+static int bound[MAX_LOOPS];
+static int nr_bound;
+
+/* What a child tells its parent: the loop devices it has bound, -1 for none. */
+struct report {
+	int n[2];
+};
+
+/* A blank FAT12 floppy image: boot sector, two FATs, an empty root directory. */
+static int write_fat12(int fd)
+{
+	struct stat st;
+	unsigned char sector[SECTOR] = {
+		0xeb, 0x3c, 0x90, 'M', 'S', 'W', 'I', 'N', '4', '.', '1',
+		[11] = 0x00, 0x02,	/* bytes per sector: 512 */
+		[13] = 1,		/* sectors per cluster */
+		[14] = 1, 0,		/* reserved sectors */
+		[16] = 2,		/* FATs */
+		[17] = 0xe0, 0x00,	/* root directory entries: 224 */
+		[19] = 0x40, 0x0b,	/* total sectors: 2880 */
+		[21] = 0xf0,		/* media descriptor */
+		[22] = 9, 0,		/* sectors per FAT */
+		[24] = 18, 0,		/* sectors per track */
+		[26] = 2, 0,		/* heads */
+		[38] = 0x29,		/* extended boot signature */
+		[39] = 0x12, 0x34, 0x56, 0x78,
+		[43] = 'N', 'O', ' ', 'N', 'A', 'M', 'E', ' ', ' ', ' ', ' ',
+		[54] = 'F', 'A', 'T', '1', '2', ' ', ' ', ' ',
+		[510] = 0x55, 0xaa,
+	};
+	unsigned char fat[SECTOR] = { 0xf0, 0xff, 0xff };
+
+	if (pwrite(fd, sector, SECTOR, 0) != SECTOR)
+		return -1;
+	/* the first FAT and the second one, one sector each is enough */
+	if (pwrite(fd, fat, SECTOR, 1 * SECTOR) != SECTOR ||
+	    pwrite(fd, fat, SECTOR, 10 * SECTOR) != SECTOR)
+		return -1;
+	if (fstat(fd, &st) || S_ISBLK(st.st_mode))
+		return 0;
+	return ftruncate(fd, IMAGE_SIZE);
+}
+
+/*
+ * A blank FAT12 image with 4 KiB sectors and @sectors of them, for a device
+ * with 4 KiB logical blocks (zram) or for a bigger image than a floppy.
+ */
+static int write_fat12_4k(int fd, unsigned int sectors)
+{
+	unsigned int fat_sectors = (sectors * 3 / 2 + 4095) / 4096;
+	unsigned char sector[4096] = {
+		0xeb, 0x3c, 0x90, 'M', 'S', 'W', 'I', 'N', '4', '.', '1',
+		[11] = 0x00, 0x10,	/* bytes per sector: 4096 */
+		[13] = 1,		/* sectors per cluster */
+		[14] = 1, 0,		/* reserved sectors */
+		[16] = 2,		/* FATs */
+		[17] = 128, 0,		/* root directory entries: one sector */
+		[19] = sectors & 0xff, sectors >> 8,
+		[21] = 0xf8,		/* media descriptor */
+		[22] = fat_sectors, 0,
+		[24] = 63, 0,		/* sectors per track */
+		[26] = 255, 0,		/* heads */
+		[38] = 0x29,		/* extended boot signature */
+		[39] = 0x12, 0x34, 0x56, 0x78,
+		[43] = 'N', 'O', ' ', 'N', 'A', 'M', 'E', ' ', ' ', ' ', ' ',
+		[54] = 'F', 'A', 'T', '1', '2', ' ', ' ', ' ',
+		[510] = 0x55, 0xaa,
+	};
+	unsigned char fat[4096] = { 0xf8, 0xff, 0xff };
+	struct stat st;
+
+	if (pwrite(fd, sector, sizeof(sector), 0) != sizeof(sector))
+		return -1;
+	if (pwrite(fd, fat, sizeof(fat), 1 * 4096) != sizeof(fat) ||
+	    pwrite(fd, fat, sizeof(fat), (1 + fat_sectors) * 4096) != sizeof(fat))
+		return -1;
+	if (fstat(fd, &st) || S_ISBLK(st.st_mode))
+		return 0;
+	return ftruncate(fd, (off_t)sectors * 4096);
+}
+
+#define MINIX_BLOCK	1024
+#define MINIX_BLOCKS	4096			/* a 4 MiB image */
+#define MINIX_INODES	512
+#define MINIX_ITABLE	(MINIX_INODES * 32 / MINIX_BLOCK)
+#define MINIX_FIRSTDATA	(2 + 1 + 1 + MINIX_ITABLE)	/* boot, super, imap, zmap, inodes */
+
+/*
+ * A blank minix v1 image, for the holders that need a FIFO or a device
+ * node on the dying mount, which vfat can't hold. Superblock in block 1,
+ * one block each for the inode and zone bitmaps, the inode table, and
+ * the root directory in the first data zone.
+ */
+static int write_minix(int fd)
+{
+	struct {
+		__u16 s_ninodes, s_nzones, s_imap_blocks, s_zmap_blocks;
+		__u16 s_firstdatazone, s_log_zone_size;
+		__u32 s_max_size;
+		__u16 s_magic, s_state;
+	} sb = {
+		.s_ninodes = MINIX_INODES,
+		.s_nzones = MINIX_BLOCKS,
+		.s_imap_blocks = 1,
+		.s_zmap_blocks = 1,
+		.s_firstdatazone = MINIX_FIRSTDATA,
+		.s_max_size = (7 + 512 + 512 * 512) * MINIX_BLOCK,
+		.s_magic = MINIX_SUPER_MAGIC,
+		.s_state = 1,		/* MINIX_VALID_FS */
+	};
+	struct {
+		__u16 i_mode, i_uid;
+		__u32 i_size, i_time;
+		__u8 i_gid, i_nlinks;
+		__u16 i_zone[9];
+	} root = {
+		.i_mode = S_IFDIR | 0755,
+		.i_size = 2 * 16,
+		.i_nlinks = 2,
+		.i_zone = { MINIX_FIRSTDATA },
+	};
+	unsigned char imap[MINIX_BLOCK], zmap[MINIX_BLOCK], dir[MINIX_BLOCK] = {};
+	int i;
+
+	/* bit 0 is reserved in both maps, the root inode and its zone are in use */
+	memset(imap, 0xff, sizeof(imap));
+	for (i = 2; i <= MINIX_INODES; i++)
+		imap[i / 8] &= ~(1 << (i % 8));
+	memset(zmap, 0xff, sizeof(zmap));
+	for (i = 2; i <= MINIX_BLOCKS - MINIX_FIRSTDATA; i++)
+		zmap[i / 8] &= ~(1 << (i % 8));
+	dir[0] = 1;
+	dir[2] = '.';
+	dir[16] = 1;
+	dir[18] = '.';
+	dir[19] = '.';
+
+	if (pwrite(fd, &sb, sizeof(sb), 1 * MINIX_BLOCK) != sizeof(sb) ||
+	    pwrite(fd, imap, sizeof(imap), 2 * MINIX_BLOCK) != sizeof(imap) ||
+	    pwrite(fd, zmap, sizeof(zmap), 3 * MINIX_BLOCK) != sizeof(zmap) ||
+	    pwrite(fd, &root, sizeof(root), 4 * MINIX_BLOCK) != sizeof(root) ||
+	    pwrite(fd, dir, sizeof(dir), MINIX_FIRSTDATA * MINIX_BLOCK) != sizeof(dir))
+		return -1;
+	return ftruncate(fd, (off_t)MINIX_BLOCKS * MINIX_BLOCK);
+}
+
+static int read_sysfs(const char *path, char *buf, size_t size)
+{
+	ssize_t n;
+	int fd;
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0)
+		return -1;
+	n = read(fd, buf, size - 1);
+	close(fd);
+	if (n < 0)
+		return -1;
+	buf[n] = '\0';
+	return 0;
+}
+
+/* Is @dev the mount source of this line of mountinfo? loop1 is not loop10. */
+static bool line_has_source(const char *line, const char *dev)
+{
+	const char *sep, *src, *end;
+
+	sep = strstr(line, " - ");
+	if (!sep)
+		return false;
+	src = strchr(sep + 3, ' ');	/* skip the filesystem type */
+	if (!src)
+		return false;
+	src++;
+	end = strchr(src, ' ');
+	if (!end)
+		return false;
+	return (size_t)(end - src) == strlen(dev) && !strncmp(src, dev, end - src);
+}
+
+/*
+ * Does a mount namespace of a process that /proc shows have a mount of @dev?
+ * The mounts under test are in no namespace at this point on any kernel, so
+ * this only checks that the test got as far as it thinks.
+ */
+static bool mounted_anywhere(const char *dev)
+{
+	char path[PATH_MAX], line[4096];
+	struct dirent *de;
+	bool found = false;
+	DIR *proc;
+	FILE *f;
+
+	proc = opendir("/proc");
+	if (!proc)
+		return false;
+	while (!found && (de = readdir(proc))) {
+		if (de->d_name[0] < '0' || de->d_name[0] > '9')
+			continue;
+		snprintf(path, sizeof(path), "/proc/%s/mountinfo", de->d_name);
+		f = fopen(path, "re");
+		if (!f)
+			continue;
+		while (fgets(line, sizeof(line), f)) {
+			if (line_has_source(line, dev)) {
+				found = true;
+				break;
+			}
+		}
+		fclose(f);
+	}
+	closedir(proc);
+	return found;
+}
+
+/* Wait up to @ms milliseconds for the loop device to give up its backing file. */
+static bool loop_released(const char *sysfs, int ms)
+{
+	char buf[PATH_MAX];
+
+	for (; ms > 0; ms -= 100) {
+		if (read_sysfs(sysfs, buf, sizeof(buf)) < 0)
+			return errno == ENOENT;
+		usleep(100000);
+	}
+	return read_sysfs(sysfs, buf, sizeof(buf)) < 0 && errno == ENOENT;
+}
+
+/* Tell the loop device to give its file up, now or when its last user is gone. */
+static void loop_clear(int n)
+{
+	char dev[32];
+	int lfd;
+
+	if (n < 0)
+		return;
+	snprintf(dev, sizeof(dev), "/dev/loop%d", n);
+	lfd = open(dev, O_RDWR);
+	if (lfd < 0)
+		return;
+	ioctl(lfd, LOOP_CLR_FD);
+	close(lfd);
+}
+
+FIXTURE(loop_cycle) {
+	char dev[32];		/* the loop device the child set up */
+	char sysfs[64];		/* its backing_file attribute */
+	int loops[MAX_LOOPS];	/* every loop device the case has bound */
+	int nr_loops;
+	bool gadget;		/* the gadget's configfs directory exists */
+};
+
+static void remember_loop(FIXTURE_DATA(loop_cycle) *self, int n)
+{
+	if (n >= 0 && self->nr_loops < MAX_LOOPS)
+		self->loops[self->nr_loops++] = n;
+}
+
+FIXTURE_SETUP(loop_cycle)
+{
+	if (geteuid() != 0)
+		SKIP(return, "test requires CAP_SYS_ADMIN");
+	if (access("/dev/loop-control", R_OK | W_OK))
+		SKIP(return, "test requires loop devices");
+
+	ASSERT_EQ(unshare(CLONE_NEWNS), 0);
+	ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0);
+
+	snprintf(base, sizeof(base), "/tmp/loop_cycle.XXXXXX");
+	ASSERT_NE(mkdtemp(base), NULL);
+	/* a failed assertion in the setup does not run the teardown */
+	ASSERT_EQ(mount("tmpfs", base, "tmpfs", 0, NULL), 0)
+		rmdir(base);
+	snprintf(gadget, sizeof(gadget),
+		 "/sys/kernel/config/usb_gadget/kselftest_cycle_%d", getpid());
+	self->dev[0] = '\0';
+	self->nr_loops = 0;
+	self->gadget = false;
+}
+
+static int mount_configfs(void);
+static void gadget_remove(void);
+
+FIXTURE_TEARDOWN(loop_cycle)
+{
+	if (self->gadget)
+		gadget_remove();
+	/* whatever a failed assertion has left bound */
+	for (int i = 0; i < self->nr_loops; i++)
+		loop_clear(self->loops[i]);
+	umount2(base, MNT_DETACH);
+	rmdir(base);
+}
+
+/* Child exit codes. */
+enum {
+	CHILD_OK,
+	CHILD_NS,		/* could not set up the namespace or the tmpfs */
+	CHILD_IMAGE,		/* could not write the image */
+	CHILD_LOOP,		/* could not set up the loop device */
+	CHILD_MOUNT,		/* could not mount it (vfat and msdos both refused) */
+	CHILD_PIPE,		/* the parent went away */
+	CHILD_HOLDER,		/* could not set the holder up below the mount */
+	CHILD_SKIP,		/* the kernel lacks what the holder needs */
+	CHILD_NOFS,		/* the kernel lacks the filesystem of the image */
+};
+
+/* Bind the loop device that LOOP_CTL_GET_FREE names to @ifd; the device number. */
+static int loop_bind_free(int ifd)
+{
+	int cfd, lfd, n;
+	char dev[32];
+
+	cfd = open("/dev/loop-control", O_RDWR);
+	if (cfd < 0)
+		return -1;
+	n = ioctl(cfd, LOOP_CTL_GET_FREE);
+	close(cfd);
+	if (n < 0)
+		return -1;
+	snprintf(dev, sizeof(dev), "/dev/loop%d", n);
+	lfd = open(dev, O_RDWR);
+	if (lfd < 0)
+		return -1;
+	if (ioctl(lfd, LOOP_SET_FD, ifd))
+		n = -1;
+	close(lfd);
+	return n;
+}
+
+/*
+ * Bind a free loop device to the open image @ifd; the device number. The
+ * device is free when LOOP_CTL_GET_FREE names it and may be somebody else's
+ * a moment later, so try again when it is busy.
+ */
+static int loop_bind(int ifd)
+{
+	int n = -1;
+
+	for (int i = 0; i < 64 && n < 0; i++) {
+		n = loop_bind_free(ifd);
+		if (n < 0 && errno != EBUSY)
+			return -1;
+	}
+	if (n >= 0 && nr_bound < MAX_LOOPS)
+		bound[nr_bound++] = n;
+	return n;
+}
+
+/* Mount a FAT image, as vfat or as msdos; -1 with ENODEV if the kernel has neither. */
+static int mount_fat(const char *dev, const char *mp)
+{
+	int err;
+
+	if (!mount(dev, mp, "vfat", 0, NULL))
+		return 0;
+	err = errno;
+	if (!mount(dev, mp, "msdos", 0, NULL))
+		return 0;
+	if (err != ENODEV)
+		errno = err;
+	return -1;
+}
+
+/*
+ * A child that gives up has to take down what it has set up: nobody else
+ * knows about it. Unmount, then tell the loop devices to let go. The mounts
+ * are released after this process is gone and the devices follow them.
+ */
+static int child_fails(int ret)
+{
+	umount2(at("vol"), MNT_DETACH);
+	umount2(at("vol2"), MNT_DETACH);
+	umount2(at("p"), MNT_DETACH);
+	umount2(at("img"), MNT_DETACH);
+	for (int i = 0; i < nr_bound; i++)
+		loop_clear(bound[i]);
+	return ret;
+}
+
+/* Write an image to @img, bind a loop device to it and mount that at @mp; the device number. */
+static int loop_mount(const char *img, const char *mp)
+{
+	char dev[32];
+	int ifd, n;
+
+	ifd = open(img, O_RDWR | O_CREAT | O_EXCL, 0600);
+	if (ifd < 0 || write_fat12(ifd))
+		return -CHILD_IMAGE;
+	n = loop_bind(ifd);
+	close(ifd);	/* the loop device holds the file from now on */
+	if (n < 0)
+		return -CHILD_LOOP;
+	snprintf(dev, sizeof(dev), "/dev/loop%d", n);
+	if (mkdir(mp, 0755))
+		return -CHILD_MOUNT;
+	if (mount_fat(dev, mp))
+		return errno == ENODEV ? -CHILD_NOFS : -CHILD_MOUNT;
+	return n;
+}
+
+/*
+ * Two tmpfs mounts, each carrying the image of the loop mount below the
+ * other: the loop mount below vol has its image on vol2 and the other way
+ * round.
+ */
+static int crossed_child(int to_parent, int from_parent)
+{
+	struct report r;
+	char c;
+
+	if (unshare(CLONE_NEWNS) || mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL))
+		return CHILD_NS;
+	if (mkdir(at("vol"), 0755) || mount("tmpfs", at("vol"), "tmpfs", 0, NULL) ||
+	    mkdir(at("vol2"), 0755) || mount("tmpfs", at("vol2"), "tmpfs", 0, NULL))
+		return child_fails(CHILD_NS);
+	r.n[0] = loop_mount(at("vol2/img"), at("vol/mnt"));
+	if (r.n[0] < 0)
+		return child_fails(-r.n[0]);
+	r.n[1] = loop_mount(at("vol/img"), at("vol2/mnt"));
+	if (r.n[1] < 0)
+		return child_fails(-r.n[1]);
+	if (write(to_parent, &r, sizeof(r)) != sizeof(r))
+		return child_fails(CHILD_PIPE);
+	if (read(from_parent, &c, 1) != 1)
+		return child_fails(CHILD_PIPE);
+	return CHILD_OK;
+}
+
+/*
+ * In its own mount namespace the child mounts a tmpfs on vol,
+ * puts a filesystem image on it, binds a loop device to the image and
+ * mounts that loop device below. The loop device's backing file is a
+ * reference on the mount the image is on, held by the loop device, held
+ * by the mounted filesystem, held by the mount below.
+ */
+static int loop_child(int to_parent, int from_parent)
+{
+	struct report r = { { -1, -1 } };
+	char c;
+
+	if (unshare(CLONE_NEWNS) || mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL))
+		return CHILD_NS;
+	if (mkdir(at("vol"), 0755) || mount("tmpfs", at("vol"), "tmpfs", 0, NULL))
+		return child_fails(CHILD_NS);
+	r.n[0] = loop_mount(at("vol/img"), at("vol/mnt"));
+	if (r.n[0] < 0)
+		return child_fails(-r.n[0]);
+
+	if (write(to_parent, &r, sizeof(r)) != sizeof(r))
+		return child_fails(CHILD_PIPE);
+	/* keep the namespace alive while the parent removes the directory */
+	if (read(from_parent, &c, 1) != 1)
+		return child_fails(CHILD_PIPE);
+	return CHILD_OK;
+}
+
+/*
+ * The child did not report. Skip if the kernel lacks something, fail with
+ * what the child said otherwise. A child that a signal killed has failed.
+ */
+#define CHILD_GAVE_UP(pid) do {						\
+	int __status;							\
+									\
+	ASSERT_EQ(waitpid(pid, &__status, 0), pid);			\
+	if (WIFEXITED(__status) && WEXITSTATUS(__status) == CHILD_NOFS)	\
+		SKIP(return, "test requires the filesystem of the image (FAT or minix)"); \
+	if (WIFEXITED(__status) && WEXITSTATUS(__status) == CHILD_SKIP)	\
+		SKIP(return, "the kernel lacks what this holder needs"); \
+	ASSERT_TRUE(false)						\
+		TH_LOG("child failed to set up: %s %d",			\
+		       WIFEXITED(__status) ? "exit status" : "signal",	\
+		       WIFEXITED(__status) ? WEXITSTATUS(__status) : WTERMSIG(__status)); \
+} while (0)
+
+/* The child has to leave by itself and with nothing to complain about. */
+#define CHILD_LEFT(pid) do {						\
+	int __status;							\
+									\
+	ASSERT_EQ(waitpid(pid, &__status, 0), pid);			\
+	ASSERT_TRUE(WIFEXITED(__status));				\
+	ASSERT_EQ(WEXITSTATUS(__status), CHILD_OK);			\
+} while (0)
+
+/*
+ * An exclusive open of the device fails while a filesystem holds it, and
+ * the only filesystem that ever did is the one mounted below the dead
+ * mount. Give a release in flight a moment. Then the device must clear
+ * right away rather than only be marked for autoclear.
+ */
+static void assert_loop_released(struct __test_metadata *_metadata,
+				 FIXTURE_DATA(loop_cycle) *self)
+{
+	int lfd;
+
+	for (int i = 0; i < 20; i++) {
+		lfd = open(self->dev, O_RDONLY | O_EXCL);
+		if (lfd >= 0)
+			break;
+		usleep(100000);
+	}
+	EXPECT_GE(lfd, 0)
+		TH_LOG("%s is still held by the loop mount below the dead mount: nothing refers to either mount and nothing can release them",
+		       self->dev);
+	if (lfd >= 0)
+		close(lfd);
+
+	lfd = open(self->dev, O_RDWR);
+	ASSERT_GE(lfd, 0);
+	ASSERT_EQ(ioctl(lfd, LOOP_CLR_FD), 0);
+	close(lfd);
+	ASSERT_TRUE(loop_released(self->sysfs, 5000))
+		TH_LOG("%s kept its backing file after LOOP_CLR_FD: the filesystem on it is still mounted somewhere nobody can reach",
+		       self->dev);
+	/* somebody else may bind it from now on, so the teardown leaves it alone */
+	for (int i = 0; i < self->nr_loops; i++) {
+		char dev[32];
+
+		snprintf(dev, sizeof(dev), "/dev/loop%d", self->loops[i]);
+		if (!strcmp(dev, self->dev))
+			self->loops[i] = -1;
+	}
+}
+
+/*
+ * rmdir of vol from here, where it is not a mountpoint, detaches
+ * the child's tmpfs with the loop mount connected below it. Once the child
+ * is gone nothing refers to either mount. The loop device must then be
+ * free to give up its backing file, which only happens when the mounted
+ * filesystem below the detached tmpfs has been released.
+ */
+TEST_F(loop_cycle, detached_loop_mount_released)
+{
+	int to_parent[2], to_child[2];
+	struct report r = { { -1, -1 } };
+	char buf[PATH_MAX];
+	pid_t pid;
+
+	ASSERT_EQ(pipe(to_parent), 0);
+	ASSERT_EQ(pipe(to_child), 0);
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (pid == 0) {
+		close(to_parent[0]);
+		close(to_child[1]);
+		_exit(loop_child(to_parent[1], to_child[0]));
+	}
+	close(to_parent[1]);
+	close(to_child[0]);
+
+	if (read(to_parent[0], &r, sizeof(r)) != sizeof(r))
+		CHILD_GAVE_UP(pid);
+	remember_loop(self, r.n[0]);
+	snprintf(self->dev, sizeof(self->dev), "/dev/loop%d", r.n[0]);
+	snprintf(self->sysfs, sizeof(self->sysfs), "/sys/block/loop%d/loop/backing_file", r.n[0]);
+	ASSERT_EQ(read_sysfs(self->sysfs, buf, sizeof(buf)), 0);
+	ASSERT_NE(strstr(buf, "/vol/img"), NULL);
+
+	/* not a mountpoint in this namespace, so the directory can go */
+	ASSERT_EQ(rmdir(at("vol")), 0);
+
+	/* the child leaves: its namespace and every reference it held are gone */
+	ASSERT_EQ(write(to_child[1], "", 1), 1);
+	CHILD_LEFT(pid);
+	close(to_parent[0]);
+	close(to_child[1]);
+
+	/* nothing can reach the two mounts any more */
+	ASSERT_EQ(access(at("vol"), F_OK), -1);
+	ASSERT_FALSE(mounted_anywhere(self->dev));
+
+	assert_loop_released(_metadata, self);
+}
+
+/*
+ * The same two mounts in a detached tree: a clone of vol from
+ * open_tree(), the image opened through the clone so that the loop device
+ * holds the clone, and the loop mount moved below the clone. The last
+ * close of the tree's fd dissolves the tree with the loop mount left
+ * connected below the dead clone, and nothing refers to either afterwards.
+ */
+TEST_F(loop_cycle, dissolved_tree_loop_mount_released)
+{
+	int tfd, ifd, fsfd, mfd, n;
+	char buf[PATH_MAX];
+
+	fsfd = sys_fsopen("vfat", 0);
+	if (fsfd < 0)
+		fsfd = sys_fsopen("msdos", 0);
+	if (fsfd < 0)
+		SKIP(return, "test requires a FAT filesystem");
+
+	ASSERT_EQ(mkdir(at("vol"), 0755), 0);
+	ASSERT_EQ(mount("tmpfs", at("vol"), "tmpfs", 0, NULL), 0);
+	tfd = sys_open_tree(AT_FDCWD, at("vol"), OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
+	ASSERT_GE(tfd, 0);
+
+	/* the image, opened through the clone: the loop device holds the clone */
+	ifd = openat(tfd, "img", O_RDWR | O_CREAT | O_EXCL, 0600);
+	ASSERT_GE(ifd, 0);
+	ASSERT_EQ(write_fat12(ifd), 0);
+	n = loop_bind(ifd);
+	close(ifd);
+	ASSERT_GE(n, 0);
+	remember_loop(self, n);
+	snprintf(self->dev, sizeof(self->dev), "/dev/loop%d", n);
+	snprintf(self->sysfs, sizeof(self->sysfs), "/sys/block/loop%d/loop/backing_file", n);
+
+	/* the loop mount, moved below the clone */
+	ASSERT_EQ(mkdirat(tfd, "mnt", 0755), 0);
+	ASSERT_EQ(sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "source", self->dev, 0), 0);
+	ASSERT_EQ(sys_fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0), 0);
+	mfd = sys_fsmount(fsfd, 0, 0);
+	ASSERT_GE(mfd, 0);
+	close(fsfd);
+	ASSERT_EQ(sys_move_mount(mfd, "", tfd, "mnt", MOVE_MOUNT_F_EMPTY_PATH), 0);
+	close(mfd);
+
+	ASSERT_EQ(read_sysfs(self->sysfs, buf, sizeof(buf)), 0);
+	ASSERT_NE(strstr(buf, "img"), NULL);
+
+	/* the last fd of the tree: both mounts die, the loop mount connected */
+	close(tfd);
+
+	/* nothing can reach the two mounts any more */
+	ASSERT_FALSE(mounted_anywhere(self->dev));
+
+	assert_loop_released(_metadata, self);
+}
+
+/*
+ * The cycle in two steps: rmdir of vol leaves the loop mount below it
+ * connected while its image's mount, vol2, is alive; then rmdir of vol2
+ * takes that one with the loop mount whose image is on the dead vol. Each
+ * dead mount now owns a loop mount whose filesystem pins the other.
+ */
+TEST_F(loop_cycle, crossed_images_released)
+{
+	int to_parent[2], to_child[2];
+	struct report r = { { -1, -1 } };
+	char sysfs[2][64], dev[2][32];
+	pid_t pid;
+
+	ASSERT_EQ(pipe(to_parent), 0);
+	ASSERT_EQ(pipe(to_child), 0);
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (pid == 0) {
+		close(to_parent[0]);
+		close(to_child[1]);
+		_exit(crossed_child(to_parent[1], to_child[0]));
+	}
+	close(to_parent[1]);
+	close(to_child[0]);
+
+	if (read(to_parent[0], &r, sizeof(r)) != sizeof(r))
+		CHILD_GAVE_UP(pid);
+	for (int i = 0; i < 2; i++) {
+		remember_loop(self, r.n[i]);
+		snprintf(dev[i], sizeof(dev[i]), "/dev/loop%d", r.n[i]);
+		snprintf(sysfs[i], sizeof(sysfs[i]), "/sys/block/loop%d/loop/backing_file", r.n[i]);
+	}
+
+	/* step one: the mount with the first loop mount below it goes */
+	ASSERT_EQ(rmdir(at("vol")), 0);
+
+	/* step two: the other one, with the loop mount whose image is on the first */
+	ASSERT_EQ(rmdir(at("vol2")), 0);
+
+	/* the child leaves: its namespace and every reference it held are gone */
+	ASSERT_EQ(write(to_child[1], "", 1), 1);
+	CHILD_LEFT(pid);
+	close(to_parent[0]);
+	close(to_child[1]);
+
+	/* nothing can reach the four mounts any more */
+	for (int i = 0; i < 2; i++) {
+		ASSERT_FALSE(mounted_anywhere(dev[i]));
+		strcpy(self->dev, dev[i]);
+		strcpy(self->sysfs, sysfs[i]);
+		assert_loop_released(_metadata, self);
+	}
+}
+
+/*
+ * The holders. Each keeps a file or a path on a mount P for as long as
+ * its own filesystem or device lives, and each has that filesystem or
+ * device mounted at C below P. Once another namespace's rmdir has
+ * detached P with C connected below it and the child is gone, P is owned
+ * by nobody, C by P, and P is kept by whatever the holder still holds.
+ *
+ * P is a loop mount so that its death can be observed: the loop device
+ * gives its backing file up when P's superblock goes. The image sits on
+ * a tmpfs next to P, not above it, so the loop device's own reference is
+ * not part of the picture.
+ */
+enum holder {
+	HOLDER_AUTOFS,		/* a FIFO on P as the daemon's pipe */
+	HOLDER_ZRAM,		/* a device node on P as the writeback device */
+	HOLDER_ECRYPTFS,	/* a directory on P as the lower directory */
+	HOLDER_BINFMT_MISC,	/* an executable on P as an 'F' interpreter */
+	HOLDER_FUSE,		/* a file on P as a passthrough backing file */
+	HOLDER_ZLOOP,		/* a directory on P for the zone files */
+	HOLDER_GADGET,		/* a file on P as a mass storage LUN, over dummy_hcd */
+	HOLDER_MD,		/* a file on P as an array's bitmap file */
+};
+
+#define HOLDER_IMG	at("img/p.img")
+#define HOLDER_MNT	at("p")
+#define HOLDER_BELOW	at("p/c")
+
+static int write_file(const char *path, const char *s)
+{
+	int fd = open(path, O_WRONLY);
+	ssize_t n;
+
+	if (fd < 0)
+		return -1;
+	n = write(fd, s, strlen(s));
+	close(fd);
+	return n == (ssize_t)strlen(s) ? 0 : -1;
+}
+
+/* Bind a free loop device to @img; the device number. */
+static int loop_attach(const char *img)
+{
+	int ifd, n;
+
+	ifd = open(img, O_RDWR);
+	if (ifd < 0)
+		return -1;
+	n = loop_bind(ifd);
+	close(ifd);
+	return n;
+}
+
+static int holder_autofs(void)
+{
+	char opts[64];
+	int pfd;
+
+	if (mkfifo(at("p/pipe"), 0600))
+		return CHILD_HOLDER;
+	pfd = open(at("p/pipe"), O_RDWR);
+	if (pfd < 0)
+		return CHILD_HOLDER;
+	snprintf(opts, sizeof(opts), "fd=%d,minproto=5,maxproto=5", pfd);
+	if (mount("autofs", HOLDER_BELOW, "autofs", 0, opts))
+		return errno == ENODEV ? CHILD_SKIP : CHILD_HOLDER;
+	close(pfd);	/* the mount keeps its own */
+	return CHILD_OK;
+}
+
+/*
+ * zram's writeback device has to be a block device node, and that node has
+ * to be on P. Point it at a second loop device.
+ */
+static void zram_reset(void);
+
+static int holder_zram(void)
+{
+	char dev[32], buf[64];
+	struct stat st;
+	int fd, n;
+
+	if (access("/sys/block/zram0/backing_dev", W_OK))
+		return CHILD_SKIP;
+	/* somebody else's device, leave it alone */
+	if (read_sysfs("/sys/block/zram0/initstate", buf, sizeof(buf)) || buf[0] != '0')
+		return CHILD_SKIP;
+	fd = open(at("img/wb.img"), O_RDWR | O_CREAT | O_EXCL, 0600);
+	if (fd < 0 || ftruncate(fd, IMAGE_SIZE))
+		return CHILD_HOLDER;
+	close(fd);
+	n = loop_attach(at("img/wb.img"));
+	if (n < 0)
+		return CHILD_HOLDER;
+	/* the minor is not the number of the device when loop has partitions */
+	snprintf(dev, sizeof(dev), "/dev/loop%d", n);
+	if (stat(dev, &st) || mknod(at("p/wbdev"), S_IFBLK | 0600, st.st_rdev))
+		return CHILD_HOLDER;
+	if (write_file("/sys/block/zram0/backing_dev", at("p/wbdev")))
+		return CHILD_HOLDER;
+	snprintf(buf, sizeof(buf), "%d", 4 * 1024 * 1024);
+	if (write_file("/sys/block/zram0/disksize", buf))
+		goto undo;
+	fd = open("/dev/zram0", O_RDWR);
+	if (fd < 0)
+		goto undo;
+	n = write_fat12_4k(fd, 1024);	/* zram has 4 KiB blocks */
+	close(fd);	/* the reset in undo is refused while the device is open */
+	if (n)
+		goto undo;
+	if (mount_fat("/dev/zram0", HOLDER_BELOW)) {
+		n = errno == ENODEV ? CHILD_NOFS : CHILD_HOLDER;
+		zram_reset();
+		return n;
+	}
+	return CHILD_OK;
+undo:
+	zram_reset();
+	return CHILD_HOLDER;
+}
+
+/* Is @alg in /proc/crypto? A module is listed once a request has loaded it. */
+static bool crypto_has(const char *alg)
+{
+	char line[256], name[64];
+	bool found = false;
+	FILE *f;
+
+	f = fopen("/proc/crypto", "r");
+	if (!f)
+		return true;	/* no way to tell, assume it is */
+	while (!found && fgets(line, sizeof(line), f))
+		found = sscanf(line, "name : %63s", name) == 1 && !strcmp(name, alg);
+	fclose(f);
+	return found;
+}
+
+/* The kernel's auth token layout, which userspace has to match byte for byte. */
+struct ecryptfs_auth_tok {
+	__u16 version;
+	__u16 token_type;
+	__u32 flags;
+	struct {
+		__u32 flags, encrypted_key_size, decrypted_key_size;
+		__u8 encrypted_key[512], decrypted_key[64];
+	} session_key;
+	__u8 reserved[32];
+	struct {
+		__u32 password_bytes;
+		__s32 hash_algo;
+		__u32 hash_iterations, session_key_encryption_key_bytes, flags;
+		__u8 session_key_encryption_key[64];
+		__u8 signature[17];
+		__u8 salt[8];
+	} password;
+} __attribute__((packed));
+
+#define ECRYPTFS_SIG	"0123456789abcdef"
+
+static int holder_ecryptfs(void)
+{
+	struct ecryptfs_auth_tok tok = {
+		.version = 0x0004,
+		.token_type = 0,		/* ECRYPTFS_PASSWORD */
+		.password.session_key_encryption_key_bytes = 16,
+		.password.flags = 0x02,		/* ECRYPTFS_SESSION_KEY_ENCRYPTION_KEY_SET */
+		.password.signature = ECRYPTFS_SIG,
+	};
+
+	/* a session keyring of this child's own, so that the key goes with it */
+	if (syscall(__NR_keyctl, KEYCTL_JOIN_SESSION_KEYRING, NULL) < 0)
+		return errno == ENOSYS ? CHILD_SKIP : CHILD_HOLDER;
+	if (syscall(__NR_add_key, "user", ECRYPTFS_SIG, &tok, sizeof(tok),
+		    KEY_SPEC_SESSION_KEYRING) < 0)
+		return CHILD_HOLDER;
+	if (mkdir(at("p/lower"), 0755))
+		return CHILD_HOLDER;
+	if (mount(at("p/lower"), HOLDER_BELOW, "ecryptfs", 0,
+		  "ecryptfs_sig=" ECRYPTFS_SIG ",ecryptfs_cipher=aes,ecryptfs_key_bytes=16")) {
+		/* EINVAL without the cipher; a module is loaded by the attempt */
+		if (errno == ENODEV || (errno == EINVAL && !crypto_has("aes")))
+			return CHILD_SKIP;
+		return CHILD_HOLDER;
+	}
+	return CHILD_OK;
+}
+
+/*
+ * binfmt_misc instances are per user namespace, so the mount below P is
+ * made from a new one, which gets a copy of P.
+ */
+static int holder_binfmt_misc(void)
+{
+	static const char interp[] = "#!/bin/true\n";
+	char reg[PATH_MAX];
+	int out;
+
+	/* 'F' opens the interpreter at registration, nothing runs it here */
+	out = open(at("p/interp"), O_WRONLY | O_CREAT | O_EXCL, 0755);
+	if (out < 0 || write(out, interp, sizeof(interp) - 1) != sizeof(interp) - 1)
+		return CHILD_HOLDER;
+	close(out);
+
+	/* ENOSPC: user.max_user_namespaces is 0, EPERM: an LSM says no */
+	if (unshare(CLONE_NEWUSER | CLONE_NEWNS)) {
+		if (errno == EINVAL || errno == ENOSPC || errno == EPERM)
+			return CHILD_SKIP;
+		return CHILD_HOLDER;
+	}
+	if (write_file("/proc/self/setgroups", "deny") ||
+	    write_file("/proc/self/uid_map", "0 0 1") ||
+	    write_file("/proc/self/gid_map", "0 0 1"))
+		return CHILD_HOLDER;
+	if (mount("binfmt_misc", HOLDER_BELOW, "binfmt_misc", 0, NULL))
+		return errno == ENODEV ? CHILD_SKIP : CHILD_HOLDER;
+	snprintf(reg, sizeof(reg), ":cycle:E::cyc::%s:F", at("p/interp"));
+	if (write_file(at("p/c/register"), reg))
+		return CHILD_HOLDER;
+	return CHILD_OK;
+}
+
+/*
+ * A fuse server that only ever answers FUSE_INIT, with passthrough on, and
+ * then registers a file on P as a backing file. The registration alone
+ * makes the fuse superblock hold the file.
+ */
+static int holder_fuse(void)
+{
+	struct fuse_backing_map map = {};
+	struct fuse_in_header *ih;
+	struct fuse_init_out init = {
+		.major = FUSE_KERNEL_VERSION,
+		.minor = FUSE_KERNEL_MINOR_VERSION,
+		.flags = FUSE_INIT_EXT,
+		.flags2 = FUSE_PASSTHROUGH >> 32,
+		.max_write = 4096,
+		.max_stack_depth = 1,
+	};
+	struct fuse_out_header oh = { .len = sizeof(oh) + sizeof(init) };
+	struct iovec iov[2] = { { &oh, sizeof(oh) }, { &init, sizeof(init) } };
+	char opts[64], buf[8192];
+	int ffd, bfd;
+	ssize_t n;
+
+	bfd = open(at("p/backing"), O_RDWR | O_CREAT | O_EXCL, 0600);
+	if (bfd < 0)
+		return CHILD_HOLDER;
+	ffd = open("/dev/fuse", O_RDWR);
+	if (ffd < 0)
+		return CHILD_SKIP;
+	snprintf(opts, sizeof(opts), "fd=%d,rootmode=40000,user_id=0,group_id=0", ffd);
+	if (mount("fuse", HOLDER_BELOW, "fuse", 0, opts))
+		return errno == ENODEV ? CHILD_SKIP : CHILD_HOLDER;
+
+	n = read(ffd, buf, sizeof(buf));
+	ih = (void *)buf;
+	if (n < (ssize_t)sizeof(*ih) || ih->opcode != FUSE_INIT)
+		return CHILD_HOLDER;
+	oh.unique = ih->unique;
+	if (writev(ffd, iov, 2) != (ssize_t)oh.len)
+		return CHILD_HOLDER;
+
+	map.fd = bfd;
+	if (ioctl(ffd, FUSE_DEV_IOC_BACKING_OPEN, &map) < 0) {
+		/* EOPNOTSUPP: no FUSE_PASSTHROUGH, ENOTTY: no such ioctl */
+		if (errno == EPERM || errno == EOPNOTSUPP || errno == ENOTTY)
+			return CHILD_SKIP;
+		return CHILD_HOLDER;
+	}
+	close(bfd);	/* the connection keeps its own */
+	return CHILD_OK;	/* ffd stays open until the child exits */
+}
+
+/* Wait for a device node the kernel is about to create. */
+static int open_when_there(const char *dev, int flags, int ms)
+{
+	int fd;
+
+	for (; ms > 0; ms -= 100) {
+		fd = open(dev, flags);
+		if (fd >= 0)
+			return fd;
+		usleep(100000);
+	}
+	return -1;
+}
+
+/*
+ * zloop keeps every zone file open. One conventional zone is enough for a
+ * FAT image, the sequential one stays empty.
+ */
+static int holder_zloop(void)
+{
+	char cmd[PATH_MAX];
+	int fd, ret = CHILD_HOLDER;
+
+	if (access("/dev/zloop-control", W_OK))
+		return CHILD_SKIP;
+	/* somebody else's device, leave it alone */
+	if (!access("/sys/block/zloop0", F_OK))
+		return CHILD_SKIP;
+	if (mkdir(at("p/zl"), 0755) || mkdir(at("p/zl/0"), 0755))
+		return CHILD_HOLDER;
+	snprintf(cmd, sizeof(cmd),
+		 "add id=0,capacity_mb=8,zone_size_mb=4,conv_zones=1,base_dir=%s",
+		 at("p/zl"));
+	if (write_file("/dev/zloop-control", cmd))
+		return CHILD_HOLDER;
+	fd = open_when_there("/dev/zloop0", O_RDWR, 5000);
+	if (fd < 0 || write_fat12_4k(fd, 1024))	/* 4 KiB blocks, like P */
+		goto undo;
+	close(fd);
+	fd = -1;
+	if (mount_fat("/dev/zloop0", HOLDER_BELOW)) {
+		ret = errno == ENODEV ? CHILD_NOFS : CHILD_HOLDER;
+		goto undo;
+	}
+	return CHILD_OK;
+undo:
+	if (fd >= 0)
+		close(fd);
+	write_file("/dev/zloop-control", "remove id=0");
+	return ret;
+}
+
+static int mount_configfs(void)
+{
+	if (access("/sys/kernel/config", F_OK))
+		return -1;
+	if (mount("configfs", "/sys/kernel/config", "configfs", 0, NULL) && errno != EBUSY)
+		return -1;
+	return 0;
+}
+
+/* Take the gadget out of configfs again, in the reverse order of its creation. */
+static void gadget_remove(void)
+{
+	if (mount_configfs())
+		return;
+	write_file(gat("/functions/mass_storage.0/lun.0/file"), "\n");
+	write_file(gat("/UDC"), "\n");
+	unlink(gat("/configs/c.1/mass_storage.0"));
+	rmdir(gat("/configs/c.1/strings/0x409"));
+	rmdir(gat("/configs/c.1"));
+	rmdir(gat("/functions/mass_storage.0"));
+	rmdir(gat("/strings/0x409"));
+	rmdir(gat(""));
+}
+
+/* The disk usb-storage created for the gadget, by the LUN's inquiry string. */
+static int find_gadget_disk(char *dev, size_t len, int ms)
+{
+	char path[PATH_MAX], model[64];
+	struct dirent *de;
+	DIR *d;
+
+	for (; ms > 0; ms -= 100, usleep(100000)) {
+		d = opendir("/sys/block");
+		if (!d)
+			return -1;
+		while ((de = readdir(d))) {
+			if (strncmp(de->d_name, "sd", 2))
+				continue;
+			snprintf(path, sizeof(path), "/sys/block/%s/device/model", de->d_name);
+			if (read_sysfs(path, model, sizeof(model)) ||
+			    strncmp(model, "File-Stor Gadget", 16))
+				continue;
+			snprintf(dev, len, "/dev/%s", de->d_name);
+			closedir(d);
+			return 0;
+		}
+		closedir(d);
+	}
+	return -1;
+}
+
+/*
+ * A mass storage gadget bound to the dummy UDC, so that this kernel is
+ * also the USB host that sees the LUN as a SCSI disk. sd locks the
+ * medium on open, which is what keeps the LUN's file from being ejected.
+ */
+#define GADGET_STEP(x) do {						\
+	if (x) {							\
+		fprintf(stderr, "gadget: %s failed: %s\n", #x, strerror(errno)); \
+		gadget_remove();					\
+		return CHILD_HOLDER;					\
+	}								\
+} while (0)
+
+static int holder_gadget(void)
+{
+	char dev[PATH_MAX];
+	int fd;
+
+	if (mount_configfs())
+		return CHILD_SKIP;
+	if (access("/sys/kernel/config/usb_gadget", F_OK) ||
+	    access("/sys/class/udc/dummy_udc.0", F_OK))
+		return CHILD_SKIP;
+
+	fd = open(at("p/lun.img"), O_RDWR | O_CREAT | O_EXCL, 0600);
+	if (fd < 0 || write_fat12(fd))
+		return CHILD_HOLDER;
+	close(fd);
+
+	GADGET_STEP(mkdir(gat(""), 0755));
+	GADGET_STEP(write_file(gat("/idVendor"), "0x1d6b"));
+	GADGET_STEP(write_file(gat("/idProduct"), "0x0104"));
+	GADGET_STEP(mkdir(gat("/strings/0x409"), 0755));
+	GADGET_STEP(write_file(gat("/strings/0x409/serialnumber"), "1"));
+	GADGET_STEP(write_file(gat("/strings/0x409/manufacturer"), "kselftest"));
+	GADGET_STEP(write_file(gat("/strings/0x409/product"), "cycle"));
+	GADGET_STEP(mkdir(gat("/configs/c.1"), 0755));
+	GADGET_STEP(mkdir(gat("/configs/c.1/strings/0x409"), 0755));
+	GADGET_STEP(write_file(gat("/configs/c.1/strings/0x409/configuration"), "c"));
+	GADGET_STEP(mkdir(gat("/functions/mass_storage.0"), 0755));
+	GADGET_STEP(write_file(gat("/functions/mass_storage.0/lun.0/removable"), "1"));
+	GADGET_STEP(write_file(gat("/functions/mass_storage.0/lun.0/file"), at("p/lun.img")));
+	GADGET_STEP(symlink(gat("/functions/mass_storage.0"), gat("/configs/c.1/mass_storage.0")));
+	/* EBUSY: the controller is somebody else's, leave it alone */
+	if (write_file(gat("/UDC"), "dummy_udc.0")) {
+		fd = errno == EBUSY ? CHILD_SKIP : CHILD_HOLDER;
+		gadget_remove();
+		return fd;
+	}
+
+	/* usb-storage waits a second before it scans the device */
+	if (find_gadget_disk(dev, sizeof(dev), 15000)) {
+		/* without usb-storage and sd the LUN never shows up as a disk */
+		fd = (access("/sys/bus/usb/drivers/usb-storage", F_OK) ||
+		      access("/sys/bus/scsi/drivers/sd", F_OK)) ? CHILD_SKIP : CHILD_HOLDER;
+		gadget_remove();
+		return fd;
+	}
+	fd = open_when_there(dev, O_RDONLY, 5000);
+	GADGET_STEP(fd < 0);
+	close(fd);
+	if (mount_fat(dev, HOLDER_BELOW)) {
+		fd = errno == ENODEV ? CHILD_NOFS : CHILD_HOLDER;
+		gadget_remove();
+		return fd;
+	}
+	return CHILD_OK;
+}
+
+#define BITMAP_MAGIC	0x6d746962
+
+/*
+ * A RAID1 of one loop device, not persistent, with its bitmap in a file
+ * on P. The bitmap file needs a superblock the kernel accepts; sync_size,
+ * uuid and events are not looked at for a non-persistent array.
+ */
+static int holder_md(void)
+{
+	struct {
+		__u32 magic, version;
+		__u8 uuid[16];
+		__u64 events, events_cleared, sync_size;
+		__u32 state, chunksize, daemon_sleep, write_behind;
+	} bsb = {
+		.magic = BITMAP_MAGIC,
+		.version = 4,
+		.chunksize = 64 * 1024,
+		.daemon_sleep = 5,
+	};
+	mdu_array_info_t info = {
+		.level = 1,
+		.raid_disks = 1,
+		.size = 8 * 1024,		/* KiB */
+		.not_persistent = 1,
+	};
+	mdu_disk_info_t disk = {
+		.major = 7,
+		.state = (1 << MD_DISK_ACTIVE) | (1 << MD_DISK_SYNC),
+	};
+	int fd, mdfd, bfd, n, ret = CHILD_HOLDER;
+	char dev[32], buf[4096];
+	struct stat st;
+
+	fd = open(at("img/md.img"), O_RDWR | O_CREAT | O_EXCL, 0600);
+	if (fd < 0 || ftruncate(fd, 8 * 1024 * 1024))
+		return CHILD_HOLDER;
+	close(fd);
+	n = loop_attach(at("img/md.img"));
+	if (n < 0)
+		return CHILD_HOLDER;
+	/* the minor is not the number of the device when loop has partitions */
+	snprintf(dev, sizeof(dev), "/dev/loop%d", n);
+	if (stat(dev, &st))
+		return CHILD_HOLDER;
+	disk.major = major(st.st_rdev);
+	disk.minor = minor(st.st_rdev);
+
+	bfd = open(at("p/bitmap"), O_RDWR | O_CREAT | O_EXCL, 0600);
+	if (bfd < 0 || write(bfd, &bsb, sizeof(bsb)) != sizeof(bsb) || ftruncate(bfd, 4096))
+		return CHILD_HOLDER;
+
+	if (read_sysfs("/proc/mdstat", buf, sizeof(buf))) {
+		fprintf(stderr, "md: no /proc/mdstat\n");
+		return CHILD_SKIP;
+	}
+	if (!strstr(buf, "[raid1]")) {
+		fprintf(stderr, "md: no raid1 personality: %s\n", buf);
+		return CHILD_SKIP;
+	}
+	/* a node of our own: /dev may not have one and is not ours to change */
+	if (mknod(at("img/md0"), S_IFBLK | 0600, makedev(MD_MAJOR, 0)))
+		return CHILD_HOLDER;
+	mdfd = open(at("img/md0"), O_RDWR);
+	if (mdfd < 0)
+		return CHILD_SKIP;
+	/* somebody else's array: SET_ARRAY_INFO would say EINVAL, not EBUSY */
+	if (!read_sysfs("/sys/block/md0/md/array_state", buf, sizeof(buf)) &&
+	    strncmp(buf, "clear", 5)) {
+		fprintf(stderr, "md: md0 is in use: %s", buf);
+		close(mdfd);
+		return CHILD_SKIP;
+	}
+	/* the bitmap ops are only installed once a bitmap type is chosen */
+	write_file("/sys/block/md0/md/bitmap_type", "bitmap");
+	/* EBUSY: somebody else's array, leave it alone */
+	if (ioctl(mdfd, SET_ARRAY_INFO, &info))
+		return errno == EBUSY ? CHILD_SKIP : CHILD_HOLDER;
+	/* the array is ours from here on and has to be stopped on every path */
+	if (ioctl(mdfd, ADD_NEW_DISK, &disk))
+		goto undo;
+	/* attach the bitmap file before the array runs, the way mdadm does */
+	if (ioctl(mdfd, SET_BITMAP_FILE, bfd)) {
+		fprintf(stderr, "md: SET_BITMAP_FILE: %s\n", strerror(errno));
+		if (errno == EINVAL)
+			ret = CHILD_SKIP;
+		goto undo;
+	}
+	close(bfd);	/* the array keeps its own */
+	if (ioctl(mdfd, RUN_ARRAY, NULL)) {
+		fprintf(stderr, "md: RUN_ARRAY: %s\n", strerror(errno));
+		goto undo;
+	}
+	if (write_fat12(mdfd))
+		goto undo;
+	close(mdfd);
+	if (mount_fat(at("img/md0"), HOLDER_BELOW)) {
+		ret = errno == ENODEV ? CHILD_NOFS : CHILD_HOLDER;
+		mdfd = open(at("img/md0"), O_RDWR);
+		goto undo;
+	}
+	return CHILD_OK;
+undo:
+	if (mdfd >= 0) {
+		ioctl(mdfd, STOP_ARRAY);
+		close(mdfd);
+	}
+	return ret;
+}
+
+/*
+ * A device keeps its file for as long as it is configured, so once nothing
+ * below the dead mount is left the device has to be told to let go. With
+ * the cycle unbroken C's filesystem still holds the device and every one
+ * of these refuses.
+ */
+static int holder_let_go_once(enum holder holder)
+{
+	int fd, ret;
+
+	switch (holder) {
+	case HOLDER_ZRAM:
+		return write_file("/sys/block/zram0/reset", "1");
+	case HOLDER_ZLOOP:
+		return write_file("/dev/zloop-control", "remove id=0");
+	case HOLDER_GADGET:
+		if (mount_configfs())
+			return -1;
+		/* a zero-length write is a no-op for configfs; a newline ejects */
+		return write_file(gat("/functions/mass_storage.0/lun.0/file"), "\n");
+	case HOLDER_MD:
+		fd = open(at("img/md0"), O_RDONLY);
+		if (fd < 0) {
+			/* the child's node went with its tmpfs */
+			mknod(at("md0"), S_IFBLK | 0600, makedev(MD_MAJOR, 0));
+			fd = open(at("md0"), O_RDONLY);
+		}
+		if (fd < 0)
+			return -1;
+		ret = ioctl(fd, STOP_ARRAY);
+		close(fd);
+		return ret;
+	default:
+		return 0;
+	}
+}
+
+static void zram_reset(void)
+{
+	holder_let_go_once(HOLDER_ZRAM);
+}
+
+/*
+ * In its own mount namespace the child puts the image on a tmpfs next to
+ * P, mounts P from a loop device, and sets the holder up with its own
+ * filesystem or device mounted at C below P.
+ */
+static int holder_child(int to_parent, int from_parent, enum holder holder)
+{
+	bool fifo = holder == HOLDER_AUTOFS || holder == HOLDER_ZRAM;
+	/* room for a 4 MiB zone file or a floppy image on P */
+	bool big = holder == HOLDER_ZLOOP || holder == HOLDER_GADGET;
+	struct report r = { { -1, -1 } };
+	char dev[32], c;
+	int ifd, n, ret;
+
+	if (unshare(CLONE_NEWNS) || mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL))
+		return CHILD_NS;
+	if (mkdir(at("img"), 0755) || mount("tmpfs", at("img"), "tmpfs", 0, NULL))
+		return child_fails(CHILD_NS);
+
+	ifd = open(HOLDER_IMG, O_RDWR | O_CREAT | O_EXCL, 0600);
+	if (ifd < 0 || (fifo ? write_minix(ifd) :
+			big ? write_fat12_4k(ifd, 3072) : write_fat12(ifd)))
+		return child_fails(CHILD_IMAGE);
+	close(ifd);
+	n = loop_attach(HOLDER_IMG);
+	if (n < 0)
+		return child_fails(CHILD_LOOP);
+	snprintf(dev, sizeof(dev), "/dev/loop%d", n);
+	if (mkdir(HOLDER_MNT, 0755))
+		return child_fails(CHILD_MOUNT);
+	if (fifo ? mount(dev, HOLDER_MNT, "minix", 0, NULL) : mount_fat(dev, HOLDER_MNT))
+		return child_fails(errno == ENODEV ? CHILD_NOFS : CHILD_MOUNT);
+	if (mkdir(HOLDER_BELOW, 0755))
+		return child_fails(CHILD_MOUNT);
+
+	switch (holder) {
+	case HOLDER_AUTOFS:
+		ret = holder_autofs();
+		break;
+	case HOLDER_ZRAM:
+		ret = holder_zram();
+		break;
+	case HOLDER_ECRYPTFS:
+		ret = holder_ecryptfs();
+		break;
+	case HOLDER_BINFMT_MISC:
+		ret = holder_binfmt_misc();
+		break;
+	case HOLDER_FUSE:
+		ret = holder_fuse();
+		break;
+	case HOLDER_ZLOOP:
+		ret = holder_zloop();
+		break;
+	case HOLDER_GADGET:
+		ret = holder_gadget();
+		break;
+	case HOLDER_MD:
+		ret = holder_md();
+		break;
+	default:
+		ret = CHILD_HOLDER;
+	}
+	if (ret != CHILD_OK)
+		return child_fails(ret);
+
+	/* P's device first, then the one the holder has bound for itself */
+	r.n[0] = n;
+	if (nr_bound > 1)
+		r.n[1] = bound[1];
+	if (write(to_parent, &r, sizeof(r)) != sizeof(r) ||
+	    read(from_parent, &c, 1) != 1) {
+		/* the parent went away and will not tell the devices to let go */
+		umount2(HOLDER_BELOW, MNT_DETACH);
+		for (int i = 0; i < 50 && holder_let_go_once(holder); i++)
+			usleep(100000);
+		if (holder == HOLDER_GADGET)
+			gadget_remove();
+		return child_fails(CHILD_PIPE);
+	}
+	return CHILD_OK;
+}
+
+/*
+ * The release of C's filesystem may still be in flight when the child is
+ * gone, and a device that is still held refuses to let go, so try for a
+ * while. With the cycle unbroken it refuses for good.
+ */
+static void holder_let_go(enum holder holder)
+{
+	for (int i = 0; i < 50; i++) {
+		if (!holder_let_go_once(holder))
+			return;
+		usleep(100000);
+	}
+}
+
+/*
+ * rmdir of p from here, where it is a plain directory, detaches
+ * P in the child's namespace with C connected below it. Once the child is
+ * gone the holder's file on P is the only thing left that refers to P,
+ * and it is dropped only when C's filesystem dies, which waits for P.
+ * The loop device backing P tells whether that resolved.
+ */
+static void holder_cycle(struct __test_metadata *_metadata,
+			 FIXTURE_DATA(loop_cycle) *self, enum holder holder)
+{
+	struct report r = { { -1, -1 } };
+	int to_parent[2], from_parent[2];
+	pid_t pid;
+
+	ASSERT_EQ(pipe(to_parent), 0);
+	ASSERT_EQ(pipe(from_parent), 0);
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (pid == 0) {
+		close(to_parent[0]);
+		close(from_parent[1]);
+		_exit(holder_child(to_parent[1], from_parent[0], holder));
+	}
+	close(to_parent[1]);
+	close(from_parent[0]);
+
+	if (read(to_parent[0], &r, sizeof(r)) != sizeof(r))
+		CHILD_GAVE_UP(pid);
+	self->gadget = holder == HOLDER_GADGET;
+	remember_loop(self, r.n[0]);
+	remember_loop(self, r.n[1]);
+	snprintf(self->dev, sizeof(self->dev), "/dev/loop%d", r.n[0]);
+	snprintf(self->sysfs, sizeof(self->sysfs), "/sys/block/loop%d/loop/backing_file", r.n[0]);
+
+	ASSERT_EQ(rmdir(HOLDER_MNT), 0);
+	ASSERT_EQ(write(from_parent[1], "x", 1), 1);
+	CHILD_LEFT(pid);
+	close(to_parent[0]);
+	close(from_parent[1]);
+
+	holder_let_go(holder);
+	assert_loop_released(_metadata, self);
+	/* the teardown takes the gadget and the holder's own loop device away */
+}
+
+TEST_F(loop_cycle, autofs_pipe_on_dead_mount_released)
+{
+	holder_cycle(_metadata, self, HOLDER_AUTOFS);
+}
+
+TEST_F(loop_cycle, zram_writeback_node_on_dead_mount_released)
+{
+	holder_cycle(_metadata, self, HOLDER_ZRAM);
+}
+
+TEST_F(loop_cycle, ecryptfs_lower_on_dead_mount_released)
+{
+	holder_cycle(_metadata, self, HOLDER_ECRYPTFS);
+}
+
+TEST_F(loop_cycle, binfmt_misc_interpreter_on_dead_mount_released)
+{
+	holder_cycle(_metadata, self, HOLDER_BINFMT_MISC);
+}
+
+TEST_F(loop_cycle, fuse_backing_file_on_dead_mount_released)
+{
+	holder_cycle(_metadata, self, HOLDER_FUSE);
+}
+
+TEST_F(loop_cycle, zloop_zone_files_on_dead_mount_released)
+{
+	holder_cycle(_metadata, self, HOLDER_ZLOOP);
+}
+
+/* up to 15 s for the disk to show up and 12 s for a device that is held */
+TEST_F_TIMEOUT(loop_cycle, mass_storage_lun_on_dead_mount_released, 120)
+{
+	holder_cycle(_metadata, self, HOLDER_GADGET);
+}
+
+TEST_F(loop_cycle, md_bitmap_file_on_dead_mount_released)
+{
+	holder_cycle(_metadata, self, HOLDER_MD);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/filesystems/mount_cycle/mount_cover_test.c b/tools/testing/selftests/filesystems/mount_cycle/mount_cover_test.c
new file mode 100644
index 0000000..0a0b0c0
--- /dev/null
+++ b/tools/testing/selftests/filesystems/mount_cycle/mount_cover_test.c
@@ -0,0 +1,579 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * An unmounted mount that would have stayed attached to its unmounted parent
+ * leaves a cover behind instead. A lookup on the parent at the mountpoint
+ * finds knullfs: an empty read-only directory that is shared by every cover
+ * and every kernel thread, so it can't be watched or locked and nothing can
+ * be mounted on it. The mount itself is a root from then on. Where a file
+ * was mounted, the stand-in is an empty regular file of the same instance.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/fanotify.h>
+#include <sys/file.h>
+#include <sys/inotify.h>
+#include <sys/mount.h>
+#include <sys/prctl.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/syscall.h>
+#include <sys/vfs.h>
+#include <sys/wait.h>
+
+#include "../../kselftest_harness.h"
+#include "../readdir_hold.h"
+
+#ifndef NULL_FS_MAGIC
+#define NULL_FS_MAGIC 0x4E554C4C
+#endif
+
+#ifndef TMPFS_MAGIC
+#define TMPFS_MAGIC 0x01021994
+#endif
+
+#ifndef F_SETDELEG
+#define F_SETDELEG	(F_SETLEASE + 16)
+#endif
+
+/* struct delegation of <linux/fcntl.h>, which doesn't mix with <fcntl.h> */
+struct delegation_req {
+	uint32_t d_flags;
+	uint16_t d_type;
+	uint16_t __pad;
+};
+
+#define DIR_LEN 64
+#define PATH_LEN 128
+
+/* what the parent asks the child to do */
+#define CMD_RMDIR	'r'
+#define CMD_CLOSE_T	't'
+#define CMD_QUIT	'q'
+
+static int write_file(const char *path, const char *s)
+{
+	ssize_t n = -1;
+	int fd;
+
+	fd = open(path, O_WRONLY | O_CLOEXEC);
+	if (fd >= 0) {
+		n = write(fd, s, strlen(s));
+		close(fd);
+	}
+	return n == (ssize_t)strlen(s) ? 0 : -1;
+}
+
+static int touch(const char *path)
+{
+	int fd;
+
+	fd = open(path, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644);
+	if (fd < 0)
+		return -1;
+	close(fd);
+	return 0;
+}
+
+/* Become root in a new user namespace with a private mount namespace. */
+static int enter_userns(void)
+{
+	uid_t uid = getuid();
+	gid_t gid = getgid();
+	char map[32];
+
+	prctl(PR_SET_DUMPABLE, 1);
+	if (unshare(CLONE_NEWUSER | CLONE_NEWNS))
+		return -1;
+	if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT)
+		return -1;
+	snprintf(map, sizeof(map), "0 %d 1", uid);
+	if (write_file("/proc/self/uid_map", map))
+		return -1;
+	snprintf(map, sizeof(map), "0 %d 1", gid);
+	if (write_file("/proc/self/gid_map", map))
+		return -1;
+	if (setgid(0) || setuid(0))
+		return -1;
+	return mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL);
+}
+
+/*
+ * The child mounts P on @base/p, C on P/covered and the file P/src on P/file
+ * in a mount namespace of its own, binds P a second time at @base/q and hands
+ * out descriptors on P and on C. rmdir() of @base/p from here unmounts P
+ * together with C and the file bind. P and C are held by the descriptors and
+ * the unmounted children leave their covers behind. On request the child
+ * removes C's mountpoint through the bind.
+ *
+ * It also mounts T on @base/t with a child on T/covered, binds T at @base/u
+ * with a second child on the same dentry and hands out descriptors on T and
+ * U. rmdir() of both from here leaves two covers on one mountpoint. On request
+ * the child lets go of T.
+ */
+static int cover_child(const char *base, int to_parent, int from_parent)
+{
+	char p[PATH_LEN], c[PATH_LEN], q[PATH_LEN], qc[PATH_LEN];
+	char f[PATH_LEN], src[PATH_LEN], t[PATH_LEN], u[PATH_LEN], tc[PATH_LEN];
+	int fds[4], ret;
+	char cmd;
+
+	if (unshare(CLONE_NEWNS) || mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL))
+		return 1;
+	snprintf(p, sizeof(p), "%s/p", base);
+	if (mkdir(p, 0755) || mount("tmpfs", p, "tmpfs", 0, NULL))
+		return 2;
+	snprintf(c, sizeof(c), "%s/p/covered", base);
+	if (mkdir(c, 0755) || mount("tmpfs", c, "tmpfs", 0, NULL))
+		return 3;
+	snprintf(f, sizeof(f), "%s/p/file", base);
+	snprintf(src, sizeof(src), "%s/p/src", base);
+	if (touch(src) || touch(f) || mount(src, f, NULL, MS_BIND, NULL))
+		return 4;
+	snprintf(q, sizeof(q), "%s/q", base);
+	if (mkdir(q, 0755) || mount(p, q, NULL, MS_BIND, NULL))
+		return 5;
+	snprintf(t, sizeof(t), "%s/t", base);
+	if (mkdir(t, 0755) || mount("tmpfs", t, "tmpfs", 0, NULL))
+		return 6;
+	snprintf(tc, sizeof(tc), "%s/t/covered", base);
+	if (mkdir(tc, 0755) || mount("tmpfs", tc, "tmpfs", 0, NULL))
+		return 7;
+	snprintf(u, sizeof(u), "%s/u", base);
+	if (mkdir(u, 0755) || mount(t, u, NULL, MS_BIND, NULL))
+		return 8;
+	snprintf(tc, sizeof(tc), "%s/u/covered", base);
+	if (mount("tmpfs", tc, "tmpfs", 0, NULL))
+		return 9;
+	fds[0] = open(p, O_PATH | O_DIRECTORY | O_CLOEXEC);
+	fds[1] = open(c, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+	fds[2] = open(t, O_PATH | O_DIRECTORY | O_CLOEXEC);
+	fds[3] = open(u, O_PATH | O_DIRECTORY | O_CLOEXEC);
+	if (fds[0] < 0 || fds[1] < 0 || fds[2] < 0 || fds[3] < 0)
+		return 10;
+	if (write(to_parent, fds, sizeof(fds)) != sizeof(fds))
+		return 11;
+
+	snprintf(qc, sizeof(qc), "%s/q/covered", base);
+	for (;;) {
+		if (read(from_parent, &cmd, 1) != 1)
+			return 12;
+		switch (cmd) {
+		case CMD_RMDIR:
+			ret = rmdir(qc) ? errno : 0;
+			if (write(to_parent, &ret, sizeof(ret)) != sizeof(ret))
+				return 13;
+			break;
+		case CMD_CLOSE_T:
+			ret = close(fds[2]) ? errno : 0;
+			if (write(to_parent, &ret, sizeof(ret)) != sizeof(ret))
+				return 13;
+			break;
+		case CMD_QUIT:
+			return 0;
+		default:
+			return 14;
+		}
+	}
+}
+
+FIXTURE(mount_cover) {
+	char base[DIR_LEN];
+	pid_t child;
+	int to_child;
+	int from_child;
+	int dfd;	/* P, unmounted, held */
+	int cfd;	/* C, unmounted, held */
+	int fd;		/* what a lookup on P finds at C's mountpoint */
+	int tfd;	/* T, unmounted, held */
+	int ufd;	/* U, a bind of T, unmounted, held */
+	int fan;	/* fanotify group from before the user namespace, or -1 */
+	struct readdir_hold hold;	/* likewise from before, uffd -1 without */
+};
+
+/*
+ * Everything the setup has made. The harness does not run the teardown
+ * when an assertion of the setup fails, so the setup calls this itself.
+ */
+static void cover_cleanup(FIXTURE_DATA(mount_cover) *self)
+{
+	char cmd = CMD_QUIT;
+	int status;
+
+	if (self->fd >= 0)
+		close(self->fd);
+	if (self->cfd >= 0)
+		close(self->cfd);
+	if (self->dfd >= 0)
+		close(self->dfd);
+	if (self->tfd >= 0)
+		close(self->tfd);
+	if (self->ufd >= 0)
+		close(self->ufd);
+	if (self->fan >= 0)
+		close(self->fan);
+	readdir_hold_destroy(&self->hold);
+	if (self->child > 0) {
+		if (write(self->to_child, &cmd, 1) != 1)
+			kill(self->child, SIGKILL);
+		waitpid(self->child, &status, 0);
+	}
+	if (self->to_child >= 0)
+		close(self->to_child);
+	if (self->from_child >= 0)
+		close(self->from_child);
+	umount2(self->base, MNT_DETACH);
+	rmdir(self->base);
+}
+
+static int same_file(int fd1, int fd2)
+{
+	struct stat st1, st2;
+
+	if (fstat(fd1, &st1) || fstat(fd2, &st2))
+		return 0;
+	return st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino;
+}
+
+FIXTURE_SETUP(mount_cover)
+{
+	int to_parent[2], to_child[2], fds[4], pidfd, status;
+	char dir[PATH_LEN];
+	struct statfs sf;
+
+	self->child = 0;
+	self->to_child = self->from_child = -1;
+	self->dfd = self->cfd = self->fd = self->tfd = self->ufd = -1;
+
+	/*
+	 * A group for plain events takes CAP_SYS_ADMIN in the initial user
+	 * namespace, so get one before that is gone. An inode mark can be
+	 * added to it from anywhere.
+	 */
+	self->fan = fanotify_init(FAN_CLASS_NOTIF | FAN_CLOEXEC, O_RDONLY);
+	/* same for the userfaultfd that holds a readdir in its fault */
+	readdir_hold_init(&self->hold);
+
+	snprintf(self->base, sizeof(self->base), "/tmp/mount_cover.XXXXXX");
+	ASSERT_NE(mkdtemp(self->base), NULL);
+	if (enter_userns()) {
+		cover_cleanup(self);
+		SKIP(return, "test requires user namespaces");
+	}
+	ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, NULL), 0)
+		cover_cleanup(self);
+
+	snprintf(dir, sizeof(dir), "%s/p", self->base);
+	ASSERT_EQ(pipe(to_parent), 0)
+		cover_cleanup(self);
+	ASSERT_EQ(pipe(to_child), 0)
+		cover_cleanup(self);
+	self->child = fork();
+	ASSERT_GE(self->child, 0)
+		cover_cleanup(self);
+	if (self->child == 0) {
+		close(to_parent[0]);
+		close(to_child[1]);
+		_exit(cover_child(self->base, to_parent[1], to_child[0]));
+	}
+	close(to_parent[1]);
+	close(to_child[0]);
+	self->to_child = to_child[1];
+	self->from_child = to_parent[0];
+	if (read(self->from_child, fds, sizeof(fds)) != sizeof(fds)) {
+		pid_t pid = self->child;
+
+		waitpid(pid, &status, 0);
+		self->child = 0;
+		cover_cleanup(self);
+		ASSERT_TRUE(false)
+			TH_LOG("child failed to set up: %s %d",
+			       WIFEXITED(status) ? "step" : "signal",
+			       WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status));
+	}
+
+	pidfd = syscall(__NR_pidfd_open, self->child, 0);
+	ASSERT_GE(pidfd, 0)
+		cover_cleanup(self);
+	self->dfd = syscall(__NR_pidfd_getfd, pidfd, fds[0], 0);
+	self->cfd = syscall(__NR_pidfd_getfd, pidfd, fds[1], 0);
+	self->tfd = syscall(__NR_pidfd_getfd, pidfd, fds[2], 0);
+	self->ufd = syscall(__NR_pidfd_getfd, pidfd, fds[3], 0);
+	close(pidfd);
+	ASSERT_GE(self->dfd, 0)
+		cover_cleanup(self);
+	ASSERT_GE(self->cfd, 0)
+		cover_cleanup(self);
+	ASSERT_GE(self->tfd, 0)
+		cover_cleanup(self);
+	ASSERT_GE(self->ufd, 0)
+		cover_cleanup(self);
+
+	/* unmounts P and C, C leaves its cover behind */
+	ASSERT_EQ(rmdir(dir), 0)
+		cover_cleanup(self);
+	/* unmounts T and U with their children, two covers on one mountpoint */
+	snprintf(dir, sizeof(dir), "%s/t", self->base);
+	ASSERT_EQ(rmdir(dir), 0)
+		cover_cleanup(self);
+	snprintf(dir, sizeof(dir), "%s/u", self->base);
+	ASSERT_EQ(rmdir(dir), 0)
+		cover_cleanup(self);
+
+	self->fd = openat(self->dfd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+	ASSERT_GE(self->fd, 0)
+		cover_cleanup(self);
+	ASSERT_EQ(fstatfs(self->fd, &sf), 0)
+		cover_cleanup(self);
+	ASSERT_EQ(sf.f_type, NULL_FS_MAGIC)
+		cover_cleanup(self);
+}
+
+FIXTURE_TEARDOWN(mount_cover)
+{
+	cover_cleanup(self);
+}
+
+TEST_F(mount_cover, not_watchable)
+{
+	char p[PATH_LEN];
+	int ifd;
+
+	snprintf(p, sizeof(p), "/proc/self/fd/%d", self->fd);
+
+	ifd = inotify_init1(IN_CLOEXEC);
+	if (ifd < 0 && errno == ENOSYS) {
+		TH_LOG("no inotify in this kernel, skipping that part");
+	} else {
+		ASSERT_GE(ifd, 0);
+		EXPECT_EQ(inotify_add_watch(ifd, p, IN_OPEN), -1);
+		EXPECT_EQ(errno, EINVAL);
+		close(ifd);
+	}
+
+	/* dnotify ends up at the same place */
+	EXPECT_EQ(fcntl(self->fd, F_NOTIFY, DN_ACCESS), -1);
+	EXPECT_EQ(errno, EINVAL);
+
+	if (self->fan < 0) {
+		TH_LOG("no fanotify group without CAP_SYS_ADMIN in the initial user namespace, skipping the fanotify part");
+		return;
+	}
+	EXPECT_EQ(fanotify_mark(self->fan, FAN_MARK_ADD, FAN_OPEN, self->fd, NULL), -1);
+	EXPECT_EQ(errno, EINVAL);
+}
+
+TEST_F(mount_cover, not_lockable)
+{
+	struct flock fl = {
+		.l_type = F_RDLCK,
+		.l_whence = SEEK_SET,
+	};
+
+	EXPECT_EQ(flock(self->fd, LOCK_EX | LOCK_NB), -1);
+	EXPECT_EQ(errno, ENOLCK);
+	EXPECT_EQ(fcntl(self->fd, F_SETLK, &fl), -1);
+	EXPECT_EQ(errno, ENOLCK);
+	EXPECT_EQ(fcntl(self->fd, F_GETLK, &fl), -1);
+	EXPECT_EQ(errno, ENOLCK);
+}
+
+/* a lease is refused for the owner and for everybody else alike */
+TEST_F(mount_cover, not_leasable)
+{
+	struct delegation_req deleg = {
+		.d_type = F_RDLCK,
+	};
+
+	EXPECT_EQ(fcntl(self->fd, F_SETLEASE, F_RDLCK), -1);
+	EXPECT_TRUE(errno == EINVAL || errno == EACCES);
+	EXPECT_EQ(fcntl(self->fd, F_SETDELEG, &deleg), -1);
+	EXPECT_TRUE(errno == EINVAL || errno == EACCES);
+}
+
+TEST_F(mount_cover, not_mountable)
+{
+	char p[PATH_LEN];
+
+	snprintf(p, sizeof(p), "/proc/self/fd/%d", self->fd);
+	EXPECT_EQ(mount("tmpfs", p, "tmpfs", 0, NULL), -1);
+	EXPECT_EQ(errno, ENOENT);
+}
+
+TEST_F(mount_cover, read_only)
+{
+	struct statvfs sv;
+
+	EXPECT_EQ(mkdirat(self->fd, "x", 0755), -1);
+	EXPECT_EQ(errno, ENOENT);
+	EXPECT_EQ(fchmod(self->fd, 0777), -1);
+	EXPECT_EQ(errno, EROFS);
+	/* the immutable inode is checked before the read-only mount */
+	EXPECT_EQ(faccessat(self->fd, ".", W_OK, 0), -1);
+	EXPECT_EQ(errno, EPERM);
+	ASSERT_EQ(fstatvfs(self->fd, &sv), 0);
+	EXPECT_TRUE(sv.f_flag & ST_RDONLY);
+}
+
+/* the stand-in is a root of its own, ".." stays put */
+TEST_F(mount_cover, island)
+{
+	int fd;
+
+	fd = openat(self->fd, "..", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+	ASSERT_GE(fd, 0);
+	EXPECT_TRUE(same_file(fd, self->fd));
+	close(fd);
+}
+
+/*
+ * C is alive for as long as the child holds it, but it can't be reached
+ * through P anymore and it's a root of its own as well.
+ */
+TEST_F(mount_cover, held_child_detached)
+{
+	struct statfs sf;
+	int fd;
+
+	ASSERT_EQ(fstatfs(self->cfd, &sf), 0);
+	EXPECT_EQ(sf.f_type, TMPFS_MAGIC);
+
+	fd = openat(self->cfd, "..", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+	ASSERT_GE(fd, 0);
+	EXPECT_TRUE(same_file(fd, self->cfd));
+	close(fd);
+
+	fd = openat(self->dfd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+	ASSERT_GE(fd, 0);
+	ASSERT_EQ(fstatfs(fd, &sf), 0);
+	EXPECT_EQ(sf.f_type, NULL_FS_MAGIC);
+	close(fd);
+}
+
+/*
+ * The cover goes with its mountpoint: once the child has removed C's
+ * mountpoint through the bind of P, the name is gone from P as well.
+ * What was opened through the cover before stays open.
+ */
+TEST_F(mount_cover, cover_goes_with_mountpoint)
+{
+	char cmd = CMD_RMDIR;
+	struct statfs sf;
+	int ret;
+
+	ASSERT_EQ(write(self->to_child, &cmd, 1), 1);
+	ASSERT_EQ(read(self->from_child, &ret, sizeof(ret)), sizeof(ret));
+	ASSERT_EQ(ret, 0);
+
+	EXPECT_EQ(openat(self->dfd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC), -1);
+	EXPECT_EQ(errno, ENOENT);
+	ASSERT_EQ(fstatfs(self->fd, &sf), 0);
+	EXPECT_EQ(sf.f_type, NULL_FS_MAGIC);
+}
+
+/*
+ * A readdir of the stand-in holds nothing that others wait for: one holder
+ * sticks in the page fault of its buffer, and a create and a lookup of
+ * another come back meanwhile.
+ */
+TEST_F(mount_cover, readdir_blocks_nobody)
+{
+	bool stalled;
+
+	if (self->hold.uffd < 0)
+		SKIP(return, "test requires userfaultfd");
+	ASSERT_EQ(readdir_hold_check(&self->hold, self->fd, &stalled), 0);
+	EXPECT_FALSE(stalled);
+}
+
+/* where a file was mounted, the stand-in is an empty regular file */
+TEST_F(mount_cover, file_stand_in)
+{
+	char src[PATH_LEN], p[PATH_LEN];
+	struct statfs sf;
+	struct stat st;
+	char c;
+	int fd;
+
+	fd = openat(self->dfd, "file", O_RDONLY | O_CLOEXEC);
+	ASSERT_GE(fd, 0);
+	ASSERT_EQ(fstat(fd, &st), 0);
+	EXPECT_TRUE(S_ISREG(st.st_mode));
+	ASSERT_EQ(fstatfs(fd, &sf), 0);
+	EXPECT_EQ(sf.f_type, NULL_FS_MAGIC);
+	/* the two stand-ins are two inodes */
+	EXPECT_FALSE(same_file(fd, self->fd));
+	EXPECT_EQ(read(fd, &c, 1), 0);
+	EXPECT_EQ(flock(fd, LOCK_EX | LOCK_NB), -1);
+	EXPECT_EQ(errno, ENOLCK);
+
+	snprintf(src, sizeof(src), "%s/src", self->base);
+	ASSERT_EQ(touch(src), 0);
+	snprintf(p, sizeof(p), "/proc/self/fd/%d", fd);
+	EXPECT_EQ(mount(src, p, NULL, MS_BIND, NULL), -1);
+	EXPECT_EQ(errno, ENOENT);
+	close(fd);
+
+	/* writes and truncates go nowhere, like /dev/null */
+	fd = openat(self->dfd, "file", O_WRONLY | O_TRUNC | O_CLOEXEC);
+	ASSERT_GE(fd, 0);
+	EXPECT_EQ(write(fd, "xy", 2), 2);
+	EXPECT_EQ(ftruncate(fd, 4), 0);
+	ASSERT_EQ(fstat(fd, &st), 0);
+	EXPECT_EQ(st.st_size, 0);
+	/* the shared inode keeps its identity */
+	EXPECT_EQ(fchmod(fd, 0600), -1);
+	EXPECT_EQ(errno, EPERM);
+	close(fd);
+	fd = openat(self->dfd, "file", O_RDONLY | O_CLOEXEC);
+	ASSERT_GE(fd, 0);
+	EXPECT_EQ(read(fd, &c, 1), 0);
+	close(fd);
+
+	EXPECT_EQ(openat(self->dfd, "file", O_RDONLY | O_DIRECTORY | O_CLOEXEC), -1);
+	EXPECT_EQ(errno, ENOTDIR);
+}
+
+/*
+ * Two unmounted parents left covers on the same dentry. A lookup on
+ * either finds a stand-in, and U's cover stays when T goes.
+ */
+TEST_F(mount_cover, shared_mountpoint)
+{
+	char cmd = CMD_CLOSE_T;
+	struct statfs sf;
+	int fd, ret;
+
+	fd = openat(self->tfd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+	ASSERT_GE(fd, 0);
+	ASSERT_EQ(fstatfs(fd, &sf), 0);
+	EXPECT_EQ(sf.f_type, NULL_FS_MAGIC);
+	close(fd);
+
+	fd = openat(self->ufd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+	ASSERT_GE(fd, 0);
+	ASSERT_EQ(fstatfs(fd, &sf), 0);
+	EXPECT_EQ(sf.f_type, NULL_FS_MAGIC);
+	close(fd);
+
+	/* the last references to T go, with T its cover */
+	ASSERT_EQ(write(self->to_child, &cmd, 1), 1);
+	ASSERT_EQ(read(self->from_child, &ret, sizeof(ret)), sizeof(ret));
+	ASSERT_EQ(ret, 0);
+	close(self->tfd);
+	self->tfd = -1;
+
+	fd = openat(self->ufd, "covered", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+	ASSERT_GE(fd, 0);
+	ASSERT_EQ(fstatfs(fd, &sf), 0);
+	EXPECT_EQ(sf.f_type, NULL_FS_MAGIC);
+	close(fd);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/filesystems/mount_cycle/nsfs_rbind_loop_test.c b/tools/testing/selftests/filesystems/mount_cycle/nsfs_rbind_loop_test.c
new file mode 100644
index 0000000..0928a58
--- /dev/null
+++ b/tools/testing/selftests/filesystems/mount_cycle/nsfs_rbind_loop_test.c
@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A recursive bind mount of a namespace file that lives in another mount
+ * namespace copies whatever is stacked on top of it there. If that includes
+ * the file of the caller's own mount namespace, or of an older one, the copy
+ * would pin the namespace it is put in forever. The bind mount has to be
+ * refused, a plain bind mount of the file itself still works.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include "../../kselftest_harness.h"
+
+#define DIR_LEN 64
+#define PATH_LEN 128
+
+/* Child exit codes. */
+enum {
+	CHILD_OK,
+	CHILD_UNSHARE,		/* could not create the newer mount namespace */
+	CHILD_PIPE,		/* the parent went away */
+	CHILD_TMPFS,		/* could not mount the tmpfs in the new namespace */
+	CHILD_REC_ALLOWED,	/* the recursive bind mount was not refused */
+	CHILD_REC_ERRNO,	/* it was refused with the wrong error */
+	CHILD_PLAIN_REFUSED,	/* the plain bind mount of the file was refused */
+};
+
+static int write_file(const char *path, const char *s)
+{
+	ssize_t n = -1;
+	int fd;
+
+	fd = open(path, O_WRONLY | O_CLOEXEC);
+	if (fd >= 0) {
+		n = write(fd, s, strlen(s));
+		close(fd);
+	}
+	return n == (ssize_t)strlen(s) ? 0 : -1;
+}
+
+static int create_file(const char *path)
+{
+	int fd = open(path, O_WRONLY | O_CREAT | O_CLOEXEC, 0644);
+
+	if (fd < 0)
+		return -1;
+	close(fd);
+	return 0;
+}
+
+/* Become root in a new user namespace with a private mount namespace. */
+static int enter_userns(void)
+{
+	uid_t uid = getuid();
+	gid_t gid = getgid();
+	char map[32];
+
+	if (unshare(CLONE_NEWUSER | CLONE_NEWNS))
+		return -1;
+	if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT)
+		return -1;
+	snprintf(map, sizeof(map), "0 %d 1", uid);
+	if (write_file("/proc/self/uid_map", map))
+		return -1;
+	snprintf(map, sizeof(map), "0 %d 1", gid);
+	if (write_file("/proc/self/gid_map", map))
+		return -1;
+	if (setgid(0) || setuid(0))
+		return -1;
+	return mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL);
+}
+
+static int send_msg(int fd, char msg)
+{
+	return write(fd, &msg, 1) == 1 ? 0 : -1;
+}
+
+static char recv_msg(int fd)
+{
+	char msg;
+
+	if (read(fd, &msg, 1) != 1)
+		return 0;
+	return msg;
+}
+
+FIXTURE(nsfs_rbind_loop) {
+	char dir[DIR_LEN];
+	char x[PATH_LEN];
+};
+
+FIXTURE_SETUP(nsfs_rbind_loop)
+{
+	snprintf(self->dir, sizeof(self->dir), "/tmp/nsfs_rbind_loop.XXXXXX");
+	ASSERT_NE(mkdtemp(self->dir), NULL);
+	if (enter_userns()) {
+		rmdir(self->dir);
+		SKIP(return, "test requires user namespaces");
+	}
+	ASSERT_EQ(mount("tmpfs", self->dir, "tmpfs", 0, NULL), 0);
+	snprintf(self->x, sizeof(self->x), "%s/x", self->dir);
+	ASSERT_EQ(create_file(self->x), 0);
+}
+
+FIXTURE_TEARDOWN(nsfs_rbind_loop)
+{
+	umount2(self->dir, MNT_DETACH);
+	rmdir(self->dir);
+}
+
+/*
+ * The child in the newer mount namespace binds the network namespace file
+ * mount of the parent through @fd. Recursively that would copy the mount of
+ * its own mount namespace file that the parent stacked on top.
+ */
+static int newer_ns_child(const char *dir, int fd, int to_parent, int from_parent)
+{
+	char src[32], y[PATH_LEN];
+
+	if (unshare(CLONE_NEWNS))
+		return CHILD_UNSHARE;
+	if (send_msg(to_parent, 'r') || recv_msg(from_parent) != 'g')
+		return CHILD_PIPE;
+
+	snprintf(y, sizeof(y), "%s/y", dir);
+	if (mount("tmpfs", y, "tmpfs", 0, NULL))
+		return CHILD_TMPFS;
+	snprintf(src, sizeof(src), "/proc/self/fd/%d", fd);
+	snprintf(y, sizeof(y), "%s/y/f", dir);
+	if (create_file(y))
+		return CHILD_TMPFS;
+
+	if (!mount(src, y, NULL, MS_BIND | MS_REC, NULL))
+		return CHILD_REC_ALLOWED;
+	if (errno != EINVAL)
+		return CHILD_REC_ERRNO;
+	if (mount(src, y, NULL, MS_BIND, NULL))
+		return CHILD_PLAIN_REFUSED;
+	umount2(y, MNT_DETACH);
+	return CHILD_OK;
+}
+
+TEST_F(nsfs_rbind_loop, own_ns_file_below_foreign_source)
+{
+	int to_child[2], to_parent[2], fd, status;
+	char p[PATH_LEN];
+	pid_t pid;
+
+	snprintf(p, sizeof(p), "%s/y", self->dir);
+	ASSERT_EQ(mkdir(p, 0755), 0);
+
+	/* M, a mount of our network namespace file, held by a descriptor */
+	ASSERT_EQ(mount("/proc/self/ns/net", self->x, NULL, MS_BIND, NULL), 0);
+	fd = open(self->x, O_PATH | O_CLOEXEC);
+	ASSERT_GE(fd, 0);
+
+	ASSERT_EQ(pipe(to_child), 0);
+	ASSERT_EQ(pipe(to_parent), 0);
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (pid == 0) {
+		close(to_child[1]);
+		close(to_parent[0]);
+		_exit(newer_ns_child(self->dir, fd, to_parent[1], to_child[0]));
+	}
+	close(to_child[0]);
+	close(to_parent[1]);
+	ASSERT_EQ(recv_msg(to_parent[0]), 'r');
+
+	/* the child's mount namespace file on top of M */
+	snprintf(p, sizeof(p), "/proc/%d/ns/mnt", pid);
+	ASSERT_EQ(mount(p, self->x, NULL, MS_BIND, NULL), 0);
+
+	ASSERT_EQ(send_msg(to_child[1], 'g'), 0);
+	ASSERT_EQ(waitpid(pid, &status, 0), pid);
+	ASSERT_TRUE(WIFEXITED(status));
+	ASSERT_EQ(WEXITSTATUS(status), CHILD_OK);
+
+	close(fd);
+	ASSERT_EQ(umount2(self->x, MNT_DETACH), 0);
+	ASSERT_EQ(umount2(self->x, MNT_DETACH), 0);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/filesystems/mount_cycle/overmount_ns_file_test.c b/tools/testing/selftests/filesystems/mount_cycle/overmount_ns_file_test.c
new file mode 100644
index 0000000..ea1b33f
--- /dev/null
+++ b/tools/testing/selftests/filesystems/mount_cycle/overmount_ns_file_test.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A mount namespace file bind-mounted on top of the mount that is moved
+ * onto a shared mount isn't copied to the peers and slaves. The mount that
+ * already sits at the destination in a slave has to end up on top of the
+ * propagated copy, not below the root of the mount namespace file where no
+ * path walk ever finds it.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+
+#include "../wrappers.h"
+#include "../../kselftest_harness.h"
+
+#define DIR_LEN 64
+#define PATH_LEN 128
+
+static int write_file(const char *path, const char *s)
+{
+	ssize_t n = -1;
+	int fd;
+
+	fd = open(path, O_WRONLY | O_CLOEXEC);
+	if (fd >= 0) {
+		n = write(fd, s, strlen(s));
+		close(fd);
+	}
+	return n == (ssize_t)strlen(s) ? 0 : -1;
+}
+
+static int create_file(const char *path, const char *s)
+{
+	ssize_t n = -1;
+	int fd;
+
+	fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
+	if (fd >= 0) {
+		n = write(fd, s, strlen(s));
+		close(fd);
+	}
+	return n == (ssize_t)strlen(s) ? 0 : -1;
+}
+
+/* the first bytes of the file at @path, "" if it can't be read */
+static const char *read_file(const char *path, char *buf, size_t len)
+{
+	ssize_t n = -1;
+	int fd;
+
+	fd = open(path, O_RDONLY | O_CLOEXEC);
+	if (fd >= 0) {
+		n = read(fd, buf, len - 1);
+		close(fd);
+	}
+	buf[n > 0 ? n : 0] = '\0';
+	return buf;
+}
+
+/* Become root in a new user namespace with a private mount namespace. */
+static int enter_userns(void)
+{
+	uid_t uid = getuid();
+	gid_t gid = getgid();
+	char map[32];
+
+	if (unshare(CLONE_NEWUSER | CLONE_NEWNS))
+		return -1;
+	if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT)
+		return -1;
+	snprintf(map, sizeof(map), "0 %d 1", uid);
+	if (write_file("/proc/self/uid_map", map))
+		return -1;
+	snprintf(map, sizeof(map), "0 %d 1", gid);
+	if (write_file("/proc/self/gid_map", map))
+		return -1;
+	if (setgid(0) || setuid(0))
+		return -1;
+	return mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL);
+}
+
+FIXTURE(overmount_ns_file) {
+	char dir[DIR_LEN];
+	pid_t child;
+};
+
+FIXTURE_SETUP(overmount_ns_file)
+{
+	self->child = -1;
+	snprintf(self->dir, sizeof(self->dir), "/tmp/overmount_ns_file.XXXXXX");
+	ASSERT_NE(mkdtemp(self->dir), NULL);
+	if (enter_userns()) {
+		rmdir(self->dir);
+		SKIP(return, "test requires user namespaces");
+	}
+	ASSERT_EQ(mount("tmpfs", self->dir, "tmpfs", 0, NULL), 0);
+}
+
+FIXTURE_TEARDOWN(overmount_ns_file)
+{
+	if (self->child > 0) {
+		kill(self->child, SIGKILL);
+		waitpid(self->child, NULL, 0);
+	}
+	umount2(self->dir, MNT_DETACH);
+	rmdir(self->dir);
+}
+
+/*
+ * A is a shared tmpfs and B its slave with Q, a bind mount of a file, on
+ * B/file. S is a bind mount of a file with N, a bind mount of a newer mount
+ * namespace's file, on top of it. S is moved onto A/file. Its copy S' lands
+ * on B/file below Q, without N. B/file keeps reading Q and once Q is
+ * unmounted it reads S'.
+ */
+TEST_F(overmount_ns_file, existing_mount_stays_on_top)
+{
+	char a[PATH_LEN], b[PATH_LEN], s[PATH_LEN], p[PATH_LEN], buf[16];
+	int fd, pfd[2];
+	char c;
+
+	snprintf(a, sizeof(a), "%s/A", self->dir);
+	snprintf(b, sizeof(b), "%s/B", self->dir);
+	snprintf(s, sizeof(s), "%s/s", self->dir);
+	ASSERT_EQ(mkdir(a, 0755), 0);
+	ASSERT_EQ(mkdir(b, 0755), 0);
+	ASSERT_EQ(mkdir(s, 0755), 0);
+
+	/* A shared, B its slave, Q on B/file */
+	ASSERT_EQ(mount("tmpfs", a, "tmpfs", 0, NULL), 0);
+	ASSERT_EQ(mount(NULL, a, NULL, MS_SHARED, NULL), 0);
+	snprintf(p, sizeof(p), "%s/A/file", self->dir);
+	ASSERT_EQ(create_file(p, "A"), 0);
+	ASSERT_EQ(mount(a, b, NULL, MS_BIND, NULL), 0);
+	ASSERT_EQ(mount(NULL, b, NULL, MS_SLAVE, NULL), 0);
+	snprintf(p, sizeof(p), "%s/Q", self->dir);
+	ASSERT_EQ(create_file(p, "Q"), 0);
+	snprintf(b, sizeof(b), "%s/B/file", self->dir);
+	ASSERT_EQ(mount(p, b, NULL, MS_BIND, NULL), 0);
+
+	/* S on s/f, pinned by a file descriptor before N goes on top */
+	ASSERT_EQ(mount("tmpfs", s, "tmpfs", 0, NULL), 0);
+	snprintf(p, sizeof(p), "%s/s/f", self->dir);
+	ASSERT_EQ(create_file(p, "f"), 0);
+	snprintf(s, sizeof(s), "%s/s/S", self->dir);
+	ASSERT_EQ(create_file(s, "S"), 0);
+	ASSERT_EQ(mount(s, p, NULL, MS_BIND, NULL), 0);
+	fd = open(p, O_PATH | O_CLOEXEC);
+	ASSERT_GE(fd, 0);
+
+	/* a newer mount namespace whose file can be bound */
+	ASSERT_EQ(pipe(pfd), 0);
+	self->child = fork();
+	ASSERT_GE(self->child, 0);
+	if (self->child == 0) {
+		close(pfd[0]);
+		if (unshare(CLONE_NEWNS) || write(pfd[1], "r", 1) != 1)
+			_exit(1);
+		pause();
+		_exit(0);
+	}
+	close(pfd[1]);
+	ASSERT_EQ(read(pfd[0], &c, 1), 1);
+	close(pfd[0]);
+	snprintf(s, sizeof(s), "/proc/%d/ns/mnt", self->child);
+	ASSERT_EQ(mount(s, p, NULL, MS_BIND, NULL), 0);
+
+	ASSERT_STREQ(read_file(b, buf, sizeof(buf)), "Q");
+
+	snprintf(a, sizeof(a), "%s/A/file", self->dir);
+	ASSERT_EQ(sys_move_mount(fd, "", AT_FDCWD, a, MOVE_MOUNT_F_EMPTY_PATH), 0);
+	close(fd);
+
+	/* Q is still on top of the copy in B and can be unmounted */
+	EXPECT_STREQ(read_file(b, buf, sizeof(buf)), "Q");
+	EXPECT_EQ(umount2(b, 0), 0);
+	EXPECT_STREQ(read_file(b, buf, sizeof(buf)), "S");
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/filesystems/mount_cycle/overmount_reparent_test.c b/tools/testing/selftests/filesystems/mount_cycle/overmount_reparent_test.c
new file mode 100644
index 0000000..46a56b7
--- /dev/null
+++ b/tools/testing/selftests/filesystems/mount_cycle/overmount_reparent_test.c
@@ -0,0 +1,181 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * propagate_umount() slides a surviving overmount off a mount that is being
+ * unmounted and unmounts that mount right after. If a file descriptor keeps
+ * the unmounted mount alive its ->overmount is left pointing at the moved
+ * mount, which is freed on its own schedule. move_mount(MOVE_MOUNT_BENEATH)
+ * with such a file descriptor as the target walks ->overmount in
+ * topmost_overmount() before it checks that the target is still mounted and
+ * must not step into the freed mount.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+
+#include "../wrappers.h"
+#include "../../kselftest_harness.h"
+
+#ifndef MOVE_MOUNT_BENEATH
+#define MOVE_MOUNT_BENEATH 0x00000200
+#endif
+
+#ifndef FSCONFIG_CMD_CREATE
+#define FSCONFIG_CMD_CREATE 6
+#endif
+
+#define DIR_LEN 64
+#define PATH_LEN 128
+
+static int write_file(const char *path, const char *s)
+{
+	ssize_t n = -1;
+	int fd;
+
+	fd = open(path, O_WRONLY | O_CLOEXEC);
+	if (fd >= 0) {
+		n = write(fd, s, strlen(s));
+		close(fd);
+	}
+	return n == (ssize_t)strlen(s) ? 0 : -1;
+}
+
+/* Become root in a new user namespace with a private mount namespace. */
+static int enter_userns(void)
+{
+	uid_t uid = getuid();
+	gid_t gid = getgid();
+	char map[32];
+
+	if (unshare(CLONE_NEWUSER | CLONE_NEWNS))
+		return -1;
+	if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT)
+		return -1;
+	snprintf(map, sizeof(map), "0 %d 1", uid);
+	if (write_file("/proc/self/uid_map", map))
+		return -1;
+	snprintf(map, sizeof(map), "0 %d 1", gid);
+	if (write_file("/proc/self/gid_map", map))
+		return -1;
+	if (setgid(0) || setuid(0))
+		return -1;
+	return mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL);
+}
+
+/* A detached tmpfs mount to serve as the source of a move. */
+static int detached_tmpfs(void)
+{
+	int sfd, mfd;
+
+	sfd = sys_fsopen("tmpfs", 0);
+	if (sfd < 0)
+		return -1;
+	if (sys_fsconfig(sfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0)) {
+		close(sfd);
+		return -1;
+	}
+	mfd = sys_fsmount(sfd, 0, 0);
+	close(sfd);
+	return mfd;
+}
+
+FIXTURE(overmount_reparent) {
+	char dir[DIR_LEN];
+};
+
+FIXTURE_SETUP(overmount_reparent)
+{
+	snprintf(self->dir, sizeof(self->dir), "/tmp/overmount_reparent.XXXXXX");
+	ASSERT_NE(mkdtemp(self->dir), NULL);
+	if (enter_userns()) {
+		rmdir(self->dir);
+		SKIP(return, "test requires user namespaces");
+	}
+}
+
+FIXTURE_TEARDOWN(overmount_reparent)
+{
+	char p[PATH_LEN];
+
+	snprintf(p, sizeof(p), "%s/b", self->dir);
+	umount2(p, MNT_DETACH);
+	snprintf(p, sizeof(p), "%s/a", self->dir);
+	umount2(p, MNT_DETACH);
+}
+
+/*
+ * A shared mount base_a is bind-mounted to base_b as its peer. A mount X on
+ * base_a/mp propagates a copy X_b onto base_b/mp. A file descriptor pins X_b,
+ * then X_b is made private and an overmount is stacked on its root.
+ *
+ * A lazy unmount of X propagates to X_b: X_b is committed to the unmount while
+ * its overmount survives, so propagate_umount() reparents the overmount onto
+ * base_b and unmounts X_b, which the file descriptor keeps alive. Unmounting
+ * the reparented overmount frees it. X_b->overmount now dangles unless
+ * mnt_change_mountpoint() reset it.
+ *
+ * move_mount(MOVE_MOUNT_BENEATH) through the file descriptor makes
+ * do_lock_mount() follow X_b->overmount in topmost_overmount() before it
+ * notices that X_b is no longer mounted. With the pointer reset the move fails
+ * cleanly with ENOENT; otherwise it reads the freed overmount.
+ */
+TEST_F(overmount_reparent, dead_overmount_holder_not_followed)
+{
+	char a[PATH_LEN], b[PATH_LEN], amp[PATH_LEN], bmp[PATH_LEN];
+	int fd, mfd, ret;
+
+	snprintf(a, sizeof(a), "%s/a", self->dir);
+	snprintf(b, sizeof(b), "%s/b", self->dir);
+	ASSERT_EQ(mkdir(a, 0755), 0);
+	ASSERT_EQ(mkdir(b, 0755), 0);
+
+	ASSERT_EQ(mount("tmpfs", a, "tmpfs", 0, NULL), 0);
+	ASSERT_EQ(mount(NULL, a, NULL, MS_SHARED, NULL), 0);
+	ASSERT_EQ(mount(a, b, NULL, MS_BIND, NULL), 0);
+
+	snprintf(amp, sizeof(amp), "%s/a/mp", self->dir);
+	snprintf(bmp, sizeof(bmp), "%s/b/mp", self->dir);
+	/* base_a and base_b share one superblock, so this dir is in both. */
+	ASSERT_EQ(mkdir(amp, 0755), 0);
+
+	/* X on base_a/mp propagates a copy X_b onto base_b/mp. */
+	ASSERT_EQ(mount("tmpfs", amp, "tmpfs", 0, NULL), 0);
+
+	/* Pin X_b before anything is stacked on top of it. */
+	fd = open(bmp, O_PATH | O_CLOEXEC);
+	ASSERT_GE(fd, 0);
+
+	/* Keep the overmount local to X_b. */
+	ASSERT_EQ(mount(NULL, bmp, NULL, MS_PRIVATE, NULL), 0);
+
+	/* The overmount on X_b's root: X_b->overmount points at it. */
+	ASSERT_EQ(mount("tmpfs", bmp, "tmpfs", 0, NULL), 0);
+
+	/* Reparents the overmount onto base_b and unmounts X_b. */
+	ASSERT_EQ(umount2(amp, MNT_DETACH), 0);
+
+	/* Free the reparented overmount. */
+	ASSERT_EQ(umount2(bmp, MNT_DETACH), 0);
+	usleep(100000);
+
+	mfd = detached_tmpfs();
+	ASSERT_GE(mfd, 0);
+
+	ret = sys_move_mount(mfd, "", fd, "",
+			     MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH |
+			     MOVE_MOUNT_BENEATH);
+	EXPECT_EQ(ret, -1);
+	EXPECT_EQ(errno, ENOENT);
+
+	close(mfd);
+	close(fd);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/filesystems/mount_cycle/settings b/tools/testing/selftests/filesystems/mount_cycle/settings
new file mode 100644
index 0000000..694d707
--- /dev/null
+++ b/tools/testing/selftests/filesystems/mount_cycle/settings
@@ -0,0 +1 @@
+timeout=300
diff --git a/tools/testing/selftests/filesystems/mount_cycle/unmounted_tree_test.c b/tools/testing/selftests/filesystems/mount_cycle/unmounted_tree_test.c
new file mode 100644
index 0000000..233f8ea
--- /dev/null
+++ b/tools/testing/selftests/filesystems/mount_cycle/unmounted_tree_test.c
@@ -0,0 +1,484 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * An unmounted mount tree that a file descriptor keeps alive is put and
+ * vacated behind the root's back, so nothing may walk it under
+ * namespace_sem: it can't be copied recursively and its propagation can't
+ * be changed. The mount at its root alone can still be copied and that copy
+ * is an ordinary mount.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <linux/mount.h>
+
+#include "../wrappers.h"
+#include "../../kselftest_harness.h"
+
+#ifndef __NR_mount_setattr
+#define __NR_mount_setattr 442
+#endif
+
+#ifndef __NR_pidfd_open
+#define __NR_pidfd_open 434
+#endif
+
+#define PATH_LEN	64
+
+static inline int sys_mount_setattr(int dfd, const char *path, unsigned int flags,
+				    struct mount_attr *attr, size_t size)
+{
+	return syscall(__NR_mount_setattr, dfd, path, flags, attr, size);
+}
+
+static inline int sys_pidfd_open(pid_t pid, unsigned int flags)
+{
+	return syscall(__NR_pidfd_open, pid, flags);
+}
+
+static int write_file(const char *path, const char *s)
+{
+	ssize_t n = -1;
+	int fd;
+
+	fd = open(path, O_WRONLY | O_CLOEXEC);
+	if (fd >= 0) {
+		n = write(fd, s, strlen(s));
+		close(fd);
+	}
+	return n == (ssize_t)strlen(s) ? 0 : -1;
+}
+
+static int touch(const char *path)
+{
+	int fd;
+
+	fd = open(path, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644);
+	if (fd < 0)
+		return -1;
+	close(fd);
+	return 0;
+}
+
+/* Become root in a new user namespace with a private mount namespace. */
+static int enter_userns(void)
+{
+	uid_t uid = getuid();
+	gid_t gid = getgid();
+	char map[32];
+
+	if (unshare(CLONE_NEWUSER | CLONE_NEWNS))
+		return -1;
+	if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT)
+		return -1;
+	snprintf(map, sizeof(map), "0 %d 1", uid);
+	if (write_file("/proc/self/uid_map", map))
+		return -1;
+	snprintf(map, sizeof(map), "0 %d 1", gid);
+	if (write_file("/proc/self/gid_map", map))
+		return -1;
+	if (setgid(0) || setuid(0))
+		return -1;
+	return mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL);
+}
+
+/* A private mount namespace of our own, all mounts private. */
+static int own_mntns(void)
+{
+	if (unshare(CLONE_NEWNS))
+		return -1;
+	return mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL);
+}
+
+FIXTURE(unmounted_tree) {
+	char dir[PATH_LEN];
+};
+
+FIXTURE_SETUP(unmounted_tree)
+{
+	snprintf(self->dir, sizeof(self->dir), "/tmp/unmounted_tree.XXXXXX");
+	ASSERT_NE(mkdtemp(self->dir), NULL);
+	if (enter_userns()) {
+		rmdir(self->dir);
+		SKIP(return, "test requires user namespaces");
+	}
+	ASSERT_EQ(mount("tmpfs", self->dir, "tmpfs", 0, NULL), 0);
+}
+
+FIXTURE_TEARDOWN(unmounted_tree)
+{
+	umount2(self->dir, MNT_DETACH);
+	rmdir(self->dir);
+}
+
+/* A recursive copy of the mount @fd refers to must fail with EINVAL. */
+static void assert_not_walked(struct __test_metadata *_metadata, int fd,
+			      const char *target)
+{
+	char link[PATH_LEN];
+	int tfd;
+
+	snprintf(link, sizeof(link), "/proc/self/fd/%d", fd);
+	EXPECT_EQ(mount(link, target, NULL, MS_BIND | MS_REC, NULL), -1);
+	EXPECT_EQ(errno, EINVAL);
+
+	tfd = sys_open_tree(fd, "", AT_EMPTY_PATH | AT_RECURSIVE | OPEN_TREE_CLONE |
+			    OPEN_TREE_CLOEXEC);
+	EXPECT_LT(tfd, 0);
+	EXPECT_EQ(errno, EINVAL);
+	if (tfd >= 0)
+		close(tfd);
+}
+
+/* The mount @fd refers to can be copied on its own and mounted on @target. */
+static void assert_root_copied(struct __test_metadata *_metadata, int fd,
+			       const char *target)
+{
+	int tfd;
+
+	tfd = sys_open_tree(fd, "", AT_EMPTY_PATH | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
+	ASSERT_GE(tfd, 0);
+	ASSERT_EQ(sys_move_mount(tfd, "", AT_FDCWD, target, MOVE_MOUNT_F_EMPTY_PATH), 0);
+	close(tfd);
+	ASSERT_EQ(umount2(target, MNT_DETACH), 0);
+}
+
+/*
+ * A lazily unmounted bind mount of an nsfs file, kept alive by an fd, can't
+ * be copied recursively anymore. Copying the mount itself still works and
+ * so does copying a mounted bind mount of the same file.
+ */
+TEST_F(unmounted_tree, detached_nsfs_bind_not_walked)
+{
+	char x[PATH_LEN], y[PATH_LEN], z[PATH_LEN], link[PATH_LEN];
+	int fd, tfd;
+
+	snprintf(x, sizeof(x), "%s/x", self->dir);
+	snprintf(y, sizeof(y), "%s/y", self->dir);
+	snprintf(z, sizeof(z), "%s/z", self->dir);
+	ASSERT_EQ(touch(x), 0);
+	ASSERT_EQ(touch(y), 0);
+	ASSERT_EQ(touch(z), 0);
+
+	ASSERT_EQ(mount("/proc/self/ns/net", x, NULL, MS_BIND, NULL), 0);
+	fd = open(x, O_PATH | O_CLOEXEC);
+	ASSERT_GE(fd, 0);
+	ASSERT_EQ(umount2(x, MNT_DETACH), 0);
+
+	assert_not_walked(_metadata, fd, y);
+
+	snprintf(link, sizeof(link), "/proc/self/fd/%d", fd);
+	ASSERT_EQ(mount(link, y, NULL, MS_BIND, NULL), 0);
+	ASSERT_EQ(umount2(y, MNT_DETACH), 0);
+	assert_root_copied(_metadata, fd, y);
+	/* opening it is fine too */
+	tfd = sys_open_tree(fd, "", AT_EMPTY_PATH | OPEN_TREE_CLOEXEC);
+	ASSERT_GE(tfd, 0);
+	close(tfd);
+	close(fd);
+
+	ASSERT_EQ(mount("/proc/self/ns/net", y, NULL, MS_BIND, NULL), 0);
+	ASSERT_EQ(mount(y, z, NULL, MS_BIND | MS_REC, NULL), 0);
+	tfd = sys_open_tree(AT_FDCWD, y, AT_RECURSIVE | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
+	ASSERT_GE(tfd, 0);
+	close(tfd);
+}
+
+/* Bind-mount the pidfd @pidfd on @target the way pidfd_bind_mount does. */
+static int bind_pidfd(int pidfd, const char *target)
+{
+	int tfd, ret;
+
+	tfd = sys_open_tree(pidfd, "", AT_EMPTY_PATH | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
+	if (tfd < 0)
+		return -1;
+	ret = sys_move_mount(tfd, "", AT_FDCWD, target, MOVE_MOUNT_F_EMPTY_PATH);
+	close(tfd);
+	return ret;
+}
+
+/* The same for a bind mount of a pidfd. */
+TEST_F(unmounted_tree, detached_pidfs_bind_not_walked)
+{
+	char x[PATH_LEN], y[PATH_LEN];
+	int pidfd, fd, tfd;
+
+	snprintf(x, sizeof(x), "%s/x", self->dir);
+	snprintf(y, sizeof(y), "%s/y", self->dir);
+	ASSERT_EQ(touch(x), 0);
+	ASSERT_EQ(touch(y), 0);
+
+	pidfd = sys_pidfd_open(getpid(), 0);
+	ASSERT_GE(pidfd, 0);
+	ASSERT_EQ(bind_pidfd(pidfd, x), 0);
+	fd = open(x, O_PATH | O_CLOEXEC);
+	ASSERT_GE(fd, 0);
+	ASSERT_EQ(umount2(x, MNT_DETACH), 0);
+
+	assert_not_walked(_metadata, fd, y);
+	assert_root_copied(_metadata, fd, y);
+	close(fd);
+
+	ASSERT_EQ(bind_pidfd(pidfd, y), 0);
+	tfd = sys_open_tree(AT_FDCWD, y, AT_RECURSIVE | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
+	ASSERT_GE(tfd, 0);
+	close(tfd);
+	close(pidfd);
+}
+
+/* What a child in its own mount namespace reports back. */
+enum {
+	CHILD_OK,
+	CHILD_NS,		/* could not set up the mount namespace */
+	CHILD_MOUNT,		/* could not set up the mounts */
+	CHILD_PIPE,		/* the parent went away */
+	CHILD_WALKED,		/* the detached tree was copied recursively */
+	CHILD_ROOT_REFUSED,	/* the mount at its root wasn't copied */
+	CHILD_STILL_MOUNTED,	/* the copy survived the unlink of its mountpoint */
+	CHILD_ERRNO,		/* refused, but not with EINVAL */
+};
+
+static const char *child_reason(int code)
+{
+	static const char *const reasons[] = {
+		[CHILD_OK]		= "ok",
+		[CHILD_NS]		= "could not set up the mount namespace",
+		[CHILD_MOUNT]		= "could not set up the mounts",
+		[CHILD_PIPE]		= "the parent went away",
+		[CHILD_WALKED]		= "the detached tree was copied recursively",
+		[CHILD_ROOT_REFUSED]	= "the mount at its root wasn't copied",
+		[CHILD_STILL_MOUNTED]	= "the copy survived the unlink of its mountpoint",
+		[CHILD_ERRNO]		= "refused, but not with EINVAL",
+	};
+
+	if (code < 0 || code >= (int)(sizeof(reasons) / sizeof(reasons[0])))
+		return "child died";
+	return reasons[code];
+}
+
+struct child_args {
+	const char *x;		/* the nsfs bind mount goes here */
+	const char *s;		/* a file to bind on top of it */
+	const char *y;		/* a target to copy to */
+};
+
+/*
+ * Run @fn in a child in a mount namespace of its own. Once the child
+ * reports that its mounts are in place, unlink @victim here, where it is a
+ * plain file, and let the child carry on. Returns what the child reported.
+ */
+static int run_child(struct __test_metadata *_metadata,
+		     int (*fn)(const struct child_args *, int, int),
+		     const struct child_args *a, const char *victim)
+{
+	int to_parent[2], to_child[2], status;
+	pid_t pid;
+	char c;
+
+	if (pipe(to_parent) || pipe(to_child))
+		return -1;
+	pid = fork();
+	if (pid < 0)
+		return -1;
+	if (pid == 0) {
+		close(to_parent[0]);
+		close(to_child[1]);
+		_exit(fn(a, to_parent[1], to_child[0]));
+	}
+	close(to_parent[1]);
+	close(to_child[0]);
+	if (read(to_parent[0], &c, 1) == 1) {
+		/* not a mountpoint in this namespace, so the file can go */
+		EXPECT_EQ(unlink(victim), 0);
+		EXPECT_EQ(write(to_child[1], "", 1), 1);
+	}
+	close(to_parent[0]);
+	close(to_child[1]);
+	if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status))
+		return -1;
+	return WEXITSTATUS(status);
+}
+
+/*
+ * An nsfs bind mount on @x, an fd on it and a bind mount of @s on top. Once
+ * the parent has unlinked @x underneath, the first mount is detached and the
+ * second one, which stayed attached to it, has been vacated. The tree may
+ * not be walked, the mount at its root may still be copied.
+ */
+static int vacant_child(const struct child_args *a, int to_parent, int from_parent)
+{
+	char link[PATH_LEN];
+	int fd, tfd;
+	char c;
+
+	if (own_mntns())
+		return CHILD_NS;
+	if (mount("/proc/self/ns/net", a->x, NULL, MS_BIND, NULL))
+		return CHILD_MOUNT;
+	fd = open(a->x, O_PATH | O_CLOEXEC);
+	if (fd < 0 || mount(a->s, a->x, NULL, MS_BIND, NULL))
+		return CHILD_MOUNT;
+
+	if (write(to_parent, "", 1) != 1 || read(from_parent, &c, 1) != 1)
+		return CHILD_PIPE;
+
+	tfd = sys_open_tree(fd, "", AT_EMPTY_PATH | AT_RECURSIVE | OPEN_TREE_CLONE |
+			    OPEN_TREE_CLOEXEC);
+	if (tfd >= 0) {
+		close(tfd);
+		return CHILD_WALKED;
+	}
+	if (errno != EINVAL)
+		return CHILD_ERRNO;
+	snprintf(link, sizeof(link), "/proc/self/fd/%d", fd);
+	if (!mount(link, a->y, NULL, MS_BIND | MS_REC, NULL))
+		return CHILD_WALKED;
+	if (errno != EINVAL)
+		return CHILD_ERRNO;
+
+	tfd = sys_open_tree(fd, "", AT_EMPTY_PATH | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
+	if (tfd < 0)
+		return CHILD_ROOT_REFUSED;
+	close(tfd);
+	/* the last reference on the detached mount collects the vacant one */
+	close(fd);
+	return CHILD_OK;
+}
+
+/*
+ * unlink() of the child's mountpoint from here, where it is a plain file,
+ * detaches the child's nsfs bind mount with the mount on top left attached
+ * to it. The mount on top loses its last reference right there and is
+ * vacated. The child must not be able to walk that tree.
+ */
+TEST_F(unmounted_tree, vacant_child_not_copied)
+{
+	char x[PATH_LEN], s[PATH_LEN], y[PATH_LEN];
+	struct child_args a = { .x = x, .s = s, .y = y };
+	int ret;
+
+	snprintf(x, sizeof(x), "%s/x", self->dir);
+	snprintf(s, sizeof(s), "%s/s", self->dir);
+	snprintf(y, sizeof(y), "%s/y", self->dir);
+	ASSERT_EQ(touch(x), 0);
+	ASSERT_EQ(touch(s), 0);
+	ASSERT_EQ(touch(y), 0);
+
+	ret = run_child(_metadata, vacant_child, &a, x);
+	ASSERT_EQ(ret, CHILD_OK)
+		TH_LOG("child: %s", child_reason(ret));
+}
+
+/*
+ * An nsfs bind mount on @x, lazily unmounted but kept alive by an fd, and a
+ * copy of it on @y. That copy is a mount like any other: once the parent has
+ * unlinked @y underneath, it is unmounted and its attributes can't be changed
+ * anymore. A copy that took the flag along is only unhooked, stays in the
+ * namespace and still takes the change.
+ */
+static int ordinary_copy(const struct child_args *a, int to_parent, int from_parent)
+{
+	struct mount_attr attr = {
+		.attr_set = MOUNT_ATTR_NOSUID,
+	};
+	char link[PATH_LEN];
+	int fd, fdy;
+	char c;
+
+	if (own_mntns())
+		return CHILD_NS;
+	if (mount("/proc/self/ns/net", a->x, NULL, MS_BIND, NULL))
+		return CHILD_MOUNT;
+	fd = open(a->x, O_PATH | O_CLOEXEC);
+	if (fd < 0 || umount2(a->x, MNT_DETACH))
+		return CHILD_MOUNT;
+	snprintf(link, sizeof(link), "/proc/self/fd/%d", fd);
+	if (mount(link, a->y, NULL, MS_BIND, NULL))
+		return CHILD_MOUNT;
+	fdy = open(a->y, O_PATH | O_CLOEXEC);
+	if (fdy < 0)
+		return CHILD_MOUNT;
+
+	if (write(to_parent, "", 1) != 1 || read(from_parent, &c, 1) != 1)
+		return CHILD_PIPE;
+
+	if (!sys_mount_setattr(fdy, "", AT_EMPTY_PATH, &attr, sizeof(attr)))
+		return CHILD_STILL_MOUNTED;
+	if (errno != EINVAL)
+		return CHILD_ERRNO;
+	close(fdy);
+	close(fd);
+	return CHILD_OK;
+}
+
+/*
+ * A copy of a detached nsfs bind mount must not take after its source and
+ * read as unmounted. unlink() of its mountpoint from here unmounts it like
+ * any other mount, so the child can't change its attributes anymore.
+ */
+TEST_F(unmounted_tree, copy_of_detached_bind_is_ordinary)
+{
+	char x[PATH_LEN], y[PATH_LEN];
+	struct child_args a = { .x = x, .y = y };
+	int ret;
+
+	snprintf(x, sizeof(x), "%s/x", self->dir);
+	snprintf(y, sizeof(y), "%s/y", self->dir);
+	ASSERT_EQ(touch(x), 0);
+	ASSERT_EQ(touch(y), 0);
+
+	ret = run_child(_metadata, ordinary_copy, &a, y);
+	ASSERT_EQ(ret, CHILD_OK)
+		TH_LOG("child: %s", child_reason(ret));
+}
+
+/*
+ * mount_setattr() may change the propagation of a detached tree while that
+ * tree is still the root of an anonymous mount namespace, but not once the
+ * tree has been dissolved and only a second fd keeps its root alive.
+ */
+TEST_F(unmounted_tree, detached_tree_setattr_refused)
+{
+	struct mount_attr attr = {
+		.propagation = MS_SHARED,
+	};
+	char a[PATH_LEN], b[PATH_LEN];
+	int tfd, fd;
+
+	snprintf(a, sizeof(a), "%s/a", self->dir);
+	snprintf(b, sizeof(b), "%s/a/b", self->dir);
+	ASSERT_EQ(mkdir(a, 0755), 0);
+	ASSERT_EQ(mount("tmpfs", a, "tmpfs", 0, NULL), 0);
+	ASSERT_EQ(mkdir(b, 0755), 0);
+	ASSERT_EQ(mount("tmpfs", b, "tmpfs", 0, NULL), 0);
+
+	tfd = sys_open_tree(AT_FDCWD, self->dir, AT_RECURSIVE | OPEN_TREE_CLONE |
+			    OPEN_TREE_CLOEXEC);
+	ASSERT_GE(tfd, 0);
+	ASSERT_EQ(sys_mount_setattr(tfd, "", AT_EMPTY_PATH | AT_RECURSIVE, &attr, sizeof(attr)), 0);
+	attr.propagation = MS_PRIVATE;
+	ASSERT_EQ(sys_mount_setattr(tfd, "", AT_EMPTY_PATH | AT_RECURSIVE, &attr, sizeof(attr)), 0);
+
+	/* a second reference on the root, then dissolve the tree */
+	fd = sys_open_tree(tfd, "", AT_EMPTY_PATH | OPEN_TREE_CLOEXEC);
+	ASSERT_GE(fd, 0);
+	close(tfd);
+
+	attr.propagation = MS_SHARED;
+	ASSERT_EQ(sys_mount_setattr(fd, "", AT_EMPTY_PATH | AT_RECURSIVE, &attr, sizeof(attr)), -1);
+	ASSERT_EQ(errno, EINVAL);
+	attr.propagation = MS_PRIVATE;
+	ASSERT_EQ(sys_mount_setattr(fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), -1);
+	ASSERT_EQ(errno, EINVAL);
+	close(fd);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/filesystems/open_tree_ns/.gitignore b/tools/testing/selftests/filesystems/open_tree_ns/.gitignore
index fb12b93..76f95c0 100644
--- a/tools/testing/selftests/filesystems/open_tree_ns/.gitignore
+++ b/tools/testing/selftests/filesystems/open_tree_ns/.gitignore
@@ -1 +1,2 @@
 open_tree_ns_test
+open_tree_ns_covered_test
diff --git a/tools/testing/selftests/filesystems/open_tree_ns/Makefile b/tools/testing/selftests/filesystems/open_tree_ns/Makefile
index 4976ed1..fb2aa77 100644
--- a/tools/testing/selftests/filesystems/open_tree_ns/Makefile
+++ b/tools/testing/selftests/filesystems/open_tree_ns/Makefile
@@ -1,5 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0
-TEST_GEN_PROGS := open_tree_ns_test
+TEST_GEN_PROGS := open_tree_ns_test open_tree_ns_covered_test
 
 CFLAGS += -Wall -O0 -g $(KHDR_INCLUDES) $(TOOLS_INCLUDES)
 LDLIBS := -lcap
diff --git a/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_covered_test.c b/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_covered_test.c
new file mode 100644
index 0000000..27f5cdd
--- /dev/null
+++ b/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_covered_test.c
@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * open_tree(OPEN_TREE_NAMESPACE) by a caller that isn't privileged over the
+ * mount namespace it copies from must not reveal what the mounts below the
+ * copied mount cover. Without AT_RECURSIVE the copy is refused when there's
+ * anything mounted below the requested directory. With AT_RECURSIVE
+ * unbindable mounts are copied as well.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include "../wrappers.h"
+#include "../../kselftest_harness.h"
+
+#ifndef OPEN_TREE_NAMESPACE
+#define OPEN_TREE_NAMESPACE	(1 << 1)
+#endif
+
+#define DIR_LEN 64
+#define PATH_LEN 128
+
+/* Child exit codes. */
+enum {
+	CHILD_OK,
+	CHILD_USERNS,		/* could not create the child user namespace */
+	CHILD_NONREC_ALLOWED,	/* the non-recursive copy was not refused */
+	CHILD_NONREC_ERRNO,	/* it was refused with the wrong error */
+	CHILD_REC_REFUSED,	/* the recursive copy failed */
+	CHILD_SETNS,		/* could not enter the new mount namespace */
+	CHILD_NO_COVER,		/* the covering mount is missing in the copy */
+	CHILD_REVEALED,		/* the covered file is visible in the copy */
+};
+
+static int write_file(const char *path, const char *s)
+{
+	ssize_t n = -1;
+	int fd;
+
+	fd = open(path, O_WRONLY | O_CLOEXEC);
+	if (fd >= 0) {
+		n = write(fd, s, strlen(s));
+		close(fd);
+	}
+	return n == (ssize_t)strlen(s) ? 0 : -1;
+}
+
+static int create_file(const char *path, const char *s)
+{
+	ssize_t n = -1;
+	int fd;
+
+	fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
+	if (fd >= 0) {
+		n = write(fd, s, strlen(s));
+		close(fd);
+	}
+	return n == (ssize_t)strlen(s) ? 0 : -1;
+}
+
+/* Become root in a new user namespace, the uid @uid is mapped to 0. */
+static int enter_userns(uid_t uid, gid_t gid)
+{
+	char map[32];
+
+	if (unshare(CLONE_NEWUSER))
+		return -1;
+	if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT)
+		return -1;
+	snprintf(map, sizeof(map), "0 %d 1", uid);
+	if (write_file("/proc/self/uid_map", map))
+		return -1;
+	snprintf(map, sizeof(map), "0 %d 1", gid);
+	if (write_file("/proc/self/gid_map", map))
+		return -1;
+	return setgid(0) || setuid(0) ? -1 : 0;
+}
+
+FIXTURE(open_tree_ns_covered) {
+	char dir[DIR_LEN];
+	char cover[PATH_LEN];
+};
+
+FIXTURE_VARIANT(open_tree_ns_covered) {
+	int propagation;
+};
+
+FIXTURE_VARIANT_ADD(open_tree_ns_covered, private_cover) {
+	.propagation = MS_PRIVATE,
+};
+
+FIXTURE_VARIANT_ADD(open_tree_ns_covered, unbindable_cover) {
+	.propagation = MS_UNBINDABLE,
+};
+
+/*
+ * Root in a user namespace owns a private mount namespace with a tmpfs
+ * on @dir and a second tmpfs covering @dir/covered/under.txt.
+ */
+FIXTURE_SETUP(open_tree_ns_covered)
+{
+	char p[PATH_LEN];
+	int fd;
+
+	snprintf(self->dir, sizeof(self->dir), "/tmp/open_tree_ns_covered.XXXXXX");
+	ASSERT_NE(mkdtemp(self->dir), NULL);
+	if (enter_userns(getuid(), getgid()) || unshare(CLONE_NEWNS)) {
+		rmdir(self->dir);
+		SKIP(return, "test requires user namespaces");
+	}
+	ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0);
+	ASSERT_EQ(mount("tmpfs", self->dir, "tmpfs", 0, NULL), 0);
+
+	/* the flag has to exist before its rules can be checked */
+	fd = sys_open_tree(AT_FDCWD, self->dir,
+			   OPEN_TREE_NAMESPACE | OPEN_TREE_CLOEXEC);
+	if (fd < 0 && (errno == EINVAL || errno == ENOSYS)) {
+		umount2(self->dir, MNT_DETACH);
+		rmdir(self->dir);
+		SKIP(return, "OPEN_TREE_NAMESPACE not supported");
+	}
+	ASSERT_GE(fd, 0);
+	close(fd);
+
+	snprintf(self->cover, sizeof(self->cover), "%s/covered", self->dir);
+	ASSERT_EQ(mkdir(self->cover, 0755), 0);
+	snprintf(p, sizeof(p), "%s/covered/under.txt", self->dir);
+	ASSERT_EQ(create_file(p, "hidden"), 0);
+	ASSERT_EQ(mount("tmpfs", self->cover, "tmpfs", 0, NULL), 0);
+	ASSERT_EQ(mount(NULL, self->cover, NULL, variant->propagation, NULL), 0);
+}
+
+FIXTURE_TEARDOWN(open_tree_ns_covered)
+{
+	umount2(self->dir, MNT_DETACH);
+	rmdir(self->dir);
+}
+
+/* A caller in a new user namespace that doesn't own the mount namespace. */
+static int foreign_child(const char *dir)
+{
+	struct stat st;
+	int fd;
+
+	if (enter_userns(0, 0))
+		return CHILD_USERNS;
+
+	fd = sys_open_tree(AT_FDCWD, dir, OPEN_TREE_NAMESPACE | OPEN_TREE_CLOEXEC);
+	if (fd >= 0)
+		return CHILD_NONREC_ALLOWED;
+	if (errno != EINVAL)
+		return CHILD_NONREC_ERRNO;
+
+	fd = sys_open_tree(AT_FDCWD, dir,
+			   OPEN_TREE_NAMESPACE | OPEN_TREE_CLOEXEC | AT_RECURSIVE);
+	if (fd < 0)
+		return CHILD_REC_REFUSED;
+	if (setns(fd, CLONE_NEWNS))
+		return CHILD_SETNS;
+	if (stat("/covered", &st))
+		return CHILD_NO_COVER;
+	if (!access("/covered/under.txt", F_OK) || errno != ENOENT)
+		return CHILD_REVEALED;
+	return CHILD_OK;
+}
+
+TEST_F(open_tree_ns_covered, foreign_user_namespace)
+{
+	int status, fd;
+	pid_t pid;
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (pid == 0)
+		_exit(foreign_child(self->dir));
+	ASSERT_EQ(waitpid(pid, &status, 0), pid);
+	ASSERT_TRUE(WIFEXITED(status));
+	ASSERT_EQ(WEXITSTATUS(status), CHILD_OK);
+
+	/* the owner of the mount namespace keeps bind mount semantics */
+	fd = sys_open_tree(AT_FDCWD, self->dir,
+			   OPEN_TREE_NAMESPACE | OPEN_TREE_CLOEXEC);
+	ASSERT_GE(fd, 0);
+	close(fd);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/filesystems/overlayfs/.gitignore b/tools/testing/selftests/filesystems/overlayfs/.gitignore
index 077f7a1..b343cc4 100644
--- a/tools/testing/selftests/filesystems/overlayfs/.gitignore
+++ b/tools/testing/selftests/filesystems/overlayfs/.gitignore
@@ -2,3 +2,4 @@
 dev_in_maps
 set_layers_via_fds
 idmapped_mounts
+automount_in_layer
diff --git a/tools/testing/selftests/filesystems/overlayfs/Makefile b/tools/testing/selftests/filesystems/overlayfs/Makefile
index b3185f6..382a59b 100644
--- a/tools/testing/selftests/filesystems/overlayfs/Makefile
+++ b/tools/testing/selftests/filesystems/overlayfs/Makefile
@@ -9,6 +9,7 @@ LOCAL_HDRS += ../wrappers.h log.h
 TEST_GEN_PROGS := dev_in_maps
 TEST_GEN_PROGS += set_layers_via_fds
 TEST_GEN_PROGS += idmapped_mounts
+TEST_GEN_PROGS += automount_in_layer
 
 include ../../lib.mk
 
diff --git a/tools/testing/selftests/filesystems/overlayfs/automount_in_layer.c b/tools/testing/selftests/filesystems/overlayfs/automount_in_layer.c
new file mode 100644
index 0000000..0476c45
--- /dev/null
+++ b/tools/testing/selftests/filesystems/overlayfs/automount_in_layer.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A layer of an overlay is a private clone of the mount it was given and
+ * belongs to no mount namespace. fanotify hands out descriptors on it. An
+ * automount triggered through one has no namespace to go into: the open has
+ * to fail, not oops with namespace_sem held.
+ */
+#define _GNU_SOURCE
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <linux/magic.h>
+#include <sys/fanotify.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/vfs.h>
+
+#include "../../kselftest_harness.h"
+
+#define DIR_LEN		64
+#define PATH_LEN	192
+
+static bool have_fs(const char *name)
+{
+	char line[128];
+	bool found = false;
+	FILE *f;
+
+	f = fopen("/proc/filesystems", "re");
+	if (!f)
+		return false;
+	while (fgets(line, sizeof(line), f)) {
+		char *nl = strchr(line, '\n');
+		char *tab = strchr(line, '\t');
+
+		if (nl)
+			*nl = 0;
+		if (tab && !strcmp(tab + 1, name))
+			found = true;
+	}
+	fclose(f);
+	return found;
+}
+
+static int mnt_id_of(int fd)
+{
+	char path[64], buf[4096], *p;
+	ssize_t n;
+	int info;
+
+	snprintf(path, sizeof(path), "/proc/self/fdinfo/%d", fd);
+	info = open(path, O_RDONLY | O_CLOEXEC);
+	if (info < 0)
+		return -1;
+	n = read(info, buf, sizeof(buf) - 1);
+	close(info);
+	if (n <= 0)
+		return -1;
+	buf[n] = 0;
+	p = strstr(buf, "mnt_id:");
+	return p ? atoi(p + strlen("mnt_id:")) : -1;
+}
+
+static bool on_debugfs(int fd)
+{
+	struct statfs sf;
+
+	return !fstatfs(fd, &sf) && sf.f_type == DEBUGFS_MAGIC;
+}
+
+FIXTURE(layer) {
+	char base[DIR_LEN];
+	int fan;
+	int evfd;	/* on the layer clone of the lower debugfs */
+};
+
+FIXTURE_SETUP(layer)
+{
+	char lower[PATH_LEN], other[PATH_LEN], ovl[PATH_LEN], opts[2 * PATH_LEN + 16];
+	struct fanotify_event_metadata *ev;
+	struct pollfd pfd;
+	char buf[4096];
+	int lfd, lower_id;
+	ssize_t n;
+	DIR *d;
+
+	self->fan = -1;
+	self->evfd = -1;
+	if (geteuid())
+		SKIP(return, "test requires root");
+	if (!have_fs("debugfs") || !have_fs("tracefs"))
+		SKIP(return, "test requires debugfs with the tracefs automount");
+	if (!have_fs("overlay"))
+		SKIP(return, "test requires overlayfs");
+
+	snprintf(self->base, sizeof(self->base), "/tmp/layer.XXXXXX");
+	ASSERT_NE(mkdtemp(self->base), NULL);
+	ASSERT_EQ(unshare(CLONE_NEWNS), 0);
+	ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0);
+	ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, "mode=0755"), 0);
+	snprintf(lower, sizeof(lower), "%s/lower", self->base);
+	snprintf(other, sizeof(other), "%s/other", self->base);
+	snprintf(ovl, sizeof(ovl), "%s/ovl", self->base);
+	ASSERT_EQ(mkdir(lower, 0755), 0);
+	ASSERT_EQ(mkdir(other, 0755), 0);
+	ASSERT_EQ(mkdir(ovl, 0755), 0);
+	ASSERT_EQ(mount("debugfs", lower, "debugfs", 0, NULL), 0);
+	snprintf(opts, sizeof(opts), "lowerdir=%s:%s", lower, other);
+	ASSERT_EQ(mount("overlay", ovl, "overlay", MS_RDONLY, opts), 0);
+
+	self->fan = fanotify_init(FAN_CLASS_NOTIF | FAN_NONBLOCK | FAN_CLOEXEC,
+				  O_RDONLY | O_CLOEXEC);
+	ASSERT_GE(self->fan, 0);
+	ASSERT_EQ(fanotify_mark(self->fan, FAN_MARK_ADD | FAN_MARK_FILESYSTEM,
+				FAN_OPEN | FAN_ONDIR, AT_FDCWD, lower), 0);
+	lfd = open(lower, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+	ASSERT_GE(lfd, 0);
+	lower_id = mnt_id_of(lfd);
+	close(lfd);
+
+	/* the overlay opens its lower directory through the layer clone */
+	d = opendir(ovl);
+	ASSERT_NE(d, NULL);
+	closedir(d);
+	pfd.fd = self->fan;
+	pfd.events = POLLIN;
+	ASSERT_EQ(poll(&pfd, 1, 5000), 1);
+	n = read(self->fan, buf, sizeof(buf));
+	ASSERT_GT(n, 0);
+	for (ev = (void *)buf; FAN_EVENT_OK(ev, n); ev = FAN_EVENT_NEXT(ev, n)) {
+		if (ev->fd < 0)
+			continue;
+		if (self->evfd < 0 && on_debugfs(ev->fd) &&
+		    mnt_id_of(ev->fd) != lower_id)
+			self->evfd = ev->fd;
+		else
+			close(ev->fd);
+	}
+	ASSERT_GE(self->evfd, 0)
+		TH_LOG("no event on the layer clone");
+}
+
+FIXTURE_TEARDOWN(layer)
+{
+	if (self->evfd >= 0)
+		close(self->evfd);
+	if (self->fan >= 0)
+		close(self->fan);
+	umount2(self->base, MNT_DETACH);
+	rmdir(self->base);
+}
+
+TEST_F(layer, automount_below_the_clone_is_refused)
+{
+	struct stat st;
+	int fd;
+
+	/* the automount point is there */
+	ASSERT_EQ(fstatat(self->evfd, "tracing", &st, AT_NO_AUTOMOUNT), 0);
+	/* the clone is in no namespace, so the automount has nowhere to go */
+	fd = openat(self->evfd, "tracing", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+	EXPECT_LT(fd, 0);
+	EXPECT_EQ(errno, EINVAL);
+	if (fd >= 0)
+		close(fd);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/filesystems/readdir_hold.h b/tools/testing/selftests/filesystems/readdir_hold.h
new file mode 100644
index 0000000..57eee1b
--- /dev/null
+++ b/tools/testing/selftests/filesystems/readdir_hold.h
@@ -0,0 +1,224 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Hold a readdir of a directory in the page fault of its buffer and see
+ * whether a create and a lookup in that directory wait for it. For a
+ * directory that is permanently empty they must not.
+ */
+#ifndef __SELFTESTS_READDIR_HOLD_H
+#define __SELFTESTS_READDIR_HOLD_H
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <linux/userfaultfd.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+
+#define HOLD_FAULT_MS	5000	/* for the reader to reach the fault */
+#define HOLD_QUEUE_MS	2000	/* for the create to queue up behind it */
+#define HOLD_LOOKUP_MS	5000	/* for the lookup to come back */
+
+struct readdir_hold {
+	int uffd;
+	int taskdir;		/* /proc/self/task, from before any namespace change */
+	long page_size;
+	char *page;		/* faults until released */
+	int dfd;
+	pid_t creator_tid;
+	int created;
+	int done[2];		/* the finder writes a byte when it is back */
+};
+
+/*
+ * The fault happens in the kernel, so the userfaultfd needs CAP_SYS_PTRACE
+ * in the initial user namespace or vm.unprivileged_userfaultfd. Call this
+ * before entering a user namespace.
+ */
+static inline int readdir_hold_init(struct readdir_hold *h)
+{
+	struct uffdio_api api = { .api = UFFD_API };
+
+	h->page = MAP_FAILED;
+	h->page_size = sysconf(_SC_PAGESIZE);
+	h->taskdir = open("/proc/self/task", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+	h->uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
+	if (h->uffd < 0 || h->taskdir < 0 || ioctl(h->uffd, UFFDIO_API, &api)) {
+		if (h->uffd >= 0)
+			close(h->uffd);
+		if (h->taskdir >= 0)
+			close(h->taskdir);
+		h->uffd = h->taskdir = -1;
+		return -1;
+	}
+	return 0;
+}
+
+static inline void readdir_hold_destroy(struct readdir_hold *h)
+{
+	if (h->uffd >= 0)
+		close(h->uffd);
+	if (h->taskdir >= 0)
+		close(h->taskdir);
+	h->uffd = h->taskdir = -1;
+}
+
+static inline void *readdir_hold_reader(void *arg)
+{
+	struct readdir_hold *h = arg;
+
+	/* the first byte written to the buffer faults until released */
+	syscall(__NR_getdents64, h->dfd, h->page, h->page_size);
+	return NULL;
+}
+
+static inline void *readdir_hold_creator(void *arg)
+{
+	struct readdir_hold *h = arg;
+
+	h->creator_tid = syscall(__NR_gettid);
+	/* takes the directory lock exclusive before it fails */
+	mkdirat(h->dfd, "x", 0755);
+	__atomic_store_n(&h->created, 1, __ATOMIC_RELEASE);
+	return NULL;
+}
+
+static inline void *readdir_hold_finder(void *arg)
+{
+	struct readdir_hold *h = arg;
+	int fd;
+
+	/* a lookup that misses the dcache takes the lock shared */
+	fd = openat(h->dfd, "no_such_name", O_RDONLY | O_CLOEXEC);
+	if (fd >= 0)
+		close(fd);
+	if (write(h->done[1], "x", 1) != 1)
+		perror("readdir_hold: finder");
+	return NULL;
+}
+
+/* the creator is back, or waits in the kernel for the lock */
+static inline bool readdir_hold_creator_settled(struct readdir_hold *h)
+{
+	char path[32], buf[256], *p;
+	ssize_t n;
+	int fd;
+
+	if (__atomic_load_n(&h->created, __ATOMIC_ACQUIRE))
+		return true;
+	if (!h->creator_tid)
+		return false;
+	snprintf(path, sizeof(path), "%d/stat", h->creator_tid);
+	fd = openat(h->taskdir, path, O_RDONLY | O_CLOEXEC);
+	if (fd < 0)
+		return false;
+	n = read(fd, buf, sizeof(buf) - 1);
+	close(fd);
+	if (n <= 0)
+		return false;
+	buf[n] = 0;
+	/* "pid (comm) state ..." */
+	p = strrchr(buf, ')');
+	return p && p[1] == ' ' && p[2] == 'D';
+}
+
+static inline bool readdir_hold_faulted(struct readdir_hold *h)
+{
+	struct pollfd pfd = { .fd = h->uffd, .events = POLLIN };
+	struct uffd_msg msg;
+
+	if (poll(&pfd, 1, HOLD_FAULT_MS) != 1)
+		return false;
+	if (read(h->uffd, &msg, sizeof(msg)) != sizeof(msg))
+		return false;
+	return msg.event == UFFD_EVENT_PAGEFAULT;
+}
+
+/* let the reader go on */
+static inline void readdir_hold_release(struct readdir_hold *h)
+{
+	struct uffdio_copy cp = {
+		.dst = (unsigned long)h->page,
+		.len = h->page_size,
+	};
+	void *zero;
+
+	zero = calloc(1, h->page_size);
+	if (!zero)
+		return;
+	cp.src = (unsigned long)zero;
+	if (ioctl(h->uffd, UFFDIO_COPY, &cp) && errno != EEXIST)
+		perror("readdir_hold: UFFDIO_COPY");
+	free(zero);
+}
+
+/*
+ * A readdir of @dfd that sticks in the fault of its buffer, then a create
+ * and a lookup in @dfd. Whether the lookup came back while the readdir was
+ * still stuck goes to @stalled. Returns -1 when that could not be found
+ * out.
+ */
+static inline int readdir_hold_check(struct readdir_hold *h, int dfd,
+				     bool *stalled)
+{
+	pthread_t reader, creator, finder;
+	struct uffdio_register reg = {};
+	struct pollfd pfd;
+	int ret = -1, i;
+
+	h->dfd = dfd;
+	h->created = 0;
+	h->creator_tid = 0;
+	h->page = mmap(NULL, h->page_size, PROT_READ | PROT_WRITE,
+		       MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+	if (h->page == MAP_FAILED)
+		return -1;
+	reg.range.start = (unsigned long)h->page;
+	reg.range.len = h->page_size;
+	reg.mode = UFFDIO_REGISTER_MODE_MISSING;
+	if (ioctl(h->uffd, UFFDIO_REGISTER, &reg) || pipe2(h->done, O_CLOEXEC))
+		goto out_page;
+
+	if (pthread_create(&reader, NULL, readdir_hold_reader, h))
+		goto out_pipe;
+	if (!readdir_hold_faulted(h))
+		goto out_reader;
+	if (pthread_create(&creator, NULL, readdir_hold_creator, h))
+		goto out_reader;
+	for (i = 0; i < HOLD_QUEUE_MS / 10 && !readdir_hold_creator_settled(h); i++)
+		usleep(10000);
+	if (pthread_create(&finder, NULL, readdir_hold_finder, h))
+		goto out_creator;
+
+	pfd.fd = h->done[0];
+	pfd.events = POLLIN;
+	*stalled = poll(&pfd, 1, HOLD_LOOKUP_MS) != 1;
+	ret = 0;
+
+	readdir_hold_release(h);
+	pthread_join(finder, NULL);
+out_creator:
+	if (ret)
+		readdir_hold_release(h);
+	pthread_join(creator, NULL);
+out_reader:
+	if (ret)
+		readdir_hold_release(h);
+	pthread_join(reader, NULL);
+out_pipe:
+	close(h->done[0]);
+	close(h->done[1]);
+out_page:
+	munmap(h->page, h->page_size);
+	h->page = MAP_FAILED;
+	return ret;
+}
+
+#endif /* __SELFTESTS_READDIR_HOLD_H */
diff --git a/tools/testing/selftests/filesystems/rw_hint_test.c b/tools/testing/selftests/filesystems/rw_hint_test.c
new file mode 100644
index 0000000..d1930f8
--- /dev/null
+++ b/tools/testing/selftests/filesystems/rw_hint_test.c
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * F_SET_RW_HINT is refused on an immutable inode. Nothing is ever written
+ * to it and it may be shared with everybody, like a namespace file or the
+ * root of an empty mount namespace.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include "../kselftest_harness.h"
+
+/* <linux/fs.h> and <linux/fcntl.h> don't mix with the libc headers */
+#ifndef FS_IOC_GETFLAGS
+#define FS_IOC_GETFLAGS		_IOR('f', 1, long)
+#define FS_IOC_SETFLAGS		_IOW('f', 2, long)
+#endif
+#ifndef FS_IMMUTABLE_FL
+#define FS_IMMUTABLE_FL		0x00000010
+#endif
+#ifndef F_LINUX_SPECIFIC_BASE
+#define F_LINUX_SPECIFIC_BASE	1024
+#endif
+#ifndef F_GET_RW_HINT
+#define F_GET_RW_HINT		(F_LINUX_SPECIFIC_BASE + 11)
+#define F_SET_RW_HINT		(F_LINUX_SPECIFIC_BASE + 12)
+#endif
+#ifndef RWH_WRITE_LIFE_SHORT
+#define RWH_WRITE_LIFE_SHORT	2
+#endif
+#ifndef UNSHARE_EMPTY_MNTNS
+#define UNSHARE_EMPTY_MNTNS	0x00100000
+#endif
+
+static int set_hint(int fd, uint64_t hint)
+{
+	return fcntl(fd, F_SET_RW_HINT, &hint);
+}
+
+static long get_hint(int fd)
+{
+	uint64_t hint;
+
+	if (fcntl(fd, F_GET_RW_HINT, &hint))
+		return -1;
+	return hint;
+}
+
+TEST(immutable_file)
+{
+	char path[] = "/tmp/rw_hint.XXXXXX";
+	int fd, flags;
+
+	if (geteuid())
+		SKIP(return, "test requires root");
+
+	fd = mkstemp(path);
+	ASSERT_GE(fd, 0);
+	unlink(path);
+	ASSERT_EQ(set_hint(fd, RWH_WRITE_LIFE_SHORT), 0);
+	EXPECT_EQ(get_hint(fd), RWH_WRITE_LIFE_SHORT);
+
+	if (ioctl(fd, FS_IOC_GETFLAGS, &flags)) {
+		close(fd);
+		SKIP(return, "no file attributes on this filesystem");
+	}
+	flags |= FS_IMMUTABLE_FL;
+	ASSERT_EQ(ioctl(fd, FS_IOC_SETFLAGS, &flags), 0);
+	EXPECT_EQ(set_hint(fd, RWH_WRITE_LIFE_SHORT), -1);
+	EXPECT_EQ(errno, EPERM);
+	flags &= ~FS_IMMUTABLE_FL;
+	ASSERT_EQ(ioctl(fd, FS_IOC_SETFLAGS, &flags), 0);
+	EXPECT_EQ(set_hint(fd, RWH_WRITE_LIFE_SHORT), 0);
+	close(fd);
+}
+
+TEST(namespace_file)
+{
+	int fd;
+
+	if (geteuid())
+		SKIP(return, "test requires root");
+
+	fd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC);
+	ASSERT_GE(fd, 0);
+	EXPECT_EQ(set_hint(fd, RWH_WRITE_LIFE_SHORT), -1);
+	EXPECT_EQ(errno, EPERM);
+	close(fd);
+}
+
+TEST(empty_mntns_root)
+{
+	int status;
+	pid_t pid;
+
+	if (geteuid())
+		SKIP(return, "test requires root");
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (pid == 0) {
+		int fd;
+
+		if (unshare(UNSHARE_EMPTY_MNTNS))
+			_exit(errno == EINVAL ? 100 : 1);
+		fd = open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+		if (fd < 0)
+			_exit(2);
+		if (set_hint(fd, RWH_WRITE_LIFE_SHORT) == 0)
+			_exit(3);
+		_exit(errno == EPERM ? 0 : 4);
+	}
+	ASSERT_EQ(waitpid(pid, &status, 0), pid);
+	ASSERT_TRUE(WIFEXITED(status));
+	if (WEXITSTATUS(status) == 100)
+		SKIP(return, "UNSHARE_EMPTY_MNTNS not supported");
+	EXPECT_EQ(WEXITSTATUS(status), 0);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/filesystems/umount_propagation/Makefile b/tools/testing/selftests/filesystems/umount_propagation/Makefile
new file mode 100644
index 0000000..31f783a
--- /dev/null
+++ b/tools/testing/selftests/filesystems/umount_propagation/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+TEST_GEN_PROGS := umount_propagation_test shrink_submounts_test locked_mount_test
+
+CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES)
+
+include ../../lib.mk
diff --git a/tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.c b/tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.c
new file mode 100644
index 0000000..0799b2b
--- /dev/null
+++ b/tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.c
@@ -0,0 +1,432 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * MNT_LOCKED keeps the owner of a user namespace from revealing what a
+ * mount covers. The lock has to be set on the copy in that namespace and
+ * only there, it has to survive the expiry of a mount placed beneath the
+ * locked one, and it has to survive the propagated umount of such a copy.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mount.h>
+#include <sys/prctl.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+
+#include "../../kselftest_harness.h"
+
+#ifndef MOVE_MOUNT_F_EMPTY_PATH
+#define MOVE_MOUNT_F_EMPTY_PATH	0x00000004
+#endif
+#ifndef MOVE_MOUNT_BENEATH
+#define MOVE_MOUNT_BENEATH	0x00000200
+#endif
+
+#define DIR_LEN		64
+#define PATH_LEN	192
+#define FLAGS		(MS_NOSUID | MS_NODEV | MS_NOEXEC)
+
+/* exit codes of the children, each test says what they mean */
+enum {
+	CHILD_OK,
+	CHILD_SETUP,
+	CHILD_STEP1,
+	CHILD_STEP2,
+	CHILD_STEP3,
+	CHILD_STEP4,
+};
+
+static int write_file(const char *path, const char *s)
+{
+	ssize_t n = -1;
+	int fd;
+
+	fd = open(path, O_WRONLY | O_CLOEXEC);
+	if (fd >= 0) {
+		n = write(fd, s, strlen(s));
+		close(fd);
+	}
+	return n == (ssize_t)strlen(s) ? 0 : -1;
+}
+
+static int create_file(const char *path, const char *s)
+{
+	ssize_t n = -1;
+	int fd;
+
+	fd = open(path, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644);
+	if (fd >= 0) {
+		n = write(fd, s, strlen(s));
+		close(fd);
+	}
+	return n == (ssize_t)strlen(s) ? 0 : -1;
+}
+
+/* Root in a new user namespace with a copy of the mount namespace. */
+static int enter_userns(void)
+{
+	uid_t uid = getuid();
+	gid_t gid = getgid();
+	char map[32];
+
+	prctl(PR_SET_DUMPABLE, 1);
+	if (unshare(CLONE_NEWUSER | CLONE_NEWNS))
+		return -1;
+	if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT)
+		return -1;
+	snprintf(map, sizeof(map), "0 %d 1", uid);
+	if (write_file("/proc/self/uid_map", map))
+		return -1;
+	snprintf(map, sizeof(map), "0 %d 1", gid);
+	if (write_file("/proc/self/gid_map", map))
+		return -1;
+	return setgid(0) || setuid(0) ? -1 : 0;
+}
+
+static int wait_child(pid_t pid)
+{
+	int status;
+
+	if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status))
+		return -1;
+	return WEXITSTATUS(status);
+}
+
+static int wait_byte(int fd)
+{
+	char c;
+
+	return read(fd, &c, 1) == 1 ? 0 : -1;
+}
+
+static int send_byte(int fd)
+{
+	return write(fd, "x", 1) == 1 ? 0 : -1;
+}
+
+/* A read of @path fails: the file is covered. */
+static bool covered(const char *path)
+{
+	int fd = open(path, O_RDONLY | O_CLOEXEC);
+
+	if (fd >= 0)
+		close(fd);
+	return fd < 0;
+}
+
+FIXTURE(locked_mount) {
+	char base[DIR_LEN];
+	bool tracing;		/* debugfs with the tracefs automount is there */
+};
+
+FIXTURE_SETUP(locked_mount)
+{
+	char p[PATH_LEN];
+	struct stat st;
+
+	if (geteuid())
+		SKIP(return, "test requires root");
+
+	snprintf(self->base, sizeof(self->base), "/tmp/locked_mount.XXXXXX");
+	ASSERT_NE(mkdtemp(self->base), NULL);
+	ASSERT_EQ(unshare(CLONE_NEWNS), 0);
+	ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0);
+	ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, "mode=0755"), 0);
+
+	/* an automount that a user can name: the tracefs below debugfs */
+	snprintf(p, sizeof(p), "%s/dbg", self->base);
+	ASSERT_EQ(mkdir(p, 0755), 0);
+	self->tracing = !mount("debugfs", p, "debugfs", FLAGS, NULL);
+	if (self->tracing) {
+		/* the cases trigger it themselves */
+		snprintf(p, sizeof(p), "%s/dbg/tracing", self->base);
+		self->tracing = !fstatat(AT_FDCWD, p, &st, AT_NO_AUTOMOUNT) &&
+				S_ISDIR(st.st_mode);
+	}
+}
+
+FIXTURE_TEARDOWN(locked_mount)
+{
+	umount2(self->base, MNT_DETACH);
+	rmdir(self->base);
+}
+
+/*
+ * The child keeps a directory descriptor on the host's debugfs mount, moves
+ * to a user namespace of its own and triggers the automount through the
+ * descriptor. The mount goes below the host's mount and propagates into the
+ * child's copy. The child's copy has to be locked, the host's not.
+ */
+static int automount_child(const char *base, int dfd, int to_host, int from_host)
+{
+	char p[PATH_LEN];
+	struct stat st;
+
+	if (enter_userns())
+		return CHILD_SETUP;
+	if (fstatat(dfd, "tracing/.", &st, 0))
+		return CHILD_STEP1;
+	if (send_byte(to_host) || wait_byte(from_host))
+		return CHILD_SETUP;
+	/* the flags of the copy are locked: EPERM */
+	snprintf(p, sizeof(p), "%s/dbg/tracing", base);
+	if (!mount(NULL, p, NULL, MS_REMOUNT | MS_BIND, NULL) || errno != EPERM)
+		return CHILD_STEP2;
+	return CHILD_OK;
+}
+
+TEST_F(locked_mount, automount_locked_in_the_triggering_namespace)
+{
+	int to_host[2], from_host[2], dfd, ret;
+	char p[PATH_LEN];
+	pid_t pid;
+
+	if (!self->tracing)
+		SKIP(return, "test requires debugfs with the tracefs automount");
+
+	snprintf(p, sizeof(p), "%s/dbg", self->base);
+	ASSERT_EQ(mount(NULL, p, NULL, MS_SHARED, NULL), 0);
+	dfd = open(p, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+	ASSERT_GE(dfd, 0);
+	ASSERT_EQ(pipe(to_host), 0);
+	ASSERT_EQ(pipe(from_host), 0);
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (pid == 0)
+		_exit(automount_child(self->base, dfd, to_host[1], from_host[0]));
+	close(dfd);
+	ASSERT_EQ(wait_byte(to_host[0]), 0);
+
+	/* the host's own mount isn't locked: the flags can go */
+	snprintf(p, sizeof(p), "%s/dbg/tracing", self->base);
+	EXPECT_EQ(mount(NULL, p, NULL, MS_REMOUNT | MS_BIND, NULL), 0);
+
+	ASSERT_EQ(send_byte(from_host[1]), 0);
+	ret = wait_child(pid);
+	TH_LOG("child exit code %d", ret);
+	EXPECT_EQ(ret, CHILD_OK);
+}
+
+/*
+ * A copy of a tree with a locked cover. The child puts a shrinkable mount
+ * beneath the cover, which hands the lock down, unmounts the cover and then
+ * asks for the umount of an unlocked ancestor. That expires the shrinkable
+ * mount on a kernel that doesn't look at the lock, and the covered
+ * directory is bare.
+ */
+static int expiry_child(const char *base)
+{
+	char srv[PATH_LEN], shr[PATH_LEN], x[PATH_LEN], c[PATH_LEN];
+	char hidden[PATH_LEN], secret[PATH_LEN];
+
+	snprintf(srv, sizeof(srv), "%s/srv", base);
+	snprintf(shr, sizeof(shr), "%s/shr", base);
+	snprintf(x, sizeof(x), "%s/x", base);
+	snprintf(c, sizeof(c), "%s/c", base);
+	snprintf(hidden, sizeof(hidden), "%s/x/hidden", base);
+	snprintf(secret, sizeof(secret), "%s/x/hidden/secret", base);
+
+	if (enter_userns())
+		return CHILD_SETUP;
+	if (mount(srv, x, NULL, MS_BIND | MS_REC, NULL) ||
+	    mount(shr, c, NULL, MS_BIND, NULL))
+		return CHILD_SETUP;
+	/* the cover is locked */
+	if (!umount2(hidden, 0) || errno != EINVAL)
+		return CHILD_STEP1;
+	if (syscall(__NR_move_mount, AT_FDCWD, c, AT_FDCWD, hidden, MOVE_MOUNT_BENEATH))
+		return CHILD_STEP2;
+	/* the lock moved down, the cover may go */
+	if (umount2(hidden, 0))
+		return CHILD_STEP3;
+	/* the holder of the lock may not, in any way */
+	if (!umount2(hidden, 0) || errno != EINVAL)
+		return CHILD_STEP4;
+	if (chdir(x))
+		return CHILD_SETUP;
+	umount2(x, 0);
+	return covered(secret) ? CHILD_OK : CHILD_STEP4;
+}
+
+TEST_F(locked_mount, expiry_leaves_a_locked_mount_alone)
+{
+	char p[PATH_LEN], q[PATH_LEN];
+	pid_t pid;
+	int ret;
+
+	if (!self->tracing)
+		SKIP(return, "test requires debugfs with the tracefs automount");
+
+	snprintf(p, sizeof(p), "%s/dbg/tracing", self->base);
+	snprintf(q, sizeof(q), "%s/shr", self->base);
+	ASSERT_EQ(mkdir(q, 0755), 0);
+	/* a bind of an automount is shrinkable as well */
+	ASSERT_EQ(mount(p, q, NULL, MS_BIND, NULL), 0);
+	snprintf(p, sizeof(p), "%s/srv", self->base);
+	ASSERT_EQ(mkdir(p, 0755), 0);
+	ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, "mode=0755"), 0);
+	snprintf(p, sizeof(p), "%s/srv/hidden", self->base);
+	ASSERT_EQ(mkdir(p, 0755), 0);
+	snprintf(p, sizeof(p), "%s/srv/hidden/secret", self->base);
+	ASSERT_EQ(create_file(p, "covered-by-root\n"), 0);
+	snprintf(p, sizeof(p), "%s/srv/hidden", self->base);
+	ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, "mode=0755"), 0);
+	snprintf(p, sizeof(p), "%s/x", self->base);
+	ASSERT_EQ(mkdir(p, 0755), 0);
+	snprintf(p, sizeof(p), "%s/c", self->base);
+	ASSERT_EQ(mkdir(p, 0755), 0);
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (pid == 0)
+		_exit(expiry_child(self->base));
+	ret = wait_child(pid);
+	TH_LOG("child exit code %d", ret);
+	EXPECT_EQ(ret, CHILD_OK);
+}
+
+/*
+ * The copy of the mount tree that a user namespace gets at its creation has
+ * the automounts in it locked unless they are on an expiry list. A bind of
+ * such a tree inside the namespace copies the lock to the automount but not
+ * to the root of the bind, so the child may ask for the umount of that root.
+ * That must not expire the locked automount below it: the root is busy, the
+ * umount fails and the automount has to be there afterwards.
+ */
+static int copied_tree_child(const char *base)
+{
+	char dbg[PATH_LEN], x[PATH_LEN], tracing[PATH_LEN];
+	struct stat root, st;
+
+	snprintf(dbg, sizeof(dbg), "%s/dbg", base);
+	snprintf(x, sizeof(x), "%s/x", base);
+	snprintf(tracing, sizeof(tracing), "%s/x/tracing", base);
+
+	if (enter_userns())
+		return CHILD_SETUP;
+	if (mount(dbg, x, NULL, MS_BIND | MS_REC, NULL))
+		return CHILD_SETUP;
+	/* the copy of the automount carries the lock */
+	if (!umount2(tracing, 0) || errno != EINVAL)
+		return CHILD_STEP1;
+	/* the root of the bind doesn't; keep it busy */
+	if (chdir(x))
+		return CHILD_SETUP;
+	if (!umount2(x, 0) || errno != EBUSY)
+		return CHILD_STEP2;
+	/* the automount below it must not have gone */
+	if (stat(x, &root) || fstatat(AT_FDCWD, tracing, &st, AT_NO_AUTOMOUNT))
+		return CHILD_SETUP;
+	return st.st_dev != root.st_dev ? CHILD_OK : CHILD_STEP3;
+}
+
+TEST_F(locked_mount, umount_of_a_bind_leaves_a_locked_automount_alone)
+{
+	char p[PATH_LEN];
+	struct stat st;
+	pid_t pid;
+	int ret;
+
+	if (!self->tracing)
+		SKIP(return, "test requires debugfs with the tracefs automount");
+
+	/* the copy the child gets has to contain the automount */
+	snprintf(p, sizeof(p), "%s/dbg/tracing/.", self->base);
+	ASSERT_EQ(stat(p, &st), 0);
+	snprintf(p, sizeof(p), "%s/x", self->base);
+	ASSERT_EQ(mkdir(p, 0755), 0);
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (pid == 0)
+		_exit(copied_tree_child(self->base));
+	ret = wait_child(pid);
+	TH_LOG("child exit code %d", ret);
+	EXPECT_EQ(ret, CHILD_OK);
+}
+
+/*
+ * Z is a user namespace with a copy of a tree in which P/d is covered by a
+ * locked mount. The host mounts X on P/d, which propagates beneath Z's
+ * cover, and unmounts it again. Z's cover has to be locked afterwards as
+ * it was before.
+ */
+static int propagation_child(const char *base, int to_host, int from_host)
+{
+	char d[PATH_LEN], secret[PATH_LEN];
+
+	snprintf(d, sizeof(d), "%s/P/d", base);
+	snprintf(secret, sizeof(secret), "%s/P/d/secret", base);
+
+	if (enter_userns())
+		return CHILD_SETUP;
+	/* the cover is locked */
+	if (!umount2(d, 0) || errno != EINVAL)
+		return CHILD_STEP1;
+	if (send_byte(to_host) || wait_byte(from_host))
+		return CHILD_SETUP;
+	/* X came and went beneath it: still locked */
+	if (!umount2(d, 0) || errno != EINVAL)
+		return CHILD_STEP2;
+	return covered(secret) ? CHILD_OK : CHILD_STEP3;
+}
+
+TEST_F(locked_mount, propagated_copy_keeps_the_cover_locked)
+{
+	int to_host[2], from_host[2], ret;
+	char p[PATH_LEN];
+	pid_t pid;
+
+	snprintf(p, sizeof(p), "%s/P", self->base);
+	ASSERT_EQ(mkdir(p, 0755), 0);
+	ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, "mode=0755"), 0);
+	ASSERT_EQ(mount(NULL, p, NULL, MS_SHARED, NULL), 0);
+	snprintf(p, sizeof(p), "%s/P/d", self->base);
+	ASSERT_EQ(mkdir(p, 0755), 0);
+	snprintf(p, sizeof(p), "%s/P/d/secret", self->base);
+	ASSERT_EQ(create_file(p, "covered-by-root\n"), 0);
+	ASSERT_EQ(pipe(to_host), 0);
+	ASSERT_EQ(pipe(from_host), 0);
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (pid == 0) {
+		/* M: a manager's namespace that covers P/d for Z */
+		pid_t z;
+
+		if (unshare(CLONE_NEWNS))
+			_exit(CHILD_SETUP);
+		snprintf(p, sizeof(p), "%s/P", self->base);
+		if (mount(NULL, p, NULL, MS_SLAVE, NULL))
+			_exit(CHILD_SETUP);
+		snprintf(p, sizeof(p), "%s/P/d", self->base);
+		if (mount("tmpfs", p, "tmpfs", 0, "mode=0755"))
+			_exit(CHILD_SETUP);
+		z = fork();
+		if (z < 0)
+			_exit(CHILD_SETUP);
+		if (z == 0)
+			_exit(propagation_child(self->base, to_host[1], from_host[0]));
+		_exit(wait_child(z));
+	}
+	ASSERT_EQ(wait_byte(to_host[0]), 0);
+
+	/* the host mounts on P/d and unmounts again; both propagate */
+	snprintf(p, sizeof(p), "%s/P/d", self->base);
+	ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, "mode=0755"), 0);
+	ASSERT_EQ(umount2(p, 0), 0);
+
+	ASSERT_EQ(send_byte(from_host[1]), 0);
+	ret = wait_child(pid);
+	TH_LOG("child exit code %d", ret);
+	EXPECT_EQ(ret, CHILD_OK);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/filesystems/umount_propagation/shrink_submounts_test.c b/tools/testing/selftests/filesystems/umount_propagation/shrink_submounts_test.c
new file mode 100644
index 0000000..b024ae3
--- /dev/null
+++ b/tools/testing/selftests/filesystems/umount_propagation/shrink_submounts_test.c
@@ -0,0 +1,211 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A synchronous umount first unmounts the shrinkable submounts of the
+ * victim that aren't busy. Every one of them has to be checked right
+ * before it is unmounted: unmounting one can slide a busy mount to where
+ * the propagated copy of the next one is looked up.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/fanotify.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <sys/vfs.h>
+#include <linux/magic.h>
+
+#include "../../kselftest_harness.h"
+
+#ifndef FAN_REPORT_MNT
+#define FAN_REPORT_MNT		0x00004000
+#endif
+#ifndef FAN_MARK_MNTNS
+#define FAN_MARK_MNTNS		0x00000110
+#endif
+#ifndef FAN_MNT_ATTACH
+#define FAN_MNT_ATTACH		0x01000000
+#endif
+#ifndef FAN_MNT_DETACH
+#define FAN_MNT_DETACH		0x02000000
+#endif
+
+#define DIR_LEN 64
+#define PATH_LEN 128
+
+FIXTURE(shrink_submounts) {
+	char base[DIR_LEN];
+	char automount[PATH_LEN];
+	bool mounted;
+	int fan;
+};
+
+/*
+ * Shrinkable mounts come from an automount. The tracefs mount below debugfs
+ * is one and bind mounts inherit the flag.
+ */
+FIXTURE_SETUP(shrink_submounts)
+{
+	struct stat st;
+	char p[PATH_LEN];
+
+	self->mounted = false;
+	self->fan = -1;
+
+	if (geteuid() != 0)
+		SKIP(return, "test requires CAP_SYS_ADMIN");
+
+	ASSERT_EQ(unshare(CLONE_NEWNS), 0);
+	ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0);
+
+	snprintf(self->base, sizeof(self->base), "/tmp/shrink_submounts.XXXXXX");
+	ASSERT_NE(mkdtemp(self->base), NULL);
+	ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, NULL), 0);
+	self->mounted = true;
+	ASSERT_EQ(mount(NULL, self->base, NULL, MS_PRIVATE, NULL), 0);
+
+	snprintf(p, sizeof(p), "%s/dbg", self->base);
+	ASSERT_EQ(mkdir(p, 0755), 0);
+	if (mount("debugfs", p, "debugfs", 0, NULL)) {
+		umount2(self->base, MNT_DETACH);
+		rmdir(self->base);
+		SKIP(return, "test requires debugfs");
+	}
+	snprintf(self->automount, sizeof(self->automount), "%s/dbg/tracing",
+		 self->base);
+	snprintf(p, sizeof(p), "%s/dbg/tracing/.", self->base);
+	if (stat(p, &st)) {
+		umount2(self->base, MNT_DETACH);
+		rmdir(self->base);
+		SKIP(return, "test requires the tracefs automount");
+	}
+}
+
+FIXTURE_TEARDOWN(shrink_submounts)
+{
+	if (self->fan >= 0)
+		close(self->fan);
+	chdir("/");
+	if (self->mounted)
+		umount2(self->base, MNT_DETACH);
+	rmdir(self->base);
+}
+
+static bool mounted_tmpfs(const char *path)
+{
+	struct statfs st;
+
+	return !statfs(path, &st) && st.f_type == TMPFS_MAGIC;
+}
+
+/*
+ * P is a shared bind mount of the automount, P1 a slave of P that is shared
+ * in turn and P2 its peer. B, another bind mount of the automount, goes on
+ * P/options and propagates copies Bc1 and Bc2 onto P1/options and
+ * P2/options. R, a tmpfs and our working directory, sits on top of Bc2 which
+ * is made private first. P, with B on it, is moved to P1/instances.
+ *
+ * A synchronous umount of P1 unmounts the shrinkable submounts Bc1 and B
+ * first. Unmounting Bc1 takes Bc2 along and slides R to P2/options where
+ * the propagated copy of B is looked up next. R is busy, so B has to stay
+ * and the umount fails with EBUSY.
+ */
+TEST_F(shrink_submounts, busy_mount_moved_into_reach)
+{
+	char p[PATH_LEN], p1[PATH_LEN], p2[PATH_LEN], r[PATH_LEN], cwd[PATH_LEN];
+	int nsfd;
+
+	snprintf(p, sizeof(p), "%s/p", self->base);
+	snprintf(p1, sizeof(p1), "%s/p1", self->base);
+	snprintf(p2, sizeof(p2), "%s/p2", self->base);
+	ASSERT_EQ(mkdir(p, 0755), 0);
+	ASSERT_EQ(mkdir(p1, 0755), 0);
+	ASSERT_EQ(mkdir(p2, 0755), 0);
+
+	/* watch the mount namespace so that the detached mounts get queued */
+	self->fan = fanotify_init(FAN_REPORT_MNT, O_RDONLY);
+	if (self->fan >= 0) {
+		nsfd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC);
+		ASSERT_GE(nsfd, 0);
+		EXPECT_EQ(fanotify_mark(self->fan, FAN_MARK_ADD | FAN_MARK_MNTNS,
+					FAN_MNT_ATTACH | FAN_MNT_DETACH, nsfd, NULL), 0);
+		close(nsfd);
+	}
+
+	ASSERT_EQ(mount(self->automount, p, NULL, MS_BIND, NULL), 0);
+	ASSERT_EQ(mount(NULL, p, NULL, MS_SHARED, NULL), 0);
+	ASSERT_EQ(mount(p, p1, NULL, MS_BIND, NULL), 0);
+	ASSERT_EQ(mount(NULL, p1, NULL, MS_SLAVE, NULL), 0);
+	ASSERT_EQ(mount(NULL, p1, NULL, MS_SHARED, NULL), 0);
+	ASSERT_EQ(mount(p1, p2, NULL, MS_BIND, NULL), 0);
+
+	/* B on P/options, copies on P1/options and P2/options */
+	snprintf(r, sizeof(r), "%s/p/options", self->base);
+	ASSERT_EQ(mount(self->automount, r, NULL, MS_BIND, NULL), 0);
+
+	/* R on top of Bc2 */
+	snprintf(r, sizeof(r), "%s/p2/options", self->base);
+	ASSERT_EQ(mount(NULL, r, NULL, MS_PRIVATE, NULL), 0);
+	ASSERT_EQ(mount("R", r, "tmpfs", 0, NULL), 0);
+	ASSERT_EQ(chdir(r), 0);
+
+	/* P, with B on it, below the victim */
+	snprintf(cwd, sizeof(cwd), "%s/p1/instances", self->base);
+	ASSERT_EQ(mount(p, cwd, NULL, MS_MOVE, NULL), 0);
+
+	ASSERT_TRUE(mounted_tmpfs(r));
+	ASSERT_EQ(umount2(p1, 0), -1);
+	EXPECT_EQ(errno, EBUSY);
+
+	/* R is still mounted and still our working directory */
+	EXPECT_TRUE(mounted_tmpfs(r));
+	ASSERT_NE(getcwd(cwd, sizeof(cwd)), NULL);
+	EXPECT_STREQ(cwd, r);
+}
+
+/*
+ * T is shared and Q, a slave of T, has T moved into it, so Q receives
+ * propagation from its own child. M, another bind mount of the automount, is
+ * on T/options and that is the dentry T sits on in Q. Unmounting M makes T
+ * the propagated victim at that dentry in Q and takes T along. The shrink
+ * walk of V has just unmounted M and continues in the children of T.
+ */
+TEST_F(shrink_submounts, parent_goes_with_child)
+{
+	char v[PATH_LEN], t[PATH_LEN], q[PATH_LEN], p[PATH_LEN];
+	struct stat before, after;
+
+	snprintf(v, sizeof(v), "%s/v", self->base);
+	snprintf(t, sizeof(t), "%s/v/t", self->base);
+	snprintf(q, sizeof(q), "%s/v/q", self->base);
+	ASSERT_EQ(mkdir(v, 0755), 0);
+	ASSERT_EQ(mount("V", v, "tmpfs", 0, NULL), 0);
+	ASSERT_EQ(mount(NULL, v, NULL, MS_PRIVATE, NULL), 0);
+	ASSERT_EQ(stat(v, &before), 0);
+	ASSERT_EQ(mkdir(t, 0755), 0);
+	ASSERT_EQ(mkdir(q, 0755), 0);
+
+	/* T shared, M on T/options before anything receives from T */
+	ASSERT_EQ(mount(self->automount, t, NULL, MS_BIND, NULL), 0);
+	ASSERT_EQ(mount(NULL, t, NULL, MS_SHARED, NULL), 0);
+	snprintf(p, sizeof(p), "%s/v/t/options", self->base);
+	ASSERT_EQ(mount(self->automount, p, NULL, MS_BIND, NULL), 0);
+
+	/* Q, a slave of T, and T moved into Q at the dentry M sits on */
+	ASSERT_EQ(mount(t, q, NULL, MS_BIND, NULL), 0);
+	ASSERT_EQ(mount(NULL, q, NULL, MS_SLAVE, NULL), 0);
+	snprintf(p, sizeof(p), "%s/v/q/options", self->base);
+	ASSERT_EQ(mount(t, p, NULL, MS_MOVE, NULL), 0);
+
+	/* M, T and then Q go, V is empty and can be unmounted */
+	ASSERT_EQ(umount2(v, 0), 0);
+	ASSERT_EQ(stat(v, &after), 0);
+	EXPECT_NE(before.st_dev, after.st_dev);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/filesystems/umount_propagation/umount_propagation_test.c b/tools/testing/selftests/filesystems/umount_propagation/umount_propagation_test.c
new file mode 100644
index 0000000..9e18d54
--- /dev/null
+++ b/tools/testing/selftests/filesystems/umount_propagation/umount_propagation_test.c
@@ -0,0 +1,226 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A synchronous umount fails with EBUSY when a mount it would pull out by
+ * propagation is still in use.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <linux/mount.h>
+#include <linux/stat.h>
+
+#include "../../kselftest_harness.h"
+
+#ifndef OPEN_TREE_CLONE
+#define OPEN_TREE_CLONE		1
+#endif
+#ifndef OPEN_TREE_CLOEXEC
+#define OPEN_TREE_CLOEXEC	O_CLOEXEC
+#endif
+#ifndef AT_RECURSIVE
+#define AT_RECURSIVE		0x8000
+#endif
+#ifndef MOVE_MOUNT_F_EMPTY_PATH
+#define MOVE_MOUNT_F_EMPTY_PATH	0x00000004
+#endif
+#ifndef MOVE_MOUNT_BENEATH
+#define MOVE_MOUNT_BENEATH	0x00000200
+#endif
+#ifndef STATX_MNT_ID
+#define STATX_MNT_ID		0x00001000U
+#endif
+
+static int sys_open_tree(int dfd, const char *filename, unsigned int flags)
+{
+	return syscall(__NR_open_tree, dfd, filename, flags);
+}
+
+static int sys_move_mount(int from_dfd, const char *from_pathname,
+			  int to_dfd, const char *to_pathname,
+			  unsigned int flags)
+{
+	return syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd,
+		       to_pathname, flags);
+}
+
+/* Child exit codes. */
+enum {
+	CHILD_OK,
+	CHILD_UNSHARE,		/* could not set up the slave namespace */
+	CHILD_OPEN_TREE,	/* open_tree() failed */
+	CHILD_MOVE_MOUNT,	/* move_mount() failed */
+	CHILD_STATX,		/* statx() failed */
+	CHILD_PIPE,		/* the parent went away */
+};
+
+/* Messages between parent and child. */
+enum {
+	MSG_READY = 'r',	/* child: the copy is mounted and referenced */
+	MSG_CHECK = 'c',	/* parent: check that the copy is still attached */
+	MSG_ATTACHED = 'a',	/* child: it is */
+	MSG_DETACHED = 'd',	/* child: it is not */
+	MSG_CLOSE = 'x',	/* parent: drop the reference */
+	MSG_CLOSED = 'y',	/* child: dropped */
+	MSG_EXIT = 'e',		/* parent: done */
+};
+
+FIXTURE(umount_propagation)
+{
+	char base[64];
+	char victim[80];
+	bool mounted;
+};
+
+FIXTURE_SETUP(umount_propagation)
+{
+	self->mounted = false;
+
+	if (geteuid() != 0)
+		SKIP(return, "test requires CAP_SYS_ADMIN");
+
+	ASSERT_EQ(unshare(CLONE_NEWNS), 0);
+	ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0);
+
+	snprintf(self->base, sizeof(self->base), "/tmp/umount_propagation.XXXXXX");
+	ASSERT_NE(mkdtemp(self->base), NULL);
+	ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, NULL), 0);
+	self->mounted = true;
+	ASSERT_EQ(mount(NULL, self->base, NULL, MS_SHARED, NULL), 0);
+
+	snprintf(self->victim, sizeof(self->victim), "%s/victim", self->base);
+	ASSERT_EQ(mkdir(self->victim, 0755), 0);
+	ASSERT_EQ(mount("tmpfs", self->victim, "tmpfs", 0, NULL), 0);
+}
+
+FIXTURE_TEARDOWN(umount_propagation)
+{
+	if (self->mounted)
+		umount2(self->base, MNT_DETACH);
+	rmdir(self->base);
+}
+
+static int send_msg(int fd, char msg)
+{
+	return write(fd, &msg, 1) == 1 ? 0 : -1;
+}
+
+static char recv_msg(int fd)
+{
+	char msg;
+
+	if (read(fd, &msg, 1) != 1)
+		return 0;
+	return msg;
+}
+
+/* Is the mount with id @mnt_id attached in this mount namespace? */
+static bool mount_attached(__u64 mnt_id)
+{
+	char line[4096];
+	bool found = false;
+	FILE *f;
+
+	f = fopen("/proc/self/mountinfo", "re");
+	if (!f)
+		return false;
+
+	while (fgets(line, sizeof(line), f)) {
+		if (strtoull(line, NULL, 10) == mnt_id) {
+			found = true;
+			break;
+		}
+	}
+	fclose(f);
+	return found;
+}
+
+/*
+ * The slave namespace: take a detached copy of the shared tree and move it
+ * beneath the propagated copy of the victim, keeping the open_tree()
+ * descriptor as a reference on it.
+ */
+static int slave_child(const char *base, const char *victim, int to_parent,
+		       int from_parent)
+{
+	struct statx stx;
+	int fd;
+
+	if (unshare(CLONE_NEWNS))
+		return CHILD_UNSHARE;
+	if (mount("", "/", NULL, MS_REC | MS_SLAVE, NULL))
+		return CHILD_UNSHARE;
+
+	fd = sys_open_tree(AT_FDCWD, base,
+			   OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC | AT_RECURSIVE);
+	if (fd < 0)
+		return CHILD_OPEN_TREE;
+	if (sys_move_mount(fd, "", AT_FDCWD, victim,
+			   MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_BENEATH))
+		return CHILD_MOVE_MOUNT;
+	if (statx(fd, "", AT_EMPTY_PATH, STATX_MNT_ID, &stx))
+		return CHILD_STATX;
+
+	if (send_msg(to_parent, MSG_READY) || recv_msg(from_parent) != MSG_CHECK)
+		return CHILD_PIPE;
+	if (send_msg(to_parent, mount_attached(stx.stx_mnt_id) ?
+				MSG_ATTACHED : MSG_DETACHED))
+		return CHILD_PIPE;
+
+	if (recv_msg(from_parent) != MSG_CLOSE)
+		return CHILD_PIPE;
+	close(fd);
+	if (send_msg(to_parent, MSG_CLOSED) || recv_msg(from_parent) != MSG_EXIT)
+		return CHILD_PIPE;
+	return CHILD_OK;
+}
+
+TEST_F(umount_propagation, busy_copy_pulled_out)
+{
+	int to_child[2], to_parent[2];
+	int status;
+	pid_t pid;
+
+	ASSERT_EQ(pipe(to_child), 0);
+	ASSERT_EQ(pipe(to_parent), 0);
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (pid == 0) {
+		close(to_child[1]);
+		close(to_parent[0]);
+		_exit(slave_child(self->base, self->victim, to_parent[1],
+				  to_child[0]));
+	}
+	close(to_child[0]);
+	close(to_parent[1]);
+
+	ASSERT_EQ(recv_msg(to_parent[0]), MSG_READY);
+
+	/* the copy in the slave namespace is in use */
+	ASSERT_EQ(umount2(self->victim, 0), -1);
+	ASSERT_EQ(errno, EBUSY);
+
+	ASSERT_EQ(send_msg(to_child[1], MSG_CHECK), 0);
+	ASSERT_EQ(recv_msg(to_parent[0]), MSG_ATTACHED);
+
+	/* and once it is not, the umount goes through */
+	ASSERT_EQ(send_msg(to_child[1], MSG_CLOSE), 0);
+	ASSERT_EQ(recv_msg(to_parent[0]), MSG_CLOSED);
+	ASSERT_EQ(umount2(self->victim, 0), 0);
+
+	ASSERT_EQ(send_msg(to_child[1], MSG_EXIT), 0);
+	ASSERT_EQ(waitpid(pid, &status, 0), pid);
+	ASSERT_TRUE(WIFEXITED(status));
+	ASSERT_EQ(WEXITSTATUS(status), CHILD_OK);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/move_mount_set_group/move_mount_set_group_test.c b/tools/testing/selftests/move_mount_set_group/move_mount_set_group_test.c
index 1243441..9c8fc8c 100644
--- a/tools/testing/selftests/move_mount_set_group/move_mount_set_group_test.c
+++ b/tools/testing/selftests/move_mount_set_group/move_mount_set_group_test.c
@@ -146,17 +146,19 @@ static void null_endofword(char *word)
 	*word = '\0';
 }
 
-static bool is_shared_mount(const char *path)
+/* Does the mount on @path carry the optional field @field in mountinfo? */
+static bool mount_has_field(const char *path, const char *field)
 {
 	size_t len = 0;
 	char *line = NULL;
 	FILE *f = NULL;
+	bool found = false;
 
 	f = fopen("/proc/self/mountinfo", "re");
 	if (!f)
 		return false;
 
-	while (getline(&line, &len, f) != -1) {
+	while (!found && getline(&line, &len, f) != -1) {
 		char *opts, *target;
 
 		target = get_field(line, 4);
@@ -172,15 +174,29 @@ static bool is_shared_mount(const char *path)
 		if (strcmp(target, path) != 0)
 			continue;
 
-		null_endofword(opts);
-		if (strstr(opts, "shared:"))
-			return true;
+		/* the optional fields end at the "-" separator */
+		while (opts && *opts != '-') {
+			char *next = strchr(opts, ' ');
+
+			if (next)
+				*next++ = '\0';
+			if (!strncmp(opts, field, strlen(field))) {
+				found = true;
+				break;
+			}
+			opts = next;
+		}
 	}
 
 	free(line);
 	fclose(f);
 
-	return false;
+	return found;
+}
+
+static bool is_shared_mount(const char *path)
+{
+	return mount_has_field(path, "shared:");
 }
 
 /* Attempt to de-conflict with the selftests tree. */
@@ -372,4 +388,50 @@ TEST_F(move_mount_set_group, complex_sharing_copying)
 	ASSERT_EQ(is_shared_mount(SET_GROUP_A), 1);
 }
 
+#define SET_GROUP_B "/tmp/B"
+#define SET_GROUP_C "/tmp/C"
+
+/*
+ * An unbindable mount is neither shared nor a slave, so it must not be
+ * accepted as the target: with a slave source it would end up unbindable
+ * and a slave at the same time.
+ */
+TEST_F(move_mount_set_group, unbindable_target)
+{
+	bool ret;
+
+	ret = move_mount_set_group_supported();
+	ASSERT_GE(ret, 0);
+	if (!ret)
+		SKIP(return, "move_mount(MOVE_MOUNT_SET_GROUP) is not supported");
+
+	ASSERT_EQ(mount(NULL, SET_GROUP_A, NULL, MS_SHARED, 0), 0);
+
+	/* B: a slave of A's peer group */
+	ASSERT_EQ(mkdir(SET_GROUP_B, 0777), 0);
+	ASSERT_EQ(mount(SET_GROUP_A, SET_GROUP_B, NULL, MS_BIND, NULL), 0);
+	ASSERT_EQ(mount(NULL, SET_GROUP_B, NULL, MS_SLAVE, 0), 0);
+	ASSERT_TRUE(mount_has_field(SET_GROUP_B, "master:"));
+
+	/* C: unbindable */
+	ASSERT_EQ(mkdir(SET_GROUP_C, 0777), 0);
+	ASSERT_EQ(mount(SET_GROUP_A, SET_GROUP_C, NULL, MS_BIND, NULL), 0);
+	ASSERT_EQ(mount(NULL, SET_GROUP_C, NULL, MS_UNBINDABLE, 0), 0);
+	ASSERT_TRUE(mount_has_field(SET_GROUP_C, "unbindable"));
+
+	/* from a slave */
+	ASSERT_EQ(syscall(__NR_move_mount, AT_FDCWD, SET_GROUP_B,
+			  AT_FDCWD, SET_GROUP_C, MOVE_MOUNT_SET_GROUP), -1);
+	ASSERT_EQ(errno, EINVAL);
+	ASSERT_FALSE(mount_has_field(SET_GROUP_C, "master:"));
+	ASSERT_TRUE(mount_has_field(SET_GROUP_C, "unbindable"));
+
+	/* from a shared mount */
+	ASSERT_EQ(syscall(__NR_move_mount, AT_FDCWD, SET_GROUP_A,
+			  AT_FDCWD, SET_GROUP_C, MOVE_MOUNT_SET_GROUP), -1);
+	ASSERT_EQ(errno, EINVAL);
+	ASSERT_FALSE(mount_has_field(SET_GROUP_C, "shared:"));
+	ASSERT_TRUE(mount_has_field(SET_GROUP_C, "unbindable"));
+}
+
 TEST_HARNESS_MAIN
