All of lore.kernel.org
 help / color / mirror / Atom feed
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


  parent reply	other threads:[~2026-10-27 13:59 UTC|newest]

Thread overview: 9+ 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 14:05   ` sashiko-bot
2026-10-27 13:59 ` [RFC PATCH 2/3] mm/damon/core: avoid overflows in monitoring results calculations DaeMyung Kang
2026-10-27 14:10   ` sashiko-bot
2026-10-27 14:18   ` Gutierrez Asier
2026-10-27 13:59 ` DaeMyung Kang [this message]
2026-10-27 14:12   ` [RFC PATCH 3/3] mm/damon/core: avoid overflows in quota goal metrics and scores sashiko-bot
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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.