From: "Lorenzo Stoakes (ARM)" <ljs@kernel.org>
To: Arnd Bergmann <arnd@arndb.de>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Andrew Morton <akpm@linux-foundation.org>,
"Liam R. Howlett" <liam@infradead.org>,
Vlastimil Babka <vbabka@kernel.org>,
Jann Horn <jannh@google.com>, Pedro Falcato <pfalcato@suse.de>,
David Hildenbrand <david@kernel.org>,
Mike Rapoport <rppt@kernel.org>,
Suren Baghdasaryan <surenb@google.com>,
Michal Hocko <mhocko@suse.com>, Hugh Dickins <hughd@google.com>,
Baolin Wang <baolin.wang@linux.alibaba.com>,
"Matthew Wilcox (Oracle)" <willy@infradead.org>,
Jan Kara <jack@suse.cz>
Cc: linux-kernel@vger.kernel.org, linux-mm@kvack.org,
linux-fsdevel@vger.kernel.org, linux-kselftest@vger.kernel.org,
"Lorenzo Stoakes (ARM)" <ljs@kernel.org>
Subject: [PATCH 3/6] mm/vma: only permit MAP_PRIVATE /dev/zero to be mapped anonymous
Date: Wed, 02 Sep 2026 19:00:20 +0100 [thread overview]
Message-ID: <20260902-map-private-dev-zero-v1-3-a578c730cec7@kernel.org> (raw)
In-Reply-To: <20260902-map-private-dev-zero-v1-0-a578c730cec7@kernel.org>
In order to use mmap_prepare() with MAP_PRIVATE mappings of /dev/zero
without the success_hook hack we explicitly permitted mmap_prepare handlers
to set NULL vm_ops.
However this is dangerous and we really only want to allow this for
MAP_PRIVATE-mapped /dev/zero.
Therefore use the newly introduced file_is_dev_zero() to uniquely identify
MAP_PRIVATE-/dev/zero mappings and only permit this behaviour for them.
Then, remove all ability for mmap_prepare or mmap hooks to set a VMA
anonymous and update mmap_zero_prepare() to leave it to the core mmap code
to do so.
Note that this disallows nested MAP_PRIVATE-mappings of /dev/zero
regions. Doing this would be broken in any case.
We therefore do not need to update the mmap_prepare() compatibility layer
to reflect these changes, as the mmap hook check suffices to disallow this
behaviour.
Now we're setting vma->vm_ops to NULL for an mmap_prepare-initialised
MAP_PRIVATE-/dev/zero mapping, we have to avoid a subtle issue when
updating user-defined fields via set_vma_user_defined_fields().
The default for vma->vm_ops for all mmap_prepare-initialised mappings is
vma_dummy_vm_ops, so map->vm_ops will be set to this and setting
vma->vm_ops to this will render the VMA mistakenly non-anon.
In general, we should never be setting user-defined fields for an anonymous
VMA, so explicitly check for this to avoid doing so for the one case where
a mapping can be both mmap_prepare and anonymous.
In the case of legacy ->mmap hooks some drivers may set vma->vm_ops NULL
believing this is the equivalent of setting no VMA operations. Therefore
update mmap_file() to correct this by setting dummy VMA operations if this
occurs.
An example of this is drm_gem_shmem_mmap() which deliberately clears
vma->vm_ops before handing the VMA to dma-buf. Cases such as this will be
updated when they are converted to mmap_prepare.
Also, in order to avoid a single commit bisection hazard, add a temporary
workaround to set the VMA anonymous only after vma->vm_file is assigned in
__mmap_new_file_vma().
This is because vma_set_range() calls vma_set_pgoff() and
assert_sane_pgoff() in turn, prior to the vma->vm_file being assigned. If
we set the VMA anonymous early then this assert will fail.
This is removed in the subsequent commit.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/char-mem.c | 6 +-----
mm/internal.h | 17 ++++++++++-------
mm/vma.c | 33 +++++++++++++++++++++++++--------
3 files changed, 36 insertions(+), 20 deletions(-)
diff --git a/mm/char-mem.c b/mm/char-mem.c
index e53e89e6ddd8..c0b5fb019223 100644
--- a/mm/char-mem.c
+++ b/mm/char-mem.c
@@ -508,11 +508,7 @@ static int mmap_zero_prepare(struct vm_area_desc *desc)
if (vma_desc_test(desc, VMA_SHARED_BIT))
return shmem_zero_setup_desc(desc);
- /*
- * This is a highly unique situation where we mark a MAP_PRIVATE mapping
- * of /dev/zero anonymous, despite it not being.
- */
- vma_desc_set_anonymous(desc);
+ /* MAP_PRIVATE semantics are taken care of for us by core mm. */
return 0;
}
diff --git a/mm/internal.h b/mm/internal.h
index 5d474e5f7709..da14c56fb24e 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -226,15 +226,18 @@ static inline int mmap_file(struct file *file, struct vm_area_struct *vma)
{
int err = vfs_mmap(file, vma);
- if (likely(!err))
- return 0;
-
/*
- * OK, we tried to call the file hook for mmap(), but an error
- * arose. The mapping is in an inconsistent state and we must not invoke
- * any further hooks on it.
+ * Either we tried to call the file hook for mmap() and an error arose
+ * or a driver set vma->vm_ops = NULL intending there to be no VMA
+ * operations.
+ *
+ * In the former case the VMA is in an inconsistent state and we mustn't
+ * invoke any further hooks on it, in the latter case the hook actually
+ * wanted no further hooks to be invoked, so fix both by setting dummy
+ * VMA ops.
*/
- vma->vm_ops = &vma_dummy_vm_ops;
+ if (unlikely(err || !vma->vm_ops))
+ vma->vm_ops = &vma_dummy_vm_ops;
return err;
}
diff --git a/mm/vma.c b/mm/vma.c
index 35e7a64855fa..4b8d430d9619 100644
--- a/mm/vma.c
+++ b/mm/vma.c
@@ -2621,6 +2621,19 @@ static int __mmap_new_file_vma(struct mmap_state *map,
return 0;
}
+static bool map_is_private(const struct mmap_state *map)
+{
+ return !vma_flags_test(&map->vma_flags, VMA_SHARED_BIT);
+}
+
+static bool map_is_anon(const struct mmap_state *map)
+{
+ if (!map_is_private(map))
+ return false;
+
+ return !map->file || file_is_dev_zero(map->file);
+}
+
/*
* __mmap_new_vma() - Allocate a new VMA for the region, as merging was not
* possible.
@@ -2634,8 +2647,7 @@ static int __mmap_new_file_vma(struct mmap_state *map,
static int __mmap_new_vma(struct mmap_state *map, struct vm_area_struct **vmap,
struct mmap_action *action)
{
- const bool is_anon = !map->file &&
- !vma_flags_test(&map->vma_flags, VMA_SHARED_BIT);
+ const bool is_anon = map_is_anon(map);
struct vma_iterator *vmi = map->vmi;
int error = 0;
struct vm_area_struct *vma;
@@ -2651,7 +2663,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)
+ if (is_anon && !map->file)
vma_set_anonymous(vma);
vma_set_range(vma, map->addr, map->end, map->pgoff, map->anon_pgoff);
@@ -2669,6 +2681,10 @@ 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;
@@ -2777,6 +2793,10 @@ static int call_mmap_prepare(struct mmap_state *map,
if (err)
return err;
+ /* Hooks cannot mark themselves anonymous. */
+ if (!desc->vm_ops)
+ return -EINVAL;
+
err = call_action_prepare(map, desc);
if (err)
return err;
@@ -2799,10 +2819,7 @@ static int call_mmap_prepare(struct mmap_state *map,
static void set_vma_user_defined_fields(struct vm_area_struct *vma,
struct mmap_state *map)
{
- if (map->vm_ops)
- vma->vm_ops = map->vm_ops;
- else /* Only /dev/zero should do this. */
- vma_set_anonymous(vma);
+ vma->vm_ops = map->vm_ops;
vma->vm_private_data = map->vm_private_data;
}
@@ -2882,7 +2899,7 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr,
allocated_new = true;
}
- if (have_mmap_prepare)
+ if (have_mmap_prepare && !map_is_anon(&map))
set_vma_user_defined_fields(vma, &map);
__mmap_complete(&map, vma);
--
2.55.0
next prev parent reply other threads:[~2026-09-02 18:01 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-02 18:00 [PATCH 0/6] mm: make MAP_PRIVATE-/dev/zero mappings truly anonymous Lorenzo Stoakes (ARM)
2026-09-02 18:00 ` [PATCH 1/6] mm: move drivers/char/mem.c to mm/char-mem.c Lorenzo Stoakes (ARM)
2026-09-03 12:34 ` Mike Rapoport
2026-09-02 18:00 ` [PATCH 2/6] mm: implement file_is_dev_zero() to uniquely identify /dev/zero Lorenzo Stoakes (ARM)
2026-09-02 18:00 ` Lorenzo Stoakes (ARM) [this message]
2026-09-02 18:00 ` [PATCH 4/6] mm/vma: make MAP_PRIVATE-mapped /dev/zero mappings truly anonymous Lorenzo Stoakes (ARM)
2026-09-02 18:00 ` [PATCH 5/6] tools/testing/vma: add test to assert MAP_PRIVATE-/dev/zero is anon Lorenzo Stoakes (ARM)
2026-09-02 18:00 ` [PATCH 6/6] tools/testing/selftests/mm: add MAP_PRIVATE-/dev/zero merge tests Lorenzo Stoakes (ARM)
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260902-map-private-dev-zero-v1-3-a578c730cec7@kernel.org \
--to=ljs@kernel.org \
--cc=akpm@linux-foundation.org \
--cc=arnd@arndb.de \
--cc=baolin.wang@linux.alibaba.com \
--cc=david@kernel.org \
--cc=gregkh@linuxfoundation.org \
--cc=hughd@google.com \
--cc=jack@suse.cz \
--cc=jannh@google.com \
--cc=liam@infradead.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=mhocko@suse.com \
--cc=pfalcato@suse.de \
--cc=rppt@kernel.org \
--cc=surenb@google.com \
--cc=vbabka@kernel.org \
--cc=willy@infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox