Re: [PATCH v3 42/47] mm/vma: call ->may_split() before a partial merge
From: Lance Yang
Date: Tue Nov 03 2026 - 11:29:32 EST
On Sat, Oct 31, 2026 at 10:40:11AM +0100, David Hildenbrand (Arm) wrote:
>From: Chris Mason <mason@xxxxxxxxxx>
>
>A driver can use the ->may_split() hook to forbid a VMA boundary at
>certain addresses.
>
>A device-dax device is a character device that gives raw access to a
>range of memory through mmap(). device-dax refuses any address that is not
>a multiple of the device's alignment through vm_ops->may_split.
>
>vma_merge_existing_range() can move boundaries, but does not call
>vm_ops->may_split.
>
>If a range at the edge of a VMA gets the attributes of the adjacent VMA,
>the function moves only that range over to the neighbour: one VMA shrinks
>and the other grows. This is a partial merge, and vma_modify() tries it
>before any split.
>
>Take a device-dax VMA that lies next to another VMA of the same file.
>The user calls mprotect() on the one page of the first VMA that touches
>the second VMA, and asks for the protection that the second VMA already
>has. The result is a partial merge. It puts the boundary inside a 1 GiB
>mapping, which is a single leaf entry at the PUD level.
>
>As part of the merge, the kernel looks for a huge PMD to split at the
>new boundary. That code does not expect a PUD leaf:
>
> vma_merge_existing_range()
> commit_merge()
> vma_adjust_trans_huge()
> split_huge_pmd_address()
> mm_find_pmd()
> treats the PUD leaf as a pointer to a PMD table,
> so the "table" is the device memory that the leaf maps
> __split_huge_pmd()
> reads a PMD that the user wrote to the device memory
> folio_put() on the folio that this PMD names
>
>Bad. While the code must at some point be taught to split PUD entries as
>well, the issue here is that we move VMA boundaries such that they are
>incompatible with the driver.
>
>Fix this by calling ->may_split() on the address that a partial merge
>would move the boundary to, and by abandoning the merge if the driver
>refuses. vma_modify() then tries the split, which fails with -EINVAL.
>
>Factor out existing split handling into check_split() and reuse it.
>
>Fix generated by an LLM and sent by Chris Mason in private.
>
>Fixes: 38607c62b34b ("fs/dax: properly refcount fs dax pages")
>Cc: stable@xxxxxxxxxxxxxxx
>Signed-off-by: Chris Mason <mason@xxxxxxxxxx>
>Reviewed-by: Lorenzo Stoakes (ARM) <ljs@xxxxxxxxxx>
>[ david: improve description; cleanup the code ]
>Reviewed-by: Gregory Price (Meta) <gourry@xxxxxxxxxx>
>Tested-by: Gregory Price (Meta) <gourry@xxxxxxxxxx>
>Signed-off-by: Gregory Price (Meta) <gourry@xxxxxxxxxx>
>Tested-by: Muhammad Usama Anjum <usama.anjum@xxxxxxx>
>Signed-off-by: David Hildenbrand (Arm) <david@xxxxxxxxxx>
>---
Reviewed-by: Lance Yang <lance.yang@xxxxxxxxx>