]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
mm: introduce VM_MAYBE_GUARD and make visible in /proc/$pid/smaps
authorLorenzo Stoakes <lorenzo.stoakes@oracle.com>
Tue, 18 Nov 2025 10:17:43 +0000 (10:17 +0000)
committerAndrew Morton <akpm@linux-foundation.org>
Thu, 20 Nov 2025 21:43:58 +0000 (13:43 -0800)
Patch series "introduce VM_MAYBE_GUARD and make it sticky", v4.

Currently, guard regions are not visible to users except through
/proc/$pid/pagemap, with no explicit visibility at the VMA level.

This makes the feature less useful, as it isn't entirely apparent which
VMAs may have these entries present, especially when performing actions
which walk through memory regions such as those performed by CRIU.

This series addresses this issue by introducing the VM_MAYBE_GUARD flag
which fulfils this role, updating the smaps logic to display an entry for
these.

The semantics of this flag are that a guard region MAY be present if set
(we cannot be sure, as we can't efficiently track whether an
MADV_GUARD_REMOVE finally removes all the guard regions in a VMA) - but if
not set the VMA definitely does NOT have any guard regions present.

It's problematic to establish this flag without further action, because
that means that VMAs with guard regions in them become non-mergeable with
adjacent VMAs for no especially good reason.

To work around this, this series also introduces the concept of 'sticky'
VMA flags - that is flags which:

a. if set in one VMA and not in another still permit those VMAs to be
   merged (if otherwise compatible).

b. When they are merged, the resultant VMA must have the flag set.

The VMA logic is updated to propagate these flags correctly.

Additionally, VM_MAYBE_GUARD being an explicit VMA flag allows us to solve
an issue with file-backed guard regions - previously these established an
anon_vma object for file-backed mappings solely to have vma_needs_copy()
correctly propagate guard region mappings to child processes.

We introduce a new flag alias VM_COPY_ON_FORK (which currently only
specifies VM_MAYBE_GUARD) and update vma_needs_copy() to check explicitly
for this flag and to copy page tables if it is present, which resolves
this issue.

Additionally, we add the ability for allow-listed VMA flags to be
atomically writable with only mmap/VMA read locks held.

The only flag we allow so far is VM_MAYBE_GUARD, which we carefully ensure
does not cause any races by being allowed to do so.

This allows us to maintain guard region installation as a read-locked
operation and not endure the overhead of obtaining a write lock here.

Finally we introduce extensive VMA userland tests to assert that the
sticky VMA logic behaves correctly as well as guard region self tests to
assert that smaps visibility is correctly implemented.

This patch (of 9):

Currently, if a user needs to determine if guard regions are present in a
range, they have to scan all VMAs (or have knowledge of which ones might
have guard regions).

Since commit 8e2f2aeb8b48 ("fs/proc/task_mmu: add guard region bit to
pagemap") and the related commit a516403787e0 ("fs/proc: extend the
PAGEMAP_SCAN ioctl to report guard regions"), users can use either
/proc/$pid/pagemap or the PAGEMAP_SCAN functionality to perform this
operation at a virtual address level.

This is not ideal, and it gives no visibility at a /proc/$pid/smaps level
that guard regions exist in ranges.

This patch remedies the situation by establishing a new VMA flag,
VM_MAYBE_GUARD, to indicate that a VMA may contain guard regions (it is
uncertain because we cannot reasonably determine whether a
MADV_GUARD_REMOVE call has removed all of the guard regions in a VMA, and
additionally VMAs may change across merge/split).

We utilise 0x800 for this flag which makes it available to 32-bit
architectures also, a flag that was previously used by VM_DENYWRITE, which
was removed in commit 8d0920bde5eb ("mm: remove VM_DENYWRITE") and hasn't
bee reused yet.

We also update the smaps logic and documentation to identify these VMAs.

Another major use of this functionality is that we can use it to identify
that we ought to copy page tables on fork.

We do not actually implement usage of this flag in mm/madvise.c yet as we
need to allow some VMA flags to be applied atomically under mmap/VMA read
lock in order to avoid the need to acquire a write lock for this purpose.

Link: https://lkml.kernel.org/r/cover.1763460113.git.lorenzo.stoakes@oracle.com
Link: https://lkml.kernel.org/r/cf8ef821eba29b6c5b5e138fffe95d6dcabdedb9.1763460113.git.lorenzo.stoakes@oracle.com
Signed-off-by: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
Reviewed-by: Pedro Falcato <pfalcato@suse.de>
Reviewed-by: Vlastimil Babka <vbabka@suse.cz>
Acked-by: David Hildenbrand (Red Hat) <david@kernel.org>
Reviewed-by: Lance Yang <lance.yang@linux.dev>
Cc: Andrei Vagin <avagin@gmail.com>
Cc: Baolin Wang <baolin.wang@linux.alibaba.com>
Cc: Barry Song <baohua@kernel.org>
Cc: Dev Jain <dev.jain@arm.com>
Cc: Jann Horn <jannh@google.com>
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Liam Howlett <liam.howlett@oracle.com>
Cc: "Masami Hiramatsu (Google)" <mhiramat@kernel.org>
Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Mike Rapoport <rppt@kernel.org>
Cc: Nico Pache <npache@redhat.com>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Suren Baghdasaryan <surenb@google.com>
Cc: Zi Yan <ziy@nvidia.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Documentation/filesystems/proc.rst
fs/proc/task_mmu.c
include/linux/mm.h
include/trace/events/mmflags.h
mm/memory.c
tools/testing/vma/vma_internal.h

index 0b86a8022fa1ec780d62a08aa3e9ba5fc6359d15..8256e857e2d74714d54089822cb38bbd173092ec 100644 (file)
@@ -553,7 +553,7 @@ otherwise.
 kernel flags associated with the particular virtual memory area in two letter
 encoded manner. The codes are the following:
 
-    ==    =======================================
+    ==    =============================================================
     rd    readable
     wr    writeable
     ex    executable
@@ -591,7 +591,8 @@ encoded manner. The codes are the following:
     sl    sealed
     lf    lock on fault pages
     dp    always lazily freeable mapping
-    ==    =======================================
+    gu    maybe contains guard regions (if not set, definitely doesn't)
+    ==    =============================================================
 
 Note that there is no guarantee that every flag and associated mnemonic will
 be present in all further kernel releases. Things get changed, the flags may
index fc35a0543f0191d263ef410292416ad922cb7ed2..db16ed91c269d6b247de00bc48ab90e1a68237b7 100644 (file)
@@ -1146,6 +1146,7 @@ static void show_smap_vma_flags(struct seq_file *m, struct vm_area_struct *vma)
                [ilog2(VM_MAYSHARE)]    = "ms",
                [ilog2(VM_GROWSDOWN)]   = "gd",
                [ilog2(VM_PFNMAP)]      = "pf",
+               [ilog2(VM_MAYBE_GUARD)] = "gu",
                [ilog2(VM_LOCKED)]      = "lo",
                [ilog2(VM_IO)]          = "io",
                [ilog2(VM_SEQ_READ)]    = "sr",
index df9f258a017cff720d9dc5079e110905b522028e..36b9418c00fcaed72e9519bdad45cc5020aa3b47 100644 (file)
@@ -271,6 +271,8 @@ extern struct rw_semaphore nommu_region_sem;
 extern unsigned int kobjsize(const void *objp);
 #endif
 
+#define VM_MAYBE_GUARD_BIT 11
+
 /*
  * vm_flags in vm_area_struct, see mm_types.h.
  * When changing, update also include/trace/events/mmflags.h
@@ -296,6 +298,7 @@ extern unsigned int kobjsize(const void *objp);
 #define VM_UFFD_MISSING        0
 #endif /* CONFIG_MMU */
 #define VM_PFNMAP      0x00000400      /* Page-ranges managed without "struct page", just pure PFN */
+#define VM_MAYBE_GUARD BIT(VM_MAYBE_GUARD_BIT) /* The VMA maybe contains guard regions. */
 #define VM_UFFD_WP     0x00001000      /* wrprotect pages tracking */
 
 #define VM_LOCKED      0x00002000
index aa441f593e9a6b537d02189add91eb77bebc6a97..a6e5a44c9b42994f687d59e9b62b1031624b1543 100644 (file)
@@ -213,6 +213,7 @@ IF_HAVE_PG_ARCH_3(arch_3)
        {VM_UFFD_MISSING,               "uffd_missing"  },              \
 IF_HAVE_UFFD_MINOR(VM_UFFD_MINOR,      "uffd_minor"    )               \
        {VM_PFNMAP,                     "pfnmap"        },              \
+       {VM_MAYBE_GUARD,                "maybe_guard"   },              \
        {VM_UFFD_WP,                    "uffd_wp"       },              \
        {VM_LOCKED,                     "locked"        },              \
        {VM_IO,                         "io"            },              \
index b09de6274da3146f6208e168123eb110cc9e5e1b..d1728d0538d64a34d4f6f15e6665da44e543e932 100644 (file)
@@ -1478,6 +1478,10 @@ vma_needs_copy(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma)
        if (src_vma->anon_vma)
                return true;
 
+       /* Guard regions have modified page tables that require copying. */
+       if (src_vma->vm_flags & VM_MAYBE_GUARD)
+               return true;
+
        /*
         * Don't copy ptes where a page fault will fill them correctly.  Fork
         * becomes much lighter when there are big shared or private readonly
index c68d382dac81ff9b9ceaca3c867875efa249fb9a..46acb4df45de6c833f4969987df4d627ac8bf531 100644 (file)
@@ -56,6 +56,7 @@ extern unsigned long dac_mmap_min_addr;
 #define VM_MAYEXEC     0x00000040
 #define VM_GROWSDOWN   0x00000100
 #define VM_PFNMAP      0x00000400
+#define VM_MAYBE_GUARD 0x00000800
 #define VM_LOCKED      0x00002000
 #define VM_IO           0x00004000
 #define VM_SEQ_READ    0x00008000      /* App will access data sequentially */