]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
mm: allow handling of stacked mmap_prepare hooks in more drivers
authorLorenzo Stoakes (Oracle) <ljs@kernel.org>
Fri, 20 Mar 2026 22:39:43 +0000 (22:39 +0000)
committerAndrew Morton <akpm@linux-foundation.org>
Sun, 5 Apr 2026 20:53:44 +0000 (13:53 -0700)
While the conversion of mmap hooks to mmap_prepare is underway, we will
encounter situations where mmap hooks need to invoke nested mmap_prepare
hooks.

The nesting of mmap hooks is termed 'stacking'.  In order to flexibly
facilitate the conversion of custom mmap hooks in drivers which stack, we
must split up the existing __compat_vma_mmap() function into two separate
functions:

* compat_set_desc_from_vma() - This allows the setting of a vm_area_desc
  object's fields to the relevant fields of a VMA.

* __compat_vma_mmap() - Once an mmap_prepare hook has been executed upon a
  vm_area_desc object, this function performs any mmap actions specified by
  the mmap_prepare hook and then invokes its vm_ops->mapped() hook if any
  were specified.

In ordinary cases, where a file's f_op->mmap_prepare() hook simply needs
to be invoked in a stacked mmap() hook, compat_vma_mmap() can be used.

However some drivers define their own nested hooks, which are invoked in
turn by another hook.

A concrete example is vmbus_channel->mmap_ring_buffer(), which is invoked
in turn by bin_attribute->mmap():

vmbus_channel->mmap_ring_buffer() has a signature of:

int (*mmap_ring_buffer)(struct vmbus_channel *channel,
struct vm_area_struct *vma);

And bin_attribute->mmap() has a signature of:

int (*mmap)(struct file *, struct kobject *,
    const struct bin_attribute *attr,
    struct vm_area_struct *vma);

And so compat_vma_mmap() cannot be used here for incremental conversion of
hooks from mmap() to mmap_prepare().

There are many such instances like this, where conversion to mmap_prepare
would otherwise cascade to a huge change set due to nesting of this kind.

The changes in this patch mean we could now instead convert
vmbus_channel->mmap_ring_buffer() to
vmbus_channel->mmap_prepare_ring_buffer(), and implement something like:

struct vm_area_desc desc;
int err;

compat_set_desc_from_vma(&desc, file, vma);
err = channel->mmap_prepare_ring_buffer(channel, &desc);
if (err)
return err;

return __compat_vma_mmap(&desc, vma);

Allowing us to incrementally update this logic, and other logic like it.

Unfortunately, as part of this change, we need to be able to flexibly
assign to the VMA descriptor, so have to remove some of the const
declarations within the structure.

Also update the VMA tests to reflect the changes.

Link: https://lkml.kernel.org/r/24aac3019dd34740e788d169fccbe3c62781e648.1774045440.git.ljs@kernel.org
Signed-off-by: Lorenzo Stoakes (Oracle) <ljs@kernel.org>
Acked-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Alexandre Torgue <alexandre.torgue@foss.st.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Bodo Stroesser <bostroesser@gmail.com>
Cc: Christian Brauner <brauner@kernel.org>
Cc: Clemens Ladisch <clemens@ladisch.de>
Cc: David Hildenbrand <david@kernel.org>
Cc: David Howells <dhowells@redhat.com>
Cc: Dexuan Cui <decui@microsoft.com>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Haiyang Zhang <haiyangz@microsoft.com>
Cc: Jan Kara <jack@suse.cz>
Cc: Jann Horn <jannh@google.com>
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: K. Y. Srinivasan <kys@microsoft.com>
Cc: Liam Howlett <liam.howlett@oracle.com>
Cc: Long Li <longli@microsoft.com>
Cc: Marc Dionne <marc.dionne@auristor.com>
Cc: "Martin K. Petersen" <martin.petersen@oracle.com>
Cc: Maxime Coquelin <mcoquelin.stm32@gmail.com>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Mike Rapoport <rppt@kernel.org>
Cc: Miquel Raynal <miquel.raynal@bootlin.com>
Cc: Pedro Falcato <pfalcato@suse.de>
Cc: Richard Weinberger <richard@nod.at>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Suren Baghdasaryan <surenb@google.com>
Cc: Vignesh Raghavendra <vigneshr@ti.com>
Cc: Wei Liu <wei.liu@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
include/linux/fs.h
include/linux/mm_types.h
mm/util.c
mm/vma.h
tools/testing/vma/include/dup.h

index c390f5c667e3eae601ff3758ea65e103968db51d..0bdccfa70b44c05a400159579ddd31ddf482cb52 100644 (file)
@@ -2058,6 +2058,9 @@ static inline bool can_mmap_file(struct file *file)
        return true;
 }
 
+void compat_set_desc_from_vma(struct vm_area_desc *desc, const struct file *file,
+                             const struct vm_area_struct *vma);
+int __compat_vma_mmap(struct vm_area_desc *desc, struct vm_area_struct *vma);
 int compat_vma_mmap(struct file *file, struct vm_area_struct *vma);
 int __vma_check_mmap_hook(struct vm_area_struct *vma);
 
index 91a3db174d78c7c079f65e4c820de5090cbacded..b702c63bf0e09767fe94654f7d73e9ad808f5826 100644 (file)
@@ -891,8 +891,8 @@ static __always_inline bool vma_flags_empty(const vma_flags_t *flags)
  */
 struct vm_area_desc {
        /* Immutable state. */
-       const struct mm_struct *const mm;
-       struct file *const file; /* May vary from vm_file in stacked callers. */
+       struct mm_struct *mm;
+       struct file *file; /* May vary from vm_file in stacked callers. */
        unsigned long start;
        unsigned long end;
 
index 9a27d33273fd5ed3a353adab2795cef375de4310..5ae20876ef2c947c9e1a26299d724345084b6069 100644 (file)
--- a/mm/util.c
+++ b/mm/util.c
@@ -1163,38 +1163,78 @@ void flush_dcache_folio(struct folio *folio)
 EXPORT_SYMBOL(flush_dcache_folio);
 #endif
 
-static int __compat_vma_mmap(struct file *file, struct vm_area_struct *vma)
-{
-       struct vm_area_desc desc = {
-               .mm = vma->vm_mm,
-               .file = file,
-               .start = vma->vm_start,
-               .end = vma->vm_end,
-
-               .pgoff = vma->vm_pgoff,
-               .vm_file = vma->vm_file,
-               .vma_flags = vma->flags,
-               .page_prot = vma->vm_page_prot,
-
-               .action.type = MMAP_NOTHING, /* Default */
-       };
-       struct mmap_action *action = &desc.action;
-       int err;
+/**
+ * compat_set_desc_from_vma() - assigns VMA descriptor @desc fields from a VMA.
+ * @desc: A VMA descriptor whose fields need to be set.
+ * @file: The file object describing the file being mmap()'d.
+ * @vma: The VMA whose fields we wish to assign to @desc.
+ *
+ * This is a compatibility function to allow an mmap() hook to call
+ * mmap_prepare() hooks when drivers nest these. This function specifically
+ * allows the construction of a vm_area_desc value, @desc, from a VMA @vma for
+ * the purposes of doing this.
+ *
+ * Once the conversion of drivers is complete this function will no longer be
+ * required and will be removed.
+ */
+void compat_set_desc_from_vma(struct vm_area_desc *desc,
+                             const struct file *file,
+                             const struct vm_area_struct *vma)
+{
+       memset(desc, 0, sizeof(*desc));
 
-       err = vfs_mmap_prepare(file, &desc);
-       if (err)
-               return err;
+       desc->mm = vma->vm_mm;
+       desc->file = (struct file *)file;
+       desc->start = vma->vm_start;
+       desc->end = vma->vm_end;
 
-       err = mmap_action_prepare(&desc);
-       if (err)
-               return err;
+       desc->pgoff = vma->vm_pgoff;
+       desc->vm_file = vma->vm_file;
+       desc->vma_flags = vma->flags;
+       desc->page_prot = vma->vm_page_prot;
 
-       /* being invoked from .mmap means we don't have to enforce this. */
-       action->hide_from_rmap_until_complete = false;
+       /* Default. */
+       desc->action.type = MMAP_NOTHING;
+}
+EXPORT_SYMBOL(compat_set_desc_from_vma);
+
+/**
+ * __compat_vma_mmap() - Similar to compat_vma_mmap(), only it allows
+ * flexibility as to how the mmap_prepare callback is invoked, which is useful
+ * for drivers which invoke nested mmap_prepare callbacks in an mmap() hook.
+ * @desc: A VMA descriptor upon which an mmap_prepare() hook has already been
+ * executed.
+ * @vma: The VMA to which @desc should be applied.
+ *
+ * The function assumes that you have obtained a VMA descriptor @desc from
+ * compat_set_desc_from_vma(), and already executed the mmap_prepare() hook upon
+ * it.
+ *
+ * It then performs any specified mmap actions, and invokes the vm_ops->mapped()
+ * hook if one is present.
+ *
+ * See the description of compat_vma_mmap() for more details.
+ *
+ * Once the conversion of drivers is complete this function will no longer be
+ * required and will be removed.
+ *
+ * Returns: 0 on success or error.
+ */
+int __compat_vma_mmap(struct vm_area_desc *desc,
+                     struct vm_area_struct *vma)
+{
+       int err;
 
-       set_vma_from_desc(vma, &desc);
-       return mmap_action_complete(vma, action);
+       /* Perform any preparatory tasks for mmap action. */
+       err = mmap_action_prepare(desc);
+       if (err)
+               return err;
+       /* Update the VMA from the descriptor. */
+       compat_set_vma_from_desc(vma, desc);
+       /* Complete any specified mmap actions. */
+       return mmap_action_complete(vma, &desc->action);
 }
+EXPORT_SYMBOL(__compat_vma_mmap);
 
 /**
  * compat_vma_mmap() - Apply the file's .mmap_prepare() hook to an
@@ -1203,10 +1243,10 @@ static int __compat_vma_mmap(struct file *file, struct vm_area_struct *vma)
  * @vma: The VMA to apply the .mmap_prepare() hook to.
  *
  * Ordinarily, .mmap_prepare() is invoked directly upon mmap(). However, certain
- * stacked filesystems invoke a nested mmap hook of an underlying file.
+ * stacked drivers invoke a nested mmap hook of an underlying file.
  *
- * Until all filesystems are converted to use .mmap_prepare(), we must be
- * conservative and continue to invoke these stacked filesystems using the
+ * Until all drivers are converted to use .mmap_prepare(), we must be
+ * conservative and continue to invoke these stacked drivers using the
  * deprecated .mmap() hook.
  *
  * However we have a problem if the underlying file system possesses an
@@ -1217,14 +1257,27 @@ static int __compat_vma_mmap(struct file *file, struct vm_area_struct *vma)
  * establishes a struct vm_area_desc descriptor, passes to the underlying
  * .mmap_prepare() hook and applies any changes performed by it.
  *
- * Once the conversion of filesystems is complete this function will no longer
- * be required and will be removed.
+ * Once the conversion of drivers is complete this function will no longer be
+ * required and will be removed.
  *
  * Returns: 0 on success or error.
  */
 int compat_vma_mmap(struct file *file, struct vm_area_struct *vma)
 {
-       return __compat_vma_mmap(file, vma);
+       struct vm_area_desc desc;
+       struct mmap_action *action;
+       int err;
+
+       compat_set_desc_from_vma(&desc, file, vma);
+       err = vfs_mmap_prepare(file, &desc);
+       if (err)
+               return err;
+       action = &desc.action;
+
+       /* being invoked from .mmmap means we don't have to enforce this. */
+       action->hide_from_rmap_until_complete = false;
+
+       return __compat_vma_mmap(&desc, vma);
 }
 EXPORT_SYMBOL(compat_vma_mmap);
 
index 1bfe7e47f6be7581e9bc7fee985609447af0f33e..8e4b61a7304c688e07cde70fca242868a39d57b3 100644 (file)
--- a/mm/vma.h
+++ b/mm/vma.h
@@ -300,7 +300,7 @@ static inline int vma_iter_store_gfp(struct vma_iterator *vmi,
  * f_op->mmap() but which might have an underlying file system which implements
  * f_op->mmap_prepare().
  */
-static inline void set_vma_from_desc(struct vm_area_struct *vma,
+static inline void compat_set_vma_from_desc(struct vm_area_struct *vma,
                struct vm_area_desc *desc)
 {
        /*
index b31207bbe10d3daa4a73ae642b44561eafd62fee..ecd47d0f7d17443928a059a93a0b79358ecd22b7 100644 (file)
@@ -519,8 +519,8 @@ enum vma_operation {
  */
 struct vm_area_desc {
        /* Immutable state. */
-       const struct mm_struct *const mm;
-       struct file *const file; /* May vary from vm_file in stacked callers. */
+       struct mm_struct *mm;
+       struct file *file; /* May vary from vm_file in stacked callers. */
        unsigned long start;
        unsigned long end;
 
@@ -1278,50 +1278,70 @@ static inline void vma_set_anonymous(struct vm_area_struct *vma)
 }
 
 /* Declared in vma.h. */
-static inline void set_vma_from_desc(struct vm_area_struct *vma,
+static inline void compat_set_vma_from_desc(struct vm_area_struct *vma,
                struct vm_area_desc *desc);
 
-static inline int vfs_mmap_prepare(struct file *file, struct vm_area_desc *desc)
+static inline void compat_set_desc_from_vma(struct vm_area_desc *desc,
+                             const struct file *file,
+                             const struct vm_area_struct *vma)
 {
-       return file->f_op->mmap_prepare(desc);
+       memset(desc, 0, sizeof(*desc));
+
+       desc->mm = vma->vm_mm;
+       desc->file = (struct file *)file;
+       desc->start = vma->vm_start;
+       desc->end = vma->vm_end;
+
+       desc->pgoff = vma->vm_pgoff;
+       desc->vm_file = vma->vm_file;
+       desc->vma_flags = vma->flags;
+       desc->page_prot = vma->vm_page_prot;
+
+       /* Default. */
+       desc->action.type = MMAP_NOTHING;
 }
 
-static inline unsigned long vma_pages(struct vm_area_struct *vma)
+static inline unsigned long vma_pages(const struct vm_area_struct *vma)
 {
        return (vma->vm_end - vma->vm_start) >> PAGE_SHIFT;
 }
 
-static inline int compat_vma_mmap(struct file *file, struct vm_area_struct *vma)
+static inline int vfs_mmap_prepare(struct file *file, struct vm_area_desc *desc)
 {
-       struct vm_area_desc desc = {
-               .mm = vma->vm_mm,
-               .file = file,
-               .start = vma->vm_start,
-               .end = vma->vm_end,
-
-               .pgoff = vma->vm_pgoff,
-               .vm_file = vma->vm_file,
-               .vma_flags = vma->flags,
-               .page_prot = vma->vm_page_prot,
+       return file->f_op->mmap_prepare(desc);
+}
 
-               .action.type = MMAP_NOTHING, /* Default */
-       };
-       struct mmap_action *action = &desc.action;
+static inline int __compat_vma_mmap(struct vm_area_desc *desc,
+               struct vm_area_struct *vma)
+{
        int err;
 
-       err = vfs_mmap_prepare(file, &desc);
+       /* Perform any preparatory tasks for mmap action. */
+       err = mmap_action_prepare(desc);
        if (err)
                return err;
+       /* Update the VMA from the descriptor. */
+       compat_set_vma_from_desc(vma, desc);
+       /* Complete any specified mmap actions. */
+       return mmap_action_complete(vma, &desc->action);
+}
 
-       err = mmap_action_prepare(&desc);
+static inline int compat_vma_mmap(struct file *file, struct vm_area_struct *vma)
+{
+       struct vm_area_desc desc;
+       struct mmap_action *action;
+       int err;
+
+       compat_set_desc_from_vma(&desc, file, vma);
+       err = vfs_mmap_prepare(file, &desc);
        if (err)
                return err;
+       action = &desc.action;
 
        /* being invoked from .mmmap means we don't have to enforce this. */
        action->hide_from_rmap_until_complete = false;
 
-       set_vma_from_desc(vma, &desc);
-       return mmap_action_complete(vma, action);
+       return __compat_vma_mmap(&desc, vma);
 }
 
 static inline void vma_iter_init(struct vma_iterator *vmi,