Re: [PATCH] arm64: mm: fix accidental linear mapping of no-map reserved memory

From: Will Deacon

Date: Thu Jul 16 2026 - 11:12:29 EST


[+Ard]

On Wed, May 13, 2026 at 09:02:55AM +0800, liulhong617@xxxxxxx wrote:
> From: liulhong617 <liulhong617@xxxxxxxxx>
>
> When reserved-memory regions with the "no-map" property are not
> page-aligned, the kernel may accidentally map them into the linear
> mapping, contradicting the no-map semantics.

Crikey, I wonder what the semantics are for "no-map" if the region isn't
page aligned? If the remaining part of the page is advertised as memory
but doesn't have the "no-map" property, then we can't really satisfy
what we're being asked to do.

> The root cause is a mismatch between /proc/iomem's address boundaries
> and the actual page table mapping boundaries:
>
> 1. /proc/iomem derives its ranges from memblock via
> memblock_region_reserved_base_pfn/memblock_region_reserved_end_pfn,
> which perform PFN rounding so the displayed boundaries are
> page-aligned. This gives the impression that the no-map region
> occupies whole pages.
>
> 2. However, memblock_mark_nomap() splits memblock.memory regions at
> exact byte boundaries (memblock_isolate_range preserves raw DT
> base/size with no alignment). When for_each_mem_range iterates the
> non-NOMAP regions adjacent to a no-map region, it returns start/end
> values that are NOT page-aligned — they are the precise byte
> boundaries from the memblock split.
>
> 3. These sub-page-aligned values are passed to
> __create_pgd_mapping_locked(), which does:
> phys &= PAGE_MASK;
> addr = virt & PAGE_MASK;
> end = PAGE_ALIGN(virt + size);
> The downward rounding of phys via PAGE_MASK extends the mapped
> range backward into the adjacent no-map region, effectively
> including no-map memory in the linear mapping.
>
> For example, with 64K pages, reserved_region@A2000000 (base=0xA2000000,
> size=0x8000, no-map) causes for_each_mem_range to return
> start=0xA2008000 for the next mappable region. After phys &= PAGE_MASK,
> the actual mapping starts at 0xA2000000 — the entire no-map region is
> incorrectly mapped.
>
> Fix this by rounding the mappable range inward to PAGE_SIZE boundaries
> before passing it to __map_memblock: start is rounded UP and end is
> rounded DOWN. This ensures the mapped area never overlaps with adjacent
> no-map regions. The cost is at most one page of unmapped gap at each
> boundary, which is preferable to violating no-map semantics.
>
> Signed-off-by: liulhong617 <liulhong617@xxxxxxxxx>
> ---
> arch/arm64/mm/mmu.c | 14 ++++++++++++++
> 1 file changed, 14 insertions(+)
>
> diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c
> index dd85e093f..bc8ac7622 100644
> --- a/arch/arm64/mm/mmu.c
> +++ b/arch/arm64/mm/mmu.c
> @@ -1175,6 +1175,20 @@ static void __init map_mem(pgd_t *pgdp)
> for_each_mem_range(i, &start, &end) {
> if (start >= end)
> break;
> + /*
> + * for_each_mem_range may return sub-page-aligned boundaries
> + * after memblock_mark_nomap() splits regions at byte precision.
> + * __create_pgd_mapping_locked aligns phys down to PAGE_MASK,
> + * which could accidentally map no-map memory on the boundary.
> + * Round the mappable range inward: start UP, end DOWN, so
> + * that the mapped area never overlaps with adjacent no-map
> + * regions. The cost is at most one page of unmapped gap at
> + * each boundary.
> + */
> + start = PAGE_ALIGN(start);
> + end = end & PAGE_MASK;
> + if (start >= end)
> + continue;

Maybe I'm over-worrying here, but I've seen firmware describe parts of
memory as lots of small, adjacent regions in some cases and so I'm
worried we'd fail to map the pages with this change.

I'd be more in favour of detecting sub-page sized "no-map" regions that
are adjacent to normal memory regions, emitting a warning/firmware taint
and then doing... precisely nothing about it. What practical issues are
you seeing on your system?

Will