[v2,1/6] debugfs: fix automount d_fsdata usage

Message ID 20231124172522.01928f127e73.I64fe5615568e87f9ae2d7fb2ac4e5fa96924cb50@changeid
State New
Headers
Series fixes for debugfs/wireless locking issue |

Commit Message

Johannes Berg Nov. 24, 2023, 4:25 p.m. UTC
  From: Johannes Berg <johannes.berg@intel.com>

debugfs_create_automount() stores a function pointer in d_fsdata,
but since commit 7c8d469877b1 ("debugfs: add support for more
elaborate ->d_fsdata") debugfs_release_dentry() will free it, now
conditionally on DEBUGFS_FSDATA_IS_REAL_FOPS_BIT, but that's not
set for the function pointer in automount. As a result, removing
an automount dentry would attempt to free the function pointer.
Luckily, the only user of this (tracing) never removes it.

Nevertheless, it's safer if we just handle the fsdata in one way,
namely either DEBUGFS_FSDATA_IS_REAL_FOPS_BIT or allocated. Thus,
change the automount to allocate it, and use the real_fops in the
data to indicate whether or not automount is filled, rather than
adding a type tag. At least for now this isn't actually needed,
but the next changes will require it.

Also check in debugfs_file_get() that it gets only called
on regular files, just to make things clearer.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
v2: add missing kfree() pointed out by smatch
---
 fs/debugfs/file.c     |  8 ++++++++
 fs/debugfs/inode.c    | 27 ++++++++++++++++++++-------
 fs/debugfs/internal.h | 10 ++++++++--
 3 files changed, 36 insertions(+), 9 deletions(-)
  

Comments

Greg KH Nov. 25, 2023, 2:48 p.m. UTC | #1
On Fri, Nov 24, 2023 at 05:25:24PM +0100, Johannes Berg wrote:
> From: Johannes Berg <johannes.berg@intel.com>
> 
> debugfs_create_automount() stores a function pointer in d_fsdata,
> but since commit 7c8d469877b1 ("debugfs: add support for more
> elaborate ->d_fsdata") debugfs_release_dentry() will free it, now
> conditionally on DEBUGFS_FSDATA_IS_REAL_FOPS_BIT, but that's not
> set for the function pointer in automount. As a result, removing
> an automount dentry would attempt to free the function pointer.
> Luckily, the only user of this (tracing) never removes it.
> 
> Nevertheless, it's safer if we just handle the fsdata in one way,
> namely either DEBUGFS_FSDATA_IS_REAL_FOPS_BIT or allocated. Thus,
> change the automount to allocate it, and use the real_fops in the
> data to indicate whether or not automount is filled, rather than
> adding a type tag. At least for now this isn't actually needed,
> but the next changes will require it.
> 
> Also check in debugfs_file_get() that it gets only called
> on regular files, just to make things clearer.
> 
> Signed-off-by: Johannes Berg <johannes.berg@intel.com>
> ---
> v2: add missing kfree() pointed out by smatch

Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
  
Johannes Berg Nov. 25, 2023, 6:31 p.m. UTC | #2
On Sat, 2023-11-25 at 14:48 +0000, Greg Kroah-Hartman wrote:
> On Fri, Nov 24, 2023 at 05:25:24PM +0100, Johannes Berg wrote:
> > From: Johannes Berg <johannes.berg@intel.com>
> > 
> > debugfs_create_automount() stores a function pointer in d_fsdata,
> > but since commit 7c8d469877b1 ("debugfs: add support for more
> > elaborate ->d_fsdata") debugfs_release_dentry() will free it, now
> > conditionally on DEBUGFS_FSDATA_IS_REAL_FOPS_BIT, but that's not
> > set for the function pointer in automount. As a result, removing
> > an automount dentry would attempt to free the function pointer.
> > Luckily, the only user of this (tracing) never removes it.
> > 
> > Nevertheless, it's safer if we just handle the fsdata in one way,
> > namely either DEBUGFS_FSDATA_IS_REAL_FOPS_BIT or allocated. Thus,
> > change the automount to allocate it, and use the real_fops in the
> > data to indicate whether or not automount is filled, rather than
> > adding a type tag. At least for now this isn't actually needed,
> > but the next changes will require it.
> > 
> > Also check in debugfs_file_get() that it gets only called
> > on regular files, just to make things clearer.
> > 
> > Signed-off-by: Johannes Berg <johannes.berg@intel.com>
> > ---
> > v2: add missing kfree() pointed out by smatch
> 
> Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

I have to fix the Kconfig issue in one of the later (wireless) patches,
but does that mean you'd actually prefer the debugfs changes go through
the wireless tree, together with the code using it?

Thanks,
johannes
  
Greg KH Nov. 25, 2023, 7:29 p.m. UTC | #3
On Sat, Nov 25, 2023 at 07:31:47PM +0100, Johannes Berg wrote:
> On Sat, 2023-11-25 at 14:48 +0000, Greg Kroah-Hartman wrote:
> > On Fri, Nov 24, 2023 at 05:25:24PM +0100, Johannes Berg wrote:
> > > From: Johannes Berg <johannes.berg@intel.com>
> > > 
> > > debugfs_create_automount() stores a function pointer in d_fsdata,
> > > but since commit 7c8d469877b1 ("debugfs: add support for more
> > > elaborate ->d_fsdata") debugfs_release_dentry() will free it, now
> > > conditionally on DEBUGFS_FSDATA_IS_REAL_FOPS_BIT, but that's not
> > > set for the function pointer in automount. As a result, removing
> > > an automount dentry would attempt to free the function pointer.
> > > Luckily, the only user of this (tracing) never removes it.
> > > 
> > > Nevertheless, it's safer if we just handle the fsdata in one way,
> > > namely either DEBUGFS_FSDATA_IS_REAL_FOPS_BIT or allocated. Thus,
> > > change the automount to allocate it, and use the real_fops in the
> > > data to indicate whether or not automount is filled, rather than
> > > adding a type tag. At least for now this isn't actually needed,
> > > but the next changes will require it.
> > > 
> > > Also check in debugfs_file_get() that it gets only called
> > > on regular files, just to make things clearer.
> > > 
> > > Signed-off-by: Johannes Berg <johannes.berg@intel.com>
> > > ---
> > > v2: add missing kfree() pointed out by smatch
> > 
> > Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> 
> I have to fix the Kconfig issue in one of the later (wireless) patches,
> but does that mean you'd actually prefer the debugfs changes go through
> the wireless tree, together with the code using it?

I'm all for you taking these through the wireless tree, have fun!

greg k-h
  

Patch

diff --git a/fs/debugfs/file.c b/fs/debugfs/file.c
index 87b3753aa4b1..e00189aebbf4 100644
--- a/fs/debugfs/file.c
+++ b/fs/debugfs/file.c
@@ -84,6 +84,14 @@  int debugfs_file_get(struct dentry *dentry)
 	struct debugfs_fsdata *fsd;
 	void *d_fsd;
 
+	/*
+	 * This could only happen if some debugfs user erroneously calls
+	 * debugfs_file_get() on a dentry that isn't even a file, let
+	 * them know about it.
+	 */
+	if (WARN_ON(!d_is_reg(dentry)))
+		return -EINVAL;
+
 	d_fsd = READ_ONCE(dentry->d_fsdata);
 	if (!((unsigned long)d_fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT)) {
 		fsd = d_fsd;
diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c
index 83e57e9f9fa0..dcde4199a625 100644
--- a/fs/debugfs/inode.c
+++ b/fs/debugfs/inode.c
@@ -236,17 +236,19 @@  static const struct super_operations debugfs_super_operations = {
 
 static void debugfs_release_dentry(struct dentry *dentry)
 {
-	void *fsd = dentry->d_fsdata;
+	struct debugfs_fsdata *fsd = dentry->d_fsdata;
 
-	if (!((unsigned long)fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT))
-		kfree(dentry->d_fsdata);
+	if ((unsigned long)fsd & DEBUGFS_FSDATA_IS_REAL_FOPS_BIT)
+		return;
+
+	kfree(fsd);
 }
 
 static struct vfsmount *debugfs_automount(struct path *path)
 {
-	debugfs_automount_t f;
-	f = (debugfs_automount_t)path->dentry->d_fsdata;
-	return f(path->dentry, d_inode(path->dentry)->i_private);
+	struct debugfs_fsdata *fsd = path->dentry->d_fsdata;
+
+	return fsd->automount(path->dentry, d_inode(path->dentry)->i_private);
 }
 
 static const struct dentry_operations debugfs_dops = {
@@ -634,13 +636,23 @@  struct dentry *debugfs_create_automount(const char *name,
 					void *data)
 {
 	struct dentry *dentry = start_creating(name, parent);
+	struct debugfs_fsdata *fsd;
 	struct inode *inode;
 
 	if (IS_ERR(dentry))
 		return dentry;
 
+	fsd = kzalloc(sizeof(*fsd), GFP_KERNEL);
+	if (!fsd) {
+		failed_creating(dentry);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	fsd->automount = f;
+
 	if (!(debugfs_allow & DEBUGFS_ALLOW_API)) {
 		failed_creating(dentry);
+		kfree(fsd);
 		return ERR_PTR(-EPERM);
 	}
 
@@ -648,13 +660,14 @@  struct dentry *debugfs_create_automount(const char *name,
 	if (unlikely(!inode)) {
 		pr_err("out of free dentries, can not create automount '%s'\n",
 		       name);
+		kfree(fsd);
 		return failed_creating(dentry);
 	}
 
 	make_empty_dir_inode(inode);
 	inode->i_flags |= S_AUTOMOUNT;
 	inode->i_private = data;
-	dentry->d_fsdata = (void *)f;
+	dentry->d_fsdata = fsd;
 	/* directory inodes start off with i_nlink == 2 (for "." entry) */
 	inc_nlink(inode);
 	d_instantiate(dentry, inode);
diff --git a/fs/debugfs/internal.h b/fs/debugfs/internal.h
index 92af8ae31313..f7c489b5a368 100644
--- a/fs/debugfs/internal.h
+++ b/fs/debugfs/internal.h
@@ -17,8 +17,14 @@  extern const struct file_operations debugfs_full_proxy_file_operations;
 
 struct debugfs_fsdata {
 	const struct file_operations *real_fops;
-	refcount_t active_users;
-	struct completion active_users_drained;
+	union {
+		/* automount_fn is used when real_fops is NULL */
+		debugfs_automount_t automount;
+		struct {
+			refcount_t active_users;
+			struct completion active_users_drained;
+		};
+	};
 };
 
 /*