mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Chad Aldreda <ca@modelmiser.com>
To: linux-kernel@vger.kernel.org
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	"Rafael J. Wysocki" <rafael@kernel.org>,
	Danilo Krummrich <dakr@kernel.org>,
	Johan Hovold <johan@kernel.org>,
	driver-core@lists.linux.dev, linux-media@vger.kernel.org,
	Mauro Carvalho Chehab <mchehab@kernel.org>,
	Hans Verkuil <hverkuil+cisco@kernel.org>,
	Laurent Pinchart <laurent.pinchart@ideasonboard.com>,
	Nick Faro <yux50000@hotmail.com>
Subject: [RFC PATCH 1/4] debugfs: add debugfs_file_revoked() for debugging aids
Date: Wed, 04 Nov 2026 21:55:58 +0000	[thread overview]
Message-ID: <20261104215546.3990950-2-ca@modelmiser.com> (raw)
In-Reply-To: <20261104215546.3990950-1-ca@modelmiser.com>

A debugging aid that finds a stale pointer behind an open debugfs file
needs to know whether debugfs itself has already cut that file off from
its driver, and if so which release callback close() will still run.
That knowledge is debugfs-internal: the proxy file_operations
identities, the removal state of the dentry, and which of debugfs's own
_unsafe helpers take the removal guard or copy their data at open.

Add debugfs_file_revoked(), built only with CONFIG_DEBUG_DEVRES_REFS
(added by the next patch, its only user) and not exported, since that
user is built in. It returns true for a removed file created through
the full proxy, the short proxy, or one of debugfs's guarded helpers
(the DEFINE_DEBUGFS_ATTRIBUTE() family, blobs, bools, strings) or one
that copies its data at open (u32 arrays), and hands back the
file_operations whose release callback will still run. A file created
with an _unsafe variant and the driver's own file_operations gets no
guard from debugfs, and the function returns false for it.

Written with Claude (Anthropic) under my direction; see the cover letter
for the disclosure required by Documentation/process/generated-content.rst.

Signed-off-by: Chad Aldreda <ca@modelmiser.com>
---
 fs/debugfs/file.c       | 112 ++++++++++++++++++++++++++++++++++++++++
 include/linux/debugfs.h |  29 +++++++++++
 2 files changed, 141 insertions(+)

diff --git a/fs/debugfs/file.c b/fs/debugfs/file.c
index 170feb7531..1673fd7e2e 100644
--- a/fs/debugfs/file.c
+++ b/fs/debugfs/file.c
@@ -11,6 +11,7 @@
 
 #include <linux/module.h>
 #include <linux/fs.h>
+#include <linux/magic.h>
 #include <linux/seq_file.h>
 #include <linux/pagemap.h>
 #include <linux/debugfs.h>
@@ -1373,3 +1374,114 @@ void debugfs_create_devm_seqfile(struct device *dev, const char *name,
 			    &debugfs_devm_entry_ops);
 }
 EXPORT_SYMBOL_GPL(debugfs_create_devm_seqfile);
+
+#ifdef CONFIG_DEBUG_DEVRES_REFS
+/*
+ * debugfs's own file_operations for _unsafe files on which nothing after
+ * open can reach the driver's data: either every operation takes the
+ * removal guard (debugfs_file_get()), or the data is copied at open.
+ */
+static bool debugfs_unsafe_fops_safe_after_removal(const struct file_operations *fops)
+{
+	if (fops == &fops_blob || fops == &fops_str ||
+	    fops == &fops_bool || fops == &fops_bool_ro || fops == &fops_bool_wo)
+		return true;
+	if (fops == &u32_array_fops)	/* copies the array at open */
+		return true;
+	/*
+	 * DEFINE_DEBUGFS_ATTRIBUTE(): read/write go through
+	 * debugfs_file_get(), release only frees the simple_attr, and the
+	 * macro sets no other operation at all.
+	 */
+	return fops->read == debugfs_attr_read &&
+	       (fops->write == debugfs_attr_write ||
+		fops->write == debugfs_attr_write_signed) &&
+	       fops->release == simple_attr_release &&
+	       !fops->llseek && !fops->read_iter && !fops->write_iter &&
+	       !fops->iopoll && !fops->iterate_shared && !fops->poll &&
+	       !fops->unlocked_ioctl && !fops->compat_ioctl && !fops->mmap &&
+	       !fops->mmap_prepare && !fops->flush && !fops->fsync &&
+	       !fops->fasync && !fops->lock && !fops->get_unmapped_area &&
+	       !fops->check_flags && !fops->flock && !fops->splice_write &&
+	       !fops->splice_read && !fops->splice_eof && !fops->setlease &&
+	       !fops->fallocate && !fops->show_fdinfo &&
+	       !fops->copy_file_range && !fops->remap_file_range &&
+	       !fops->fadvise && !fops->uring_cmd && !fops->uring_cmd_iopoll &&
+#ifndef CONFIG_MMU
+	       !fops->mmap_capabilities &&
+#endif
+	       !fops->init_dma_buf_io_ctx && !fops->fop_flags;
+	/* Keep in step with struct file_operations in include/linux/fs.h. */
+}
+
+/**
+ * debugfs_file_release_safe - will close() of a removed file leave the
+ *                             driver's data alone?
+ * @fops: the file_operations debugfs_file_revoked() handed back, or NULL
+ *
+ * Return: true if @fops has no release callback, or one of the generic
+ * ones that only free what open() allocated (seq_file, simple_attr, u32
+ * array). A driver's own release may dereference i_private or
+ * private_data; full_proxy_release() documents that it must not, but
+ * nothing enforces it.
+ */
+bool debugfs_file_release_safe(const struct file_operations *fops)
+{
+	if (!fops || !fops->release)
+		return true;
+	return fops->release == single_release ||
+	       fops->release == seq_release ||
+	       fops->release == simple_attr_release ||
+	       fops->release == u32_array_release;
+}
+
+/**
+ * debugfs_file_revoked - has debugfs cut @file off from its driver?
+ * @file: an open file, held by the caller
+ * @real_fops: set to the file_operations close() will still consult, or
+ *             NULL for a short-fops proxy; callers must still test
+ *             ->release, which may be NULL
+ *
+ * For debugging aids that find a stale pointer behind an open debugfs file
+ * after the file was removed; not for use elsewhere.
+ *
+ * Return: true if @file is a removed debugfs file on which no further
+ * operation can reach the driver's data: one created through the full
+ * proxy (debugfs_create_file() and the helpers built on it), on which
+ * read/write/llseek/ioctl fail with -EIO and poll reports EPOLLHUP (see
+ * __debugfs_file_get()); or one of
+ * the _unsafe files whose read/write callbacks are debugfs's own guarded
+ * ones (the DEFINE_DEBUGFS_ATTRIBUTE() family, blobs, bools, strings) or
+ * which copy their data at open (u32 arrays). close() still runs the real
+ * release callback on such a file, through full_proxy_release() for
+ * proxied files and directly otherwise, so @real_fops names it. A file
+ * created with an _unsafe variant and the driver's own file_operations
+ * gets no guard from debugfs, and this returns false.
+ */
+bool debugfs_file_revoked(const struct file *file,
+			  const struct file_operations **real_fops)
+{
+	const struct debugfs_fsdata *fsd;
+
+	*real_fops = NULL;
+
+	if (!d_unlinked(file->f_path.dentry))
+		return false;
+
+	if (file->f_op == &debugfs_full_proxy_file_operations) {
+		/* Populated by full_proxy_open_regular(), pinned by the file. */
+		fsd = READ_ONCE(file->f_path.dentry->d_fsdata);
+		if (fsd)
+			*real_fops = fsd->real_fops;
+		return true;
+	}
+	if (file->f_op == &debugfs_full_short_proxy_file_operations)
+		return true;	/* short fops: no release callback at all */
+	if (file_inode(file)->i_sb->s_magic == DEBUGFS_MAGIC &&
+	    debugfs_unsafe_fops_safe_after_removal(file->f_op)) {
+		*real_fops = file->f_op;
+		return true;
+	}
+	return false;
+}
+#endif /* CONFIG_DEBUG_DEVRES_REFS */
diff --git a/include/linux/debugfs.h b/include/linux/debugfs.h
index 4177c47382..b2a63037d5 100644
--- a/include/linux/debugfs.h
+++ b/include/linux/debugfs.h
@@ -166,6 +166,23 @@ void *debugfs_get_aux(const struct file *file);
 
 int debugfs_file_get(struct dentry *dentry);
 void debugfs_file_put(struct dentry *dentry);
+#ifdef CONFIG_DEBUG_DEVRES_REFS
+bool debugfs_file_revoked(const struct file *file,
+			  const struct file_operations **real_fops);
+bool debugfs_file_release_safe(const struct file_operations *fops);
+#else
+static inline bool debugfs_file_revoked(const struct file *file,
+					const struct file_operations **real_fops)
+{
+	*real_fops = NULL;
+	return false;
+}
+
+static inline bool debugfs_file_release_safe(const struct file_operations *fops)
+{
+	return true;
+}
+#endif
 
 ssize_t debugfs_attr_read(struct file *file, char __user *buf,
 			size_t len, loff_t *ppos);
@@ -336,6 +353,18 @@ static inline int debugfs_file_get(struct dentry *dentry)
 static inline void debugfs_file_put(struct dentry *dentry)
 { }
 
+static inline bool debugfs_file_revoked(const struct file *file,
+					const struct file_operations **real_fops)
+{
+	*real_fops = NULL;
+	return false;
+}
+
+static inline bool debugfs_file_release_safe(const struct file_operations *fops)
+{
+	return true;
+}
+
 static inline ssize_t debugfs_attr_read(struct file *file, char __user *buf,
 					size_t len, loff_t *ppos)
 {
-- 
2.43.0



  reply	other threads:[~2026-11-04 21:56 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-04 21:55 [RFC PATCH 0/4] devres: warn about managed memory still held by open files at unbind Chad Aldreda
2026-11-04 21:55 ` Chad Aldreda [this message]
2026-11-04 22:00 ` [RFC PATCH 2/4] devres: add DEBUG_DEVRES_REFS to warn about managed memory still held by open files Chad Aldreda
2026-11-04 22:00 ` [RFC PATCH 3/4] media: v4l2-dev: name the video_device behind an open file for DEBUG_DEVRES_REFS Chad Aldreda
2026-11-04 22:00 ` [RFC PATCH 4/4] drivers: base: test: add a vehicle " Chad Aldreda
2026-11-04 22:07 ` [RFC PATCH 0/4] devres: warn about managed memory still held by open files at unbind Laurent Pinchart
2026-11-04 22:31   ` Chad Aldreda
2026-11-04 22:41     ` Laurent Pinchart

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261104215546.3990950-2-ca@modelmiser.com \
    --to=ca@modelmiser.com \
    --cc=dakr@kernel.org \
    --cc=driver-core@lists.linux.dev \
    --cc=gregkh@linuxfoundation.org \
    --cc=hverkuil+cisco@kernel.org \
    --cc=johan@kernel.org \
    --cc=laurent.pinchart@ideasonboard.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-media@vger.kernel.org \
    --cc=mchehab@kernel.org \
    --cc=rafael@kernel.org \
    --cc=yux50000@hotmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®