aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason A. Harmening <jah@FreeBSD.org>2021-06-12 19:45:18 +0000
committerJason A. Harmening <jah@FreeBSD.org>2021-06-29 13:02:01 +0000
commit372691a7ae1878ecdf707195b0854750f07bf44e (patch)
tree80b11eaa3bdd5b25d18222f238439f20cb7af77f
parent435754a59ee203c3369f47ff9103f269c81581b7 (diff)
downloadsrc-372691a7ae1878ecdf707195b0854750f07bf44e.tar.gz
src-372691a7ae1878ecdf707195b0854750f07bf44e.zip
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
-rw-r--r--sys/fs/unionfs/union.h6
-rw-r--r--sys/fs/unionfs/union_subr.c55
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);
}
/*