[PATCH RFC v4 09/12] mm/gup: build get_user_page_lookup_vma() on get_user_page_vma()

From: Rik van Riel

Date: Fri Jul 24 2026 - 18:32:36 EST


get_user_page_lookup_vma() faults in one page of a remote mm and returns it
together with the VMA that covers it. It open-codes that with
get_user_pages_remote() followed by vma_lookup(), duplicating the
single-page walk that get_user_page_vma() now provides.

Every caller already holds the mmap lock, and the helper needs it anyway:
get_user_pages_remote(locked=NULL) and vma_lookup() both rely on it.

Look the VMA up under the held mmap lock and fault the page in through
get_user_page_vma() without FOLL_VMA_LOCK, so the lock is held for the
whole call and never dropped. Force FOLL_REMOTE and FOLL_TOUCH to
preserve the foreign-access permission check and accessed-bit behavior
that get_user_pages_remote() applied before.

Add mmap_assert_locked() so the lock requirement is documented and any
future caller that forgets it trips the assertion rather than walking page
tables unlocked.

Reject FOLL_UNLOCKABLE alongside the existing FOLL_NOWAIT check: both let
the fault handler drop the mmap lock, which would invalidate the VMA looked
up here. get_user_page_vma() passes neither, so the lock is held for the
whole walk and the returned VMA stays valid.

The one behavioral change is that the old path could drop and retake the
mmap lock across a fault, while get_user_page_vma() holds it throughout.
None of the callers rely on the lock being dropped.

Assisted-by: Claude:claude-opus-4.8
Signed-off-by: Rik van Riel <riel@xxxxxxxxxxx>
---
include/linux/mm.h | 32 +++-----------------------------
mm/gup.c | 35 +++++++++++++++++++++++++++++++++++
2 files changed, 38 insertions(+), 29 deletions(-)

diff --git a/include/linux/mm.h b/include/linux/mm.h
index 24ead14b4790..0f66d76e6ca7 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -3235,35 +3235,9 @@ long pin_user_pages_remote(struct mm_struct *mm,
unsigned int gup_flags, struct page **pages,
int *locked);

-/*
- * Retrieves a single page alongside its VMA. Does not support FOLL_NOWAIT.
- */
-static inline struct page *get_user_page_lookup_vma(struct mm_struct *mm,
- unsigned long addr,
- int gup_flags,
- struct vm_area_struct **vmap)
-{
- struct page *page;
- struct vm_area_struct *vma;
- int got;
-
- if (WARN_ON_ONCE(unlikely(gup_flags & FOLL_NOWAIT)))
- return ERR_PTR(-EINVAL);
-
- got = get_user_pages_remote(mm, addr, 1, gup_flags, &page, NULL);
-
- if (got < 0)
- return ERR_PTR(got);
-
- vma = vma_lookup(mm, addr);
- if (WARN_ON_ONCE(!vma)) {
- put_page(page);
- return ERR_PTR(-EINVAL);
- }
-
- *vmap = vma;
- return page;
-}
+struct page *get_user_page_lookup_vma(struct mm_struct *mm, unsigned long addr,
+ int gup_flags,
+ struct vm_area_struct **vmap);

long get_user_pages(unsigned long start, unsigned long nr_pages,
unsigned int gup_flags, struct page **pages);
diff --git a/mm/gup.c b/mm/gup.c
index cb7245b7542f..7f4107f7ff10 100644
--- a/mm/gup.c
+++ b/mm/gup.c
@@ -1288,6 +1288,41 @@ struct page *get_user_page_vma(struct vm_area_struct *vma, unsigned long addr,
return ERR_PTR(ret);
}

+/*
+ * get_user_page_lookup_vma - fault in one page of a remote mm and hand back the
+ * page along with the VMA that covers it. The caller must hold the mmap_lock.
+ * Returns with the mmap_lock still held.
+ *
+ * Looks up the VMA, and gets a reference to the page through
+ * get_user_page_vma(), faulting in the page if needed.
+ *
+ * FOLL_NOWAIT and FOLL_UNLOCKABLE are rejected: both let the fault handler
+ * drop the mmap lock, which could invalidate the looked-up VMA.
+ */
+struct page *get_user_page_lookup_vma(struct mm_struct *mm, unsigned long addr,
+ int gup_flags,
+ struct vm_area_struct **vmap)
+{
+ struct vm_area_struct *vma;
+ struct page *page;
+
+ if (WARN_ON_ONCE(unlikely(gup_flags & (FOLL_NOWAIT | FOLL_UNLOCKABLE))))
+ return ERR_PTR(-EINVAL);
+
+ mmap_assert_locked(mm);
+
+ vma = vma_lookup(mm, addr);
+ if (!vma)
+ return ERR_PTR(-EFAULT);
+
+ page = get_user_page_vma(vma, addr, gup_flags | FOLL_REMOTE | FOLL_TOUCH);
+ if (IS_ERR(page))
+ return page;
+
+ *vmap = vma;
+ return page;
+}
+
/*
* Writing to file-backed mappings which require folio dirty tracking using GUP
* is a fundamentally broken operation, as kernel write access to GUP mappings
--
2.53.0-Meta