Re: [PATCH v2 13/15] mm/vma: make MAP_PRIVATE-mapped /dev/zero mappings truly anonymous

From: Yang Shi

Date: Mon Jul 27 2026 - 17:32:03 EST


On Mon, Jul 20, 2026 at 8:30 AM Lorenzo Stoakes (ARM) <ljs@xxxxxxxxxx> wrote:
>
> When mapping /dev/zero with MAP_PRIVATE, one ends up with strange VMAs
> originating from Linux's distant past.
>
> These have vma->vm_file set but NULL vma->vm_ops, meaning they satisfy
> vma_is_anonymous() but otherwise resemble a file-backed VMA.
>
> The introduction of virtual page offsets and their subsequent use as
> indexes for MAP_PRIVATE-file-backed mappings mean the rmap does the right
> thing with these but we are left with inconsistencies.
>
> The vma_start_pgoff(vma) == vma_start_virt_pgoff(vma) invariant is true for
> all other anonymous VMAs, but not these.
>
> These VMAs are also observable as files in /proc/<pid>/[s]maps but
> otherwise behave like anonymous mappings.
>
> Therefore let's make these VMAs actually anonymous at mapping time which
> will activate the anonymous code path for mappings.
>
> This means we no longer have to account for this discrepancy anywhere and
> no longer have to think about these at all.

I'm glad to see this is solved finally. But /proc/<pid>/smaps will not
show /dev/zero anymore. It is user visible. I recalled you were
concerned about it before.
https://lore.kernel.org/linux-mm/c98f68c1-4bfd-4410-9b65-1eb5a1efe271@lucifer.local/

Anyway I don't think it is a big deal. It should be harmless. I would
be surprised any real life workload would care whether the anonymous
mapping is backed by /dev/zero or not in 2026.

Thanks,
Yang

>
> A previous commit gave us map_is_dev_zero() to positively identify these
> mappings, so we expressly only do so for these alone.
>
> The impact of this change should be low as likely very few are relying upon
> this in any case, and in using them are asking for anonymous memory, so no
> longer seeing these as file mappings in smaps should have no meaningful
> impact.
>
> Update assert_sane_pgoff(), the comment for vma_start_pgoff() and
> linear_virt_page_index() to reflect the change.
>
> We make this change in call_mmap_prepare() alone as /dev/zero has been
> converted to an mmap_prepare hook and we do not permit nested MAP_PRIVATE
> mapping of /dev/zero.
>
> We also remove the now defunct vma_desc_set_anonymous() and eliminate the
> temporary bisection hazard fix from the previous commit.
>
> Also update the VMA userland tests to reflect the change.
>
> Signed-off-by: Lorenzo Stoakes (ARM) <ljs@xxxxxxxxxx>
> ---
> include/linux/mm.h | 10 ++--------
> include/linux/pagemap.h | 3 +--
> mm/vma.c | 26 +++++++++++++++++---------
> mm/vma.h | 3 ---
> tools/testing/vma/include/dup.h | 3 +--
> 5 files changed, 21 insertions(+), 24 deletions(-)
>
> diff --git a/include/linux/mm.h b/include/linux/mm.h
> index 7614afe99c96..7fabe6c66b4b 100644
> --- a/include/linux/mm.h
> +++ b/include/linux/mm.h
> @@ -1554,11 +1554,6 @@ static inline void vma_set_anonymous(struct vm_area_struct *vma)
> vma->vm_ops = NULL;
> }
>
> -static inline void vma_desc_set_anonymous(struct vm_area_desc *desc)
> -{
> - desc->vm_ops = NULL;
> -}
> -
> static inline bool vma_is_anonymous(const struct vm_area_struct *vma)
> {
> return !vma->vm_ops;
> @@ -4352,9 +4347,8 @@ static inline unsigned long vma_pages(const struct vm_area_struct *vma)
> * If @vma is a MAP_PRIVATE file-backed mapping, then this returns the
> * page offset within the file.
> *
> - * Edge cases: nommu does not abide by these, MAP_PRIVATE-/dev/zero satisfies
> - * vma_is_anonymous() but has file-backed page offset, and MAP_PRIVATE-pfnmap
> - * regions have their page offset set to the first PFN in the range.
> + * Edge cases: nommu does not abide by these and CoW MAP_PRIVATE-pfnmap regions
> + * have their page offset set to the first PFN in the range.
> *
> * Returns: The page offset of the start of @vma.
> */
> diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
> index 3307c0551a0a..4e8b2b29f6d3 100644
> --- a/include/linux/pagemap.h
> +++ b/include/linux/pagemap.h
> @@ -1136,8 +1136,7 @@ static inline pgoff_t linear_virt_page_index(const struct vm_area_struct *vma,
> const pgoff_t pgoff = __linear_virt_page_index(vma, address);
>
> VM_WARN_ON_ONCE(vma_test(vma, VMA_SHARED_BIT));
> - /* Account for MAP_PRIVATE-/dev/zero which is only semi-anonymous. */
> - if (vma_is_anonymous(vma) && !vma->vm_file)
> + if (vma_is_anonymous(vma))
> VM_WARN_ON_ONCE(pgoff != linear_page_index(vma, address));
>
> return pgoff;
> diff --git a/mm/vma.c b/mm/vma.c
> index 103a6a134dec..d3e0457a11a1 100644
> --- a/mm/vma.c
> +++ b/mm/vma.c
> @@ -2622,6 +2622,13 @@ static bool map_is_dev_zero(const struct mmap_state *map)
> return imajor(inode) == MEM_MAJOR && iminor(inode) == DEVZERO_MINOR;
> }
>
> +static void map_set_anon(struct mmap_state *map)
> +{
> + map->file = NULL;
> + map->vm_ops = NULL;
> + map->pgoff = map->addr >> PAGE_SHIFT;
> +}
> +
> static bool map_is_private(const struct mmap_state *map)
> {
> return !vma_flags_test(&map->vma_flags, VMA_SHARED_BIT);
> @@ -2629,10 +2636,7 @@ static bool map_is_private(const struct mmap_state *map)
>
> static bool map_is_anon(const struct mmap_state *map)
> {
> - if (!map_is_private(map))
> - return false;
> -
> - return !map->file || map_is_dev_zero(map);
> + return map_is_private(map) && !map->file;
> }
>
> /*
> @@ -2664,7 +2668,7 @@ static int __mmap_new_vma(struct mmap_state *map, struct vm_area_struct **vmap,
>
> vma_iter_config(vmi, map->addr, map->end);
>
> - if (is_anon && !map->file)
> + if (is_anon)
> vma_set_anonymous(vma);
>
> vma_set_range(vma, map->addr, map->end, map->pgoff, map->virt_pgoff);
> @@ -2682,10 +2686,6 @@ static int __mmap_new_vma(struct mmap_state *map, struct vm_area_struct **vmap,
> else if (!is_anon)
> error = shmem_zero_setup(vma);
>
> - /* Temporary MAP_PRIVATE-/dev/zero workaround. */
> - if (is_anon && map->file)
> - vma_set_anonymous(vma);
> -
> if (error)
> goto free_iter_vma;
>
> @@ -2814,6 +2814,14 @@ static int call_mmap_prepare(struct mmap_state *map,
> map->vm_ops = desc->vm_ops;
> map->vm_private_data = desc->private_data;
>
> + /*
> + * MAP_PRIVATE-/dev/zero mappings are an ancient way of getting
> + * anonymous mappings. Rather than allowing these mappings to be odd
> + * outliers, simply make them truly anonymous.
> + */
> + if (map_is_private(map) && map_is_dev_zero(map))
> + map_set_anon(map);
> +
> return 0;
> }
>
> diff --git a/mm/vma.h b/mm/vma.h
> index 94277837ce6e..44fbb50dc3fe 100644
> --- a/mm/vma.h
> +++ b/mm/vma.h
> @@ -267,9 +267,6 @@ static inline void assert_sane_pgoff(struct vm_area_struct *vma, pgoff_t pgoff)
> */
> if (!vma_is_anonymous(vma))
> return;
> - /* MAP_PRIVATE-/dev/zero is anon, non-NULL vm_file, but has file pgoff. */
> - if (vma->vm_file)
> - return;
> /* If faulted in, could have been remapped. */
> if (vma->anon_vma)
> return;
> diff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h
> index c617ee2532bd..c5191b9718fd 100644
> --- a/tools/testing/vma/include/dup.h
> +++ b/tools/testing/vma/include/dup.h
> @@ -1648,8 +1648,7 @@ static inline pgoff_t linear_virt_page_index(const struct vm_area_struct *vma,
> const pgoff_t pgoff = __linear_virt_page_index(vma, address);
>
> VM_WARN_ON_ONCE(vma_test(vma, VMA_SHARED_BIT));
> - /* Account for MAP_PRIVATE-/dev/zero which is only semi-anonymous. */
> - if (vma_is_anonymous(vma) && !vma->vm_file)
> + if (vma_is_anonymous(vma))
> VM_WARN_ON_ONCE(pgoff != linear_page_index(vma, address));
>
> return pgoff;
>
> --
> 2.55.0
>
>