From: DaeMyung Kang <charsyam@gmail.com>
To: SJ Park <sj@kernel.org>
Cc: damon@lists.linux.dev, linux-mm@kvack.org,
Andrew Morton <akpm@linux-foundation.org>,
Asier Gutierrez <gutierrez.asier@huawei-partners.com>,
DaeMyung Kang <charsyam@gmail.com>
Subject: [RFC PATCH 3/3] mm/damon/core: avoid overflows in quota goal metrics and scores
Date: Tue, 27 Oct 2026 22:59:32 +0900 [thread overview]
Message-ID: <20261027135932.2301646-4-charsyam@gmail.com> (raw)
In-Reply-To: <20261027135932.2301646-1-charsyam@gmail.com>
The node memory, node memcg memory, LRU [in]active memory, and hugepage
memory quota goal metrics, as well as damos_quota_score(), use
mult_frac() to scale a value by 10000. mult_frac() multiplies the
remainder by 10000 in unsigned long, which overflows on 32-bit kernels
even when the result fits:
- For the page based metrics, this happens once the numerator exceeds
about 429,496 pages, or 1.6 GiB with 4 KiB pages. For example, 2 GiB
used out of 3 GiB returns 1205 bp instead of 6666 bp. The LRU
[in]active and hugepage metrics do not depend on NUMA.
- For quota goal scores, any current value above about 429,496 can
overflow. For example, a DAMOS_QUOTA_SOME_MEM_PSI_US goal with a
target of one second and a current value of half a second returns 705
instead of 5000. On any architecture, mult_frac() also silently wraps
scores that do not fit in unsigned long.
Use damon_mult_frac() for those calculations.
The node eligible memory metric counts bytes in phys_addr_t, so on
32-bit kernels it overflows once the eligible memory exceeds about
420 KiB, and it also truncates the byte counts to unsigned long with
PAE. Use mul_u64_u64_div_u64() on the phys_addr_t values for it. The
node eligible memory never exceeds the total, so the result fits.
Add a KUnit case for the quota goal scores. The helper's KUnit cases
also cover the large page ratio used by the metrics.
The issues were discovered [1,2,3] by Sashiko.
[1] https://lore.kernel.org/20260901133650.EF4F71F000E9@smtp.kernel.org
[2] https://lore.kernel.org/20260626002448.975E21F000E9@smtp.kernel.org
[3] https://lore.kernel.org/20260831184956.1DA321F000E9@smtp.kernel.org
Fixes: 0e1c773b501f ("mm/damon/core: introduce damos quota goal metrics for memory node utilization")
Fixes: b74a120bcf50 ("mm/damon/core: implement DAMOS_QUOTA_NODE_MEMCG_USED_BP")
Fixes: 4835e2871321 ("mm/damon/core: introduce [in]active memory ratio damos quota goal metric")
Fixes: f465dc71aba0 ("mm/damon: introduce DAMOS_QUOTA_HUGEPAGE auto tuning")
Fixes: 9138e27a3bc3 ("mm/damon: add node_eligible_mem_bp goal metric")
Fixes: 06ba5b309ed8 ("mm/damon/core: let goal specified with only target and current values")
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
---
mm/damon/core.c | 18 +++++++++---------
mm/damon/tests/core-kunit.h | 31 +++++++++++++++++++++++++++++++
2 files changed, 40 insertions(+), 9 deletions(-)
diff --git a/mm/damon/core.c b/mm/damon/core.c
index 5cd4007cc370..cc4e010e400d 100644
--- a/mm/damon/core.c
+++ b/mm/damon/core.c
@@ -3056,7 +3056,7 @@ static __kernel_ulong_t damos_get_node_mem_bp(
numerator = i.totalram - i.freeram;
else /* DAMOS_QUOTA_NODE_MEM_FREE_BP */
numerator = i.freeram;
- return mult_frac(numerator, 10000, i.totalram);
+ return damon_mult_frac(numerator, 10000, i.totalram);
}
static unsigned long damos_get_node_memcg_used_bp(
@@ -3102,7 +3102,7 @@ static unsigned long damos_get_node_memcg_used_bp(
numerator = used_pages;
else /* DAMOS_QUOTA_NODE_MEMCG_FREE_BP */
numerator = i.totalram - used_pages;
- return mult_frac(numerator, 10000, i.totalram);
+ return damon_mult_frac(numerator, 10000, i.totalram);
}
#ifdef CONFIG_DAMON_PADDR
@@ -3199,11 +3199,11 @@ static unsigned long damos_get_node_eligible_mem_bp(struct damon_ctx *c,
node_eligible = damos_calc_eligible_bytes(c, s, nid, &total_eligible);
- if (!(unsigned long)total_eligible)
+ if (!total_eligible)
return 0;
- return mult_frac((unsigned long)node_eligible, 10000,
- (unsigned long)total_eligible);
+ /* node_eligible <= total_eligible, so this cannot overflow */
+ return mul_u64_u64_div_u64(node_eligible, 10000, total_eligible);
}
#else /* CONFIG_DAMON_PADDR */
static unsigned long damos_get_node_eligible_mem_bp(struct damon_ctx *c,
@@ -3246,8 +3246,8 @@ static unsigned int damos_get_in_active_mem_bp(bool active_ratio)
global_node_page_state(NR_LRU_BASE + LRU_INACTIVE_FILE);
total = max(active + inactive, 1);
if (active_ratio)
- return mult_frac(active, 10000, total);
- return mult_frac(inactive, 10000, total);
+ return damon_mult_frac(active, 10000, total);
+ return damon_mult_frac(inactive, 10000, total);
}
static unsigned int damos_hugepage_mem_bp(void)
@@ -3263,7 +3263,7 @@ static unsigned int damos_hugepage_mem_bp(void)
thp = global_node_page_state(NR_ANON_THPS) +
global_node_page_state(NR_SHMEM_THPS) +
global_node_page_state(NR_FILE_THPS);
- return mult_frac(thp, 10000, total_pages - free_pages);
+ return damon_mult_frac(thp, 10000, total_pages - free_pages);
}
static void damos_set_quota_goal_current_value(struct damon_ctx *c,
@@ -3329,7 +3329,7 @@ static unsigned long damos_quota_score(struct damon_ctx *c, struct damos *s)
damos_for_each_quota_goal(goal, quota) {
damos_set_quota_goal_current_value(c, s, goal);
highest_score = max(highest_score,
- mult_frac(goal->current_value, 10000,
+ damon_mult_frac(goal->current_value, 10000,
goal->target_value));
}
diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h
index e71e60d59400..aaf65828a5e1 100644
--- a/mm/damon/tests/core-kunit.h
+++ b/mm/damon/tests/core-kunit.h
@@ -756,6 +756,36 @@ static void damon_test_sample_ratio_large_value(struct kunit *test)
UINT_MAX);
}
+static void damos_test_quota_score_large_value(struct kunit *test)
+{
+ struct damos s;
+ struct damos_quota_goal goal = {
+ .metric = DAMOS_QUOTA_USER_INPUT,
+ .current_value = 500000,
+ .target_value = 1000000,
+ };
+
+ memset(&s, 0, sizeof(s));
+ INIT_LIST_HEAD(&s.quota.goals);
+ INIT_LIST_HEAD(&goal.list);
+ damos_add_quota_goal(&s.quota, &goal);
+ KUNIT_EXPECT_EQ(test, damos_quota_score(NULL, &s), 5000ul);
+
+ goal.current_value = 1500001;
+ KUNIT_EXPECT_EQ(test, damos_quota_score(NULL, &s), 15000ul);
+
+ goal.current_value = ULONG_MAX;
+ goal.target_value = 1;
+ KUNIT_EXPECT_EQ(test, damos_quota_score(NULL, &s), ULONG_MAX);
+
+ /* the largest representable score, and one beyond it */
+ goal.current_value = ULONG_MAX - ULONG_MAX / 10000;
+ goal.target_value = 9999;
+ KUNIT_EXPECT_EQ(test, damos_quota_score(NULL, &s), ULONG_MAX);
+ goal.current_value++;
+ KUNIT_EXPECT_EQ(test, damos_quota_score(NULL, &s), ULONG_MAX);
+}
+
static void damon_test_set_attrs(struct kunit *test)
{
struct damon_ctx *c = damon_new_ctx();
@@ -2568,6 +2598,7 @@ static struct kunit_case damon_test_cases[] = {
KUNIT_CASE(damon_test_mult_frac),
KUNIT_CASE(damon_test_age_for_new_attrs_large_value),
KUNIT_CASE(damon_test_sample_ratio_large_value),
+ KUNIT_CASE(damos_test_quota_score_large_value),
KUNIT_CASE(damon_test_set_attrs),
KUNIT_CASE(damon_test_mvsum),
KUNIT_CASE(damon_test_nr_accesses_mvsum),
--
2.43.0
next prev parent reply other threads:[~2026-10-27 13:59 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-27 13:59 [RFC PATCH 0/3] mm/damon: fix overflows in score and ratio calculations DaeMyung Kang
2026-10-27 13:59 ` [RFC PATCH 1/3] mm/damon/ops-common: avoid overflow in weighted hotness score DaeMyung Kang
2026-10-27 13:59 ` [RFC PATCH 2/3] mm/damon/core: avoid overflows in monitoring results calculations DaeMyung Kang
2026-10-27 14:18 ` Gutierrez Asier
2026-10-27 13:59 ` DaeMyung Kang [this message]
2026-10-27 14:23 ` [RFC PATCH 0/3] mm/damon: fix overflows in score and ratio calculations SJ Park
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261027135932.2301646-4-charsyam@gmail.com \
--to=charsyam@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=damon@lists.linux.dev \
--cc=gutierrez.asier@huawei-partners.com \
--cc=linux-mm@kvack.org \
--cc=sj@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox