Re: [PATCH 1/2] mm/damon/core: cover discrete System RAM areas with per-range regions

Jiayuan Chen <[email protected]> Mon, 27 Jul 2026 19:04:32 +0800
Newsgroups dev.linux.lists.damon,dev.linux.lists.sashiko-reviews
Message-ID <[email protected]>
On 7/27/26 6:07 PM, [email protected] wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [High] Time-of-check to time-of-use (TOCTOU) race condition in `damon_find_system_rams()` leads to a heap out-of-bounds write during concurrent memory hotplug.
> - [High] DAMON initialization fails with `-EINVAL` on systems with sub-page System RAM gaps due to alignment overlap in `damon_set_regions()`.
> --
>
> --- Patch [1]: [PATCH 1/2] mm/damon/core: cover discrete System RAM areas with per-range regions ---
> commit 20fb01bf6e8e4cefc6016dfc073fb77c95ca3511
> Author: Jiayuan Chen <[email protected]>
>
> mm/damon/core: cover discrete System RAM areas with per-range regions
>
> This commit changes how DAMON sets default monitoring regions for System RAM.
> Instead of a single region covering all RAM (and the holes between), it now
> creates separate regions for each discrete System RAM area. This excludes
> large memory holes from being monitored.
>
>> diff --git a/mm/damon/core.c b/mm/damon/core.c
>> index 644daf5a16560..365c43271c0c6 100644
>> --- a/mm/damon/core.c
>> +++ b/mm/damon/core.c
> [ ... ]
>
>> +static int damon_find_system_rams(struct damon_addr_range **ranges_out,
>> +				  unsigned long addr_unit)
>> +{
>> +	struct damon_system_ram_range_walk_arg arg = { .addr_unit = addr_unit };
>> +	struct damon_addr_range *ranges;
>>   
>> +	/* First pass: count the coalesced ranges. */
>>   	walk_system_ram_res(0, -1, &arg, damon_system_ram_walk_fn);
>> -	if (!arg.walked)
>> -		return false;
>> -	*start = damon_res_to_core_addr(arg.res.start, addr_unit);
>> -	*end = damon_res_to_core_addr(arg.res.end + 1, addr_unit);
>> -	if (*end <= *start)
>> -		return false;
>> -	return true;
>> +	if (!arg.nr_ranges)
>> +		return 0;
>> +	ranges = kvmalloc_objs(*ranges, arg.nr_ranges, GFP_KERNEL);
>> +	if (!ranges)
>> +		return -ENOMEM;
>> +	/* Second pass: fill in the coalesced ranges. */
>> +	arg.ranges = ranges;
>> +	arg.nr_ranges = 0;
>> +	arg.has_prev = false;
>> +	walk_system_ram_res(0, -1, &arg, damon_system_ram_walk_fn);
> [Severity: High]
> Can this race with memory hotplug events and cause an out-of-bounds write?
>
> walk_system_ram_res() is called twice without holding memory hotplug locks.
> If a new System RAM region is hotplugged between or during the second pass,
> the number of visited regions could exceed the allocated buffer size.
>
> If that happens, damon_system_ram_walk_fn() would write past the end of the
> kvmalloc-allocated buffer:
>
> mm/damon/core.c:damon_system_ram_walk_fn() {
> 	...
> 	if (a->ranges) {
> 		a->ranges[a->nr_ranges].start = start;
> 		a->ranges[a->nr_ranges].end = end;
> 	}
> 	a->nr_ranges++;
> 	...
> }

You're right. The two walk_system_ram_res() calls should be wrapped with

get_online_mems()/put_online_mems() so the resource set cannot change

between the counting and filling passes.



>> +	*ranges_out = ranges;
>> +	return arg.nr_ranges;
>>   }
> [ ... ]
>
>> @@ -3984,14 +4021,25 @@ int damon_set_region_system_rams_default(struct damon_target *t,
>>   			unsigned long addr_unit, unsigned long min_region_sz)
>>   {
>>   	struct damon_addr_range addr_range;
>> +	struct damon_addr_range *ranges;
>> +	int nr_ranges, err;
>>   
>> -	if (!*start && !*end &&
>> -		!damon_find_system_rams_range(start, end, addr_unit))
>> -		return -EINVAL;
>> +	if (*start || *end) {
>> +		addr_range.start = *start;
>> +		addr_range.end = *end;
>> +		return damon_set_regions(t, &addr_range, 1, min_region_sz);
>> +	}
>>   
>> -	addr_range.start = *start;
>> -	addr_range.end = *end;
>> -	return damon_set_regions(t, &addr_range, 1, min_region_sz);
>> +	nr_ranges = damon_find_system_rams(&ranges, addr_unit);
>> +	if (nr_ranges < 0)
>> +		return nr_ranges;
>> +	if (!nr_ranges)
>> +		return -EINVAL;
>> +	*start = ranges[0].start;
>> +	*end = ranges[nr_ranges - 1].end;
>> +	err = damon_set_regions(t, ranges, nr_ranges, min_region_sz);
> [Severity: High]
> Could this fail with -EINVAL during initialization on systems with sub-page
> memory gaps?
>
> If the system has System RAM fragmented by small non-RAM holes (like sub-page
> BIOS or EFI reserved regions), damon_system_ram_walk_fn() will create separate
> discrete regions.
>
> When these regions are passed to damon_set_regions(), it aligns the boundaries
> to min_region_sz (typically PAGE_SIZE):
>
> mm/damon/core.c:damon_set_regions() {
> 	for (i = 0; i < nr_ranges; i++) {
> 		...
> 		start = ALIGN_DOWN(ranges[i].start, min_region_sz);
> 		end = ALIGN(ranges[i].end, min_region_sz);
> 		if (start >= end)
> 			return -EINVAL;
> 		if (i > 0 && last_end > start)
> 			return -EINVAL;
> 		last_end = end;
> 	}
> 	...
> }
>
> If the gap between two RAM areas is smaller than PAGE_SIZE, the ALIGN of the
> first region's end will evaluate to a higher address than the ALIGN_DOWN of
> the second region's start. Does this cause the strictly-ascending overlap
> check (last_end > start) to trigger and reject valid configurations?


this is a real issue. I overlooked the case where System RAM boundaries 
are not page-size aligned


>> +	kvfree(ranges);
>> +	return err;
>>   }