* [RFC PATCH v1.2 1/5] mm/damon/core: avoid infinite kdamond_merge_regions() internal loop
2026-07-13 13:58 [RFC PATCH v1.2 0/5] mm/damon: unurgent fixes for infinite loop, NULL de-ref and races SJ Park
@ 2026-07-13 13:58 ` SJ Park
2026-07-13 13:58 ` [RFC PATCH v1.2 2/5] mm/damon/tests/core-kunit: catch test failure in test_merge_regions_of() SJ Park
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: SJ Park @ 2026-07-13 13:58 UTC (permalink / raw)
Cc: SJ Park, stable, Andrew Morton, damon, linux-kernel, linux-mm
Due to online parameter update like events, the number of DAMON regions
could be higher than the user-set upper limit. kdamond_merge_regions()
repeats merge regions until the number meets the limit, while doubling
the merge threshold up to the theoretical maximum threshold. It is
tried only up to the theoretical maximum threshold because even the
aggressive merging can fail from reducing the number of regions under
the user-defined upper limit. For example, there could be many
user-defined non-contiguous regions that cannot be merged.
The threshold based loop break condition is evaluated by comparing the
threshold for the next merging try against the theoretical maximum
threshold. If max_thres is larger than UINT_MAX / 2, doubling the
threshold could make it overflow, and bypass the loop break condition.
In the case, if the number of regions cannot be reduced under the upper
limit like explained above, the loop will run infinitely.
Prevent the case by doing the break condition check before doubling the
threshold. Also, prevent the threshold exceeding the maximum threshold,
as it could overflow and apply the wrong merge threshold.
This issue is unlikely to occur in real world, since having the
max_thres higher than UINT_MAX / 2 require unrealistically large
aggregation intervals compared to the sampling interval. Also, it
requires an unrealistically large number of uncontiguous regions setup.
Nonetheless, the consequence is bad and the fix is simple.
The issue was discovered [1] by Sashiko.
[1] https://lore.kernel.org/20260709145425.96247-1-sj@kernel.org
Fixes: 310d6c15e910 ("mm/damon/core: merge regions aggressively when max_nr_regions is unmet")
Cc: <stable@vger.kernel.org> # 6.10.x
Signed-off-by: SJ Park <sj@kernel.org>
---
mm/damon/core.c | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/mm/damon/core.c b/mm/damon/core.c
index 806a67d02a6e9..f3b6a46fdaabd 100644
--- a/mm/damon/core.c
+++ b/mm/damon/core.c
@@ -3369,15 +3369,20 @@ static void kdamond_merge_regions(struct damon_ctx *c, unsigned int threshold,
max_thres = c->attrs.aggr_interval /
(c->attrs.sample_interval ? c->attrs.sample_interval : 1);
- do {
+ while (true) {
nr_regions = 0;
damon_for_each_target(t, c) {
damon_merge_regions_of(t, threshold, sz_limit, c);
nr_regions += damon_nr_regions(t);
}
- threshold = max(1, threshold * 2);
- } while (nr_regions > c->attrs.max_nr_regions &&
- threshold / 2 < max_thres);
+ if (nr_regions <= c->attrs.max_nr_regions ||
+ max_thres <= threshold)
+ break;
+ if (threshold < max_thres / 2)
+ threshold = max(1, threshold * 2);
+ else
+ threshold = max_thres;
+ }
}
#ifdef CONFIG_DAMON_DEBUG_SANITY
--
2.47.3
^ permalink raw reply related [flat|nested] 5+ messages in thread* [RFC PATCH v1.2 2/5] mm/damon/tests/core-kunit: catch test failure in test_merge_regions_of()
2026-07-13 13:58 [RFC PATCH v1.2 0/5] mm/damon: unurgent fixes for infinite loop, NULL de-ref and races SJ Park
2026-07-13 13:58 ` [RFC PATCH v1.2 1/5] mm/damon/core: avoid infinite kdamond_merge_regions() internal loop SJ Park
@ 2026-07-13 13:58 ` SJ Park
2026-07-13 13:58 ` [RFC PATCH v1.2 3/5] mm/damon/vaddr: drop last same folio access check optimization SJ Park
2026-07-13 13:58 ` [RFC PATCH v1.2 5/5] mm/damon/sysfs: read ops_id only once SJ Park
3 siblings, 0 replies; 5+ messages in thread
From: SJ Park @ 2026-07-13 13:58 UTC (permalink / raw)
Cc: SJ Park, stable, Andrew Morton, Brendan Higgins, David Gow,
SeongJae Park, damon, kunit-dev, linux-kernel, linux-kselftest,
linux-mm
KUNIT_EXPECT_EQ() does not abort the execution of test code when the
expectation is not met. But damon_test_merge_regions_of() code after
its initial KUNIT_EXPECT_EQ() call assumes the expectation is met. It
does a per-region test with a hard-coded number of regions that is
correct only if the expectation was met. As a result, __nth_region_of()
could return NULL, and the test code can dereference NULL pointers. Fix
the issue by catching the expectation failure and skip the per-region
tests.
The user impact on realistic setups should be negligible, as it is a
unit test.
The issue was discovered [1] by Sashiko.
[1] https://lore.kernel.org/20260710144937.26981-1-sj@kernel.org
Fixes: 17ccae8bb5c9 ("mm/damon: add kunit tests")
Cc: <stable@vger.kernel.org> # 5.15.x
Signed-off-by: SJ Park <sj@kernel.org>
---
mm/damon/tests/core-kunit.h | 3 +++
1 file changed, 3 insertions(+)
diff --git a/mm/damon/tests/core-kunit.h b/mm/damon/tests/core-kunit.h
index 6ad73559dd8ea..a99363720e677 100644
--- a/mm/damon/tests/core-kunit.h
+++ b/mm/damon/tests/core-kunit.h
@@ -260,11 +260,14 @@ static void damon_test_merge_regions_of(struct kunit *test)
damon_merge_regions_of(t, 9, 9999, ctx);
/* 0-112, 114-130, 130-156, 156-170, 170-230, 230-10170 */
KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 6u);
+ if (damon_nr_regions(t) != 6)
+ goto out;
for (i = 0; i < 6; i++) {
r = __nth_region_of(t, i);
KUNIT_EXPECT_EQ(test, r->ar.start, saddrs[i]);
KUNIT_EXPECT_EQ(test, r->ar.end, eaddrs[i]);
}
+out:
damon_free_target(t);
damon_destroy_ctx(ctx);
}
--
2.47.3
^ permalink raw reply related [flat|nested] 5+ messages in thread* [RFC PATCH v1.2 3/5] mm/damon/vaddr: drop last same folio access check optimization
2026-07-13 13:58 [RFC PATCH v1.2 0/5] mm/damon: unurgent fixes for infinite loop, NULL de-ref and races SJ Park
2026-07-13 13:58 ` [RFC PATCH v1.2 1/5] mm/damon/core: avoid infinite kdamond_merge_regions() internal loop SJ Park
2026-07-13 13:58 ` [RFC PATCH v1.2 2/5] mm/damon/tests/core-kunit: catch test failure in test_merge_regions_of() SJ Park
@ 2026-07-13 13:58 ` SJ Park
2026-07-13 13:58 ` [RFC PATCH v1.2 5/5] mm/damon/sysfs: read ops_id only once SJ Park
3 siblings, 0 replies; 5+ messages in thread
From: SJ Park @ 2026-07-13 13:58 UTC (permalink / raw)
Cc: SJ Park, stable, Andrew Morton, Fernand Sieber, Leonard Foerster,
SeongJae Park, Shakeel Butt, damon, linux-kernel, linux-mm
The optimization can race when multiple kdamonds are running.
Meanwhile, the impact of the optimization is quite doubtful. Just
remove it.
The user impact of the issue should be quite trivial. After all, the
race can happen only when the user intentionally setup DAMON in the way.
Even if it happens, it would be rare and only degrade the best-effort
monitoring results. No critical consequences like kernel panic or
memory corruption happen.
The race possibility was discovered [1] by Sashiko.
[1] https://lore.kernel.org/20260621204050.10993-1-sj@kernel.org
Fixes: 3f49584b262c ("mm/damon: implement primitives for the virtual memory address spaces")
Cc: <stable@vger.kernel.org> # 5.15.x
Signed-off-by: SJ Park <sj@kernel.org>
---
mm/damon/vaddr.c | 33 ++++++---------------------------
1 file changed, 6 insertions(+), 27 deletions(-)
diff --git a/mm/damon/vaddr.c b/mm/damon/vaddr.c
index d10b8042adb5b..d487b7a4a1042 100644
--- a/mm/damon/vaddr.c
+++ b/mm/damon/vaddr.c
@@ -383,8 +383,6 @@ static void damon_va_prepare_access_checks(struct damon_ctx *ctx)
}
struct damon_young_walk_private {
- /* size of the folio for the access checked virtual memory address */
- unsigned long *folio_sz;
bool young;
};
@@ -411,7 +409,6 @@ static int damon_young_pmd_entry(pmd_t *pmd, unsigned long addr,
mmu_notifier_test_young(walk->mm,
addr))
priv->young = true;
- *priv->folio_sz = HPAGE_PMD_SIZE;
huge_out:
spin_unlock(ptl);
return 0;
@@ -430,7 +427,6 @@ static int damon_young_pmd_entry(pmd_t *pmd, unsigned long addr,
if (pte_young(ptent) || !folio_test_idle(folio) ||
mmu_notifier_test_young(walk->mm, addr))
priv->young = true;
- *priv->folio_sz = folio_size(folio);
out:
pte_unmap_unlock(pte, ptl);
return 0;
@@ -458,7 +454,6 @@ static int damon_young_hugetlb_entry(pte_t *pte, unsigned long hmask,
if (pte_young(entry) || !folio_test_idle(folio) ||
mmu_notifier_test_young(walk->mm, addr))
priv->young = true;
- *priv->folio_sz = huge_page_size(h);
folio_put(folio);
@@ -470,11 +465,9 @@ static int damon_young_hugetlb_entry(pte_t *pte, unsigned long hmask,
#define damon_young_hugetlb_entry NULL
#endif /* CONFIG_HUGETLB_PAGE */
-static bool damon_va_young(struct mm_struct *mm, unsigned long addr,
- unsigned long *folio_sz)
+static bool damon_va_young(struct mm_struct *mm, unsigned long addr)
{
struct damon_young_walk_private arg = {
- .folio_sz = folio_sz,
.young = false,
};
@@ -494,28 +487,17 @@ static bool damon_va_young(struct mm_struct *mm, unsigned long addr,
* r the region to be checked
*/
static void __damon_va_check_access(struct mm_struct *mm,
- struct damon_region *r, bool same_target)
+ struct damon_region *r)
{
- static unsigned long last_addr;
- static unsigned long last_folio_sz = PAGE_SIZE;
- static bool last_accessed;
+ bool accessed;
if (!mm) {
damon_update_region_access_rate(r, false);
return;
}
- /* If the region is in the last checked page, reuse the result */
- if (same_target && (ALIGN_DOWN(last_addr, last_folio_sz) ==
- ALIGN_DOWN(r->sampling_addr, last_folio_sz))) {
- damon_update_region_access_rate(r, last_accessed);
- return;
- }
-
- last_accessed = damon_va_young(mm, r->sampling_addr, &last_folio_sz);
- damon_update_region_access_rate(r, last_accessed);
-
- last_addr = r->sampling_addr;
+ accessed = damon_va_young(mm, r->sampling_addr);
+ damon_update_region_access_rate(r, accessed);
}
static unsigned int damon_va_check_accesses(struct damon_ctx *ctx)
@@ -524,15 +506,12 @@ static unsigned int damon_va_check_accesses(struct damon_ctx *ctx)
struct mm_struct *mm;
struct damon_region *r;
unsigned int max_nr_accesses = 0;
- bool same_target;
damon_for_each_target(t, ctx) {
mm = damon_get_mm(t);
- same_target = false;
damon_for_each_region(r, t) {
- __damon_va_check_access(mm, r, same_target);
+ __damon_va_check_access(mm, r);
max_nr_accesses = max(r->nr_accesses, max_nr_accesses);
- same_target = true;
}
if (mm)
mmput(mm);
--
2.47.3
^ permalink raw reply related [flat|nested] 5+ messages in thread* [RFC PATCH v1.2 5/5] mm/damon/sysfs: read ops_id only once
2026-07-13 13:58 [RFC PATCH v1.2 0/5] mm/damon: unurgent fixes for infinite loop, NULL de-ref and races SJ Park
` (2 preceding siblings ...)
2026-07-13 13:58 ` [RFC PATCH v1.2 3/5] mm/damon/vaddr: drop last same folio access check optimization SJ Park
@ 2026-07-13 13:58 ` SJ Park
3 siblings, 0 replies; 5+ messages in thread
From: SJ Park @ 2026-07-13 13:58 UTC (permalink / raw)
Cc: SJ Park, Andrew Morton, damon, linux-kernel, linux-mm
damon_sysfs_apply_inputs() reads ops_id twice. It could race with
ops_id_store(). As a result, the min_region_sz could wrongly be set up.
Read it once.
The user impact is trivial. Sane users ain't update the parameter in
parallel. Even if it happens, only monitoring itself runs differently
than expected. No critical consequences like kernel panic or memory
corruption happen.
The issue was discovered [1] by Sashiko.
[1] https://lore.kernel.org/20260703172417.95426-1-sj@kernel.org
Signed-off-by: SJ Park <sj@kernel.org>
---
mm/damon/sysfs.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/mm/damon/sysfs.c b/mm/damon/sysfs.c
index b5fe036f78015..60a1a9e4ada34 100644
--- a/mm/damon/sysfs.c
+++ b/mm/damon/sysfs.c
@@ -2094,14 +2094,16 @@ static inline bool damon_sysfs_kdamond_running(
static int damon_sysfs_apply_inputs(struct damon_ctx *ctx,
struct damon_sysfs_context *sys_ctx)
{
+ enum damon_ops_id ops_id;
int err;
- err = damon_select_ops(ctx, sys_ctx->ops_id);
+ ops_id = READ_ONCE(sys_ctx->ops_id);
+ err = damon_select_ops(ctx, ops_id);
if (err)
return err;
ctx->addr_unit = sys_ctx->addr_unit;
/* addr_unit is respected by only DAMON_OPS_PADDR */
- if (sys_ctx->ops_id == DAMON_OPS_PADDR)
+ if (ops_id == DAMON_OPS_PADDR)
ctx->min_region_sz = max(
DAMON_MIN_REGION_SZ / sys_ctx->addr_unit, 1);
ctx->pause = sys_ctx->pause;
--
2.47.3
^ permalink raw reply related [flat|nested] 5+ messages in thread