⬢ DragonFlyBSD Kernel Audit
DF-3169 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/vfs/autofs/autofs.h b/sys/vfs/autofs/autofs.h
index d4458c7..dd6aeb6 100644
--- a/sys/vfs/autofs/autofs.h
+++ b/sys/vfs/autofs/autofs.h
@@ -113,6 +113,8 @@ struct autofs_node {
 struct autofs_mount {
 	struct autofs_node		*am_root;
 	mtx_t				am_lock;
+	bool				am_dying;
+	int				am_triggers;
 	char				am_from[MAXPATHLEN];
 	char				am_on[MAXPATHLEN];
 	char				am_options[MAXPATHLEN];
diff --git a/sys/vfs/autofs/autofs_vfsops.c b/sys/vfs/autofs/autofs_vfsops.c
index cd00711..f9c6945 100644
--- a/sys/vfs/autofs/autofs_vfsops.c
+++ b/sys/vfs/autofs/autofs_vfsops.c
@@ -171,6 +171,8 @@ autofs_mount(struct mount *mp, char *mntpt, caddr_t data, struct ucred *cred)
 	 * Initialize the autofs mount.
 	 */
 	mtx_init(&amp->am_lock, "autofsmnlk");
+	amp->am_dying = false;
+	amp->am_triggers = 0;
 	amp->am_last_ino = AUTOFS_ROOTINO;
 
 	mtx_lock_ex_quick(&amp->am_lock);
@@ -206,6 +208,18 @@ autofs_unmount(struct mount *mp, int mntflags)
 		return (error);
 	}
 
+	/*
+	 * DF-3169: Mark the mount dying so new triggers (and new vop
+	 * entries) refuse to start, then fail all outstanding requests.
+	 * NOTE: the old assumption that no new triggerings can appear
+	 * after vflush() is wrong under MNT_FORCE -- force-closed vnodes
+	 * can still have in-flight vops -- hence the am_triggers wait
+	 * below before any node is freed.
+	 */
+	mtx_lock_ex_quick(&amp->am_lock);
+	amp->am_dying = true;
+	mtx_unlock_ex(&amp->am_lock);
+
 	/*
 	 * All vnodes are gone, and new one will not appear - so,
 	 * no new triggerings.
@@ -236,6 +250,36 @@ autofs_unmount(struct mount *mp, int mntflags)
 		tsleep(&dummy, 0, "autofs_umount", hz);
 	}
 
+	/*
+	 * DF-3169: wait for in-flight trigger paths (nresolve/readdir/
+	 * nmkdir hold an am_triggers reference across their whole body)
+	 * before freeing anything.  Bounded: a trigger stuck inside an
+	 * inner vfs_busy() waiting for this very unmount must not
+	 * deadlock it -- abort with EBUSY and let the caller retry.
+	 */
+	{
+		int waits;
+
+		for (waits = 0; ; waits++) {
+			int triggers;
+
+			mtx_lock_ex_quick(&amp->am_lock);
+			triggers = amp->am_triggers;
+			mtx_unlock_ex(&amp->am_lock);
+			if (triggers == 0)
+				break;
+			if (waits > 120) {
+				AUTOFS_WARN("autofs_unmount: in-flight "
+				    "trigger did not finish, retry later");
+				mtx_lock_ex_quick(&amp->am_lock);
+				amp->am_dying = false;
+				mtx_unlock_ex(&amp->am_lock);
+				return (EBUSY);
+			}
+			tsleep(amp, 0, "autofs_umnt2", hz);
+		}
+	}
+
 	mtx_lock_ex_quick(&amp->am_lock);
 	while (!RB_EMPTY(&amp->am_root->an_children)) {
 		struct autofs_node *anp;
diff --git a/sys/vfs/autofs/autofs_vnops.c b/sys/vfs/autofs/autofs_vnops.c
index 2a94cf7..7624aef 100644
--- a/sys/vfs/autofs/autofs_vnops.c
+++ b/sys/vfs/autofs/autofs_vnops.c
@@ -41,6 +41,47 @@
 
 #include "autofs.h"
 
+
+/*
+ * DF-3169: mount-lifetime references for the trigger paths.
+ * autofs_unmount() marks the mount dying and waits for am_triggers
+ * to drain before freeing any autofs_node or the autofs_mount, so an
+ * in-flight trigger (daemon dispatch + retry loop can sleep for many
+ * seconds) can never run on freed memory.
+ */
+static bool
+autofs_mount_acquire(struct autofs_mount *amp)
+{
+	bool ok;
+
+	mtx_lock_ex_quick(&amp->am_lock);
+	ok = !amp->am_dying;
+	if (ok)
+		amp->am_triggers++;
+	mtx_unlock_ex(&amp->am_lock);
+
+	return (ok);
+}
+
+static void
+autofs_mount_release(struct autofs_mount *amp)
+{
+	mtx_lock_ex_quick(&amp->am_lock);
+	KKASSERT(amp->am_triggers > 0);
+	if (--amp->am_triggers == 0)
+		wakeup(amp);
+	mtx_unlock_ex(&amp->am_lock);
+}
+
+static __inline struct autofs_mount *
+autofs_vnode_mount(struct vnode *vp)
+{
+
+	if (vp->v_flag & VRECLAIMED)
+		return (NULL);
+	return (VFSTOAUTOFS(vp->v_mount));
+}
+
 static bool
 test_fs_root(struct vnode *vp)
 {
@@ -195,11 +236,14 @@ autofs_nresolve(struct vop_nresolve_args *ap)
 	struct vnode *dvp = ap->a_dvp;
 	struct nchandle *nch = ap->a_nch;
 	struct namecache *ncp = nch->ncp;
-	struct autofs_mount *amp = VFSTOAUTOFS(dvp->v_mount);
+	struct autofs_mount *amp = autofs_vnode_mount(dvp);
 	struct autofs_node *anp = VTOI(dvp);
 	struct autofs_node *child = NULL;
 	int error;
 
+	if (amp == NULL || autofs_mount_acquire(amp) == false)
+		return (ENXIO);
+
 	if (autofs_cached(anp, ncp->nc_name, ncp->nc_nlen) == false &&
 	    autofs_ignore_thread() == false) {
 		struct vnode *newvp = NULL;
@@ -210,13 +254,17 @@ autofs_nresolve(struct vop_nresolve_args *ap)
 		    &newvp);
 		cache_lock(nch);
 		cache_drop(nch);
-		if (error)
+		if (error) {
+			autofs_mount_release(amp);
 			return (error);
+		}
 		if (newvp != NULL) {
 			KKASSERT(newvp->v_tag != VT_AUTOFS);
 			vput(newvp);
+			autofs_mount_release(amp);
 			return (ESTALE);
 		}
+		autofs_mount_release(amp);
 		return (0);
 	}
 
@@ -226,6 +274,7 @@ autofs_nresolve(struct vop_nresolve_args *ap)
 
 	if (error) {
 		cache_setvp(nch, NULL);
+		autofs_mount_release(amp);
 		return (0);
 	}
 
@@ -235,9 +284,11 @@ autofs_nresolve(struct vop_nresolve_args *ap)
 		vn_unlock(vp);
 		cache_setvp(nch, vp);
 		vrele(vp);
+		autofs_mount_release(amp);
 		return (0);
 	}
 
+	autofs_mount_release(amp);
 	return (error);
 }
 
@@ -248,17 +299,22 @@ autofs_nmkdir(struct vop_nmkdir_args *ap)
 	struct vnode *dvp = ap->a_dvp;
 	struct nchandle *nch = ap->a_nch;
 	struct namecache *ncp = nch->ncp;
-	struct autofs_mount *amp = VFSTOAUTOFS(dvp->v_mount);
+	struct autofs_mount *amp = autofs_vnode_mount(dvp);
 	struct autofs_node *anp = VTOI(dvp);
 	struct autofs_node *child = NULL;
 	int error;
 
+	if (amp == NULL || autofs_mount_acquire(amp) == false)
+		return (ENXIO);
+
 	/*
 	 * Do not allow mkdir() if the calling thread is not
 	 * automountd(8) descendant.
 	 */
-	if (autofs_ignore_thread() == false)
+	if (autofs_ignore_thread() == false) {
+		autofs_mount_release(amp);
 		return (EPERM);
+	}
 
 	mtx_lock_ex_quick(&amp->am_lock);
 	error = autofs_node_new(anp, amp, ncp->nc_name, ncp->nc_nlen, &child);
@@ -271,9 +327,11 @@ autofs_nmkdir(struct vop_nmkdir_args *ap)
 		cache_setunresolved(nch);
 		cache_setvp(nch, vp);
 		*(ap->a_vpp) = vp;
+		autofs_mount_release(amp);
 		return (0);
 	}
 
+	autofs_mount_release(amp);
 	return (error);
 }
 
@@ -298,7 +356,7 @@ static int
 autofs_readdir(struct vop_readdir_args *ap)
 {
 	struct vnode *vp = ap->a_vp;
-	struct autofs_mount *amp = VFSTOAUTOFS(vp->v_mount);
+	struct autofs_mount *amp = autofs_vnode_mount(vp);
 	struct autofs_node *anp = VTOI(vp);
 	struct autofs_node *child;
 	struct uio *uio = ap->a_uio;
@@ -306,26 +364,34 @@ autofs_readdir(struct vop_readdir_args *ap)
 	size_t reclens;
 	int error;
 
+	if (amp == NULL || autofs_mount_acquire(amp) == false)
+		return (ENXIO);
+
 	KASSERT(vp->v_type == VDIR, ("!VDIR"));
 
 	if (autofs_cached(anp, NULL, 0) == false &&
 	    autofs_ignore_thread() == false) {
 		struct vnode *newvp = NULL;
 		error = autofs_trigger_vn(vp, "", 0, &newvp);
-		if (error)
+		if (error) {
+			autofs_mount_release(amp);
 			return (error);
+		}
 		if (newvp != NULL) {
 			KKASSERT(newvp->v_tag != VT_AUTOFS);
 			vn_unlock(newvp);
 			error = VOP_READDIR(newvp, ap->a_uio, ap->a_cred,
 			    ap->a_eofflag, ap->a_ncookies, ap->a_cookies);
 			vrele(newvp);
+			autofs_mount_release(amp);
 			return (error);
 		}
 	}
 
-	if (uio->uio_offset < 0)
+	if (uio->uio_offset < 0) {
+		autofs_mount_release(amp);
 		return (EINVAL);
+	}
 
 	if (ap->a_eofflag != NULL)
 		*ap->a_eofflag = FALSE;
@@ -347,8 +413,10 @@ autofs_readdir(struct vop_readdir_args *ap)
 	 * Write out the directory entry for "..".
 	 */
 	if (uio->uio_offset <= reclens) {
-		if (uio->uio_offset != reclens)
+		if (uio->uio_offset != reclens) {
+			autofs_mount_release(amp);
 			return (EINVAL);
+		}
 		error = autofs_write_dirent(uio, "..",
 		    anp->an_parent ? anp->an_parent->an_ino : anp->an_ino);
 		if (error) {
@@ -378,6 +446,7 @@ autofs_readdir(struct vop_readdir_args *ap)
 		 */
 		if (uio->uio_offset != reclens) {
 			mtx_unlock_sh(&amp->am_lock);
+			autofs_mount_release(amp);
 			return (EINVAL);
 		}
 
@@ -395,20 +464,26 @@ autofs_readdir(struct vop_readdir_args *ap)
 	if (ap->a_eofflag != NULL)
 		*ap->a_eofflag = TRUE;
 
+	autofs_mount_release(amp);
 	return (0);
 out:
 	/*
 	 * Return error if the initial buffer was too small to do anything.
 	 */
-	if (uio->uio_resid == initial_resid)
+	if (uio->uio_resid == initial_resid) {
+		autofs_mount_release(amp);
 		return (error);
+	}
 
 	/*
 	 * Don't return an error if we managed to copy out some entries.
 	 */
-	if (uio->uio_resid < initial_resid)
+	if (uio->uio_resid < initial_resid) {
+		autofs_mount_release(amp);
 		return (0);
+	}
 
+	autofs_mount_release(amp);
 	return (error);
 }