[PATCH v4 1/2] debugfs: fix use-after-free in debugfs_read_file_str()

From: Aldo Ariel Panzardo

Date: Sat Sep 26 2026 - 15:59:36 EST


debugfs_write_file_str() publishes a new string pointer via
rcu_assign_pointer(), waits for a grace period with synchronize_rcu(),
then frees the old string.

debugfs_read_file_str() dereferences file->private_data without holding
an RCU read-side critical section: it loads the pointer, calls strlen()
on it, and then copies the string. If a concurrent writer completes
synchronize_rcu() and kfree()s the old string between the load and the
use, the reader accesses freed memory.

Fix by measuring the string length under rcu_read_lock(), allocating
with GFP_KERNEL outside the critical section, and then copying with
strscpy() under a second rcu_read_lock(). If the current string no
longer fits the allocated buffer, retry with a PAGE_SIZE allocation
which is the upper bound enforced by the write path.

Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: sashiko.dev <sashiko-bot@xxxxxxxxxx>
Signed-off-by: Aldo Ariel Panzardo <qwe.aldo@xxxxxxxxx>
---
fs/debugfs/file.c | 39 +++++++++++++++++++++++++--------------
1 file changed, 25 insertions(+), 14 deletions(-)

diff --git a/fs/debugfs/file.c b/fs/debugfs/file.c
index 08de6652a..f9a1f7739 100644
--- a/fs/debugfs/file.c
+++ b/fs/debugfs/file.c
@@ -1018,7 +1018,7 @@ ssize_t debugfs_read_file_str(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
struct dentry *dentry = F_DENTRY(file);
- char *str, *copy = NULL;
+ char *str, *copy;
int copy_len, len;
ssize_t ret;

@@ -1026,26 +1026,37 @@ ssize_t debugfs_read_file_str(struct file *file, char __user *user_buf,
if (unlikely(ret))
return ret;

- str = *(char **)file->private_data;
- len = strlen(str) + 1;
- copy = kmalloc(len, GFP_KERNEL);
- if (!copy) {
- debugfs_file_put(dentry);
- return -ENOMEM;
- }
+ len = 0;
+ for (;;) {
+ rcu_read_lock();
+ str = rcu_dereference(*(char __rcu **)file->private_data);
+ len = max_t(int, strlen(str) + 1, len);
+ rcu_read_unlock();
+
+ copy = kmalloc(len, GFP_KERNEL);
+ if (!copy) {
+ debugfs_file_put(dentry);
+ return -ENOMEM;
+ }
+
+ rcu_read_lock();
+ str = rcu_dereference(*(char __rcu **)file->private_data);
+ copy_len = strscpy(copy, str, len);
+ rcu_read_unlock();
+
+ if (copy_len >= 0)
+ break;

- copy_len = strscpy(copy, str, len);
- debugfs_file_put(dentry);
- if (copy_len < 0) {
kfree(copy);
- return copy_len;
+ len = PAGE_SIZE;
}

- copy[copy_len] = '\n';
+ debugfs_file_put(dentry);

+ copy[copy_len] = '\n';
+ len = copy_len + 1;
ret = simple_read_from_buffer(user_buf, count, ppos, copy, len);
kfree(copy);
-
return ret;
}

--
2.43.0