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 2/3] mm/damon/core: avoid overflows in monitoring results calculations
Date: Tue, 27 Oct 2026 22:59:31 +0900 [thread overview]
Message-ID: <20261027135932.2301646-3-charsyam@gmail.com> (raw)
In-Reply-To: <20261027135932.2301646-1-charsyam@gmail.com>
When monitoring intervals change, DAMON converts region ages and sample
counts for the new intervals. The conversions overflow before division:
- damon_age_for_new_attrs() multiplies the age by the old aggregation
interval in unsigned long. On 32-bit kernels, the default 100 ms
interval makes this overflow after about 72 minutes.
- The sample ratio conversions multiply unsigned int values. They can
overflow on every architecture when an aggregation has more than
429,496 samples, such as a 1 ms sample interval and a 430 second
aggregation interval.
The moving sums also use mult_frac() for the fraction of an aggregation
window that remains and for the previous window's contribution. On
32-bit kernels, both can overflow with long aggregation intervals. For
example, 500,000 remaining samples of a 600,000-sample window are
8,333 bp, but the first calculation returns 1,175 bp.
Add damon_mult_frac(), which preserves mult_frac() results when its
intermediates fit, calculates larger remainder products in full width,
and saturates results that exceed unsigned long. Use it for the
conversions and the moving sums. Saturate the unsigned int conversion
results at UINT_MAX.
Add KUnit coverage for the helper, the conversions, and the moving sums.
The conversion issues were discovered [1] by Sashiko.
[1] https://lore.kernel.org/20260708140944.F26821F000E9@smtp.kernel.org
Fixes: 2f5bef5a590b ("mm/damon/core: update monitoring results for new monitoring attributes")
Fixes: 9f3786d8602f ("mm/damon/core: introduce damon_nr_accesses_mvsum()")
Fixes: 2ec26dd341d1 ("mm/damon/core: introduce damon_probe_hits_mvsum()")
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
---
mm/damon/core.c | 41 +++++++++++++++++---
mm/damon/tests/core-kunit.h | 74 +++++++++++++++++++++++++++++++++++++
2 files changed, 109 insertions(+), 6 deletions(-)
diff --git a/mm/damon/core.c b/mm/damon/core.c
index 73b571b429cb..5cd4007cc370 100644
--- a/mm/damon/core.c
+++ b/mm/damon/core.c
@@ -281,6 +281,32 @@ static bool damon_has_probe_weights(struct damon_ctx *c)
return false;
}
+/*
+ * damon_mult_frac() - calculate @x * @n / @d without overflowing.
+ *
+ * Same as mult_frac(), but the intermediate product of the remainder does not
+ * overflow, and a result that does not fit in unsigned long is saturated to
+ * ULONG_MAX. Returns the same value as mult_frac() when mult_frac() does not
+ * overflow. @d must not be zero.
+ */
+static unsigned long damon_mult_frac(unsigned long x, unsigned long n,
+ unsigned long d)
+{
+ unsigned long q = x / d, r = x % d;
+ unsigned long frac;
+
+ if (n && q > ULONG_MAX / n)
+ return ULONG_MAX;
+ if (!n || r <= ULONG_MAX / n)
+ frac = r * n / d;
+ else
+ /* r < d, so the quotient is smaller than n */
+ frac = mul_u64_u64_div_u64(r, n, d);
+ if (frac > ULONG_MAX - q * n)
+ return ULONG_MAX;
+ return q * n + frac;
+}
+
/*
* damon_mvsum() - Returns pseudo moving sum value for a time window.
* @current_nr: The value of the current aggregation window.
@@ -305,7 +331,7 @@ static bool damon_has_probe_weights(struct damon_ctx *c)
static unsigned long damon_mvsum(unsigned long current_nr,
unsigned long last_nr, unsigned long left_window_bp)
{
- return current_nr + mult_frac(last_nr, left_window_bp, 10000);
+ return current_nr + damon_mult_frac(last_nr, left_window_bp, 10000);
}
/**
@@ -337,7 +363,7 @@ unsigned int damon_nr_accesses_mvsum(struct damon_region *r,
else
left_window = ctx->next_aggregation_sis -
ctx->passed_sample_intervals;
- left_window_bp = mult_frac(left_window, 10000, window_len);
+ left_window_bp = damon_mult_frac(left_window, 10000, window_len);
if (left_window_bp == 10000)
return r->last_nr_accesses;
@@ -361,7 +387,7 @@ unsigned char damon_probe_hits_mvsum(int probe_idx, struct damon_region *r,
else
left_window = ctx->next_aggregation_sis -
ctx->passed_sample_intervals;
- left_window_bp = mult_frac(left_window, 10000, window_len);
+ left_window_bp = damon_mult_frac(left_window, 10000, window_len);
if (left_window_bp == 10000)
return r->last_probe_hits[probe_idx];
@@ -990,21 +1016,24 @@ static bool damon_attrs_equals(const struct damon_attrs *attrs1,
static unsigned int damon_age_for_new_attrs(unsigned int age,
struct damon_attrs *old_attrs, struct damon_attrs *new_attrs)
{
- return age * old_attrs->aggr_interval / new_attrs->aggr_interval;
+ return min(damon_mult_frac(age, old_attrs->aggr_interval,
+ new_attrs->aggr_interval), UINT_MAX);
}
/* convert sample ratio in bp (per 10,000) to count */
static unsigned int damon_sample_bp_to_count(
unsigned int bp, struct damon_attrs *attrs)
{
- return bp * damon_nr_samples_per_aggr(attrs) / 10000;
+ return min(damon_mult_frac(bp, damon_nr_samples_per_aggr(attrs),
+ 10000), UINT_MAX);
}
/* convert sample count to ratio in bp (per 10,000) */
static unsigned int damon_sample_count_to_bp(
unsigned int count, struct damon_attrs *attrs)
{
- return mult_frac(count, 10000, damon_nr_samples_per_aggr(attrs));
+ return min(damon_mult_frac(count, 10000,
+ damon_nr_samples_per_aggr(attrs)), UINT_MAX);
}
static unsigned int damon_nr_samples_for_new_attrs(unsigned int nr,
diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h
index 43e2f950f8ff..e71e60d59400 100644
--- a/mm/damon/tests/core-kunit.h
+++ b/mm/damon/tests/core-kunit.h
@@ -698,6 +698,64 @@ static void damon_test_update_monitoring_result(struct kunit *test)
damon_destroy_ctx(ctx);
}
+static void damon_test_mult_frac(struct kunit *test)
+{
+ KUNIT_EXPECT_EQ(test, damon_mult_frac(7, 3, 2), 10ul);
+ KUNIT_EXPECT_EQ(test, damon_mult_frac(ULONG_MAX, 0, 1), 0ul);
+ /* the remainder product overflows on 32-bit */
+ KUNIT_EXPECT_EQ(test, damon_mult_frac(524288, 10000, 786432), 6666ul);
+ KUNIT_EXPECT_EQ(test, damon_mult_frac(500000, 10000, 1000000), 5000ul);
+ /* the remainder product overflows even on 64-bit */
+ KUNIT_EXPECT_EQ(test, damon_mult_frac(ULONG_MAX - 1, ULONG_MAX,
+ ULONG_MAX), ULONG_MAX - 1);
+ /* the quotient product overflows */
+ KUNIT_EXPECT_EQ(test, damon_mult_frac(ULONG_MAX, 10000, 1), ULONG_MAX);
+ /* the largest representable result, and one beyond it */
+ KUNIT_EXPECT_EQ(test, damon_mult_frac(ULONG_MAX - ULONG_MAX / 10000,
+ 10000, 9999), ULONG_MAX);
+ KUNIT_EXPECT_EQ(test, damon_mult_frac(ULONG_MAX - ULONG_MAX / 10000 + 1,
+ 10000, 9999), ULONG_MAX);
+}
+
+static void damon_test_age_for_new_attrs_large_value(struct kunit *test)
+{
+ struct damon_attrs old_attrs = {.aggr_interval = 100000};
+ struct damon_attrs new_attrs = {.aggr_interval = 50000};
+ unsigned int age;
+
+ age = damon_age_for_new_attrs(50000, &old_attrs, &new_attrs);
+ KUNIT_EXPECT_EQ(test, age, 100000u);
+
+ new_attrs.aggr_interval = 1;
+ age = damon_age_for_new_attrs(UINT_MAX, &old_attrs, &new_attrs);
+ KUNIT_EXPECT_EQ(test, age, UINT_MAX);
+
+ old_attrs.aggr_interval = ULONG_MAX;
+ age = damon_age_for_new_attrs(UINT_MAX, &old_attrs, &new_attrs);
+ KUNIT_EXPECT_EQ(test, age, UINT_MAX);
+}
+
+static void damon_test_sample_ratio_large_value(struct kunit *test)
+{
+ struct damon_attrs attrs = {
+ .sample_interval = 1000,
+ .aggr_interval = 600000000,
+ };
+
+ KUNIT_EXPECT_EQ(test, damon_sample_bp_to_count(10000, &attrs),
+ 600000u);
+ KUNIT_EXPECT_EQ(test, damon_sample_count_to_bp(600000, &attrs),
+ 10000u);
+ KUNIT_EXPECT_EQ(test, damon_sample_count_to_bp(500000, &attrs),
+ 8333u);
+ KUNIT_EXPECT_EQ(test, damon_sample_bp_to_count(UINT_MAX, &attrs),
+ UINT_MAX);
+
+ attrs.aggr_interval = attrs.sample_interval;
+ KUNIT_EXPECT_EQ(test, damon_sample_count_to_bp(UINT_MAX, &attrs),
+ UINT_MAX);
+}
+
static void damon_test_set_attrs(struct kunit *test)
{
struct damon_ctx *c = damon_new_ctx();
@@ -735,6 +793,7 @@ static void damon_test_mvsum(struct kunit *test)
3, 10, 5000, 8, /* 3 + 10 * 0.5 */
32, 100, 1000, 42, /* 32 + 100 * 0.1 */
42, 49, 0, 42, /* 42 + 49 * 0 */
+ 0, 600000, 8333, 499980, /* 32-bit remainder product overflow */
};
int i;
@@ -794,6 +853,18 @@ static void damon_test_nr_accesses_mvsum(struct kunit *test)
damon_nr_accesses_mvsum(r, c));
}
+ c->attrs.sample_interval = 1000;
+ c->attrs.aggr_interval = 600000000;
+ c->passed_sample_intervals = 100000;
+ c->next_aggregation_sis = 600000;
+ r->nr_accesses = 100000;
+ r->last_nr_accesses = 600000;
+ KUNIT_EXPECT_EQ(test, damon_nr_accesses_mvsum(r, c), 599980u);
+
+ r->probe_hits[0] = 0;
+ r->last_probe_hits[0] = 100;
+ KUNIT_EXPECT_EQ(test, damon_probe_hits_mvsum(0, r, c), 83);
+
damon_free_region(r);
damon_destroy_ctx(c);
}
@@ -2494,6 +2565,9 @@ static struct kunit_case damon_test_cases[] = {
KUNIT_CASE(damon_test_set_regions),
KUNIT_CASE(damon_test_nr_samples_per_aggr),
KUNIT_CASE(damon_test_update_monitoring_result),
+ 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(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 ` DaeMyung Kang [this message]
2026-10-27 14:18 ` [RFC PATCH 2/3] mm/damon/core: avoid overflows in monitoring results calculations Gutierrez Asier
2026-10-27 13:59 ` [RFC PATCH 3/3] mm/damon/core: avoid overflows in quota goal metrics and scores DaeMyung Kang
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-3-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