Skip to contentSkip to conversation

mm/damon: fix DAMOS bugs in core, paddr and vaddr

RFC PATCH In review SJ Park 2 participants 23 messages Aug 1

Subsystems (3)
lore
Share & tools
lore

SJ Park

author maintainer
#0 lore raw

cover mm/damon: fix DAMOS bugs in core, paddr and vaddr

Fix misc bugs of DAMOS. Patch 1 make DAMOS less stress memory allocator under extreme situation. Patch 2 fixes wrong DAMOS quota auto-tuning feedback loop start point for PSI goal. Patches 3 and 4 fix wrong folios walking in DAMON_PADDR. Patches 5 and 6 fix wrong folios walking in DAMON_VADDR. Patches 7-9 handle extreme and unlikely memory situations that can cause divide by zero and underflow.

All bugs are discovered by Sashiko.

The bugs are not very critical, but better to be merged sooner than later. Since mm.git is closed for urgent changes, I aim for 7.3-rcX. I will drop the RFC tag after 7.3-rc1.

SJ Park (9):
mm/damon/core: skip applying scheme if region split for quota fails mm/damon/core: initialize damos_quota_goal->last_psi_total mm/damon/paddr: respect folio end for DAMOS_STAT mm/damon/paddr: respect folio end for DAMOS actions except STAT mm/damon/vaddr: respect folio end for DAMOS_STAT mm/damon/vaddr: respect folio end for DAMOS_MIGRATE_{HOT,COLD} mm/damon/core: handle extreme memory state in damon_get_node_mem_bp() mm/damon/core: handle extreme memory state in get_node_memcg_used_bp() mm/damon/core: handle extreme memory state in get_in_active_mem_bp()

 mm/damon/core.c  | 29 +++++++++++++++++++++++++----
 mm/damon/paddr.c |  8 ++++----
 mm/damon/vaddr.c | 10 ++++++++--
 3 files changed, 37 insertions(+), 10 deletions(-)

base-commit: 70802c13e2c9a0ab52605035e2d63b964afe6add

Signature · 1 line
2.47.3
author maintainer
#1 lore raw

RFC PATCH 1/9 mm/damon/core: skip applying scheme if region split for quota fails

damos_apply_scheme() splits a region and apply the action to the subregion if it is needed for not violating the quota. The split operation (damon_split_region_at()) could fail for allocation failure. In the case, the quota could be violated. From the user's perspective, DAMOS becomes more aggressive than expected under the extreme situation. Handle the failure.

The user impact is not critical. The failure of damon_split_region_at() is unlikely since it is arguably too small to fail. Also DAMOS being aggressive is limited to the single region. Users can set min_nr_regions to set the maximum size of each region. If it is reasonably set, the transient overhead shouldn't be critical.

The issue was discovered [1] by Sashiko.

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

Fixes 2b8a248d5873 ("mm/damon/schemes: implement size quota for schemes application speed control")
Cc <[email protected]> # 5.16.x
---
 mm/damon/core.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
mm/damon/core.c +2 -1
index 644daf5a16560..e2900d0c984c9 100644
@@ -2613,7 +2613,8 @@ static void damos_apply_scheme(struct damon_ctx *c, struct damon_target *t,
 					c->min_region_sz);
 			if (!sz)
 				goto update_stat;
-			damon_split_region_at(t, r, sz);
+			if (damon_split_region_at(t, r, sz))
+				goto update_stat;
 		}
 		if (damos_core_filter_out(c, t, r, s))
 			return;
Signature · 1 line
2.47.3
#2 lore raw

Re: [RFC PATCH 1/9] mm/damon/core: skip applying scheme if region split for quota fails

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] Unhandled failure of damon_split_region_at() in damos_filter_match() leads to bypassing or incorrectly applying address filters. --

--- Patch [1]: [RFC PATCH 1/9] mm/damon/core: skip applying scheme if region split for quota fails --- commit 812934b542c69c596afadb66403f8c9c11c5bf91
Author: SJ Park <[email protected]>

mm/damon/core: skip applying scheme if region split for quota fails

damos_apply_scheme() splits a region and apply the action to the subregion if it is needed for not violating the quota. The split operation (damon_split_region_at()) could fail for allocation failure. In the case, the quota could be violated. From the user's perspective, DAMOS becomes more aggressive than expected under the extreme situation. Handle the failure.

mm/damon/core.c static void damos_apply_scheme(struct damon_ctx *c, struct damon_target *t, · line 2617

Quoted 11 lines
mm/damon/core.c +2 -1
index 644daf5a16560..e2900d0c984c9 100644
@@ -2613,7 +2613,8 @@ static void damos_apply_scheme(struct damon_ctx *c, struct damon_target *t,
 					c->min_region_sz);
 			if (!sz)
 				goto update_stat;
-			damon_split_region_at(t, r, sz);
+			if (damon_split_region_at(t, r, sz))
+				goto update_stat;

[Severity: High]
This is a pre-existing issue, but since we are handling the failure of damon_split_region_at() here, should we also handle it in damos_filter_match()?

If a memory region partially overlaps a filter range and the split allocation fails in damos_filter_match(), the return value is ignored:

mm/damon/core.c:damos_filter_match() {
    ...
		/* start before the range and overlap */
		if (r->ar.start < start) {
			damon_split_region_at(t, r, start - r->ar.start);
			matched = false;
			break;
		}
		/* start inside the range */
		damon_split_region_at(t, r, end - r->ar.start);
		matched = true;
		break;
    ...
}

If the split fails, the intact region spanning both outside and inside the filter range is unconditionally evaluated as 'matched = false' or 'matched = true', incorrectly bypassing the filter or applying it to unrelated memory outside the intended range.

Could this lead to destructive actions (e.g., PAGEOUT) being applied to explicitly protected memory?

Quoted 3 lines
mm/damon/core.c +0 -0
 		}
 		if (damos_core_filter_out(c, t, r, s))
 			return;
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
author maintainer
#3 lore raw

Re: [RFC PATCH 1/9] mm/damon/core: skip applying scheme if region split for quota fails

Quoted 56 lines · On Sat, 01 Aug 2026 17:53:34 +0000 [email protected] wrote:

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

Pre-existing issues:
- [High] Unhandled failure of damon_split_region_at() in damos_filter_match() leads to bypassing or incorrectly applying address filters. --

--- Patch [1]: [RFC PATCH 1/9] mm/damon/core: skip applying scheme if region split for quota fails --- commit 812934b542c69c596afadb66403f8c9c11c5bf91
Author: SJ Park <[email protected]>

mm/damon/core: skip applying scheme if region split for quota fails

damos_apply_scheme() splits a region and apply the action to the subregion if it is needed for not violating the quota. The split operation (damon_split_region_at()) could fail for allocation failure. In the case, the quota could be violated. From the user's perspective, DAMOS becomes more aggressive than expected under the extreme situation. Handle the failure.

mm/damon/core.c static void damos_apply_scheme(struct damon_ctx *c, struct damon_target *t, · line 2617

Quoted 11 lines
mm/damon/core.c +2 -1
index 644daf5a16560..e2900d0c984c9 100644
@@ -2613,7 +2613,8 @@ static void damos_apply_scheme(struct damon_ctx *c, struct damon_target *t,
 					c->min_region_sz);
 			if (!sz)
 				goto update_stat;
-			damon_split_region_at(t, r, sz);
+			if (damon_split_region_at(t, r, sz))
+				goto update_stat;

[Severity: High]
This is a pre-existing issue, but since we are handling the failure of damon_split_region_at() here, should we also handle it in damos_filter_match()?

If a memory region partially overlaps a filter range and the split allocation fails in damos_filter_match(), the return value is ignored:

mm/damon/core.c:damos_filter_match() {
    ...
		/* start before the range and overlap */
		if (r->ar.start < start) {
			damon_split_region_at(t, r, start - r->ar.start);
			matched = false;
			break;
		}
		/* start inside the range */
		damon_split_region_at(t, r, end - r->ar.start);
		matched = true;
		break;
    ...
}
Quoted 7 lines

If the split fails, the intact region spanning both outside and inside the filter range is unconditionally evaluated as 'matched = false' or 'matched = true', incorrectly bypassing the filter or applying it to unrelated memory outside the intended range.

Could this lead to destructive actions (e.g., PAGEOUT) being applied to explicitly protected memory?

Nice catch. I will fix this together.

Quoted 7 lines
Quoted 4 lines
mm/damon/core.c +0 -0
 		}
 		if (damos_core_filter_out(c, t, r, s))
 			return;
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1

Thanks,
SJ

author maintainer
#4 lore raw

Re: [RFC PATCH 1/9] mm/damon/core: skip applying scheme if region split for quota fails

Quoted 63 lines · On Sat, 01 Aug 2026 13:02:08 -0700 SJ Park <[email protected]> wrote:
Quoted 61 lines · On Sat, 01 Aug 2026 17:53:34 +0000 [email protected] wrote:

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

Pre-existing issues:
- [High] Unhandled failure of damon_split_region_at() in damos_filter_match() leads to bypassing or incorrectly applying address filters. --

--- Patch [1]: [RFC PATCH 1/9] mm/damon/core: skip applying scheme if region split for quota fails --- commit 812934b542c69c596afadb66403f8c9c11c5bf91
Author: SJ Park <[email protected]>

mm/damon/core: skip applying scheme if region split for quota fails

damos_apply_scheme() splits a region and apply the action to the subregion if it is needed for not violating the quota. The split operation (damon_split_region_at()) could fail for allocation failure. In the case, the quota could be violated. From the user's perspective, DAMOS becomes more aggressive than expected under the extreme situation. Handle the failure.

mm/damon/core.c static void damos_apply_scheme(struct damon_ctx *c, struct damon_target *t, · line 2617

Quoted 11 lines
mm/damon/core.c +2 -1
index 644daf5a16560..e2900d0c984c9 100644
@@ -2613,7 +2613,8 @@ static void damos_apply_scheme(struct damon_ctx *c, struct damon_target *t,
 					c->min_region_sz);
 			if (!sz)
 				goto update_stat;
-			damon_split_region_at(t, r, sz);
+			if (damon_split_region_at(t, r, sz))
+				goto update_stat;

[Severity: High]
This is a pre-existing issue, but since we are handling the failure of damon_split_region_at() here, should we also handle it in damos_filter_match()?

If a memory region partially overlaps a filter range and the split allocation fails in damos_filter_match(), the return value is ignored:

mm/damon/core.c:damos_filter_match() {
    ...
		/* start before the range and overlap */
		if (r->ar.start < start) {
			damon_split_region_at(t, r, start - r->ar.start);
			matched = false;
			break;
		}
		/* start inside the range */
		damon_split_region_at(t, r, end - r->ar.start);
		matched = true;
		break;
    ...
}

If the split fails, the intact region spanning both outside and inside the filter range is unconditionally evaluated as 'matched = false' or 'matched = true', incorrectly bypassing the filter or applying it to unrelated memory outside the intended range.

Quoted 4 lines
Quoted 2 lines

Could this lead to destructive actions (e.g., PAGEOUT) being applied to explicitly protected memory?

Nice catch. I will fix this together.

But, it is unclear what to do in this case. Maybe it is better to just back off and cancel the DAMOS action. And that would be no small change. I will work on it separately.

Quoted 6 lines
Quoted 6 lines
Quoted 5 lines
mm/damon/core.c +0 -0
 		}
 		if (damos_core_filter_out(c, t, r, s))
 			return;
Quoted 7 lines
Quoted 2 lines
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1

Thanks,
SJ

Thanks,
SJ

author maintainer
#5 lore raw

RFC PATCH 2/9 mm/damon/core: initialize damos_quota_goal->last_psi_total

In reply to SJ Park

When DAMOS_QUOTA_SOME_MEM_PSI_US metric damos quota goal is set, the PSI delta for the feedback loop is calculated using damos_quota_goal->last_psi_total. It is not initialized at the beginning. So the first iteration of the feedback loop uses the uninitialized value and makes an unexpected starting point quota. Initialize the value at the beginning of kdamond.

The user impact would be trivial because the issue impacts only the initial iteration of the feedback loop. The feedback loop also has an internal cap of the quota adjustment. The wrong adjustment will soon be corrected over a few iterations.

The issue was discovered [1] by Sashiko.

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

Fixes 2dbb60f789cb ("mm/damon/core: implement PSI metric DAMOS quota goal")
Cc <[email protected]> # 6.9.x
---
 mm/damon/core.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)
mm/damon/core.c +12 -0
index e2900d0c984c9..3bdbf4fbf7147 100644
@@ -3728,6 +3728,17 @@ static int kdamond_wait_activation(struct damon_ctx *ctx)
 	return -EBUSY;
 }
 
+static void damos_init_quota_goal_last_psi(struct damos *s)
+{
+	struct damos_quota_goal *goal;
+
+	damos_for_each_quota_goal(goal, &s->quota) {
+		if (goal->metric != DAMOS_QUOTA_SOME_MEM_PSI_US)
+			continue;
+		goal->last_psi_total = damos_get_some_mem_psi_total();
+	}
+}
+
 static void kdamond_init_ctx(struct damon_ctx *ctx)
 {
 	unsigned long sample_interval = ctx->attrs.sample_interval ?
@@ -3744,6 +3755,7 @@ static void kdamond_init_ctx(struct damon_ctx *ctx)
 	damon_for_each_scheme(scheme, ctx) {
 		damos_set_next_apply_sis(scheme, ctx);
 		damos_set_filters_default_reject(scheme);
+		damos_init_quota_goal_last_psi(scheme);
 	}
 }
 
Signature · 1 line
2.47.3
author maintainer
#6 lore raw

RFC PATCH 3/9 mm/damon/paddr: respect folio end for DAMOS_STAT

In reply to SJ Park

The function for applying DAMOS_STAT in DAMON physical address space operation set (paddr), namely damon_pa_stat(), applies DAMOS filters to folios of the given region. For that, it gets folios of addresses in the region. It starts from the region start address and advances the address by the size of the folio of the address until it goes out of the region. If the start address is in the middle of a large folio, and if the next folios are small, some of the next folios could be skipped. Fix the issue by advancing the address to exactly the start address of the next folio.

The user impact is that the DAMOS_STAT-based page level monitoring results become inaccurate. Since the page level monitoring is supposed to provide relatively high precision, this is definitely a problem. It is arguably not critical since it is only monitoring quality degradation.

Fixes bdbe1d7bc325 ("mm/damon/paddr: increment pa_stat damon address range by folio size")
Cc <[email protected]> # 6.14.x
---
 mm/damon/paddr.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
mm/damon/paddr.c +1 -1
index 5c6c3a597fd0b..2ab7b3842701e 100644
@@ -379,7 +379,7 @@ static unsigned long damon_pa_stat(struct damon_region *r,
 
 		if (!damos_pa_filter_out(s, folio))
 			*sz_filter_passed += folio_size(folio) / addr_unit;
-		addr += folio_size(folio);
+		addr = PFN_PHYS(folio_pfn(folio)) + folio_size(folio);
 		folio_put(folio);
 	}
 	s->last_applied = folio;
Signature · 1 line
2.47.3
#7 lore raw

Re: [RFC PATCH 3/9] mm/damon/paddr: respect folio end for DAMOS_STAT

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] Incomplete fix for large folio address iteration boundary bug. --

--- Patch [3]: [RFC PATCH 3/9] mm/damon/paddr: respect folio end for DAMOS_STAT --- commit b98d6f7836f4a90b6724f683127a934a339f77d5
Author: SJ Park <[email protected]>

mm/damon/paddr: respect folio end for DAMOS_STAT

This commit fixes an issue in damon_pa_stat() where advancing the address by folio_size(folio) could skip subsequent folios if the start address is unaligned within a large folio. It corrects this by advancing the address to exactly the start address of the next folio.

mm/damon/paddr.c static unsigned long damon_pa_stat(struct damon_region *r, · line 382

Quoted 10 lines
mm/damon/paddr.c +1 -1
index 5c6c3a597fd0b..2ab7b3842701e 100644
@@ -379,7 +379,7 @@ static unsigned long damon_pa_stat(struct damon_region *r,
 
 		if (!damos_pa_filter_out(s, folio))
 			*sz_filter_passed += folio_size(folio) / addr_unit;
-		addr += folio_size(folio);
+		addr = PFN_PHYS(folio_pfn(folio)) + folio_size(folio);

[Severity: High]
This is a pre-existing issue, but does this same bug exist in other DAMON operations that iterate over folios?

For example, looking at damon_pa_pageout():

mm/damon/paddr.c:damon_pa_pageout() {
    ...
		else
			list_add(&folio->lru, &folio_list);
put_folio:
		addr += folio_size(folio);
		folio_put(folio);
	}
    ...
}

It appears damon_pa_pageout(), damon_pa_de_activate(), and damon_pa_migrate() all still advance the address by folio_size(folio) instead of calculating the exact folio end.

Could these operations also overshoot subsequent memory and skip valid memory folios if the region starts unaligned inside a large folio?

Quoted 3 lines
mm/damon/paddr.c +0 -0
 		folio_put(folio);
 	}
 	s->last_applied = folio;
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
author maintainer
#8 lore raw

Re: [RFC PATCH 3/9] mm/damon/paddr: respect folio end for DAMOS_STAT

Quoted 45 lines · On Sat, 01 Aug 2026 18:21:01 +0000 [email protected] wrote:

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

Pre-existing issues:
- [High] Incomplete fix for large folio address iteration boundary bug. --

--- Patch [3]: [RFC PATCH 3/9] mm/damon/paddr: respect folio end for DAMOS_STAT --- commit b98d6f7836f4a90b6724f683127a934a339f77d5
Author: SJ Park <[email protected]>

mm/damon/paddr: respect folio end for DAMOS_STAT

This commit fixes an issue in damon_pa_stat() where advancing the address by folio_size(folio) could skip subsequent folios if the start address is unaligned within a large folio. It corrects this by advancing the address to exactly the start address of the next folio.

mm/damon/paddr.c static unsigned long damon_pa_stat(struct damon_region *r, · line 382

Quoted 10 lines
mm/damon/paddr.c +1 -1
index 5c6c3a597fd0b..2ab7b3842701e 100644
@@ -379,7 +379,7 @@ static unsigned long damon_pa_stat(struct damon_region *r,
 
 		if (!damos_pa_filter_out(s, folio))
 			*sz_filter_passed += folio_size(folio) / addr_unit;
-		addr += folio_size(folio);
+		addr = PFN_PHYS(folio_pfn(folio)) + folio_size(folio);

[Severity: High]
This is a pre-existing issue, but does this same bug exist in other DAMON operations that iterate over folios?

For example, looking at damon_pa_pageout():

mm/damon/paddr.c:damon_pa_pageout() {
    ...
		else
			list_add(&folio->lru, &folio_list);
put_folio:
		addr += folio_size(folio);
		folio_put(folio);
	}
    ...
}
Quoted 6 lines

It appears damon_pa_pageout(), damon_pa_de_activate(), and damon_pa_migrate() all still advance the address by folio_size(folio) instead of calculating the exact folio end.

Could these operations also overshoot subsequent memory and skip valid memory folios if the region starts unaligned inside a large folio?

A later patch of this series is fixinfg the bug.

Quoted 7 lines
Quoted 4 lines
mm/damon/paddr.c +0 -0
 		folio_put(folio);
 	}
 	s->last_applied = folio;
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3

Thanks,
SJ

author maintainer
#9 lore raw

RFC PATCH 4/9 mm/damon/paddr: respect folio end for DAMOS actions except STAT

In reply to SJ Park

A few functions for applying DAMOS actions including pageout, lru_[de]prio and migrate_{hot,cold} in DAMON physical address space operation set (paddr) collect folios of the given region by getting the folios of region-internal addresses. Then, those functions apply the action to the collected folios at once. The collection starts from the region start address and advances the address by the size of the folio of the address until it goes out of the region. If the start address is in the middle of a large folio, and if the next folios are small, some of the next folios could be skipped. Fix the issue by advancing the address to exactly the start address of the next folio.

The user impact is that DAMOS action is applied to less than expected amount of memory. Given the best effort nature of DAMON, it is no big problem, but it is clearly a bug that is better to be fixed.

The issue was discovered [1] by Sashiko.

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

Fixes 3a06696305e7 ("mm/damon/ops: have damon_get_folio return folio even for tail pages")
Cc <[email protected]> # 6.15.x
---
 mm/damon/paddr.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
mm/damon/paddr.c +3 -3
index 2ab7b3842701e..9ddd1ec8202b7 100644
@@ -264,7 +264,7 @@ static unsigned long damon_pa_pageout(struct damon_region *r,
 		else
 			list_add(&folio->lru, &folio_list);
 put_folio:
-		addr += folio_size(folio);
+		addr = PFN_PHYS(folio_pfn(folio)) + folio_size(folio);
 		folio_put(folio);
 	}
 	if (install_young_filter)
@@ -302,7 +302,7 @@ static inline unsigned long damon_pa_de_activate(
 			folio_deactivate(folio);
 		applied += folio_nr_pages(folio);
 put_folio:
-		addr += folio_size(folio);
+		addr = PFN_PHYS(folio_pfn(folio)) + folio_size(folio);
 		folio_put(folio);
 	}
 	s->last_applied = folio;
@@ -350,7 +350,7 @@ static unsigned long damon_pa_migrate(struct damon_region *r,
 				folio_is_file_lru(folio));
 		list_add(&folio->lru, &folio_list);
 put_folio:
-		addr += folio_size(folio);
+		addr = PFN_PHYS(folio_pfn(folio)) + folio_size(folio);
 		folio_put(folio);
 	}
 	applied = damon_migrate_pages(&folio_list, s->target_nid);
Signature · 1 line
2.47.3
author maintainer
#10 lore raw

RFC PATCH 5/9 mm/damon/vaddr: respect folio end for DAMOS_STAT

In reply to SJ Park

For applying DAMOS_STAT action to a region, DAMON virtual address space operation set (vaddr) calls walk_page_range[_vma]() for the region. The pmd walk entry function, namely damon_va_stat_pmd_entry(), applies DAMOS filters to folios of addresses of the region in the pmd. It starts from the walking address and advances the address by the size of the folio of the address until it goes out of the pmd or the region.

Let's suppose it is for the first pmd of the region, and the region start address is in the middle of a large folio. Also, the next folios are small. Then, some of the next folios could be skipped. Fix the issue by advancing the address to exactly the start address of the next folio.

The user impact is that the DAMOS_STAT-based page level monitoring results become inaccurate. Since the page level monitoring is supposed to provide relatively high precision, this is definitely a problem. It is arguably not critical since it is only monitoring quality degradation.

The issue was discovered [1] by Sashiko.

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

Fixes 63f39737d1e3 ("mm/damon/vaddr: support stat-purpose DAMOS filters")
Cc <[email protected]> # 6.18.x
---
 mm/damon/vaddr.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)
mm/damon/vaddr.c +4 -1
index 0648400b2d65b..035cd509f6b58 100644
@@ -831,6 +831,8 @@ static int damos_va_stat_pmd_entry(pmd_t *pmd, unsigned long addr,
 		return 0;
 
 	for (; addr < next; pte += nr, addr += nr * PAGE_SIZE) {
+		unsigned long page_idx;
+
 		nr = 1;
 		ptent = ptep_get(pte);
 
@@ -844,7 +846,8 @@ static int damos_va_stat_pmd_entry(pmd_t *pmd, unsigned long addr,
 
 		if (!damos_va_filter_out(s, folio, vma, addr, pte, NULL))
 			*sz_filter_passed += folio_size(folio);
-		nr = folio_nr_pages(folio);
+		page_idx = folio_page_idx(folio, pte_page(*pte));
+		nr = folio_nr_pages(folio) - page_idx;
 		s->last_applied = folio;
 	}
 	pte_unmap_unlock(start_pte, ptl);
Signature · 1 line
2.47.3
#11 lore raw

Re: [RFC PATCH 5/9] mm/damon/vaddr: respect folio end for DAMOS_STAT

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

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

New issues:
- [Medium] Direct dereference of PTE pointer *pte instead of using the local safely fetched ptent.

Pre-existing issues:
- [High] Batched PTE advancement blindly skips interleaved folios and PTE-level Accessed/Young bits within a large folio's virtual footprint.
- [High] The exact page iteration bug fixed in damos_va_stat_pmd_entry() is also present in damos_va_migrate_pmd_entry() but was missed by this patch. --

--- Patch [5]: [RFC PATCH 5/9] mm/damon/vaddr: respect folio end for DAMOS_STAT --- commit 20f53909d4e28dc2c1b2dd4ce52890349fc6c85d
Author: SJ Park <[email protected]>

mm/damon/vaddr: respect folio end for DAMOS_STAT

For applying DAMOS_STAT action to a region, DAMON virtual address space operation set (vaddr) calls walk_page_range[_vma]() for the region. The pmd walk entry function, namely damon_va_stat_pmd_entry(), applies DAMOS filters to folios of addresses of the region in the pmd. It starts from the walking address and advances the address by the size of the folio of the address until it goes out of the pmd or the region.

Quoted 14 lines
mm/damon/vaddr.c +2 -0
index 0648400b2d65b..035cd509f6b58 100644
@@ -831,6 +831,8 @@ static int damos_va_stat_pmd_entry(pmd_t *pmd, unsigned long addr,
 		return 0;
 
 	for (; addr < next; pte += nr, addr += nr * PAGE_SIZE) {
+		unsigned long page_idx;
+
 		nr = 1;
 		ptent = ptep_get(pte);
 

mm/damon/vaddr.c static int damos_va_stat_pmd_entry(pmd_t *pmd, unsigned long addr, · line 849

Quoted 5 lines
mm/damon/vaddr.c +1 -1
@@ -844,7 +846,8 @@ static int damos_va_stat_pmd_entry(pmd_t *pmd, unsigned long addr,
 
 		if (!damos_va_filter_out(s, folio, vma, addr, pte, NULL))
 			*sz_filter_passed += folio_size(folio);
-		nr = folio_nr_pages(folio);
+		page_idx = folio_page_idx(folio, pte_page(*pte));

[Severity: Medium]
Is it safe to directly dereference *pte here instead of using the local safely fetched ptent?

Reading a PTE directly on 32-bit architectures (like x86 PAE) can result in a torn read if the hardware concurrently updates the Accessed/Dirty bits, potentially leading to an incorrect PFN and subsequent wild pointer arithmetic.

The local snapshot ptent was already obtained via ptep_get(pte) and should likely be used here.

mm/damon/vaddr.c static int damos_va_stat_pmd_entry(pmd_t *pmd, unsigned long addr, · line 850

Quoted 1 line
mm/damon/vaddr.c +1 -0
+		nr = folio_nr_pages(folio) - page_idx;

[Severity: High]
This is a pre-existing issue, but does this batched PTE advancement blindly skip interleaved folios and PTE-level Accessed/Young bits within a large folio's virtual footprint?

For PTE-mapped large folios, hardware tracks the Accessed bit independently per 4K PTE. By checking only the first PTE and blindly skipping the rest, DAMON ignores accesses to subsequent pages of the folio, potentially classifying hot memory as cold.

Furthermore, if userspace partially unmaps or interleaves different mappings, the blind pte += nr skips those unrelated folios completely without verifying they map the same folio.

mm/damon/vaddr.c static int damos_va_stat_pmd_entry(pmd_t *pmd, unsigned long addr, · line 853

Quoted 3 lines
mm/damon/vaddr.c +0 -0
 		s->last_applied = folio;
 	}
 	pte_unmap_unlock(start_pte, ptl);

While reviewing this patch, I noticed the exact page iteration bug fixed in damos_va_stat_pmd_entry() is also present in damos_va_migrate_pmd_entry() but was missed by this patch.

[Severity: High]
This is a pre-existing issue, but does the DAMOS migrate action also advance the PTE pointer unconditionally by the full size of the large folio?

mm/damon/vaddr.c:damos_va_migrate_pmd_entry() {
	...
	for (; addr < next; pte += nr, addr += nr * PAGE_SIZE) {
		...
		damos_va_migrate_dests_add(folio, walk->vma, addr, dests,
				migration_lists);
		nr = folio_nr_pages(folio);
	}
	...
}

If the scanning starts in the middle of a large folio, it will advance past the end of the large folio and skip unrelated folios that follow it, failing to migrate them.

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

Re: [RFC PATCH 5/9] mm/damon/vaddr: respect folio end for DAMOS_STAT

Quoted 49 lines · On Sat, 01 Aug 2026 18:50:13 +0000 [email protected] wrote:

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

New issues:
- [Medium] Direct dereference of PTE pointer *pte instead of using the local safely fetched ptent.

Pre-existing issues:
- [High] Batched PTE advancement blindly skips interleaved folios and PTE-level Accessed/Young bits within a large folio's virtual footprint.
- [High] The exact page iteration bug fixed in damos_va_stat_pmd_entry() is also present in damos_va_migrate_pmd_entry() but was missed by this patch. --

--- Patch [5]: [RFC PATCH 5/9] mm/damon/vaddr: respect folio end for DAMOS_STAT --- commit 20f53909d4e28dc2c1b2dd4ce52890349fc6c85d
Author: SJ Park <[email protected]>

mm/damon/vaddr: respect folio end for DAMOS_STAT

For applying DAMOS_STAT action to a region, DAMON virtual address space operation set (vaddr) calls walk_page_range[_vma]() for the region. The pmd walk entry function, namely damon_va_stat_pmd_entry(), applies DAMOS filters to folios of addresses of the region in the pmd. It starts from the walking address and advances the address by the size of the folio of the address until it goes out of the pmd or the region.

Quoted 14 lines
mm/damon/vaddr.c +2 -0
index 0648400b2d65b..035cd509f6b58 100644
@@ -831,6 +831,8 @@ static int damos_va_stat_pmd_entry(pmd_t *pmd, unsigned long addr,
 		return 0;
 
 	for (; addr < next; pte += nr, addr += nr * PAGE_SIZE) {
+		unsigned long page_idx;
+
 		nr = 1;
 		ptent = ptep_get(pte);
 

mm/damon/vaddr.c static int damos_va_stat_pmd_entry(pmd_t *pmd, unsigned long addr, · line 849

Quoted 5 lines
mm/damon/vaddr.c +1 -1
@@ -844,7 +846,8 @@ static int damos_va_stat_pmd_entry(pmd_t *pmd, unsigned long addr,
 
 		if (!damos_va_filter_out(s, folio, vma, addr, pte, NULL))
 			*sz_filter_passed += folio_size(folio);
-		nr = folio_nr_pages(folio);
+		page_idx = folio_page_idx(folio, pte_page(*pte));

[Severity: Medium]
Is it safe to directly dereference *pte here instead of using the local safely fetched ptent?

Quoted 7 lines

Reading a PTE directly on 32-bit architectures (like x86 PAE) can result in a torn read if the hardware concurrently updates the Accessed/Dirty bits, potentially leading to an incorrect PFN and subsequent wild pointer arithmetic.

The local snapshot ptent was already obtained via ptep_get(pte) and should likely be used here.

Good catch, I will fix this in the next version.

Quoted 8 lines

mm/damon/vaddr.c static int damos_va_stat_pmd_entry(pmd_t *pmd, unsigned long addr, · line 850

Quoted 2 lines
mm/damon/vaddr.c +1 -0
+		nr = folio_nr_pages(folio) - page_idx;

[Severity: High]
This is a pre-existing issue, but does this batched PTE advancement blindly skip interleaved folios and PTE-level Accessed/Young bits within a large folio's virtual footprint?

Quoted 8 lines

For PTE-mapped large folios, hardware tracks the Accessed bit independently per 4K PTE. By checking only the first PTE and blindly skipping the rest, DAMON ignores accesses to subsequent pages of the folio, potentially classifying hot memory as cold.

Furthermore, if userspace partially unmaps or interleaves different mappings, the blind pte += nr skips those unrelated folios completely without verifying they map the same folio.

Good points that can further improved. But seems handling these will make it too complicated, compared to the gain from the improvements. I will keep this as is unless others raise different opinions.

Quoted 24 lines

mm/damon/vaddr.c static int damos_va_stat_pmd_entry(pmd_t *pmd, unsigned long addr, · line 853

Quoted 4 lines
mm/damon/vaddr.c +0 -0
 		s->last_applied = folio;
 	}
 	pte_unmap_unlock(start_pte, ptl);

While reviewing this patch, I noticed the exact page iteration bug fixed in damos_va_stat_pmd_entry() is also present in damos_va_migrate_pmd_entry() but was missed by this patch.

[Severity: High]
This is a pre-existing issue, but does the DAMOS migrate action also advance the PTE pointer unconditionally by the full size of the large folio?

mm/damon/vaddr.c:damos_va_migrate_pmd_entry() {
	...
	for (; addr < next; pte += nr, addr += nr * PAGE_SIZE) {
		...
		damos_va_migrate_dests_add(folio, walk->vma, addr, dests,
				migration_lists);
		nr = folio_nr_pages(folio);
	}
	...
}
Quoted 3 lines

If the scanning starts in the middle of a large folio, it will advance past the end of the large folio and skip unrelated folios that follow it, failing to migrate them.

Yes, a later patch in this series will fix it.

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

Thanks,
SJ

author maintainer
#13 lore raw

RFC PATCH 6/9 mm/damon/vaddr: respect folio end for DAMOS_MIGRATE_{HOT,COLD}

In reply to SJ Park

For applying DAMOS_MIGRATE_{HOT,COLD} actions to a region, DAMON virtual address space operation set (vaddr) calls walk_page_range[_vma]() for the region. The pmd walk entry function, namely damon_va_migrate_pmd_entry(), collects folios of addresses of the region in the pmd. It starts from the walking address and advances the address by the size of the folio of the address until it goes out of the pmd or the region.

Let's suppose it is for the first pmd of the region, and the region start address is in the middle of a large folio. Also, the next folios are small. Then, some of the next folios could be skipped. Fix the issue by advancing the address to exactly the start address of the next folio.

The user impact is that DAMOS action is applied to less than expected amount of memory. Given the best effort nature of DAMON, it is no big problem, but it is clearly a bug that is better to be fixed.

The issue was discovered [1] by Sashiko.

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

Fixes 09efc56a3b1c ("mm/damon/vaddr: consistently use only pmd_entry for damos_migrate")
Cc <[email protected]> # 6.19.x
---
 mm/damon/vaddr.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)
mm/damon/vaddr.c +4 -1
index 035cd509f6b58..aa833c2c4fa35 100644
@@ -669,6 +669,8 @@ static int damos_va_migrate_pmd_entry(pmd_t *pmd, unsigned long addr,
 		return 0;
 
 	for (; addr < next; pte += nr, addr += nr * PAGE_SIZE) {
+		unsigned long page_idx;
+
 		nr = 1;
 		ptent = ptep_get(pte);
 
@@ -681,7 +683,8 @@ static int damos_va_migrate_pmd_entry(pmd_t *pmd, unsigned long addr,
 			continue;
 		damos_va_migrate_dests_add(folio, walk->vma, addr, dests,
 				migration_lists);
-		nr = folio_nr_pages(folio);
+		page_idx = folio_page_idx(folio, pte_page(*pte));
+		nr = folio_nr_pages(folio) - page_idx;
 	}
 	pte_unmap_unlock(start_pte, ptl);
 	return 0;
Signature · 1 line
2.47.3
author maintainer
#14 lore raw

RFC PATCH 7/9 mm/damon/core: handle extreme memory state in damon_get_node_mem_bp()

In reply to SJ Park

In an extreme and unlikely situation, si_meminfo_node() might let the caller show zero total ram. That could cause a divide by zero in damon_get_node_mem_bp(). Fix it by setting the totalram one byte in the case.

The issue was discovered [1] by Sashiko.

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

Fixes 0e1c773b501f ("mm/damon/core: introduce damos quota goal metrics for memory node utilization")
Cc <[email protected]> # 6.16.x
---
 mm/damon/core.c | 2 ++
 1 file changed, 2 insertions(+)
mm/damon/core.c +2 -0
index 3bdbf4fbf7147..269865eb8fbfa 100644
@@ -2816,6 +2816,8 @@ static __kernel_ulong_t damos_get_node_mem_bp(
 	}
 
 	si_meminfo_node(&i, goal->nid);
+	if (!i.totalram)
+		i.totalram = 1;
 	if (goal->metric == DAMOS_QUOTA_NODE_MEM_USED_BP)
 		numerator = i.totalram - i.freeram;
 	else	/* DAMOS_QUOTA_NODE_MEM_FREE_BP */
Signature · 1 line
2.47.3
#15 lore raw

Re: [RFC PATCH 7/9] mm/damon/core: handle extreme memory state in damon_get_node_mem_bp()

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

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

New issues:
- [Medium] Falsely reporting 100% memory utilization for an empty node due to a flawed division-by-zero fix.

Pre-existing issues:
- [High] Potential integer underflow when evaluating i.totalram - i.freeram due to lockless vmstat reads.
- [High] The patch fixes a potential divide-by-zero in damos_get_node_mem_bp(), but fails to apply the same fix to damos_get_node_memcg_used_bp().
- [High] Multiplication overflow in basis-point calculation on 32-bit architectures.
- [High] Potential divide by zero in damos_get_in_active_mem_bp() if the system has no active or inactive LRU pages.
--

--- Patch [7]: [RFC PATCH 7/9] mm/damon/core: handle extreme memory state in damon_get_node_mem_bp() --- commit 0e2bb1b3ade5000769644aa674efbcfec5b43152
Author: SJ Park <[email protected]>
Subject: mm/damon/core: handle extreme memory state in damon_get_node_mem_bp()

In an extreme and unlikely situation, si_meminfo_node() might let the caller show zero total ram. That could cause a divide by zero in damon_get_node_mem_bp(). Fix it by setting the totalram one byte in the case.

mm/damon/core.c static __kernel_ulong_t damos_get_node_mem_bp( · line 2822

Quoted 11 lines
mm/damon/core.c +4 -0
@@ -2816,6 +2816,8 @@ static __kernel_ulong_t damos_get_node_mem_bp(
 	}
 
 	si_meminfo_node(&i, goal->nid);
+	if (!i.totalram)
+		i.totalram = 1;
+	if (goal->metric == DAMOS_QUOTA_NODE_MEM_USED_BP)
+		numerator = i.totalram - i.freeram;

[Severity: Medium]
Does setting totalram to 1 artificially inflate the utilization for an empty node?

If si_meminfo_node() returns 0 for both totalram and freeram, numerator becomes 1 - 0 = 1. This would result in 100 percent used memory (10000 basis points), which contradicts the fallback logic that reports 0 percent for an empty node. Will this trigger DAMOS quota actions erroneously?

[Severity: High]
This is a pre-existing issue, but can this subtraction underflow?

Because si_meminfo_node() computes freeram via lockless zone stats, per-CPU stat lag can temporarily cause freeram to exceed totalram. If freeram > totalram, the unsigned long numerator will underflow to a massive value.

If we force totalram to 1 here and freeram is greater than 0, doesn't it guarantee this underflow will occur?

mm/damon/core.c static __kernel_ulong_t damos_get_node_mem_bp( · line 2823

Quoted 1 line
mm/damon/core.c +1 -0
+	else	/* DAMOS_QUOTA_NODE_MEM_FREE_BP */

[ ... ]

[Severity: High]
This is a pre-existing issue, but does the same divide-by-zero happen in the adjacent damos_get_node_memcg_used_bp() function?

mm/damon/core.c:damos_get_node_memcg_used_bp() {
        ...
        si_meminfo_node(&i, goal->nid);
        ...
        return mult_frac(numerator, 10000, i.totalram);
}

Since si_meminfo_node() can return 0 for totalram, wouldn't this also cause a divide-by-zero here if i.totalram isn't validated?

[Severity: High]
This is a pre-existing issue, but can the basis-point calculation overflow on 32-bit architectures?

mm/damon/core.c:damos_get_node_mem_bp() {
        ...
        return mult_frac(numerator, 10000, i.totalram);
}

Since mult_frac(numerator, 10000, denom) computes the remainder times 10000, if the remainder exceeds roughly 429,496, won't this overflow a 32-bit unsigned long and produce an incorrect result?

[Severity: High]
This is a pre-existing issue, but is there a potential divide-by-zero here if the system has no active or inactive LRU pages?

mm/damon/core.c:damos_get_in_active_mem_bp() {
        ...
        total = active + inactive;
        if (active_ratio)
                return mult_frac(active, 10000, total);
        return mult_frac(inactive, 10000, total);
}

If all memory is mlocked or purely kernel-allocated, both active and inactive could be 0, making total 0. Will mult_frac() panic when dividing by total?

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

Re: [RFC PATCH 7/9] mm/damon/core: handle extreme memory state in damon_get_node_mem_bp()

Quoted 39 lines · On Sat, 01 Aug 2026 19:11:30 +0000 [email protected] wrote:

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

New issues:
- [Medium] Falsely reporting 100% memory utilization for an empty node due to a flawed division-by-zero fix.

Pre-existing issues:
- [High] Potential integer underflow when evaluating i.totalram - i.freeram due to lockless vmstat reads.
- [High] The patch fixes a potential divide-by-zero in damos_get_node_mem_bp(), but fails to apply the same fix to damos_get_node_memcg_used_bp().
- [High] Multiplication overflow in basis-point calculation on 32-bit architectures.
- [High] Potential divide by zero in damos_get_in_active_mem_bp() if the system has no active or inactive LRU pages.
--

--- Patch [7]: [RFC PATCH 7/9] mm/damon/core: handle extreme memory state in damon_get_node_mem_bp() --- commit 0e2bb1b3ade5000769644aa674efbcfec5b43152
Author: SJ Park <[email protected]>
Subject: mm/damon/core: handle extreme memory state in damon_get_node_mem_bp()

In an extreme and unlikely situation, si_meminfo_node() might let the caller show zero total ram. That could cause a divide by zero in damon_get_node_mem_bp(). Fix it by setting the totalram one byte in the case.

mm/damon/core.c static __kernel_ulong_t damos_get_node_mem_bp( · line 2822

Quoted 11 lines
mm/damon/core.c +4 -0
@@ -2816,6 +2816,8 @@ static __kernel_ulong_t damos_get_node_mem_bp(
 	}
 
 	si_meminfo_node(&i, goal->nid);
+	if (!i.totalram)
+		i.totalram = 1;
+	if (goal->metric == DAMOS_QUOTA_NODE_MEM_USED_BP)
+		numerator = i.totalram - i.freeram;

[Severity: Medium]
Does setting totalram to 1 artificially inflate the utilization for an empty node?

Quoted 4 lines

If si_meminfo_node() returns 0 for both totalram and freeram, numerator becomes 1 - 0 = 1. This would result in 100 percent used memory (10000 basis points), which contradicts the fallback logic that reports 0 percent for an empty node. Will this trigger DAMOS quota actions erroneously?

I think this is fine. Just different perspectives, and no real problem in real life. We can later revisit for consistent interpretation of this extreme situations.

Quoted 10 lines

[Severity: High]
This is a pre-existing issue, but can this subtraction underflow?

Because si_meminfo_node() computes freeram via lockless zone stats, per-CPU stat lag can temporarily cause freeram to exceed totalram. If freeram > totalram, the unsigned long numerator will underflow to a massive value.

If we force totalram to 1 here and freeram is greater than 0, doesn't it guarantee this underflow will occur?

Good point, I will fix in the next version.

Quoted 16 lines

mm/damon/core.c static __kernel_ulong_t damos_get_node_mem_bp( · line 2823

Quoted 2 lines
mm/damon/core.c +1 -0
+	else	/* DAMOS_QUOTA_NODE_MEM_FREE_BP */

[ ... ]

[Severity: High]
This is a pre-existing issue, but does the same divide-by-zero happen in the adjacent damos_get_node_memcg_used_bp() function?

mm/damon/core.c:damos_get_node_memcg_used_bp() {
        ...
        si_meminfo_node(&i, goal->nid);
        ...
        return mult_frac(numerator, 10000, i.totalram);
}
Quoted 2 lines

Since si_meminfo_node() can return 0 for totalram, wouldn't this also cause a divide-by-zero here if i.totalram isn't validated?

A later patch in this series will fix it.

Quoted 5 lines

[Severity: High]
This is a pre-existing issue, but can the basis-point calculation overflow on 32-bit architectures?

Quoted 8 lines
mm/damon/core.c:damos_get_node_mem_bp() {
        ...
        return mult_frac(numerator, 10000, i.totalram);
}

Since mult_frac(numerator, 10000, denom) computes the remainder times 10000, if the remainder exceeds roughly 429,496, won't this overflow a 32-bit unsigned long and produce an incorrect result?

Known low priority issue.

Quoted 13 lines

[Severity: High]
This is a pre-existing issue, but is there a potential divide-by-zero here if the system has no active or inactive LRU pages?

mm/damon/core.c:damos_get_in_active_mem_bp() {
        ...
        total = active + inactive;
        if (active_ratio)
                return mult_frac(active, 10000, total);
        return mult_frac(inactive, 10000, total);
}
Quoted 3 lines

If all memory is mlocked or purely kernel-allocated, both active and inactive could be 0, making total 0. Will mult_frac() panic when dividing by total?

A later patch in this series will fix it.

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

Thanks,
SJ

author maintainer
#17 lore raw

RFC PATCH 8/9 mm/damon/core: handle extreme memory state in get_node_memcg_used_bp()

In reply to SJ Park

In extreme unlikely situations, total memory might be zero. In less extreme but still very unlikely situations, lruvec_page_state() calls might let the caller show used memory larger than total memory. In the two cases, damos_get_node_memcg_used_bp() could cause division by zero, or return underflowed value, respectively. Handle the cases by returning 100% and 0% for the two cases, respectively.

This issue was discovered [1] by Sashiko.

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

Fixes b74a120bcf50 ("mm/damon/core: implement DAMOS_QUOTA_NODE_MEMCG_USED_BP")
Cc <[email protected]> # 6.19.x
---
 mm/damon/core.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)
mm/damon/core.c +8 -2
index 269865eb8fbfa..76764a2056f7c 100644
@@ -2858,10 +2858,16 @@ static unsigned long damos_get_node_memcg_used_bp(
 	mem_cgroup_put(memcg);
 
 	si_meminfo_node(&i, goal->nid);
-	if (goal->metric == DAMOS_QUOTA_NODE_MEMCG_USED_BP)
+	if (!i.totalram)
+		return 10000;
+	if (goal->metric == DAMOS_QUOTA_NODE_MEMCG_USED_BP) {
 		numerator = used_pages;
-	else	/* DAMOS_QUOTA_NODE_MEMCG_FREE_BP */
+	} else {
+		/* DAMOS_QUOTA_NODE_MEMCG_FREE_BP */
+		if (i.totalram < used_pages)
+			return 0;
 		numerator = i.totalram - used_pages;
+	}
 	return mult_frac(numerator, 10000, i.totalram);
 }
 
Signature · 1 line
2.47.3
#18 lore raw

Re: [RFC PATCH 8/9] mm/damon/core: handle extreme memory state in get_node_memcg_used_bp()

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

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

Pre-existing issues:
- [High] Fallback values for invalid nodes and missing memcgs incorrectly encourage aggressive scheme application instead of throttling.
- [High] Division by zero in damos_get_in_active_mem_bp() when total LRU memory is zero.
- [High] Division by zero in damos_quota_score() when a quota goal has a target_value of 0.
--

--- Patch [8]: [RFC PATCH 8/9] mm/damon/core: handle extreme memory state in get_node_memcg_used_bp() --- commit 7261d4dd3fd172b597cdefa3462cc680deabe142
Author: SJ Park <[email protected]>

mm/damon/core: handle extreme memory state in get_node_memcg_used_bp()

This commit adds fallback logic to handle extreme situations where total memory is zero, or when lruvec_page_state() returns used memory larger than total memory. It returns 100% and 0% respectively to avoid division by zero or underflow in damos_get_node_memcg_used_bp().

mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2862

Quoted 11 lines
mm/damon/core.c +2 -1
index 269865eb8fbfa..76764a2056f7c 100644
@@ -2858,10 +2858,16 @@ static unsigned long damos_get_node_memcg_used_bp(
 	mem_cgroup_put(memcg);
 
 	si_meminfo_node(&i, goal->nid);
-	if (goal->metric == DAMOS_QUOTA_NODE_MEMCG_USED_BP)
+	if (!i.totalram)
+		return 10000;

[Severity: High]
This isn't a bug introduced by this patch, but returning 0 in other fallback paths for missing memcgs or invalid nodes incorrectly encourages aggressive scheme application.

In the DAMOS quota auto-tuner (damon_feed_loop_next_input()), a metric score of 0 evaluates as severe under-achievement, causing the tuner to aggressively maximize the quota. To safely halt a scheme for invalid targets, the metric getter should probably return a value that yields a high score (e.g., 10000).

However, multiple pre-existing fallback paths incorrectly return 0:

mm/damon/core.c:damos_get_node_memcg_used_bp()

	if (goal->metric == DAMOS_QUOTA_NODE_MEMCG_USED_BP)
		return 0;

mm/damon/core.c:damos_get_node_mem_bp()

	else	/* DAMOS_QUOTA_NODE_MEM_FREE_BP */
		numerator = i.freeram;
	return mult_frac(numerator, 10000, i.totalram);

(when !totalram, this evaluates to 0)

mm/damon/core.c:damos_get_node_mem_bp()

	static __kernel_ulong_t damos_get_node_mem_bp(
			struct damos_quota_goal *goal)
	{
		return 0;
	}

Could these fallbacks cause the auto-tuner to unleash maximum operations and lead to severe over-reclaim or wasted CPU when a node becomes invalid or a memcg is destroyed?

mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2872

Quoted 11 lines
mm/damon/core.c +6 -1
+	if (goal->metric == DAMOS_QUOTA_NODE_MEMCG_USED_BP) {
 		numerator = used_pages;
-	else	/* DAMOS_QUOTA_NODE_MEMCG_FREE_BP */
+	} else {
+		/* DAMOS_QUOTA_NODE_MEMCG_FREE_BP */
+		if (i.totalram < used_pages)
+			return 0;
 		numerator = i.totalram - used_pages;
+	}
 	return mult_frac(numerator, 10000, i.totalram);
 }

[Severity: High]
This is a pre-existing issue with a similar root cause, but does this same division by zero problem exist in damos_get_in_active_mem_bp()?

mm/damon/core.c:damos_get_in_active_mem_bp()

	if (active_ratio)
		return mult_frac(active, 10000, total);
	return mult_frac(inactive, 10000, total);

If the system has 0 pages on the LRU (e.g., during early boot, in extremely constrained environments, or if all memory is unevictable), total evaluates to 0. Would this result in a kernel panic when passed to mult_frac()?

[Severity: High]
This is another pre-existing issue, but does damos_quota_score() also suffer from a division by zero when a quota goal has a target_value of 0?

mm/damon/core.c:damos_quota_score()

	highest_score = max(highest_score,
		mult_frac(goal->current_value, 10000,
			goal->target_value));

While the sysfs interface drops goals with a 0 target value, the core allocation function damos_new_quota_goal() does not validate it. The mtier sample module exposes these as module parameters:

samples/damon/mtier.c:damon_sample_mtier_build_ctx()

	quota_goal = damos_new_quota_goal(
		promote ? DAMOS_QUOTA_NODE_MEM_USED_BP :
		DAMOS_QUOTA_NODE_MEM_FREE_BP,
		promote ? node0_mem_used_bp : node0_mem_free_bp);

If an administrator or privileged user writes 0 to the parameter and enables the module, would this immediately crash the kernel when the DAMOS worker thread evaluates the quota?

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

Re: [RFC PATCH 8/9] mm/damon/core: handle extreme memory state in get_node_memcg_used_bp()

Quoted 62 lines · On Sat, 01 Aug 2026 19:31:01 +0000 [email protected] wrote:

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

Pre-existing issues:
- [High] Fallback values for invalid nodes and missing memcgs incorrectly encourage aggressive scheme application instead of throttling.
- [High] Division by zero in damos_get_in_active_mem_bp() when total LRU memory is zero.
- [High] Division by zero in damos_quota_score() when a quota goal has a target_value of 0.
--

--- Patch [8]: [RFC PATCH 8/9] mm/damon/core: handle extreme memory state in get_node_memcg_used_bp() --- commit 7261d4dd3fd172b597cdefa3462cc680deabe142
Author: SJ Park <[email protected]>

mm/damon/core: handle extreme memory state in get_node_memcg_used_bp()

This commit adds fallback logic to handle extreme situations where total memory is zero, or when lruvec_page_state() returns used memory larger than total memory. It returns 100% and 0% respectively to avoid division by zero or underflow in damos_get_node_memcg_used_bp().

mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2862

Quoted 11 lines
mm/damon/core.c +2 -1
index 269865eb8fbfa..76764a2056f7c 100644
@@ -2858,10 +2858,16 @@ static unsigned long damos_get_node_memcg_used_bp(
 	mem_cgroup_put(memcg);
 
 	si_meminfo_node(&i, goal->nid);
-	if (goal->metric == DAMOS_QUOTA_NODE_MEMCG_USED_BP)
+	if (!i.totalram)
+		return 10000;

[Severity: High]
This isn't a bug introduced by this patch, but returning 0 in other fallback paths for missing memcgs or invalid nodes incorrectly encourages aggressive scheme application.

In the DAMOS quota auto-tuner (damon_feed_loop_next_input()), a metric score of 0 evaluates as severe under-achievement, causing the tuner to aggressively maximize the quota. To safely halt a scheme for invalid targets, the metric getter should probably return a value that yields a high score (e.g., 10000).

However, multiple pre-existing fallback paths incorrectly return 0:

mm/damon/core.c:damos_get_node_memcg_used_bp()

	if (goal->metric == DAMOS_QUOTA_NODE_MEMCG_USED_BP)
		return 0;

mm/damon/core.c:damos_get_node_mem_bp()

	else	/* DAMOS_QUOTA_NODE_MEM_FREE_BP */
		numerator = i.freeram;
	return mult_frac(numerator, 10000, i.totalram);

(when !totalram, this evaluates to 0)

mm/damon/core.c:damos_get_node_mem_bp()

	static __kernel_ulong_t damos_get_node_mem_bp(
			struct damos_quota_goal *goal)
	{
		return 0;
	}
Quoted 3 lines

Could these fallbacks cause the auto-tuner to unleash maximum operations and lead to severe over-reclaim or wasted CPU when a node becomes invalid or a memcg is destroyed?

This is an intended behavior.

Quoted 17 lines

mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2872

Quoted 12 lines
mm/damon/core.c +6 -1
+	if (goal->metric == DAMOS_QUOTA_NODE_MEMCG_USED_BP) {
 		numerator = used_pages;
-	else	/* DAMOS_QUOTA_NODE_MEMCG_FREE_BP */
+	} else {
+		/* DAMOS_QUOTA_NODE_MEMCG_FREE_BP */
+		if (i.totalram < used_pages)
+			return 0;
 		numerator = i.totalram - used_pages;
+	}
 	return mult_frac(numerator, 10000, i.totalram);
 }

[Severity: High]
This is a pre-existing issue with a similar root cause, but does this same division by zero problem exist in damos_get_in_active_mem_bp()?

Quoted 8 lines

mm/damon/core.c:damos_get_in_active_mem_bp()

	if (active_ratio)
		return mult_frac(active, 10000, total);
	return mult_frac(inactive, 10000, total);

If the system has 0 pages on the LRU (e.g., during early boot, in extremely constrained environments, or if all memory is unevictable), total evaluates to 0. Would this result in a kernel panic when passed to mult_frac()?

A later patch in this series will fix it.

Quoted 16 lines

[Severity: High]
This is another pre-existing issue, but does damos_quota_score() also suffer from a division by zero when a quota goal has a target_value of 0?

mm/damon/core.c:damos_quota_score()

	highest_score = max(highest_score,
		mult_frac(goal->current_value, 10000,
			goal->target_value));

While the sysfs interface drops goals with a 0 target value, the core allocation function damos_new_quota_goal() does not validate it. The mtier sample module exposes these as module parameters:

samples/damon/mtier.c:damon_sample_mtier_build_ctx()

Quoted 8 lines
	quota_goal = damos_new_quota_goal(
		promote ? DAMOS_QUOTA_NODE_MEM_USED_BP :
		DAMOS_QUOTA_NODE_MEM_FREE_BP,
		promote ? node0_mem_used_bp : node0_mem_free_bp);

If an administrator or privileged user writes 0 to the parameter and enables the module, would this immediately crash the kernel when the DAMOS worker thread evaluates the quota?

Good catch, I will work on this separately or as a part of this series.

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

Thanks,
SJ

author maintainer
#20 lore raw

RFC PATCH 9/9 mm/damon/core: handle extreme memory state in get_in_active_mem_bp()

In reply to SJ Park

damon_get_intervals_adaptation_bp() uses the sum of the active and inactive memory amount as a denominator. In an extreme and unlikely environment, active and inactive memory might be zero. In this case, hence, it results in a divide by zero problem. Avoid it by changing the denominator to one if it is zero, before it is being used.

The issue was discovered [1] by Sashiko.

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

Fixes 4835e2871321 ("mm/damon/core: introduce [in]active memory ratio damos quota goal metric")
Cc <[email protected]> # 7.0.x
---
 mm/damon/core.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
mm/damon/core.c +1 -1
index 76764a2056f7c..4944cf2c5afae 100644
@@ -3010,7 +3010,7 @@ static unsigned int damos_get_in_active_mem_bp(bool active_ratio)
 		global_node_page_state(NR_LRU_BASE + LRU_ACTIVE_FILE);
 	inactive = global_node_page_state(NR_LRU_BASE + LRU_INACTIVE_ANON) +
 		global_node_page_state(NR_LRU_BASE + LRU_INACTIVE_FILE);
-	total = active + inactive;
+	total = max(active + inactive, 1);
 	if (active_ratio)
 		return mult_frac(active, 10000, total);
 	return mult_frac(inactive, 10000, total);
Signature · 1 line
2.47.3
#21 lore raw

Re: [RFC PATCH 9/9] mm/damon/core: handle extreme memory state in get_in_active_mem_bp()

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

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

New issues:
- [Medium] The commit message incorrectly identifies the function being modified, failing to align its claims with the code changes.

Pre-existing issues:
- [High] Multiplication overflow in mult_frac() and other basis point scaling calculations on 32-bit architectures leads to incorrect memory ratio calculations and scaling. --

--- Patch [9]: [RFC PATCH 9/9] mm/damon/core: handle extreme memory state in get_in_active_mem_bp() --- commit fee9e9f299e48399ca370c1f9725936302eaeb8c
Author: SJ Park <[email protected]>
Subject: mm/damon/core: handle extreme memory state in get_in_active_mem_bp()

This patch prevents a divide-by-zero risk in DAMON's active memory ratio calculations. In extreme environments where active and inactive memory both amount to zero, the patch clamps the total memory denominator to a minimum of 1 to avoid a crash.

Quoted 3 lines

damon_get_intervals_adaptation_bp() uses the sum of the active and inactive memory amount as a denominator. In an extreme and unlikely environment, active and inactive memory might be zero.

[Severity: Medium]
Is the function name referenced in the commit message correct?

The commit message explicitly states that damon_get_intervals_adaptation_bp() is being modified to fix a divide-by-zero bug, but the patch actually modifies damos_get_in_active_mem_bp().

mm/damon/core.c static unsigned int damos_get_in_active_mem_bp(bool active_ratio) · line 3015

Quoted 12 lines
mm/damon/core.c +1 -1
index 76764a2056f7c..4944cf2c5afae 100644
@@ -3010,7 +3010,7 @@ static unsigned int damos_get_in_active_mem_bp(bool active_ratio)
 		global_node_page_state(NR_LRU_BASE + LRU_ACTIVE_FILE);
 	inactive = global_node_page_state(NR_LRU_BASE + LRU_INACTIVE_ANON) +
 		global_node_page_state(NR_LRU_BASE + LRU_INACTIVE_FILE);
-	total = active + inactive;
+	total = max(active + inactive, 1);
 	if (active_ratio)
 		return mult_frac(active, 10000, total);

[Severity: High]
This is a pre-existing issue, but does this calculation overflow on 32-bit architectures?

In damos_get_in_active_mem_bp(), if active memory exceeds approximately 429,496 pages (~1.75 GB), multiplying the active page count by 10000 inside mult_frac() will overflow an unsigned long on 32-bit systems before the division by total. This could result in artificially low active/inactive memory ratios and break DAMON's adaptive tuning.

Does this same overflow affect damon_feed_loop_next_input()?

That function returns last_input * score_cg / goal. When last_input is passed as 100,000,000 and score_cg can be up to 10,000, that multiplication would similarly wrap around 32-bit boundaries.

Quoted 2 lines
mm/damon/core.c +0 -0
 	return mult_frac(inactive, 10000, total);

}

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

Re: [RFC PATCH 9/9] mm/damon/core: handle extreme memory state in get_in_active_mem_bp()

Quoted 25 lines · On Sat, 01 Aug 2026 19:39:48 +0000 [email protected] wrote:

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

New issues:
- [Medium] The commit message incorrectly identifies the function being modified, failing to align its claims with the code changes.

Pre-existing issues:
- [High] Multiplication overflow in mult_frac() and other basis point scaling calculations on 32-bit architectures leads to incorrect memory ratio calculations and scaling. --

--- Patch [9]: [RFC PATCH 9/9] mm/damon/core: handle extreme memory state in get_in_active_mem_bp() --- commit fee9e9f299e48399ca370c1f9725936302eaeb8c
Author: SJ Park <[email protected]>
Subject: mm/damon/core: handle extreme memory state in get_in_active_mem_bp()

This patch prevents a divide-by-zero risk in DAMON's active memory ratio calculations. In extreme environments where active and inactive memory both amount to zero, the patch clamps the total memory denominator to a minimum of 1 to avoid a crash.

Quoted 3 lines

damon_get_intervals_adaptation_bp() uses the sum of the active and inactive memory amount as a denominator. In an extreme and unlikely environment, active and inactive memory might be zero.

Quoted 6 lines

[Severity: Medium]
Is the function name referenced in the commit message correct?

The commit message explicitly states that damon_get_intervals_adaptation_bp() is being modified to fix a divide-by-zero bug, but the patch actually modifies damos_get_in_active_mem_bp().

Good catch, I will fix.

Quoted 24 lines

mm/damon/core.c static unsigned int damos_get_in_active_mem_bp(bool active_ratio) · line 3015

Quoted 13 lines
mm/damon/core.c +1 -1
index 76764a2056f7c..4944cf2c5afae 100644
@@ -3010,7 +3010,7 @@ static unsigned int damos_get_in_active_mem_bp(bool active_ratio)
 		global_node_page_state(NR_LRU_BASE + LRU_ACTIVE_FILE);
 	inactive = global_node_page_state(NR_LRU_BASE + LRU_INACTIVE_ANON) +
 		global_node_page_state(NR_LRU_BASE + LRU_INACTIVE_FILE);
-	total = active + inactive;
+	total = max(active + inactive, 1);
 	if (active_ratio)
 		return mult_frac(active, 10000, total);

[Severity: High]
This is a pre-existing issue, but does this calculation overflow on 32-bit architectures?

In damos_get_in_active_mem_bp(), if active memory exceeds approximately 429,496 pages (~1.75 GB), multiplying the active page count by 10000 inside mult_frac() will overflow an unsigned long on 32-bit systems before the division by total. This could result in artificially low active/inactive memory ratios and break DAMON's adaptive tuning.

Quoted 5 lines

Does this same overflow affect damon_feed_loop_next_input()?

That function returns last_input * score_cg / goal. When last_input is passed as 100,000,000 and score_cg can be up to 10,000, that multiplication would similarly wrap around 32-bit boundaries.

Known low priority issue.

Quoted 6 lines
Quoted 3 lines
mm/damon/core.c +0 -0
 	return mult_frac(inactive, 10000, total);

}

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

Thanks,
SJ