Re: [PATCH] arm64: mm: validate /dev/mem mmap ranges without overflow
From: Catalin Marinas
Date: Tue Nov 03 2026 - 12:15:24 EST
On Mon, Nov 02, 2026 at 01:10:30AM +0800, Zhao Gongyi wrote:
> An arm64 syzkaller reproducer creates a FUSE character device with
> major:minor 1:1, so opening it uses the /dev/mem file operations. It
> maps one page from offset 0xfffffffffffff000 at user address zero.
> The next openat() with a NULL pathname triggers a synchronous external
> abort (SEA, ESR 0x96000010) in strncpy_from_user() via do_getname(),
> panicking the kernel.
>
> valid_mmap_phys_addr_range() checks the exclusive end by adding the
> mapping size to the starting address. For this offset, the sum wraps
> to zero and passes the PHYS_MASK test, allowing an out-of-range PFN to
> reach remap_pfn_range(). The inclusive-end overflow check in
> mmap_mem_prepare() does not catch it because the end is ULONG_MAX.
>
> Reject starting PFNs outside PHYS_MASK and compare the inclusive length
> against the remaining physical range without overflowing. This also
> allows mappings that end exactly at PHYS_MASK, which the old check
> incorrectly rejects.
>
> Fixes: 1d18c47c735e ("arm64: MMU fault handling and page table management")
> Cc: stable@xxxxxxxxxxxxxxx
> Signed-off-by: Zhao Gongyi <zhaogongyi@xxxxxxxxxxxxx>
> ---
> Tested on the base commit with QEMU TCG and 52-bit LPA2:
> the overflowing one-page mapping changes from success to -EINVAL,
> while the last valid one- and two-page mappings change from -EINVAL to
> success. A direct /dev/mem mmap followed by openat(NULL) panics before
> this change; with the change, mmap fails with -EINVAL and openat()
> returns -EFAULT.
>
> arch/arm64/mm/mmap.c | 7 ++++++-
> 1 file changed, 6 insertions(+), 1 deletion(-)
>
> diff --git a/arch/arm64/mm/mmap.c b/arch/arm64/mm/mmap.c
> index 32e0771..d6286bc 100644
> --- a/arch/arm64/mm/mmap.c
> +++ b/arch/arm64/mm/mmap.c
> @@ -61,7 +61,12 @@ int valid_phys_addr_range(phys_addr_t addr, size_t size)
> */
> int valid_mmap_phys_addr_range(unsigned long pfn, size_t size)
> {
> - return !(((pfn << PAGE_SHIFT) + size) & ~PHYS_MASK);
> + const phys_addr_t max_addr = PHYS_MASK;
> +
> + if (!size || pfn > (max_addr >> PAGE_SHIFT))
> + return 0;
> +
> + return size - 1 <= max_addr - (pfn << PAGE_SHIFT);
> }
Can we write this as:
int valid_mmap_phys_addr_range(unsigned long pfn, size_t size)
{
phys_addr_t limit = PHYS_MASK + 1;
return size <= limit && pfn <= (limit - size) >> PAGE_SHIFT;
}
It would resemble the generic __access_ok() better where we need to
check for similar overflow. I find it easier to read.
(happy to make the change when applying)
--
Catalin