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>] [day] [month] [year] [list]
Message-ID: <20251119191758.612694-2-kevmitch@arista.com>
Date: Wed, 19 Nov 2025 11:17:57 -0800
From: Kevin Mitchell <kevmitch@...sta.com>
To: Tejun Heo <tj@...nel.org>
Cc: Kevin Mitchell <kevmitch@...sta.com>,
	Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
	Chengming Zhou <zhouchengming@...edance.com>,
	linux-kernel@...r.kernel.org
Subject: [PATCH] kernfs: use atomics for open_node refcounts

Counts of files to be released and mmapped files were added to
kernfs_open_node in commit bdb2fd7fc56e ("kernfs: Skip
kernfs_drain_open_files() more aggressively") to optimize
kernfs_drain_open_files(). A WARN_ON_ONCE sanity check was also added in
kernfs_drain_open_files to ensure that these counters were brought to
zero once all files had been drained.

This WARNING was found to trigger on occasion during pci hotplug link
down when mmapped pci resource files were removed in
pci_remove_resource_files. This was caused by a race condition during
the initial mmappings where incrementing nr_mmapped from multiple CPUs
such that 2 (or possibly more) simultaneous increments overwrote each
other thus appearing as only a single increment. The correct number of
kernfs_open_files with mmapped == true were nevertheless still present
in the kernfs_open_node's files list. Consequently, the iteration in
kernfs_drain_open_files would underflow nr_mmapped and the WARNING would
fire.

Since both nr_mmapped and nr_to_release may be incremented and
decremented from multiple CPUs simultaneously without acquiring the
kernfs_open_file_mutex_lock, make these members atomic. This should
ensure that simultaneous increments and decrements don't overwrite each
other.

Fixes: bdb2fd7fc56e ("kernfs: Skip kernfs_drain_open_files() more aggressively")
Signed-off-by: Kevin Mitchell <kevmitch@...sta.com>
---
 fs/kernfs/file.c | 22 ++++++++++++----------
 1 file changed, 12 insertions(+), 10 deletions(-)

diff --git a/fs/kernfs/file.c b/fs/kernfs/file.c
index 9adf36e6364b..a9d0bf82ceba 100644
--- a/fs/kernfs/file.c
+++ b/fs/kernfs/file.c
@@ -23,8 +23,8 @@ struct kernfs_open_node {
 	atomic_t		event;
 	wait_queue_head_t	poll;
 	struct list_head	files; /* goes through kernfs_open_file.list */
-	unsigned int		nr_mmapped;
-	unsigned int		nr_to_release;
+	atomic_t		nr_mmapped;
+	atomic_t		nr_to_release;
 };
 
 /*
@@ -503,7 +503,7 @@ static int kernfs_fop_mmap(struct file *file, struct vm_area_struct *vma)
 	rc = 0;
 	if (!of->mmapped) {
 		of->mmapped = true;
-		of_on(of)->nr_mmapped++;
+		atomic_inc(&of_on(of)->nr_mmapped);
 		of->vm_ops = vma->vm_ops;
 	}
 	vma->vm_ops = &kernfs_vm_ops;
@@ -553,7 +553,7 @@ static int kernfs_get_open_node(struct kernfs_node *kn,
 
 	list_add_tail(&of->list, &on->files);
 	if (kn->flags & KERNFS_HAS_RELEASE)
-		on->nr_to_release++;
+		atomic_inc(&on->nr_to_release);
 
 	mutex_unlock(mutex);
 	return 0;
@@ -592,10 +592,10 @@ static void kernfs_unlink_open_file(struct kernfs_node *kn,
 		if (kn->flags & KERNFS_HAS_RELEASE) {
 			WARN_ON_ONCE(of->released == open_failed);
 			if (open_failed)
-				on->nr_to_release--;
+				atomic_dec(&on->nr_to_release);
 		}
 		if (of->mmapped)
-			on->nr_mmapped--;
+			atomic_dec(&on->nr_mmapped);
 		list_del(&of->list);
 	}
 
@@ -763,7 +763,7 @@ static void kernfs_release_file(struct kernfs_node *kn,
 		 */
 		kn->attr.ops->release(of);
 		of->released = true;
-		of_on(of)->nr_to_release--;
+		atomic_dec(&of_on(of)->nr_to_release);
 	}
 }
 
@@ -802,7 +802,8 @@ bool kernfs_should_drain_open_files(struct kernfs_node *kn)
 
 	rcu_read_lock();
 	on = rcu_dereference(kn->attr.open);
-	ret = on && (on->nr_mmapped || on->nr_to_release);
+	ret = on && (atomic_read(&on->nr_mmapped) ||
+		     atomic_read(&on->nr_to_release));
 	rcu_read_unlock();
 
 	return ret;
@@ -827,14 +828,15 @@ void kernfs_drain_open_files(struct kernfs_node *kn)
 		if (of->mmapped) {
 			unmap_mapping_range(inode->i_mapping, 0, 0, 1);
 			of->mmapped = false;
-			on->nr_mmapped--;
+			atomic_dec(&on->nr_mmapped);
 		}
 
 		if (kn->flags & KERNFS_HAS_RELEASE)
 			kernfs_release_file(kn, of);
 	}
 
-	WARN_ON_ONCE(on->nr_mmapped || on->nr_to_release);
+	WARN_ON_ONCE(atomic_read(&on->nr_mmapped) ||
+		     atomic_read(&on->nr_to_release));
 	mutex_unlock(mutex);
 }
 
-- 
2.51.0


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ