Skip to contentSkip to conversation

mm/damon: fix damon_call()-related leak and deadlock

RFC PATCH In review SeongJae Park 1 participant 7 messages Mar 26

Subsystems (3)
lore
Share & tools
lore

SeongJae Park

author maintainer
#0 lore raw

cover mm/damon: fix damon_call()-related leak and deadlock

DAMON_SYSFS can leak memory due to improper handling of damon_call() failure. It can also cause a deadlock due to a race between damon_call() and kdamond_fn() termination. Fix those.

SeongJae Park (2):
mm/damon/sysfs: dealloc repeat_call_control if damon_call() fails mm/damon/core: fix damon_call() vs kdamond_fn() exit race deadlock

 include/linux/damon.h |  1 +
 mm/damon/core.c       | 41 ++++++++++-------------------------------
 mm/damon/sysfs.c      |  3 ++-
 3 files changed, 13 insertions(+), 32 deletions(-)

base-commit: 2b3fbc1796d335685d9b7a825c621914a1c97d1d

Signature · 1 line
2.47.3
author maintainer
#1 lore raw

RFC PATCH 1/2 mm/damon/sysfs: dealloc repeat_call_control if damon_call() fails

damon_call() for repeat_call_control of DAMON_SYSFS could fail if somehow the kdamond is stopped before the damon_call(). It could happen, for example, when te damon context was made for monitroing of a virtual address processes, and the process is terminated immediately, before the damon_call() invocation. In the case, the dyanmically allocated repeat_call_control is not deallocated and leaked.

Fix the leak by deallocating the repeat_call_control under the damon_call() failure.

This issue is discovered by sashiko [1].

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

---
 mm/damon/sysfs.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
mm/damon/sysfs.c +2 -1
index 6a44a2f3d8fc9..eefa959aa30ae 100644
@@ -1670,7 +1670,8 @@ static int damon_sysfs_turn_damon_on(struct damon_sysfs_kdamond *kdamond)
 	repeat_call_control->data = kdamond;
 	repeat_call_control->repeat = true;
 	repeat_call_control->dealloc_on_cancel = true;
-	damon_call(ctx, repeat_call_control);
+	if (damon_call(ctx, repeat_call_control))
+		kfree(repeat_call_control);
 	return err;
 }
 
Signature · 1 line
2.47.3
author maintainer
#2 lore raw

RFC PATCH 2/2 mm/damon/core: fix damon_call() vs kdamond_fn() exit race deadlock

In reply to SeongJae Park

When kdamond_fn() main loop is finished, the function cancels all remaining damon_call() requests and unset the damon_ctx->kdamond so that API callers can show the context is terminated. damon_call() adds the caller's request to the queue first. After that, it shows if the kdamond is of the damon_ctx is still running (damon_ctx->kdamond is set). Only if the kdamond is running, damon_call() starts waiting for the kdamond's handling of the newly added request.

The damon_call() requests registration and damon_ctx->kdamond unset are protected by different mutexes, though. Hence, damon_call() could race with damon_ctx->kdamond unset, and results in deadlocks.

For example, let's suppose kdamond successfully finished the damon_call() requests cancelling. Right after that, damon_call() is called for the context. It registers the new request, and show the context is still running, because damon_ctx->kdamond unset is not yet done. Hence the damon_call() caller starts waiting for the handling of the request. However, the kdamond is already on the termination steps, so never handles the new request. As a result, the damon_call() caller threads infinitely waits.

Fix this by introducing another damon_ctx field, namely call_controls_obsolete. It is protected by the damon_ctx->call_controls_lock, which protects damon_call() registration. Initialize (unset) it in kdamond_init_ctx() and set just before the cancelling of remaining damon_call() is executed. damon_call() reads the obsolete field under the lock and avoid adding a new request.

After this change, only requests that guaranteed to be handled or cancelled are registered. Hence the after-registration DAMON context termination check is no more needed. Remove it together.

The issue is found by sashiko [1].

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

Fixes 42b7491af14c ("mm/damon/core: introduce damon_call()")
Cc <[email protected]> # 6.14.x
---
 include/linux/damon.h |  1 +
 mm/damon/core.c       | 41 ++++++++++-------------------------------
 2 files changed, 11 insertions(+), 31 deletions(-)
include/linux/damon.h +1 -0
index d9a3babbafc16..5129de70e7b70 100644
@@ -818,6 +818,7 @@ struct damon_ctx {
 
 	/* lists of &struct damon_call_control */
 	struct list_head call_controls;
+	bool call_controls_obsolete;
 	struct mutex call_controls_lock;
 
 	struct damos_walk_control *walk_control;
mm/damon/core.c +10 -31

67 lines not on this page · raw message

Signature · 1 line
2.47.3
author maintainer
#3 lore raw

Re: (sashiko review) [RFC PATCH 2/2] mm/damon/core: fix damon_call() vs kdamond_fn() exit race deadlock

Forwarding Sashiko review for doing discussions via mails.

# review url: https://sashiko.dev/#/patchset/[email protected]
# start of sashiko.dev inline review
commit b886bf00a72b1462f29533c91c431cd2ba3fa3a6
Author: SeongJae Park <[email protected]>
mm/damon/core: fix damon_call() vs kdamond_fn() exit race deadlock

This commit introduces a call_controls_obsolete flag to protect damon_call() from racing with the kdamond termination sequence. This replaces the previous after-registration damon_is_running() check.

Quoted 4 lines
mm/damon/core.c +0 -0
index db6c67e52d2b8..a2b553e2c5a81 100644

[ ... ]

mm/damon/core.c int damon_call(struct damon_ctx *ctx, struct damon_call_control *control) · line 1635

Quoted 12 lines
mm/damon/core.c +4 -2
@@ -1629,10 +1600,12 @@ int damon_call(struct damon_ctx *ctx, struct damon_call_control *control)
 	INIT_LIST_HEAD(&control->list);
 
 	mutex_lock(&ctx->call_controls_lock);
+	if (ctx->call_controls_obsolete) {
+		mutex_unlock(&ctx->call_controls_lock);
+		return -ECANCELED;
+	}
 	list_add_tail(&control->list, &ctx->call_controls);
 	mutex_unlock(&ctx->call_controls_lock);
-	if (!damon_is_running(ctx))
-		return damon_call_handle_inactive_ctx(ctx, control);

Does this removal cause an indefinite wait if damon_call() is invoked on an unstarted context?

For a newly allocated damon_ctx, call_controls_obsolete is zero-initialized to false. Without the damon_is_running() check, if a caller invokes damon_call() before damon_start(), it appears the request will be queued and block on wait_for_completion() indefinitely since no kdamond thread is running to handle or cancel it.

Quoted 3 lines
mm/damon/core.c +0 -0
 	if (control->repeat)
 		return 0;
 	wait_for_completion(&control->completion);

[ ... ]

mm/damon/core.c static int kdamond_fn(void *data) · line 3045

Quoted 9 lines
mm/damon/core.c +3 -0
@@ -3062,6 +3038,9 @@ static int kdamond_fn(void *data)
 	damon_destroy_targets(ctx);
 
 	kfree(ctx->regions_score_histogram);
+	mutex_lock(&ctx->call_controls_lock);
+	ctx->call_controls_obsolete = true;
+	mutex_unlock(&ctx->call_controls_lock);
 	kdamond_call(ctx, true);
 	damos_walk_cancel(ctx);

Can this sequence also cause a spurious -ECANCELED failure when a context is restarted?

When restarting a context, kdamond_fn() signals kdamond_started before calling kdamond_init_ctx():

        complete(&ctx->kdamond_started);
        kdamond_init_ctx(ctx);

If damon_start() unblocks and immediately invokes damon_call(), couldn't it observe the stale call_controls_obsolete == true from the previous run and return -ECANCELED, since kdamond_init_ctx() has not yet reset the flag?

Also, is damos_walk() vulnerable to the same termination race being fixed here for damon_call()?

In kdamond_fn(), damos_walk_cancel() processes cancellations before ctx->kdamond is set to NULL. If damos_walk() is called during this window, it checks damon_is_running() (which still returns true), sets the walk control, and blocks on wait_for_completion().

Since cancellations are already processed, will it wait indefinitely?

# end of sashiko.dev inline review
# review url: https://sashiko.dev/#/patchset/[email protected]
#
# hkml [1] generated a draft of this mail.  It can be regenerated
# using below command:
#
#     hkml patch sashiko_dev --for_forwarding \
#             [email protected]
#
# [1] https://github.com/sjp38/hackermail

Sent using hkml (https://github.com/sjp38/hackermail)

author maintainer
#4 lore raw

Re: (sashiko review) [RFC PATCH 2/2] mm/damon/core: fix damon_call() vs kdamond_fn() exit race deadlock

Quoted 35 lines · On Thu, 26 Mar 2026 06:43:29 -0700 SeongJae Park <[email protected]> wrote:

Forwarding Sashiko review for doing discussions via mails.

# review url: https://sashiko.dev/#/patchset/[email protected]
# start of sashiko.dev inline review
commit b886bf00a72b1462f29533c91c431cd2ba3fa3a6
Author: SeongJae Park <[email protected]>
mm/damon/core: fix damon_call() vs kdamond_fn() exit race deadlock

This commit introduces a call_controls_obsolete flag to protect damon_call() from racing with the kdamond termination sequence. This replaces the previous after-registration damon_is_running() check.

Quoted 4 lines
mm/damon/core.c +0 -0
index db6c67e52d2b8..a2b553e2c5a81 100644

[ ... ]

mm/damon/core.c int damon_call(struct damon_ctx *ctx, struct damon_call_control *control) · line 1635

Quoted 12 lines
mm/damon/core.c +4 -2
@@ -1629,10 +1600,12 @@ int damon_call(struct damon_ctx *ctx, struct damon_call_control *control)
 	INIT_LIST_HEAD(&control->list);
 
 	mutex_lock(&ctx->call_controls_lock);
+	if (ctx->call_controls_obsolete) {
+		mutex_unlock(&ctx->call_controls_lock);
+		return -ECANCELED;
+	}
 	list_add_tail(&control->list, &ctx->call_controls);
 	mutex_unlock(&ctx->call_controls_lock);
-	if (!damon_is_running(ctx))
-		return damon_call_handle_inactive_ctx(ctx, control);

Does this removal cause an indefinite wait if damon_call() is invoked on an unstarted context?

Quoted 5 lines

For a newly allocated damon_ctx, call_controls_obsolete is zero-initialized to false. Without the damon_is_running() check, if a caller invokes damon_call() before damon_start(), it appears the request will be queued and block on wait_for_completion() indefinitely since no kdamond thread is running to handle or cancel it.

Yes. But it is clearly wrong usage of this function. I will add a comment clarifying this, e.g., "this function shouldn't be called for unstarted DAMON context. In the case, it could be indefinitely sleep."

Quoted 21 lines
Quoted 4 lines
mm/damon/core.c +0 -0
 	if (control->repeat)
 		return 0;
 	wait_for_completion(&control->completion);

[ ... ]

mm/damon/core.c static int kdamond_fn(void *data) · line 3045

Quoted 9 lines
mm/damon/core.c +3 -0
@@ -3062,6 +3038,9 @@ static int kdamond_fn(void *data)
 	damon_destroy_targets(ctx);
 
 	kfree(ctx->regions_score_histogram);
+	mutex_lock(&ctx->call_controls_lock);
+	ctx->call_controls_obsolete = true;
+	mutex_unlock(&ctx->call_controls_lock);
 	kdamond_call(ctx, true);
 	damos_walk_cancel(ctx);

Can this sequence also cause a spurious -ECANCELED failure when a context is restarted?

When restarting a context, kdamond_fn() signals kdamond_started before calling kdamond_init_ctx():

Quoted 6 lines
        complete(&ctx->kdamond_started);
        kdamond_init_ctx(ctx);

If damon_start() unblocks and immediately invokes damon_call(), couldn't it observe the stale call_controls_obsolete == true from the previous run and return -ECANCELED, since kdamond_init_ctx() has not yet reset the flag?

Good catch, I will do the call_controls_obsolete unset before the complete() call.

Quoted 10 lines

Also, is damos_walk() vulnerable to the same termination race being fixed here for damon_call()?

In kdamond_fn(), damos_walk_cancel() processes cancellations before ctx->kdamond is set to NULL. If damos_walk() is called during this window, it checks damon_is_running() (which still returns true), sets the walk control, and blocks on wait_for_completion().

Since cancellations are already processed, will it wait indefinitely?

Yes. I'm working on it for another patch.

Thanks,
SJ

[...]

author maintainer
#5 lore raw

Re: (sashiko status) [RFC PATCH 0/2] mm/damon: fix damon_call()-related leak and deadlock

In reply to SeongJae Park

Forwarding sashiko.dev review status for this thread.

# review url: https://sashiko.dev/#/patchset/[email protected]

- [RFC PATCH 1/2] mm/damon/sysfs: dealloc repeat_call_control if damon_call() fails
- status: Reviewed
- review: No issues found.
- [RFC PATCH 2/2] mm/damon/core: fix damon_call() vs kdamond_fn() exit race deadlock
- status: Reviewed
- review: ISSUES MAY FOUND

# hkml [1] generated a draft of this mail.  It can be regenerated
# using below command:
#
#     hkml patch sashiko_dev --thread_status --for_forwarding \
#             [email protected]
#
# [1] https://github.com/sjp38/hackermail

Sent using hkml (https://github.com/sjp38/hackermail)

author maintainer
#6 lore raw

Re: (sashiko status) [RFC PATCH 0/2] mm/damon: fix damon_call()-related leak and deadlock

Quoted 10 lines · On Thu, 26 Mar 2026 06:42:08 -0700 SeongJae Park <[email protected]> wrote:

Forwarding sashiko.dev review status for this thread.

# review url: https://sashiko.dev/#/patchset/[email protected]

- [RFC PATCH 1/2] mm/damon/sysfs: dealloc repeat_call_control if damon_call() fails
- status: Reviewed
- review: No issues found.
- [RFC PATCH 2/2] mm/damon/core: fix damon_call() vs kdamond_fn() exit race deadlock
- status: Reviewed
- review: ISSUES MAY FOUND

The patches are hotfixes, and have no reason to be one series.

I will send the first patch as an individual one without RFC, soon.

For the second patch, I will go one more RFC round.

Thanks,
SJ

[...]