Skip to contentSkip to conversation

FAILED: patch "[PATCH] mm/damon/core: fix unconditionally skip last region" failed to apply to 6.12-stable tree

PATCH In review [email protected] 5 participants 23 messages Sep 29 – Oct 11

Subsystems (3)
lore
Share & tools
lore

[email protected]

author maintainer
#0 lore raw

The patch below does not apply to the 6.12-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.

To reproduce the conflict and resubmit, you may use the following commands:

git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.12.y
git checkout FETCH_HEAD
git cherry-pick -x b3723b596b548c837a766aae3553c14a7b15af2b
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026092948-agency-overhand-3fd1@gregkh' --subject-prefix 'PATCH 6.12.y' 'HEAD^..'

Possible dependencies:

thanks,

greg k-h

------------------ original commit in Linus's tree ------------------

From b3723b596b548c837a766aae3553c14a7b15af2b Mon Sep 17 00:00:00 2001
From: Liew Rui Yan <[email protected]>
Date: Tue, 8 Sep 2026 06:47:38 -0700
Subject: [PATCH] mm/damon/core: fix unconditionally skip last region

Once quota set, the charge_{target,addr}_from unconditionally skips and resets at the last region of the tracked target, so the last region can be skipped even when it has not been processed.

Example:

1. Target has 2 regions: R1 (0-100 bytes) and R2 (100-200 bytes).
2. Quota is configured to process only 100 bytes per window.
3. Window 1: Processes R1 (0-100). Quota is full. charge_{target, addr}_from is saved at (Target, 100).
4. Window 2: The loop reaches R2. Because R2 is damon_last_region(t), the old code unconditionally returns true, skipping R2 entirely and resetting the charge_{target,addr}_from.

Result: R2 is permanently skipped even though it has never been processed.

However, it is important to note that this is a very minor issue. This is because it is triggered only when the previous window saved/kept charge_{target,addr}_from, and in the next window, all regions except the last region were skipped by damos_skip_charged_region().

Fix this by only resetting the charge_{target,addr}_from when last region is reached, only skipping when it is applied or cannot split.

Fixes 50585192bc2e ("mm/damon/schemes: skip already charged targets and regions")
Cc <[email protected]> # v5.16.x
mm/damon/core.c +14 -11
index 2b294fb46648..06a253df8d4b 100644
@@ -2342,36 +2342,39 @@ static bool damos_skip_charged_region(struct damon_target *t,
 {
 	struct damos_quota *quota = &s->quota;
 	unsigned long sz_to_skip;
+	bool skip = false;
 
 	/* Skip previously charged regions */
 	if (quota->charge_target_from) {
 		if (t != quota->charge_target_from)
 			return true;
-		if (r == damon_last_region(t)) {
-			quota->charge_target_from = NULL;
-			quota->charge_addr_from = 0;
-			return true;
-		}
 		if (quota->charge_addr_from &&
-				r->ar.end <= quota->charge_addr_from)
-			return true;
+				r->ar.end <= quota->charge_addr_from) {
+			skip = true;
+			goto out;
+		}
 
 		if (quota->charge_addr_from && r->ar.start <
 				quota->charge_addr_from) {
 			sz_to_skip = ALIGN_DOWN(quota->charge_addr_from -
 					r->ar.start, min_region_sz);
 			if (!sz_to_skip) {
-				if (damon_sz_region(r) <= min_region_sz)
-					return true;
+				if (damon_sz_region(r) <= min_region_sz) {
+					skip = true;
+					goto out;
+				}
 				sz_to_skip = min_region_sz;
 			}
 			damon_split_region_at(t, r, sz_to_skip);
-			return true;
+			skip = true;
 		}
+	}
+out:
+	if (r == damon_last_region(t)) {
 		quota->charge_target_from = NULL;
 		quota->charge_addr_from = 0;
 	}
-	return false;
+	return skip;
 }
 
 static void damos_update_stat(struct damos *s,
maintainer
#1 lore raw

PATCH 1/3 mm/damon/core: fix wasteful CPU calls by skipping non-existent targets

From: Enze Li <[email protected]>

[ Upstream commit 6e4930e33329eec80dd245f28b52202271f5fb28 ]

Currently, DAMON does not proactively clean up invalid monitoring targets during its runtime. When some monitored processes exit, DAMON continues to make the following unnecessary function calls,

--damon_for_each_target--
--damon_for_each_region--
damon_do_apply_schemes
damos_apply_scheme
damon_va_apply_scheme
damos_madvise
damon_get_mm

it is only in the damon_get_mm() function that it may finally discover the target no longer exists, which wastes CPU resources. A simple idea is to check for the existence of monitoring targets within the kdamond_need_stop() function and promptly clean up non-existent targets.

However, SJ pointed out that this approach is problematic because the online commit logic incorrectly uses list indices to update the monitoring state. This can lead to data loss if the target list is changed concurrently. Meanwhile, SJ suggests checking for target existence at the damon_for_each_target level, and if a target does not exist, simply skip it and proceed to the next one.

Stable-dep-of: b3723b596b54 ("mm/damon/core: fix unconditionally skip last region")

---
 mm/damon/core.c | 3 +++
 1 file changed, 3 insertions(+)
mm/damon/core.c +3 -0
index 32737cc5f877b..b3a28a857d075 100644
@@ -1749,6 +1749,9 @@ static void kdamond_apply_schemes(struct damon_ctx *c)
 		return;
 
 	damon_for_each_target(t, c) {
+		if (c->ops.target_valid && c->ops.target_valid(t) == false)
+			continue;
+
 		damon_for_each_region_safe(r, next_r, t)
 			damon_do_apply_schemes(c, t, r);
 	}
Signature · 1 line
2.53.0
maintainer
#2 lore raw

PATCH 2/3 mm/damon: rename DAMON_MIN_REGION to DAMON_MIN_REGION_SZ

From: SeongJae Park <[email protected]>

[ Upstream commit dfb1b0c9dc0d61e422905640e1e7334b3cf6f384 ]

The macro is for the default minimum size of each DAMON region. There was a case that a reader was confused if it is the minimum number of total DAMON regions, which is set on damon_attrs->min_nr_regions. Make the name more explicit.

Stable backport preparation for b3723b596b548 ("mm/damon/core: fix unconditionally skip last region"):

Rename all uses present in 6.12, including the core KUnit override and tests. Drop changes to address-unit/minimum-size context fields and the four-argument damon_set_regions() calls, which do not exist in this tree.

Match the target's quota helper preimage by passing the fixed region size as min_region_sz and incorporating the existing-function changes from 1745ccbd2907 ("mm/damon/core: do non-safe region walk on kdamond_apply_schemes()"). The region walk must visit newly split tails when the helper returns true for the already charged prefix. Preserve the existing target-validity check and leave the last-region fix to the target commit. No new functions or context fields are introduced.

[ sashal: Reduced backport -- upstream dfb1b0c9dc0d6 touches 7 file(s), this
backport carries 4. Not backported here:
mm/damon/lru_sort.c
mm/damon/reclaim.c
mm/damon/sysfs.c
mm/damon/tests/vaddr-kunit.h
This note is generated from the file lists only; see the resolution record for the reasoning. ]

Stable-dep-of: b3723b596b54 ("mm/damon/core: fix unconditionally skip last region")

---
 include/linux/damon.h       |  2 +-
 mm/damon/core.c             | 64 +++++++++++++++++++------------------
 mm/damon/tests/core-kunit.h | 36 ++++++++++-----------
 mm/damon/vaddr.c            | 22 ++++++-------
 4 files changed, 63 insertions(+), 61 deletions(-)
include/linux/damon.h +1 -1
index 4688a10eca89b..a634d47eaa8bc 100644
@@ -15,7 +15,7 @@
 #include <linux/random.h>
 
 /* Minimal region size.  Every damon_region is aligned by this. */
-#define DAMON_MIN_REGION	PAGE_SIZE
+#define DAMON_MIN_REGION_SZ	PAGE_SIZE
 /* Max priority score for DAMON-based operation schemes */
 #define DAMOS_MAX_SCORE		(99)
 
mm/damon/core.c +33 -31

168 lines not on this page · raw message

mm/damon/tests/core-kunit.h +18 -18
index ff9f73579f7b3..a97a4326a227e 100644
@@ -530,10 +530,10 @@ static void damos_test_filter_out(struct kunit *test)
 
 	f = damos_new_filter(DAMOS_FILTER_TYPE_ADDR, true);
 	f->addr_range = (struct damon_addr_range){
-		.start = DAMON_MIN_REGION * 2, .end = DAMON_MIN_REGION * 6};
+		.start = DAMON_MIN_REGION_SZ * 2, .end = DAMON_MIN_REGION_SZ * 6};
 
 	t = damon_new_target();
-	r = damon_new_region(DAMON_MIN_REGION * 3, DAMON_MIN_REGION * 5);
+	r = damon_new_region(DAMON_MIN_REGION_SZ * 3, DAMON_MIN_REGION_SZ * 5);
 	damon_add_region(r, t);
 
 	/* region in the range */
@@ -541,45 +541,45 @@ static void damos_test_filter_out(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 1);
 
 	/* region before the range */
-	r->ar.start = DAMON_MIN_REGION * 1;
-	r->ar.end = DAMON_MIN_REGION * 2;
+	r->ar.start = DAMON_MIN_REGION_SZ * 1;
+	r->ar.end = DAMON_MIN_REGION_SZ * 2;
 	KUNIT_EXPECT_FALSE(test, __damos_filter_out(NULL, t, r, f));
 	KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 1);
 
 	/* region after the range */
-	r->ar.start = DAMON_MIN_REGION * 6;
-	r->ar.end = DAMON_MIN_REGION * 8;
+	r->ar.start = DAMON_MIN_REGION_SZ * 6;
+	r->ar.end = DAMON_MIN_REGION_SZ * 8;
 	KUNIT_EXPECT_FALSE(test, __damos_filter_out(NULL, t, r, f));
 	KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 1);
 
 	/* region started before the range */
-	r->ar.start = DAMON_MIN_REGION * 1;
-	r->ar.end = DAMON_MIN_REGION * 4;
+	r->ar.start = DAMON_MIN_REGION_SZ * 1;
+	r->ar.end = DAMON_MIN_REGION_SZ * 4;
 	KUNIT_EXPECT_FALSE(test, __damos_filter_out(NULL, t, r, f));
 	/* filter should have split the region */
-	KUNIT_EXPECT_EQ(test, r->ar.start, DAMON_MIN_REGION * 1);
-	KUNIT_EXPECT_EQ(test, r->ar.end, DAMON_MIN_REGION * 2);
+	KUNIT_EXPECT_EQ(test, r->ar.start, DAMON_MIN_REGION_SZ * 1);
+	KUNIT_EXPECT_EQ(test, r->ar.end, DAMON_MIN_REGION_SZ * 2);
 	KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 2);
 	if (damon_nr_regions(t) != 2)
 		goto out;
 	r2 = damon_next_region(r);
-	KUNIT_EXPECT_EQ(test, r2->ar.start, DAMON_MIN_REGION * 2);
-	KUNIT_EXPECT_EQ(test, r2->ar.end, DAMON_MIN_REGION * 4);
+	KUNIT_EXPECT_EQ(test, r2->ar.start, DAMON_MIN_REGION_SZ * 2);
+	KUNIT_EXPECT_EQ(test, r2->ar.end, DAMON_MIN_REGION_SZ * 4);
 	damon_destroy_region(r2, t);
 
 	/* region started in the range */
-	r->ar.start = DAMON_MIN_REGION * 2;
-	r->ar.end = DAMON_MIN_REGION * 8;
+	r->ar.start = DAMON_MIN_REGION_SZ * 2;
+	r->ar.end = DAMON_MIN_REGION_SZ * 8;
 	KUNIT_EXPECT_TRUE(test, __damos_filter_out(NULL, t, r, f));
 	/* filter should have split the region */
-	KUNIT_EXPECT_EQ(test, r->ar.start, DAMON_MIN_REGION * 2);
-	KUNIT_EXPECT_EQ(test, r->ar.end, DAMON_MIN_REGION * 6);
+	KUNIT_EXPECT_EQ(test, r->ar.start, DAMON_MIN_REGION_SZ * 2);
+	KUNIT_EXPECT_EQ(test, r->ar.end, DAMON_MIN_REGION_SZ * 6);
 	KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 2);
 	if (damon_nr_regions(t) != 2)
 		goto out;
 	r2 = damon_next_region(r);
-	KUNIT_EXPECT_EQ(test, r2->ar.start, DAMON_MIN_REGION * 6);
-	KUNIT_EXPECT_EQ(test, r2->ar.end, DAMON_MIN_REGION * 8);
+	KUNIT_EXPECT_EQ(test, r2->ar.start, DAMON_MIN_REGION_SZ * 6);
+	KUNIT_EXPECT_EQ(test, r2->ar.end, DAMON_MIN_REGION_SZ * 8);
 	damon_destroy_region(r2, t);
 
 out:
mm/damon/vaddr.c +11 -11
index 089ee23e07de7..b423ce9da392d 100644
@@ -18,8 +18,8 @@
 #include "ops-common.h"
 
 #ifdef CONFIG_DAMON_VADDR_KUNIT_TEST
-#undef DAMON_MIN_REGION
-#define DAMON_MIN_REGION 1
+#undef DAMON_MIN_REGION_SZ
+#define DAMON_MIN_REGION_SZ 1
 #endif
 
 /*
@@ -74,7 +74,7 @@ static int damon_va_evenly_split_region(struct damon_target *t,
 
 	orig_end = r->ar.end;
 	sz_orig = damon_sz_region(r);
-	sz_piece = ALIGN_DOWN(sz_orig / nr_pieces, DAMON_MIN_REGION);
+	sz_piece = ALIGN_DOWN(sz_orig / nr_pieces, DAMON_MIN_REGION_SZ);
 
 	if (!sz_piece)
 		return -EINVAL;
@@ -157,12 +157,12 @@ static int __damon_va_three_regions(struct mm_struct *mm,
 		swap(first_gap, second_gap);
 
 	/* Store the result */
-	regions[0].start = ALIGN(start, DAMON_MIN_REGION);
-	regions[0].end = ALIGN(first_gap.start, DAMON_MIN_REGION);
-	regions[1].start = ALIGN(first_gap.end, DAMON_MIN_REGION);
-	regions[1].end = ALIGN(second_gap.start, DAMON_MIN_REGION);
-	regions[2].start = ALIGN(second_gap.end, DAMON_MIN_REGION);
-	regions[2].end = ALIGN(prev->vm_end, DAMON_MIN_REGION);
+	regions[0].start = ALIGN(start, DAMON_MIN_REGION_SZ);
+	regions[0].end = ALIGN(first_gap.start, DAMON_MIN_REGION_SZ);
+	regions[1].start = ALIGN(first_gap.end, DAMON_MIN_REGION_SZ);
+	regions[1].end = ALIGN(second_gap.start, DAMON_MIN_REGION_SZ);
+	regions[2].start = ALIGN(second_gap.end, DAMON_MIN_REGION_SZ);
+	regions[2].end = ALIGN(prev->vm_end, DAMON_MIN_REGION_SZ);
 
 	return 0;
 }
@@ -255,8 +255,8 @@ static void __damon_va_init_regions(struct damon_ctx *ctx,
 		sz += regions[i].end - regions[i].start;
 	if (ctx->attrs.min_nr_regions)
 		sz /= ctx->attrs.min_nr_regions;
-	if (sz < DAMON_MIN_REGION)
-		sz = DAMON_MIN_REGION;
+	if (sz < DAMON_MIN_REGION_SZ)
+		sz = DAMON_MIN_REGION_SZ;
 
 	/* Set the initial three regions of the target */
 	for (i = 0; i < 3; i++) {
Signature · 1 line
2.53.0
maintainer
#3 lore raw

Re: [PATCH 6.12.y 2/3] mm/damon: rename DAMON_MIN_REGION to DAMON_MIN_REGION_SZ

Hello Sasha,

Quoted 40 lines · On Tue, 29 Sep 2026 22:34:00 -0400 Sasha Levin <[email protected]> wrote:

From: SeongJae Park <[email protected]>

[ Upstream commit dfb1b0c9dc0d61e422905640e1e7334b3cf6f384 ]

The macro is for the default minimum size of each DAMON region. There was a case that a reader was confused if it is the minimum number of total DAMON regions, which is set on damon_attrs->min_nr_regions. Make the name more explicit.

Stable backport preparation for b3723b596b548 ("mm/damon/core: fix unconditionally skip last region"):

Rename all uses present in 6.12, including the core KUnit override and tests. Drop changes to address-unit/minimum-size context fields and the four-argument damon_set_regions() calls, which do not exist in this tree.

Match the target's quota helper preimage by passing the fixed region size as min_region_sz and incorporating the existing-function changes from 1745ccbd2907 ("mm/damon/core: do non-safe region walk on kdamond_apply_schemes()"). The region walk must visit newly split tails when the helper returns true for the already charged prefix. Preserve the existing target-validity check and leave the last-region fix to the target commit. No new functions or context fields are introduced.

[ sashal: Reduced backport -- upstream dfb1b0c9dc0d6 touches 7 file(s), this
backport carries 4. Not backported here:
mm/damon/lru_sort.c
mm/damon/reclaim.c
mm/damon/sysfs.c
mm/damon/tests/vaddr-kunit.h
This note is generated from the file lists only; see the resolution record for the reasoning. ]

Stable-dep-of: b3723b596b54 ("mm/damon/core: fix unconditionally skip last region")

---

[...]

Quoted 7 lines
mm/damon/core.c +1 -1
@@ -1339,15 +1342,14 @@ static bool damos_skip_charged_region(struct damon_target *t,
 		if (quota->charge_addr_from && r->ar.start <
 				quota->charge_addr_from) {
 			sz_to_skip = ALIGN_DOWN(quota->charge_addr_from -
-					r->ar.start, DAMON_MIN_REGION);
+					r->ar.start, min_region_sz);
 			if (!sz_to_skip) {

mm/damon/core.c static bool damos_skip_charged_region(struct damon_target *t, · line 1352

Quoted 10 lines
mm/damon/core.c +3 -4
-				if (damon_sz_region(r) <= DAMON_MIN_REGION)
+				if (damon_sz_region(r) <= min_region_sz)
 					return true;
-				sz_to_skip = DAMON_MIN_REGION;
+				sz_to_skip = min_region_sz;
 			}
 			damon_split_region_at(t, r, sz_to_skip);
-			r = damon_next_region(r);
-			*rp = r;
+			return true;

As I mentioned [1] on the backport of this patch for 6.18, this looks wrong to me. I will post a manual backport. Could we drop this series and use the manual backport instead?

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

Thanks,
SJ

[...]

maintainer
#4 lore raw

Re: [PATCH 6.12.y 2/3] mm/damon: rename DAMON_MIN_REGION to DAMON_MIN_REGION_SZ

On Wed, 30 Sep 2026 02:24:48 -0700 SJ Park <[email protected]> wrote:
[...]

Quoted 2 lines

I will post a manual backport. Could we drop this series and use the manual backport instead?

Just sent my manual backport:
https://lore.kernel.org/[email protected]

Thanks,
SJ

[...]

maintainer
#5 lore raw

PATCH 3/3 mm/damon/core: fix unconditionally skip last region

In reply to Sasha Levin

From: Liew Rui Yan <[email protected]>

[ Upstream commit b3723b596b548c837a766aae3553c14a7b15af2b ]

Once quota set, the charge_{target,addr}_from unconditionally skips and resets at the last region of the tracked target, so the last region can be skipped even when it has not been processed.

Example:

1. Target has 2 regions: R1 (0-100 bytes) and R2 (100-200 bytes).
2. Quota is configured to process only 100 bytes per window.
3. Window 1: Processes R1 (0-100). Quota is full. charge_{target, addr}_from is saved at (Target, 100).
4. Window 2: The loop reaches R2. Because R2 is damon_last_region(t), the old code unconditionally returns true, skipping R2 entirely and resetting the charge_{target,addr}_from.

Result: R2 is permanently skipped even though it has never been processed.

However, it is important to note that this is a very minor issue. This is because it is triggered only when the previous window saved/kept charge_{target,addr}_from, and in the next window, all regions except the last region were skipped by damos_skip_charged_region().

Fix this by only resetting the charge_{target,addr}_from when last region is reached, only skipping when it is applied or cannot split.

Fixes 50585192bc2e ("mm/damon/schemes: skip already charged targets and regions")
Cc <[email protected]> # v5.16.x
---
 mm/damon/core.c | 25 ++++++++++++++-----------
 1 file changed, 14 insertions(+), 11 deletions(-)
mm/damon/core.c +14 -11
index 53d479da2007e..2a24e817e5360 100644
@@ -1325,36 +1325,39 @@ static bool damos_skip_charged_region(struct damon_target *t,
 {
 	struct damos_quota *quota = &s->quota;
 	unsigned long sz_to_skip;
+	bool skip = false;
 
 	/* Skip previously charged regions */
 	if (quota->charge_target_from) {
 		if (t != quota->charge_target_from)
 			return true;
-		if (r == damon_last_region(t)) {
-			quota->charge_target_from = NULL;
-			quota->charge_addr_from = 0;
-			return true;
-		}
 		if (quota->charge_addr_from &&
-				r->ar.end <= quota->charge_addr_from)
-			return true;
+				r->ar.end <= quota->charge_addr_from) {
+			skip = true;
+			goto out;
+		}
 
 		if (quota->charge_addr_from && r->ar.start <
 				quota->charge_addr_from) {
 			sz_to_skip = ALIGN_DOWN(quota->charge_addr_from -
 					r->ar.start, min_region_sz);
 			if (!sz_to_skip) {
-				if (damon_sz_region(r) <= min_region_sz)
-					return true;
+				if (damon_sz_region(r) <= min_region_sz) {
+					skip = true;
+					goto out;
+				}
 				sz_to_skip = min_region_sz;
 			}
 			damon_split_region_at(t, r, sz_to_skip);
-			return true;
+			skip = true;
 		}
+	}
+out:
+	if (r == damon_last_region(t)) {
 		quota->charge_target_from = NULL;
 		quota->charge_addr_from = 0;
 	}
-	return false;
+	return skip;
 }
 
 static void damos_update_stat(struct damos *s,
Signature · 1 line
2.53.0
#6 lore raw

Re: [PATCH 6.12.y 3/3] mm/damon/core: fix unconditionally skip last region

Quoted 100 lines · Sasha Levin

From: Liew Rui Yan <[email protected]>

[ Upstream commit b3723b596b548c837a766aae3553c14a7b15af2b ]

Once quota set, the charge_{target,addr}_from unconditionally skips and resets at the last region of the tracked target, so the last region can be skipped even when it has not been processed.

Example:

1. Target has 2 regions: R1 (0-100 bytes) and R2 (100-200 bytes).
2. Quota is configured to process only 100 bytes per window.
3. Window 1: Processes R1 (0-100). Quota is full. charge_{target, addr}_from is saved at (Target, 100).
4. Window 2: The loop reaches R2. Because R2 is damon_last_region(t), the old code unconditionally returns true, skipping R2 entirely and resetting the charge_{target,addr}_from.

Result: R2 is permanently skipped even though it has never been processed.

However, it is important to note that this is a very minor issue. This is because it is triggered only when the previous window saved/kept charge_{target,addr}_from, and in the next window, all regions except the last region were skipped by damos_skip_charged_region().

Fix this by only resetting the charge_{target,addr}_from when last region is reached, only skipping when it is applied or cannot split.

Fixes 50585192bc2e ("mm/damon/schemes: skip already charged targets and regions")
Cc <[email protected]> # v5.16.x
---
 mm/damon/core.c | 25 ++++++++++++++-----------
 1 file changed, 14 insertions(+), 11 deletions(-)
mm/damon/core.c +14 -11
index 53d479da2007e..2a24e817e5360 100644
@@ -1325,36 +1325,39 @@ static bool damos_skip_charged_region(struct damon_target *t,
 {
 	struct damos_quota *quota = &s->quota;
 	unsigned long sz_to_skip;
+	bool skip = false;
 
 	/* Skip previously charged regions */
 	if (quota->charge_target_from) {
 		if (t != quota->charge_target_from)
 			return true;
-		if (r == damon_last_region(t)) {
-			quota->charge_target_from = NULL;
-			quota->charge_addr_from = 0;
-			return true;
-		}
 		if (quota->charge_addr_from &&
-				r->ar.end <= quota->charge_addr_from)
-			return true;
+				r->ar.end <= quota->charge_addr_from) {
+			skip = true;
+			goto out;
+		}
 
 		if (quota->charge_addr_from && r->ar.start <
 				quota->charge_addr_from) {
 			sz_to_skip = ALIGN_DOWN(quota->charge_addr_from -
 					r->ar.start, min_region_sz);
 			if (!sz_to_skip) {
-				if (damon_sz_region(r) <= min_region_sz)
-					return true;
+				if (damon_sz_region(r) <= min_region_sz) {
+					skip = true;
+					goto out;
+				}
 				sz_to_skip = min_region_sz;
 			}
 			damon_split_region_at(t, r, sz_to_skip);
-			return true;
+			skip = true;
 		}
+	}
+out:
+	if (r == damon_last_region(t)) {
 		quota->charge_target_from = NULL;
 		quota->charge_addr_from = 0;
 	}
-	return false;
+	return skip;
 }
 
 static void damos_update_stat(struct damos *s,
Signature · 1 line
2.53.0

Did not apply :(

maintainer
#7 lore raw

PATCH 1/1 mm/damon/core: fix unconditionally skip last region

In reply to [email protected]

From: Liew Rui Yan <[email protected]>

Once quota set, the charge_{target,addr}_from unconditionally skips and resets at the last region of the tracked target, so the last region can be skipped even when it has not been processed.

Example:

1. Target has 2 regions: R1 (0-100 bytes) and R2 (100-200 bytes).
2. Quota is configured to process only 100 bytes per window.
3. Window 1: Processes R1 (0-100). Quota is full. charge_{target, addr}_from is saved at (Target, 100).
4. Window 2: The loop reaches R2. Because R2 is damon_last_region(t), the old code unconditionally returns true, skipping R2 entirely and resetting the charge_{target,addr}_from.

Result: R2 is permanently skipped even though it has never been processed.

However, it is important to note that this is a very minor issue. This is because it is triggered only when the previous window saved/kept charge_{target,addr}_from, and in the next window, all regions except the last region were skipped by damos_skip_charged_region().

Fix this by only resetting the charge_{target,addr}_from when last region is reached, only skipping when it is applied or cannot split.

Fixes 50585192bc2e ("mm/damon/schemes: skip already charged targets and regions")
Cc <[email protected]> # v5.16.x
(cherry picked from commit b3723b596b548c837a766aae3553c14a7b15af2b)
---
 mm/damon/core.c | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)
mm/damon/core.c +10 -8
index 32737cc5f877b..802005d9186e8 100644
@@ -1322,19 +1322,17 @@ static bool damos_skip_charged_region(struct damon_target *t,
 	struct damon_region *r = *rp;
 	struct damos_quota *quota = &s->quota;
 	unsigned long sz_to_skip;
+	bool skip = false;
 
 	/* Skip previously charged regions */
 	if (quota->charge_target_from) {
 		if (t != quota->charge_target_from)
 			return true;
-		if (r == damon_last_region(t)) {
-			quota->charge_target_from = NULL;
-			quota->charge_addr_from = 0;
-			return true;
-		}
 		if (quota->charge_addr_from &&
-				r->ar.end <= quota->charge_addr_from)
-			return true;
+				r->ar.end <= quota->charge_addr_from) {
+			skip = true;
+			goto out;
+		}
 
 		if (quota->charge_addr_from && r->ar.start <
 				quota->charge_addr_from) {
@@ -1348,11 +1346,15 @@ static bool damos_skip_charged_region(struct damon_target *t,
 			damon_split_region_at(t, r, sz_to_skip);
 			r = damon_next_region(r);
 			*rp = r;
+			skip = false;
 		}
+	}
+out:
+	if (r == damon_last_region(t)) {
 		quota->charge_target_from = NULL;
 		quota->charge_addr_from = 0;
 	}
-	return false;
+	return skip;
 }
 
 static void damos_update_stat(struct damos *s,
Signature · 1 line
2.47.3
#8 lore raw

Re: [PATCH 6.12.y] mm/damon/core: fix unconditionally skip last region

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Critical] damon: OOB write and list corruption when damon_split_region_at() fails - [High] damon…

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] damon: OOB write and list corruption when damon_split_region_at() fails
- [High] damon: permanent skipping of targets due to charge_target_from state leak --

commit 035ce5241b6b256409f7ad3d4de6c01dbe7e8563
Author: Liew Rui Yan <[email protected]> mm/damon/core: fix unconditionally skip last region

This patch modifies the skip logic in DAMON to only reset the charge_target_from state when the last region is reached. It also updates the function to skip the region only if it is applied or cannot be split, rather than unconditionally skipping the last region.

Quoted 4 lines
mm/damon/core.c +0 -0
index 32737cc5f877b..802005d9186e8 100644

[ ... ]

mm/damon/core.c static bool damos_skip_charged_region(struct damon_target *t, · line 1348

Quoted 4 lines
mm/damon/core.c +0 -0
@@ -1348,11 +1346,15 @@ static bool damos_skip_charged_region(struct damon_target *t,
 			damon_split_region_at(t, r, sz_to_skip);
 			r = damon_next_region(r);
 			*rp = r;

[Severity: Critical]
Could this lead to out-of-bounds writes and list corruption if damon_split_region_at() fails?

This patch removes the early exit condition for the last region, exposing it to damon_split_region_at(). If memory allocation fails, the split function returns silently without modifying the list:

mm/damon/core.c:damon_split_region_at() {
	new = damon_new_region(r->ar.start + sz_r, r->ar.end);
	if (!new)
		return;

...
}

The pointer is then unconditionally advanced with r = damon_next_region(r). If r was the last region, damon_next_region() wraps around to the list head (&t->regions_list), generating an invalid struct damon_region pointer pointing outside valid regions. Subsequent writes to this pointer will corrupt the target sibling list integrity.

mm/damon/core.c static bool damos_skip_charged_region(struct damon_target *t, · line 1353

Quoted 5 lines
mm/damon/core.c +4 -0
+			skip = false;
 		}
+	}
+out:
+	if (r == damon_last_region(t)) {

[Severity: High]
Does the early return inside the region size check bypass this cleanup logic?

If sz_to_skip evaluates to 0 and the region size is <= DAMON_MIN_REGION, the code hits an early return:

mm/damon/core.c:damos_skip_charged_region() {
...

		sz_to_skip = ALIGN_DOWN(quota->charge_addr_from -
				r->ar.start, DAMON_MIN_REGION);
		if (!sz_to_skip) {
			if (damon_sz_region(r) <= DAMON_MIN_REGION)
				return true;

...
}

Because this early return bypasses the new out label, quota->charge_target_from remains populated if this occurs on the last region. For all subsequent targets, the check
if (t != quota->charge_target_from) evaluates to true and indefinitely skips those regions, causing targets to be starved.

Quoted 6 lines
mm/damon/core.c +1 -1
 		quota->charge_target_from = NULL;
 		quota->charge_addr_from = 0;
 	}
-	return false;
+	return skip;
 }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
maintainer
#9 lore raw

Re: [PATCH 6.12.y] mm/damon/core: fix unconditionally skip last region

Quoted 38 lines · On Wed, 30 Sep 2026 09:53:34 +0000 [email protected] wrote:

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] damon: OOB write and list corruption when damon_split_region_at() fails
- [High] damon: permanent skipping of targets due to charge_target_from state leak --

commit 035ce5241b6b256409f7ad3d4de6c01dbe7e8563
Author: Liew Rui Yan <[email protected]> mm/damon/core: fix unconditionally skip last region

This patch modifies the skip logic in DAMON to only reset the charge_target_from state when the last region is reached. It also updates the function to skip the region only if it is applied or cannot be split, rather than unconditionally skipping the last region.

Quoted 4 lines
mm/damon/core.c +0 -0
index 32737cc5f877b..802005d9186e8 100644

[ ... ]

mm/damon/core.c static bool damos_skip_charged_region(struct damon_target *t, · line 1348

Quoted 4 lines
mm/damon/core.c +0 -0
@@ -1348,11 +1346,15 @@ static bool damos_skip_charged_region(struct damon_target *t,
 			damon_split_region_at(t, r, sz_to_skip);
 			r = damon_next_region(r);
 			*rp = r;

[Severity: Critical]
Could this lead to out-of-bounds writes and list corruption if damon_split_region_at() fails?

This patch removes the early exit condition for the last region, exposing it to damon_split_region_at(). If memory allocation fails, the split function returns silently without modifying the list:

mm/damon/core.c:damon_split_region_at() {
	new = damon_new_region(r->ar.start + sz_r, r->ar.end);
	if (!new)
		return;
Quoted 8 lines

...
}

The pointer is then unconditionally advanced with r = damon_next_region(r). If r was the last region, damon_next_region() wraps around to the list head (&t->regions_list), generating an invalid struct damon_region pointer pointing outside valid regions. Subsequent writes to this pointer will corrupt the target sibling list integrity.

This is a pre-existing issue. I will separately work on the fix.

Quoted 20 lines

mm/damon/core.c static bool damos_skip_charged_region(struct damon_target *t,

Quoted 6 lines
mm/damon/core.c +4 -0
+			skip = false;
 		}
+	}
+out:
+	if (r = damon_last_region(t)) {

[Severity: High]
Does the early return inside the region size check bypass this cleanup logic?

If sz_to_skip evaluates to 0 and the region size is <= DAMON_MIN_REGION, the code hits an early return:

mm/damon/core.c:damos_skip_charged_region() {
...

		sz_to_skip = ALIGN_DOWN(quota->charge_addr_from -
				r->ar.start, DAMON_MIN_REGION);
		if (!sz_to_skip) {
			if (damon_sz_region(r) <= DAMON_MIN_REGION)
				return true;
Quoted 8 lines

...
}

Because this early return bypasses the new out label, quota->charge_target_from remains populated if this occurs on the last region. For all subsequent targets, the check
if (t != quota->charge_target_from) evaluates to true and indefinitely skips those regions, causing targets to be starved.

Good catch. This is a mistake in my backporting. I will send v2.

Quoted 11 lines
Quoted 7 lines
mm/damon/core.c +1 -1
 		quota->charge_target_from = NULL;
 		quota->charge_addr_from = 0;
 	}
-	return false;
+	return skip;
 }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1

Thanks,
SJ

maintainer
#11 lore raw

PATCH v2 1/1 mm/damon/core: fix unconditionally skip last region

In reply to [email protected]

From: Liew Rui Yan <[email protected]>

Once quota set, the charge_{target,addr}_from unconditionally skips and resets at the last region of the tracked target, so the last region can be skipped even when it has not been processed.

Example:

1. Target has 2 regions: R1 (0-100 bytes) and R2 (100-200 bytes).
2. Quota is configured to process only 100 bytes per window.
3. Window 1: Processes R1 (0-100). Quota is full. charge_{target, addr}_from is saved at (Target, 100).
4. Window 2: The loop reaches R2. Because R2 is damon_last_region(t), the old code unconditionally returns true, skipping R2 entirely and resetting the charge_{target,addr}_from.

Result: R2 is permanently skipped even though it has never been processed.

However, it is important to note that this is a very minor issue. This is because it is triggered only when the previous window saved/kept charge_{target,addr}_from, and in the next window, all regions except the last region were skipped by damos_skip_charged_region().

Fix this by only resetting the charge_{target,addr}_from when last region is reached, only skipping when it is applied or cannot split.

Fixes 50585192bc2e ("mm/damon/schemes: skip already charged targets and regions")
Cc <[email protected]> # v5.16.x
(cherry picked from commit b3723b596b548c837a766aae3553c14a7b15af2b)

---
Changes from v1
- v1: https://lore.kernel.org/[email protected]
- Add missed change for damon_sz_region(r) <= min_region_sz case.

 mm/damon/core.c | 24 ++++++++++++++----------
 1 file changed, 14 insertions(+), 10 deletions(-)
mm/damon/core.c +14 -10
index 32737cc5f877b..cc7250f0936af 100644
@@ -1322,37 +1322,41 @@ static bool damos_skip_charged_region(struct damon_target *t,
 	struct damon_region *r = *rp;
 	struct damos_quota *quota = &s->quota;
 	unsigned long sz_to_skip;
+	bool skip = false;
 
 	/* Skip previously charged regions */
 	if (quota->charge_target_from) {
 		if (t != quota->charge_target_from)
 			return true;
-		if (r == damon_last_region(t)) {
-			quota->charge_target_from = NULL;
-			quota->charge_addr_from = 0;
-			return true;
-		}
 		if (quota->charge_addr_from &&
-				r->ar.end <= quota->charge_addr_from)
-			return true;
+				r->ar.end <= quota->charge_addr_from) {
+			skip = true;
+			goto out;
+		}
 
 		if (quota->charge_addr_from && r->ar.start <
 				quota->charge_addr_from) {
 			sz_to_skip = ALIGN_DOWN(quota->charge_addr_from -
 					r->ar.start, DAMON_MIN_REGION);
 			if (!sz_to_skip) {
-				if (damon_sz_region(r) <= DAMON_MIN_REGION)
-					return true;
+				if (damon_sz_region(r) <= DAMON_MIN_REGION) {
+					skip = true;
+					goto out;
+				}
 				sz_to_skip = DAMON_MIN_REGION;
 			}
 			damon_split_region_at(t, r, sz_to_skip);
 			r = damon_next_region(r);
 			*rp = r;
+			skip = false;
 		}
+	}
+out:
+	if (r == damon_last_region(t)) {
 		quota->charge_target_from = NULL;
 		quota->charge_addr_from = 0;
 	}
-	return false;
+	return skip;
 }
 
 static void damos_update_stat(struct damos *s,
Signature · 1 line
2.47.3
#12 lore raw

Re: [PATCH 6.12.y v2] mm/damon/core: fix unconditionally skip last region

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] mm: OOB dereference in damon_do_apply_schemes() on split failure --

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] mm: OOB dereference in damon_do_apply_schemes() on split failure --

commit 61e0ff2e7385bb544355d1f3aa3598f7f8e9e1d0
Author: Liew Rui Yan <[email protected]>

mm/damon/core: fix unconditionally skip last region

This commit changes the logic in damos_skip_charged_region() to only reset charge_{target,addr}_from when the last region is reached. This is intended to ensure the last region is not skipped if it has not been fully processed.

Quoted 4 lines
mm/damon/core.c +0 -0
index 32737cc5f877b..cc7250f0936af 100644

[ ... ]

Quoted 34 lines
mm/damon/core.c +9 -9
@@ -1322,37 +1322,41 @@ static bool damos_skip_charged_region(struct damon_target *t,
 	struct damon_region *r = *rp;
 	struct damos_quota *quota = &s->quota;
 	unsigned long sz_to_skip;
+	bool skip = false;
 
 	/* Skip previously charged regions */
 	if (quota->charge_target_from) {
 		if (t != quota->charge_target_from)
 			return true;
-		if (r == damon_last_region(t)) {
-			quota->charge_target_from = NULL;
-			quota->charge_addr_from = 0;
-			return true;
-		}
 		if (quota->charge_addr_from &&
-				r->ar.end <= quota->charge_addr_from)
-			return true;
+				r->ar.end <= quota->charge_addr_from) {
+			skip = true;
+			goto out;
+		}
 
 		if (quota->charge_addr_from && r->ar.start <
 				quota->charge_addr_from) {
 			sz_to_skip = ALIGN_DOWN(quota->charge_addr_from -
 					r->ar.start, DAMON_MIN_REGION);
 			if (!sz_to_skip) {
-				if (damon_sz_region(r) <= DAMON_MIN_REGION)
-					return true;
+				if (damon_sz_region(r) <= DAMON_MIN_REGION) {
+					skip = true;
+					goto out;
+				}

mm/damon/core.c static bool damos_skip_charged_region(struct damon_target *t, · line 1355

Quoted 10 lines
mm/damon/core.c +4 -0
 				sz_to_skip = DAMON_MIN_REGION;
 			}
 			damon_split_region_at(t, r, sz_to_skip);
 			r = damon_next_region(r);
 			*rp = r;
+			skip = false;
 		}
+	}
+out:
+	if (r == damon_last_region(t)) {

[Severity: High]
If damon_split_region_at() fails to allocate memory, it returns silently and the region remains unmodified. If r happens to be the last region in the list, could damon_next_region(r) evaluate past the list boundary and return an invalid pointer?

Since this invalid pointer is assigned to *rp and the function continues to the out label, could it fail this (r == damon_last_region(t)) check, bypass the intended quota reset, and return false?

If so, could the caller damon_do_apply_schemes() then receive this invalid pointer and pass it to damos_valid_target():

mm/damon/core.c:damon_do_apply_schemes() {
    ...
		if (damos_skip_charged_region(t, &r, s))
			continue;

		if (!damos_valid_target(c, t, r, s))
			continue;
    ...
}

causing it to dereference garbage data (such as r->ar.start and r->ar.end) and potentially lead to an out-of-bounds memory access?

Quoted 8 lines
mm/damon/core.c +1 -1
 		quota->charge_target_from = NULL;
 		quota->charge_addr_from = 0;
 	}
-	return false;
+	return skip;
 }
 
 static void damos_update_stat(struct damos *s,
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
maintainer
#13 lore raw

Re: [PATCH 6.12.y v2] mm/damon/core: fix unconditionally skip last region

Quoted 88 lines · On Wed, 30 Sep 2026 10:26:49 +0000 [email protected] wrote:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] mm: OOB dereference in damon_do_apply_schemes() on split failure --

commit 61e0ff2e7385bb544355d1f3aa3598f7f8e9e1d0
Author: Liew Rui Yan <[email protected]>

mm/damon/core: fix unconditionally skip last region

This commit changes the logic in damos_skip_charged_region() to only reset charge_{target,addr}_from when the last region is reached. This is intended to ensure the last region is not skipped if it has not been fully processed.

Quoted 4 lines
mm/damon/core.c +0 -0
index 32737cc5f877b..cc7250f0936af 100644

[ ... ]

Quoted 34 lines
mm/damon/core.c +9 -9
@@ -1322,37 +1322,41 @@ static bool damos_skip_charged_region(struct damon_target *t,
 	struct damon_region *r = *rp;
 	struct damos_quota *quota = &s->quota;
 	unsigned long sz_to_skip;
+	bool skip = false;
 
 	/* Skip previously charged regions */
 	if (quota->charge_target_from) {
 		if (t != quota->charge_target_from)
 			return true;
-		if (r == damon_last_region(t)) {
-			quota->charge_target_from = NULL;
-			quota->charge_addr_from = 0;
-			return true;
-		}
 		if (quota->charge_addr_from &&
-				r->ar.end <= quota->charge_addr_from)
-			return true;
+				r->ar.end <= quota->charge_addr_from) {
+			skip = true;
+			goto out;
+		}
 
 		if (quota->charge_addr_from && r->ar.start <
 				quota->charge_addr_from) {
 			sz_to_skip = ALIGN_DOWN(quota->charge_addr_from -
 					r->ar.start, DAMON_MIN_REGION);
 			if (!sz_to_skip) {
-				if (damon_sz_region(r) <= DAMON_MIN_REGION)
-					return true;
+				if (damon_sz_region(r) <= DAMON_MIN_REGION) {
+					skip = true;
+					goto out;
+				}

mm/damon/core.c static bool damos_skip_charged_region(struct damon_target *t, · line 1355

Quoted 10 lines
mm/damon/core.c +4 -0
 				sz_to_skip = DAMON_MIN_REGION;
 			}
 			damon_split_region_at(t, r, sz_to_skip);
 			r = damon_next_region(r);
 			*rp = r;
+			skip = false;
 		}
+	}
+out:
+	if (r == damon_last_region(t)) {

[Severity: High]
If damon_split_region_at() fails to allocate memory, it returns silently and the region remains unmodified. If r happens to be the last region in the list, could damon_next_region(r) evaluate past the list boundary and return an invalid pointer?

Since this invalid pointer is assigned to *rp and the function continues to the out label, could it fail this (r == damon_last_region(t)) check, bypass the intended quota reset, and return false?

If so, could the caller damon_do_apply_schemes() then receive this invalid pointer and pass it to damos_valid_target():

mm/damon/core.c:damon_do_apply_schemes() {
    ...
		if (damos_skip_charged_region(t, &r, s))
			continue;

		if (!damos_valid_target(c, t, r, s))
			continue;
    ...
}
Quoted 2 lines

causing it to dereference garbage data (such as r->ar.start and r->ar.end) and potentially lead to an out-of-bounds memory access?

I will separately work on this. Refer to my other reply to same issue report for details.

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

Quoted 12 lines
Quoted 9 lines
mm/damon/core.c +1 -1
 		quota->charge_target_from = NULL;
 		quota->charge_addr_from = 0;
 	}
-	return false;
+	return skip;
 }
 
 static void damos_update_stat(struct damos *s,
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1

Thanks,
SJ

maintainer
#14 lore raw

Re: [PATCH 6.12.y v2] mm/damon/core: fix unconditionally skip last region

In reply to SJ Park
Quoted 2 lines

Fix this by only resetting the charge_{target,addr}_from when last region is reached, only skipping when it is applied or cannot split.

I'll hold this until the damon_split_region_at() failure handling fix you mentioned is posted, and then take 6.12, 6.6 and 6.1 together with it.

The 6.18 backport that is already queued has the same split failure exposure, so it will need that fix too.

Signature · 2 lines
Thanks,
Sasha
maintainer
#15 lore raw

v3 cover mm/damon/core: fix last region handling of damos_skip_charged_regions()

In reply to [email protected]

Patch 1 is a dependency of patch 2. Without it, patch 2 introduces out-of-bounds memory access bug that was found by Sashiko. Patch 2 fixes a bug that categorized to be backported to stable@.

Changes from v2
- v2: https://lore.kernel.org/[email protected]
- Fix out-of-bounds memory access bug by adding patch 1. Changes from v1
- v1: https://lore.kernel.org/[email protected]
- Add missed change for damon_sz_region(r) <= min_region_sz case.

Liew Rui Yan (1):
mm/damon/core: fix unconditionally skip last region

SeongJae Park (1):
mm/damon/core: do non-safe region walk on kdamond_apply_schemes()

 mm/damon/core.c | 45 ++++++++++++++++++++++++---------------------
 1 file changed, 24 insertions(+), 21 deletions(-)

base-commit: da5a052a482924abb7ca43f3cc70622b3033f5cb

Signature · 1 line
2.47.3
maintainer
#16 lore raw

PATCH v3 1/2 mm/damon/core: do non-safe region walk on kdamond_apply_schemes()

From: SeongJae Park <[email protected]>

kdamond_apply_schemes() is using damon_for_each_region_safe(), which is safe for deallocation of the region inside the loop. However, the loop internal logic does not deallocate regions. Hence it is only wasting the next pointer. Also, it causes a problem.

When an address filter is applied, and there is a region that intersects with the filter, the filter splits the region on the filter boundary. The intention is to let DAMOS apply action to only filtered-in address ranges. However, it is using damon_for_each_region_safe(), which sets the next region before the execution of the iteration. Hence, the region that split and now will be next to the previous region, is simply ignored. As a result, DAMOS applies the action to target regions bit slower than expected, when the address filter is used. Shouldn't be a big problem but definitely better to be fixed. damos_skip_charged_region() was working around the issue using a double pointer hack.

Use damon_for_each_region(), which is safe for this use case. And drop the work around in damos_skip_charged_region().

(cherry picked from commit 1745ccbd2907db2bdaa843e4abccde4fdaccbe5d)
---
 mm/damon/core.c | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)
mm/damon/core.c +11 -11
index 32737cc5f877..76f23c1d2f56 100644
@@ -1310,16 +1310,17 @@ static bool damos_valid_target(struct damon_ctx *c, struct damon_target *t,
  * This function checks if a given region should be skipped or not for the
  * reason.  If only the starting part of the region has previously charged,
  * this function splits the region into two so that the second one covers the
- * area that not charged in the previous charge widnow and saves the second
- * region in *rp and returns false, so that the caller can apply DAMON action
- * to the second one.
+ * area that not charged in the previous charge widnow, and return true.  The
+ * caller can see the second one on the next iteration of the region walk.
+ * Note that this means the caller should use damon_for_each_region() instead
+ * of damon_for_each_region_safe().  If damon_for_each_region_safe() is used,
+ * the second region will just be ignored.
  *
- * Return: true if the region should be entirely skipped, false otherwise.
+ * Return: true if the region should be skipped, false otherwise.
  */
 static bool damos_skip_charged_region(struct damon_target *t,
-		struct damon_region **rp, struct damos *s)
+		struct damon_region *r, struct damos *s)
 {
-	struct damon_region *r = *rp;
 	struct damos_quota *quota = &s->quota;
 	unsigned long sz_to_skip;
 
@@ -1346,8 +1347,7 @@ static bool damos_skip_charged_region(struct damon_target *t,
 				sz_to_skip = DAMON_MIN_REGION;
 			}
 			damon_split_region_at(t, r, sz_to_skip);
-			r = damon_next_region(r);
-			*rp = r;
+			return true;
 		}
 		quota->charge_target_from = NULL;
 		quota->charge_addr_from = 0;
@@ -1514,7 +1514,7 @@ static void damon_do_apply_schemes(struct damon_ctx *c,
 		if (quota->esz && quota->charged_sz >= quota->esz)
 			continue;
 
-		if (damos_skip_charged_region(t, &r, s))
+		if (damos_skip_charged_region(t, r, s))
 			continue;
 
 		if (!damos_valid_target(c, t, r, s))
@@ -1727,7 +1727,7 @@ static void damos_adjust_quota(struct damon_ctx *c, struct damos *s)
 static void kdamond_apply_schemes(struct damon_ctx *c)
 {
 	struct damon_target *t;
-	struct damon_region *r, *next_r;
+	struct damon_region *r;
 	struct damos *s;
 	unsigned long sample_interval = c->attrs.sample_interval ?
 		c->attrs.sample_interval : 1;
@@ -1749,7 +1749,7 @@ static void kdamond_apply_schemes(struct damon_ctx *c)
 		return;
 
 	damon_for_each_target(t, c) {
-		damon_for_each_region_safe(r, next_r, t)
+		damon_for_each_region(r, t)
 			damon_do_apply_schemes(c, t, r);
 	}
 
Signature · 1 line
2.47.3
#17 lore raw

Re: [PATCH 6.12.y v3 1/2] mm/damon/core: do non-safe region walk on kdamond_apply_schemes()

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] mm: damon: double application of DAMOS schemes due to non-safe region walk --

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] mm: damon: double application of DAMOS schemes due to non-safe region walk --

commit 7c60a2f66acad38f06b09b2a9e484519168800bc
Author: SeongJae Park <[email protected]>

mm/damon/core: do non-safe region walk on kdamond_apply_schemes()

This commit changes kdamond_apply_schemes() to use the non-safe damon_for_each_region() iterator instead of damon_for_each_region_safe(). It also drops the double-pointer workaround in damos_skip_charged_region() to let newly split regions be seen on the next iteration of the region walk.

mm/damon/core.c static void kdamond_apply_schemes(struct damon_ctx *c) · line 1754

Quoted 12 lines
mm/damon/core.c +1 -1
index 32737cc5f877b..76f23c1d2f56b 100644
@@ -1749,7 +1749,7 @@ static void kdamond_apply_schemes(struct damon_ctx *c)
 		return;
 
 	damon_for_each_target(t, c) {
-		damon_for_each_region_safe(r, next_r, t)
+		damon_for_each_region(r, t)
 			damon_do_apply_schemes(c, t, r);
 	}

[Severity: High]
Does this change cause earlier schemes to double-apply their actions when a subsequent scheme splits the region?

In kdamond_apply_schemes(), changing to the non-safe iterator means the loop dynamically visits newly created fragments on the very next iteration.

When damon_do_apply_schemes() runs, it evaluates all schemes sequentially on the region:

mm/damon/core.c:damon_do_apply_schemes() {
	...
	damon_for_each_scheme(s, c) {
		...
		if (damos_skip_charged_region(t, r, s))
			continue;
		...
		damos_apply_scheme(c, t, r, s);
	}
}

If an earlier scheme evaluates the region, it may apply its action and update its statistics for the full size of the region.

If a subsequent scheme then evaluates the same region and splits it (for example via damos_skip_charged_region()), it shrinks the original region and inserts a new region immediately after it.

Because damon_for_each_region() is no longer safe against modifications to the list, the next iteration of the outer loop will process the newly inserted region across all schemes.

Since the newly inserted region retains the exact properties (age, access counts) of the original region, will the earlier scheme re-evaluate it and apply its action a second time, double-charging its statistics and applying redundant actions to the memory?

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

Re: [PATCH 6.12.y v3 1/2] mm/damon/core: do non-safe region walk on kdamond_apply_schemes()

Quoted 57 lines · On Fri, 09 Oct 2026 12:55:36 +0000 [email protected] wrote:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] mm: damon: double application of DAMOS schemes due to non-safe region walk --

commit 7c60a2f66acad38f06b09b2a9e484519168800bc
Author: SeongJae Park <[email protected]>

mm/damon/core: do non-safe region walk on kdamond_apply_schemes()

This commit changes kdamond_apply_schemes() to use the non-safe damon_for_each_region() iterator instead of damon_for_each_region_safe(). It also drops the double-pointer workaround in damos_skip_charged_region() to let newly split regions be seen on the next iteration of the region walk.

mm/damon/core.c static void kdamond_apply_schemes(struct damon_ctx *c) · line 1754

Quoted 12 lines
mm/damon/core.c +1 -1
index 32737cc5f877b..76f23c1d2f56b 100644
@@ -1749,7 +1749,7 @@ static void kdamond_apply_schemes(struct damon_ctx *c)
 		return;
 
 	damon_for_each_target(t, c) {
-		damon_for_each_region_safe(r, next_r, t)
+		damon_for_each_region(r, t)
 			damon_do_apply_schemes(c, t, r);
 	}

[Severity: High]
Does this change cause earlier schemes to double-apply their actions when a subsequent scheme splits the region?

In kdamond_apply_schemes(), changing to the non-safe iterator means the loop dynamically visits newly created fragments on the very next iteration.

When damon_do_apply_schemes() runs, it evaluates all schemes sequentially on the region:

mm/damon/core.c:damon_do_apply_schemes() {
	...
	damon_for_each_scheme(s, c) {
		...
		if (damos_skip_charged_region(t, r, s))
			continue;
		...
		damos_apply_scheme(c, t, r, s);
	}
}

If an earlier scheme evaluates the region, it may apply its action and update its statistics for the full size of the region.

If a subsequent scheme then evaluates the same region and splits it (for example via damos_skip_charged_region()), it shrinks the original region and inserts a new region immediately after it.

Quoted 8 lines

Because damon_for_each_region() is no longer safe against modifications to the list, the next iteration of the outer loop will process the newly inserted region across all schemes.

Since the newly inserted region retains the exact properties (age, access counts) of the original region, will the earlier scheme re-evaluate it and apply its action a second time, double-charging its statistics and applying redundant actions to the memory?

Good finding. But the issue is also in the mainline. Also, because DAMOS quota is a sort of best effort, the user impact is minor. I think this is not a blocker of this backport patch. I will separately work on it.

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

Thanks,
SJ

maintainer
#19 lore raw

PATCH v3 2/2 mm/damon/core: fix unconditionally skip last region

In reply to SJ Park

From: Liew Rui Yan <[email protected]>

Once quota set, the charge_{target,addr}_from unconditionally skips and resets at the last region of the tracked target, so the last region can be skipped even when it has not been processed.

Example:

1. Target has 2 regions: R1 (0-100 bytes) and R2 (100-200 bytes).
2. Quota is configured to process only 100 bytes per window.
3. Window 1: Processes R1 (0-100). Quota is full. charge_{target, addr}_from is saved at (Target, 100).
4. Window 2: The loop reaches R2. Because R2 is damon_last_region(t), the old code unconditionally returns true, skipping R2 entirely and resetting the charge_{target,addr}_from.

Result: R2 is permanently skipped even though it has never been processed.

However, it is important to note that this is a very minor issue. This is because it is triggered only when the previous window saved/kept charge_{target,addr}_from, and in the next window, all regions except the last region were skipped by damos_skip_charged_region().

Fix this by only resetting the charge_{target,addr}_from when last region is reached, only skipping when it is applied or cannot split.

Fixes 50585192bc2e ("mm/damon/schemes: skip already charged targets and regions")
Cc <[email protected]> # v5.16.x
(cherry picked from commit b3723b596b548c837a766aae3553c14a7b15af2b)
---
 mm/damon/core.c | 25 ++++++++++++++-----------
 1 file changed, 14 insertions(+), 11 deletions(-)
mm/damon/core.c +14 -11
index 76f23c1d2f56..dab9669de04b 100644
@@ -1323,36 +1323,39 @@ static bool damos_skip_charged_region(struct damon_target *t,
 {
 	struct damos_quota *quota = &s->quota;
 	unsigned long sz_to_skip;
+	bool skip = false;
 
 	/* Skip previously charged regions */
 	if (quota->charge_target_from) {
 		if (t != quota->charge_target_from)
 			return true;
-		if (r == damon_last_region(t)) {
-			quota->charge_target_from = NULL;
-			quota->charge_addr_from = 0;
-			return true;
-		}
 		if (quota->charge_addr_from &&
-				r->ar.end <= quota->charge_addr_from)
-			return true;
+				r->ar.end <= quota->charge_addr_from) {
+			skip = true;
+			goto out;
+		}
 
 		if (quota->charge_addr_from && r->ar.start <
 				quota->charge_addr_from) {
 			sz_to_skip = ALIGN_DOWN(quota->charge_addr_from -
 					r->ar.start, DAMON_MIN_REGION);
 			if (!sz_to_skip) {
-				if (damon_sz_region(r) <= DAMON_MIN_REGION)
-					return true;
+				if (damon_sz_region(r) <= DAMON_MIN_REGION) {
+					skip = true;
+					goto out;
+				}
 				sz_to_skip = DAMON_MIN_REGION;
 			}
 			damon_split_region_at(t, r, sz_to_skip);
-			return true;
+			skip = true;
 		}
+	}
+out:
+	if (r == damon_last_region(t)) {
 		quota->charge_target_from = NULL;
 		quota->charge_addr_from = 0;
 	}
-	return false;
+	return skip;
 }
 
 static void damos_update_stat(struct damos *s,
Signature · 1 line
2.47.3
#20 lore raw

Re: [PATCH 6.12.y v3 2/2] mm/damon/core: fix unconditionally skip last region

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] mm/damon: dangling charge_target_from pointer permanently freezes schemes --

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] mm/damon: dangling charge_target_from pointer permanently freezes schemes --

commit a9baf64370898b2dec04fa2f17e4061166d6d27a
Author: Liew Rui Yan <[email protected]>

mm/damon/core: fix unconditionally skip last region

This commit modifies damos_skip_charged_region() to clear the quota's charge_target_from pointer only when the last region of a target is reached, rather than clearing it unconditionally during previous charge checks.

Quoted 14 lines
mm/damon/core.c +1 -0
@@ -1323,36 +1323,39 @@ static bool damos_skip_charged_region(struct damon_target *t,
 		struct damon_region *r, struct damos *s)
 {
 	struct damos_quota *quota = &s->quota;
 	unsigned long sz_to_skip;
+	bool skip = false;
 
 	/* Skip previously charged regions */
 	if (quota->charge_target_from) {
 		if (t != quota->charge_target_from)
 			return true;

[ ... ]

mm/damon/core.c static bool damos_skip_charged_region(struct damon_target *t, · line 1357

Quoted 6 lines
mm/damon/core.c +3 -0
+	}
+out:
+	if (r == damon_last_region(t)) {
 		quota->charge_target_from = NULL;
 		quota->charge_addr_from = 0;
 	}

[Severity: High]
If the target tracked by quota->charge_target_from is dynamically destroyed via sysfs, or if it loses all of its regions, will this clearing logic ever be reached?

If the saved target is removed or has zero regions, the region loop will be empty, meaning r == damon_last_region(t) is never evaluated for that target.

Since charge_target_from is never cleared in these scenarios, wouldn't this cause the condition earlier in this function:

        if (t != quota->charge_target_from)
                return true;

to remain true indefinitely for all other valid targets, permanently freezing the scheme? Additionally, if the target is freed, does this leave a dangling pointer comparison for future checks?

Quoted 3 lines
mm/damon/core.c +1 -1
-	return false;
+	return skip;
 }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
maintainer
#21 lore raw

Re: [PATCH 6.12.y v3 2/2] mm/damon/core: fix unconditionally skip last region

Quoted 33 lines · On Fri, 09 Oct 2026 13:06:32 +0000 [email protected] wrote:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] mm/damon: dangling charge_target_from pointer permanently freezes schemes --

commit a9baf64370898b2dec04fa2f17e4061166d6d27a
Author: Liew Rui Yan <[email protected]>

mm/damon/core: fix unconditionally skip last region

This commit modifies damos_skip_charged_region() to clear the quota's charge_target_from pointer only when the last region of a target is reached, rather than clearing it unconditionally during previous charge checks.

Quoted 14 lines
mm/damon/core.c +1 -0
@@ -1323,36 +1323,39 @@ static bool damos_skip_charged_region(struct damon_target *t,
 		struct damon_region *r, struct damos *s)
 {
 	struct damos_quota *quota = &s->quota;
 	unsigned long sz_to_skip;
+	bool skip = false;
 
 	/* Skip previously charged regions */
 	if (quota->charge_target_from) {
 		if (t != quota->charge_target_from)
 			return true;

[ ... ]

Quoted 11 lines

mm/damon/core.c static bool damos_skip_charged_region(struct damon_target *t, · line 1357

Quoted 6 lines
mm/damon/core.c +3 -0
+	}
+out:
+	if (r = damon_last_region(t)) {
 		quota->charge_target_from = NULL;
 		quota->charge_addr_from = 0;
 	}

[Severity: High]
If the target tracked by quota->charge_target_from is dynamically destroyed via sysfs, or if it loses all of its regions, will this clearing logic ever be reached?

Good finding. But this is pre-existing issue that also exist in the mainline tree. So not a blocker of this patch in my opinion. I will separately working on this.

Quoted 11 lines

If the saved target is removed or has zero regions, the region loop will be empty, meaning r = damon_last_region(t) is never evaluated for that target.

Since charge_target_from is never cleared in these scenarios, wouldn't this cause the condition earlier in this function:

        if (t != quota->charge_target_from)
                return true;
Quoted 11 lines

to remain true indefinitely for all other valid targets, permanently freezing the scheme? Additionally, if the target is freed, does this leave a dangling pointer comparison for future checks?

Quoted 3 lines
mm/damon/core.c +1 -1
-	return false;
+	return skip;
 }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2

Thanks,
SJ

maintainer Applied
#22 lore raw

Re: [PATCH 6.12.y v3 0/2] mm/damon/core: fix last region handling of damos_skip_charged_regions()

In reply to SJ Park
Quoted 3 lines

Patch 1 is a dependency of patch 2. Without it, patch 2 introduces out-of-bounds memory access bug that was found by Sashiko. Patch 2 fixes a bug that categorized to be backported to stable@.

Queued the series for 6.12, thanks.

Signature · 2 lines
Thanks,
Sasha