[PATCH v2 0/6] mm: make MAP_PRIVATE-/dev/zero mappings truly anonymous

Lorenzo Stoakes (ARM) posted 6 patches 2 weeks, 3 days ago
MAINTAINERS                                        |  4 +-
drivers/char/Makefile                              |  2 +-
include/linux/mm.h                                 | 10 +--
include/linux/pagemap.h                            |  3 +-
mm/Makefile                                        |  3 +-
drivers/char/mem.c => mm/char-mem.c                | 21 +++--
mm/internal.h                                      | 20 +++--
mm/shmem.c                                         |  2 +-
mm/vma.c                                           | 39 +++++++--
mm/vma.h                                           |  3 -
tools/testing/selftests/mm/merge.c                 | 95 ++++++++++++++++++++++
.../selftests/proc/proc-self-map-files-001.c       |  2 +-
.../selftests/proc/proc-self-map-files-002.c       |  2 +-
tools/testing/vma/include/dup.h                    | 10 ++-
tools/testing/vma/shared.c                         |  9 ++
tools/testing/vma/tests/mmap.c                     | 37 +++++++++
16 files changed, 220 insertions(+), 42 deletions(-)
[PATCH v2 0/6] mm: make MAP_PRIVATE-/dev/zero mappings truly anonymous
Posted by Lorenzo Stoakes (ARM) 2 weeks, 3 days ago
Historically anonymous memory was obtained in linux by MAP_PRIVATE-mapping
/dev/zero.

The canonical way of doing these now is mmap() specifying MAP_PRIVATE |
MAP_ANON, but we must continue to support the legacy means of obtaining these
mappings.

As-is these mappings are an unusual edge-case - they satisfy
vma_is_anonymous() but have non-NULL vma->vm_file, and their page offset is
the offset into the /dev/zero file.

Commit 93c0c8dc87f6 ("mm/rmap: use anon pgoff to track MAP_PRIVATE
file-backed anon folios") causes all other anonymous folios to be tracked
by their anon index (vma->vm_start >> PAGE_SHIFT at the point of first
fault), leaving MAP_PRIVATE-/dev/zero as the outlier.

This series remedies the situation by making MAP_PRIVATE-/dev/zero mappings
truly anonymous with !vma->vm_file and correct anonymous page offset.

It starts by bringing the memory character driver into mm/ - this file
implements /dev/zero, /dev/mem among other things and is already (as
clearly indicated by its name) within the remit of memory management.

By doing this, the file_is_dev_zero() function can be provided, internal to
mm, which allows for positive identification of these mappings.

Using this, first prevent any other mappings from mapping memory
anonymously, then make these mappings truly anonymous and eliminate all
code in the kernel that previously had to account for these strange beasts.

Finally, it adds userland VMA tests to assert the behaviour and selftests
to assert expected merge behaviour.

v2:
* Added tags (thanks everybody!)
* Reworked comment about mappings not setting themselves anon as per David.
* Fixed up test typo as per David.
* Fixed up test close() as per David.

v1:
https://lore.kernel.org/r/20260902-map-private-dev-zero-v1-0-a578c730cec7@kernel.org

Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
Lorenzo Stoakes (ARM) (6):
      mm: move drivers/char/mem.c to mm/char-mem.c
      mm: implement file_is_dev_zero() to uniquely identify /dev/zero
      mm/vma: only permit MAP_PRIVATE /dev/zero to be mapped anonymous
      mm/vma: make MAP_PRIVATE-mapped /dev/zero mappings truly anonymous
      tools/testing/vma: add test to assert MAP_PRIVATE-/dev/zero is anon
      tools/testing/selftests/mm: add MAP_PRIVATE-/dev/zero merge tests

 MAINTAINERS                                        |  4 +-
 drivers/char/Makefile                              |  2 +-
 include/linux/mm.h                                 | 10 +--
 include/linux/pagemap.h                            |  3 +-
 mm/Makefile                                        |  3 +-
 drivers/char/mem.c => mm/char-mem.c                | 21 +++--
 mm/internal.h                                      | 20 +++--
 mm/shmem.c                                         |  2 +-
 mm/vma.c                                           | 39 +++++++--
 mm/vma.h                                           |  3 -
 tools/testing/selftests/mm/merge.c                 | 95 ++++++++++++++++++++++
 .../selftests/proc/proc-self-map-files-001.c       |  2 +-
 .../selftests/proc/proc-self-map-files-002.c       |  2 +-
 tools/testing/vma/include/dup.h                    | 10 ++-
 tools/testing/vma/shared.c                         |  9 ++
 tools/testing/vma/tests/mmap.c                     | 37 +++++++++
 16 files changed, 220 insertions(+), 42 deletions(-)
---
base-commit: e3b5239afe1b8f0194db7436b17c33e94c1988c4
change-id: 20260902-map-private-dev-zero-ba36d76a2fc8

Best regards,
-- 
Lorenzo Stoakes (ARM) <ljs@kernel.org>
Re: [PATCH v2 0/6] mm: make MAP_PRIVATE-/dev/zero mappings truly anonymous
Posted by Andrew Morton 2 weeks, 2 days ago
On Tue, 08 Sep 2026 12:23:37 +0100 "Lorenzo Stoakes (ARM)" <ljs@kernel.org> wrote:

> Historically anonymous memory was obtained in linux by MAP_PRIVATE-mapping
> /dev/zero.
> 
> The canonical way of doing these now is mmap() specifying MAP_PRIVATE |
> MAP_ANON, but we must continue to support the legacy means of obtaining these
> mappings.
> 
> As-is these mappings are an unusual edge-case - they satisfy
> vma_is_anonymous() but have non-NULL vma->vm_file, and their page offset is
> the offset into the /dev/zero file.
> 
> Commit 93c0c8dc87f6 ("mm/rmap: use anon pgoff to track MAP_PRIVATE
> file-backed anon folios") causes all other anonymous folios to be tracked
> by their anon index (vma->vm_start >> PAGE_SHIFT at the point of first
> fault), leaving MAP_PRIVATE-/dev/zero as the outlier.
> 
> This series remedies the situation by making MAP_PRIVATE-/dev/zero mappings
> truly anonymous with !vma->vm_file and correct anonymous page offset.
> 
> It starts by bringing the memory character driver into mm/ - this file
> implements /dev/zero, /dev/mem among other things and is already (as
> clearly indicated by its name) within the remit of memory management.
> 
> By doing this, the file_is_dev_zero() function can be provided, internal to
> mm, which allows for positive identification of these mappings.
> 
> Using this, first prevent any other mappings from mapping memory
> anonymously, then make these mappings truly anonymous and eliminate all
> code in the kernel that previously had to account for these strange beasts.
> 
> Finally, it adds userland VMA tests to assert the behaviour and selftests
> to assert expected merge behaviour.

Thanks, I've updated mm-unstable to this version.

> v2:
> * Added tags (thanks everybody!)
> * Reworked comment about mappings not setting themselves anon as per David.
> * Fixed up test typo as per David.
> * Fixed up test close() as per David.

Here's how v2 altered mm.git:

--- a/mm/vma.c~b
+++ a/mm/vma.c
@@ -2801,7 +2801,7 @@ static int call_mmap_prepare(struct mmap
 	if (err)
 		return err;
 
-	/* Hooks cannot mark themselves anonymous. */
+	/* It's invalid for mmap_preprare hooks to clear vm_ops. */
 	if (!desc->vm_ops)
 		return -EINVAL;
 
--- a/tools/testing/selftests/mm/merge.c~b
+++ a/tools/testing/selftests/mm/merge.c
@@ -1381,7 +1381,7 @@ TEST_F(merge, merge_map_private_dev_zero
 	 *
 	 * With these being made truly anonymous upon mapping, they will
 	 * merge. If they were file-backed VMAs the page offsets would prevent
-	 * merge:
+	 * the merge:
 	 *
 	 * |-----||------|    |-------------|
 	 * | ptr || ptr2 | -> |     ptr     |
@@ -1389,17 +1389,11 @@ TEST_F(merge, merge_map_private_dev_zero
 	 */
 	ptr = mmap(carveout, 5 * page_size, PROT_READ | PROT_WRITE,
 		   MAP_FIXED | MAP_PRIVATE, fd_zero, 0);
-	if (ptr == MAP_FAILED) {
-		close(fd_zero);
-		ASSERT_TRUE(false);
-	}
 	ptr2 = mmap(&carveout[5 * page_size], 5 * page_size,
 		   PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE, fd_zero, 0);
-	if (ptr2 == MAP_FAILED) {
-		close(fd_zero);
-		ASSERT_TRUE(false);
-	}
 	close(fd_zero);
+	ASSERT_NE(ptr, MAP_FAILED);
+	ASSERT_NE(ptr2, MAP_FAILED);
 
 	/* Assert that they merged. */
 	ASSERT_TRUE(find_vma_procmap(procmap, ptr));
@@ -1430,10 +1424,7 @@ TEST_F(merge, merge_map_private_dev_zero
 	 */
 	ptr = mmap(carveout, 15 * page_size, PROT_READ | PROT_WRITE,
 		   MAP_FIXED | MAP_PRIVATE, fd_zero, 0);
-	if (ptr == MAP_FAILED) {
-		close(fd_zero);
-		ASSERT_TRUE(false);
-	}
+	ASSERT_NE(ptr, MAP_FAILED);
 	memset(ptr, 'x', 15 * page_size);
 
 	/*
_