* [PATCH] mm/damon/sysfs-schemes: fix use-after-free on memcg_path and goal path
@ 2026-04-20 8:53 Junxi Qian
2026-04-20 12:54 ` [PATCH v2] " Junxi Qian
2026-04-21 7:06 ` [PATCH] " Junxi Qian
0 siblings, 2 replies; 5+ messages in thread
From: Junxi Qian @ 2026-04-20 8:53 UTC (permalink / raw)
To: linux-mm; +Cc: sj, akpm, damon, linux-kernel
memcg_path_store() and path_store() free and replace their respective
string pointers without holding damon_sysfs_lock. This creates a race
with the kdamond thread, which reads these pointers in
damon_sysfs_add_scheme_filters() and damos_sysfs_add_quota_score()
via damon_call() during a "commit" operation.
The race window is as follows:
Thread A (commit): holds damon_sysfs_lock, triggers damon_call()
kdamond thread: reads sysfs_filter->memcg_path in
damon_sysfs_memcg_path_to_id()
Thread B (sysfs): calls memcg_path_store() WITHOUT lock,
kfree(filter->memcg_path), replaces pointer
Since Thread B does not hold damon_sysfs_lock, the kdamond thread can
observe a freed pointer, resulting in a use-after-free when
damon_sysfs_memcg_path_eq() dereferences it for string comparison.
KASAN reports the following on v7.0.0-rc5 with CONFIG_KASAN=y:
==================================================================
BUG: KASAN: double-free in memcg_path_store+0x6e/0xc0
Free of addr ffff888100a086c0 by task exp/149
CPU: 1 UID: 0 PID: 149 Comm: exp Not tainted 7.0.0-rc5-gd38efd7c139a #18 PREEMPT(lazy)
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0x55/0x70
print_report+0xcb/0x5d0
kasan_report_invalid_free+0x94/0xc0
check_slab_allocation+0xde/0x110
kfree+0x114/0x3b0
memcg_path_store+0x6e/0xc0
kernfs_fop_write_iter+0x2fc/0x490
vfs_write+0x8e1/0xcc0
ksys_write+0xee/0x1c0
do_syscall_64+0xfc/0x580
entry_SYSCALL_64_after_hwframe+0x77/0x7f
</TASK>
Allocated by task 147 on cpu 0 at 12.364295s:
kasan_save_stack+0x24/0x50
kasan_save_track+0x17/0x60
__kasan_kmalloc+0x7f/0x90
__kmalloc_noprof+0x191/0x490
memcg_path_store+0x32/0xc0
kernfs_fop_write_iter+0x2fc/0x490
vfs_write+0x8e1/0xcc0
ksys_write+0xee/0x1c0
Freed by task 150 on cpu 0 at 13.373810s:
kasan_save_stack+0x24/0x50
kasan_save_track+0x17/0x60
kasan_save_free_info+0x3b/0x60
__kasan_slab_free+0x43/0x70
kfree+0x137/0x3b0
memcg_path_store+0x6e/0xc0
kernfs_fop_write_iter+0x2fc/0x490
vfs_write+0x8e1/0xcc0
ksys_write+0xee/0x1c0
The buggy address belongs to the object at ffff888100a086c0
which belongs to the cache kmalloc-8 of size 8
==================================================================
Fix this by holding damon_sysfs_lock while swapping the path pointer
in both memcg_path_store() and path_store(). The actual kfree() is
moved outside the lock since the old pointer is no longer reachable
once replaced.
Fixes: 490a43d07f16 ("mm/damon/sysfs-schemes: free old damon_sysfs_scheme_filter->memcg_path on write")
Signed-off-by: Junxi Qian <qjx1298677004@gmail.com>
---
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <errno.h>
#include <sys/stat.h>
#define DAMON_BASE "/sys/kernel/mm/damon/admin"
static volatile int stop;
static char memcg_path_file[1024], state_file[1024];
static char real_memcg_path[512] = "/";
static int num_extra_cgroups = 200;
static char cgroup_base[256];
static int write_str(const char *path, const char *val) {
int fd = open(path, O_WRONLY);
if (fd < 0) return -errno;
ssize_t r = write(fd, val, strlen(val));
int e = errno; close(fd);
return r > 0 ? 0 : -e;
}
static int create_extra_cgroups(void) {
struct stat st; char path[512]; int created = 0;
if (stat("/sys/fs/cgroup/cgroup.controllers", &st) == 0)
strncpy(cgroup_base, "/sys/fs/cgroup", sizeof(cgroup_base));
else if (stat("/sys/fs/cgroup/memory", &st) == 0)
strncpy(cgroup_base, "/sys/fs/cgroup/memory", sizeof(cgroup_base));
else return 0;
for (int i = 0; i < num_extra_cgroups; i++) {
snprintf(path, sizeof(path), "%s/damon_poc_%d", cgroup_base, i);
if (mkdir(path, 0755) == 0) created++;
}
if (created > 0)
snprintf(real_memcg_path, sizeof(real_memcg_path),
"%s/damon_poc_0", cgroup_base);
return created;
}
static void cleanup_extra_cgroups(void) {
char path[512];
if (!cgroup_base[0]) return;
for (int i = 0; i < num_extra_cgroups; i++) {
snprintf(path, sizeof(path), "%s/damon_poc_%d", cgroup_base, i);
rmdir(path);
}
}
static void *path_writer(void *arg) {
long tid = (long)arg; int i = 0; char buf[512];
while (!stop) {
switch (i % 4) {
case 0: snprintf(buf,sizeof(buf),"%s",real_memcg_path); break;
case 1: snprintf(buf,sizeof(buf),
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_%d_%ld",i,tid); break;
case 2: snprintf(buf,sizeof(buf),"YYYYYYYYYYYYYY_%d",i); break;
case 3: snprintf(buf,sizeof(buf),"Z_%d",i); break;
}
write_str(memcg_path_file, buf); i++;
}
return NULL;
}
static void *committer(void *arg) {
while (!stop) {
if (write_str(state_file, "commit") == 0) usleep(10);
}
return NULL;
}
static int setup_damon(void) {
char path[1024]; int err;
snprintf(path,sizeof(path),"%s/kdamonds/nr_kdamonds",DAMON_BASE);
write_str(path,"1");
snprintf(path,sizeof(path),
"%s/kdamonds/0/contexts/nr_contexts",DAMON_BASE);
write_str(path,"1");
snprintf(path,sizeof(path),
"%s/kdamonds/0/contexts/0/operations",DAMON_BASE);
if (write_str(path,"paddr")) {
write_str(path,"vaddr");
snprintf(path,sizeof(path),
"%s/kdamonds/0/contexts/0/targets/nr_targets",DAMON_BASE);
write_str(path,"1");
char pid[32]; snprintf(pid,sizeof(pid),"%d",getpid());
snprintf(path,sizeof(path),
"%s/kdamonds/0/contexts/0/targets/0/pid_target",DAMON_BASE);
write_str(path,pid);
}
snprintf(path,sizeof(path),
"%s/kdamonds/0/contexts/0/schemes/nr_schemes",DAMON_BASE);
write_str(path,"1");
snprintf(path,sizeof(path),
"%s/kdamonds/0/contexts/0/schemes/0/action",DAMON_BASE);
write_str(path,"stat");
snprintf(state_file,sizeof(state_file),
"%s/kdamonds/0/state",DAMON_BASE);
if ((err = write_str(state_file,"on"))) return err;
snprintf(path,sizeof(path),
"%s/kdamonds/0/contexts/0/schemes/0/filters/nr_filters",DAMON_BASE);
write_str(path,"1");
snprintf(path,sizeof(path),
"%s/kdamonds/0/contexts/0/schemes/0/filters/0/type",DAMON_BASE);
write_str(path,"memcg");
snprintf(memcg_path_file,sizeof(memcg_path_file),
"%s/kdamonds/0/contexts/0/schemes/0/filters/0/memcg_path",
DAMON_BASE);
write_str(memcg_path_file, real_memcg_path);
return 0;
}
int main(int argc, char *argv[]) {
int duration = 60, n_writers = 4;
if (argc > 1) duration = atoi(argv[1]);
if (argc > 2) num_extra_cgroups = atoi(argv[2]);
if (getuid()) { fprintf(stderr,"Need root\n"); return 1; }
create_extra_cgroups();
if (setup_damon()) { cleanup_extra_cgroups(); return 1; }
sleep(1);
pthread_t w[8], c;
for (int i = 0; i < n_writers; i++)
pthread_create(&w[i],NULL,path_writer,(void*)(long)i);
pthread_create(&c,NULL,committer,NULL);
sleep(duration);
stop = 1;
for (int i = 0; i < n_writers; i++) pthread_join(w[i],NULL);
pthread_join(c,NULL);
write_str(state_file,"off");
cleanup_extra_cgroups();
printf("Check: dmesg | grep -A20 'BUG: KASAN'\n");
}
---
mm/damon/sysfs-schemes.c | 22 ++++++++++++++++------
1 file changed, 16 insertions(+), 6 deletions(-)
diff --git a/mm/damon/sysfs-schemes.c b/mm/damon/sysfs-schemes.c
index 5186966da..005cfecb1 100644
--- a/mm/damon/sysfs-schemes.c
+++ b/mm/damon/sysfs-schemes.c
@@ -543,15 +543,20 @@ static ssize_t memcg_path_store(struct kobject *kobj,
{
struct damon_sysfs_scheme_filter *filter = container_of(kobj,
struct damon_sysfs_scheme_filter, kobj);
- char *path = kmalloc_array(size_add(count, 1), sizeof(*path),
- GFP_KERNEL);
+ char *path, *old;
+ path = kmalloc_array(size_add(count, 1), sizeof(*path), GFP_KERNEL);
if (!path)
return -ENOMEM;
strscpy(path, buf, count + 1);
- kfree(filter->memcg_path);
+
+ mutex_lock(&damon_sysfs_lock);
+ old = filter->memcg_path;
filter->memcg_path = path;
+ mutex_unlock(&damon_sysfs_lock);
+
+ kfree(old);
return count;
}
@@ -1196,15 +1201,20 @@ static ssize_t path_store(struct kobject *kobj,
{
struct damos_sysfs_quota_goal *goal = container_of(kobj,
struct damos_sysfs_quota_goal, kobj);
- char *path = kmalloc_array(size_add(count, 1), sizeof(*path),
- GFP_KERNEL);
+ char *path, *old;
+ path = kmalloc_array(size_add(count, 1), sizeof(*path), GFP_KERNEL);
if (!path)
return -ENOMEM;
strscpy(path, buf, count + 1);
- kfree(goal->path);
+
+ mutex_lock(&damon_sysfs_lock);
+ old = goal->path;
goal->path = path;
+ mutex_unlock(&damon_sysfs_lock);
+
+ kfree(old);
return count;
}
--
2.34.1
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v2] mm/damon/sysfs-schemes: fix use-after-free on memcg_path and goal path
2026-04-20 8:53 [PATCH] mm/damon/sysfs-schemes: fix use-after-free on memcg_path and goal path Junxi Qian
@ 2026-04-20 12:54 ` Junxi Qian
2026-04-21 1:20 ` SeongJae Park
2026-04-21 7:06 ` [PATCH] " Junxi Qian
1 sibling, 1 reply; 5+ messages in thread
From: Junxi Qian @ 2026-04-20 12:54 UTC (permalink / raw)
To: linux-mm; +Cc: sj, akpm, damon, linux-kernel
memcg_path_store() and path_store() free and replace their respective
string pointers without holding damon_sysfs_lock. This creates a race
with the kdamond thread, which reads these pointers in
damon_sysfs_add_scheme_filters() and damos_sysfs_add_quota_score()
via damon_call() during a "commit" operation.
The race window is as follows:
Thread A (commit): holds damon_sysfs_lock, triggers damon_call()
kdamond thread: reads sysfs_filter->memcg_path in
damon_sysfs_memcg_path_to_id()
Thread B (sysfs): calls memcg_path_store() WITHOUT lock,
kfree(filter->memcg_path), replaces pointer
Since Thread B does not hold damon_sysfs_lock, the kdamond thread can
observe a freed pointer, resulting in a use-after-free when
damon_sysfs_memcg_path_eq() dereferences it for string comparison.
Similarly, memcg_path_show() and path_show() read the string pointers
without holding the lock, so a concurrent store can free the string
just before sysfs_emit() dereferences it, causing a use-after-free.
KASAN reports the following on v7.0.0-rc5 with CONFIG_KASAN=y:
==================================================================
BUG: KASAN: double-free in memcg_path_store+0x6e/0xc0
Free of addr ffff888100a086c0 by task exp/149
CPU: 1 UID: 0 PID: 149 Comm: exp Not tainted 7.0.0-rc5-gd38efd7c139a #18 PREEMPT(lazy)
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0x55/0x70
print_report+0xcb/0x5d0
kasan_report_invalid_free+0x94/0xc0
check_slab_allocation+0xde/0x110
kfree+0x114/0x3b0
memcg_path_store+0x6e/0xc0
kernfs_fop_write_iter+0x2fc/0x490
vfs_write+0x8e1/0xcc0
ksys_write+0xee/0x1c0
do_syscall_64+0xfc/0x580
entry_SYSCALL_64_after_hwframe+0x77/0x7f
</TASK>
Allocated by task 147 on cpu 0 at 12.364295s:
kasan_save_stack+0x24/0x50
kasan_save_track+0x17/0x60
__kasan_kmalloc+0x7f/0x90
__kmalloc_noprof+0x191/0x490
memcg_path_store+0x32/0xc0
kernfs_fop_write_iter+0x2fc/0x490
vfs_write+0x8e1/0xcc0
ksys_write+0xee/0x1c0
Freed by task 150 on cpu 0 at 13.373810s:
kasan_save_stack+0x24/0x50
kasan_save_track+0x17/0x60
kasan_save_free_info+0x3b/0x60
__kasan_slab_free+0x43/0x70
kfree+0x137/0x3b0
memcg_path_store+0x6e/0xc0
kernfs_fop_write_iter+0x2fc/0x490
vfs_write+0x8e1/0xcc0
ksys_write+0xee/0x1c0
The buggy address belongs to the object at ffff888100a086c0
which belongs to the cache kmalloc-8 of size 8
==================================================================
Fix this by holding damon_sysfs_lock while swapping the path pointer
in both memcg_path_store() and path_store(), and while reading the
path pointer in memcg_path_show() and path_show(). The actual kfree()
is moved outside the lock since the old pointer is no longer reachable
once replaced.
Fixes: 490a43d07f16 ("mm/damon/sysfs-schemes: free old damon_sysfs_scheme_filter->memcg_path on write")
Signed-off-by: Junxi Qian <qjx1298677004@gmail.com>
---
Changes in v2:
- Also protect memcg_path_show() and path_show() with damon_sysfs_lock,
since sysfs show and store callbacks can run concurrently and a
lockless read in show can race with the kfree in store. (Sashiko AI)
mm/damon/sysfs-schemes.c | 36 ++++++++++++++++++++++++++++--------
1 file changed, 28 insertions(+), 8 deletions(-)
diff --git a/mm/damon/sysfs-schemes.c b/mm/damon/sysfs-schemes.c
index 5186966da..1a890e2a4 100644
--- a/mm/damon/sysfs-schemes.c
+++ b/mm/damon/sysfs-schemes.c
@@ -533,9 +533,14 @@ static ssize_t memcg_path_show(struct kobject *kobj,
{
struct damon_sysfs_scheme_filter *filter = container_of(kobj,
struct damon_sysfs_scheme_filter, kobj);
+ ssize_t len;
- return sysfs_emit(buf, "%s\n",
+ mutex_lock(&damon_sysfs_lock);
+ len = sysfs_emit(buf, "%s\n",
filter->memcg_path ? filter->memcg_path : "");
+ mutex_unlock(&damon_sysfs_lock);
+
+ return len;
}
static ssize_t memcg_path_store(struct kobject *kobj,
@@ -543,15 +548,20 @@ static ssize_t memcg_path_store(struct kobject *kobj,
{
struct damon_sysfs_scheme_filter *filter = container_of(kobj,
struct damon_sysfs_scheme_filter, kobj);
- char *path = kmalloc_array(size_add(count, 1), sizeof(*path),
- GFP_KERNEL);
+ char *path, *old;
+ path = kmalloc_array(size_add(count, 1), sizeof(*path), GFP_KERNEL);
if (!path)
return -ENOMEM;
strscpy(path, buf, count + 1);
- kfree(filter->memcg_path);
+
+ mutex_lock(&damon_sysfs_lock);
+ old = filter->memcg_path;
filter->memcg_path = path;
+ mutex_unlock(&damon_sysfs_lock);
+
+ kfree(old);
return count;
}
@@ -1187,8 +1197,13 @@ static ssize_t path_show(struct kobject *kobj,
{
struct damos_sysfs_quota_goal *goal = container_of(kobj,
struct damos_sysfs_quota_goal, kobj);
+ ssize_t len;
- return sysfs_emit(buf, "%s\n", goal->path ? goal->path : "");
+ mutex_lock(&damon_sysfs_lock);
+ len = sysfs_emit(buf, "%s\n", goal->path ? goal->path : "");
+ mutex_unlock(&damon_sysfs_lock);
+
+ return len;
}
static ssize_t path_store(struct kobject *kobj,
@@ -1196,15 +1211,20 @@ static ssize_t path_store(struct kobject *kobj,
{
struct damos_sysfs_quota_goal *goal = container_of(kobj,
struct damos_sysfs_quota_goal, kobj);
- char *path = kmalloc_array(size_add(count, 1), sizeof(*path),
- GFP_KERNEL);
+ char *path, *old;
+ path = kmalloc_array(size_add(count, 1), sizeof(*path), GFP_KERNEL);
if (!path)
return -ENOMEM;
strscpy(path, buf, count + 1);
- kfree(goal->path);
+
+ mutex_lock(&damon_sysfs_lock);
+ old = goal->path;
goal->path = path;
+ mutex_unlock(&damon_sysfs_lock);
+
+ kfree(old);
return count;
}
--
2.34.1
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v2] mm/damon/sysfs-schemes: fix use-after-free on memcg_path and goal path
2026-04-20 12:54 ` [PATCH v2] " Junxi Qian
@ 2026-04-21 1:20 ` SeongJae Park
2026-04-21 1:23 ` SeongJae Park
0 siblings, 1 reply; 5+ messages in thread
From: SeongJae Park @ 2026-04-21 1:20 UTC (permalink / raw)
To: Junxi Qian; +Cc: SeongJae Park, linux-mm, akpm, damon, linux-kernel
Hello Junxi,
When you send a new version of a patch, please send it as a new mail with
changes log [1], rather than as a reply to the old version.
On Mon, 20 Apr 2026 20:54:05 +0800 Junxi Qian <qjx1298677004@gmail.com> wrote:
> memcg_path_store() and path_store() free and replace their respective
> string pointers without holding damon_sysfs_lock. This creates a race
> with the kdamond thread, which reads these pointers in
> damon_sysfs_add_scheme_filters() and damos_sysfs_add_quota_score()
> via damon_call() during a "commit" operation.
>
> The race window is as follows:
> Thread A (commit): holds damon_sysfs_lock, triggers damon_call()
> kdamond thread: reads sysfs_filter->memcg_path in
> damon_sysfs_memcg_path_to_id()
> Thread B (sysfs): calls memcg_path_store() WITHOUT lock,
> kfree(filter->memcg_path), replaces pointer
>
> Since Thread B does not hold damon_sysfs_lock, the kdamond thread can
> observe a freed pointer, resulting in a use-after-free when
> damon_sysfs_memcg_path_eq() dereferences it for string comparison.
Thank you for finding and sharing this bug!
>
> Similarly, memcg_path_show() and path_show() read the string pointers
> without holding the lock, so a concurrent store can free the string
> just before sysfs_emit() dereferences it, causing a use-after-free.
Is this correct? Isn't sysfs prohibiting such concurrent read/write using
kernfs_open_file->mutex? Were you able to trigger this?
>
> KASAN reports the following on v7.0.0-rc5 with CONFIG_KASAN=y:
>
> ==================================================================
> BUG: KASAN: double-free in memcg_path_store+0x6e/0xc0
>
> Free of addr ffff888100a086c0 by task exp/149
>
> CPU: 1 UID: 0 PID: 149 Comm: exp Not tainted 7.0.0-rc5-gd38efd7c139a #18 PREEMPT(lazy)
> Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
> Call Trace:
> <TASK>
> dump_stack_lvl+0x55/0x70
> print_report+0xcb/0x5d0
> kasan_report_invalid_free+0x94/0xc0
> check_slab_allocation+0xde/0x110
> kfree+0x114/0x3b0
> memcg_path_store+0x6e/0xc0
> kernfs_fop_write_iter+0x2fc/0x490
> vfs_write+0x8e1/0xcc0
> ksys_write+0xee/0x1c0
> do_syscall_64+0xfc/0x580
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
> </TASK>
>
> Allocated by task 147 on cpu 0 at 12.364295s:
> kasan_save_stack+0x24/0x50
> kasan_save_track+0x17/0x60
> __kasan_kmalloc+0x7f/0x90
> __kmalloc_noprof+0x191/0x490
> memcg_path_store+0x32/0xc0
> kernfs_fop_write_iter+0x2fc/0x490
> vfs_write+0x8e1/0xcc0
> ksys_write+0xee/0x1c0
>
> Freed by task 150 on cpu 0 at 13.373810s:
> kasan_save_stack+0x24/0x50
> kasan_save_track+0x17/0x60
> kasan_save_free_info+0x3b/0x60
> __kasan_slab_free+0x43/0x70
> kfree+0x137/0x3b0
> memcg_path_store+0x6e/0xc0
> kernfs_fop_write_iter+0x2fc/0x490
> vfs_write+0x8e1/0xcc0
> ksys_write+0xee/0x1c0
>
> The buggy address belongs to the object at ffff888100a086c0
> which belongs to the cache kmalloc-8 of size 8
> ==================================================================
>
> Fix this by holding damon_sysfs_lock while swapping the path pointer
> in both memcg_path_store() and path_store(),
I'd like to avoid use of the lock in long term. But this sounds good and
simple for hotfixes.
> and while reading the
> path pointer in memcg_path_show() and path_show().
As I commented above, I'm not sure if this is really needed.
> The actual kfree()
> is moved outside the lock since the old pointer is no longer reachable
> once replaced.
>
> Fixes: 490a43d07f16 ("mm/damon/sysfs-schemes: free old damon_sysfs_scheme_filter->memcg_path on write")
I think this deserves Cc-ing stable@.
Also, this patch is fixing two bugs. For making backport of the fixes easy,
could we split this patch into two patches, one for memcg_path_{show,store}(),
and the other one for path_{show,store}()?
> Signed-off-by: Junxi Qian <qjx1298677004@gmail.com>
>
> ---
> Changes in v2:
> - Also protect memcg_path_show() and path_show() with damon_sysfs_lock,
> since sysfs show and store callbacks can run concurrently and a
> lockless read in show can race with the kfree in store. (Sashiko AI)
As I abovely commented, I'm not sure that Sashiko comment is correct.
>
> mm/damon/sysfs-schemes.c | 36 ++++++++++++++++++++++++++++--------
> 1 file changed, 28 insertions(+), 8 deletions(-)
>
> diff --git a/mm/damon/sysfs-schemes.c b/mm/damon/sysfs-schemes.c
> index 5186966da..1a890e2a4 100644
> --- a/mm/damon/sysfs-schemes.c
> +++ b/mm/damon/sysfs-schemes.c
> @@ -533,9 +533,14 @@ static ssize_t memcg_path_show(struct kobject *kobj,
> {
> struct damon_sysfs_scheme_filter *filter = container_of(kobj,
> struct damon_sysfs_scheme_filter, kobj);
> + ssize_t len;
>
> - return sysfs_emit(buf, "%s\n",
> + mutex_lock(&damon_sysfs_lock);
I read Sashiko is commenting [2] about possible ABBA deadlock, and suggesting
mutex_trylock(). I think that makes sense. damon_sysfs_lock is always only
mutex_trylock()-ed for the reason.
> + len = sysfs_emit(buf, "%s\n",
> filter->memcg_path ? filter->memcg_path : "");
> + mutex_unlock(&damon_sysfs_lock);
> +
> + return len;
> }
And I'm unsure if this change for memcg_path_show() is really needed.
>
> static ssize_t memcg_path_store(struct kobject *kobj,
> @@ -543,15 +548,20 @@ static ssize_t memcg_path_store(struct kobject *kobj,
> {
> struct damon_sysfs_scheme_filter *filter = container_of(kobj,
> struct damon_sysfs_scheme_filter, kobj);
> - char *path = kmalloc_array(size_add(count, 1), sizeof(*path),
> - GFP_KERNEL);
> + char *path, *old;
>
> + path = kmalloc_array(size_add(count, 1), sizeof(*path), GFP_KERNEL);
> if (!path)
> return -ENOMEM;
>
> strscpy(path, buf, count + 1);
> - kfree(filter->memcg_path);
> +
> + mutex_lock(&damon_sysfs_lock);
Let's use mutex_trylock().
> + old = filter->memcg_path;
> filter->memcg_path = path;
> + mutex_unlock(&damon_sysfs_lock);
> +
> + kfree(old);
> return count;
> }
Same comments for path_show() and path_store() changes.
[1] https://docs.kernel.org/process/submitting-patches.html#commentary
[2] https://lore.kernel.org/20260420183146.0DA7AC2BCB0@smtp.kernel.org
Thanks,
SJ
[...]
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v2] mm/damon/sysfs-schemes: fix use-after-free on memcg_path and goal path
2026-04-21 1:20 ` SeongJae Park
@ 2026-04-21 1:23 ` SeongJae Park
0 siblings, 0 replies; 5+ messages in thread
From: SeongJae Park @ 2026-04-21 1:23 UTC (permalink / raw)
To: SeongJae Park; +Cc: Junxi Qian, linux-mm, akpm, damon, linux-kernel
On Mon, 20 Apr 2026 18:20:27 -0700 SeongJae Park <sj@kernel.org> wrote:
> Hello Junxi,
>
>
> When you send a new version of a patch, please send it as a new mail with
> changes log [1], rather than as a reply to the old version.
Also, please share your revision plan first, and wait for possible others'
comments for about a day before posting a new version.
Thanks,
SJ
>
> On Mon, 20 Apr 2026 20:54:05 +0800 Junxi Qian <qjx1298677004@gmail.com> wrote:
>
> > memcg_path_store() and path_store() free and replace their respective
> > string pointers without holding damon_sysfs_lock. This creates a race
> > with the kdamond thread, which reads these pointers in
> > damon_sysfs_add_scheme_filters() and damos_sysfs_add_quota_score()
> > via damon_call() during a "commit" operation.
> >
> > The race window is as follows:
> > Thread A (commit): holds damon_sysfs_lock, triggers damon_call()
> > kdamond thread: reads sysfs_filter->memcg_path in
> > damon_sysfs_memcg_path_to_id()
> > Thread B (sysfs): calls memcg_path_store() WITHOUT lock,
> > kfree(filter->memcg_path), replaces pointer
> >
> > Since Thread B does not hold damon_sysfs_lock, the kdamond thread can
> > observe a freed pointer, resulting in a use-after-free when
> > damon_sysfs_memcg_path_eq() dereferences it for string comparison.
>
> Thank you for finding and sharing this bug!
> >
> > Similarly, memcg_path_show() and path_show() read the string pointers
> > without holding the lock, so a concurrent store can free the string
> > just before sysfs_emit() dereferences it, causing a use-after-free.
>
> Is this correct? Isn't sysfs prohibiting such concurrent read/write using
> kernfs_open_file->mutex? Were you able to trigger this?
>
> >
> > KASAN reports the following on v7.0.0-rc5 with CONFIG_KASAN=y:
> >
> > ==================================================================
> > BUG: KASAN: double-free in memcg_path_store+0x6e/0xc0
> >
> > Free of addr ffff888100a086c0 by task exp/149
> >
> > CPU: 1 UID: 0 PID: 149 Comm: exp Not tainted 7.0.0-rc5-gd38efd7c139a #18 PREEMPT(lazy)
> > Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
> > Call Trace:
> > <TASK>
> > dump_stack_lvl+0x55/0x70
> > print_report+0xcb/0x5d0
> > kasan_report_invalid_free+0x94/0xc0
> > check_slab_allocation+0xde/0x110
> > kfree+0x114/0x3b0
> > memcg_path_store+0x6e/0xc0
> > kernfs_fop_write_iter+0x2fc/0x490
> > vfs_write+0x8e1/0xcc0
> > ksys_write+0xee/0x1c0
> > do_syscall_64+0xfc/0x580
> > entry_SYSCALL_64_after_hwframe+0x77/0x7f
> > </TASK>
> >
> > Allocated by task 147 on cpu 0 at 12.364295s:
> > kasan_save_stack+0x24/0x50
> > kasan_save_track+0x17/0x60
> > __kasan_kmalloc+0x7f/0x90
> > __kmalloc_noprof+0x191/0x490
> > memcg_path_store+0x32/0xc0
> > kernfs_fop_write_iter+0x2fc/0x490
> > vfs_write+0x8e1/0xcc0
> > ksys_write+0xee/0x1c0
> >
> > Freed by task 150 on cpu 0 at 13.373810s:
> > kasan_save_stack+0x24/0x50
> > kasan_save_track+0x17/0x60
> > kasan_save_free_info+0x3b/0x60
> > __kasan_slab_free+0x43/0x70
> > kfree+0x137/0x3b0
> > memcg_path_store+0x6e/0xc0
> > kernfs_fop_write_iter+0x2fc/0x490
> > vfs_write+0x8e1/0xcc0
> > ksys_write+0xee/0x1c0
> >
> > The buggy address belongs to the object at ffff888100a086c0
> > which belongs to the cache kmalloc-8 of size 8
> > ==================================================================
> >
> > Fix this by holding damon_sysfs_lock while swapping the path pointer
> > in both memcg_path_store() and path_store(),
>
> I'd like to avoid use of the lock in long term. But this sounds good and
> simple for hotfixes.
>
> > and while reading the
> > path pointer in memcg_path_show() and path_show().
>
> As I commented above, I'm not sure if this is really needed.
>
> > The actual kfree()
> > is moved outside the lock since the old pointer is no longer reachable
> > once replaced.
> >
> > Fixes: 490a43d07f16 ("mm/damon/sysfs-schemes: free old damon_sysfs_scheme_filter->memcg_path on write")
>
> I think this deserves Cc-ing stable@.
>
> Also, this patch is fixing two bugs. For making backport of the fixes easy,
> could we split this patch into two patches, one for memcg_path_{show,store}(),
> and the other one for path_{show,store}()?
>
> > Signed-off-by: Junxi Qian <qjx1298677004@gmail.com>
> >
> > ---
> > Changes in v2:
> > - Also protect memcg_path_show() and path_show() with damon_sysfs_lock,
> > since sysfs show and store callbacks can run concurrently and a
> > lockless read in show can race with the kfree in store. (Sashiko AI)
>
> As I abovely commented, I'm not sure that Sashiko comment is correct.
>
> >
> > mm/damon/sysfs-schemes.c | 36 ++++++++++++++++++++++++++++--------
> > 1 file changed, 28 insertions(+), 8 deletions(-)
> >
> > diff --git a/mm/damon/sysfs-schemes.c b/mm/damon/sysfs-schemes.c
> > index 5186966da..1a890e2a4 100644
> > --- a/mm/damon/sysfs-schemes.c
> > +++ b/mm/damon/sysfs-schemes.c
> > @@ -533,9 +533,14 @@ static ssize_t memcg_path_show(struct kobject *kobj,
> > {
> > struct damon_sysfs_scheme_filter *filter = container_of(kobj,
> > struct damon_sysfs_scheme_filter, kobj);
> > + ssize_t len;
> >
> > - return sysfs_emit(buf, "%s\n",
> > + mutex_lock(&damon_sysfs_lock);
>
> I read Sashiko is commenting [2] about possible ABBA deadlock, and suggesting
> mutex_trylock(). I think that makes sense. damon_sysfs_lock is always only
> mutex_trylock()-ed for the reason.
>
> > + len = sysfs_emit(buf, "%s\n",
> > filter->memcg_path ? filter->memcg_path : "");
> > + mutex_unlock(&damon_sysfs_lock);
> > +
> > + return len;
> > }
>
> And I'm unsure if this change for memcg_path_show() is really needed.
>
> >
> > static ssize_t memcg_path_store(struct kobject *kobj,
> > @@ -543,15 +548,20 @@ static ssize_t memcg_path_store(struct kobject *kobj,
> > {
> > struct damon_sysfs_scheme_filter *filter = container_of(kobj,
> > struct damon_sysfs_scheme_filter, kobj);
> > - char *path = kmalloc_array(size_add(count, 1), sizeof(*path),
> > - GFP_KERNEL);
> > + char *path, *old;
> >
> > + path = kmalloc_array(size_add(count, 1), sizeof(*path), GFP_KERNEL);
> > if (!path)
> > return -ENOMEM;
> >
> > strscpy(path, buf, count + 1);
> > - kfree(filter->memcg_path);
> > +
> > + mutex_lock(&damon_sysfs_lock);
>
> Let's use mutex_trylock().
>
> > + old = filter->memcg_path;
> > filter->memcg_path = path;
> > + mutex_unlock(&damon_sysfs_lock);
> > +
> > + kfree(old);
> > return count;
> > }
>
> Same comments for path_show() and path_store() changes.
>
> [1] https://docs.kernel.org/process/submitting-patches.html#commentary
> [2] https://lore.kernel.org/20260420183146.0DA7AC2BCB0@smtp.kernel.org
>
>
> Thanks,
> SJ
>
> [...]
>
Sent using hkml (https://github.com/sjp38/hackermail)
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] mm/damon/sysfs-schemes: fix use-after-free on memcg_path and goal path
2026-04-20 8:53 [PATCH] mm/damon/sysfs-schemes: fix use-after-free on memcg_path and goal path Junxi Qian
2026-04-20 12:54 ` [PATCH v2] " Junxi Qian
@ 2026-04-21 7:06 ` Junxi Qian
1 sibling, 0 replies; 5+ messages in thread
From: Junxi Qian @ 2026-04-21 7:06 UTC (permalink / raw)
To: sj; +Cc: akpm, damon, linux-kernel, linux-mm
Hi SJ,
Thanks for the suggestion.
I checked the show/store race further and was able to reproduce the
memcg_path_show() side issue on a KASAN kernel.
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#define DAMON_BASE "/sys/kernel/mm/damon/admin"
#define MAX_PATH 1024
#define MAX_BUF 4096
static atomic_int stop_flag;
struct worker_arg {
const char *path;
int tid;
unsigned long long ops;
unsigned int seed;
};
static void on_signal(int sig)
{
(void)sig;
atomic_store(&stop_flag, 1);
}
static int write_buf(const char *path, const char *buf, size_t len)
{
int fd;
ssize_t written;
int saved_errno;
fd = open(path, O_WRONLY | O_CLOEXEC);
if (fd < 0)
return -errno;
written = write(fd, buf, len);
saved_errno = errno;
close(fd);
if (written < 0)
return -saved_errno;
if ((size_t)written != len)
return -EIO;
return 0;
}
static int write_str(const char *path, const char *val)
{
return write_buf(path, val, strlen(val));
}
static int read_once(const char *path, char *buf, size_t buf_sz)
{
int fd;
ssize_t n;
int saved_errno;
fd = open(path, O_RDONLY | O_CLOEXEC);
if (fd < 0)
return -errno;
n = read(fd, buf, buf_sz - 1);
saved_errno = errno;
close(fd);
if (n < 0)
return -saved_errno;
buf[n] = '\0';
return 0;
}
static size_t make_payload(char *buf, size_t buf_sz, unsigned int *seed,
int tid, unsigned long long iter)
{
static const size_t interesting_lengths[] = {
1, 2, 3, 4, 7, 8, 15, 16, 31, 32, 63, 64,
127, 128, 255, 256, 511, 512, 1023, 1024,
1535, 2047, 2048, 3071, 3583, 4095,
};
const char alphabet[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/_-.";
size_t len;
size_t i;
unsigned int idx;
idx = rand_r(seed) % (sizeof(interesting_lengths) / sizeof(interesting_lengths[0]));
len = interesting_lengths[idx];
if (len >= buf_sz)
len = buf_sz - 1;
for (i = 0; i < len; i++)
buf[i] = alphabet[rand_r(seed) % (sizeof(alphabet) - 1)];
if (len > 20)
snprintf(buf, len, "/damon_race/%d/%llu/", tid, iter);
buf[len] = '\0';
return len;
}
static void *reader_thread(void *data)
{
struct worker_arg *arg = data;
char buf[MAX_BUF + 1];
while (!atomic_load(&stop_flag)) {
int err = read_once(arg->path, buf, sizeof(buf));
if (err && err != -EBUSY) {
fprintf(stderr, "reader[%d] %s failed: %s\n",
arg->tid, arg->path, strerror(-err));
atomic_store(&stop_flag, 1);
break;
}
arg->ops++;
}
return NULL;
}
static void *writer_thread(void *data)
{
struct worker_arg *arg = data;
char buf[MAX_BUF + 1];
unsigned long long iter = 0;
while (!atomic_load(&stop_flag)) {
size_t len = make_payload(buf, sizeof(buf), &arg->seed, arg->tid, iter++);
int err = write_buf(arg->path, buf, len);
if (err && err != -EBUSY) {
fprintf(stderr, "writer[%d] %s failed: %s\n",
arg->tid, arg->path, strerror(-err));
atomic_store(&stop_flag, 1);
break;
}
arg->ops++;
}
return NULL;
}
static int ensure_damon_layout(char *memcg_path_file, size_t memcg_sz,
char *goal_path_file, size_t goal_sz)
{
char path[MAX_PATH];
int err;
snprintf(path, sizeof(path), "%s/kdamonds/nr_kdamonds", DAMON_BASE);
if ((err = write_str(path, "1")))
return err;
snprintf(path, sizeof(path), "%s/kdamonds/0/contexts/nr_contexts", DAMON_BASE);
if ((err = write_str(path, "1")))
return err;
snprintf(path, sizeof(path), "%s/kdamonds/0/contexts/0/schemes/nr_schemes", DAMON_BASE);
if ((err = write_str(path, "1")))
return err;
snprintf(path, sizeof(path), "%s/kdamonds/0/contexts/0/schemes/0/action", DAMON_BASE);
if ((err = write_str(path, "stat")))
return err;
snprintf(path, sizeof(path),
"%s/kdamonds/0/contexts/0/schemes/0/filters/nr_filters", DAMON_BASE);
if ((err = write_str(path, "1")))
return err;
snprintf(path, sizeof(path),
"%s/kdamonds/0/contexts/0/schemes/0/filters/0/type", DAMON_BASE);
if ((err = write_str(path, "memcg")))
return err;
snprintf(memcg_path_file, memcg_sz,
"%s/kdamonds/0/contexts/0/schemes/0/filters/0/memcg_path", DAMON_BASE);
if ((err = write_str(memcg_path_file, "/")))
return err;
snprintf(path, sizeof(path),
"%s/kdamonds/0/contexts/0/schemes/0/quotas/goals/nr_goals", DAMON_BASE);
if ((err = write_str(path, "1")))
return err;
snprintf(path, sizeof(path),
"%s/kdamonds/0/contexts/0/schemes/0/quotas/goals/0/target_metric", DAMON_BASE);
if ((err = write_str(path, "node_memcg_used_bp")))
return err;
snprintf(path, sizeof(path),
"%s/kdamonds/0/contexts/0/schemes/0/quotas/goals/0/target_value", DAMON_BASE);
if ((err = write_str(path, "1")))
return err;
snprintf(path, sizeof(path),
"%s/kdamonds/0/contexts/0/schemes/0/quotas/goals/0/nid", DAMON_BASE);
if ((err = write_str(path, "0")))
return err;
snprintf(goal_path_file, goal_sz,
"%s/kdamonds/0/contexts/0/schemes/0/quotas/goals/0/path", DAMON_BASE);
if ((err = write_str(goal_path_file, "/")))
return err;
return 0;
}
static int cleanup_damon_layout(void)
{
char path[MAX_PATH];
snprintf(path, sizeof(path), "%s/kdamonds/nr_kdamonds", DAMON_BASE);
return write_str(path, "0");
}
static int run_stress(const char *name, const char *path,
int seconds, int nr_readers, int nr_writers)
{
pthread_t *threads;
struct worker_arg *args;
int total_threads = nr_readers + nr_writers;
int i;
int err;
unsigned long long total_reads = 0;
unsigned long long total_writes = 0;
threads = calloc(total_threads, sizeof(*threads));
args = calloc(total_threads, sizeof(*args));
if (!threads || !args) {
free(threads);
free(args);
return -ENOMEM;
}
atomic_store(&stop_flag, 0);
printf("[*] stressing %s: %s (%d readers, %d writers, %d sec)\n",
name, path, nr_readers, nr_writers, seconds);
fflush(stdout);
for (i = 0; i < total_threads; i++) {
args[i].path = path;
args[i].tid = i;
args[i].seed = (unsigned int)time(NULL) ^ (i * 0x9e3779b9U);
if (i < nr_readers)
err = pthread_create(&threads[i], NULL, reader_thread, &args[i]);
else
err = pthread_create(&threads[i], NULL, writer_thread, &args[i]);
if (err) {
fprintf(stderr, "pthread_create failed: %s\n", strerror(err));
atomic_store(&stop_flag, 1);
total_threads = i;
break;
}
}
for (i = 0; i < seconds && !atomic_load(&stop_flag); i++)
sleep(1);
atomic_store(&stop_flag, 1);
for (i = 0; i < total_threads; i++)
pthread_join(threads[i], NULL);
for (i = 0; i < nr_readers && i < total_threads; i++)
total_reads += args[i].ops;
for (; i < total_threads; i++)
total_writes += args[i].ops;
printf("[*] %s done: reads=%llu writes=%llu\n",
name, total_reads, total_writes);
free(threads);
free(args);
return 0;
}
static void usage(const char *prog)
{
fprintf(stderr,
"Usage: %s [memcg|goal|both] [seconds] [readers] [writers]\n"
" default mode: both\n"
" default seconds/readers/writers: 60 8 8\n",
prog);
}
int main(int argc, char **argv)
{
char memcg_path_file[MAX_PATH];
char goal_path_file[MAX_PATH];
const char *mode = "both";
int seconds = 60;
int readers = 8;
int writers = 8;
int err;
bool test_memcg = true;
bool test_goal = true;
if (argc > 1)
mode = argv[1];
if (argc > 2)
seconds = atoi(argv[2]);
if (argc > 3)
readers = atoi(argv[3]);
if (argc > 4)
writers = atoi(argv[4]);
if (seconds <= 0 || readers <= 0 || writers <= 0) {
usage(argv[0]);
return 1;
}
if (!strcmp(mode, "memcg"))
test_goal = false;
else if (!strcmp(mode, "goal"))
test_memcg = false;
else if (strcmp(mode, "both")) {
usage(argv[0]);
return 1;
}
if (geteuid() != 0) {
fprintf(stderr, "Need root\n");
return 1;
}
signal(SIGINT, on_signal);
signal(SIGTERM, on_signal);
err = ensure_damon_layout(memcg_path_file, sizeof(memcg_path_file),
goal_path_file, sizeof(goal_path_file));
if (err) {
fprintf(stderr, "DAMON setup failed: %s\n", strerror(-err));
return 1;
}
printf("[*] memcg_path file: %s\n", memcg_path_file);
printf("[*] goal path file : %s\n", goal_path_file);
printf("[*] each access opens the sysfs file anew to avoid reusing the same kernfs_open_file mutex\n");
printf("[*] watch dmesg for KASAN splats while this runs\n");
fflush(stdout);
if (test_memcg) {
err = run_stress("memcg_path show/store race", memcg_path_file,
seconds, readers, writers);
if (err)
goto out;
}
if (test_goal) {
err = run_stress("quota goal path show/store race", goal_path_file,
seconds, readers, writers);
if (err)
goto out;
}
out:
if (cleanup_damon_layout())
fprintf(stderr, "warning: failed to cleanup DAMON sysfs layout\n");
if (err)
return 1;
printf("[*] done. Check: dmesg | grep -A20 -E 'BUG: KASAN|use-after-free'\n");
return 0;
}
And below is the full KASAN report I got.
[*] memcg_path file: /sys/kernel/mm/damon/admin/kdamonds/0/contexts/0/schemes/0/filters/0/memcg_path
[*] goal path file : /sys/kernel/mm/damon/admin/kdamonds/0/contexts/0/schemes/0/quotas/goals/0/path
[*] each access opens the sysfs file anew to avoid reusing the same kernfs_open_file mutex
[*] watch dmesg for KASAN splats while this runs
[*] stressing memcg_path show/store race: /sys/kernel/mm/damon/admin/kdamonds/0/contexts/0/schemes/0/filters/0/memcg_path (8 readers, 8 writers, 60 sec)
[ 4.038673] ==================================================================
[ 4.040040] BUG: KASAN: slab-use-after-free in string+0x396/0x440
[ 4.041106] Read of size 1 at addr ffff888100be9120 by task exp/155
[ 4.042288]
[ 4.042617] CPU: 1 UID: 0 PID: 155 Comm: exp Not tainted 7.0.0-rc5-gd38efd7c139a #18 PREEMPT(lazy)
[ 4.042621] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
[ 4.042622] Call Trace:
[ 4.042624] <TASK>
[ 4.042625] dump_stack_lvl+0x55/0x70
[ 4.042629] print_report+0xcb/0x5d0
[ 4.042633] ? string+0x396/0x440
[ 4.042635] kasan_report+0xb8/0xf0
[ 4.042637] ? string+0x396/0x440
[ 4.042640] string+0x396/0x440
[ 4.042642] ? __pfx_string+0x10/0x10
[ 4.042644] ? kasan_save_stack+0x24/0x50
[ 4.042646] ? __kvmalloc_node_noprof+0x1cf/0x660
[ 4.042648] ? seq_read_iter+0x67c/0x1050
[ 4.042652] ? vfs_read+0x657/0x910
[ 4.042654] ? ksys_read+0xee/0x1c0
[ 4.042656] ? do_syscall_64+0xfc/0x580
[ 4.042658] vsnprintf+0x435/0x1270
[ 4.042661] ? stack_trace_save+0x8e/0xc0
[ 4.042664] ? __pfx_vsnprintf+0x10/0x10
[ 4.042667] ? do_sys_openat2+0xe5/0x170
[ 4.042668] ? kasan_save_stack+0x34/0x50
[ 4.042670] ? kasan_save_stack+0x24/0x50
[ 4.042671] ? kasan_save_track+0x17/0x60
[ 4.042673] ? __pfx_kobj_attr_show+0x10/0x10
[ 4.042676] vscnprintf+0x12/0x30
[ 4.042678] sysfs_emit+0xbe/0x110
[ 4.042681] ? __pfx_sysfs_emit+0x10/0x10
[ 4.042683] memcg_path_show+0x48/0x60
[ 4.042685] sysfs_kf_seq_show+0x2a0/0x4a0
[ 4.042687] seq_read_iter+0x402/0x1050
[ 4.042690] ? kvm_sched_clock_read+0x11/0x20
[ 4.042692] vfs_read+0x657/0x910
[ 4.042694] ? do_sys_openat2+0xe5/0x170
[ 4.042696] ? kmem_cache_free+0xb5/0x3c0
[ 4.042698] ? __pfx_vfs_read+0x10/0x10
[ 4.042700] ? __pfx_mutex_lock+0x10/0x10
[ 4.042703] ? fdget_pos+0x249/0x4c0
[ 4.042706] ksys_read+0xee/0x1c0
[ 4.042708] ? __pfx_ksys_read+0x10/0x10
[ 4.042710] do_syscall_64+0xfc/0x580
[ 4.042712] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 4.042714] RIP: 0033:0x44c89c
[ 4.042716] Code: ec 28 48 89 54 24 18 48 89 74 24 10 89 7c 24 08 e8 49 ab 02 00 48 8b 54 24 18 48 8b 74 24 10 41 89 c0 8b 7c 24 08 31 c0 0f 05 <48> 3d 00 f0 ff ff8
[ 4.042718] RSP: 002b:00007f779d33e170 EFLAGS: 00000246 ORIG_RAX: 0000000000000000
[ 4.042721] RAX: ffffffffffffffda RBX: 00007f779d33f5f0 RCX: 000000000044c89c
[ 4.042723] RDX: 0000000000001000 RSI: 00007f779d33e1a0 RDI: 000000000000000b
[ 4.042724] RBP: 000000000000000b R08: 0000000000000000 R09: 00007ffe9b7899df
[ 4.042725] R10: 0000000000000000 R11: 0000000000000246 R12: 000000002ef138d0
[ 4.042726] R13: 00007f779d33e1a0 R14: 0000000000000002 R15: 00007f779cb3f000
[ 4.042728] </TASK>
[ 4.042729]
[ 4.089403] Allocated by task 158 on cpu 0 at 4.038654s:
[ 4.090348] kasan_save_stack+0x24/0x50
[ 4.091058] kasan_save_track+0x17/0x60
[ 4.091780] __kasan_kmalloc+0x7f/0x90
[ 4.092508] __kmalloc_noprof+0x191/0x490
[ 4.093275] memcg_path_store+0x32/0xc0
[ 4.094000] kernfs_fop_write_iter+0x2fc/0x490
[ 4.094825] vfs_write+0x8e1/0xcc0
[ 4.095465] ksys_write+0xee/0x1c0
[ 4.096132] do_syscall_64+0xfc/0x580
[ 4.096758] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 4.097648]
[ 4.097957] Freed by task 158 on cpu 0 at 4.038671s:
[ 4.098985] kasan_save_stack+0x24/0x50
[ 4.099767] kasan_save_track+0x17/0x60
[ 4.100615] kasan_save_free_info+0x3b/0x60
[ 4.101478] __kasan_slab_free+0x43/0x70
[ 4.102193] kfree+0x137/0x3b0
[ 4.102797] memcg_path_store+0x6e/0xc0
[ 4.103577] kernfs_fop_write_iter+0x2fc/0x490
[ 4.104382] vfs_write+0x8e1/0xcc0
[ 4.104993] ksys_write+0xee/0x1c0
[ 4.105662] do_syscall_64+0xfc/0x580
[ 4.106328] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 4.107217]
[ 4.107533] The buggy address belongs to the object at ffff888100be9120
[ 4.107533] which belongs to the cache kmalloc-8 of size 8
[ 4.109626] The buggy address is located 0 bytes inside of
[ 4.109626] freed 8-byte region [ffff888100be9120, ffff888100be9128)
[ 4.111972]
[ 4.112332] The buggy address belongs to the physical page:
[ 4.113259] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0xffff888100be9630 pfn:0x100be9
[ 4.114944] flags: 0x200000000000200(workingset|node=0|zone=2)
[ 4.116300] page_type: f5(slab)
[ 4.116909] raw: 0200000000000200 ffff888100041500 ffffea000402f550 ffffea000401ae50
[ 4.118974] raw: ffff888100be9630 000000000055003f 00000000f5000000 0000000000000000
[ 4.120293] page dumped because: kasan: bad access detected
[ 4.121299]
[ 4.121633] Memory state around the buggy address:
[ 4.122571] ffff888100be9000: fa fc fc fc fc fc fa fc fc fc fc fc fa fc fc fc
[ 4.123920] ffff888100be9080: fc fc fa fc fc fc fc fc fa fc fc fc fc fc fa fc
[ 4.125299] >ffff888100be9100: fc fc fc fc fa fc fc fc fc fc fa fc fc fc fc fc
[ 4.126615] ^
[ 4.127380] ffff888100be9180: 02 fc fc fc fc fc fa fc fc fc fc fc fa fc fc fc
[ 4.128760] ffff888100be9200: fc fc fa fc fc fc fc fc 02 fc fc fc fc fc 02 fc
[ 4.130067] ==================================================================
[ 4.389108] Disabling lock debugging due to kernel taint
[ 7.139036] BUG: kernel NULL pointer dereference, address: 0000000000000008
[ 7.141323] #PF: supervisor read access in kernel mode
[ 7.142210] #PF: error_code(0x0000) - not-present page
[ 7.143341] PGD 7c46067 P4D 7c46067 PUD 7c3a067 PMD 0
[ 7.144498] Oops: Oops: 0000 [#1] SMP KASAN NOPTI
[ 7.145386] CPU: 0 UID: 0 PID: 154 Comm: exp Tainted: G B 7.0.0-rc5-gd38efd7c139a #18 PREEMPT(lazy)
[ 7.148291] Tainted: [B]=BAD_PAGE
[ 7.148983] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
[ 7.150355] RIP: 0010:qlist_free_all+0xab/0x130
[ 7.150954] Code: 01 ca 48 c1 ea 0c 48 c1 e2 06 48 03 15 de 30 e4 03 48 8b 4a 08 48 8d 71 ff 83 e1 01 48 0f 45 d6 31 c9 80 7a 33 f5 48 0f 45 d1 <48> 8b 6a 08 e9 6a0
[ 7.153069] RSP: 0018:ffff888011e1fbb8 EFLAGS: 00010206
[ 7.153667] RAX: ffff88800f6bc000 RBX: ffff88800f6bc000 RCX: 0000000000000000
[ 7.154543] RDX: 0000000000000000 RSI: ffffffffffffffff RDI: ffff888100042000
[ 7.155362] RBP: 0000000000000000 R08: 0000000000000001 R09: ffffffff8198f0eb
[ 7.156454] R10: ffff88800e245000 R11: 0000000000000246 R12: 0000000000000000
[ 7.157576] R13: ffff888011e1fbf0 R14: ffff88800e245000 R15: ffff88800e245000
[ 7.158648] FS: 00007f779db40640(0000) GS:ffff88817bc47000(0000) knlGS:0000000000000000
[ 7.160170] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 7.160930] CR2: 0000000000000008 CR3: 0000000008ba3002 CR4: 0000000000370ef0
[ 7.161911] Call Trace:
[ 7.162685] <TASK>
[ 7.163117] kasan_quarantine_reduce+0x15d/0x180
[ 7.163636] __kasan_slab_alloc+0x49/0x70
[ 7.164079] __kvmalloc_node_noprof+0x18f/0x660
[ 7.164644] ? seq_read_iter+0x67c/0x1050
[ 7.165322] seq_read_iter+0x67c/0x1050
[ 7.165967] ? kvm_sched_clock_read+0x11/0x20
[ 7.166692] ? kvm_sched_clock_read+0x11/0x20
[ 7.167660] vfs_read+0x657/0x910
[ 7.168365] ? do_sys_openat2+0xe5/0x170
[ 7.169243] ? kmem_cache_free+0xb5/0x3c0
[ 7.169789] ? __pfx_vfs_read+0x10/0x10
[ 7.170466] ? __pfx_mutex_lock+0x10/0x10
[ 7.171028] ? fdget_pos+0x249/0x4c0
[ 7.171474] ksys_read+0xee/0x1c0
[ 7.171971] ? __pfx_ksys_read+0x10/0x10
[ 7.172545] do_syscall_64+0xfc/0x580
[ 7.173106] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 7.173879] RIP: 0033:0x44c89c
[ 7.174343] Code: ec 28 48 89 54 24 18 48 89 74 24 10 89 7c 24 08 e8 49 ab 02 00 48 8b 54 24 18 48 8b 74 24 10 41 89 c0 8b 7c 24 08 31 c0 0f 05 <48> 3d 00 f0 ff ff8
[ 7.176964] RSP: 002b:00007f779db3f170 EFLAGS: 00000246 ORIG_RAX: 0000000000000000
[ 7.178045] RAX: ffffffffffffffda RBX: 00007f779db405f0 RCX: 000000000044c89c
[ 7.179145] RDX: 0000000000001000 RSI: 00007f779db3f1a0 RDI: 0000000000000005
[ 7.180449] RBP: 0000000000000005 R08: 0000000000000000 R09: 00007ffe9b7899df
[ 7.181732] R10: 0000000000000000 R11: 0000000000000246 R12: 000000002ef138b0
[ 7.183041] R13: 00007f779db3f1a0 R14: 0000000000000016 R15: 00007f779d340000
[ 7.184602] </TASK>
[ 7.185159] Modules linked in:
[ 7.185828] CR2: 0000000000000008
[ 7.186582] ---[ end trace 0000000000000000 ]---
[ 7.187296] RIP: 0010:qlist_free_all+0xab/0x130
[ 7.187837] Code: 01 ca 48 c1 ea 0c 48 c1 e2 06 48 03 15 de 30 e4 03 48 8b 4a 08 48 8d 71 ff 83 e1 01 48 0f 45 d6 31 c9 80 7a 33 f5 48 0f 45 d1 <48> 8b 6a 08 e9 6a0
[ 7.190065] RSP: 0018:ffff888011e1fbb8 EFLAGS: 00010206
[ 7.190674] RAX: ffff88800f6bc000 RBX: ffff88800f6bc000 RCX: 0000000000000000
[ 7.191649] RDX: 0000000000000000 RSI: ffffffffffffffff RDI: ffff888100042000
[ 7.192504] RBP: 0000000000000000 R08: 0000000000000001 R09: ffffffff8198f0eb
[ 7.193436] R10: ffff88800e245000 R11: 0000000000000246 R12: 0000000000000000
[ 7.194506] R13: ffff888011e1fbf0 R14: ffff88800e245000 R15: ffff88800e245000
[ 7.195513] FS: 00007f779db40640(0000) GS:ffff88817bc47000(0000) knlGS:0000000000000000
[ 7.196530] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 7.197420] CR2: 0000000000000008 CR3: 0000000008ba3002 CR4: 0000000000370ef0
[ 7.198744] note: exp[154] exited with irqs disabled
I will spend some time thinking about how to fix this properly.
If someone more familiar with this code would like to work on the fix
directly, that would be greatly appreciated, as I am not very familiar
with this part of the code yet.
Thanks,
Junxi
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-04-21 7:06 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-04-20 8:53 [PATCH] mm/damon/sysfs-schemes: fix use-after-free on memcg_path and goal path Junxi Qian
2026-04-20 12:54 ` [PATCH v2] " Junxi Qian
2026-04-21 1:20 ` SeongJae Park
2026-04-21 1:23 ` SeongJae Park
2026-04-21 7:06 ` [PATCH] " Junxi Qian
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox