[PATCH v2] kernfs: recheck of->released after acquiring the active reference

Fan Wu <[email protected]>
Newsgroups dev.linux.lists.driver-core,org.kernel.vger.linux-kernel,org.kernel.vger.stable
Message-ID <[email protected]>
kernfs_get_active_of(), added by commit 3c9ba2777d6c ("kernfs: Fix UAF
in polling when open file is released"), tests @of->released before
acquiring the active reference on @of->kn.  A hide/drain/show cycle can
run between those steps: the drain path releases the open file, and the
reactivation lets kernfs_get_active() succeed again.  Any entry guarded
by kernfs_get_active_of() can consequently run its file operation on an
already released open file; on the cgroup pressure files, the poll
callback dereferences of->priv while forming &ctx->psi.trigger and can
hit either stale, freed memory or NULL.

  CPU 0 (kernfs_fop_poll)       CPU 1 (echo 0/1 > cgroup.pressure)
  -------------------------     ---------------------------------
  of->released == false         kernfs_show(kn, false)
  ... preempted ...               kernfs_drain()
                                   kernfs_release_file()
                                     ->release(of)  (frees of->priv)
                                     of->released = true
                                kernfs_show(kn, true)
                                  kernfs_activate_one(kn)
  kernfs_get_active(of->kn)
  ops->poll(of)

The cycle needs the file operation to be delayed between the two
steps, but kernfs_show() cycles like the one above are fully
userspace driven.

Acquire the active reference first and re-check @of->released after
it.  While the reference is held, @kn cannot be drained: kernfs_drain()
waits for kn->active to reach KN_DEACTIVATED_BIAS before draining open
files, and the only other kernfs_release_file() caller,
kernfs_fop_release(), is serialized against in-flight file operations
by the VFS, so the re-read settles whether @of was released for good.
The re-check needs no lock: @of->released is only ever set to true,
the drain which sets it precedes the reactivation under kernfs_rwsem,
and the fully-ordered RMW on @kn->active in kernfs_get_active() then
orders the read after that reactivation.

This issue was found by an in-house static analysis tool.

Fixes: 3c9ba2777d6c ("kernfs: Fix UAF in polling when open file is released")
Cc: [email protected]
Suggested-by: Tejun Heo <[email protected]>
Assisted-by: Codex:gpt-5.6
Signed-off-by: Fan Wu <[email protected]>
---
Changes since v1:
- Drop the kernfs_open_file_mutex re-check; the fully-ordered RMW in
  kernfs_get_active() together with the kernfs_rwsem serialization of
  drain and reactivation already orders the released re-read.
- Read @of->released with READ_ONCE().
---
 fs/kernfs/file.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/fs/kernfs/file.c b/fs/kernfs/file.c
index 9adf36e6364b..44d40d9e6dd1 100644
--- a/fs/kernfs/file.c
+++ b/fs/kernfs/file.c
@@ -73,12 +73,17 @@ static struct kernfs_open_node *of_on(struct kernfs_open_file *of)
 /* Get active reference to kernfs node for an open file */
 static struct kernfs_open_file *kernfs_get_active_of(struct kernfs_open_file *of)
 {
-	/* Skip if file was already released */
-	if (unlikely(of->released))
+	if (!kernfs_get_active(of->kn))
 		return NULL;
 
-	if (!kernfs_get_active(of->kn))
+	/*
+	 * A successful active reference prevents a new drain and orders this
+	 * check after an earlier reactivation.
+	 */
+	if (unlikely(READ_ONCE(of->released))) {
+		kernfs_put_active(of->kn);
 		return NULL;
+	}
 
 	return of;
 }
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.