Skip to contentSkip to conversation

mm/damon/{reclaim,lru_sort}: fix commit_inputs infinite hang

RFC PATCH In review SJ Park 2 participants 7 messages Aug 2

Subsystems (3)
lore
Share & tools
lore

SJ Park

author maintainer
#0 lore raw

cover mm/damon/{reclaim,lru_sort}: fix commit_inputs infinite hang

Writing 'Y' to commit_inputs parameters of DAMON_RECLAIM and DAMON_LRU_SORT before the modules were ever turned on causes infinite hang. Fix those.

The issue was discovered [1] by Sashiko.

[1] https://lore.kernel.org/[email protected]

SJ Park (2):
mm/damon/reclaim: skip damon_call() if ctx has not started mm/damon/lru_sort: skip damon_call() if ctx has not started

 mm/damon/lru_sort.c | 11 ++++++-----
 mm/damon/reclaim.c  | 11 ++++++-----
 2 files changed, 12 insertions(+), 10 deletions(-)

base-commit: d6580a342bc8c459628d2b68b313e9c0668bca59

Signature · 1 line
2.47.3
author maintainer
#1 lore raw

RFC PATCH 1/2 mm/damon/reclaim: skip damon_call() if ctx has not started

DAMON_RECLAIM calls damon_call() for commit_inputs parameter user input if the DAMON context is initialized. The context could be initialized, but not yet successfully started. In the case, damon_call() could indefinitely hang. Read the comment on damon_call() for more detail. Fix the problem by memorizing if the DAMON context has ever successfully started, and skip damon_call() if it has not.

This issue can easily be reproduced by writing Y to commit_inputs on a system that DAMON_RECLAIM was not turned on before.

Fixes de3c60e1c831 ("mm/damon: add synchronous commit for commit_inputs")
Cc <[email protected]> # 7.2.x
---
 mm/damon/reclaim.c | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)
mm/damon/reclaim.c +6 -5
index 09e941d75f67d..45d5557cc575a 100644
@@ -271,6 +271,8 @@ static int damon_reclaim_commit_inputs_fn(void *arg)
 	return damon_reclaim_apply_parameters();
 }
 
+static bool damon_reclaim_damon_has_started;
+
 static int damon_reclaim_commit_inputs_store(const char *val,
 					     const struct kernel_param *kp)
 {
@@ -291,11 +293,8 @@ static int damon_reclaim_commit_inputs_store(const char *val,
 	if (!commit_inputs_request)
 		return 0;
 
-	/*
-	 * Skip damon_call() if ctx is not initialized to avoid
-	 * NULL pointer dereference.
-	 */
-	if (!ctx)
+	/* Skip damon_call() if ctx has not successfully started. */
+	if (!damon_reclaim_damon_has_started)
 		return -EINVAL;
 
 	err = damon_call(ctx, &control);
@@ -344,6 +343,8 @@ static int damon_reclaim_turn(bool on)
 	err = damon_start(&ctx, 1, true);
 	if (err)
 		return err;
+	if (!damon_reclaim_damon_has_started)
+		damon_reclaim_damon_has_started = true;
 	return damon_call(ctx, &call_control);
 }
 
Signature · 1 line
2.47.3
#2 lore raw

Re: [RFC PATCH 1/2] mm/damon/reclaim: skip damon_call() if ctx has not started

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but the patch fixes an indefinite hang in mm/damon/reclaim.c when writing to commit_inputs before the DAMON context starts, while failing to apply the identical fix to mm/damon/lru_sort.c. This leaves damon_lru_sort vulnerable to the exact same bug. --

--- Patch [1]: [RFC PATCH 1/2] mm/damon/reclaim: skip damon_call() if ctx has not started --- commit 86e90c966d8636445486cfde6487a797b433cb75
Author: SJ Park <[email protected]>

mm/damon/reclaim: skip damon_call() if ctx has not started

This commit addresses an indefinite hang in DAMON reclaim when writing to commit_inputs before the context has fully started. It introduces a tracking flag to determine whether the DAMON context has successfully started in order to safely skip damon_call().

Cc <[email protected]> # 7.2.x
Quoted 4 lines
mm/damon/reclaim.c +0 -0
index 09e941d75f67d..45d5557cc575a 100644

[ ... ]

Quoted 4 lines
mm/damon/reclaim.c +0 -0
@@ -291,11 +293,8 @@ static int damon_reclaim_commit_inputs_store(const char *val,
 	if (!commit_inputs_request)
 		return 0;
 

mm/damon/reclaim.c static int damon_reclaim_commit_inputs_store(const char *val, · line 300

Quoted 10 lines
mm/damon/reclaim.c +2 -5
-	/*
-	 * Skip damon_call() if ctx is not initialized to avoid
-	 * NULL pointer dereference.
-	 */
-	if (!ctx)
+	/* Skip damon_call() if ctx has not successfully started. */
+	if (!damon_reclaim_damon_has_started)
 		return -EINVAL;
 
 	err = damon_call(ctx, &control);

[Severity: High]
This isn't a bug introduced by this patch, but does the same vulnerability exist in mm/damon/lru_sort.c?

It looks like damon_lru_sort_commit_inputs_store() uses the exact same logic that was fixed here in reclaim.c:

mm/damon/lru_sort.c:damon_lru_sort_commit_inputs_store() {
    ...
    /*
     * Skip damon_call() if ctx is not initialized to avoid
     * NULL pointer dereference.
     */
    if (!ctx)
        return -EINVAL;

    err = damon_call(ctx, &control);
    ...
}

If a user writes to commit_inputs for damon_lru_sort before the thread has started, could damon_call() blindly invoke wait_for_completion() and hang indefinitely without a running thread to signal it?

Should a similar fix be applied to mm/damon/lru_sort.c to prevent a task from hanging in an uninterruptible sleep state?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
author maintainer
#3 lore raw

Re: [RFC PATCH 1/2] mm/damon/reclaim: skip damon_call() if ctx has not started

Quoted 59 lines · On Sun, 02 Aug 2026 19:04:30 +0000 [email protected] wrote:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but the patch fixes an indefinite hang in mm/damon/reclaim.c when writing to commit_inputs before the DAMON context starts, while failing to apply the identical fix to mm/damon/lru_sort.c. This leaves damon_lru_sort vulnerable to the exact same bug. --

--- Patch [1]: [RFC PATCH 1/2] mm/damon/reclaim: skip damon_call() if ctx has not started --- commit 86e90c966d8636445486cfde6487a797b433cb75
Author: SJ Park <[email protected]>

mm/damon/reclaim: skip damon_call() if ctx has not started

This commit addresses an indefinite hang in DAMON reclaim when writing to commit_inputs before the context has fully started. It introduces a tracking flag to determine whether the DAMON context has successfully started in order to safely skip damon_call().

Cc <[email protected]> # 7.2.x
Quoted 4 lines
mm/damon/reclaim.c +0 -0
index 09e941d75f67d..45d5557cc575a 100644

[ ... ]

Quoted 4 lines
mm/damon/reclaim.c +0 -0
@@ -291,11 +293,8 @@ static int damon_reclaim_commit_inputs_store(const char *val,
 	if (!commit_inputs_request)
 		return 0;
 

mm/damon/reclaim.c static int damon_reclaim_commit_inputs_store(const char *val, · line 300

Quoted 10 lines
mm/damon/reclaim.c +2 -5
-	/*
-	 * Skip damon_call() if ctx is not initialized to avoid
-	 * NULL pointer dereference.
-	 */
-	if (!ctx)
+	/* Skip damon_call() if ctx has not successfully started. */
+	if (!damon_reclaim_damon_has_started)
 		return -EINVAL;
 
 	err = damon_call(ctx, &control);

[Severity: High]
This isn't a bug introduced by this patch, but does the same vulnerability exist in mm/damon/lru_sort.c?

It looks like damon_lru_sort_commit_inputs_store() uses the exact same logic that was fixed here in reclaim.c:

mm/damon/lru_sort.c:damon_lru_sort_commit_inputs_store() {
    ...
    /*
     * Skip damon_call() if ctx is not initialized to avoid
     * NULL pointer dereference.
     */
    if (!ctx)
        return -EINVAL;

    err = damon_call(ctx, &control);
    ...
}
Quoted 6 lines

If a user writes to commit_inputs for damon_lru_sort before the thread has started, could damon_call() blindly invoke wait_for_completion() and hang indefinitely without a running thread to signal it?

Should a similar fix be applied to mm/damon/lru_sort.c to prevent a task from hanging in an uninterruptible sleep state?

Yes, and the next patch of this series is fixing it.

Quoted 3 lines
Signature · 2 lines
-- 
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1

Thanks,
SJ

author maintainer
#4 lore raw

RFC PATCH 2/2 mm/damon/lru_sort: skip damon_call() if ctx has not started

In reply to SJ Park

DAMON_LRU_SORT calls damon_call() for commit_inputs parameter user input if the DAMON context is initialized. The context could be initialized, but not yet successfully started. In the case, damon_call() could indefinitely hang. Read the comment on damon_call() for more detail. Fix the problem by memorizing if the DAMON context has ever successfully started, and skip damon_call() if it has not.

This issue can easily be reproduced by writing Y to the commit_inputs parameter file on a system that DAMON_LRU_SORT has not turned on before.

Fixes de3c60e1c831 ("mm/damon: add synchronous commit for commit_inputs")
Cc <[email protected]> # 7.2.x
---
 mm/damon/lru_sort.c | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)
mm/damon/lru_sort.c +6 -5
index 3920d5042f109..bd847829a9907 100644
@@ -346,6 +346,8 @@ static int damon_lru_sort_commit_inputs_fn(void *arg)
 	return damon_lru_sort_apply_parameters();
 }
 
+static bool damon_lru_sort_damon_has_started;
+
 static int damon_lru_sort_commit_inputs_store(const char *val,
 					      const struct kernel_param *kp)
 {
@@ -366,11 +368,8 @@ static int damon_lru_sort_commit_inputs_store(const char *val,
 	if (!commit_inputs_request)
 		return 0;
 
-	/*
-	 * Skip damon_call() if ctx is not initialized to avoid
-	 * NULL pointer dereference.
-	 */
-	if (!ctx)
+	/* Skip damon_call() if ctx has not successfully started. */
+	if (!damon_lru_sort_damon_has_started)
 		return -EINVAL;
 
 	err = damon_call(ctx, &control);
@@ -423,6 +422,8 @@ static int damon_lru_sort_turn(bool on)
 	err = damon_start(&ctx, 1, true);
 	if (err)
 		return err;
+	if (!damon_lru_sort_damon_has_started)
+		damon_lru_sort_damon_has_started = true;
 	return damon_call(ctx, &call_control);
 }
 
Signature · 1 line
2.47.3
#5 lore raw

Re: [RFC PATCH 2/2] mm/damon/lru_sort: skip damon_call() if ctx has not started

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] A TOCTOU race between damon_start() and damon_call() can cause damon_call() to loop indefinitely. --

--- Patch [2]: [RFC PATCH 2/2] mm/damon/lru_sort: skip damon_call() if ctx has not started --- commit 9a410362ed5a4418640b6bf73c4ee49300c9dc16
Author: SJ Park <[email protected]>

mm/damon/lru_sort: skip damon_call() if ctx has not started

This commit prevents damon_call() from hanging when the DAMON context is initialized but not yet started. It introduces a boolean flag to track whether the context has ever successfully started, and skips the call if it has not.

Quoted 4 lines
mm/damon/lru_sort.c +0 -0
index 7e077084cb03a..7df45f9a0b3ae 100644

[ ... ]

Quoted 4 lines
mm/damon/lru_sort.c +0 -0
@@ -364,11 +366,8 @@ static int damon_lru_sort_commit_inputs_store(const char *val,
 	if (!commit_inputs_request)
 		return 0;
 

mm/damon/lru_sort.c static int damon_lru_sort_commit_inputs_store(const char *val, · line 373

Quoted 10 lines
mm/damon/lru_sort.c +2 -5
-	/*
-	 * Skip damon_call() if ctx is not initialized to avoid
-	 * NULL pointer dereference.
-	 */
-	if (!ctx)
+	/* Skip damon_call() if ctx has not successfully started. */
+	if (!damon_lru_sort_damon_has_started)
 		return -EINVAL;
 
 	err = damon_call(ctx, &control);

[Severity: High]
This is a pre-existing issue, but could there be a TOCTOU race here between damon_start() and damon_call() that causes damon_call() to loop indefinitely?

If a user starts DAMON (setting damon_lru_sort_damon_has_started = true), stops it (leaving call_controls_obsolete = true), and then concurrently starts DAMON again while calling damon_call() via commit_inputs, can damon_call() get stuck?

Looking at mm/damon/core.c, __damon_start() sets ctx->kdamond before the new thread clears ctx->call_controls_obsolete:

mm/damon/core.c:__damon_start() {
    ...
    ctx->kdamond = kthread_run(kdamond_fn, ctx, "kdamond.%d", nr_running_ctxs);
    ...
}

If damon_call() runs concurrently, it checks call_controls_obsolete without holding kdamond_lock, sees the stale true value, and jumps to the canceled label:

mm/damon/core.c:damon_call() {
    ...
    if (ctx->call_controls_obsolete) {
        ...
        goto canceled;
    }
    ...
canceled:
    while (damon_is_running(ctx))
        schedule_timeout_idle(msecs_to_jiffies(100));
    return -ECANCELED;
}

Since damon_is_running() returns true (because ctx->kdamond is set), and the new healthy thread will run indefinitely, will this task loop forever in TASK_IDLE?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
author maintainer
#6 lore raw

Re: [RFC PATCH 2/2] mm/damon/lru_sort: skip damon_call() if ctx has not started

Quoted 40 lines · On Sun, 02 Aug 2026 19:08:37 +0000 [email protected] wrote:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] A TOCTOU race between damon_start() and damon_call() can cause damon_call() to loop indefinitely. --

--- Patch [2]: [RFC PATCH 2/2] mm/damon/lru_sort: skip damon_call() if ctx has not started --- commit 9a410362ed5a4418640b6bf73c4ee49300c9dc16
Author: SJ Park <[email protected]>

mm/damon/lru_sort: skip damon_call() if ctx has not started

This commit prevents damon_call() from hanging when the DAMON context is initialized but not yet started. It introduces a boolean flag to track whether the context has ever successfully started, and skips the call if it has not.

Quoted 4 lines
mm/damon/lru_sort.c +0 -0
index 7e077084cb03a..7df45f9a0b3ae 100644

[ ... ]

Quoted 4 lines
mm/damon/lru_sort.c +0 -0
@@ -364,11 +366,8 @@ static int damon_lru_sort_commit_inputs_store(const char *val,
 	if (!commit_inputs_request)
 		return 0;
 

mm/damon/lru_sort.c static int damon_lru_sort_commit_inputs_store(const char *val, · line 373

Quoted 10 lines
mm/damon/lru_sort.c +2 -5
-	/*
-	 * Skip damon_call() if ctx is not initialized to avoid
-	 * NULL pointer dereference.
-	 */
-	if (!ctx)
+	/* Skip damon_call() if ctx has not successfully started. */
+	if (!damon_lru_sort_damon_has_started)
 		return -EINVAL;
 
 	err = damon_call(ctx, &control);

[Severity: High]
This is a pre-existing issue, but could there be a TOCTOU race here between damon_start() and damon_call() that causes damon_call() to loop indefinitely?

Quoted 4 lines

If a user starts DAMON (setting damon_lru_sort_damon_has_started = true), stops it (leaving call_controls_obsolete = true), and then concurrently starts DAMON again while calling damon_call() via commit_inputs, can damon_call() get stuck?

There is no such concurrent calls.

Quoted 27 lines

Looking at mm/damon/core.c, __damon_start() sets ctx->kdamond before the new thread clears ctx->call_controls_obsolete:

mm/damon/core.c:__damon_start() {
    ...
    ctx->kdamond = kthread_run(kdamond_fn, ctx, "kdamond.%d", nr_running_ctxs);
    ...
}

If damon_call() runs concurrently, it checks call_controls_obsolete without holding kdamond_lock, sees the stale true value, and jumps to the canceled label:

mm/damon/core.c:damon_call() {
    ...
    if (ctx->call_controls_obsolete) {
        ...
        goto canceled;
    }
    ...
canceled:
    while (damon_is_running(ctx))
        schedule_timeout_idle(msecs_to_jiffies(100));
    return -ECANCELED;
}
Quoted 3 lines

Since damon_is_running() returns true (because ctx->kdamond is set), and the new healthy thread will run indefinitely, will this task loop forever in TASK_IDLE?

So the issue cannot happen in real. But good point. Maybe the API or the documentation can be safer. I will add this to my TODO list.

Quoted 3 lines
Signature · 2 lines
-- 
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2

Thanks,
SJ