mm/damon: fix DAMOS bugs in core, paddr and vaddr
Subsystems (3)
SJ Park
maintainercover 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.
Changes from RFC
- RFC: https://lore.kernel.org/[email protected]
- Use cached pte content (patch 5 and 6).
- Handle totalram < freeram case (patch 7).
- Fix wrong function name in commit message (patch 9).
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 | 37 +++++++++++++++++++++++++++++++------ mm/damon/paddr.c | 8 ++++---- mm/damon/vaddr.c | 10 ++++++++-- 3 files changed, 43 insertions(+), 12 deletions(-)
base-commit: 481be0db7cfd092f0aea64e65ab0a9cacff88453
Signature · 1 line
2.47.3
SJ Park
maintainerRFC 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]
--- mm/damon/core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
mm/damon/core.c +2 -1
@@ -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
SJ Park
maintainerRFC PATCH 2/9 mm/damon/core: initialize damos_quota_goal->last_psi_total
In reply to SJ ParkWhen 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]
--- mm/damon/core.c | 12 ++++++++++++ 1 file changed, 12 insertions(+)
mm/damon/core.c +12 -0
@@ -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
[email protected]
AI reviewRe: [RFC PATCH v1.1 2/9] mm/damon/core: initialize damos_quota_goal->last_psi_total
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:
- [Medium] The patch incompletely fixes the uninitialized last_psi_total variable usage. While it initializes last_psi_total when the kdamond thread starts, it fails to initialize it for schemes or quota goals added dynamically at runtime via sysfs. --
--- Patch [2]: [RFC PATCH v1.1 2/9] mm/damon/core: initialize damos_quota_goal->last_psi_total --- commit ca3c4694fe111f676d0de56148cf986c02532428
Author: SJ Park <[email protected]>
mm/damon/core: initialize damos_quota_goal->last_psi_total
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. This commit initializes the value at the beginning of kdamond.
Quoted 4 lines
mm/damon/core.c +0
-0
[ ... ]
mm/damon/core.c static void kdamond_init_ctx(struct damon_ctx *ctx) · line 3758
Quoted 5 lines
mm/damon/core.c +1
-0@@ -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);
[Severity: Medium]
This is a pre-existing issue, but does this completely fix the uninitialized variable usage?
While this initializes last_psi_total when the kdamond thread starts, it appears goals added dynamically at runtime via sysfs will still be uninitialized.
When a goal is parsed from sysfs, memory is allocated without zeroing the structure, leaving last_psi_total filled with slab garbage:
mm/damon/sysfs-schemes.c:damos_sysfs_add_quota_score() {
...
goal = damos_new_quota_goal(sysfs_goal->metric,
sysfs_goal->target_value);
...
}During the context commit phase, the goal is dynamically allocated but last_psi_total is intentionally bypassed:
mm/damon/core.c:damos_commit_quota_goals() {
...
new_goal = damos_new_quota_goal(
src_goal->metric, src_goal->target_value);
...
}When the feedback loop executes, this uninitialized memory is consumed:
mm/damon/core.c:damos_set_quota_goal_current_value() {
...
goal->current_value = now_psi_total - goal->last_psi_total;
...
}Will this cause a random, extremely large or small initial quota adjustment when a user adds a new DAMON scheme or quota goal with the DAMOS_QUOTA_SOME_MEM_PSI_US metric to a running context via sysfs?
Quoted 2 lines
mm/damon/core.c +0
-0} }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
SJ Park
maintainerRe: [RFC PATCH v1.1 2/9] mm/damon/core: initialize damos_quota_goal->last_psi_total
Quoted 68 lines · On Sun, 02 Aug 2026 16:43:14 +0000 [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [Medium] The patch incompletely fixes the uninitializedlast_psi_totalvariable usage. While it initializeslast_psi_totalwhen thekdamondthread starts, it fails to initialize it for schemes or quota goals added dynamically at runtime via sysfs. ----- Patch [2]: [RFC PATCH v1.1 2/9]
mm/damon/core: initializedamos_quota_goal->last_psi_total--- commitca3c4694fe111f676d0de56148cf986c02532428
Author: SJ Park <[email protected]>
mm/damon/core: initializedamos_quota_goal->last_psi_totalWhen
DAMOS_QUOTA_SOME_MEM_PSI_USmetric damos quota goal is set, the PSI delta for the feedback loop is calculated usingdamos_quota_goal->last_psi_total. It is not initialized at the beginning. This commit initializes the value at the beginning of kdamond.Quoted 4 lines
mm/damon/core.c +0
-0[ ... ]
mm/damon/core.c static void kdamond_init_ctx(struct damon_ctx *ctx) · line 3758
Quoted 5 lines
mm/damon/core.c +1
-0@@ -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);[Severity: Medium]
This is a pre-existing issue, but does this completely fix the uninitialized variable usage?While this initializes
last_psi_totalwhen the kdamond thread starts, it appears goals added dynamically at runtime via sysfs will still be uninitialized.When a goal is parsed from sysfs, memory is allocated without zeroing the structure, leaving
last_psi_totalfilled with slab garbage:mm/damon/sysfs-schemes.c:damos_sysfs_add_quota_score() { ... goal = damos_new_quota_goal(sysfs_goal->metric, sysfs_goal->target_value); ... }During the context commit phase, the goal is dynamically allocated but
last_psi_totalis intentionally bypassed:mm/damon/core.c:damos_commit_quota_goals() { ... new_goal = damos_new_quota_goal( src_goal->metric, src_goal->target_value); ... }When the feedback loop executes, this uninitialized memory is consumed:
mm/damon/core.c:damos_set_quota_goal_current_value() { ... goal->current_value = now_psi_total - goal->last_psi_total; ... }
Quoted 3 lines
Will this cause a random, extremely large or small initial quota adjustment when a user adds a new DAMON scheme or quota goal with the
DAMOS_QUOTA_SOME_MEM_PSI_USmetric to a running context via sysfs?
Good catch. To handle this, the fix will be little bit complicated. It may not deserve stable@ backport. I will drop this from the next version of this series and work on more complete fix.
Quoted 7 lines
Quoted 3 lines
mm/damon/core.c +0
-0} }Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
Thanks,
SJ
SJ Park
maintainerRFC PATCH 3/9 mm/damon/paddr: respect folio end for DAMOS_STAT
In reply to SJ ParkThe 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.
--- mm/damon/paddr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
mm/damon/paddr.c +1 -1
@@ -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
[email protected]
AI reviewRe: [RFC PATCH v1.1 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] This problem wasn't introduced by this patch, but the patch provides an incomplete fix for an address advancement logic bug. While it corrects the addr += folio_size(folio) issue in damon_pa_stat() to correctly align the next address to the end of the large folio, it fails to apply the exact same fix to damon_pa_pageout(), damon_pa_de_activate(), and damon_pa_migrate(). --
--- Patch [3]: [RFC PATCH v1.1 3/9] mm/damon/paddr: respect folio end for DAMOS_STAT --- commit 99e0c032eda9c903df8d327aa77b989129d9b193
Author: SJ Park <[email protected]>
mm/damon/paddr: respect folio end for DAMOS_STAT
This commit fixes the address advancement in damon_pa_stat(). When a region starts in the middle of a large folio, it now properly aligns to the start of the next folio instead of simply adding the folio size and overshooting.
mm/damon/paddr.c static unsigned long damon_pa_stat(struct damon_region *r, · line 382
Quoted 9 lines
mm/damon/paddr.c +1
-1@@ -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 isn't a bug introduced by this patch, but does the same address advancement logic regression still exist in other DAMON actions?
While you fixed the overshoot behavior here in damon_pa_stat(), it looks like damon_pa_pageout(), damon_pa_de_activate(), and damon_pa_migrate() in mm/damon/paddr.c might still be using the flawed logic.
For example, in damon_pa_pageout():
put_folio: addr += folio_size(folio); folio_put(folio);
Could this cause DAMON actions to silently skip sections of valid memory if a region starts in the middle of a large folio?
Quoted 3 lines
mm/damon/paddr.c +0
-0folio_put (folio); } s->last_applied = folio;
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
SJ Park
maintainerRe: [RFC PATCH v1.1 3/9] mm/damon/paddr: respect folio end for DAMOS_STAT
Quoted 34 lines · On Sun, 02 Aug 2026 16:34:57 +0000 [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but the patch provides an incomplete fix for an address advancement logic bug. While it corrects theaddr += folio_size(folio)issue indamon_pa_stat()to correctly align the next address to the end of the large folio, it fails to apply the exact same fix todamon_pa_pageout(),damon_pa_de_activate(), anddamon_pa_migrate(). ----- Patch [3]: [RFC PATCH v1.1 3/9]
mm/damon/paddr: respect folio end forDAMOS_STAT--- commit99e0c032eda9c903df8d327aa77b989129d9b193
Author: SJ Park <[email protected]>
mm/damon/paddr: respect folio end forDAMOS_STATThis commit fixes the address advancement in
damon_pa_stat(). When a region starts in the middle of a large folio, it now properly aligns to the start of the next folio instead of simply adding the folio size and overshooting.mm/damon/paddr.c static unsigned long damon_pa_stat(struct damon_region *r, · line 382
Quoted 9 lines
mm/damon/paddr.c +1
-1@@ -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 isn't a bug introduced by this patch, but does the same address advancement logic regression still exist in other DAMON actions?While you fixed the overshoot behavior here in
damon_pa_stat(), it looks likedamon_pa_pageout(),damon_pa_de_activate(), anddamon_pa_migrate()inmm/damon/paddr.cmight still be using the flawed logic.
Quoted 8 lines
For example, in
damon_pa_pageout():put_folio: addr += folio_size(folio); folio_put(folio);Could this cause DAMON actions to silently skip sections of valid memory if a region starts in the middle of a large folio?
A later patch of this series fix that.
Quoted 8 lines
Quoted 4 lines
mm/damon/paddr.c +0
-0folio_put (folio); } s->last_applied = folio;Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
Thanks,
SJ
SJ Park
maintainerRFC PATCH 4/9 mm/damon/paddr: respect folio end for DAMOS actions except STAT
In reply to SJ ParkA 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]
--- mm/damon/paddr.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
mm/damon/paddr.c +3 -3
@@ -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
SJ Park
maintainerRFC PATCH 5/9 mm/damon/vaddr: respect folio end for DAMOS_STAT
In reply to SJ ParkFor 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]
--- mm/damon/vaddr.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
mm/damon/vaddr.c +4 -1
@@ -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 (ptent)); + nr =folio_nr_pages (folio) - page_idx; s->last_applied = folio; }pte_unmap_unlock (start_pte, ptl);
Signature · 1 line
2.47.3
[email protected]
AI reviewRe: [RFC PATCH v1.1 5/9] mm/damon/vaddr: 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] Unsafe blind PTE batching skips intermediate mappings and misses per-PTE Accessed/Young bits for large folios. --
--- Patch [5]: [RFC PATCH v1.1 5/9] mm/damon/vaddr: respect folio end for DAMOS_STAT --- commit d6ffeea9e85e0fc3aa2c2575b8113904f9da4123
Author: SJ Park <[email protected]>
mm/damon/vaddr: respect folio end for DAMOS_STAT
This commit fixes an issue in DAMOS_STAT page-level monitoring where the address advancement could incorrectly skip folios if a region starts in the middle of a large folio. It ensures the address is advanced exactly to the start of the next folio by calculating the correct page index.
[1] https://lore.kernel.org/[email protected]
Quoted 4 lines
mm/damon/vaddr.c +0
-0
[ ... ]
mm/damon/vaddr.c static int damos_va_stat_pmd_entry(pmd_t *pmd, unsigned long addr, · line 850
Quoted 7 lines
mm/damon/vaddr.c +2
-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 (ptent)); + nr =folio_nr_pages (folio) - page_idx;
[Severity: High]
This is a pre-existing issue, but does this blind PTE batching skip intermediate mappings and miss per-PTE Accessed/Young bits for large folios?
When damos_va_filter_out() tests the young filter, it only checks the accessed bit of the first pte. By unconditionally advancing pte += nr without aggregating the accessed bits of the remaining subpages, could this miss hardware accesses on PTE-mapped large folios (like mTHP) and cause DAMON to falsely classify actively used memory as cold?
Additionally, if userspace partially unmaps or remaps portions of this large folio (for example, via MADV_DONTNEED or mmap), the page table might contain different folios or empty entries in the skipped range. By blindly advancing the PTE walker based purely on the first folio's size, does this ignore those mixed mappings and fail to monitor the substituted folios?
Quoted 3 lines
mm/damon/vaddr.c +0
-0s->last_applied = folio; }pte_unmap_unlock (start_pte, ptl);
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
SJ Park
maintainerRe: [RFC PATCH v1.1 5/9] mm/damon/vaddr: respect folio end for DAMOS_STAT
Quoted 42 lines · On Sun, 02 Aug 2026 16:36:30 +0000 [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Unsafe blind PTE batching skips intermediate mappings and misses per-PTE Accessed/Young bits for large folios. ----- Patch [5]: [RFC PATCH v1.1 5/9]
mm/damon/vaddr: respect folio end forDAMOS_STAT--- commitd6ffeea9e85e0fc3aa2c2575b8113904f9da4123
Author: SJ Park <[email protected]>
mm/damon/vaddr: respect folio end forDAMOS_STATThis commit fixes an issue in
DAMOS_STATpage-level monitoring where the address advancement could incorrectly skip folios if a region starts in the middle of a large folio. It ensures the address is advanced exactly to the start of the next folio by calculating the correct page index.[1] https://lore.kernel.org/[email protected]
Quoted 4 lines
mm/damon/vaddr.c +0
-0[ ... ]
mm/damon/vaddr.c static int damos_va_stat_pmd_entry(pmd_t *pmd, unsigned long addr, · line 850
Quoted 7 lines
mm/damon/vaddr.c +2
-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 (ptent)); + nr =folio_nr_pages (folio) - page_idx;[Severity: High]
This is a pre-existing issue, but does this blind PTE batching skip intermediate mappings and miss per-PTE Accessed/Young bits for large folios?When
damos_va_filter_out()tests the young filter, it only checks the accessed bit of the first pte. By unconditionally advancing pte += nr without aggregating the accessed bits of the remaining subpages, could this miss hardware accesses on PTE-mapped large folios (like mTHP) and cause DAMON to falsely classify actively used memory as cold?
Quoted 5 lines
Additionally, if userspace partially unmaps or remaps portions of this large folio (for example, via
MADV_DONTNEEDor mmap), the page table might contain different folios or empty entries in the skipped range. By blindly advancing the PTE walker based purely on the first folio's size, does this ignore those mixed mappings and fail to monitor the substituted folios?
Handling all the cases would be too complicated for backporting. I will revisit this later.
Quoted 8 lines
Quoted 4 lines
mm/damon/vaddr.c +0
-0s->last_applied = folio; }pte_unmap_unlock (start_pte, ptl);Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
Thanks,
SJ
SJ Park
maintainerRFC PATCH 6/9 mm/damon/vaddr: respect folio end for DAMOS_MIGRATE_{HOT,COLD}
In reply to SJ ParkFor 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]
--- mm/damon/vaddr.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
mm/damon/vaddr.c +4 -1
@@ -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 (ptent)); + nr =folio_nr_pages (folio) - page_idx; }pte_unmap_unlock (start_pte, ptl);return 0 ;
Signature · 1 line
2.47.3
SJ Park
maintainerRFC PATCH 7/9 mm/damon/core: handle extreme memory state in damon_get_node_mem_bp()
In reply to SJ ParkIn 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]
--- mm/damon/core.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-)
mm/damon/core.c +8 -2
@@ -2816,10 +2816,16 @@ static __kernel_ulong_t damos_get_node_mem_bp( }si_meminfo_node (&i, goal->nid); -if (goal->metric ==DAMOS_QUOTA_NODE_MEM_USED_BP ) +if (!i.totalram) +return 10000 ; +if (goal->metric ==DAMOS_QUOTA_NODE_MEM_USED_BP ) { numerator = i.totalram - i.freeram; -else /* DAMOS_QUOTA_NODE_MEM_FREE_BP */ + }else { +/* DAMOS_QUOTA_NODE_MEM_FREE_BP */ +if (i.totalram < i.freeram) +return 0 ; numerator = i.freeram; + }return mult_frac (numerator,10000 , i.totalram); }
Signature · 1 line
2.47.3
[email protected]
AI reviewRe: [RFC PATCH v1.1 7/9] mm/damon/core: handle extreme memory state in damon_get_node_mem_bp()
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] Returning 0 for DAMOS_QUOTA_NODE_MEM_FREE_BP when freeram > totalram incorrectly forces maximum scheme aggressiveness instead of reporting 100% free memory.
- [Medium] The commit message contradicts the implementation, and the unconditional return of 10000 for zero totalram breaks metric semantics.
Pre-existing issues:
- [High] Unhandled integer underflow in MEM_USED_BP and MEMCG_FREE_BP calculations when counters drift.
- [High] The patch fixes a potential divide-by-zero in damos_get_node_mem_bp() but misses identical unvalidated divisions in related functions. --
--- Patch [7]: [RFC PATCH v1.1 7/9] mm/damon/core: handle extreme memory state in damon_get_node_mem_bp() --- commit 05d27d4d51952669ff23ec9285ccb83a273556ad
Author: SJ Park <[email protected]>
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.
Quoted 4 lines
In an extreme and unlikely situation,
si_meminfo_node()might let the caller show zero total ram. That could cause a divide by zero indamon_get_node_mem_bp(). Fix it by setting the totalram one byte in the case.
[Severity: Medium]
The commit message states "Fix it by setting the totalram one byte in the case."
However, the implementation actually bypasses the metric type check and returns 10000.
Does this unconditional return contradict the intent documented in the commit message?
mm/damon/core.c static __kernel_ulong_t damos_get_node_mem_bp( · line 2820
Quoted 11 lines
mm/damon/core.c +2
-1@@ -2816,10 +2816,16 @@ static __kernel_ulong_t damos_get_node_mem_bp( }si_meminfo_node (&i, goal->nid); -if (goal->metric ==DAMOS_QUOTA_NODE_MEM_USED_BP ) +if (!i.totalram) +return 10000 ;
[Severity: Medium]
If the metric is DAMOS_QUOTA_NODE_MEM_FREE_BP, shouldn't it report 0% free rather than 100% free if the memory is absent (which is what totalram=1, freeram=0 would yield)?
Does returning 10000 here break the semantic constraint that used + free = 100%?
mm/damon/core.c static __kernel_ulong_t damos_get_node_mem_bp( · line 2822
Quoted 2 lines
mm/damon/core.c +1
-0+if (goal->metric ==DAMOS_QUOTA_NODE_MEM_USED_BP ) { numerator = i.totalram - i.freeram;
[Severity: High]
This is a pre-existing issue, but if counter drift results in freeram > totalram, does this subtraction silently underflow, producing a massive unsigned value?
If this is passed to mult_frac(), could it generate a huge metric value, leading the auto-tuner to mistakenly act as if the memory is massively overused?
An identical unhandled underflow also appears to exist in damos_get_node_memcg_used_bp() when evaluating DAMOS_QUOTA_NODE_MEMCG_FREE_BP if used_pages exceeds totalram:
mm/damon/core.c:damos_get_node_memcg_used_bp() {
...
numerator = i.totalram - used_pages;
...
}mm/damon/core.c static __kernel_ulong_t damos_get_node_mem_bp( · line 2826
Quoted 5 lines
mm/damon/core.c +4
-1- else/* DAMOS_QUOTA_NODE_MEM_FREE_BP */ + }else { +/* DAMOS_QUOTA_NODE_MEM_FREE_BP */ +if (i.totalram < i.freeram) +return 0 ;
[Severity: High]
When counter drift causes freeram > totalram, the node effectively has ~100% free memory.
By returning 0 (0% free memory) in this scenario, doesn't the DAMON quota auto-tuning feedback loop interpret this as severely under-achieving the free memory goal, causing it to wastefully and drastically increase the reclamation budget (e.g., pageout/demotion) on a node that is already completely free?
Should this return 10000 (100% free) instead?
mm/damon/core.c static __kernel_ulong_t damos_get_node_mem_bp( · line 2830
Quoted 4 lines
mm/damon/core.c +1
-0numerator = i.freeram; + }return mult_frac (numerator,10000 , i.totalram); }
[Severity: High]
This is a pre-existing issue, but while this patch addresses a potential divide-by-zero when si_meminfo_node() reports totalram == 0, does the same unvalidated division exist in related functions?
For example, damos_get_node_memcg_used_bp() divides by i.totalram without a zero-check:
mm/damon/core.c:damos_get_node_memcg_used_bp() {
...
si_meminfo_node(&i, goal->nid);
...
return mult_frac(numerator, 10000, i.totalram);
}Similarly, damos_get_in_active_mem_bp() divides by total (active + inactive) via mult_frac() without checking if total is zero:
mm/damon/core.c:damos_get_in_active_mem_bp() {
...
if (goal->metric == DAMOS_QUOTA_SOME_MEM_USED_BP)
return mult_frac(active, 10000, total);
return mult_frac(inactive, 10000, total);
}Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7
SJ Park
maintainerRe: [RFC PATCH v1.1 7/9] mm/damon/core: handle extreme memory state in damon_get_node_mem_bp()
Quoted 33 lines · On Sun, 02 Aug 2026 16:35:44 +0000 [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] Returning 0 forDAMOS_QUOTA_NODE_MEM_FREE_BPwhen freeram > totalram incorrectly forces maximum scheme aggressiveness instead of reporting 100% free memory.
- [Medium] The commit message contradicts the implementation, and the unconditional return of 10000 for zero totalram breaks metric semantics.Pre-existing issues:
- [High] Unhandled integer underflow inMEM_USED_BPandMEMCG_FREE_BPcalculations when counters drift.
- [High] The patch fixes a potential divide-by-zero indamos_get_node_mem_bp()but misses identical unvalidated divisions in related functions. ----- Patch [7]: [RFC PATCH v1.1 7/9]
mm/damon/core: handle extreme memory state indamon_get_node_mem_bp()--- commit05d27d4d51952669ff23ec9285ccb83a273556ad
Author: SJ Park <[email protected]>
mm/damon/core: handle extreme memory state indamon_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 indamon_get_node_mem_bp(). Fix it by setting the totalram one byte in the case.Quoted 4 lines
In an extreme and unlikely situation,
si_meminfo_node()might let the caller show zero total ram. That could cause a divide by zero indamon_get_node_mem_bp(). Fix it by setting the totalram one byte in the case.[Severity: Medium]
The commit message states "Fix it by setting the totalram one byte in the case."
Quoted 5 lines
However, the implementation actually bypasses the metric type check and returns 10000.
Does this unconditional return contradict the intent documented in the commit message?
Good catch, I will update the commit message.
Quoted 18 lines
mm/damon/core.c static __kernel_ulong_t damos_get_node_mem_bp( · line 2820
Quoted 12 lines
mm/damon/core.c +2
-1@@ -2816,10 +2816,16 @@ static __kernel_ulong_t damos_get_node_mem_bp( }si_meminfo_node (&i, goal->nid); -if (goal->metric =DAMOS_QUOTA_NODE_MEM_USED_BP ) +if (!i.totalram) +return 10000 ;[Severity: Medium]
If the metric isDAMOS_QUOTA_NODE_MEM_FREE_BP, shouldn't it report 0% free rather than 100% free if the memory is absent (which is what totalram=1, freeram=0 would yield)?
Quoted 1 line
Does returning 10000 here break the semantic constraint that used + free = 100%?
I made mistakes at revisioning. I will fix this.
Quoted 17 lines
mm/damon/core.c static __kernel_ulong_t damos_get_node_mem_bp( · line 2822
Quoted 3 lines
mm/damon/core.c +1
-0+if (goal->metric =DAMOS_QUOTA_NODE_MEM_USED_BP ) { numerator = i.totalram - i.freeram;[Severity: High]
This is a pre-existing issue, but if counter drift results in freeram > totalram, does this subtraction silently underflow, producing a massive unsigned value?If this is passed to
mult_frac(), could it generate a huge metric value, leading the auto-tuner to mistakenly act as if the memory is massively overused?An identical unhandled underflow also appears to exist in
damos_get_node_memcg_used_bp()when evaluatingDAMOS_QUOTA_NODE_MEMCG_FREE_BPifused_pagesexceeds totalram:
Quoted 5 lines
mm/damon/core.c:damos_get_node_memcg_used_bp() {
...
numerator = i.totalram - used_pages;
...
}The intention of this revision was handling this, but I made a complete mistake. I will fix.
Quoted 11 lines
mm/damon/core.c static __kernel_ulong_t damos_get_node_mem_bp( · line 2826
Quoted 6 lines
mm/damon/core.c +4
-1- else/* DAMOS_QUOTA_NODE_MEM_FREE_BP */ + }else { +/* DAMOS_QUOTA_NODE_MEM_FREE_BP */ +if (i.totalram < i.freeram) +return 0 ;[Severity: High]
When counter drift causes freeram > totalram, the node effectively has ~100% free memory.
Quoted 6 lines
By returning 0 (0% free memory) in this scenario, doesn't the DAMON quota auto-tuning feedback loop interpret this as severely under-achieving the free memory goal, causing it to wastefully and drastically increase the reclamation budget (e.g., pageout/demotion) on a node that is already completely free?
Should this return 10000 (100% free) instead?
That depends on the perspective. I think 0% makes sense.
Quoted 24 lines
mm/damon/core.c static __kernel_ulong_t damos_get_node_mem_bp( · line 2830
Quoted 5 lines
mm/damon/core.c +1
-0numerator = i.freeram; + }return mult_frac (numerator,10000 , i.totalram); }[Severity: High]
This is a pre-existing issue, but while this patch addresses a potential divide-by-zero whensi_meminfo_node()reports totalram = 0, does the same unvalidated division exist in related functions?For example,
damos_get_node_memcg_used_bp()divides by i.totalram without a zero-check:mm/damon/core.c:damos_get_node_memcg_used_bp() { ... si_meminfo_node(&i, goal->nid); ... return mult_frac(numerator, 10000, i.totalram); }Similarly,
damos_get_in_active_mem_bp()divides by total (active + inactive) viamult_frac()without checking if total is zero:
Quoted 6 lines
mm/damon/core.c:damos_get_in_active_mem_bp() {
...
if (goal->metric = DAMOS_QUOTA_SOME_MEM_USED_BP)
return mult_frac(active, 10000, total);
return mult_frac(inactive, 10000, total);
}Later patches in this series fix those.
Quoted 4 lines
Signature · 2 lines
-- Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7
Thanks,
SJ
SJ Park
maintainerRe: [RFC PATCH v1.1 7/9] mm/damon/core: handle extreme memory state in damon_get_node_mem_bp()
Quoted 61 lines · On Sun, 2 Aug 2026 10:49:50 -0700 SJ Park <[email protected]> wrote:
Quoted 33 lines · On Sun, 02 Aug 2026 16:35:44 +0000 [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] Returning 0 forDAMOS_QUOTA_NODE_MEM_FREE_BPwhen freeram > totalram incorrectly forces maximum scheme aggressiveness instead of reporting 100% free memory.
- [Medium] The commit message contradicts the implementation, and the unconditional return of 10000 for zero totalram breaks metric semantics.Pre-existing issues:
- [High] Unhandled integer underflow inMEM_USED_BPandMEMCG_FREE_BPcalculations when counters drift.
- [High] The patch fixes a potential divide-by-zero indamos_get_node_mem_bp()but misses identical unvalidated divisions in related functions. ----- Patch [7]: [RFC PATCH v1.1 7/9]
mm/damon/core: handle extreme memory state indamon_get_node_mem_bp()--- commit05d27d4d51952669ff23ec9285ccb83a273556ad
Author: SJ Park <[email protected]>
mm/damon/core: handle extreme memory state indamon_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 indamon_get_node_mem_bp(). Fix it by setting the totalram one byte in the case.Quoted 4 lines
In an extreme and unlikely situation,
si_meminfo_node()might let the caller show zero total ram. That could cause a divide by zero indamon_get_node_mem_bp(). Fix it by setting the totalram one byte in the case.[Severity: Medium]
The commit message states "Fix it by setting the totalram one byte in the case."Quoted 5 lines
However, the implementation actually bypasses the metric type check and returns 10000.
Does this unconditional return contradict the intent documented in the commit message?
Good catch, I will update the commit message.
Quoted 18 lines
mm/damon/core.c static __kernel_ulong_t damos_get_node_mem_bp( · line 2820
Quoted 12 lines
mm/damon/core.c +2
-1@@ -2816,10 +2816,16 @@ static __kernel_ulong_t damos_get_node_mem_bp( }si_meminfo_node (&i, goal->nid); -if (goal->metric =DAMOS_QUOTA_NODE_MEM_USED_BP ) +if (!i.totalram) +return 10000 ;[Severity: Medium]
If the metric isDAMOS_QUOTA_NODE_MEM_FREE_BP, shouldn't it report 0% free rather than 100% free if the memory is absent (which is what totalram=1, freeram=0 would yield)?
Quoted 3 lines
Quoted 1 line
Does returning 10000 here break the semantic constraint that used + free = 100%?
I made mistakes at revisioning. I will fix this.
And this semantic gap exist in the next patch, too. I will fix it together.
Quoted 81 lines
Quoted 18 lines
mm/damon/core.c static __kernel_ulong_t damos_get_node_mem_bp( · line 2822
Quoted 4 lines
mm/damon/core.c +1
-0+if (goal->metric =DAMOS_QUOTA_NODE_MEM_USED_BP ) { numerator = i.totalram - i.freeram;[Severity: High]
This is a pre-existing issue, but if counter drift results in freeram > totalram, does this subtraction silently underflow, producing a massive unsigned value?If this is passed to
mult_frac(), could it generate a huge metric value, leading the auto-tuner to mistakenly act as if the memory is massively overused?An identical unhandled underflow also appears to exist in
damos_get_node_memcg_used_bp()when evaluatingDAMOS_QUOTA_NODE_MEMCG_FREE_BPifused_pagesexceeds totalram:Quoted 5 lines
mm/damon/core.c:damos_get_node_memcg_used_bp() { ... numerator = i.totalram - used_pages; ... }The intention of this revision was handling this, but I made a complete mistake. I will fix.
Quoted 11 lines
mm/damon/core.c static __kernel_ulong_t damos_get_node_mem_bp( · line 2826
Quoted 6 lines
mm/damon/core.c +4
-1- else/* DAMOS_QUOTA_NODE_MEM_FREE_BP */ + }else { +/* DAMOS_QUOTA_NODE_MEM_FREE_BP */ +if (i.totalram < i.freeram) +return 0 ;[Severity: High]
When counter drift causes freeram > totalram, the node effectively has ~100% free memory.Quoted 6 lines
By returning 0 (0% free memory) in this scenario, doesn't the DAMON quota auto-tuning feedback loop interpret this as severely under-achieving the free memory goal, causing it to wastefully and drastically increase the reclamation budget (e.g., pageout/demotion) on a node that is already completely free?
Should this return 10000 (100% free) instead?
That depends on the perspective. I think 0% makes sense.
Quoted 24 lines
mm/damon/core.c static __kernel_ulong_t damos_get_node_mem_bp( · line 2830
Quoted 5 lines
mm/damon/core.c +1
-0numerator = i.freeram; + }return mult_frac (numerator,10000 , i.totalram); }[Severity: High]
This is a pre-existing issue, but while this patch addresses a potential divide-by-zero whensi_meminfo_node()reports totalram = 0, does the same unvalidated division exist in related functions?For example,
damos_get_node_memcg_used_bp()divides by i.totalram without a zero-check:mm/damon/core.c:damos_get_node_memcg_used_bp() { ... si_meminfo_node(&i, goal->nid); ... return mult_frac(numerator, 10000, i.totalram); }Similarly,
damos_get_in_active_mem_bp()divides by total (active + inactive) viamult_frac()without checking if total is zero:Quoted 6 lines
mm/damon/core.c:damos_get_in_active_mem_bp() { ... if (goal->metric = DAMOS_QUOTA_SOME_MEM_USED_BP) return mult_frac(active, 10000, total); return mult_frac(inactive, 10000, total); }Later patches in this series fix those.
Quoted 1 line
Quoted 8 lines
Quoted 3 lines
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7Thanks,
SJ
Thanks,
SJ
SJ Park
maintainerRFC PATCH 8/9 mm/damon/core: handle extreme memory state in get_node_memcg_used_bp()
In reply to SJ ParkIn 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]
--- mm/damon/core.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-)
mm/damon/core.c +8 -2
@@ -2862,10 +2862,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
[email protected]
AI reviewRe: [RFC PATCH v1.1 8/9] mm/damon/core: handle extreme memory state in get_node_memcg_used_bp()
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but damos_get_node_mem_bp() contains a severe logic bug and underflow when free memory exceeds total memory (i.totalram < i.freeram). --
--- Patch [8]: [RFC PATCH v1.1 8/9] mm/damon/core: handle extreme memory state in get_node_memcg_used_bp() --- commit 0fb31bc202c766c30f5d776d57b867135fba4aa6
Author: SJ Park <[email protected]>mm/damon/core: handle extreme memory state in get_node_memcg_used_bp()
This commit addresses division by zero and underflow issues in damos_get_node_memcg_used_bp() by handling cases where total memory is zero or used memory exceeds total memory. It returns 100% and 0% for these respective edge cases to prevent anomalous behavior.
Quoted 8 lines
mm/damon/core.c +0
-0@@ -2862,10 +2862,16 @@ static unsigned long damos_get_node_memcg_used_bp(mem_cgroup_put (memcg);si_meminfo_node (&i, goal->nid);
mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2872
Quoted 10 lines
mm/damon/core.c +7
-2-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 ;
[Severity: High]
This isn't a bug introduced by this patch, but does the adjacent damos_get_node_mem_bp() function contain a logic bug when free memory exceeds total memory?
If i.totalram < i.freeram, it appears that for DAMOS_QUOTA_NODE_MEM_USED_BP, i.totalram - i.freeram will unconditionally underflow, falsely throttling or activating schemes. For DAMOS_QUOTA_NODE_MEM_FREE_BP, returning 0 means it reports 0% free memory when the node is actually >100% free, which could cause schemes waiting for low free memory to trigger aggressively.
mm/damon/core.c:damos_get_node_mem_bp() {
...
if (goal->metric == DAMOS_QUOTA_NODE_MEM_USED_BP) {
numerator = i.totalram - i.freeram;
} else {
/* DAMOS_QUOTA_NODE_MEM_FREE_BP */
if (i.totalram < i.freeram)
return 0;
numerator = i.freeram;
}
...
}Should this pre-existing issue in damos_get_node_mem_bp() be addressed as well, perhaps by returning 0 for USED_BP and 10000 for FREE_BP when i.totalram < i.freeram?
Quoted 4 lines
mm/damon/core.c +1
-0numerator = i.totalram - used_pages; + }return mult_frac (numerator,10000 , i.totalram); }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=8
SJ Park
maintainerRe: [RFC PATCH v1.1 8/9] mm/damon/core: handle extreme memory state in get_node_memcg_used_bp()
Quoted 61 lines · On Sun, 02 Aug 2026 16:38:33 +0000 [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] This problem wasn't introduced by this patch, butdamos_get_node_mem_bp()contains a severe logic bug and underflow when free memory exceeds total memory (i.totalram < i.freeram). ----- Patch [8]: [RFC PATCH v1.1 8/9]
mm/damon/core: handle extreme memory state inget_node_memcg_used_bp()--- commit0fb31bc202c766c30f5d776d57b867135fba4aa6
Author: SJ Park <[email protected]>mm/damon/core: handle extreme memory state inget_node_memcg_used_bp()This commit addresses division by zero and underflow issues in
damos_get_node_memcg_used_bp()by handling cases where total memory is zero or used memory exceeds total memory. It returns 100% and 0% for these respective edge cases to prevent anomalous behavior.Quoted 8 lines
mm/damon/core.c +0
-0@@ -2862,10 +2862,16 @@ static unsigned long damos_get_node_memcg_used_bp(mem_cgroup_put (memcg);si_meminfo_node (&i, goal->nid);mm/damon/core.c static unsigned long damos_get_node_memcg_used_bp( · line 2872
Quoted 10 lines
mm/damon/core.c +7
-2-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 ;[Severity: High]
This isn't a bug introduced by this patch, but does the adjacentdamos_get_node_mem_bp()function contain a logic bug when free memory exceeds total memory?If i.totalram < i.freeram, it appears that for
DAMOS_QUOTA_NODE_MEM_USED_BP, i.totalram - i.freeram will unconditionally underflow, falsely throttling or activating schemes. ForDAMOS_QUOTA_NODE_MEM_FREE_BP, returning 0 means it reports 0% free memory when the node is actually >100% free, which could cause schemes waiting for low free memory to trigger aggressively.mm/damon/core.c:damos_get_node_mem_bp() { ... if (goal->metric = DAMOS_QUOTA_NODE_MEM_USED_BP) { numerator = i.totalram - i.freeram; } else { /* DAMOS_QUOTA_NODE_MEM_FREE_BP */ if (i.totalram < i.freeram) return 0; numerator = i.freeram; } ... }
Quoted 3 lines
Should this pre-existing issue in
damos_get_node_mem_bp()be addressed as well, perhaps by returning 0 forUSED_BPand 10000 forFREE_BPwhen i.totalram < i.freeram?
The previous patch was completely broken by my mistake. I will fix.
Quoted 9 lines
Quoted 5 lines
mm/damon/core.c +1
-0numerator = i.totalram - used_pages; + }return mult_frac (numerator,10000 , i.totalram); }Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=8
Thanks,
SJ
SJ Park
maintainerRFC PATCH 9/9 mm/damon/core: handle extreme memory state in get_in_active_mem_bp()
In reply to SJ Parkdamos_get_in_active_mem_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]
--- mm/damon/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
mm/damon/core.c +1 -1
@@ -3014,7 +3014,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
[email protected]
AI reviewRe: [RFC PATCH v1.1 9/9] mm/damon/core: handle extreme memory state in get_in_active_mem_bp()
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] 32-bit integer overflows and truncations in DAMON memory metric calculations cause wildly incorrect quota logic on 32-bit architectures. --
--- Patch [9]: [RFC PATCH v1.1 9/9] mm/damon/core: handle extreme memory state in get_in_active_mem_bp() --- commit e72445b8d3c8d0866c458b7707c7433d7912c2e6
Author: SJ Park <[email protected]>
mm/damon/core: handle extreme memory state in get_in_active_mem_bp()
This commit addresses a divide-by-zero issue in damos_get_in_active_mem_bp(). It ensures the denominator is at least 1 when calculating the ratio of active or inactive memory, preventing a crash if both are zero.
mm/damon/core.c static unsigned int damos_get_in_active_mem_bp(bool active_ratio) · line 3018
Quoted 11 lines
mm/damon/core.c +1
-1@@ -3014,7 +3014,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)
[Severity: High]
This is a pre-existing issue, but does this code overflow 32-bit unsigned math on 32-bit architectures?
The mult_frac(x, n, d) macro computes (x / d) * n + (x % d) * n / d. Since n is 10000, the multiplication x * 10000 is performed using 32-bit unsigned arithmetic on 32-bit systems (where unsigned long is 32 bits). If active memory exceeds 429,496 pages (~1.76 GB), the multiplication overflows ULONG_MAX, resulting in an incorrect ratio.
Does this same overflow also affect other DAMON memory metric calculations? For example, in damos_get_node_eligible_mem_bp():
return mult_frac((unsigned long)node_eligible, 10000,
(unsigned long)total_eligible);
This explicitly casts a 64-bit phys_addr_t to unsigned long, truncating capacities over 4GB on 32-bit systems. It also overflows mult_frac() if the eligible memory exceeds just ~419 KB.
Similarly, in damon_get_intervals_score():
max_access_events += sz_region * c->attrs.aggr_samples;
access_events += sz_region * r->nr_accesses;Because sz_region is in bytes, accumulating these into an unsigned long seems to overflow for regions larger than a few tens of megabytes.
Quoted 3 lines
mm/damon/core.c +0
-0return mult_frac (active,10000 , total);return mult_frac (inactive,10000 , total);}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=9
SJ Park
maintainerRe: [RFC PATCH v1.1 9/9] mm/damon/core: handle extreme memory state in get_in_active_mem_bp()
Quoted 50 lines · On Sun, 02 Aug 2026 16:51:05 +0000 [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] 32-bit integer overflows and truncations in DAMON memory metric calculations cause wildly incorrect quota logic on 32-bit architectures. ----- Patch [9]: [RFC PATCH v1.1 9/9]
mm/damon/core: handle extreme memory state inget_in_active_mem_bp()--- commite72445b8d3c8d0866c458b7707c7433d7912c2e6
Author: SJ Park <[email protected]>
mm/damon/core: handle extreme memory state inget_in_active_mem_bp()This commit addresses a divide-by-zero issue in
damos_get_in_active_mem_bp(). It ensures the denominator is at least 1 when calculating the ratio of active or inactive memory, preventing a crash if both are zero.mm/damon/core.c static unsigned int damos_get_in_active_mem_bp(bool active_ratio) · line 3018
Quoted 11 lines
mm/damon/core.c +1
-1@@ -3014,7 +3014,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)[Severity: High]
This is a pre-existing issue, but does this code overflow 32-bit unsigned math on 32-bit architectures?The
mult_frac(x, n, d)macro computes (x / d) * n + (x % d) * n / d. Since n is 10000, the multiplication x * 10000 is performed using 32-bit unsigned arithmetic on 32-bit systems (where unsigned long is 32 bits). If active memory exceeds 429,496 pages (~1.76 GB), the multiplication overflowsULONG_MAX, resulting in an incorrect ratio.Does this same overflow also affect other DAMON memory metric calculations? For example, in
damos_get_node_eligible_mem_bp():return mult_frac((unsigned long)node_eligible, 10000,
(unsigned long)total_eligible);This explicitly casts a 64-bit
phys_addr_tto unsigned long, truncating capacities over 4GB on 32-bit systems. It also overflowsmult_frac()if the eligible memory exceeds just ~419 KB.
Quoted 7 lines
Similarly, in
damon_get_intervals_score():max_access_events += sz_region * c->attrs.aggr_samples; access_events += sz_region * r->nr_accesses;Because
sz_regionis in bytes, accumulating these into an unsigned long seems to overflow for regions larger than a few tens of megabytes.
Known low priority issue.
Quoted 8 lines
Quoted 4 lines
mm/damon/core.c +0
-0return mult_frac (active,10000 , total);return mult_frac (inactive,10000 , total);}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=9
Thanks,
SJ