The Linux Kernel Mailing List
 help / color / mirror / Atom feed
* [PATCH v2 0/3] debugfs: make debugfs_create_str() read-only
@ 2026-08-06  8:48 Yichong Chen
  2026-08-06  8:48 ` [PATCH v2 1/3] interconnect: debugfs: replace writable string helper Yichong Chen
                   ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Yichong Chen @ 2026-08-06  8:48 UTC (permalink / raw)
  To: gregkh
  Cc: rafael, dakr, djakov, quic_mdtipton, vkoul, yung-chuan.liao,
	pierre-louis.bossart, driver-core, linux-kernel, linux-pm,
	linux-sound, Yichong Chen

debugfs_create_str() currently supports writable files through a generic
string replacement helper.  Concurrent writers can race in that helper.

Instead of adding more locking to the generic debugfs string code, convert
the existing writable in-tree users to local file operations first, then make
debugfs_create_str() read-only and refuse modes with write permission bits.

Changes since v1:
- Follow Greg's suggestion to avoid adding locking to the generic
  debugfs_create_str() write path.
- Convert the existing writable in-tree users to local file operations.
- Make debugfs_create_str() read-only and refuse writable modes.
- Split the change into a 3-patch series.

Yichong Chen (3):
  interconnect: debugfs: replace writable string helper
  soundwire: debugfs: replace writable string helper
  debugfs: make debugfs_create_str() read-only

 drivers/interconnect/debugfs-client.c | 75 ++++++++++++++++++++-----
 drivers/soundwire/debugfs.c           | 56 ++++++++++++++++--
 fs/debugfs/file.c                     | 81 ++++-----------------------
 3 files changed, 124 insertions(+), 88 deletions(-)

-- 
2.51.0

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v2 1/3] interconnect: debugfs: replace writable string helper
  2026-08-06  8:48 [PATCH v2 0/3] debugfs: make debugfs_create_str() read-only Yichong Chen
@ 2026-08-06  8:48 ` Yichong Chen
  2026-08-06  9:01   ` Greg KH
  2026-08-06  8:48 ` [PATCH v2 2/3] soundwire: " Yichong Chen
  2026-08-06  8:48 ` [PATCH v2 3/3] debugfs: make debugfs_create_str() read-only Yichong Chen
  2 siblings, 1 reply; 8+ messages in thread
From: Yichong Chen @ 2026-08-06  8:48 UTC (permalink / raw)
  To: gregkh
  Cc: rafael, dakr, djakov, quic_mdtipton, vkoul, yung-chuan.liao,
	pierre-louis.bossart, driver-core, linux-kernel, linux-pm,
	linux-sound, Yichong Chen

debugfs_create_str() is being made read-only because its generic write path
is hard to make safe without adding more locking to the helper.

Convert the interconnect debugfs client src_node and dst_node entries to
local file operations before removing writable string support from
debugfs_create_str().  Protect the string replacement and path lookup with
the existing debugfs_lock.

Signed-off-by: Yichong Chen <chenyichong@uniontech.com>
---
 drivers/interconnect/debugfs-client.c | 75 ++++++++++++++++++++++-----
 1 file changed, 62 insertions(+), 13 deletions(-)

diff --git a/drivers/interconnect/debugfs-client.c b/drivers/interconnect/debugfs-client.c
index 08df9188ef94..b71c25fd9314 100644
--- a/drivers/interconnect/debugfs-client.c
+++ b/drivers/interconnect/debugfs-client.c
@@ -5,6 +5,7 @@
 #include <linux/debugfs.h>
 #include <linux/interconnect.h>
 #include <linux/platform_device.h>
+#include <linux/slab.h>
 
 #include "internal.h"
 
@@ -36,6 +37,59 @@ struct debugfs_path {
 	struct list_head list;
 };
 
+static ssize_t icc_node_read(struct file *file, char __user *user_buf,
+			     size_t count, loff_t *ppos)
+{
+	char **node = file->private_data;
+	char *copy;
+	size_t len;
+	ssize_t ret;
+
+	mutex_lock(&debugfs_lock);
+	copy = kstrdup(*node ?: "", GFP_KERNEL);
+	mutex_unlock(&debugfs_lock);
+	if (!copy)
+		return -ENOMEM;
+
+	len = strlen(copy);
+	copy[len++] = '\n';
+	ret = simple_read_from_buffer(user_buf, count, ppos, copy, len);
+	kfree(copy);
+	return ret;
+}
+
+static ssize_t icc_node_write(struct file *file, const char __user *user_buf,
+			      size_t count, loff_t *ppos)
+{
+	char **node = file->private_data;
+	char *old, *new;
+
+	if (*ppos)
+		return -EINVAL;
+	if (count + 1 > PAGE_SIZE)
+		return -E2BIG;
+
+	new = memdup_user_nul(user_buf, count);
+	if (IS_ERR(new))
+		return PTR_ERR(new);
+	strim(new);
+
+	mutex_lock(&debugfs_lock);
+	old = *node;
+	*node = new;
+	mutex_unlock(&debugfs_lock);
+
+	kfree(old);
+	return count;
+}
+
+static const struct file_operations icc_node_fops = {
+	.open = simple_open,
+	.read = icc_node_read,
+	.write = icc_node_write,
+	.llseek = default_llseek,
+};
+
 static struct icc_path *get_path(const char *src, const char *dst)
 {
 	struct debugfs_path *path;
@@ -56,24 +110,17 @@ static int icc_get_set(void *data, u64 val)
 
 	mutex_lock(&debugfs_lock);
 
-	rcu_read_lock();
-	src = rcu_dereference(src_node);
-	dst = rcu_dereference(dst_node);
-
 	/*
 	 * If we've already looked up a path, then use the existing one instead
 	 * of calling icc_get() again. This allows for updating previous BW
 	 * votes when "get" is written to multiple times for multiple paths.
 	 */
-	cur_path = get_path(src, dst);
-	if (cur_path) {
-		rcu_read_unlock();
+	cur_path = get_path(src_node, dst_node);
+	if (cur_path)
 		goto out;
-	}
 
-	src = kstrdup(src, GFP_ATOMIC);
-	dst = kstrdup(dst, GFP_ATOMIC);
-	rcu_read_unlock();
+	src = kstrdup(src_node, GFP_KERNEL);
+	dst = kstrdup(dst_node, GFP_KERNEL);
 
 	if (!src || !dst) {
 		ret = -ENOMEM;
@@ -160,8 +207,10 @@ int icc_debugfs_client_init(struct dentry *icc_dir)
 
 	client_dir = debugfs_create_dir("test_client", icc_dir);
 
-	debugfs_create_str("src_node", 0600, client_dir, &src_node);
-	debugfs_create_str("dst_node", 0600, client_dir, &dst_node);
+	debugfs_create_file("src_node", 0600, client_dir, &src_node,
+			    &icc_node_fops);
+	debugfs_create_file("dst_node", 0600, client_dir, &dst_node,
+			    &icc_node_fops);
 	debugfs_create_file("get", 0200, client_dir, NULL, &icc_get_fops);
 	debugfs_create_u32("avg_bw", 0600, client_dir, &avg_bw);
 	debugfs_create_u32("peak_bw", 0600, client_dir, &peak_bw);
-- 
2.51.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 2/3] soundwire: debugfs: replace writable string helper
  2026-08-06  8:48 [PATCH v2 0/3] debugfs: make debugfs_create_str() read-only Yichong Chen
  2026-08-06  8:48 ` [PATCH v2 1/3] interconnect: debugfs: replace writable string helper Yichong Chen
@ 2026-08-06  8:48 ` Yichong Chen
  2026-08-06  9:02   ` Greg KH
  2026-08-06  8:48 ` [PATCH v2 3/3] debugfs: make debugfs_create_str() read-only Yichong Chen
  2 siblings, 1 reply; 8+ messages in thread
From: Yichong Chen @ 2026-08-06  8:48 UTC (permalink / raw)
  To: gregkh
  Cc: rafael, dakr, djakov, quic_mdtipton, vkoul, yung-chuan.liao,
	pierre-louis.bossart, driver-core, linux-kernel, linux-pm,
	linux-sound, Yichong Chen

debugfs_create_str() is being made read-only because its generic write path
is hard to make safe without adding more locking to the helper.

Convert the SoundWire firmware_file entry to a local write-only file
operation before removing writable string support from
debugfs_create_str().

Copy the firmware name before request_firmware() so later debugfs writes
cannot replace the string while it is being used.

Signed-off-by: Yichong Chen <chenyichong@uniontech.com>
---
 drivers/soundwire/debugfs.c | 56 ++++++++++++++++++++++++++++++++++---
 1 file changed, 52 insertions(+), 4 deletions(-)

diff --git a/drivers/soundwire/debugfs.c b/drivers/soundwire/debugfs.c
index 099eb84a548e..bba2b65a0668 100644
--- a/drivers/soundwire/debugfs.c
+++ b/drivers/soundwire/debugfs.c
@@ -145,6 +145,38 @@ static u32 start_addr;
 static size_t num_bytes;
 static u8 read_buffer[MAX_CMD_BYTES];
 static char *firmware_file;
+static DEFINE_MUTEX(firmware_file_lock);
+
+static ssize_t firmware_file_write(struct file *file,
+				   const char __user *user_buf,
+				   size_t count, loff_t *ppos)
+{
+	char *old, *new;
+
+	if (*ppos)
+		return -EINVAL;
+	if (count + 1 > PAGE_SIZE)
+		return -E2BIG;
+
+	new = memdup_user_nul(user_buf, count);
+	if (IS_ERR(new))
+		return PTR_ERR(new);
+	strim(new);
+
+	mutex_lock(&firmware_file_lock);
+	old = firmware_file;
+	firmware_file = new;
+	mutex_unlock(&firmware_file_lock);
+
+	kfree(old);
+	return count;
+}
+
+static const struct file_operations firmware_file_fops = {
+	.open = simple_open,
+	.write = firmware_file_write,
+	.llseek = default_llseek,
+};
 
 static int set_command(void *data, u64 value)
 {
@@ -246,6 +278,7 @@ static int cmd_go(void *data, u64 value)
 {
 	const struct firmware *fw = NULL;
 	struct sdw_slave *slave = data;
+	char *fw_name = NULL;
 	ktime_t start_t;
 	ktime_t finish_t;
 	int ret;
@@ -265,15 +298,24 @@ static int cmd_go(void *data, u64 value)
 	}
 
 	if (cmd == 0) {
-		ret = request_firmware(&fw, firmware_file, &slave->dev);
+		mutex_lock(&firmware_file_lock);
+		if (firmware_file)
+			fw_name = kstrdup(firmware_file, GFP_KERNEL);
+		mutex_unlock(&firmware_file_lock);
+		if (!fw_name) {
+			ret = -ENOMEM;
+			goto out;
+		}
+
+		ret = request_firmware(&fw, fw_name, &slave->dev);
 		if (ret < 0) {
-			dev_err(&slave->dev, "firmware %s not found\n", firmware_file);
+			dev_err(&slave->dev, "firmware %s not found\n", fw_name);
 			goto out;
 		}
 		if (fw->size < num_bytes) {
 			dev_err(&slave->dev,
 				"firmware %s: firmware size %zd, desired %zd\n",
-				firmware_file, fw->size, num_bytes);
+				fw_name, fw->size, num_bytes);
 			goto out;
 		}
 	}
@@ -306,6 +348,7 @@ static int cmd_go(void *data, u64 value)
 out:
 	if (fw)
 		release_firmware(fw);
+	kfree(fw_name);
 
 	pm_runtime_mark_last_busy(&slave->dev);
 	pm_runtime_put(&slave->dev);
@@ -358,7 +401,8 @@ void sdw_slave_debugfs_init(struct sdw_slave *slave)
 
 	debugfs_create_file("read_buffer", 0400, d, slave, &read_buffer_fops);
 	if (firmware_file)
-		debugfs_create_str("firmware_file", 0200, d, &firmware_file);
+		debugfs_create_file("firmware_file", 0200, d, NULL,
+				    &firmware_file_fops);
 
 	slave->debugfs = d;
 }
@@ -370,8 +414,10 @@ void sdw_slave_debugfs_exit(struct sdw_slave *slave)
 
 void sdw_debugfs_init(void)
 {
+	mutex_lock(&firmware_file_lock);
 	if (!firmware_file)
 		firmware_file = kstrdup("", GFP_KERNEL);
+	mutex_unlock(&firmware_file_lock);
 
 	sdw_debugfs_root = debugfs_create_dir("soundwire", NULL);
 }
@@ -379,6 +425,8 @@ void sdw_debugfs_init(void)
 void sdw_debugfs_exit(void)
 {
 	debugfs_remove_recursive(sdw_debugfs_root);
+	mutex_lock(&firmware_file_lock);
 	kfree(firmware_file);
 	firmware_file = NULL;
+	mutex_unlock(&firmware_file_lock);
 }
-- 
2.51.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2 3/3] debugfs: make debugfs_create_str() read-only
  2026-08-06  8:48 [PATCH v2 0/3] debugfs: make debugfs_create_str() read-only Yichong Chen
  2026-08-06  8:48 ` [PATCH v2 1/3] interconnect: debugfs: replace writable string helper Yichong Chen
  2026-08-06  8:48 ` [PATCH v2 2/3] soundwire: " Yichong Chen
@ 2026-08-06  8:48 ` Yichong Chen
  2026-08-06  9:02   ` Greg KH
  2 siblings, 1 reply; 8+ messages in thread
From: Yichong Chen @ 2026-08-06  8:48 UTC (permalink / raw)
  To: gregkh
  Cc: rafael, dakr, djakov, quic_mdtipton, vkoul, yung-chuan.liao,
	pierre-louis.bossart, driver-core, linux-kernel, linux-pm,
	linux-sound, Yichong Chen

debugfs_create_str() supports replacing the backing string from userspace.
Concurrent writers can race and free the same old string twice.

All writable in-tree users have been converted to local file operations.
Remove the generic write support from debugfs_create_str(), and refuse to
create a file when the caller passes write permission bits.

This makes unsupported writable use visible instead of silently creating a
file with different permissions.

Fixes: 86b5488121db ("debugfs: Add write support to debugfs_create_str()")
Signed-off-by: Yichong Chen <chenyichong@uniontech.com>
---
 fs/debugfs/file.c | 81 ++++++-----------------------------------------
 1 file changed, 10 insertions(+), 71 deletions(-)

diff --git a/fs/debugfs/file.c b/fs/debugfs/file.c
index 08de6652a4f3..f5abd067b260 100644
--- a/fs/debugfs/file.c
+++ b/fs/debugfs/file.c
@@ -1049,98 +1049,37 @@ ssize_t debugfs_read_file_str(struct file *file, char __user *user_buf,
 	return ret;
 }
 
-static ssize_t debugfs_write_file_str(struct file *file, const char __user *user_buf,
-				      size_t count, loff_t *ppos)
-{
-	struct dentry *dentry = F_DENTRY(file);
-	char *old, *new = NULL;
-	int pos = *ppos;
-	int r;
-
-	r = debugfs_file_get(dentry);
-	if (unlikely(r))
-		return r;
-
-	old = *(char **)file->private_data;
-
-	/* only allow strict concatenation */
-	r = -EINVAL;
-	if (pos && pos != strlen(old))
-		goto error;
-
-	r = -E2BIG;
-	if (pos + count + 1 > PAGE_SIZE)
-		goto error;
-
-	r = -ENOMEM;
-	new = kmalloc(pos + count + 1, GFP_KERNEL);
-	if (!new)
-		goto error;
-
-	if (pos)
-		memcpy(new, old, pos);
-
-	r = -EFAULT;
-	if (copy_from_user(new + pos, user_buf, count))
-		goto error;
-
-	new[pos + count] = '\0';
-	strim(new);
-
-	rcu_assign_pointer(*(char __rcu **)file->private_data, new);
-	synchronize_rcu();
-	kfree(old);
-
-	debugfs_file_put(dentry);
-	return count;
-
-error:
-	kfree(new);
-	debugfs_file_put(dentry);
-	return r;
-}
-
 static const struct file_operations fops_str = {
 	.read =		debugfs_read_file_str,
-	.write =	debugfs_write_file_str,
-	.open =		simple_open,
-	.llseek =	default_llseek,
-};
-
-static const struct file_operations fops_str_ro = {
-	.read =		debugfs_read_file_str,
-	.open =		simple_open,
-	.llseek =	default_llseek,
-};
-
-static const struct file_operations fops_str_wo = {
-	.write =	debugfs_write_file_str,
 	.open =		simple_open,
 	.llseek =	default_llseek,
 };
 
 /**
- * debugfs_create_str - create a debugfs file that is used to read and write a string value
+ * debugfs_create_str - create a debugfs file that is used to read a string value
  * @name: a pointer to a string containing the name of the file to create.
  * @mode: the permission that the file should have
  * @parent: a pointer to the parent dentry for this file.  This should be a
  *          directory dentry if set.  If this parameter is %NULL, then the
  *          file will be created in the root of the debugfs filesystem.
- * @value: a pointer to the variable that the file should read to and write
- *         from. This pointer and the string it points to must not be %NULL.
+ * @value: a pointer to the variable that the file should read from. This
+ *         pointer and the string it points to must not be %NULL.
  *
  * This function creates a file in debugfs with the given name that
- * contains the value of the variable @value.  If the @mode variable is so
- * set, it can be read from, and written to.
+ * contains the value of the variable @value.  The file can be read from.
+ * Writable files are not supported; if @mode contains write permission bits,
+ * no file is created.
  */
 void debugfs_create_str(const char *name, umode_t mode,
 			struct dentry *parent, char **value)
 {
 	if (WARN_ON(!value || !*value))
 		return;
+	if (WARN_ONCE(mode & 0222,
+		      "%s() does not support writable files\n", __func__))
+		return;
 
-	debugfs_create_mode_unsafe(name, mode, parent, value, &fops_str,
-				   &fops_str_ro, &fops_str_wo);
+	debugfs_create_file_unsafe(name, mode, parent, value, &fops_str);
 }
 EXPORT_SYMBOL_GPL(debugfs_create_str);
 
-- 
2.51.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 1/3] interconnect: debugfs: replace writable string helper
  2026-08-06  8:48 ` [PATCH v2 1/3] interconnect: debugfs: replace writable string helper Yichong Chen
@ 2026-08-06  9:01   ` Greg KH
  2026-08-07  1:59     ` Yichong Chen
  0 siblings, 1 reply; 8+ messages in thread
From: Greg KH @ 2026-08-06  9:01 UTC (permalink / raw)
  To: Yichong Chen
  Cc: rafael, dakr, djakov, quic_mdtipton, vkoul, yung-chuan.liao,
	pierre-louis.bossart, driver-core, linux-kernel, linux-pm,
	linux-sound

On Thu, Aug 06, 2026 at 04:48:52PM +0800, Yichong Chen wrote:
> debugfs_create_str() is being made read-only because its generic write path
> is hard to make safe without adding more locking to the helper.
> 
> Convert the interconnect debugfs client src_node and dst_node entries to
> local file operations before removing writable string support from
> debugfs_create_str().  Protect the string replacement and path lookup with
> the existing debugfs_lock.
> 
> Signed-off-by: Yichong Chen <chenyichong@uniontech.com>
> ---
>  drivers/interconnect/debugfs-client.c | 75 ++++++++++++++++++++++-----
>  1 file changed, 62 insertions(+), 13 deletions(-)
> 
> diff --git a/drivers/interconnect/debugfs-client.c b/drivers/interconnect/debugfs-client.c
> index 08df9188ef94..b71c25fd9314 100644
> --- a/drivers/interconnect/debugfs-client.c
> +++ b/drivers/interconnect/debugfs-client.c
> @@ -5,6 +5,7 @@
>  #include <linux/debugfs.h>
>  #include <linux/interconnect.h>
>  #include <linux/platform_device.h>
> +#include <linux/slab.h>
>  
>  #include "internal.h"
>  
> @@ -36,6 +37,59 @@ struct debugfs_path {
>  	struct list_head list;
>  };
>  
> +static ssize_t icc_node_read(struct file *file, char __user *user_buf,
> +			     size_t count, loff_t *ppos)
> +{
> +	char **node = file->private_data;
> +	char *copy;
> +	size_t len;
> +	ssize_t ret;
> +
> +	mutex_lock(&debugfs_lock);
> +	copy = kstrdup(*node ?: "", GFP_KERNEL);
> +	mutex_unlock(&debugfs_lock);

scoped guard?

> +	if (!copy)
> +		return -ENOMEM;
> +
> +	len = strlen(copy);
> +	copy[len++] = '\n';
> +	ret = simple_read_from_buffer(user_buf, count, ppos, copy, len);
> +	kfree(copy);
> +	return ret;
> +}
> +
> +static ssize_t icc_node_write(struct file *file, const char __user *user_buf,
> +			      size_t count, loff_t *ppos)
> +{
> +	char **node = file->private_data;
> +	char *old, *new;
> +
> +	if (*ppos)
> +		return -EINVAL;
> +	if (count + 1 > PAGE_SIZE)
> +		return -E2BIG;
> +
> +	new = memdup_user_nul(user_buf, count);
> +	if (IS_ERR(new))
> +		return PTR_ERR(new);
> +	strim(new);
> +
> +	mutex_lock(&debugfs_lock);
> +	old = *node;
> +	*node = new;
> +	mutex_unlock(&debugfs_lock);

Same here.

> +
> +	kfree(old);
> +	return count;
> +}
> +
> +static const struct file_operations icc_node_fops = {
> +	.open = simple_open,
> +	.read = icc_node_read,
> +	.write = icc_node_write,
> +	.llseek = default_llseek,
> +};
> +
>  static struct icc_path *get_path(const char *src, const char *dst)
>  {
>  	struct debugfs_path *path;
> @@ -56,24 +110,17 @@ static int icc_get_set(void *data, u64 val)
>  
>  	mutex_lock(&debugfs_lock);
>  
> -	rcu_read_lock();
> -	src = rcu_dereference(src_node);
> -	dst = rcu_dereference(dst_node);
> -
>  	/*
>  	 * If we've already looked up a path, then use the existing one instead
>  	 * of calling icc_get() again. This allows for updating previous BW
>  	 * votes when "get" is written to multiple times for multiple paths.
>  	 */
> -	cur_path = get_path(src, dst);
> -	if (cur_path) {
> -		rcu_read_unlock();
> +	cur_path = get_path(src_node, dst_node);
> +	if (cur_path)
>  		goto out;
> -	}
>  
> -	src = kstrdup(src, GFP_ATOMIC);
> -	dst = kstrdup(dst, GFP_ATOMIC);
> -	rcu_read_unlock();
> +	src = kstrdup(src_node, GFP_KERNEL);
> +	dst = kstrdup(dst_node, GFP_KERNEL);

Why is GFP_KERNEL now ok, while GFP_ATOMIC wasn't?  Is this the rcu
stuff interacting somehow?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 2/3] soundwire: debugfs: replace writable string helper
  2026-08-06  8:48 ` [PATCH v2 2/3] soundwire: " Yichong Chen
@ 2026-08-06  9:02   ` Greg KH
  0 siblings, 0 replies; 8+ messages in thread
From: Greg KH @ 2026-08-06  9:02 UTC (permalink / raw)
  To: Yichong Chen
  Cc: rafael, dakr, djakov, quic_mdtipton, vkoul, yung-chuan.liao,
	pierre-louis.bossart, driver-core, linux-kernel, linux-pm,
	linux-sound

On Thu, Aug 06, 2026 at 04:48:53PM +0800, Yichong Chen wrote:
> debugfs_create_str() is being made read-only because its generic write path
> is hard to make safe without adding more locking to the helper.
> 
> Convert the SoundWire firmware_file entry to a local write-only file
> operation before removing writable string support from
> debugfs_create_str().
> 
> Copy the firmware name before request_firmware() so later debugfs writes
> cannot replace the string while it is being used.
> 
> Signed-off-by: Yichong Chen <chenyichong@uniontech.com>
> ---
>  drivers/soundwire/debugfs.c | 56 ++++++++++++++++++++++++++++++++++---
>  1 file changed, 52 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/soundwire/debugfs.c b/drivers/soundwire/debugfs.c
> index 099eb84a548e..bba2b65a0668 100644
> --- a/drivers/soundwire/debugfs.c
> +++ b/drivers/soundwire/debugfs.c
> @@ -145,6 +145,38 @@ static u32 start_addr;
>  static size_t num_bytes;
>  static u8 read_buffer[MAX_CMD_BYTES];
>  static char *firmware_file;
> +static DEFINE_MUTEX(firmware_file_lock);
> +
> +static ssize_t firmware_file_write(struct file *file,
> +				   const char __user *user_buf,
> +				   size_t count, loff_t *ppos)
> +{
> +	char *old, *new;
> +
> +	if (*ppos)
> +		return -EINVAL;
> +	if (count + 1 > PAGE_SIZE)
> +		return -E2BIG;
> +
> +	new = memdup_user_nul(user_buf, count);
> +	if (IS_ERR(new))
> +		return PTR_ERR(new);
> +	strim(new);
> +
> +	mutex_lock(&firmware_file_lock);
> +	old = firmware_file;
> +	firmware_file = new;
> +	mutex_unlock(&firmware_file_lock);

scoped lock?

> +
> +	kfree(old);
> +	return count;
> +}
> +
> +static const struct file_operations firmware_file_fops = {
> +	.open = simple_open,
> +	.write = firmware_file_write,
> +	.llseek = default_llseek,
> +};
>  
>  static int set_command(void *data, u64 value)
>  {
> @@ -246,6 +278,7 @@ static int cmd_go(void *data, u64 value)
>  {
>  	const struct firmware *fw = NULL;
>  	struct sdw_slave *slave = data;
> +	char *fw_name = NULL;
>  	ktime_t start_t;
>  	ktime_t finish_t;
>  	int ret;
> @@ -265,15 +298,24 @@ static int cmd_go(void *data, u64 value)
>  	}
>  
>  	if (cmd == 0) {
> -		ret = request_firmware(&fw, firmware_file, &slave->dev);
> +		mutex_lock(&firmware_file_lock);

Again, scoped lock?

> +		if (firmware_file)
> +			fw_name = kstrdup(firmware_file, GFP_KERNEL);
> +		mutex_unlock(&firmware_file_lock);
> +		if (!fw_name) {
> +			ret = -ENOMEM;
> +			goto out;
> +		}
> +
> +		ret = request_firmware(&fw, fw_name, &slave->dev);
>  		if (ret < 0) {
> -			dev_err(&slave->dev, "firmware %s not found\n", firmware_file);
> +			dev_err(&slave->dev, "firmware %s not found\n", fw_name);
>  			goto out;
>  		}
>  		if (fw->size < num_bytes) {
>  			dev_err(&slave->dev,
>  				"firmware %s: firmware size %zd, desired %zd\n",
> -				firmware_file, fw->size, num_bytes);
> +				fw_name, fw->size, num_bytes);
>  			goto out;
>  		}
>  	}
> @@ -306,6 +348,7 @@ static int cmd_go(void *data, u64 value)
>  out:
>  	if (fw)
>  		release_firmware(fw);
> +	kfree(fw_name);
>  
>  	pm_runtime_mark_last_busy(&slave->dev);
>  	pm_runtime_put(&slave->dev);
> @@ -358,7 +401,8 @@ void sdw_slave_debugfs_init(struct sdw_slave *slave)
>  
>  	debugfs_create_file("read_buffer", 0400, d, slave, &read_buffer_fops);
>  	if (firmware_file)
> -		debugfs_create_str("firmware_file", 0200, d, &firmware_file);
> +		debugfs_create_file("firmware_file", 0200, d, NULL,
> +				    &firmware_file_fops);
>  
>  	slave->debugfs = d;
>  }
> @@ -370,8 +414,10 @@ void sdw_slave_debugfs_exit(struct sdw_slave *slave)
>  
>  void sdw_debugfs_init(void)
>  {
> +	mutex_lock(&firmware_file_lock);
>  	if (!firmware_file)
>  		firmware_file = kstrdup("", GFP_KERNEL);
> +	mutex_unlock(&firmware_file_lock);

Same here.

>  
>  	sdw_debugfs_root = debugfs_create_dir("soundwire", NULL);
>  }
> @@ -379,6 +425,8 @@ void sdw_debugfs_init(void)
>  void sdw_debugfs_exit(void)
>  {
>  	debugfs_remove_recursive(sdw_debugfs_root);
> +	mutex_lock(&firmware_file_lock);

And here.

>  	kfree(firmware_file);
>  	firmware_file = NULL;

This doesn't need to get set to NULL, right?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 3/3] debugfs: make debugfs_create_str() read-only
  2026-08-06  8:48 ` [PATCH v2 3/3] debugfs: make debugfs_create_str() read-only Yichong Chen
@ 2026-08-06  9:02   ` Greg KH
  0 siblings, 0 replies; 8+ messages in thread
From: Greg KH @ 2026-08-06  9:02 UTC (permalink / raw)
  To: Yichong Chen
  Cc: rafael, dakr, djakov, quic_mdtipton, vkoul, yung-chuan.liao,
	pierre-louis.bossart, driver-core, linux-kernel, linux-pm,
	linux-sound

On Thu, Aug 06, 2026 at 04:48:54PM +0800, Yichong Chen wrote:
> debugfs_create_str() supports replacing the backing string from userspace.
> Concurrent writers can race and free the same old string twice.
> 
> All writable in-tree users have been converted to local file operations.
> Remove the generic write support from debugfs_create_str(), and refuse to
> create a file when the caller passes write permission bits.
> 
> This makes unsupported writable use visible instead of silently creating a
> file with different permissions.
> 
> Fixes: 86b5488121db ("debugfs: Add write support to debugfs_create_str()")
> Signed-off-by: Yichong Chen <chenyichong@uniontech.com>
> ---
>  fs/debugfs/file.c | 81 ++++++-----------------------------------------
>  1 file changed, 10 insertions(+), 71 deletions(-)
> 
> diff --git a/fs/debugfs/file.c b/fs/debugfs/file.c
> index 08de6652a4f3..f5abd067b260 100644
> --- a/fs/debugfs/file.c
> +++ b/fs/debugfs/file.c
> @@ -1049,98 +1049,37 @@ ssize_t debugfs_read_file_str(struct file *file, char __user *user_buf,
>  	return ret;
>  }
>  
> -static ssize_t debugfs_write_file_str(struct file *file, const char __user *user_buf,
> -				      size_t count, loff_t *ppos)
> -{
> -	struct dentry *dentry = F_DENTRY(file);
> -	char *old, *new = NULL;
> -	int pos = *ppos;
> -	int r;
> -
> -	r = debugfs_file_get(dentry);
> -	if (unlikely(r))
> -		return r;
> -
> -	old = *(char **)file->private_data;
> -
> -	/* only allow strict concatenation */
> -	r = -EINVAL;
> -	if (pos && pos != strlen(old))
> -		goto error;
> -
> -	r = -E2BIG;
> -	if (pos + count + 1 > PAGE_SIZE)
> -		goto error;
> -
> -	r = -ENOMEM;
> -	new = kmalloc(pos + count + 1, GFP_KERNEL);
> -	if (!new)
> -		goto error;
> -
> -	if (pos)
> -		memcpy(new, old, pos);
> -
> -	r = -EFAULT;
> -	if (copy_from_user(new + pos, user_buf, count))
> -		goto error;
> -
> -	new[pos + count] = '\0';
> -	strim(new);
> -
> -	rcu_assign_pointer(*(char __rcu **)file->private_data, new);
> -	synchronize_rcu();
> -	kfree(old);
> -
> -	debugfs_file_put(dentry);
> -	return count;
> -
> -error:
> -	kfree(new);
> -	debugfs_file_put(dentry);
> -	return r;
> -}
> -
>  static const struct file_operations fops_str = {
>  	.read =		debugfs_read_file_str,
> -	.write =	debugfs_write_file_str,
> -	.open =		simple_open,
> -	.llseek =	default_llseek,
> -};
> -
> -static const struct file_operations fops_str_ro = {
> -	.read =		debugfs_read_file_str,
> -	.open =		simple_open,
> -	.llseek =	default_llseek,
> -};
> -
> -static const struct file_operations fops_str_wo = {
> -	.write =	debugfs_write_file_str,
>  	.open =		simple_open,
>  	.llseek =	default_llseek,
>  };
>  
>  /**
> - * debugfs_create_str - create a debugfs file that is used to read and write a string value
> + * debugfs_create_str - create a debugfs file that is used to read a string value
>   * @name: a pointer to a string containing the name of the file to create.
>   * @mode: the permission that the file should have
>   * @parent: a pointer to the parent dentry for this file.  This should be a
>   *          directory dentry if set.  If this parameter is %NULL, then the
>   *          file will be created in the root of the debugfs filesystem.
> - * @value: a pointer to the variable that the file should read to and write
> - *         from. This pointer and the string it points to must not be %NULL.
> + * @value: a pointer to the variable that the file should read from. This
> + *         pointer and the string it points to must not be %NULL.
>   *
>   * This function creates a file in debugfs with the given name that
> - * contains the value of the variable @value.  If the @mode variable is so
> - * set, it can be read from, and written to.
> + * contains the value of the variable @value.  The file can be read from.
> + * Writable files are not supported; if @mode contains write permission bits,
> + * no file is created.
>   */
>  void debugfs_create_str(const char *name, umode_t mode,
>  			struct dentry *parent, char **value)
>  {
>  	if (WARN_ON(!value || !*value))
>  		return;
> +	if (WARN_ONCE(mode & 0222,
> +		      "%s() does not support writable files\n", __func__))

Why WARN_ONCE()?  multiple callers could be hitting this.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2 1/3] interconnect: debugfs: replace writable string helper
  2026-08-06  9:01   ` Greg KH
@ 2026-08-07  1:59     ` Yichong Chen
  0 siblings, 0 replies; 8+ messages in thread
From: Yichong Chen @ 2026-08-07  1:59 UTC (permalink / raw)
  To: gregkh
  Cc: chenyichong, dakr, djakov, driver-core, linux-kernel, linux-pm,
	linux-sound, pierre-louis.bossart, quic_mdtipton, rafael, vkoul,
	yung-chuan.liao

On Thu, Aug 06, 2026 at 11:01:07AM +0200, Greg KH wrote:
> On Thu, Aug 06, 2026 at 04:48:52PM +0800, Yichong Chen wrote:
> > +	mutex_lock(&debugfs_lock);
> > +	copy = kstrdup(*node ?: "", GFP_KERNEL);
> > +	mutex_unlock(&debugfs_lock);
> 
> scoped guard?

Yes, scoped guard would be cleaner here.  I can use it in the next version.

> > -	src = kstrdup(src, GFP_ATOMIC);
> > -	dst = kstrdup(dst, GFP_ATOMIC);
> > -	rcu_read_unlock();
> > +	src = kstrdup(src_node, GFP_KERNEL);
> > +	dst = kstrdup(dst_node, GFP_KERNEL);
> 
> Why is GFP_KERNEL now ok, while GFP_ATOMIC wasn't?  Is this the rcu
> stuff interacting somehow?

Yes.  The old code duplicated the strings while still inside the RCU
read-side critical section, so it had to use GFP_ATOMIC.

After this change, src_node and dst_node are protected by debugfs_lock
instead of RCU.  The duplication is done while holding that mutex, so the
allocation can sleep and GFP_KERNEL should be OK.

I will make this clearer in the changelog when sending the next version.

Thanks,
Yichong

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-08-07  2:01 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-06  8:48 [PATCH v2 0/3] debugfs: make debugfs_create_str() read-only Yichong Chen
2026-08-06  8:48 ` [PATCH v2 1/3] interconnect: debugfs: replace writable string helper Yichong Chen
2026-08-06  9:01   ` Greg KH
2026-08-07  1:59     ` Yichong Chen
2026-08-06  8:48 ` [PATCH v2 2/3] soundwire: " Yichong Chen
2026-08-06  9:02   ` Greg KH
2026-08-06  8:48 ` [PATCH v2 3/3] debugfs: make debugfs_create_str() read-only Yichong Chen
2026-08-06  9:02   ` Greg KH

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox