git: 42dbbaf748ea - stable/15 - dounmount(9): temporarily enable recursion for the covered vnode lock

From: Konstantin Belousov <kib_at_FreeBSD.org>
Date: Thu, 13 Aug 2026 00:35:53 UTC
The branch stable/15 has been updated by kib:

URL: https://cgit.FreeBSD.org/src/commit/?id=42dbbaf748ea18195174854dae0831180af3e256

commit 42dbbaf748ea18195174854dae0831180af3e256
Author:     Konstantin Belousov <kib@FreeBSD.org>
AuthorDate: 2026-07-31 04:12:17 +0000
Commit:     Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2026-08-13 00:34:41 +0000

    dounmount(9): temporarily enable recursion for the covered vnode lock
    
    PR:     297174
    
    (cherry picked from commit 9f5c4ef32812afb4573a278e6eafe5040f839d13)
---
 sys/kern/vfs_mount.c | 29 ++++++++++++++++++++++++++---
 1 file changed, 26 insertions(+), 3 deletions(-)

diff --git a/sys/kern/vfs_mount.c b/sys/kern/vfs_mount.c
index 0a3cce26ee48..82bba3f2ee61 100644
--- a/sys/kern/vfs_mount.c
+++ b/sys/kern/vfs_mount.c
@@ -1852,7 +1852,8 @@ vfs_check_usecounts(struct mount *mp)
 }
 
 static void
-dounmount_cleanup(struct mount *mp, struct vnode *coveredvp, int mntkflags)
+dounmount_cleanup(struct mount *mp, struct vnode *coveredvp, int mntkflags,
+    bool disablerec)
 {
 
 	mtx_assert(MNT_MTX(mp), MA_OWNED);
@@ -1864,6 +1865,8 @@ dounmount_cleanup(struct mount *mp, struct vnode *coveredvp, int mntkflags)
 	vfs_op_exit_locked(mp);
 	MNT_IUNLOCK(mp);
 	if (coveredvp != NULL) {
+		if (disablerec)
+			VN_LOCK_DREC(coveredvp);
 		VOP_UNLOCK(coveredvp);
 		vdrop(coveredvp);
 	}
@@ -2165,6 +2168,7 @@ dounmount(struct mount *mp, uint64_t flags, struct thread *td)
 	uint64_t async_flag;
 	int mnt_gen_r;
 	unsigned int retries;
+	bool coveredrec;
 
 	KASSERT((flags & MNT_DEFERRED) == 0 ||
 	    (flags & (MNT_RECURSE | MNT_FORCE)) == (MNT_RECURSE | MNT_FORCE),
@@ -2273,6 +2277,7 @@ dounmount(struct mount *mp, uint64_t flags, struct thread *td)
 	if ((flags & MNT_DEFERRED) != 0)
 		vfs_ref(mp);
 
+	coveredrec = false;
 	if ((coveredvp = mp->mnt_vnodecovered) != NULL) {
 		mnt_gen_r = mp->mnt_gen;
 		VI_LOCK(coveredvp);
@@ -2289,6 +2294,19 @@ dounmount(struct mount *mp, uint64_t flags, struct thread *td)
 			vfs_rel(mp);
 			return (EBUSY);
 		}
+
+		/*
+		 * For some complex nullfs mount configurations, it is
+		 * possible to get the covered vnode lock for the
+		 * mount shared with some inside-mount vnode lock.
+		 * Then at unmount time, vflush() would recurse on the
+		 * covered vnode lock when reclaiming the vnode.
+		 *
+		 * To work around it, temprorarily allow recursion for
+		 * the covered vnode lock.
+		 */
+		coveredrec = VN_LOCK_CANREC(coveredvp);
+		VN_LOCK_AREC(coveredvp);
 	}
 
 	vfs_op_enter(mp);
@@ -2298,7 +2316,7 @@ dounmount(struct mount *mp, uint64_t flags, struct thread *td)
 	if ((mp->mnt_kern_flag & MNTK_UNMOUNT) != 0 ||
 	    (mp->mnt_flag & MNT_UPDATE) != 0 ||
 	    !TAILQ_EMPTY(&mp->mnt_uppers)) {
-		dounmount_cleanup(mp, coveredvp, 0);
+		dounmount_cleanup(mp, coveredvp, 0, !coveredrec);
 		return (EBUSY);
 	}
 	mp->mnt_kern_flag |= MNTK_UNMOUNT;
@@ -2311,7 +2329,8 @@ dounmount(struct mount *mp, uint64_t flags, struct thread *td)
 		MNT_ILOCK(mp);
 		if (error != 0) {
 			vn_seqc_write_end(coveredvp);
-			dounmount_cleanup(mp, coveredvp, MNTK_UNMOUNT);
+			dounmount_cleanup(mp, coveredvp, MNTK_UNMOUNT,
+			    !coveredrec);
 			if (rootvp != NULL) {
 				vn_seqc_write_end(rootvp);
 				vrele(rootvp);
@@ -2393,6 +2412,8 @@ dounmount(struct mount *mp, uint64_t flags, struct thread *td)
 		MNT_IUNLOCK(mp);
 		if (coveredvp) {
 			vn_seqc_write_end(coveredvp);
+			if (!coveredrec)
+				VN_LOCK_DREC(coveredvp);
 			VOP_UNLOCK(coveredvp);
 			vdrop(coveredvp);
 		}
@@ -2413,6 +2434,8 @@ dounmount(struct mount *mp, uint64_t flags, struct thread *td)
 		coveredvp->v_mountedhere = NULL;
 		vn_seqc_write_end_locked(coveredvp);
 		VI_UNLOCK(coveredvp);
+		if (!coveredrec)
+			VN_LOCK_DREC(coveredvp);
 		VOP_UNLOCK(coveredvp);
 		vdrop(coveredvp);
 	}