git: 372691a7ae18 - main - unionfs: release parent vnodes in deferred context

Jason A. Harmening jah at FreeBSD.org
Tue Jun 29 13:01:03 UTC 2021


The branch main has been updated by jah:

URL: https://cgit.FreeBSD.org/src/commit/?id=372691a7ae1878ecdf707195b0854750f07bf44e

commit 372691a7ae1878ecdf707195b0854750f07bf44e
Author:     Jason A. Harmening <jah at FreeBSD.org>
AuthorDate: 2021-06-12 19:45:18 +0000
Commit:     Jason A. Harmening <jah at FreeBSD.org>
CommitDate: 2021-06-29 13:02:01 +0000

    unionfs: release parent vnodes in deferred context
    
    Each unionfs node holds a reference to its parent directory vnode.
    A single open file reference can therefore end up keeping an
    arbitrarily deep vnode hierarchy in place.  When that reference is
    released, the resulting VOP_RECLAIM call chain can then exhaust the
    kernel stack.
    
    This is easily reproducible by running the unionfs.sh stress2 test.
    Fix it by deferring recursive unionfs vnode release to taskqueue
    context.
    
    PR: 238883
    Reviewed By:    kib (earlier version), markj
    Differential Revision: https://reviews.freebsd.org/D30748
---
 sys/fs/unionfs/union.h      |  6 ++++-
 sys/fs/unionfs/union_subr.c | 55 ++++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 55 insertions(+), 6 deletions(-)

diff --git a/sys/fs/unionfs/union.h b/sys/fs/unionfs/union.h
index ba0318bf185c..64706b2b21a2 100644
--- a/sys/fs/unionfs/union.h
+++ b/sys/fs/unionfs/union.h
@@ -89,7 +89,11 @@ struct unionfs_node {
 						/* unionfs status head */
 	LIST_HEAD(unionfs_node_hashhead, unionfs_node) *un_hashtbl;
 						/* dir vnode hash table */
-	LIST_ENTRY(unionfs_node)   un_hash;	/* hash list entry */
+	union {
+		LIST_ENTRY(unionfs_node) un_hash; /* hash list entry */
+		STAILQ_ENTRY(unionfs_node) un_rele; /* deferred release list */
+	};
+
 	u_long		un_hashmask;		/* bit mask */
 	char           *un_path;		/* path */
 	int		un_flag;		/* unionfs node flag */
diff --git a/sys/fs/unionfs/union_subr.c b/sys/fs/unionfs/union_subr.c
index 93e4f50d98c5..04d00fd77e39 100644
--- a/sys/fs/unionfs/union_subr.c
+++ b/sys/fs/unionfs/union_subr.c
@@ -53,6 +53,8 @@
 #include <sys/fcntl.h>
 #include <sys/filedesc.h>
 #include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/taskqueue.h>
 #include <sys/resourcevar.h>
 
 #include <security/mac/mac_framework.h>
@@ -67,6 +69,18 @@ static MALLOC_DEFINE(M_UNIONFSHASH, "UNIONFS hash", "UNIONFS hash table");
 MALLOC_DEFINE(M_UNIONFSNODE, "UNIONFS node", "UNIONFS vnode private part");
 MALLOC_DEFINE(M_UNIONFSPATH, "UNIONFS path", "UNIONFS path private part");
 
+static struct task unionfs_deferred_rele_task;
+static struct mtx unionfs_deferred_rele_lock;
+static STAILQ_HEAD(, unionfs_node) unionfs_deferred_rele_list =
+    STAILQ_HEAD_INITIALIZER(unionfs_deferred_rele_list);
+static TASKQUEUE_DEFINE_THREAD(unionfs_rele);
+
+unsigned int unionfs_ndeferred = 0;
+SYSCTL_UINT(_vfs, OID_AUTO, unionfs_ndeferred, CTLFLAG_RD,
+    &unionfs_ndeferred, 0, "unionfs deferred vnode release");
+
+static void unionfs_deferred_rele(void *, int);
+
 /*
  * Initialize
  */
@@ -74,6 +88,8 @@ int
 unionfs_init(struct vfsconf *vfsp)
 {
 	UNIONFSDEBUG("unionfs_init\n");	/* printed during system boot */
+	TASK_INIT(&unionfs_deferred_rele_task, 0, unionfs_deferred_rele, NULL);
+	mtx_init(&unionfs_deferred_rele_lock, "uniondefr", NULL, MTX_DEF); 
 	return (0);
 }
 
@@ -83,9 +99,35 @@ unionfs_init(struct vfsconf *vfsp)
 int 
 unionfs_uninit(struct vfsconf *vfsp)
 {
+	taskqueue_quiesce(taskqueue_unionfs_rele);
+	taskqueue_free(taskqueue_unionfs_rele);
+	mtx_destroy(&unionfs_deferred_rele_lock);
 	return (0);
 }
 
+static void
+unionfs_deferred_rele(void *arg __unused, int pending __unused)
+{
+	STAILQ_HEAD(, unionfs_node) local_rele_list;
+	struct unionfs_node *unp, *tunp;
+	unsigned int ndeferred;
+
+	ndeferred = 0;
+	STAILQ_INIT(&local_rele_list);
+	mtx_lock(&unionfs_deferred_rele_lock);
+	STAILQ_CONCAT(&local_rele_list, &unionfs_deferred_rele_list);
+	mtx_unlock(&unionfs_deferred_rele_lock);
+	STAILQ_FOREACH_SAFE(unp, &local_rele_list, un_rele, tunp) {
+		++ndeferred;
+		MPASS(unp->un_dvp != NULL);
+		vrele(unp->un_dvp);
+		free(unp, M_UNIONFSNODE);
+	}
+
+	/* We expect this function to be single-threaded, thus no atomic */
+	unionfs_ndeferred += ndeferred;
+}
+
 static struct unionfs_node_hashhead *
 unionfs_get_hashhead(struct vnode *dvp, char *path)
 {
@@ -375,10 +417,6 @@ unionfs_noderem(struct vnode *vp, struct thread *td)
 		vrele(lvp);
 	if (uvp != NULLVP)
 		vrele(uvp);
-	if (dvp != NULLVP) {
-		vrele(dvp);
-		unp->un_dvp = NULLVP;
-	}
 	if (unp->un_path != NULL) {
 		free(unp->un_path, M_UNIONFSPATH);
 		unp->un_path = NULL;
@@ -400,7 +438,14 @@ unionfs_noderem(struct vnode *vp, struct thread *td)
 		LIST_REMOVE(unsp, uns_list);
 		free(unsp, M_TEMP);
 	}
-	free(unp, M_UNIONFSNODE);
+	if (dvp != NULLVP) {
+		mtx_lock(&unionfs_deferred_rele_lock);
+		STAILQ_INSERT_TAIL(&unionfs_deferred_rele_list, unp, un_rele);
+		mtx_unlock(&unionfs_deferred_rele_lock);
+		taskqueue_enqueue(taskqueue_unionfs_rele,
+		    &unionfs_deferred_rele_task);
+	} else
+		free(unp, M_UNIONFSNODE);
 }
 
 /*


More information about the dev-commits-src-all mailing list