lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20200226161404.14136-11-longman@redhat.com>
Date:   Wed, 26 Feb 2020 11:14:03 -0500
From:   Waiman Long <longman@...hat.com>
To:     Alexander Viro <viro@...iv.linux.org.uk>,
        Jonathan Corbet <corbet@....net>,
        Luis Chamberlain <mcgrof@...nel.org>,
        Kees Cook <keescook@...omium.org>,
        Iurii Zaikin <yzaikin@...gle.com>
Cc:     linux-kernel@...r.kernel.org, linux-fsdevel@...r.kernel.org,
        linux-doc@...r.kernel.org,
        Mauro Carvalho Chehab <mchehab+samsung@...nel.org>,
        Eric Biggers <ebiggers@...gle.com>,
        Dave Chinner <david@...morbit.com>,
        Eric Sandeen <sandeen@...hat.com>,
        Waiman Long <longman@...hat.com>
Subject: [PATCH 10/11] fs/dcache: Kill off dentry as last resort

In the unlikely case that an out-of-control application is generating
negative dentries faster than what the negative dentry reclaim process
can get rid of, we will have to kill the negative dentry directly as
the last resort.

The current threshold for killing negative dentry is the maximum of 4
times dentry-dir-max and 10,000 within a directory.

On a 32-vcpu VM, a 30-thread parallel negative dentry generation problem
was run. Without this patch, the negative dentry reclaim process was
overwhelmed by the negative dentry generator and the number of negative
dentries kept growing. With this patch applied with a "dentry-dir-max"
of 10,000. The number of negative dentries never went beyond 40,000.

Signed-off-by: Waiman Long <longman@...hat.com>
---
 fs/dcache.c | 37 +++++++++++++++++++++++++++++--------
 1 file changed, 29 insertions(+), 8 deletions(-)

diff --git a/fs/dcache.c b/fs/dcache.c
index f470763e7fb8..fe48e00349c9 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -140,7 +140,7 @@ static int negative_dentry_dir_max __read_mostly;
 
 static LLIST_HEAD(negative_reclaim_list);
 static DEFINE_STATIC_KEY_FALSE(negative_reclaim_enable);
-static void negative_reclaim_check(struct dentry *parent);
+static void negative_reclaim_check(struct dentry *parent, struct dentry *child);
 static void negative_reclaim_workfn(struct work_struct *work);
 static DECLARE_WORK(negative_reclaim_work, negative_reclaim_workfn);
 
@@ -927,7 +927,7 @@ void dput(struct dentry *dentry)
 			 */
 			if (static_branch_unlikely(&negative_reclaim_enable) &&
 			    neg_parent)
-				negative_reclaim_check(neg_parent);
+				negative_reclaim_check(neg_parent, dentry);
 			return;
 		}
 
@@ -1548,10 +1548,12 @@ static void negative_reclaim_workfn(struct work_struct *work)
  * Check the parent to see if it has too many negative dentries and queue
  * it up for the negative dentry reclaim work function to handle it.
  */
-static void negative_reclaim_check(struct dentry *parent)
+static void negative_reclaim_check(struct dentry *parent, struct dentry *child)
 	__releases(rcu)
 {
 	int limit = negative_dentry_dir_max;
+	int kill_threshold = max(4 * limit, 10000);
+	int ncnt = read_dentry_nnegative(parent);
 	struct reclaim_dentry *dentry_node;
 
 	if (!limit)
@@ -1560,16 +1562,16 @@ static void negative_reclaim_check(struct dentry *parent)
 	/*
 	 * These checks are racy before spin_lock().
 	 */
-	if (!can_reclaim_dentry(parent->d_flags) ||
-	    (parent->d_flags & DCACHE_RECLAIMING) ||
-	    (read_dentry_nnegative(parent) <= limit))
+	if ((!can_reclaim_dentry(parent->d_flags) ||
+	    (parent->d_flags & DCACHE_RECLAIMING) || (ncnt <= limit)) &&
+	    (ncnt < kill_threshold))
 		goto rcu_unlock_out;
 
 	spin_lock(&parent->d_lock);
+	ncnt = read_dentry_nnegative(parent);
 	if (!can_reclaim_dentry(parent->d_flags) ||
 	    (parent->d_flags & DCACHE_RECLAIMING) ||
-	    (read_dentry_nnegative(parent) <= limit) ||
-	    !d_is_dir(parent))
+	    (ncnt <= limit) || !d_is_dir(parent))
 		goto unlock_out;
 
 	dentry_node = kzalloc(sizeof(*dentry_node), GFP_NOWAIT);
@@ -1592,6 +1594,25 @@ static void negative_reclaim_check(struct dentry *parent)
 	return;
 
 unlock_out:
+	/*
+	 * In the unlikely case that an out-of-control application is
+	 * generating negative dentries faster than what the negative
+	 * dentry reclaim process can get rid of, we will have to kill
+	 * the negative dentry directly as the last resort.
+	 *
+	 * N.B. __dentry_kill() releases both the parent and child's d_lock.
+	 */
+	if (unlikely(ncnt >= kill_threshold)) {
+		spin_lock_nested(&child->d_lock, DENTRY_D_LOCK_NESTED);
+		if (can_reclaim_dentry(child->d_flags) &&
+		    !child->d_lockref.count && (child->d_parent == parent)) {
+			rcu_read_unlock();
+			__dentry_kill(child);
+			dput(parent);
+			return;
+		}
+		spin_unlock(&child->d_lock);
+	}
 	spin_unlock(&parent->d_lock);
 rcu_unlock_out:
 	rcu_read_unlock();
-- 
2.18.1

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ