| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/12 16:55 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": [
"TRANSPARENT_HUGEPAGE",
"HUGETLBFS"
],
"FocusSymbols": [
"get_user_pages",
"get_user_pages_remote",
"get_user_pages_unlocked",
"pin_user_pages",
"pin_user_pages_remote",
"pin_user_pages_unlocked",
"faultin_page_range",
"populate_vma_page_range"
],
"KMSANReasoning": "The patch refactors Get User Pages (GUP) to batch page table walking and refcounting. It modifies core memory management routines but does not introduce new data structures copied to userspace, nor does it handle complex parsing where uninitialized memory could be leaked. Any bugs introduced by these changes (such as returning an incorrect number of processed pages, leading to uninitialized pointers in the `pages` array) would result in wild pointer dereferences, out-of-bounds accesses, or page reference imbalances. These types of errors are effectively caught by KASAN and standard kernel debugging tools. Therefore, a dedicated KMSAN session is not necessary.",
"NeedsKMSAN": false,
"Reasoning": "The patch refactors the core Get User Pages (GUP) logic in `mm/gup.c` to batch page table walks and return multiple pages at once instead of one by one. This is a significant functional change to core memory management code that is reachable via many syscalls (e.g., mmap, madvise, ptrace, process_vm_readv). Fuzzing should focus on the exported GUP API entry points.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/12 16:55 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 67e676e8b038e01ccdc6278576936d60d9b7ab84\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Wed Aug 12 16:55:11 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/mm/gup.c b/mm/gup.c\nindex 0692119b79043..106806634b3c4 100644\n--- a/mm/gup.c\n+++ b/mm/gup.c\n@@ -608,15 +608,15 @@ static inline bool can_follow_write_common(struct page *page,\n \treturn page \u0026\u0026 PageAnon(page) \u0026\u0026 PageAnonExclusive(page);\n }\n \n-static struct page *no_page_table(struct vm_area_struct *vma,\n-\t\t\t\t unsigned int flags, unsigned long address)\n+static long no_page_table(struct vm_area_struct *vma,\n+\t\tunsigned int flags, unsigned long address)\n {\n \tif (!(flags \u0026 FOLL_DUMP))\n-\t\treturn NULL;\n+\t\treturn 0;\n \n \t/*\n \t * When core dumping, we don't want to allocate unnecessary pages or\n-\t * page tables. Return error instead of NULL to skip handle_mm_fault,\n+\t * page tables. Return error instead of 0 to skip handle_mm_fault,\n \t * then get_dump_page() will return NULL to leave a hole in the dump.\n \t * But we can only make this optimization where a hole would surely\n \t * be zero-filled if handle_mm_fault() actually did handle it.\n@@ -625,12 +625,29 @@ static struct page *no_page_table(struct vm_area_struct *vma,\n \t\tstruct hstate *h = hstate_vma(vma);\n \n \t\tif (!hugetlbfs_pagecache_present(h, vma, address))\n-\t\t\treturn ERR_PTR(-EFAULT);\n+\t\t\treturn -EFAULT;\n \t} else if ((vma_is_anonymous(vma) || !vma-\u003evm_ops-\u003efault)) {\n-\t\treturn ERR_PTR(-EFAULT);\n+\t\treturn -EFAULT;\n \t}\n \n-\treturn NULL;\n+\treturn 0;\n+}\n+\n+static void gup_fill_pages(struct vm_area_struct *vma, unsigned long address,\n+\t\tstruct page *page, unsigned long nr, struct page **pages)\n+{\n+\tunsigned long i;\n+\n+\tif (!pages)\n+\t\treturn;\n+\n+\tfor (i = 0; i \u003c nr; i++) {\n+\t\tstruct page *subpage = page + i;\n+\n+\t\tpages[i] = subpage;\n+\t\tflush_anon_page(vma, subpage, address + i * PAGE_SIZE);\n+\t\tflush_dcache_page(subpage);\n+\t}\n }\n \n #ifdef CONFIG_PGTABLE_HAS_HUGE_LEAVES\n@@ -646,38 +663,43 @@ static inline bool can_follow_write_pud(pud_t pud, struct page *page,\n \treturn can_follow_write_common(page, vma, flags);\n }\n \n-static struct page *follow_huge_pud(struct vm_area_struct *vma,\n-\t\t\t\t unsigned long addr, pud_t *pudp,\n-\t\t\t\t int flags, unsigned long *page_mask)\n+static long follow_huge_pud(struct vm_area_struct *vma,\n+\t\tunsigned long addr, unsigned long end, pud_t *pudp,\n+\t\tunsigned int flags, struct page **pages)\n {\n \tstruct mm_struct *mm = vma-\u003evm_mm;\n \tstruct page *page;\n \tpud_t pud = *pudp;\n \tunsigned long pfn = pud_pfn(pud);\n+\tunsigned long off, nr;\n \tint ret;\n \n \tassert_spin_locked(pud_lockptr(mm, pudp));\n \n \tif (!pud_present(pud))\n-\t\treturn NULL;\n+\t\treturn 0;\n \n \tif ((flags \u0026 FOLL_WRITE) \u0026\u0026\n \t !can_follow_write_pud(pud, pfn_to_page(pfn), vma, flags))\n-\t\treturn NULL;\n+\t\treturn 0;\n \n-\tpfn += (addr \u0026 ~PUD_MASK) \u003e\u003e PAGE_SHIFT;\n+\toff = PFN_DOWN(addr \u0026 ~PUD_MASK);\n+\tpfn += off;\n \tpage = pfn_to_page(pfn);\n \n \tif (!pud_write(pud) \u0026\u0026 gup_must_unshare(vma, flags, page))\n-\t\treturn ERR_PTR(-EMLINK);\n+\t\treturn -EMLINK;\n+\n+\tnr = min(HPAGE_PUD_NR - off, PFN_DOWN(end - addr));\n \n-\tret = try_grab_folio(page_folio(page), 1, flags);\n+\tret = try_grab_folio(page_folio(page), nr, flags);\n \tif (ret)\n-\t\tpage = ERR_PTR(ret);\n-\telse\n-\t\t*page_mask = HPAGE_PUD_NR - 1;\n+\t\treturn ret;\n+\n+\tif (pages)\n+\t\tpages[0] = page;\n \n-\treturn page;\n+\treturn nr;\n }\n \n /* FOLL_FORCE can write to even unwritable PMDs in COW mappings. */\n@@ -698,14 +720,14 @@ static inline bool can_follow_write_pmd(pmd_t pmd, struct page *page,\n \treturn !userfaultfd_huge_pmd_wp(vma, pmd);\n }\n \n-static struct page *follow_huge_pmd(struct vm_area_struct *vma,\n-\t\t\t\t unsigned long addr, pmd_t *pmd,\n-\t\t\t\t unsigned int flags,\n-\t\t\t\t unsigned long *page_mask)\n+static long follow_huge_pmd(struct vm_area_struct *vma,\n+\t\tunsigned long addr, unsigned long end, pmd_t *pmd,\n+\t\tunsigned int flags, struct page **pages)\n {\n \tstruct mm_struct *mm = vma-\u003evm_mm;\n \tpmd_t pmdval = *pmd;\n \tstruct page *page;\n+\tunsigned long off, nr;\n \tint ret;\n \n \tassert_spin_locked(pmd_lockptr(mm, pmd));\n@@ -713,50 +735,54 @@ static struct page *follow_huge_pmd(struct vm_area_struct *vma,\n \tpage = pmd_page(pmdval);\n \tif ((flags \u0026 FOLL_WRITE) \u0026\u0026\n \t !can_follow_write_pmd(pmdval, page, vma, flags))\n-\t\treturn NULL;\n+\t\treturn 0;\n \n \t/* Avoid dumping huge zero page */\n \tif ((flags \u0026 FOLL_DUMP) \u0026\u0026 is_huge_zero_pmd(pmdval))\n-\t\treturn ERR_PTR(-EFAULT);\n+\t\treturn -EFAULT;\n \n \tif (pmd_protnone(*pmd) \u0026\u0026 !gup_can_follow_protnone(vma, flags))\n-\t\treturn NULL;\n+\t\treturn 0;\n \n \tif (!pmd_write(pmdval) \u0026\u0026 gup_must_unshare(vma, flags, page))\n-\t\treturn ERR_PTR(-EMLINK);\n+\t\treturn -EMLINK;\n \n \tVM_WARN_ON_ONCE_PAGE((flags \u0026 FOLL_PIN) \u0026\u0026 PageAnon(page) \u0026\u0026\n \t\t\t !PageAnonExclusive(page), page);\n \n-\tret = try_grab_folio(page_folio(page), 1, flags);\n+\toff = PFN_DOWN(addr \u0026 ~HPAGE_PMD_MASK);\n+\tnr = min(HPAGE_PMD_NR - off, PFN_DOWN(end - addr));\n+\n+\tret = try_grab_folio(page_folio(page), nr, flags);\n \tif (ret)\n-\t\treturn ERR_PTR(ret);\n+\t\treturn ret;\n \n #ifdef CONFIG_TRANSPARENT_HUGEPAGE\n \tif (pmd_trans_huge(pmdval) \u0026\u0026 (flags \u0026 FOLL_TOUCH))\n \t\ttouch_pmd(vma, addr, pmd, flags \u0026 FOLL_WRITE);\n #endif\t/* CONFIG_TRANSPARENT_HUGEPAGE */\n \n-\tpage += (addr \u0026 ~HPAGE_PMD_MASK) \u003e\u003e PAGE_SHIFT;\n-\t*page_mask = HPAGE_PMD_NR - 1;\n+\tpage += off;\n+\n+\tif (pages)\n+\t\tpages[0] = page;\n \n-\treturn page;\n+\treturn nr;\n }\n \n #else /* CONFIG_PGTABLE_HAS_HUGE_LEAVES */\n-static struct page *follow_huge_pud(struct vm_area_struct *vma,\n-\t\t\t\t unsigned long addr, pud_t *pudp,\n-\t\t\t\t int flags, unsigned long *page_mask)\n+static long follow_huge_pud(struct vm_area_struct *vma,\n+\t\tunsigned long addr, unsigned long end, pud_t *pudp,\n+\t\tunsigned int flags, struct page **pages)\n {\n-\treturn NULL;\n+\treturn 0;\n }\n \n-static struct page *follow_huge_pmd(struct vm_area_struct *vma,\n-\t\t\t\t unsigned long addr, pmd_t *pmd,\n-\t\t\t\t unsigned int flags,\n-\t\t\t\t unsigned long *page_mask)\n+static long follow_huge_pmd(struct vm_area_struct *vma,\n+\t\tunsigned long addr, unsigned long end, pmd_t *pmd,\n+\t\tunsigned int flags, struct page **pages)\n {\n-\treturn NULL;\n+\treturn 0;\n }\n #endif\t/* CONFIG_PGTABLE_HAS_HUGE_LEAVES */\n \n@@ -799,110 +825,205 @@ static inline bool can_follow_write_pte(pte_t pte, struct page *page,\n \treturn !userfaultfd_pte_wp(vma, pte);\n }\n \n-static struct page *follow_page_pte(struct vm_area_struct *vma,\n-\t\tunsigned long address, pmd_t *pmd, unsigned int flags)\n+/*\n+ * The caller has already run every per-PTE safety check (present,\n+ * write-fault, gup_must_unshare()) on the PTE, so this only does the\n+ * per-folio work: the refcount grab, the FOLL_PIN accessibility fault-in,\n+ * dirty/accessed marking, and the array fill with the cache flush.\n+ */\n+static long follow_page_pte_commit(struct vm_area_struct *vma,\n+\t\tunsigned long address, struct folio *folio, struct page *page,\n+\t\tpte_t pte, unsigned long nr, unsigned int flags,\n+\t\tstruct page **pages)\n+{\n+\tlong ret;\n+\n+\t/* try_grab_folio() does nothing unless FOLL_GET or FOLL_PIN is set. */\n+\tret = try_grab_folio(folio, nr, flags);\n+\tif (unlikely(ret))\n+\t\treturn ret;\n+\n+\t/*\n+\t * We need to make the page accessible if and only if we are going\n+\t * to access its content (the FOLL_PIN case). Please see\n+\t * Documentation/core-api/pin_user_pages.rst for details.\n+\t */\n+\tif (flags \u0026 FOLL_PIN) {\n+\t\tret = arch_make_folio_accessible(folio);\n+\t\tif (ret) {\n+\t\t\tgup_put_folio(folio, nr, flags);\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n+\tif (flags \u0026 FOLL_TOUCH) {\n+\t\tif ((flags \u0026 FOLL_WRITE) \u0026\u0026\n+\t\t !pte_dirty(pte) \u0026\u0026 !folio_test_dirty(folio))\n+\t\t\tfolio_mark_dirty(folio);\n+\t\t/*\n+\t\t * pte_mkyoung() would be more correct here, but atomic care\n+\t\t * is needed to avoid losing the dirty bit: it is easier to use\n+\t\t * folio_mark_accessed().\n+\t\t */\n+\t\tfolio_mark_accessed(folio);\n+\t}\n+\n+\tgup_fill_pages(vma, address, page, nr, pages);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Resolve one present PTE to the page it maps. Returns no page and no error\n+ * when the PTE cannot be followed but the caller may fault it in, and a\n+ * negative errno when the caller must report the failure.\n+ */\n+static long follow_one_pte(struct vm_area_struct *vma, unsigned long address,\n+\t\tpte_t *ptep, pte_t pte, unsigned int flags, struct page **pagep)\n {\n-\tstruct mm_struct *mm = vma-\u003evm_mm;\n-\tstruct folio *folio;\n \tstruct page *page;\n-\tspinlock_t *ptl;\n-\tpte_t *ptep, pte;\n-\tint ret;\n \n-\tptep = pte_offset_map_lock(mm, pmd, address, \u0026ptl);\n-\tif (!ptep)\n-\t\treturn no_page_table(vma, flags, address);\n-\tpte = ptep_get(ptep);\n+\t*pagep = NULL;\n+\n \tif (!pte_present(pte))\n-\t\tgoto no_page;\n+\t\treturn 0;\n \tif (pte_protnone(pte) \u0026\u0026 !gup_can_follow_protnone(vma, flags))\n-\t\tgoto no_page;\n+\t\treturn 0;\n \n \tpage = vm_normal_page(vma, address, pte);\n \n \t/*\n \t * We only care about anon pages in can_follow_write_pte().\n \t */\n-\tif ((flags \u0026 FOLL_WRITE) \u0026\u0026\n-\t !can_follow_write_pte(pte, page, vma, flags)) {\n-\t\tpage = NULL;\n-\t\tgoto out;\n-\t}\n+\tif ((flags \u0026 FOLL_WRITE) \u0026\u0026 !can_follow_write_pte(pte, page, vma, flags))\n+\t\treturn 0;\n \n \tif (unlikely(!page)) {\n \t\tif (flags \u0026 FOLL_DUMP) {\n \t\t\t/* Avoid special (like zero) pages in core dumps */\n-\t\t\tpage = ERR_PTR(-EFAULT);\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tif (is_zero_pfn(pte_pfn(pte))) {\n-\t\t\tpage = pte_page(pte);\n-\t\t} else {\n-\t\t\tret = follow_pfn_pte(vma, address, ptep, flags);\n-\t\t\tpage = ERR_PTR(ret);\n-\t\t\tgoto out;\n+\t\t\treturn -EFAULT;\n \t\t}\n+\t\tif (!is_zero_pfn(pte_pfn(pte)))\n+\t\t\treturn follow_pfn_pte(vma, address, ptep, flags);\n+\t\tpage = pte_page(pte);\n \t}\n-\tfolio = page_folio(page);\n \n-\tif (!pte_write(pte) \u0026\u0026 gup_must_unshare(vma, flags, page)) {\n-\t\tpage = ERR_PTR(-EMLINK);\n-\t\tgoto out;\n-\t}\n+\tif (!pte_write(pte) \u0026\u0026 gup_must_unshare(vma, flags, page))\n+\t\treturn -EMLINK;\n \n \tVM_WARN_ON_ONCE_PAGE((flags \u0026 FOLL_PIN) \u0026\u0026 PageAnon(page) \u0026\u0026\n \t\t\t !PageAnonExclusive(page), page);\n \n-\t/* try_grab_folio() does nothing unless FOLL_GET or FOLL_PIN is set. */\n-\tret = try_grab_folio(folio, 1, flags);\n-\tif (unlikely(ret)) {\n-\t\tpage = ERR_PTR(ret);\n-\t\tgoto out;\n-\t}\n+\t*pagep = page;\n+\treturn 0;\n+}\n \n-\t/*\n-\t * We need to make the page accessible if and only if we are going\n-\t * to access its content (the FOLL_PIN case). Please see\n-\t * Documentation/core-api/pin_user_pages.rst for details.\n-\t */\n-\tif (flags \u0026 FOLL_PIN) {\n-\t\tret = arch_make_folio_accessible(folio);\n-\t\tif (ret) {\n-\t\t\tunpin_user_page(page);\n-\t\t\tpage = ERR_PTR(ret);\n-\t\t\tgoto out;\n+/*\n+ * Return how many PTEs map consecutive pages of the same folio and can be\n+ * committed as one run. Always at least 1.\n+ *\n+ * The write-fault and unshare checks in follow_one_pte() are per PTE, but a\n+ * writable run needs no repeat: a writable anon page is exclusive. A read-only\n+ * run under FOLL_WRITE or FOLL_PIN does need the per-page check, so it stays\n+ * one page at a time.\n+ */\n+static unsigned long follow_pte_batch(struct vm_area_struct *vma,\n+\t\tunsigned long address, unsigned long walk_end,\n+\t\tstruct folio *folio, pte_t *ptep, pte_t *batch_pte, unsigned int flags)\n+{\n+\tunsigned long max;\n+\n+\tif (!folio_test_large(folio))\n+\t\treturn 1;\n+\tif (!pte_write(*batch_pte) \u0026\u0026 (flags \u0026 (FOLL_WRITE | FOLL_PIN)))\n+\t\treturn 1;\n+\n+\tmax = (walk_end - address) \u003e\u003e PAGE_SHIFT;\n+\tif (max \u003c= 1)\n+\t\treturn 1;\n+\n+\t/* Merge young/dirty across batch so folio_mark_dirty sees any dirty. */\n+\treturn folio_pte_batch_flags(folio, vma, ptep, batch_pte, max,\n+\t\t\t\t FPB_RESPECT_WRITE | FPB_MERGE_YOUNG_DIRTY);\n+}\n+\n+/*\n+ * Walk the PTEs from the start address to the end of this page table or VMA,\n+ * whichever comes first, and commit every page found.\n+ *\n+ * A failure on the first PTE is returned to the caller. A failure after that\n+ * is a short read; __get_user_pages() retrying the read will get the error.\n+ */\n+static long follow_page_pte(struct vm_area_struct *vma,\n+\t\tunsigned long address, unsigned long end, pmd_t *pmd,\n+\t\tunsigned int flags, struct page **pages)\n+{\n+\tstruct mm_struct *mm = vma-\u003evm_mm;\n+\tbool need_no_page_table = false;\n+\tpte_t *ptep, *orig_ptep;\n+\tunsigned long walk_end;\n+\tunsigned long nr = 0;\n+\tspinlock_t *ptl;\n+\tlong ret = 0;\n+\n+\torig_ptep = ptep = pte_offset_map_lock(mm, pmd, address, \u0026ptl);\n+\tif (!ptep)\n+\t\treturn no_page_table(vma, flags, address);\n+\n+\twalk_end = min(pmd_addr_end(address, end), vma-\u003evm_end);\n+\n+\tfor (; address \u003c walk_end; address += PAGE_SIZE, ptep++) {\n+\t\tpte_t pte = ptep_get(ptep);\n+\t\tstruct page *page;\n+\n+\t\tret = follow_one_pte(vma, address, ptep, pte, flags, \u0026page);\n+\t\tif (!ret \u0026\u0026 page) {\n+\t\t\tstruct folio *folio = page_folio(page);\n+\t\t\tunsigned long batch;\n+\n+\t\t\tpte_t batch_pte = pte;\n+\n+\t\t\tbatch = follow_pte_batch(vma, address, walk_end, folio,\n+\t\t\t\t\t\t ptep, \u0026batch_pte, flags);\n+\t\t\tret = follow_page_pte_commit(vma, address, folio, page,\n+\t\t\t\t\t\t batch_pte, batch, flags,\n+\t\t\t\t\t\t pages ? pages + nr : NULL);\n+\t\t\tif (!ret) {\n+\t\t\t\tnr += batch;\n+\t\t\t\t/*\n+\t\t\t\t * The loop's own increment covers one PTE; skip\n+\t\t\t\t * the rest of the batch.\n+\t\t\t\t */\n+\t\t\t\tptep += batch - 1;\n+\t\t\t\taddress += (batch - 1) * PAGE_SIZE;\n+\t\t\t\tcontinue;\n+\t\t\t}\n \t\t}\n-\t}\n-\tif (flags \u0026 FOLL_TOUCH) {\n-\t\tif ((flags \u0026 FOLL_WRITE) \u0026\u0026\n-\t\t !pte_dirty(pte) \u0026\u0026 !folio_test_dirty(folio))\n-\t\t\tfolio_mark_dirty(folio);\n+\n \t\t/*\n-\t\t * pte_mkyoung() would be more correct here, but atomic care\n-\t\t * is needed to avoid losing the dirty bit: it is easier to use\n-\t\t * folio_mark_accessed().\n+\t\t * no_page_table() may look up the page cache, so it cannot run\n+\t\t * under the PTE lock.\n \t\t */\n-\t\tfolio_mark_accessed(folio);\n+\t\tif (!ret \u0026\u0026 pte_none(pte))\n+\t\t\tneed_no_page_table = true;\n+\t\tbreak;\n \t}\n-out:\n-\tpte_unmap_unlock(ptep, ptl);\n-\treturn page;\n-no_page:\n-\tpte_unmap_unlock(ptep, ptl);\n-\tif (!pte_none(pte))\n-\t\treturn NULL;\n-\treturn no_page_table(vma, flags, address);\n+\n+\tpte_unmap_unlock(orig_ptep, ptl);\n+\n+\tif (nr)\n+\t\treturn nr;\n+\tif (need_no_page_table)\n+\t\treturn no_page_table(vma, flags, address);\n+\treturn ret;\n }\n \n-static struct page *follow_pmd_mask(struct vm_area_struct *vma,\n-\t\t\t\t unsigned long address, pud_t *pudp,\n-\t\t\t\t unsigned int flags,\n-\t\t\t\t unsigned long *page_mask)\n+static long follow_pmd_mask(struct vm_area_struct *vma,\n+\t\tunsigned long address, unsigned long end, pud_t *pudp,\n+\t\tunsigned int flags, struct page **pages)\n {\n \tpmd_t *pmd, pmdval;\n \tspinlock_t *ptl;\n-\tstruct page *page;\n+\tlong ret;\n \tstruct mm_struct *mm = vma-\u003evm_mm;\n \n \tpmd = pmd_offset(pudp, address);\n@@ -912,7 +1033,7 @@ static struct page *follow_pmd_mask(struct vm_area_struct *vma,\n \tif (!pmd_present(pmdval))\n \t\treturn no_page_table(vma, flags, address);\n \tif (likely(!pmd_leaf(pmdval)))\n-\t\treturn follow_page_pte(vma, address, pmd, flags);\n+\t\treturn follow_page_pte(vma, address, end, pmd, flags, pages);\n \n \tif (pmd_protnone(pmdval) \u0026\u0026 !gup_can_follow_protnone(vma, flags))\n \t\treturn no_page_table(vma, flags, address);\n@@ -925,28 +1046,35 @@ static struct page *follow_pmd_mask(struct vm_area_struct *vma,\n \t}\n \tif (unlikely(!pmd_leaf(pmdval))) {\n \t\tspin_unlock(ptl);\n-\t\treturn follow_page_pte(vma, address, pmd, flags);\n+\t\treturn follow_page_pte(vma, address, end, pmd, flags, pages);\n \t}\n \tif (pmd_trans_huge(pmdval) \u0026\u0026 (flags \u0026 FOLL_SPLIT_PMD)) {\n \t\tspin_unlock(ptl);\n \t\tsplit_huge_pmd(vma, pmd, address);\n \t\t/* If pmd was left empty, stuff a page table in there quickly */\n-\t\treturn pte_alloc(mm, pmd) ? ERR_PTR(-ENOMEM) :\n-\t\t\tfollow_page_pte(vma, address, pmd, flags);\n+\t\treturn pte_alloc(mm, pmd) ? -ENOMEM :\n+\t\t\tfollow_page_pte(vma, address, end, pmd, flags, pages);\n \t}\n-\tpage = follow_huge_pmd(vma, address, pmd, flags, page_mask);\n+\tret = follow_huge_pmd(vma, address, end, pmd, flags, pages);\n \tspin_unlock(ptl);\n-\treturn page;\n+\n+\t/*\n+\t * The ref is already held, so the page cannot go away: fill the\n+\t * array and flush caches without the pmd lock.\n+\t */\n+\tif (ret \u003e 0 \u0026\u0026 pages)\n+\t\tgup_fill_pages(vma, address, pages[0], ret, pages);\n+\n+\treturn ret;\n }\n \n-static struct page *follow_pud_mask(struct vm_area_struct *vma,\n-\t\t\t\t unsigned long address, p4d_t *p4dp,\n-\t\t\t\t unsigned int flags,\n-\t\t\t\t unsigned long *page_mask)\n+static long follow_pud_mask(struct vm_area_struct *vma,\n+\t\tunsigned long address, unsigned long end, p4d_t *p4dp,\n+\t\tunsigned int flags, struct page **pages)\n {\n \tpud_t *pudp, pud;\n \tspinlock_t *ptl;\n-\tstruct page *page;\n+\tlong ret;\n \tstruct mm_struct *mm = vma-\u003evm_mm;\n \n \tpudp = pud_offset(p4dp, address);\n@@ -955,22 +1083,29 @@ static struct page *follow_pud_mask(struct vm_area_struct *vma,\n \t\treturn no_page_table(vma, flags, address);\n \tif (pud_leaf(pud)) {\n \t\tptl = pud_lock(mm, pudp);\n-\t\tpage = follow_huge_pud(vma, address, pudp, flags, page_mask);\n+\t\tret = follow_huge_pud(vma, address, end, pudp, flags, pages);\n \t\tspin_unlock(ptl);\n-\t\tif (page)\n-\t\t\treturn page;\n+\t\t/*\n+\t\t * The ref is already held, so the page cannot go away: fill\n+\t\t * the array and flush caches without the lock. A 1 GB folio\n+\t\t * can be up to HPAGE_PUD_NR pages, too long to flush under a\n+\t\t * spinlock.\n+\t\t */\n+\t\tif (ret \u003e 0 \u0026\u0026 pages)\n+\t\t\tgup_fill_pages(vma, address, pages[0], ret, pages);\n+\t\tif (ret)\n+\t\t\treturn ret;\n \t\treturn no_page_table(vma, flags, address);\n \t}\n \tif (unlikely(pud_bad(pud)))\n \t\treturn no_page_table(vma, flags, address);\n \n-\treturn follow_pmd_mask(vma, address, pudp, flags, page_mask);\n+\treturn follow_pmd_mask(vma, address, end, pudp, flags, pages);\n }\n \n-static struct page *follow_p4d_mask(struct vm_area_struct *vma,\n-\t\t\t\t unsigned long address, pgd_t *pgdp,\n-\t\t\t\t unsigned int flags,\n-\t\t\t\t unsigned long *page_mask)\n+static long follow_p4d_mask(struct vm_area_struct *vma,\n+\t\tunsigned long address, unsigned long end, pgd_t *pgdp,\n+\t\tunsigned int flags, struct page **pages)\n {\n \tp4d_t *p4dp, p4d;\n \n@@ -981,15 +1116,18 @@ static struct page *follow_p4d_mask(struct vm_area_struct *vma,\n \tif (!p4d_present(p4d) || p4d_bad(p4d))\n \t\treturn no_page_table(vma, flags, address);\n \n-\treturn follow_pud_mask(vma, address, p4dp, flags, page_mask);\n+\treturn follow_pud_mask(vma, address, end, p4dp, flags, pages);\n }\n \n /**\n- * follow_page_mask - look up a page descriptor from a user-virtual address\n+ * follow_page_mask - look up pages at a user-virtual address\n * @vma: vm_area_struct mapping @address\n * @address: virtual address to look up\n+ * @end: virtual address at which to stop batching contiguous pages\n * @flags: flags modifying lookup behaviour\n- * @page_mask: a pointer to output page_mask\n+ * @pages: array to receive the pages, refcounted per @flags, or NULL to\n+ * walk the page tables (e.g. to fault pages in) without collecting\n+ * or refcounting them\n *\n * @flags can have FOLL_ flags set, defined in \u003clinux/mm.h\u003e\n *\n@@ -998,33 +1136,32 @@ static struct page *follow_p4d_mask(struct vm_area_struct *vma,\n * trigger a fault with FAULT_FLAG_UNSHARE set. Note that unsharing is only\n * relevant with FOLL_PIN and !FOLL_WRITE.\n *\n- * On output, @page_mask is set according to the size of the page.\n- *\n- * Return: the mapped (struct page *), %NULL if no mapping exists, or\n- * an error pointer if there is a mapping to something not represented\n- * by a page descriptor (see also vm_normal_page()).\n+ * Return: the number of contiguous pages starting at @address that were\n+ * placed into @pages (if non-NULL), which may be fewer than the pages\n+ * requested via @end; 0 if no mapping exists at @address; or a negative\n+ * errno for a mapping to something not represented by a page descriptor\n+ * (see also vm_normal_page()).\n */\n-static struct page *follow_page_mask(struct vm_area_struct *vma,\n-\t\t\t unsigned long address, unsigned int flags,\n-\t\t\t unsigned long *page_mask)\n+static long follow_page_mask(struct vm_area_struct *vma,\n+\t\tunsigned long address, unsigned long end,\n+\t\tunsigned int flags, struct page **pages)\n {\n \tpgd_t *pgd;\n \tstruct mm_struct *mm = vma-\u003evm_mm;\n-\tstruct page *page;\n+\tlong ret;\n \n \tvma_pgtable_walk_begin(vma);\n \n-\t*page_mask = 0;\n \tpgd = pgd_offset(mm, address);\n \n \tif (pgd_none(*pgd) || unlikely(pgd_bad(*pgd)))\n-\t\tpage = no_page_table(vma, flags, address);\n+\t\tret = no_page_table(vma, flags, address);\n \telse\n-\t\tpage = follow_p4d_mask(vma, address, pgd, flags, page_mask);\n+\t\tret = follow_p4d_mask(vma, address, end, pgd, flags, pages);\n \n \tvma_pgtable_walk_end(vma);\n \n-\treturn page;\n+\treturn ret;\n }\n \n static int get_gate_page(struct mm_struct *mm, unsigned long address,\n@@ -1358,7 +1495,6 @@ static long __get_user_pages(struct mm_struct *mm,\n {\n \tlong ret = 0, i = 0;\n \tstruct vm_area_struct *vma = NULL;\n-\tunsigned long page_mask = 0;\n \n \tif (!nr_pages)\n \t\treturn 0;\n@@ -1373,7 +1509,7 @@ static long __get_user_pages(struct mm_struct *mm,\n \n \tdo {\n \t\tstruct page *page;\n-\t\tunsigned int page_increm;\n+\t\tlong nr;\n \n \t\t/* first iteration or cross vma bound */\n \t\tif (!vma || start \u003e= vma-\u003evm_end) {\n@@ -1400,8 +1536,12 @@ static long __get_user_pages(struct mm_struct *mm,\n \t\t\t\t\t\tpages ? \u0026page : NULL);\n \t\t\t\tif (ret)\n \t\t\t\t\tgoto out;\n-\t\t\t\tpage_mask = 0;\n-\t\t\t\tgoto next_page;\n+\t\t\t\tgup_fill_pages(vma, start, page, 1,\n+\t\t\t\t\t pages ? pages + i : NULL);\n+\t\t\t\ti++;\n+\t\t\t\tstart += PAGE_SIZE;\n+\t\t\t\tnr_pages--;\n+\t\t\t\tcontinue;\n \t\t\t}\n \n \t\t\tif (!vma) {\n@@ -1423,10 +1563,11 @@ static long __get_user_pages(struct mm_struct *mm,\n \t\t}\n \t\tcond_resched();\n \n-\t\tpage = follow_page_mask(vma, start, gup_flags, \u0026page_mask);\n-\t\tif (!page || PTR_ERR(page) == -EMLINK) {\n+\t\tnr = follow_page_mask(vma, start, start + nr_pages * PAGE_SIZE,\n+\t\t\t\t gup_flags, pages ? \u0026pages[i] : NULL);\n+\t\tif (!nr || nr == -EMLINK) {\n \t\t\tret = faultin_page(vma, start, gup_flags,\n-\t\t\t\t\t PTR_ERR(page) == -EMLINK, locked);\n+\t\t\t\t\t nr == -EMLINK, locked);\n \t\t\tswitch (ret) {\n \t\t\tcase 0:\n \t\t\t\tgoto retry;\n@@ -1440,70 +1581,30 @@ static long __get_user_pages(struct mm_struct *mm,\n \t\t\t\tgoto out;\n \t\t\t}\n \t\t\tBUG();\n-\t\t} else if (PTR_ERR(page) == -EEXIST) {\n+\t\t} else if (nr == -EEXIST) {\n \t\t\t/*\n \t\t\t * Proper page table entry exists, but no corresponding\n \t\t\t * struct page. If the caller expects **pages to be\n \t\t\t * filled in, bail out now, because that can't be done\n-\t\t\t * for this page.\n+\t\t\t * for this page. Otherwise advance by the one page\n+\t\t\t * follow_page_mask() looked at.\n \t\t\t */\n \t\t\tif (pages) {\n-\t\t\t\tret = PTR_ERR(page);\n+\t\t\t\tret = nr;\n \t\t\t\tgoto out;\n \t\t\t}\n-\t\t} else if (IS_ERR(page)) {\n-\t\t\tret = PTR_ERR(page);\n+\t\t\tnr = 1;\n+\t\t} else if (nr \u003c 0) {\n+\t\t\tret = nr;\n \t\t\tgoto out;\n \t\t}\n-next_page:\n-\t\tpage_increm = 1 + (~(start \u003e\u003e PAGE_SHIFT) \u0026 page_mask);\n-\t\tif (page_increm \u003e nr_pages)\n-\t\t\tpage_increm = nr_pages;\n-\n-\t\tif (pages) {\n-\t\t\tstruct page *subpage;\n-\t\t\tunsigned int j;\n \n-\t\t\t/*\n-\t\t\t * This must be a large folio (and doesn't need to\n-\t\t\t * be the whole folio; it can be part of it), do\n-\t\t\t * the refcount work for all the subpages too.\n-\t\t\t *\n-\t\t\t * NOTE: here the page may not be the head page\n-\t\t\t * e.g. when start addr is not thp-size aligned.\n-\t\t\t * try_grab_folio() should have taken care of tail\n-\t\t\t * pages.\n-\t\t\t */\n-\t\t\tif (page_increm \u003e 1) {\n-\t\t\t\tstruct folio *folio = page_folio(page);\n-\n-\t\t\t\t/*\n-\t\t\t\t * Since we already hold refcount on the\n-\t\t\t\t * large folio, this should never fail.\n-\t\t\t\t */\n-\t\t\t\tif (try_grab_folio(folio, page_increm - 1,\n-\t\t\t\t\t\t gup_flags)) {\n-\t\t\t\t\t/*\n-\t\t\t\t\t * Release the 1st page ref if the\n-\t\t\t\t\t * folio is problematic, fail hard.\n-\t\t\t\t\t */\n-\t\t\t\t\tgup_put_folio(folio, 1, gup_flags);\n-\t\t\t\t\tret = -EFAULT;\n-\t\t\t\t\tgoto out;\n-\t\t\t\t}\n-\t\t\t}\n-\n-\t\t\tfor (j = 0; j \u003c page_increm; j++) {\n-\t\t\t\tsubpage = page + j;\n-\t\t\t\tpages[i + j] = subpage;\n-\t\t\t\tflush_anon_page(vma, subpage, start + j * PAGE_SIZE);\n-\t\t\t\tflush_dcache_page(subpage);\n-\t\t\t}\n-\t\t}\n+\t\t/* Check that we didn't pin more pages than the caller will free. */\n+\t\tVM_WARN_ON_ONCE(nr \u003e nr_pages);\n \n-\t\ti += page_increm;\n-\t\tstart += page_increm * PAGE_SIZE;\n-\t\tnr_pages -= page_increm;\n+\t\ti += nr;\n+\t\tstart += nr * PAGE_SIZE;\n+\t\tnr_pages -= nr;\n \t} while (nr_pages);\n out:\n \treturn i ? i : ret;\n"
} |
| 2/1 | 2026/08/12 16:55 | agent | patch-evaluator |
1mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"TRANSPARENT_HUGEPAGE",
"HUGETLBFS"
],
"FocusSymbols": [
"get_user_pages",
"get_user_pages_remote",
"get_user_pages_unlocked",
"pin_user_pages",
"pin_user_pages_remote",
"pin_user_pages_unlocked",
"faultin_page_range",
"populate_vma_page_range"
],
"Reasoning": "The patch refactors the core Get User Pages (GUP) logic in `mm/gup.c` to batch page table walks and return multiple pages at once instead of one by one. This is a significant functional change to core memory management code that is reachable via many syscalls (e.g., mmap, madvise, ptrace, process_vm_readv). Fuzzing should focus on the exported GUP API entry points.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit 67e676e8b038e01ccdc6278576936d60d9b7ab84
Author: syz-cluster <triage@syzkaller.com>
Date: Wed Aug 12 16:55:11 2026 +0000
syz-cluster: applied patch under review
diff --git a/mm/gup.c b/mm/gup.c
index 0692119b79043..106806634b3c4 100644
--- a/mm/gup.c
+++ b/mm/gup.c
@@ -608,15 +608,15 @@ static inline bool can_follow_write_common(struct page *page,
return page && PageAnon(page) && PageAnonExclusive(page);
}
-static struct page *no_page_table(struct vm_area_struct *vma,
- unsigned int flags, unsigned long address)
+static long no_page_table(struct vm_area_struct *vma,
+ unsigned int flags, unsigned long address)
{
if (!(flags & FOLL_DUMP))
- return NULL;
+ return 0;
/*
* When core dumping, we don't want to allocate unnecessary pages or
- * page tables. Return error instead of NULL to skip handle_mm_fault,
+ * page tables. Return error instead of 0 to skip handle_mm_fault,
* then get_dump_page() will return NULL to leave a hole in the dump.
* But we can only make this optimization where a hole would surely
* be zero-filled if handle_mm_fault() actually did handle it.
@@ -625,12 +625,29 @@ static struct page *no_page_table(struct vm_area_struct *vma,
struct hstate *h = hstate_vma(vma);
if (!hugetlbfs_pagecache_present(h, vma, address))
- return ERR_PTR(-EFAULT);
+ return -EFAULT;
} else if ((vma_is_anonymous(vma) || !vma->vm_ops->fault)) {
- return ERR_PTR(-EFAULT);
+ return -EFAULT;
}
- return NULL;
+ return 0;
+}
+
+static void gup_fill_pages(struct vm_area_struct *vma, unsigned long address,
+ struct page *page, unsigned long nr, struct page **pages)
+{
+ unsigned long i;
+
+ if (!pages)
+ return;
+
+ for (i = 0; i < nr; i++) {
+ struct page *subpage = page + i;
+
+ pages[i] = subpage;
+ flush_anon_page(vma, subpage, address + i * PAGE_SIZE);
+ flush_dcache_page(subpage);
+ }
}
#ifdef CONFIG_PGTABLE_HAS_HUGE_LEAVES
@@ -646,38 +663,43 @@ static inline bool can_follow_write_pud(pud_t pud, struct page *page,
return can_follow_write_common(page, vma, flags);
}
-static struct page *follow_huge_pud(struct vm_area_struct *vma,
- unsigned long addr, pud_t *pudp,
- int flags, unsigned long *page_mask)
+static long follow_huge_pud(struct vm_area_struct *vma,
+ unsigned long addr, unsigned long end, pud_t *pudp,
+ unsigned int flags, struct page **pages)
{
struct mm_struct *mm = vma->vm_mm;
struct page *page;
pud_t pud = *pudp;
unsigned long pfn = pud_pfn(pud);
+ unsigned long off, nr;
int ret;
assert_spin_locked(pud_lockptr(mm, pudp));
if (!pud_present(pud))
- return NULL;
+ return 0;
if ((flags & FOLL_WRITE) &&
!can_follow_write_pud(pud, pfn_to_page(pfn), vma, flags))
- return NULL;
+ return 0;
- pfn += (addr & ~PUD_MASK) >> PAGE_SHIFT;
+ off = PFN_DOWN(addr & ~PUD_MASK);
+ pfn += off;
page = pfn_to_page(pfn);
if (!pud_write(pud) && gup_must_unshare(vma, flags, page))
- return ERR_PTR(-EMLINK);
+ return -EMLINK;
+
+ nr = min(HPAGE_PUD_NR - off, PFN_DOWN(end - addr));
- ret = try_grab_folio(page_folio(page), 1, flags);
+ ret = try_grab_folio(page_folio(page), nr, flags);
if (ret)
- page = ERR_PTR(ret);
- else
- *page_mask = HPAGE_PUD_NR - 1;
+ return ret;
+
+ if (pages)
+ pages[0] = page;
- return page;
+ return nr;
}
/* FOLL_FORCE can write to even unwritable PMDs in COW mappings. */
@@ -698,14 +720,14 @@ static inline bool can_follow_write_pmd(pmd_t pmd, struct page *page,
return !userfaultfd_huge_pmd_wp(vma, pmd);
}
-static struct page *follow_huge_pmd(struct vm_area_struct *vma,
- unsigned long addr, pmd_t *pmd,
- unsigned int flags,
- unsigned long *page_mask)
+static long follow_huge_pmd(struct vm_area_struct *vma,
+ unsigned long addr, unsigned long end, pmd_t *pmd,
+ unsigned int flags, struct page **pages)
{
struct mm_struct *mm = vma->vm_mm;
pmd_t pmdval = *pmd;
struct page *page;
+ unsigned long off, nr;
int ret;
assert_spin_locked(pmd_lockptr(mm, pmd));
@@ -713,50 +735,54 @@ static struct page *follow_huge_pmd(struct vm_area_struct *vma,
page = pmd_page(pmdval);
if ((flags & FOLL_WRITE) &&
!can_follow_write_pmd(pmdval, page, vma, flags))
- return NULL;
+ return 0;
/* Avoid dumping huge zero page */
if ((flags & FOLL_DUMP) && is_huge_zero_pmd(pmdval))
- return ERR_PTR(-EFAULT);
+ return -EFAULT;
if (pmd_protnone(*pmd) && !gup_can_follow_protnone(vma, flags))
- return NULL;
+ return 0;
if (!pmd_write(pmdval) && gup_must_unshare(vma, flags, page))
- return ERR_PTR(-EMLINK);
+ return -EMLINK;
VM_WARN_ON_ONCE_PAGE((flags & FOLL_PIN) && PageAnon(page) &&
!PageAnonExclusive(page), page);
- ret = try_grab_folio(page_folio(page), 1, flags);
+ off = PFN_DOWN(addr & ~HPAGE_PMD_MASK);
+ nr = min(HPAGE_PMD_NR - off, PFN_DOWN(end - addr));
+
+ ret = try_grab_folio(page_folio(page), nr, flags);
if (ret)
- return ERR_PTR(ret);
+ return ret;
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
if (pmd_trans_huge(pmdval) && (flags & FOLL_TOUCH))
touch_pmd(vma, addr, pmd, flags & FOLL_WRITE);
#endif /* CONFIG_TRANSPARENT_HUGEPAGE */
- page += (addr & ~HPAGE_PMD_MASK) >> PAGE_SHIFT;
- *page_mask = HPAGE_PMD_NR - 1;
+ page += off;
+
+ if (pages)
+ pages[0] = page;
- return page;
+ return nr;
}
#else /* CONFIG_PGTABLE_HAS_HUGE_LEAVES */
-static struct page *follow_huge_pud(struct vm_area_struct *vma,
- unsigned long addr, pud_t *pudp,
- int flags, unsigned long *page_mask)
+static long follow_huge_pud(struct vm_area_struct *vma,
+ unsigned long addr, unsigned long end, pud_t *pudp,
+ unsigned int flags, struct page **pages)
{
- return NULL;
+ return 0;
}
-static struct page *follow_huge_pmd(struct vm_area_struct *vma,
- unsigned long addr, pmd_t *pmd,
- unsigned int flags,
- unsigned long *page_mask)
+static long follow_huge_pmd(struct vm_area_struct *vma,
+ unsigned long addr, unsigned long end, pmd_t *pmd,
+ unsigned int flags, struct page **pages)
{
- return NULL;
+ return 0;
}
#endif /* CONFIG_PGTABLE_HAS_HUGE_LEAVES */
@@ -799,110 +825,205 @@ static inline bool can_follow_write_pte(pte_t pte, struct page *page,
return !userfaultfd_pte_wp(vma, pte);
}
-static struct page *follow_page_pte(struct vm_area_struct *vma,
- unsigned long address, pmd_t *pmd, unsigned int flags)
+/*
+ * The caller has already run every per-PTE safety check (present,
+ * write-fault, gup_must_unshare()) on the PTE, so this only does the
+ * per-folio work: the refcount grab, the FOLL_PIN accessibility fault-in,
+ * dirty/accessed marking, and the array fill with the cache flush.
+ */
+static long follow_page_pte_commit(struct vm_area_struct *vma,
+ unsigned long address, struct folio *folio, struct page *page,
+ pte_t pte, unsigned long nr, unsigned int flags,
+ struct page **pages)
+{
+ long ret;
+
+ /* try_grab_folio() does nothing unless FOLL_GET or FOLL_PIN is set. */
+ ret = try_grab_folio(folio, nr, flags);
+ if (unlikely(ret))
+ return ret;
+
+ /*
+ * We need to make the page accessible if and only if we are going
+ * to access its content (the FOLL_PIN case). Please see
+ * Documentation/core-api/pin_user_pages.rst for details.
+ */
+ if (flags & FOLL_PIN) {
+ ret = arch_make_folio_accessible(folio);
+ if (ret) {
+ gup_put_folio(folio, nr, flags);
+ return ret;
+ }
+ }
+ if (flags & FOLL_TOUCH) {
+ if ((flags & FOLL_WRITE) &&
+ !pte_dirty(pte) && !folio_test_dirty(folio))
+ folio_mark_dirty(folio);
+ /*
+ * pte_mkyoung() would be more correct here, but atomic care
+ * is needed to avoid losing the dirty bit: it is easier to use
+ * folio_mark_accessed().
+ */
+ folio_mark_accessed(folio);
+ }
+
+ gup_fill_pages(vma, address, page, nr, pages);
+
+ return 0;
+}
+
+/*
+ * Resolve one present PTE to the page it maps. Returns no page and no error
+ * when the PTE cannot be followed but the caller may fault it in, and a
+ * negative errno when the caller must report the failure.
+ */
+static long follow_one_pte(struct vm_area_struct *vma, unsigned long address,
+ pte_t *ptep, pte_t pte, unsigned int flags, struct page **pagep)
{
- struct mm_struct *mm = vma->vm_mm;
- struct folio *folio;
struct page *page;
- spinlock_t *ptl;
- pte_t *ptep, pte;
- int ret;
- ptep = pte_offset_map_lock(mm, pmd, address, &ptl);
- if (!ptep)
- return no_page_table(vma, flags, address);
- pte = ptep_get(ptep);
+ *pagep = NULL;
+
if (!pte_present(pte))
- goto no_page;
+ return 0;
if (pte_protnone(pte) && !gup_can_follow_protnone(vma, flags))
- goto no_page;
+ return 0;
page = vm_normal_page(vma, address, pte);
/*
* We only care about anon pages in can_follow_write_pte().
*/
- if ((flags & FOLL_WRITE) &&
- !can_follow_write_pte(pte, page, vma, flags)) {
- page = NULL;
- goto out;
- }
+ if ((flags & FOLL_WRITE) && !can_follow_write_pte(pte, page, vma, flags))
+ return 0;
if (unlikely(!page)) {
if (flags & FOLL_DUMP) {
/* Avoid special (like zero) pages in core dumps */
- page = ERR_PTR(-EFAULT);
- goto out;
- }
-
- if (is_zero_pfn(pte_pfn(pte))) {
- page = pte_page(pte);
- } else {
- ret = follow_pfn_pte(vma, address, ptep, flags);
- page = ERR_PTR(ret);
- goto out;
+ return -EFAULT;
}
+ if (!is_zero_pfn(pte_pfn(pte)))
+ return follow_pfn_pte(vma, address, ptep, flags);
+ page = pte_page(pte);
}
- folio = page_folio(page);
- if (!pte_write(pte) && gup_must_unshare(vma, flags, page)) {
- page = ERR_PTR(-EMLINK);
- goto out;
- }
+ if (!pte_write(pte) && gup_must_unshare(vma, flags, page))
+ return -EMLINK;
VM_WARN_ON_ONCE_PAGE((flags & FOLL_PIN) && PageAnon(page) &&
!PageAnonExclusive(page), page);
- /* try_grab_folio() does nothing unless FOLL_GET or FOLL_PIN is set. */
- ret = try_grab_folio(folio, 1, flags);
- if (unlikely(ret)) {
- page = ERR_PTR(ret);
- goto out;
- }
+ *pagep = page;
+ return 0;
+}
- /*
- * We need to make the page accessible if and only if we are going
- * to access its content (the FOLL_PIN case). Please see
- * Documentation/core-api/pin_user_pages.rst for details.
- */
- if (flags & FOLL_PIN) {
- ret = arch_make_folio_accessible(folio);
- if (ret) {
- unpin_user_page(page);
- page = ERR_PTR(ret);
- goto out;
+/*
+ * Return how many PTEs map consecutive pages of the same folio and can be
+ * committed as one run. Always at least 1.
+ *
+ * The write-fault and unshare checks in follow_one_pte() are per PTE, but a
+ * writable run needs no repeat: a writable anon page is exclusive. A read-only
+ * run under FOLL_WRITE or FOLL_PIN does need the per-page check, so it stays
+ * one page at a time.
+ */
+static unsigned long follow_pte_batch(struct vm_area_struct *vma,
+ unsigned long address, unsigned long walk_end,
+ struct folio *folio, pte_t *ptep, pte_t *batch_pte, unsigned int flags)
+{
+ unsigned long max;
+
+ if (!folio_test_large(folio))
+ return 1;
+ if (!pte_write(*batch_pte) && (flags & (FOLL_WRITE | FOLL_PIN)))
+ return 1;
+
+ max = (walk_end - address) >> PAGE_SHIFT;
+ if (max <= 1)
+ return 1;
+
+ /* Merge young/dirty across batch so folio_mark_dirty sees any dirty. */
+ return folio_pte_batch_flags(folio, vma, ptep, batch_pte, max,
+ FPB_RESPECT_WRITE | FPB_MERGE_YOUNG_DIRTY);
+}
+
+/*
+ * Walk the PTEs from the start address to the end of this page table or VMA,
+ * whichever comes first, and commit every page found.
+ *
+ * A failure on the first PTE is returned to the caller. A failure after that
+ * is a short read; __get_user_pages() retrying the read will get the error.
+ */
+static long follow_page_pte(struct vm_area_struct *vma,
+ unsigned long address, unsigned long end, pmd_t *pmd,
+ unsigned int flags, struct page **pages)
+{
+ struct mm_struct *mm = vma->vm_mm;
+ bool need_no_page_table = false;
+ pte_t *ptep, *orig_ptep;
+ unsigned long walk_end;
+ unsigned long nr = 0;
+ spinlock_t *ptl;
+ long ret = 0;
+
+ orig_ptep = ptep = pte_offset_map_lock(mm, pmd, address, &ptl);
+ if (!ptep)
+ return no_page_table(vma, flags, address);
+
+ walk_end = min(pmd_addr_end(address, end), vma->vm_end);
+
+ for (; address < walk_end; address += PAGE_SIZE, ptep++) {
+ pte_t pte = ptep_get(ptep);
+ struct page *page;
+
+ ret = follow_one_pte(vma, address, ptep, pte, flags, &page);
+ if (!ret && page) {
+ struct folio *folio = page_folio(page);
+ unsigned long batch;
+
+ pte_t batch_pte = pte;
+
+ batch = follow_pte_batch(vma, address, walk_end, folio,
+ ptep, &batch_pte, flags);
+ ret = follow_page_pte_commit(vma, address, folio, page,
+ batch_pte, batch, flags,
+ pages ? pages + nr : NULL);
+ if (!ret) {
+ nr += batch;
+ /*
+ * The loop's own increment covers one PTE; skip
+ * the rest of the batch.
+ */
+ ptep += batch - 1;
+ address += (batch - 1) * PAGE_SIZE;
+ continue;
+ }
}
- }
- if (flags & FOLL_TOUCH) {
- if ((flags & FOLL_WRITE) &&
- !pte_dirty(pte) && !folio_test_dirty(folio))
- folio_mark_dirty(folio);
+
/*
- * pte_mkyoung() would be more correct here, but atomic care
- * is needed to avoid losing the dirty bit: it is easier to use
- * folio_mark_accessed().
+ * no_page_table() may look up the page cache, so it cannot run
+ * under the PTE lock.
*/
- folio_mark_accessed(folio);
+ if (!ret && pte_none(pte))
+ need_no_page_table = true;
+ break;
}
-out:
- pte_unmap_unlock(ptep, ptl);
- return page;
-no_page:
- pte_unmap_unlock(ptep, ptl);
- if (!pte_none(pte))
- return NULL;
- return no_page_table(vma, flags, address);
+
+ pte_unmap_unlock(orig_ptep, ptl);
+
+ if (nr)
+ return nr;
+ if (need_no_page_table)
+ return no_page_table(vma, flags, address);
+ return ret;
}
-static struct page *follow_pmd_mask(struct vm_area_struct *vma,
- unsigned long address, pud_t *pudp,
- unsigned int flags,
- unsigned long *page_mask)
+static long follow_pmd_mask(struct vm_area_struct *vma,
+ unsigned long address, unsigned long end, pud_t *pudp,
+ unsigned int flags, struct page **pages)
{
pmd_t *pmd, pmdval;
spinlock_t *ptl;
- struct page *page;
+ long ret;
struct mm_struct *mm = vma->vm_mm;
pmd = pmd_offset(pudp, address);
@@ -912,7 +1033,7 @@ static struct page *follow_pmd_mask(struct vm_area_struct *vma,
if (!pmd_present(pmdval))
return no_page_table(vma, flags, address);
if (likely(!pmd_leaf(pmdval)))
- return follow_page_pte(vma, address, pmd, flags);
+ return follow_page_pte(vma, address, end, pmd, flags, pages);
if (pmd_protnone(pmdval) && !gup_can_follow_protnone(vma, flags))
return no_page_table(vma, flags, address);
@@ -925,28 +1046,35 @@ static struct page *follow_pmd_mask(struct vm_area_struct *vma,
}
if (unlikely(!pmd_leaf(pmdval))) {
spin_unlock(ptl);
- return follow_page_pte(vma, address, pmd, flags);
+ return follow_page_pte(vma, address, end, pmd, flags, pages);
}
if (pmd_trans_huge(pmdval) && (flags & FOLL_SPLIT_PMD)) {
spin_unlock(ptl);
split_huge_pmd(vma, pmd, address);
/* If pmd was left empty, stuff a page table in there quickly */
- return pte_alloc(mm, pmd) ? ERR_PTR(-ENOMEM) :
- follow_page_pte(vma, address, pmd, flags);
+ return pte_alloc(mm, pmd) ? -ENOMEM :
+ follow_page_pte(vma, address, end, pmd, flags, pages);
}
- page = follow_huge_pmd(vma, address, pmd, flags, page_mask);
+ ret = follow_huge_pmd(vma, address, end, pmd, flags, pages);
spin_unlock(ptl);
- return page;
+
+ /*
+ * The ref is already held, so the page cannot go away: fill the
+ * array and flush caches without the pmd lock.
+ */
+ if (ret > 0 && pages)
+ gup_fill_pages(vma, address, pages[0], ret, pages);
+
+ return ret;
}
-static struct page *follow_pud_mask(struct vm_area_struct *vma,
- unsigned long address, p4d_t *p4dp,
- unsigned int flags,
- unsigned long *page_mask)
+static long follow_pud_mask(struct vm_area_struct *vma,
+ unsigned long address, unsigned long end, p4d_t *p4dp,
+ unsigned int flags, struct page **pages)
{
pud_t *pudp, pud;
spinlock_t *ptl;
- struct page *page;
+ long ret;
struct mm_struct *mm = vma->vm_mm;
pudp = pud_offset(p4dp, address);
@@ -955,22 +1083,29 @@ static struct page *follow_pud_mask(struct vm_area_struct *vma,
return no_page_table(vma, flags, address);
if (pud_leaf(pud)) {
ptl = pud_lock(mm, pudp);
- page = follow_huge_pud(vma, address, pudp, flags, page_mask);
+ ret = follow_huge_pud(vma, address, end, pudp, flags, pages);
spin_unlock(ptl);
- if (page)
- return page;
+ /*
+ * The ref is already held, so the page cannot go away: fill
+ * the array and flush caches without the lock. A 1 GB folio
+ * can be up to HPAGE_PUD_NR pages, too long to flush under a
+ * spinlock.
+ */
+ if (ret > 0 && pages)
+ gup_fill_pages(vma, address, pages[0], ret, pages);
+ if (ret)
+ return ret;
return no_page_table(vma, flags, address);
}
if (unlikely(pud_bad(pud)))
return no_page_table(vma, flags, address);
- return follow_pmd_mask(vma, address, pudp, flags, page_mask);
+ return follow_pmd_mask(vma, address, end, pudp, flags, pages);
}
-static struct page *follow_p4d_mask(struct vm_area_struct *vma,
- unsigned long address, pgd_t *pgdp,
- unsigned int flags,
- unsigned long *page_mask)
+static long follow_p4d_mask(struct vm_area_struct *vma,
+ unsigned long address, unsigned long end, pgd_t *pgdp,
+ unsigned int flags, struct page **pages)
{
p4d_t *p4dp, p4d;
@@ -981,15 +1116,18 @@ static struct page *follow_p4d_mask(struct vm_area_struct *vma,
if (!p4d_present(p4d) || p4d_bad(p4d))
return no_page_table(vma, flags, address);
- return follow_pud_mask(vma, address, p4dp, flags, page_mask);
+ return follow_pud_mask(vma, address, end, p4dp, flags, pages);
}
/**
- * follow_page_mask - look up a page descriptor from a user-virtual address
+ * follow_page_mask - look up pages at a user-virtual address
* @vma: vm_area_struct mapping @address
* @address: virtual address to look up
+ * @end: virtual address at which to stop batching contiguous pages
* @flags: flags modifying lookup behaviour
- * @page_mask: a pointer to output page_mask
+ * @pages: array to receive the pages, refcounted per @flags, or NULL to
+ * walk the page tables (e.g. to fault pages in) without collecting
+ * or refcounting them
*
* @flags can have FOLL_ flags set, defined in <linux/mm.h>
*
@@ -998,33 +1136,32 @@ static struct page *follow_p4d_mask(struct vm_area_struct *vma,
* trigger a fault with FAULT_FLAG_UNSHARE set. Note that unsharing is only
* relevant with FOLL_PIN and !FOLL_WRITE.
*
- * On output, @page_mask is set according to the size of the page.
- *
- * Return: the mapped (struct page *), %NULL if no mapping exists, or
- * an error pointer if there is a mapping to something not represented
- * by a page descriptor (see also vm_normal_page()).
+ * Return: the number of contiguous pages starting at @address that were
+ * placed into @pages (if non-NULL), which may be fewer than the pages
+ * requested via @end; 0 if no mapping exists at @address; or a negative
+ * errno for a mapping to something not represented by a page descriptor
+ * (see also vm_normal_page()).
*/
-static struct page *follow_page_mask(struct vm_area_struct *vma,
- unsigned long address, unsigned int flags,
- unsigned long *page_mask)
+static long follow_page_mask(struct vm_area_struct *vma,
+ unsigned long address, unsigned long end,
+ unsigned int flags, struct page **pages)
{
pgd_t *pgd;
struct mm_struct *mm = vma->vm_mm;
- struct page *page;
+ long ret;
vma_pgtable_walk_begin(vma);
- *page_mask = 0;
pgd = pgd_offset(mm, address);
if (pgd_none(*pgd) || unlikely(pgd_bad(*pgd)))
- page = no_page_table(vma, flags, address);
+ ret = no_page_table(vma, flags, address);
else
- page = follow_p4d_mask(vma, address, pgd, flags, page_mask);
+ ret = follow_p4d_mask(vma, address, end, pgd, flags, pages);
vma_pgtable_walk_end(vma);
- return page;
+ return ret;
}
static int get_gate_page(struct mm_struct *mm, unsigned long address,
@@ -1358,7 +1495,6 @@ static long __get_user_pages(struct mm_struct *mm,
{
long ret = 0, i = 0;
struct vm_area_struct *vma = NULL;
- unsigned long page_mask = 0;
if (!nr_pages)
return 0;
@@ -1373,7 +1509,7 @@ static long __get_user_pages(struct mm_struct *mm,
do {
struct page *page;
- unsigned int page_increm;
+ long nr;
/* first iteration or cross vma bound */
if (!vma || start >= vma->vm_end) {
@@ -1400,8 +1536,12 @@ static long __get_user_pages(struct mm_struct *mm,
pages ? &page : NULL);
if (ret)
goto out;
- page_mask = 0;
- goto next_page;
+ gup_fill_pages(vma, start, page, 1,
+ pages ? pages + i : NULL);
+ i++;
+ start += PAGE_SIZE;
+ nr_pages--;
+ continue;
}
if (!vma) {
@@ -1423,10 +1563,11 @@ static long __get_user_pages(struct mm_struct *mm,
}
cond_resched();
- page = follow_page_mask(vma, start, gup_flags, &page_mask);
- if (!page || PTR_ERR(page) == -EMLINK) {
+ nr = follow_page_mask(vma, start, start + nr_pages * PAGE_SIZE,
+ gup_flags, pages ? &pages[i] : NULL);
+ if (!nr || nr == -EMLINK) {
ret = faultin_page(vma, start, gup_flags,
- PTR_ERR(page) == -EMLINK, locked);
+ nr == -EMLINK, locked);
switch (ret) {
case 0:
goto retry;
@@ -1440,70 +1581,30 @@ static long __get_user_pages(struct mm_struct *mm,
goto out;
}
BUG();
- } else if (PTR_ERR(page) == -EEXIST) {
+ } else if (nr == -EEXIST) {
/*
* Proper page table entry exists, but no corresponding
* struct page. If the caller expects **pages to be
* filled in, bail out now, because that can't be done
- * for this page.
+ * for this page. Otherwise advance by the one page
+ * follow_page_mask() looked at.
*/
if (pages) {
- ret = PTR_ERR(page);
+ ret = nr;
goto out;
}
- } else if (IS_ERR(page)) {
- ret = PTR_ERR(page);
+ nr = 1;
+ } else if (nr < 0) {
+ ret = nr;
goto out;
}
-next_page:
- page_increm = 1 + (~(start >> PAGE_SHIFT) & page_mask);
- if (page_increm > nr_pages)
- page_increm = nr_pages;
-
- if (pages) {
- struct page *subpage;
- unsigned int j;
- /*
- * This must be a large folio (and doesn't need to
- * be the whole folio; it can be part of it), do
- * the refcount work for all the subpages too.
- *
- * NOTE: here the page may not be the head page
- * e.g. when start addr is not thp-size aligned.
- * try_grab_folio() should have taken care of tail
- * pages.
- */
- if (page_increm > 1) {
- struct folio *folio = page_folio(page);
-
- /*
- * Since we already hold refcount on the
- * large folio, this should never fail.
- */
- if (try_grab_folio(folio, page_increm - 1,
- gup_flags)) {
- /*
- * Release the 1st page ref if the
- * folio is problematic, fail hard.
- */
- gup_put_folio(folio, 1, gup_flags);
- ret = -EFAULT;
- goto out;
- }
- }
-
- for (j = 0; j < page_increm; j++) {
- subpage = page + j;
- pages[i + j] = subpage;
- flush_anon_page(vma, subpage, start + j * PAGE_SIZE);
- flush_dcache_page(subpage);
- }
- }
+ /* Check that we didn't pin more pages than the caller will free. */
+ VM_WARN_ON_ONCE(nr > nr_pages);
- i += page_increm;
- start += page_increm * PAGE_SIZE;
- nr_pages -= page_increm;
+ i += nr;
+ start += nr * PAGE_SIZE;
+ nr_pages -= nr;
} while (nr_pages);
out:
return i ? i : ret;
|
| 3/2 | 2026/08/12 16:55 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 11089 output: 38 thoughts: 476 Thoughts: **GUP Refactoring and Fuzzing Considerations** Alright, let's break this down. My immediate thought is, "Yes, this patch series warrants a thorough look for fuzzing potential." This patch is clearly touching the GUP (Get User Pages) code, specifically `mm/gup.c`, and that's a red flag in a good way, meaning it’s a high-impact area. This isn't some minor tweak; it's a fundamental refactoring of how GUP works. They're changing the return values and population of the `pages` array, which in turn means the core logic for translating user virtual addresses to physical pages is being modified. We need to assess the fuzzing attack surface. They're taking `struct page *` and `page_mask` and moving to returning a `long` value (count of pages or error) and directly filling the `pages` array. A serious change to a fundamental memory management subsystem like this demands scrutiny. It's a fundamental shift in the *way* we get user pages. Looking at the changed functions... yeah, this is significant: `no_page_table`, `gup_fill_pages`, `follow_huge_pud`, `follow_huge_pmd`, the new commit functions, and of course, the mask functions. This is GUP logic, touching several key components of core memory management. So, the next step is to nail down the most critical functions for fuzzing. `__get_user_pages` might be too general, but `follow_page_mask`, `follow_page_pte`, `follow_pmd_mask`, `follow_pud_mask`, and `follow_p4d_mask` all look like prime targets. The mask functions are particularly interesting because they deal with various page table levels, and subtle errors in those can lead to significant issues. Finally, we need to check the configuration options. Since the patch is in `mm/gup.c`, the code will always be compiled, however some parts are conditionally compiled under `CONFIG_PGTABLE_HAS_HUGE_LEAVES` and `CONFIG_TRANSPARENT_HUGEPAGE`. I should pull up the tree and check. |
| 4/2 | 2026/08/12 16:55 | tool | grepper |
0mArgs:{
"Expression": "CONFIG_PGTABLE_HAS_HUGE_LEAVES",
"PathPrefix": "mm/gup.c"
}Results: {
"Output": "mm/gup.c=636=static void gup_fill_pages(struct vm_area_struct *vma, unsigned long address,\n--\nmm/gup.c-652-\nmm/gup.c:653:#ifdef CONFIG_PGTABLE_HAS_HUGE_LEAVES\nmm/gup.c-654-/* FOLL_FORCE can write to even unwritable PUDs in COW mappings. */\n--\nmm/gup.c=723=static long follow_huge_pmd(struct vm_area_struct *vma,\n--\nmm/gup.c-772-\nmm/gup.c:773:#else /* CONFIG_PGTABLE_HAS_HUGE_LEAVES */\nmm/gup.c-774-static long follow_huge_pud(struct vm_area_struct *vma,\n--\nmm/gup.c=781=static long follow_huge_pmd(struct vm_area_struct *vma,\n--\nmm/gup.c-786-}\nmm/gup.c:787:#endif\t/* CONFIG_PGTABLE_HAS_HUGE_LEAVES */\nmm/gup.c-788-\n"
} |
| 5/2 | 2026/08/12 16:55 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 12388 output: 36 thoughts: 16 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Investigating Transparent Hugepages** Alright, let's dive in. First order of business: I need to verify the status of `CONFIG_TRANSPARENT_HUGEPAGE`. This is fundamental. Depending on whether it's enabled or not, it dictates a huge swath of the kernel's memory management behavior, performance characteristics, and potential interactions with other subsystems. I need to know where we stand. A quick check of the configuration is absolutely essential before proceeding. Are THPs enabled or is there something else going on? |
| 6/2 | 2026/08/12 16:55 | tool | grepper |
0mArgs:{
"Expression": "CONFIG_TRANSPARENT_HUGEPAGE",
"PathPrefix": "mm/gup.c"
}Results: {
"Output": "mm/gup.c=723=static long follow_huge_pmd(struct vm_area_struct *vma,\n--\nmm/gup.c-759-\nmm/gup.c:760:#ifdef CONFIG_TRANSPARENT_HUGEPAGE\nmm/gup.c-761-\tif (pmd_trans_huge(pmdval) \u0026\u0026 (flags \u0026 FOLL_TOUCH))\nmm/gup.c-762-\t\ttouch_pmd(vma, addr, pmd, flags \u0026 FOLL_WRITE);\nmm/gup.c:763:#endif\t/* CONFIG_TRANSPARENT_HUGEPAGE */\nmm/gup.c-764-\n"
} |
| 7/2 | 2026/08/12 16:55 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 12755 output: 26 thoughts: 76 Thoughts: **Examining Huge Page Configuration for GUP Testing** Okay, so I'm looking at these configurations and thinking, "Yep, these are definitely the right settings to have enabled when testing huge pages in GUP." It's good to be meticulous about this stuff. *TRANSPARENT_HUGEPAGE* is a given; that's the bread and butter for transparent huge page support, no surprises there. Now, *PGTABLE_HAS_HUGE_LEAVES* is the more interesting one. That's architecture-dependent, so I need to check to see if we can enable this one directly, or if the architecture will select this itself. I'm going to dig a little deeper. |
| 8/2 | 2026/08/12 16:55 | tool | grepper |
0mArgs:{
"Expression": "config PGTABLE_HAS_HUGE_LEAVES"
}Results: {
"Output": "mm/Kconfig=1008=config PAGE_MAPCOUNT\n--\nmm/Kconfig-1013-#\nmm/Kconfig:1014:config PGTABLE_HAS_HUGE_LEAVES\nmm/Kconfig-1015-\tdef_bool TRANSPARENT_HUGEPAGE || HUGETLB_PAGE\n"
} |
| 9/2 | 2026/08/12 16:55 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 13098 output: 20 thoughts: 73 Thoughts: **Examining the Kernel Configuration for Huge Pages** Okay, so I see that `PGTABLE_HAS_HUGE_LEAVES` is defined as either `TRANSPARENT_HUGEPAGE` or `HUGETLB_PAGE`. That makes perfect sense; it's a logical connection. Considering this, the obvious approach is to make sure *both* `TRANSPARENT_HUGEPAGE` and `HUGETLB_PAGE` are enabled. It's the most robust solution. Let's dig into the `HUGETLB_PAGE` option in the Kconfig file and verify its status. I need to make sure we have the correct settings in place. |
| 10/2 | 2026/08/12 16:55 | tool | grepper |
0mArgs:{
"Expression": "config HUGETLB_PAGE"
}Results: {
"Output": "arch/sh/mm/Kconfig=158=choice\n--\narch/sh/mm/Kconfig-163-\narch/sh/mm/Kconfig:164:config HUGETLB_PAGE_SIZE_64K\narch/sh/mm/Kconfig-165-\tbool \"64kB\"\n--\narch/sh/mm/Kconfig-167-\narch/sh/mm/Kconfig:168:config HUGETLB_PAGE_SIZE_256K\narch/sh/mm/Kconfig-169-\tbool \"256kB\"\n--\narch/sh/mm/Kconfig-171-\narch/sh/mm/Kconfig:172:config HUGETLB_PAGE_SIZE_1MB\narch/sh/mm/Kconfig-173-\tbool \"1MB\"\narch/sh/mm/Kconfig-174-\narch/sh/mm/Kconfig:175:config HUGETLB_PAGE_SIZE_4MB\narch/sh/mm/Kconfig-176-\tbool \"4MB\"\n--\narch/sh/mm/Kconfig-178-\narch/sh/mm/Kconfig:179:config HUGETLB_PAGE_SIZE_64MB\narch/sh/mm/Kconfig-180-\tbool \"64MB\"\n--\nfs/Kconfig=262=if HUGETLBFS\nfs/Kconfig:263:config HUGETLB_PAGE_OPTIMIZE_VMEMMAP_DEFAULT_ON\nfs/Kconfig-264-\tbool \"HugeTLB Vmemmap Optimization (HVO) defaults to on\"\n--\nfs/Kconfig=271=endif # HUGETLBFS\nfs/Kconfig-272-\nfs/Kconfig:273:config HUGETLB_PAGE\nfs/Kconfig-274-\tdef_bool HUGETLBFS\n--\nfs/Kconfig-276-\nfs/Kconfig:277:config HUGETLB_PAGE_OPTIMIZE_VMEMMAP\nfs/Kconfig-278-\tdef_bool HUGETLB_PAGE\n--\nmm/Kconfig=706=config ARCH_ENABLE_THP_MIGRATION\n--\nmm/Kconfig-708-\nmm/Kconfig:709:config HUGETLB_PAGE_SIZE_VARIABLE\nmm/Kconfig-710-\tdef_bool n\n"
} |
| 11/2 | 2026/08/12 16:55 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 13930 output: 22 thoughts: 47 Thoughts: **Enabling Huge Pages and the Filesystem** Okay, so `HUGETLB_PAGE` is defined as a boolean (`def_bool`) and linked directly to `HUGETLBFS`. This seems straightforward: If we want to use huge pages, we definitely need the huge page filesystem (`HUGETLBFS`) enabled. It's a dependency. Makes sense. Now, let's just confirm something about how transparent huge pages are handled. Let me double-check the configuration (`Kconfig`) and see how `TRANSPARENT_HUGEPAGE` is set up in relation to all this. I'll need to make sure everything's consistent and that we're covering all our bases. |
| 12/2 | 2026/08/12 16:55 | tool | grepper |
0mArgs:{
"Expression": "config TRANSPARENT_HUGEPAGE"
}Results: {
"Output": "mm/Kconfig=843=config MM_ID\n--\nmm/Kconfig-845-\nmm/Kconfig:846:menuconfig TRANSPARENT_HUGEPAGE\nmm/Kconfig-847-\tbool \"Transparent Hugepage Support\"\n--\nmm/Kconfig=864=choice\n--\nmm/Kconfig-869-\nmm/Kconfig:870:\tconfig TRANSPARENT_HUGEPAGE_ALWAYS\nmm/Kconfig-871-\t\tbool \"always\"\n--\nmm/Kconfig-876-\nmm/Kconfig:877:\tconfig TRANSPARENT_HUGEPAGE_MADVISE\nmm/Kconfig-878-\t\tbool \"madvise\"\n--\nmm/Kconfig-885-\nmm/Kconfig:886:\tconfig TRANSPARENT_HUGEPAGE_NEVER\nmm/Kconfig-887-\t\tbool \"never\"\n--\nmm/Kconfig=893=choice\n--\nmm/Kconfig-902-\nmm/Kconfig:903:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_NEVER\nmm/Kconfig-904-\t\tbool \"never\"\n--\nmm/Kconfig-912-\nmm/Kconfig:913:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_ALWAYS\nmm/Kconfig-914-\t\tbool \"always\"\n--\nmm/Kconfig-920-\nmm/Kconfig:921:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_WITHIN_SIZE\nmm/Kconfig-922-\t\tbool \"within_size\"\n--\nmm/Kconfig-928-\nmm/Kconfig:929:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_ADVISE\nmm/Kconfig-930-\t\tbool \"advise\"\n--\nmm/Kconfig=938=choice\n--\nmm/Kconfig-947-\nmm/Kconfig:948:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_NEVER\nmm/Kconfig-949-\t\tbool \"never\"\n--\nmm/Kconfig-956-\nmm/Kconfig:957:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_ALWAYS\nmm/Kconfig-958-\t\tbool \"always\"\n--\nmm/Kconfig-964-\nmm/Kconfig:965:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_WITHIN_SIZE\nmm/Kconfig-966-\t\tbool \"within_size\"\n--\nmm/Kconfig-972-\nmm/Kconfig:973:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_ADVISE\nmm/Kconfig-974-\t\tbool \"advise\"\n"
} |
| 13/2 | 2026/08/12 16:55 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 14861 output: 32 thoughts: 504 Thoughts: **Analyzing Huge Page Handling for Optimization** Okay, so the initial thought is to enable `TRANSPARENT_HUGEPAGE` and `HUGETLBFS`. That seems like a reasonable starting point. Now, let's drill down into the kernel functions involved in handling these pages. I need to identify the key functions that are relevant to this area for potential optimization. My initial list of functions to focus on includes a comprehensive look at the various `follow_*` functions and other related functions. Hold on a moment, a deeper look at the function signatures, reveals that `follow_one_pte`, `follow_pte_batch`, `follow_page_pte_commit`, `gup_fill_pages`, and `no_page_table` are all *static* functions. That makes me wonder if they're inlined by the compiler. Let's check. It looks like the compiler isn't inlining them. They're all static, but not inline. Now, I see the prompt's instruction: "Prefer non-static, non-inlined API entrypoint functions over internal static helper functions." This means the focus should be on the *non-static* functions that call these internal helpers. This is a crucial distinction. We want the stable, well-defined entry points, not the internal implementation details. Since `__get_user_pages` is static, my next step is to examine what calls it. That's the correct path to finding a non-static API entry point. This is the path to pursue. |
| 14/2 | 2026/08/12 16:55 | tool | grepper |
0mArgs:{
"Expression": "__get_user_pages",
"PathPrefix": "mm/gup.c"
}Results: {
"Output": "mm/gup.c=929=static unsigned long follow_pte_batch(struct vm_area_struct *vma,\n--\nmm/gup.c-953- * A failure on the first PTE is returned to the caller. A failure after that\nmm/gup.c:954: * is a short read; __get_user_pages() retrying the read will get the error.\nmm/gup.c-955- */\n--\nmm/gup.c=1402=static struct vm_area_struct *gup_vma_lookup(struct mm_struct *mm,\n--\nmm/gup.c-1437-/**\nmm/gup.c:1438: * __get_user_pages() - pin user pages in memory\nmm/gup.c-1439- * @mm:\t\tmm_struct of target mm\n--\nmm/gup.c-1460- *\nmm/gup.c:1461: * __get_user_pages walks a process's page tables and takes a reference to\nmm/gup.c-1462- * each struct page that each user address corresponds to at a given\n--\nmm/gup.c-1466- * This does not guarantee that the page exists in the user mappings when\nmm/gup.c:1467: * __get_user_pages returns, and there may even be a completely different\nmm/gup.c-1468- * page there in some cases (eg. if mmapped pagecache has been invalidated\n--\nmm/gup.c-1487- * In most cases, get_user_pages or get_user_pages_fast should be used\nmm/gup.c:1488: * instead of __get_user_pages. __get_user_pages should be used only if\nmm/gup.c-1489- * you need some special @gup_flags.\nmm/gup.c-1490- */\nmm/gup.c:1491:static long __get_user_pages(struct mm_struct *mm,\nmm/gup.c-1492-\t\tunsigned long start, unsigned long nr_pages,\n--\nmm/gup.c=1726=static bool gup_signal_pending(unsigned int flags)\n--\nmm/gup.c-1746- *\nmm/gup.c:1747: * Please note that this function, unlike __get_user_pages(), will not return 0\nmm/gup.c-1748- * for nr_pages \u003e 0, unless FOLL_NOWAIT is used.\nmm/gup.c-1749- */\nmm/gup.c:1750:static __always_inline long __get_user_pages_locked(struct mm_struct *mm,\nmm/gup.c-1751-\t\t\t\t\t\tunsigned long start,\n--\nmm/gup.c-1792-\tfor (;;) {\nmm/gup.c:1793:\t\tret = __get_user_pages(mm, start, nr_pages, flags, pages,\nmm/gup.c-1794-\t\t\t\t locked);\n--\nmm/gup.c-1852-\t\t*locked = 1;\nmm/gup.c:1853:\t\tret = __get_user_pages(mm, start, 1, flags | FOLL_TRIED,\nmm/gup.c-1854-\t\t\t\t pages, locked);\n--\nmm/gup.c=1914=long populate_vma_page_range(struct vm_area_struct *vma,\n--\nmm/gup.c-1960-\t */\nmm/gup.c:1961:\tret = __get_user_pages(mm, start, nr_pages, gup_flags,\nmm/gup.c-1962-\t\t\t NULL, locked ? locked : \u0026local_locked);\n--\nmm/gup.c-1979- * Returns either number of processed pages in the MM, or a negative error\nmm/gup.c:1980: * code on error (see __get_user_pages()). Note that this function reports\nmm/gup.c-1981- * errors related to VMAs, such as incompatible mappings, as expected by\n--\nmm/gup.c=1988=long faultin_page_range(struct mm_struct *mm, unsigned long start,\n--\nmm/gup.c-2012-\nmm/gup.c:2013:\tret = __get_user_pages_locked(mm, start, nr_pages, NULL, locked,\nmm/gup.c-2014-\t\t\t\t gup_flags);\n--\nmm/gup.c=2026=int __mm_populate(unsigned long start, unsigned long len, int ignore_errors)\n--\nmm/gup.c-2079-#else /* CONFIG_MMU */\nmm/gup.c:2080:static long __get_user_pages_locked(struct mm_struct *mm, unsigned long start,\nmm/gup.c-2081-\t\tunsigned long nr_pages, struct page **pages,\n--\nmm/gup.c=2288=struct page *get_dump_page(unsigned long addr, int *locked)\n--\nmm/gup.c-2292-\nmm/gup.c:2293:\tret = __get_user_pages_locked(current-\u003emm, addr, 1, \u0026page, locked,\nmm/gup.c-2294-\t\t\t\t FOLL_FORCE | FOLL_DUMP | FOLL_GET);\n--\nmm/gup.c=2422=migrate_longterm_unpinnable_folios(struct list_head *movable_folio_list,\n--\nmm/gup.c-2450-\t\t * We can't migrate folios with unexpected references, so drop\nmm/gup.c:2451:\t\t * the reference obtained by __get_user_pages_locked().\nmm/gup.c-2452-\t\t * Migrating folios have been added to movable_folio_list after\n--\nmm/gup.c=2555=static long check_and_migrate_movable_folios(unsigned long nr_folios,\n--\nmm/gup.c-2562-/*\nmm/gup.c:2563: * __gup_longterm_locked() is a wrapper for __get_user_pages_locked which\nmm/gup.c-2564- * allows us to process the FOLL_LONGTERM flag.\n--\nmm/gup.c=2566=static long __gup_longterm_locked(struct mm_struct *mm,\n--\nmm/gup.c-2576-\tif (!(gup_flags \u0026 FOLL_LONGTERM))\nmm/gup.c:2577:\t\treturn __get_user_pages_locked(mm, start, nr_pages, pages,\nmm/gup.c-2578-\t\t\t\t\t locked, gup_flags);\n--\nmm/gup.c-2581-\tdo {\nmm/gup.c:2582:\t\tnr_pinned_pages = __get_user_pages_locked(mm, start, nr_pages,\nmm/gup.c-2583-\t\t\t\t\t\t\t pages, locked,\n--\nmm/gup.c=2704=long get_user_pages_remote(struct mm_struct *mm,\n--\nmm/gup.c-2714-\nmm/gup.c:2715:\treturn __get_user_pages_locked(mm, start, nr_pages, pages,\nmm/gup.c-2716-\t\t\t\t locked ? locked : \u0026local_locked,\n--\nmm/gup.c=2745=long get_user_pages(unsigned long start, unsigned long nr_pages,\n--\nmm/gup.c-2752-\nmm/gup.c:2753:\treturn __get_user_pages_locked(current-\u003emm, start, nr_pages, pages,\nmm/gup.c-2754-\t\t\t\t \u0026locked, gup_flags);\n--\nmm/gup.c=2773=long get_user_pages_unlocked(unsigned long start, unsigned long nr_pages,\n--\nmm/gup.c-2781-\nmm/gup.c:2782:\treturn __get_user_pages_locked(current-\u003emm, start, nr_pages, pages,\nmm/gup.c-2783-\t\t\t\t \u0026locked, gup_flags);\n"
} |
| 15/2 | 2026/08/12 16:55 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 17892 output: 31 thoughts: 233 Thoughts: **Analyzing User Page Faults** Okay, so I'm digging into these user page functions. My primary entry points here seem to be `get_user_pages`, `get_user_pages_remote`, `get_user_pages_unlocked`, `faultin_page_range`, `populate_vma_page_range`, and `get_dump_page`. I'm starting to think `get_user_pages` is a *very* hot path – I need to understand what's hitting that. Let me look at some of the syscalls to understand how those paths are touched. Alright, `mmap` with `MAP_POPULATE` will clearly hit `populate_vma_page_range`. `mlock` triggers `faultin_page_range`, makes sense for preventing swapping. Then, `madvise` with `MADV_POPULATE_READ` or `MADV_POPULATE_WRITE` *also* calls `faultin_page_range`. That's important to keep in mind, and that has implications for the `madvise` semantics. `process_vm_readv` and `process_vm_writev` will obviously invoke `get_user_pages_remote` or `pin_user_pages_remote`, as does `ptrace`. Now, before I go deeper into the `get_user_pages` family, let's take a look at `pin_user_pages` and `pin_user_pages_remote` more closely. I need to fully understand this logic path. |
| 16/2 | 2026/08/12 16:55 | tool | grepper |
0mArgs:{
"Expression": "pin_user_pages",
"PathPrefix": "mm/gup.c"
}Results: {
"Output": "mm/gup.c=140=int __must_check try_grab_folio(struct folio *folio, int refs,\n--\nmm/gup.c-179- *\nmm/gup.c:180: * Pages that were pinned via pin_user_pages*() must be released via either\nmm/gup.c:181: * unpin_user_page(), or one of the unpin_user_pages*() routines. This is so\nmm/gup.c-182- * that such pages can be separately tracked and uniquely handled. In\n--\nmm/gup.c=247=static inline struct folio *gup_folio_next(struct page **list,\n--\nmm/gup.c-262-/**\nmm/gup.c:263: * unpin_user_pages_dirty_lock() - release and optionally dirty gup-pinned pages\nmm/gup.c-264- * @pages: array of pages to be maybe marked dirty, and definitely released.\n--\nmm/gup.c-273- * listed as clean. In any case, releases all pages using unpin_user_page(),\nmm/gup.c:274: * possibly via unpin_user_pages(), for the non-dirty case.\nmm/gup.c-275- *\n--\nmm/gup.c-283- */\nmm/gup.c:284:void unpin_user_pages_dirty_lock(struct page **pages, unsigned long npages,\nmm/gup.c-285-\t\t\t\t bool make_dirty)\n--\nmm/gup.c-291-\tif (!make_dirty) {\nmm/gup.c:292:\t\tunpin_user_pages(pages, npages);\nmm/gup.c-293-\t\treturn;\n--\nmm/gup.c-326-}\nmm/gup.c:327:EXPORT_SYMBOL(unpin_user_pages_dirty_lock);\nmm/gup.c-328-\n--\nmm/gup.c-337- * \"gup-pinned page range\" refers to a range of pages that has had one of the\nmm/gup.c:338: * pin_user_pages() variants called on that page.\nmm/gup.c-339- *\n--\nmm/gup.c=373=EXPORT_SYMBOL(unpin_user_page_range_dirty_lock);\nmm/gup.c-374-\nmm/gup.c:375:static void gup_fast_unpin_user_pages(struct page **pages, unsigned long npages)\nmm/gup.c-376-{\n--\nmm/gup.c-392-/**\nmm/gup.c:393: * unpin_user_pages() - release an array of gup-pinned pages.\nmm/gup.c-394- * @pages: array of pages to be marked dirty and released.\n--\nmm/gup.c-400- */\nmm/gup.c:401:void unpin_user_pages(struct page **pages, unsigned long npages)\nmm/gup.c-402-{\n--\nmm/gup.c-424-}\nmm/gup.c:425:EXPORT_SYMBOL(unpin_user_pages);\nmm/gup.c-426-\n--\nmm/gup.c=479=static inline void mm_set_has_pinned_flag(struct mm_struct *mm)\n--\nmm/gup.c-498- * same time. (That's true throughout the get_user_pages*() and\nmm/gup.c:499: * pin_user_pages*() APIs.) Cases:\nmm/gup.c-500- *\n--\nmm/gup.c=834=static long follow_page_pte_commit(struct vm_area_struct *vma,\n--\nmm/gup.c-848-\t * to access its content (the FOLL_PIN case). Please see\nmm/gup.c:849:\t * Documentation/core-api/pin_user_pages.rst for details.\nmm/gup.c-850-\t */\n--\nmm/gup.c=2330=static void pofs_unpin(struct pages_or_folios *pofs)\n--\nmm/gup.c-2334-\telse\nmm/gup.c:2335:\t\tunpin_user_pages(pofs-\u003epages, pofs-\u003enr_entries);\nmm/gup.c-2336-}\n--\nmm/gup.c=2930=static int gup_fast_pte_range(pmd_t pmd, pmd_t *pmdp, unsigned long addr,\n--\nmm/gup.c-2987-\t\t * going to access its content (the FOLL_PIN case). Please\nmm/gup.c:2988:\t\t * see Documentation/core-api/pin_user_pages.rst for\nmm/gup.c-2989-\t\t * details.\n--\nmm/gup.c=3230=static unsigned long gup_fast(unsigned long start, unsigned long end,\n--\nmm/gup.c-3266-\t\tif (read_seqcount_retry(\u0026current-\u003emm-\u003ewrite_protect_seq, seq)) {\nmm/gup.c:3267:\t\t\tgup_fast_unpin_user_pages(pages, nr_pinned);\nmm/gup.c-3268-\t\t\treturn 0;\n--\nmm/gup.c=3390=EXPORT_SYMBOL_GPL(get_user_pages_fast);\n--\nmm/gup.c-3392-/**\nmm/gup.c:3393: * pin_user_pages_fast() - pin user pages in memory without taking locks\nmm/gup.c-3394- *\n--\nmm/gup.c-3405- * FOLL_PIN means that the pages must be released via unpin_user_page(). Please\nmm/gup.c:3406: * see Documentation/core-api/pin_user_pages.rst for further details.\nmm/gup.c-3407- *\n--\nmm/gup.c-3410- */\nmm/gup.c:3411:int pin_user_pages_fast(unsigned long start, int nr_pages,\nmm/gup.c-3412-\t\t\tunsigned int gup_flags, struct page **pages)\n--\nmm/gup.c-3417-}\nmm/gup.c:3418:EXPORT_SYMBOL_GPL(pin_user_pages_fast);\nmm/gup.c-3419-\nmm/gup.c-3420-/**\nmm/gup.c:3421: * pin_user_pages_remote() - pin pages of a remote process\nmm/gup.c-3422- *\n--\nmm/gup.c-3437- * FOLL_PIN means that the pages must be released via unpin_user_page(). Please\nmm/gup.c:3438: * see Documentation/core-api/pin_user_pages.rst for details.\nmm/gup.c-3439- *\n--\nmm/gup.c-3442- */\nmm/gup.c:3443:long pin_user_pages_remote(struct mm_struct *mm,\nmm/gup.c-3444-\t\t\t unsigned long start, unsigned long nr_pages,\n--\nmm/gup.c-3456-}\nmm/gup.c:3457:EXPORT_SYMBOL(pin_user_pages_remote);\nmm/gup.c-3458-\nmm/gup.c-3459-/**\nmm/gup.c:3460: * pin_user_pages() - pin user pages in memory for use by other devices\nmm/gup.c-3461- *\n--\nmm/gup.c-3471- * FOLL_PIN means that the pages must be released via unpin_user_page(). Please\nmm/gup.c:3472: * see Documentation/core-api/pin_user_pages.rst for details.\nmm/gup.c-3473- *\n--\nmm/gup.c-3476- */\nmm/gup.c:3477:long pin_user_pages(unsigned long start, unsigned long nr_pages,\nmm/gup.c-3478-\t\t unsigned int gup_flags, struct page **pages)\n--\nmm/gup.c-3486-}\nmm/gup.c:3487:EXPORT_SYMBOL(pin_user_pages);\nmm/gup.c-3488-\nmm/gup.c-3489-/*\nmm/gup.c:3490: * pin_user_pages_unlocked() is the FOLL_PIN variant of\nmm/gup.c-3491- * get_user_pages_unlocked(). Behavior is the same, except that this one sets\n--\nmm/gup.c-3496- */\nmm/gup.c:3497:long pin_user_pages_unlocked(unsigned long start, unsigned long nr_pages,\nmm/gup.c-3498-\t\t\t struct page **pages, unsigned int gup_flags)\n--\nmm/gup.c-3508-}\nmm/gup.c:3509:EXPORT_SYMBOL(pin_user_pages_unlocked);\nmm/gup.c-3510-\n"
} |
| 17/2 | 2026/08/12 16:55 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 20943 output: 32 thoughts: 419 Thoughts: **Analyzing API Entry Points for a Code Change** Okay, so I'm looking at these changes and need to identify the relevant API entry points affected by this logic. I see we have: `get_user_pages`, `get_user_pages_remote`, `get_user_pages_unlocked`, `pin_user_pages`, `pin_user_pages_remote`, `pin_user_pages_unlocked`, `faultin_page_range`, and `populate_vma_page_range`. Right, and I should note that `get_user_pages_fast` and `pin_user_pages_fast` have that lockless `gup_fast` path, but ultimately fall back to `__get_user_pages` if needed, so they're relevant, too, though not the primary target. The changes are focused on `follow_page_mask` and `__get_user_pages`, which I recognize as the slower fallback paths. I need to focus on the external, non-static entry points, since that's what the prompt is emphasizing: "Prefer non-static, non-inlined API entrypoint functions over internal static helper functions." So, `get_user_pages` is quite generic, and heavily used. However, `faultin_page_range` and `populate_vma_page_range` are also possibilities. Actually, `follow_page_mask` *is* static, but it's the central function being modified. So it seems the best list of potential targets includes: `get_user_pages`, `get_user_pages_remote`, `get_user_pages_unlocked`, `pin_user_pages`, `pin_user_pages_remote`, `pin_user_pages_unlocked`, `faultin_page_range`, and `populate_vma_page_range`. Now I need to verify that `faultin_page_range` and `populate_vma_page_range` are indeed exported and non-static. |
| 18/2 | 2026/08/12 16:56 | tool | grepper |
0mArgs:{
"Expression": "faultin_page_range",
"PathPrefix": "mm/gup.c"
}Results: {
"Output": "mm/gup.c=1914=long populate_vma_page_range(struct vm_area_struct *vma,\n--\nmm/gup.c-1967-/*\nmm/gup.c:1968: * faultin_page_range() - populate (prefault) page tables inside the\nmm/gup.c-1969- *\t\t\t given range readable/writable\n--\nmm/gup.c-1987- */\nmm/gup.c:1988:long faultin_page_range(struct mm_struct *mm, unsigned long start,\nmm/gup.c-1989-\t\t\tunsigned long end, bool write, int *locked)\n"
} |
| 19/2 | 2026/08/12 16:56 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 22016 output: 35 thoughts: 48 Thoughts: Okay, here's my take on those thoughts, summarizing them from an expert's perspective: **Analyzing Page Fault Handling** Alright, so `faultin_page_range` is definitely non-static, good. And, similarly, `populate_vma_page_range` isn't static either. That's what I expected based on the broader design. Now, I should shift my focus to examining `faultin_vma_page_range`. I need to understand its role and how it interacts with the other functions we've identified as non-static, to continue developing the system efficiently. I'll need to double check how the variables interact and whether they will interfere with each other. This is likely the next piece of the puzzle to understand fully. |
| 20/2 | 2026/08/12 16:56 | tool | grepper |
0mError:no matches Args: {
"Expression": "faultin_vma_page_range",
"PathPrefix": "mm/gup.c"
}Results: {
"error": "no matches"
} |
| 21/2 | 2026/08/12 16:56 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 22276 output: 28 thoughts: 36 Thoughts: **Analyzing the `populate_vma_page_range` Function** Okay, so `populate_vma_page_range` isn't static. That immediately raises a flag for me. I need to understand the scope and implications of this non-static declaration within the current context. Let's dig into the codebase and find all the places where this function is actually being *used*. Understanding the call sites is crucial to grasping its intended behavior and potential side effects within the larger virtual memory management framework. Let's get to it. |
| 22/2 | 2026/08/12 16:56 | tool | grepper |
0mArgs:{
"Expression": "populate_vma_page_range",
"PathPrefix": ""
}Results: {
"Output": "Documentation/mm/unevictable-lru.rst=476=the mmap() handler and task address space expansion functions call\nDocumentation/mm/unevictable-lru.rst:477:populate_vma_page_range() specifying the vma and the address range to mlock.\nDocumentation/mm/unevictable-lru.rst-478-\n--\nmm/gup.c=1750=static __always_inline long __get_user_pages_locked(struct mm_struct *mm,\n--\nmm/gup.c-1894-/**\nmm/gup.c:1895: * populate_vma_page_range() - populate a range of pages in the vma.\nmm/gup.c-1896- * @vma: target vma\n--\nmm/gup.c-1913- */\nmm/gup.c:1914:long populate_vma_page_range(struct vm_area_struct *vma,\nmm/gup.c-1915-\t\tunsigned long start, unsigned long end, int *locked)\n--\nmm/gup.c=2026=int __mm_populate(unsigned long start, unsigned long len, int ignore_errors)\n--\nmm/gup.c-2059-\t\t/*\nmm/gup.c:2060:\t\t * Now fault in a range of pages. populate_vma_page_range()\nmm/gup.c-2061-\t\t * double checks the vma flags, so that it won't mlock pages\n--\nmm/gup.c-2063-\t\t */\nmm/gup.c:2064:\t\tret = populate_vma_page_range(vma, nstart, nend, \u0026locked);\nmm/gup.c-2065-\t\tif (ret \u003c 0) {\n--\nmm/internal.h=1119=void unmap_mapping_folio(struct folio *folio);\nmm/internal.h:1120:extern long populate_vma_page_range(struct vm_area_struct *vma,\nmm/internal.h-1121-\t\tunsigned long start, unsigned long end, int *locked);\n--\nmm/mlock.c=467=static int mlock_fixup(struct vma_iterator *vmi, struct vm_area_struct *vma,\n--\nmm/mlock.c-504-\t * It's okay if try_to_unmap_one unmaps a page just after we\nmm/mlock.c:505:\t * set VM_LOCKED, populate_vma_page_range will bring it back.\nmm/mlock.c-506-\t */\n--\nmm/mmap.c=961=struct vm_area_struct *find_extend_vma_locked(struct mm_struct *mm, unsigned long addr)\n--\nmm/mmap.c-973-\tif (prev-\u003evm_flags \u0026 VM_LOCKED)\nmm/mmap.c:974:\t\tpopulate_vma_page_range(prev, addr, prev-\u003evm_end, NULL);\nmm/mmap.c-975-\treturn prev;\n--\nmm/mmap.c=983=struct vm_area_struct *find_extend_vma_locked(struct mm_struct *mm, unsigned long addr)\n--\nmm/mmap.c-997-\tif (vma-\u003evm_flags \u0026 VM_LOCKED)\nmm/mmap.c:998:\t\tpopulate_vma_page_range(vma, addr, start, NULL);\nmm/mmap.c-999-\treturn vma;\n--\nmm/mprotect.c=725=mprotect_fixup(struct vma_iterator *vmi, struct mmu_gather *tlb,\n--\nmm/mprotect.c-819-\t !vma_flags_test_any(\u0026old_vma_flags, VMA_WRITE_BIT, VMA_SHARED_BIT))\nmm/mprotect.c:820:\t\tpopulate_vma_page_range(vma, start, end, NULL);\nmm/mprotect.c-821-\n"
} |
| 23/2 | 2026/08/12 16:56 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 23501 output: 26 thoughts: 88 Thoughts: Here's my understanding of the situation, from an expert's perspective: **Memory Population Strategies and Their Drivers** Okay, so I'm looking at how memory is populated within the virtual memory area (VMA) management code. It looks like `populate_vma_page_range` is a key function. I see that it's the workhorse called by several important functions: `__mm_populate`, which is the core handler for the `MAP_POPULATE` flag during `mmap` calls and `mlock` operations (that's crucial for pinning memory), and then also `mprotect_fixup` which is used during page protection adjustments. It's also called by `find_extend_vma_locked`, which has to do with VMA expansion and merging. Interesting. So, `populate_vma_page_range` seems to be the function handling the *eager* population of pages. On the other hand, `faultin_page_range` is used in a more reactive context. It's invoked by `madvise` specifically when using the `MADV_POPULATE_READ` and `MADV_POPULATE_WRITE` flags. This indicates a demand-paging strategy, but with a hint to pre-fault pages. It's a nice distinction, `populate_vma_page_range` deals with upfront, eager population while `faultin_page_range` deals with hint-driven faulting. I can see the potential for some interesting performance implications depending on how these are used in the broader system. I'll need to dig deeper into the contexts and usage patterns to understand the trade-offs. |
| 24/2 | 2026/08/12 16:56 | tool | grepper |
0mArgs:{
"Expression": "faultin_page_range",
"PathPrefix": ""
}Results: {
"Output": "mm/gup.c=1914=long populate_vma_page_range(struct vm_area_struct *vma,\n--\nmm/gup.c-1967-/*\nmm/gup.c:1968: * faultin_page_range() - populate (prefault) page tables inside the\nmm/gup.c-1969- *\t\t\t given range readable/writable\n--\nmm/gup.c-1987- */\nmm/gup.c:1988:long faultin_page_range(struct mm_struct *mm, unsigned long start,\nmm/gup.c-1989-\t\t\tunsigned long end, bool write, int *locked)\n--\nmm/internal.h=1120=extern long populate_vma_page_range(struct vm_area_struct *vma,\nmm/internal.h-1121-\t\tunsigned long start, unsigned long end, int *locked);\nmm/internal.h:1122:extern long faultin_page_range(struct mm_struct *mm, unsigned long start,\nmm/internal.h-1123-\t\tunsigned long end, bool write, int *locked);\n--\nmm/madvise.c=957=static long madvise_populate(struct madvise_behavior *madv_behavior)\n--\nmm/madvise.c-967-\t\t/* Populate (prefault) page tables readable/writable. */\nmm/madvise.c:968:\t\tpages = faultin_page_range(mm, start, end, write, \u0026locked);\nmm/madvise.c-969-\t\tif (!locked) {\n"
} |
| 25/2 | 2026/08/12 16:56 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 24390 output: 28 thoughts: 73 Thoughts: Okay, let's break this down. **Potential Page-Fault Handlers and Page Pinning Functions** Right, so I'm looking at potential starting points for this, and two names immediately jump out: `faultin_page_range` and `populate_vma_page_range`. They seem like they'd be directly involved in the process of dealing with page faults and populating memory. Promising. Beyond those, I've got a mental note to check out `get_user_pages`, `get_user_pages_remote`, `pin_user_pages`, and `pin_user_pages_remote`. They're related to user-space page access and pinning, which seems relevant. Given their functions, they likely act upon some of the fundamentals of a page table. Now, before I dig any deeper, I should also take a look at `get_user_pages_unlocked`. It’s probably worth investigating that function in particular, since the 'unlocked' variant might offer insights into how they handle locking within that particular process. I'll need to understand if it’s handling concurrency at all, but I’m going to consider it a high-priority candidate as a starting point. |
| 26/2 | 2026/08/12 16:56 | tool | grepper |
0mArgs:{
"Expression": "get_user_pages_unlocked",
"PathPrefix": ""
}Results: {
"Output": "drivers/virt/nitro_enclaves/ne_misc_dev.c=914=static int ne_set_user_memory_region_ioctl(struct ne_enclave *ne_enclave,\n--\ndrivers/virt/nitro_enclaves/ne_misc_dev.c-962-\ndrivers/virt/nitro_enclaves/ne_misc_dev.c:963:\t\tgup_rc = get_user_pages_unlocked(mem_region.userspace_addr + memory_size, 1,\ndrivers/virt/nitro_enclaves/ne_misc_dev.c-964-\t\t\t\t\t\t ne_mem_region-\u003epages + i, FOLL_GET);\n--\ninclude/linux/mm.h=3270=long pin_user_pages(unsigned long start, unsigned long nr_pages,\ninclude/linux/mm.h-3271-\t\t unsigned int gup_flags, struct page **pages);\ninclude/linux/mm.h:3272:long get_user_pages_unlocked(unsigned long start, unsigned long nr_pages,\ninclude/linux/mm.h-3273-\t\t struct page **pages, unsigned int gup_flags);\n--\nmm/gup.c=2756=EXPORT_SYMBOL(get_user_pages);\n--\nmm/gup.c-2758-/*\nmm/gup.c:2759: * get_user_pages_unlocked() is suitable to replace the form:\nmm/gup.c-2760- *\n--\nmm/gup.c-2766- *\nmm/gup.c:2767: * get_user_pages_unlocked(mm, ..., pages);\nmm/gup.c-2768- *\n--\nmm/gup.c-2772- */\nmm/gup.c:2773:long get_user_pages_unlocked(unsigned long start, unsigned long nr_pages,\nmm/gup.c-2774-\t\t\t struct page **pages, unsigned int gup_flags)\n--\nmm/gup.c-2784-}\nmm/gup.c:2785:EXPORT_SYMBOL(get_user_pages_unlocked);\nmm/gup.c-2786-\n--\nmm/gup.c=3487=EXPORT_SYMBOL(pin_user_pages);\n--\nmm/gup.c-3490- * pin_user_pages_unlocked() is the FOLL_PIN variant of\nmm/gup.c:3491: * get_user_pages_unlocked(). Behavior is the same, except that this one sets\nmm/gup.c-3492- * FOLL_PIN and rejects FOLL_GET.\n--\nvirt/kvm/kvm_main.c=2883=static int hva_to_pfn_slow(struct kvm_follow_pfn *kfp, kvm_pfn_t *pfn)\n--\nvirt/kvm/kvm_main.c-2902-\telse\nvirt/kvm/kvm_main.c:2903:\t\tnpages = get_user_pages_unlocked(kfp-\u003ehva, 1, \u0026page, flags);\nvirt/kvm/kvm_main.c-2904-\tif (npages != 1)\n"
} |
| 27/2 | 2026/08/12 16:56 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 25587 output: 26 thoughts: 932 Thoughts: **My Assessment of the GUP Patch for Fuzzing** Okay, let's break this down. My goal is to identify the most effective targets for fuzzing a recent kernel patch that refactors the Get User Pages (GUP) logic. I need to balance coverage with avoiding overly generic functions that will skew results. First, I need to list out the relevant functions. Based on the patch analysis and knowing that I should prefer non-static, non-inlined API entrypoints, the initial candidates are: `get_user_pages`, `get_user_pages_remote`, `get_user_pages_unlocked`, `pin_user_pages`, `pin_user_pages_remote`, `pin_user_pages_unlocked`, `faultin_page_range`, and `populate_vma_page_range`. I know that `__get_user_pages` is the static core, so the exported functions are the correct choice. Now, which configurations are relevant? The patch uses `#ifdef CONFIG_PGTABLE_HAS_HUGE_LEAVES` and `#ifdef CONFIG_TRANSPARENT_HUGEPAGE`, and based on the config dependencies, I know that `TRANSPARENT_HUGEPAGE` and `HUGETLBFS` are sufficient to enable this, so those are the key ones to enable. It's a generic `mm/gup.c` file, so no architecture-specific concerns. The initial summary would be: * **WorthFuzzing**: `true` * **FocusSymbols**: `get_user_pages`, `get_user_pages_remote`, `get_user_pages_unlocked`, `pin_user_pages`, `pin_user_pages_remote`, `pin_user_pages_unlocked`, `faultin_page_range`, `populate_vma_page_range` * **EnableConfigs**: `TRANSPARENT_HUGEPAGE`, `HUGETLBFS` * **Reasoning**: Significant functional change. But I need to be a bit more nuanced. I need to avoid very hot paths. `get_user_pages` is *the* main entry point for GUP, so I have to include it. And I'll leave `pin_user_pages` too. `get_user_pages` itself has driver calls, but it's not likely to be as generic as `kmalloc` or `schedule`. The patch primarily deals with DMA setups and memory pinning. `faultin_page_range` and `populate_vma_page_range` are more specific, triggered by `madvise` and `mmap(MAP_POPULATE) / mlock`, respectively. `get_user_pages_remote` is also important because it deals with `ptrace` and `process_vm_readv`, which are definitely good fuzzing targets. So, taking the hot-path consideration into account, the final selection is: * **WorthFuzzing**: `true` * **FocusSymbols**: `get_user_pages`, `get_user_pages_remote`, `pin_user_pages`, `pin_user_pages_remote`, `faultin_page_range`, `populate_vma_page_range` * **EnableConfigs**: `TRANSPARENT_HUGEPAGE`, `HUGETLBFS` * **Reasoning**: The patch refactors the core Get User Pages (GUP) logic to batch page table walks, touching multiple important subsystems. Fuzzing should focus on the exported GUP API entry points, balancing coverage with targeted functions. |
| 28/2 | 2026/08/12 16:56 | tool | grepper |
0mArgs:{
"Expression": "get_user_pages\\(",
"PathPrefix": ""
}Results: {
"Output": "Documentation/admin-guide/mm/idle_page_tracking.rst=84=latter happens when:\n--\nDocumentation/admin-guide/mm/idle_page_tracking.rst-92-\nDocumentation/admin-guide/mm/idle_page_tracking.rst:93: - a page is accessed by a device driver using get_user_pages()\nDocumentation/admin-guide/mm/idle_page_tracking.rst-94-\n--\nDocumentation/core-api/cachetlb.rst=253=maps this page at its virtual address.\n--\nDocumentation/core-api/cachetlb.rst-353-\tpage, it calls this function (currently only\nDocumentation/core-api/cachetlb.rst:354:\tget_user_pages()). Note: flush_dcache_folio() deliberately\nDocumentation/core-api/cachetlb.rst-355-\tdoesn't work for an anonymous page. The default\n--\nDocumentation/core-api/pin_user_pages.rst=113=counter that increments 10 bits at a time.\n--\nDocumentation/core-api/pin_user_pages.rst-121-* Callers must specifically request \"dma-pinned tracking of pages\". In other\nDocumentation/core-api/pin_user_pages.rst:122: words, just calling get_user_pages() will not suffice; a new set of functions,\nDocumentation/core-api/pin_user_pages.rst-123- pin_user_page() and related, must be used.\n--\nDocumentation/core-api/pin_user_pages.rst=194=INCORRECT (uses FOLL_GET calls):\nDocumentation/core-api/pin_user_pages.rst:195: get_user_pages()\nDocumentation/core-api/pin_user_pages.rst-196- write to the data within the pages\n--\nDocumentation/core-api/pin_user_pages.rst=245=long-term [R]DMA pins in place, or during pin/unpin transitions.\n--\nDocumentation/core-api/pin_user_pages.rst-249- pinned once for each page (head page and each tail page) within the huge page.\nDocumentation/core-api/pin_user_pages.rst:250: This follows the same sort of behavior that get_user_pages() uses for huge\nDocumentation/core-api/pin_user_pages.rst-251- pages: the head page is refcounted once for each tail or head page in the huge\nDocumentation/core-api/pin_user_pages.rst:252: page, when get_user_pages() is applied to a huge page.\nDocumentation/core-api/pin_user_pages.rst-253-\n--\nDocumentation/core-api/pin_user_pages.rst=278=References\n--\nDocumentation/core-api/pin_user_pages.rst-280-\nDocumentation/core-api/pin_user_pages.rst:281:* `Some slow progress on get_user_pages() (Apr 2, 2019) \u003chttps://lwn.net/Articles/784574/\u003e`_\nDocumentation/core-api/pin_user_pages.rst:282:* `DMA and get_user_pages() (LPC: Dec 12, 2018) \u003chttps://lwn.net/Articles/774411/\u003e`_\nDocumentation/core-api/pin_user_pages.rst:283:* `The trouble with get_user_pages() (Apr 30, 2018) \u003chttps://lwn.net/Articles/753027/\u003e`_\nDocumentation/core-api/pin_user_pages.rst:284:* `LWN kernel index: get_user_pages() \u003chttps://lwn.net/Kernel/Index/#Memory_management-get_user_pages\u003e`_\nDocumentation/core-api/pin_user_pages.rst-285-\n--\nDocumentation/filesystems/dax.rst=289=mapped caches such as ARM, MIPS and SPARC.\nDocumentation/filesystems/dax.rst-290-\nDocumentation/filesystems/dax.rst:291:Calling :c:func:`get_user_pages()` on a range of user memory that has been\nDocumentation/filesystems/dax.rst-292-mmapped from a `DAX` file will fail when there are no 'struct page' to describe\n--\nDocumentation/filesystems/fuse/fuse.rst=438=while the page(s) belonging to the write buffer are faulted with\nDocumentation/filesystems/fuse/fuse.rst:439:get_user_pages(). The 'req-\u003elocked' flag indicates when the copy is\nDocumentation/filesystems/fuse/fuse.rst-440-taking place, and abort is delayed until this flag is unset.\n--\nDocumentation/filesystems/locking.rst=641=an error.\nDocumentation/filesystems/locking.rst-642-\nDocumentation/filesystems/locking.rst:643:-\u003eaccess() is called when get_user_pages() fails in\nDocumentation/filesystems/locking.rst-644-access_process_vm(), typically used to debug a process through\n--\nDocumentation/gpu/drm-vm-bind-locking.rst=416=Now, the method of obtaining struct page references using\nDocumentation/gpu/drm-vm-bind-locking.rst:417:get_user_pages() unfortunately can't be used under a dma_resv lock\nDocumentation/gpu/drm-vm-bind-locking.rst-418-since that would violate the locking order of the dma_resv lock vs the\n--\nDocumentation/infiniband/user_verbs.rst=47=Memory pinning\n--\nDocumentation/infiniband/user_verbs.rst-52- ib_uverbs module manages pinning and unpinning memory regions via\nDocumentation/infiniband/user_verbs.rst:53: get_user_pages() and put_page() calls. It also accounts for the\nDocumentation/infiniband/user_verbs.rst-54- amount of memory pinned in the process's pinned_vm, and checks that\n--\nDocumentation/mm/unevictable-lru.rst=304=Before returning from the system call, do_mlock() or mlockall() will call\nDocumentation/mm/unevictable-lru.rst:305:__mm_populate() to fault in the remaining pages via get_user_pages() and to\nDocumentation/mm/unevictable-lru.rst-306-mark those pages as mlocked as they are faulted.\n--\nDocumentation/mm/unevictable-lru.rst=308=Note that the VMA being mlocked might be mapped with PROT_NONE. In this case,\nDocumentation/mm/unevictable-lru.rst:309:get_user_pages() will be unable to fault in the pages. That's okay. If pages\nDocumentation/mm/unevictable-lru.rst-310-do end up getting faulted into this VM_LOCKED VMA, they will be handled in the\n--\nDocumentation/mm/unevictable-lru.rst=340=mlock_fixup() filters several classes of \"special\" VMAs:\n--\nDocumentation/mm/unevictable-lru.rst-344- mlocked. In any case, most of the pages have no struct page in which to so\nDocumentation/mm/unevictable-lru.rst:345: mark the page. Because of this, get_user_pages() will fail for these VMAs,\nDocumentation/mm/unevictable-lru.rst-346- so there is no sense in attempting to visit them.\n--\nDocumentation/translations/zh_CN/core-api/cachetlb.rst=109=HyperSparc cpu就是这样一个具有这种属性的cpu。\n--\nDocumentation/translations/zh_CN/core-api/cachetlb.rst-302-\t当内核需要访问一个匿名页的内容时,它会调用这个函数(目前只有\nDocumentation/translations/zh_CN/core-api/cachetlb.rst:303:\tget_user_pages())。注意:flush_dcache_page()故意对匿名页不起作\nDocumentation/translations/zh_CN/core-api/cachetlb.rst-304-\t用。默认的实现是nop(对于所有相干的架构应该保持这样)。对于不一致性\n--\nDocumentation/translations/zh_CN/infiniband/user_verbs.rst-53- 直接的用户空间I/O要求与作为潜在I/O目标的内存区域保持在同一物理地址上。ib_uverbs\nDocumentation/translations/zh_CN/infiniband/user_verbs.rst:54: 模块通过get_user_pages()和put_page()调用来管理内存区域的固定和解除固定。它还核\nDocumentation/translations/zh_CN/infiniband/user_verbs.rst-55- 算进程的pinned_vm中被固定的内存量,并检查非特权进程是否超过其RLIMIT_MEMLOCK限制。\n--\narch/arm/mm/flush.c=373=EXPORT_SYMBOL(flush_dcache_page);\narch/arm/mm/flush.c-374-/*\narch/arm/mm/flush.c:375: * Flush an anonymous page so that users of get_user_pages()\narch/arm/mm/flush.c-376- * can safely access the data. The expected sequence is:\narch/arm/mm/flush.c-377- *\narch/arm/mm/flush.c:378: * get_user_pages()\narch/arm/mm/flush.c-379- * -\u003e flush_anon_page\n--\narch/loongarch/kvm/mmu.c=772=static int kvm_map_page(struct kvm_vcpu *vcpu, unsigned long gpa, bool write)\n--\narch/loongarch/kvm/mmu.c-810-\t * Ensure the read of mmu_invalidate_seq isn't reordered with PTE reads in\narch/loongarch/kvm/mmu.c:811:\t * kvm_faultin_pfn() (which calls get_user_pages()), so that we don't\narch/loongarch/kvm/mmu.c-812-\t * risk the page we get a reference to getting unmapped before we have a\n--\narch/mips/kvm/mmu.c=547=static int kvm_mips_map_page(struct kvm_vcpu *vcpu, unsigned long gpa,\n--\narch/mips/kvm/mmu.c-581-\t * Ensure the read of mmu_invalidate_seq isn't reordered with PTE reads\narch/mips/kvm/mmu.c:582:\t * in kvm_faultin_pfn() (which calls get_user_pages()), so that we don't\narch/mips/kvm/mmu.c-583-\t * risk the page we get a reference to getting unmapped before we have a\n--\narch/powerpc/kvm/book3s_hv.c=678=static void kvmppc_update_vpa(struct kvm_vcpu *vcpu, struct kvmppc_vpa *vpap,\n--\narch/powerpc/kvm/book3s_hv.c-688-\t * but we can't call kvmppc_pin_guest_page under the lock\narch/powerpc/kvm/book3s_hv.c:689:\t * as it does get_user_pages() and down_read(). So we\narch/powerpc/kvm/book3s_hv.c-690-\t * have to drop the lock, pin the page, then get the lock\n--\narch/sparc/kernel/ptrace_64.c=91=void ptrace_disable(struct task_struct *child)\n--\narch/sparc/kernel/ptrace_64.c-96-/* To get the necessary page struct, access_process_vm() first calls\narch/sparc/kernel/ptrace_64.c:97: * get_user_pages(). This has done a flush_dcache_page() on the\narch/sparc/kernel/ptrace_64.c-98- * accessed page. Then our caller (copy_{to,from}_user_page()) did\n--\narch/x86/kernel/cpu/sgx/ioctl.c=207=static int __sgx_encl_add_page(struct sgx_encl *encl,\n--\narch/x86/kernel/cpu/sgx/ioctl.c-224-\narch/x86/kernel/cpu/sgx/ioctl.c:225:\tret = get_user_pages(src, 1, 0, \u0026src_page);\narch/x86/kernel/cpu/sgx/ioctl.c-226-\tif (ret \u003c 1)\n--\narch/xtensa/mm/cache.c=216=void update_mmu_cache_range(struct vm_fault *vmf, struct vm_area_struct *vma,\n--\narch/xtensa/mm/cache.c-266-/*\narch/xtensa/mm/cache.c:267: * access_process_vm() has called get_user_pages(), which has done a\narch/xtensa/mm/cache.c-268- * flush_dcache_page() on the page.\n--\nblock/bio.c=1650=EXPORT_SYMBOL(bio_free_pages);\n--\nblock/bio.c-1662- * Note that this code is very hard to test under normal circumstances because\nblock/bio.c:1663: * direct-io pins the pages with get_user_pages(). This makes\nblock/bio.c-1664- * is_page_cache_freeable return false, and the VM will not clean the pages.\n--\ndrivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c=1061=static int init_user_pages(struct kgd_mem *mem, uint64_t user_addr,\n--\ndrivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c-1104-\ndrivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c:1105:\tret = amdgpu_ttm_tt_get_user_pages(bo, range);\ndrivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c-1106-\tif (ret) {\n--\ndrivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c=2542=static int update_invalid_user_pages(struct amdkfd_process_info *process_info,\n--\ndrivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c-2600-\t\t/* Get updated user pages */\ndrivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c:2601:\t\tret = amdgpu_ttm_tt_get_user_pages(bo, mem-\u003erange);\ndrivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd_gpuvm.c-2602-\t\tif (ret) {\n--\ndrivers/gpu/drm/amd/amdgpu/amdgpu_cs.c=844=static int amdgpu_cs_parser_bos(struct amdgpu_cs_parser *p,\n--\ndrivers/gpu/drm/amd/amdgpu/amdgpu_cs.c-887-\ndrivers/gpu/drm/amd/amdgpu/amdgpu_cs.c:888:\t\tr = amdgpu_ttm_tt_get_user_pages(bo, e-\u003erange);\ndrivers/gpu/drm/amd/amdgpu/amdgpu_cs.c-889-\t\tif (r)\n--\ndrivers/gpu/drm/amd/amdgpu/amdgpu_gem.c=495=int amdgpu_gem_userptr_ioctl(struct drm_device *dev, void *data,\n--\ndrivers/gpu/drm/amd/amdgpu/amdgpu_gem.c-550-\t\t\treturn -ENOMEM;\ndrivers/gpu/drm/amd/amdgpu/amdgpu_gem.c:551:\t\tr = amdgpu_ttm_tt_get_user_pages(bo, range);\ndrivers/gpu/drm/amd/amdgpu/amdgpu_gem.c-552-\t\tif (r) {\n--\ndrivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c=722=struct amdgpu_ttm_tt {\n--\ndrivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c-743- */\ndrivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c:744:int amdgpu_ttm_tt_get_user_pages(struct amdgpu_bo *bo,\ndrivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c-745-\t\t\t\t struct amdgpu_hmm_range *range)\n--\ndrivers/gpu/drm/amd/amdgpu/amdgpu_ttm.h=207=uint64_t amdgpu_ttm_domain_start(struct amdgpu_device *adev, uint32_t type);\n--\ndrivers/gpu/drm/amd/amdgpu/amdgpu_ttm.h-209-#if IS_ENABLED(CONFIG_DRM_AMDGPU_USERPTR)\ndrivers/gpu/drm/amd/amdgpu/amdgpu_ttm.h:210:int amdgpu_ttm_tt_get_user_pages(struct amdgpu_bo *bo,\ndrivers/gpu/drm/amd/amdgpu/amdgpu_ttm.h-211-\t\t\t\t struct amdgpu_hmm_range *range);\ndrivers/gpu/drm/amd/amdgpu/amdgpu_ttm.h-212-#else\ndrivers/gpu/drm/amd/amdgpu/amdgpu_ttm.h:213:static inline int amdgpu_ttm_tt_get_user_pages(struct amdgpu_bo *bo,\ndrivers/gpu/drm/amd/amdgpu/amdgpu_ttm.h-214-\t\t\t\t\t struct amdgpu_hmm_range *range)\n--\ndrivers/gpu/drm/amd/amdgpu/amdgpu_userq.c=958=amdgpu_userq_vm_validate(struct amdgpu_userq_mgr *uq_mgr)\n--\ndrivers/gpu/drm/amd/amdgpu/amdgpu_userq.c-1059-\t\t\tbo = range-\u003ebo;\ndrivers/gpu/drm/amd/amdgpu/amdgpu_userq.c:1060:\t\t\tret = amdgpu_ttm_tt_get_user_pages(bo, range);\ndrivers/gpu/drm/amd/amdgpu/amdgpu_userq.c-1061-\t\t\tif (ret)\n--\ndrivers/gpu/drm/radeon/radeon_ttm.c=323=static int radeon_ttm_tt_pin_userptr(struct ttm_device *bdev, struct ttm_tt *ttm)\n--\ndrivers/gpu/drm/radeon/radeon_ttm.c-351-\ndrivers/gpu/drm/radeon/radeon_ttm.c:352:\t\tr = get_user_pages(userptr, num_pages, write ? FOLL_WRITE : 0,\ndrivers/gpu/drm/radeon/radeon_ttm.c-353-\t\t\t\t pages);\n--\ndrivers/misc/sgi-gru/grufault.c=177=static int non_atomic_pte_lookup(struct vm_area_struct *vma,\n--\ndrivers/misc/sgi-gru/grufault.c-187-#endif\ndrivers/misc/sgi-gru/grufault.c:188:\tif (get_user_pages(vaddr, 1, write ? FOLL_WRITE : 0, \u0026page) \u003c= 0)\ndrivers/misc/sgi-gru/grufault.c-189-\t\treturn -EFAULT;\n--\nfs/dax.c=687=static void *grab_mapping_entry(struct xa_state *xas,\n--\nfs/dax.c-783- * any page in the mapping is busy, i.e. for DMA, or other\nfs/dax.c:784: * get_user_pages() usages.\nfs/dax.c-785- *\n--\nfs/dax.c=791=struct page *dax_layout_busy_page_range(struct address_space *mapping,\n--\nfs/dax.c-813-\t * against is no longer mapped in the page tables and bail to the\nfs/dax.c:814:\t * get_user_pages() slow path. The slow path is protected by\nfs/dax.c-815-\t * pte_lock() and pmd_lock(). New references are not taken without\n--\nfs/direct-io.c=244=static ssize_t dio_complete(struct dio *dio, ssize_t ret, unsigned int flags)\n--\nfs/direct-io.c-287-\t * Try again to invalidate clean pages which might have been cached by\nfs/direct-io.c:288:\t * non-direct readahead, or faulted in by get_user_pages() if the source\nfs/direct-io.c-289-\t * of the write was an mmap'ed region of the file we're writing. Either\n--\nfs/direct-io.c=899=static int do_direct_IO(struct dio *dio, struct dio_submit *sdio,\n--\nfs/direct-io.c-1046-\nfs/direct-io.c:1047:\t\t/* Drop the pin which was taken in get_user_pages() */\nfs/direct-io.c-1048-\t\tdio_unpin_page(dio, page);\n--\nfs/exec.c=402=static int bprm_stack_limits(struct linux_binprm *bprm)\n--\nfs/exec.c-448- * 'copy_strings()' copies argument/environment strings from the old\nfs/exec.c:449: * processes's memory to the new process's stack. The call to get_user_pages()\nfs/exec.c-450- * ensures the destination page is created and not swapped out.\n--\nfs/fuse/dax.c=912=inode_inline_reclaim_one_dmap(struct fuse_conn_dax *fcd, struct inode *inode,\n--\nfs/fuse/dax.c-937-\t * Make sure there are no references to inode pages using\nfs/fuse/dax.c:938:\t * get_user_pages()\nfs/fuse/dax.c-939-\t */\n--\nfs/fuse/file.c=1561=static inline size_t fuse_get_frag_size(const struct iov_iter *ii,\n--\nfs/fuse/file.c-1566-\nfs/fuse/file.c:1567:static int fuse_get_user_pages(struct fuse_args_pages *ap, struct iov_iter *ii,\nfs/fuse/file.c-1568-\t\t\t size_t *nbytesp, int write,\n--\nfs/fuse/file.c=1665=ssize_t fuse_direct_io(struct fuse_io_priv *io, struct iov_iter *iter,\n--\nfs/fuse/file.c-1719-\nfs/fuse/file.c:1720:\t\terr = fuse_get_user_pages(\u0026ia-\u003eap, iter, \u0026nbytes, write,\nfs/fuse/file.c-1721-\t\t\t\t\t max_pages, fc-\u003euse_pages_for_kvec_io);\n--\nfs/iomap/direct-io.c=104=ssize_t iomap_dio_complete(struct iomap_dio *dio)\n--\nfs/iomap/direct-io.c-127-\t * Try again to invalidate clean pages which might have been cached by\nfs/iomap/direct-io.c:128:\t * non-direct readahead, or faulted in by get_user_pages() if the source\nfs/iomap/direct-io.c-129-\t * of the write was an mmap'ed region of the file we're writing. Either\n--\nfs/nfs/direct.c=326=static const struct nfs_pgio_completion_ops nfs_direct_read_completion_ops = {\n--\nfs/nfs/direct.c-333- * For each rsize'd chunk of the user's buffer, dispatch an NFS READ\nfs/nfs/direct.c:334: * operation. If nfs_readdata_alloc() or get_user_pages() fails,\nfs/nfs/direct.c-335- * bail and stop sending more reads. Read length accounting is\n--\nfs/nfs/direct.c=852=static const struct nfs_pgio_completion_ops nfs_direct_write_completion_ops = {\n--\nfs/nfs/direct.c-865- * For each wsize'd chunk of the user's buffer, dispatch an NFS WRITE\nfs/nfs/direct.c:866: * operation. If nfs_writedata_alloc() or get_user_pages() fails,\nfs/nfs/direct.c-867- * bail and stop sending more writes. Write length accounting is\n--\nfs/xfs/xfs_inode.c=96=xfs_lock_flags_assert(\n--\nfs/xfs/xfs_inode.c-128- * can fault in pages during copy in/out (for buffered IO) or require the\nfs/xfs/xfs_inode.c:129: * mmap_lock in get_user_pages() to map the user pages into the kernel address\nfs/xfs/xfs_inode.c-130- * space for direct IO. Similarly the i_rwsem cannot be taken inside a page\n--\ninclude/linux/mm.h=783=struct vm_operations_struct {\n--\ninclude/linux/mm.h-834-\ninclude/linux/mm.h:835:\t/* called by access_process_vm when get_user_pages() fails, typically\ninclude/linux/mm.h-836-\t * for use by special VMAs. See also generic_access_phys() for a generic\n--\ninclude/linux/mm.h=2186=static inline void put_page(struct page *page)\n--\ninclude/linux/mm.h-2222- * Locking: the lockless algorithm described in folio_try_get_rcu()\ninclude/linux/mm.h:2223: * provides safe operation for get_user_pages(), folio_mkclean() and\ninclude/linux/mm.h-2224- * other calls that race to set up page table entries.\n--\ninclude/linux/mm.h=3241=static inline struct page *get_user_page_vma_remote(struct mm_struct *mm,\n--\ninclude/linux/mm.h-3267-\ninclude/linux/mm.h:3268:long get_user_pages(unsigned long start, unsigned long nr_pages,\ninclude/linux/mm.h-3269-\t\t unsigned int gup_flags, struct page **pages);\n--\ninclude/uapi/linux/nitro_enclaves.h-146- * * NE_ERR_INVALID_FLAG_VALUE\t\t- The value of the provided flag is invalid.\ninclude/uapi/linux/nitro_enclaves.h:147: * * Error codes from get_user_pages().\ninclude/uapi/linux/nitro_enclaves.h-148- * * Error codes from the NE PCI device request.\n--\nkernel/events/uprobes.c=2400=static int is_trap_at_addr(struct mm_struct *mm, unsigned long vaddr)\n--\nkernel/events/uprobes.c-2415-\nkernel/events/uprobes.c:2416:\tresult = get_user_pages(vaddr, 1, FOLL_FORCE, \u0026page);\nkernel/events/uprobes.c-2417-\tif (result \u003c 0)\n--\nkernel/futex/core.c=502=int get_futex_key(u32 __user *uaddr, unsigned int flags, union futex_key *key,\n--\nkernel/futex/core.c-735- * disabled section so we can as well avoid the #PF overhead by\nkernel/futex/core.c:736: * calling get_user_pages() right away.\nkernel/futex/core.c-737- */\n--\nlib/iov_iter.c=129=EXPORT_SYMBOL(fault_in_iov_iter_readable);\n--\nlib/iov_iter.c-135- *\nlib/iov_iter.c:136: * Faults in the iterator using get_user_pages(), i.e., without triggering\nlib/iov_iter.c-137- * hardware page faults. This is primarily useful when we already know that\n--\nmm/Kconfig=1306=config GUP_TEST\nmm/Kconfig:1307:\tbool \"Enable infrastructure for get_user_pages()-related unit tests\"\nmm/Kconfig-1308-\tdepends on DEBUG_FS\n--\nmm/filemap.c=150=static void filemap_unaccount_folio(struct address_space *mapping,\n--\nmm/filemap.c-202-\t * But it's harmless on in-memory filesystems like tmpfs; and can\nmm/filemap.c:203:\t * occur when a driver which did get_user_pages() sets page dirty\nmm/filemap.c-204-\t * before putting it, while the inode is being finally evicted.\n--\nmm/filemap.c=4281=generic_file_direct_write(struct kiocb *iocb, struct iov_iter *from)\n--\nmm/filemap.c-4301-\t * Finally, try again to invalidate clean pages which might have been\nmm/filemap.c:4302:\t * cached by non-direct readahead, or faulted in by get_user_pages()\nmm/filemap.c-4303-\t * if the source of the write was an mmap'ed region of the file\n--\nmm/gup.c=247=static inline struct folio *gup_folio_next(struct page **list,\n--\nmm/gup.c-267- *\nmm/gup.c:268: * \"gup-pinned page\" refers to a page that has had one of the get_user_pages()\nmm/gup.c-269- * variants called on that page.\n--\nmm/gup.c=929=static unsigned long follow_pte_batch(struct vm_area_struct *vma,\n--\nmm/gup.c-953- * A failure on the first PTE is returned to the caller. A failure after that\nmm/gup.c:954: * is a short read; __get_user_pages() retrying the read will get the error.\nmm/gup.c-955- */\n--\nmm/gup.c=1402=static struct vm_area_struct *gup_vma_lookup(struct mm_struct *mm,\n--\nmm/gup.c-1437-/**\nmm/gup.c:1438: * __get_user_pages() - pin user pages in memory\nmm/gup.c-1439- * @mm:\t\tmm_struct of target mm\n--\nmm/gup.c-1490- */\nmm/gup.c:1491:static long __get_user_pages(struct mm_struct *mm,\nmm/gup.c-1492-\t\tunsigned long start, unsigned long nr_pages,\n--\nmm/gup.c=1613=static bool vma_permits_fault(struct vm_area_struct *vma,\n--\nmm/gup.c-1651- *\nmm/gup.c:1652: * The main difference with get_user_pages() is that this function will\nmm/gup.c-1653- * unconditionally call handle_mm_fault() which will in turn perform all the\nmm/gup.c-1654- * necessary SW fixup of the dirty and young bits in the PTE, while\nmm/gup.c:1655: * get_user_pages() only guarantees to update these in the struct page.\nmm/gup.c-1656- *\n--\nmm/gup.c=1726=static bool gup_signal_pending(unsigned int flags)\n--\nmm/gup.c-1746- *\nmm/gup.c:1747: * Please note that this function, unlike __get_user_pages(), will not return 0\nmm/gup.c-1748- * for nr_pages \u003e 0, unless FOLL_NOWAIT is used.\n--\nmm/gup.c=1750=static __always_inline long __get_user_pages_locked(struct mm_struct *mm,\n--\nmm/gup.c-1792-\tfor (;;) {\nmm/gup.c:1793:\t\tret = __get_user_pages(mm, start, nr_pages, flags, pages,\nmm/gup.c-1794-\t\t\t\t locked);\n--\nmm/gup.c-1852-\t\t*locked = 1;\nmm/gup.c:1853:\t\tret = __get_user_pages(mm, start, 1, flags | FOLL_TRIED,\nmm/gup.c-1854-\t\t\t\t pages, locked);\n--\nmm/gup.c=1914=long populate_vma_page_range(struct vm_area_struct *vma,\n--\nmm/gup.c-1960-\t */\nmm/gup.c:1961:\tret = __get_user_pages(mm, start, nr_pages, gup_flags,\nmm/gup.c-1962-\t\t\t NULL, locked ? locked : \u0026local_locked);\n--\nmm/gup.c-1979- * Returns either number of processed pages in the MM, or a negative error\nmm/gup.c:1980: * code on error (see __get_user_pages()). Note that this function reports\nmm/gup.c-1981- * errors related to VMAs, such as incompatible mappings, as expected by\n--\nmm/gup.c=2722=long get_user_pages_remote(struct mm_struct *mm,\n--\nmm/gup.c-2731-/**\nmm/gup.c:2732: * get_user_pages() - pin user pages in memory\nmm/gup.c-2733- * @start: starting user address\n--\nmm/gup.c-2744- */\nmm/gup.c:2745:long get_user_pages(unsigned long start, unsigned long nr_pages,\nmm/gup.c-2746-\t\t unsigned int gup_flags, struct page **pages)\n--\nmm/gup.c=2756=EXPORT_SYMBOL(get_user_pages);\n--\nmm/gup.c-2761- * mmap_read_lock(mm);\nmm/gup.c:2762: * get_user_pages(mm, ..., pages, NULL);\nmm/gup.c-2763- * mmap_read_unlock(mm);\n--\nmm/gup.c=3359=EXPORT_SYMBOL_GPL(get_user_pages_fast_only);\n--\nmm/gup.c-3370- * If not successful, it will fall back to taking the lock and\nmm/gup.c:3371: * calling get_user_pages().\nmm/gup.c-3372- *\n--\nmm/gup.c=3457=EXPORT_SYMBOL(pin_user_pages_remote);\n--\nmm/gup.c-3467- *\nmm/gup.c:3468: * Nearly the same as get_user_pages(), except that FOLL_TOUCH is not set, and\nmm/gup.c-3469- * FOLL_PIN is set.\n--\nmm/gup_test.c=101=static int __gup_test_ioctl(unsigned int cmd,\n--\nmm/gup_test.c-147-\t\tcase GUP_BASIC_TEST:\nmm/gup_test.c:148:\t\t\tnr = get_user_pages(addr, nr, gup-\u003egup_flags, pages + i);\nmm/gup_test.c-149-\t\t\tbreak;\n--\nmm/gup_test.c-166-\t\t\telse\nmm/gup_test.c:167:\t\t\t\tnr = get_user_pages(addr, nr, gup-\u003egup_flags,\nmm/gup_test.c-168-\t\t\t\t\t\t pages + i);\n--\nmm/hmm.c=631=static const struct mm_walk_ops hmm_walk_ops = {\n--\nmm/hmm.c-655- *\nmm/hmm.c:656: * This is similar to get_user_pages(), except that it can read the page tables\nmm/hmm.c-657- * without mutating them (ie causing faults).\n--\nmm/madvise.c=1887=static int madvise_do_behavior(unsigned long start, size_t len_in,\n--\nmm/madvise.c-1938- * MADV_DONTFORK - omit this area from child's address space when forking:\nmm/madvise.c:1939: *\t\ttypically, to avoid COWing pages pinned by get_user_pages().\nmm/madvise.c-1940- * MADV_DOFORK - cancel MADV_DONTFORK: no longer omit this area when forking.\n--\nmm/memfd.c=67=struct folio *memfd_alloc_folio(struct file *memfd, pgoff_t idx)\n--\nmm/memfd.c-147- * Setting SEAL_WRITE requires us to verify there's no pending writer. However,\nmm/memfd.c:148: * via get_user_pages(), drivers might have some pending I/O without any active\nmm/memfd.c-149- * user-space mappings (eg., direct-IO, AIO). Therefore, we look at all folios\n--\nmm/mlock.c=577=static unsigned long count_mm_mlocked_page_nr(struct mm_struct *mm,\n--\nmm/mlock.c-606-/*\nmm/mlock.c:607: * convert get_user_pages() return value to posix mlock() error\nmm/mlock.c-608- */\n--\nmm/pagewalk.c=800=int walk_page_mapping(struct address_space *mapping, pgoff_t first_index,\n--\nmm/pagewalk.c-881- * This function must *not* be used as a naive replacement for\nmm/pagewalk.c:882: * get_user_pages() / pin_user_pages(), especially not to perform DMA or\nmm/pagewalk.c-883- * to carelessly modify page content. This function may *only* be used to grab\n--\nmm/vmscan.c=679=static int __remove_mapping(struct address_space *mapping, struct folio *folio,\n--\nmm/vmscan.c-703-\t *\nmm/vmscan.c:704:\t * get_user_pages(\u0026page);\nmm/vmscan.c-705-\t * [user mapping goes away]\n--\nnet/rds/info.c=105=void rds_info_iter_unmap(struct rds_info_iterator *iter)\n--\nnet/rds/info.c-113-/*\nnet/rds/info.c:114: * get_user_pages() called flush_dcache_page() on the pages for us.\nnet/rds/info.c-115- */\n--\ntools/testing/selftests/iommu/iommufd.c=1812=TEST_F(iommufd_mock_domain, ro_unshare)\n--\ntools/testing/selftests/iommu/iommufd.c-1826-\t * There have been lots of changes to the \"unshare\" mechanism in\ntools/testing/selftests/iommu/iommufd.c:1827:\t * get_user_pages(), make sure it works right. The write to the page\ntools/testing/selftests/iommu/iommufd.c-1828-\t * after we map it for reading should not change the assigned PFN.\n--\ntools/testing/selftests/memfd/fuse_mnt.c-8- * pages during reads(). Due to the 1s delay of this file-system, this is a\ntools/testing/selftests/memfd/fuse_mnt.c:9: * nice way to test race-conditions against get_user_pages() in the kernel.\ntools/testing/selftests/memfd/fuse_mnt.c-10- *\n--\ntools/testing/selftests/memfd/fuse_test.c-3- * memfd GUP test-case\ntools/testing/selftests/memfd/fuse_test.c:4: * This tests memfd interactions with get_user_pages(). We require the\ntools/testing/selftests/memfd/fuse_test.c-5- * fuse_mnt.c program to provide a fake direct-IO FUSE mount-point for us. This\n--\ntools/testing/selftests/memfd/fuse_test.c-7- * read() on files in that file-system will pin the receive-buffer pages for at\ntools/testing/selftests/memfd/fuse_test.c:8: * least 1s via get_user_pages().\ntools/testing/selftests/memfd/fuse_test.c-9- *\n--\ntools/testing/vma/include/dup.h=632=struct vm_operations_struct {\n--\ntools/testing/vma/include/dup.h-683-\ntools/testing/vma/include/dup.h:684:\t/* called by access_process_vm when get_user_pages() fails, typically\ntools/testing/vma/include/dup.h-685-\t * for use by special VMAs. See also generic_access_phys() for a generic\n"
} |
| 29/2 | 2026/08/12 16:56 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 38053 output: 218 thoughts: 253 Thoughts: **My Assessment of the GUP Refactor** Okay, so I see this patch is tackling the core "Get User Pages" (GUP) logic within `mm/gup.c`. This is a *major* refactor, and it's a critical area. `get_user_pages` is the linchpin; it's the primary API used by a huge number of drivers, so it has to be on the list. I'll definitely be including that one. Looking at the other focus symbols, I see there's a family of functions: `get_user_pages_remote`, `get_user_pages_unlocked`, `pin_user_pages`, `pin_user_pages_remote`, `pin_user_pages_unlocked`, `faultin_page_range`, and `populate_vma_page_range`. They are all related to pinning or faulting in user pages, which are used to access user-space memory from the kernel. This also suggests that there are similar changes that were done. These all need to be carefully examined as well. The implications of batching page table walks for these are significant. And of course, considering the `TRANSPARENT_HUGEPAGE` and `HUGETLBFS` config options are also mentioned, I need to keep those in mind. Any changes here could have far-reaching effects when those features are enabled. The reasoning here is solid. The patch is fundamentally changing how GUP works – batching page table walks means improved performance, but also a potential for new edge cases and vulnerabilities to appear. Because this core memory management code is touched by so many system calls (mmap, madvise, ptrace, process_vm_readv), the attack surface is substantial. Fuzzing the GUP API entry points is the right approach. I'll use `set-results` to report this. My report will identify the significant changes and the focus needed for ensuring the code is correct. |
| 30/2 | 2026/08/12 16:56 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"TRANSPARENT_HUGEPAGE",
"HUGETLBFS"
],
"FocusSymbols": [
"get_user_pages",
"get_user_pages_remote",
"get_user_pages_unlocked",
"pin_user_pages",
"pin_user_pages_remote",
"pin_user_pages_unlocked",
"faultin_page_range",
"populate_vma_page_range"
],
"Reasoning": "The patch refactors the core Get User Pages (GUP) logic in `mm/gup.c` to batch page table walks and return multiple pages at once instead of one by one. This is a significant functional change to core memory management code that is reachable via many syscalls (e.g., mmap, madvise, ptrace, process_vm_readv). Fuzzing should focus on the exported GUP API entry points.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"TRANSPARENT_HUGEPAGE",
"HUGETLBFS"
],
"FocusSymbols": [
"get_user_pages",
"get_user_pages_remote",
"get_user_pages_unlocked",
"pin_user_pages",
"pin_user_pages_remote",
"pin_user_pages_unlocked",
"faultin_page_range",
"populate_vma_page_range"
],
"Reasoning": "The patch refactors the core Get User Pages (GUP) logic in `mm/gup.c` to batch page table walks and return multiple pages at once instead of one by one. This is a significant functional change to core memory management code that is reachable via many syscalls (e.g., mmap, madvise, ptrace, process_vm_readv). Fuzzing should focus on the exported GUP API entry points.",
"WorthFuzzing": true
} |
| 31/1 | 2026/08/12 16:56 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 32/2 | 2026/08/12 16:56 | agent | kmsan-evaluator |
0mModel:best-expensive Args: null Results: {
"KMSANReasoning": "The patch refactors Get User Pages (GUP) to batch page table walking and refcounting. It modifies core memory management routines but does not introduce new data structures copied to userspace, nor does it handle complex parsing where uninitialized memory could be leaked. Any bugs introduced by these changes (such as returning an incorrect number of processed pages, leading to uninitialized pointers in the `pages` array) would result in wild pointer dereferences, out-of-bounds accesses, or page reference imbalances. These types of errors are effectively caught by KASAN and standard kernel debugging tools. Therefore, a dedicated KMSAN session is not necessary.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: For your convenience, here is the diff of the changes:
commit 67e676e8b038e01ccdc6278576936d60d9b7ab84
Author: syz-cluster <triage@syzkaller.com>
Date: Wed Aug 12 16:55:11 2026 +0000
syz-cluster: applied patch under review
diff --git a/mm/gup.c b/mm/gup.c
index 0692119b79043..106806634b3c4 100644
--- a/mm/gup.c
+++ b/mm/gup.c
@@ -608,15 +608,15 @@ static inline bool can_follow_write_common(struct page *page,
return page && PageAnon(page) && PageAnonExclusive(page);
}
-static struct page *no_page_table(struct vm_area_struct *vma,
- unsigned int flags, unsigned long address)
+static long no_page_table(struct vm_area_struct *vma,
+ unsigned int flags, unsigned long address)
{
if (!(flags & FOLL_DUMP))
- return NULL;
+ return 0;
/*
* When core dumping, we don't want to allocate unnecessary pages or
- * page tables. Return error instead of NULL to skip handle_mm_fault,
+ * page tables. Return error instead of 0 to skip handle_mm_fault,
* then get_dump_page() will return NULL to leave a hole in the dump.
* But we can only make this optimization where a hole would surely
* be zero-filled if handle_mm_fault() actually did handle it.
@@ -625,12 +625,29 @@ static struct page *no_page_table(struct vm_area_struct *vma,
struct hstate *h = hstate_vma(vma);
if (!hugetlbfs_pagecache_present(h, vma, address))
- return ERR_PTR(-EFAULT);
+ return -EFAULT;
} else if ((vma_is_anonymous(vma) || !vma->vm_ops->fault)) {
- return ERR_PTR(-EFAULT);
+ return -EFAULT;
}
- return NULL;
+ return 0;
+}
+
+static void gup_fill_pages(struct vm_area_struct *vma, unsigned long address,
+ struct page *page, unsigned long nr, struct page **pages)
+{
+ unsigned long i;
+
+ if (!pages)
+ return;
+
+ for (i = 0; i < nr; i++) {
+ struct page *subpage = page + i;
+
+ pages[i] = subpage;
+ flush_anon_page(vma, subpage, address + i * PAGE_SIZE);
+ flush_dcache_page(subpage);
+ }
}
#ifdef CONFIG_PGTABLE_HAS_HUGE_LEAVES
@@ -646,38 +663,43 @@ static inline bool can_follow_write_pud(pud_t pud, struct page *page,
return can_follow_write_common(page, vma, flags);
}
-static struct page *follow_huge_pud(struct vm_area_struct *vma,
- unsigned long addr, pud_t *pudp,
- int flags, unsigned long *page_mask)
+static long follow_huge_pud(struct vm_area_struct *vma,
+ unsigned long addr, unsigned long end, pud_t *pudp,
+ unsigned int flags, struct page **pages)
{
struct mm_struct *mm = vma->vm_mm;
struct page *page;
pud_t pud = *pudp;
unsigned long pfn = pud_pfn(pud);
+ unsigned long off, nr;
int ret;
assert_spin_locked(pud_lockptr(mm, pudp));
if (!pud_present(pud))
- return NULL;
+ return 0;
if ((flags & FOLL_WRITE) &&
!can_follow_write_pud(pud, pfn_to_page(pfn), vma, flags))
- return NULL;
+ return 0;
- pfn += (addr & ~PUD_MASK) >> PAGE_SHIFT;
+ off = PFN_DOWN(addr & ~PUD_MASK);
+ pfn += off;
page = pfn_to_page(pfn);
if (!pud_write(pud) && gup_must_unshare(vma, flags, page))
- return ERR_PTR(-EMLINK);
+ return -EMLINK;
+
+ nr = min(HPAGE_PUD_NR - off, PFN_DOWN(end - addr));
- ret = try_grab_folio(page_folio(page), 1, flags);
+ ret = try_grab_folio(page_folio(page), nr, flags);
if (ret)
- page = ERR_PTR(ret);
- else
- *page_mask = HPAGE_PUD_NR - 1;
+ return ret;
+
+ if (pages)
+ pages[0] = page;
- return page;
+ return nr;
}
/* FOLL_FORCE can write to even unwritable PMDs in COW mappings. */
@@ -698,14 +720,14 @@ static inline bool can_follow_write_pmd(pmd_t pmd, struct page *page,
return !userfaultfd_huge_pmd_wp(vma, pmd);
}
-static struct page *follow_huge_pmd(struct vm_area_struct *vma,
- unsigned long addr, pmd_t *pmd,
- unsigned int flags,
- unsigned long *page_mask)
+static long follow_huge_pmd(struct vm_area_struct *vma,
+ unsigned long addr, unsigned long end, pmd_t *pmd,
+ unsigned int flags, struct page **pages)
{
struct mm_struct *mm = vma->vm_mm;
pmd_t pmdval = *pmd;
struct page *page;
+ unsigned long off, nr;
int ret;
assert_spin_locked(pmd_lockptr(mm, pmd));
@@ -713,50 +735,54 @@ static struct page *follow_huge_pmd(struct vm_area_struct *vma,
page = pmd_page(pmdval);
if ((flags & FOLL_WRITE) &&
!can_follow_write_pmd(pmdval, page, vma, flags))
- return NULL;
+ return 0;
/* Avoid dumping huge zero page */
if ((flags & FOLL_DUMP) && is_huge_zero_pmd(pmdval))
- return ERR_PTR(-EFAULT);
+ return -EFAULT;
if (pmd_protnone(*pmd) && !gup_can_follow_protnone(vma, flags))
- return NULL;
+ return 0;
if (!pmd_write(pmdval) && gup_must_unshare(vma, flags, page))
- return ERR_PTR(-EMLINK);
+ return -EMLINK;
VM_WARN_ON_ONCE_PAGE((flags & FOLL_PIN) && PageAnon(page) &&
!PageAnonExclusive(page), page);
- ret = try_grab_folio(page_folio(page), 1, flags);
+ off = PFN_DOWN(addr & ~HPAGE_PMD_MASK);
+ nr = min(HPAGE_PMD_NR - off, PFN_DOWN(end - addr));
+
+ ret = try_grab_folio(page_folio(page), nr, flags);
if (ret)
- return ERR_PTR(ret);
+ return ret;
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
if (pmd_trans_huge(pmdval) && (flags & FOLL_TOUCH))
touch_pmd(vma, addr, pmd, flags & FOLL_WRITE);
#endif /* CONFIG_TRANSPARENT_HUGEPAGE */
- page += (addr & ~HPAGE_PMD_MASK) >> PAGE_SHIFT;
- *page_mask = HPAGE_PMD_NR - 1;
+ page += off;
+
+ if (pages)
+ pages[0] = page;
- return page;
+ return nr;
}
#else /* CONFIG_PGTABLE_HAS_HUGE_LEAVES */
-static struct page *follow_huge_pud(struct vm_area_struct *vma,
- unsigned long addr, pud_t *pudp,
- int flags, unsigned long *page_mask)
+static long follow_huge_pud(struct vm_area_struct *vma,
+ unsigned long addr, unsigned long end, pud_t *pudp,
+ unsigned int flags, struct page **pages)
{
- return NULL;
+ return 0;
}
-static struct page *follow_huge_pmd(struct vm_area_struct *vma,
- unsigned long addr, pmd_t *pmd,
- unsigned int flags,
- unsigned long *page_mask)
+static long follow_huge_pmd(struct vm_area_struct *vma,
+ unsigned long addr, unsigned long end, pmd_t *pmd,
+ unsigned int flags, struct page **pages)
{
- return NULL;
+ return 0;
}
#endif /* CONFIG_PGTABLE_HAS_HUGE_LEAVES */
@@ -799,110 +825,205 @@ static inline bool can_follow_write_pte(pte_t pte, struct page *page,
return !userfaultfd_pte_wp(vma, pte);
}
-static struct page *follow_page_pte(struct vm_area_struct *vma,
- unsigned long address, pmd_t *pmd, unsigned int flags)
+/*
+ * The caller has already run every per-PTE safety check (present,
+ * write-fault, gup_must_unshare()) on the PTE, so this only does the
+ * per-folio work: the refcount grab, the FOLL_PIN accessibility fault-in,
+ * dirty/accessed marking, and the array fill with the cache flush.
+ */
+static long follow_page_pte_commit(struct vm_area_struct *vma,
+ unsigned long address, struct folio *folio, struct page *page,
+ pte_t pte, unsigned long nr, unsigned int flags,
+ struct page **pages)
+{
+ long ret;
+
+ /* try_grab_folio() does nothing unless FOLL_GET or FOLL_PIN is set. */
+ ret = try_grab_folio(folio, nr, flags);
+ if (unlikely(ret))
+ return ret;
+
+ /*
+ * We need to make the page accessible if and only if we are going
+ * to access its content (the FOLL_PIN case). Please see
+ * Documentation/core-api/pin_user_pages.rst for details.
+ */
+ if (flags & FOLL_PIN) {
+ ret = arch_make_folio_accessible(folio);
+ if (ret) {
+ gup_put_folio(folio, nr, flags);
+ return ret;
+ }
+ }
+ if (flags & FOLL_TOUCH) {
+ if ((flags & FOLL_WRITE) &&
+ !pte_dirty(pte) && !folio_test_dirty(folio))
+ folio_mark_dirty(folio);
+ /*
+ * pte_mkyoung() would be more correct here, but atomic care
+ * is needed to avoid losing the dirty bit: it is easier to use
+ * folio_mark_accessed().
+ */
+ folio_mark_accessed(folio);
+ }
+
+ gup_fill_pages(vma, address, page, nr, pages);
+
+ return 0;
+}
+
+/*
+ * Resolve one present PTE to the page it maps. Returns no page and no error
+ * when the PTE cannot be followed but the caller may fault it in, and a
+ * negative errno when the caller must report the failure.
+ */
+static long follow_one_pte(struct vm_area_struct *vma, unsigned long address,
+ pte_t *ptep, pte_t pte, unsigned int flags, struct page **pagep)
{
- struct mm_struct *mm = vma->vm_mm;
- struct folio *folio;
struct page *page;
- spinlock_t *ptl;
- pte_t *ptep, pte;
- int ret;
- ptep = pte_offset_map_lock(mm, pmd, address, &ptl);
- if (!ptep)
- return no_page_table(vma, flags, address);
- pte = ptep_get(ptep);
+ *pagep = NULL;
+
if (!pte_present(pte))
- goto no_page;
+ return 0;
if (pte_protnone(pte) && !gup_can_follow_protnone(vma, flags))
- goto no_page;
+ return 0;
page = vm_normal_page(vma, address, pte);
/*
* We only care about anon pages in can_follow_write_pte().
*/
- if ((flags & FOLL_WRITE) &&
- !can_follow_write_pte(pte, page, vma, flags)) {
- page = NULL;
- goto out;
- }
+ if ((flags & FOLL_WRITE) && !can_follow_write_pte(pte, page, vma, flags))
+ return 0;
if (unlikely(!page)) {
if (flags & FOLL_DUMP) {
/* Avoid special (like zero) pages in core dumps */
- page = ERR_PTR(-EFAULT);
- goto out;
- }
-
- if (is_zero_pfn(pte_pfn(pte))) {
- page = pte_page(pte);
- } else {
- ret = follow_pfn_pte(vma, address, ptep, flags);
- page = ERR_PTR(ret);
- goto out;
+ return -EFAULT;
}
+ if (!is_zero_pfn(pte_pfn(pte)))
+ return follow_pfn_pte(vma, address, ptep, flags);
+ page = pte_page(pte);
}
- folio = page_folio(page);
- if (!pte_write(pte) && gup_must_unshare(vma, flags, page)) {
- page = ERR_PTR(-EMLINK);
- goto out;
- }
+ if (!pte_write(pte) && gup_must_unshare(vma, flags, page))
+ return -EMLINK;
VM_WARN_ON_ONCE_PAGE((flags & FOLL_PIN) && PageAnon(page) &&
!PageAnonExclusive(page), page);
- /* try_grab_folio() does nothing unless FOLL_GET or FOLL_PIN is set. */
- ret = try_grab_folio(folio, 1, flags);
- if (unlikely(ret)) {
- page = ERR_PTR(ret);
- goto out;
- }
+ *pagep = page;
+ return 0;
+}
- /*
- * We need to make the page accessible if and only if we are going
- * to access its content (the FOLL_PIN case). Please see
- * Documentation/core-api/pin_user_pages.rst for details.
- */
- if (flags & FOLL_PIN) {
- ret = arch_make_folio_accessible(folio);
- if (ret) {
- unpin_user_page(page);
- page = ERR_PTR(ret);
- goto out;
+/*
+ * Return how many PTEs map consecutive pages of the same folio and can be
+ * committed as one run. Always at least 1.
+ *
+ * The write-fault and unshare checks in follow_one_pte() are per PTE, but a
+ * writable run needs no repeat: a writable anon page is exclusive. A read-only
+ * run under FOLL_WRITE or FOLL_PIN does need the per-page check, so it stays
+ * one page at a time.
+ */
+static unsigned long follow_pte_batch(struct vm_area_struct *vma,
+ unsigned long address, unsigned long walk_end,
+ struct folio *folio, pte_t *ptep, pte_t *batch_pte, unsigned int flags)
+{
+ unsigned long max;
+
+ if (!folio_test_large(folio))
+ return 1;
+ if (!pte_write(*batch_pte) && (flags & (FOLL_WRITE | FOLL_PIN)))
+ return 1;
+
+ max = (walk_end - address) >> PAGE_SHIFT;
+ if (max <= 1)
+ return 1;
+
+ /* Merge young/dirty across batch so folio_mark_dirty sees any dirty. */
+ return folio_pte_batch_flags(folio, vma, ptep, batch_pte, max,
+ FPB_RESPECT_WRITE | FPB_MERGE_YOUNG_DIRTY);
+}
+
+/*
+ * Walk the PTEs from the start address to the end of this page table or VMA,
+ * whichever comes first, and commit every page found.
+ *
+ * A failure on the first PTE is returned to the caller. A failure after that
+ * is a short read; __get_user_pages() retrying the read will get the error.
+ */
+static long follow_page_pte(struct vm_area_struct *vma,
+ unsigned long address, unsigned long end, pmd_t *pmd,
+ unsigned int flags, struct page **pages)
+{
+ struct mm_struct *mm = vma->vm_mm;
+ bool need_no_page_table = false;
+ pte_t *ptep, *orig_ptep;
+ unsigned long walk_end;
+ unsigned long nr = 0;
+ spinlock_t *ptl;
+ long ret = 0;
+
+ orig_ptep = ptep = pte_offset_map_lock(mm, pmd, address, &ptl);
+ if (!ptep)
+ return no_page_table(vma, flags, address);
+
+ walk_end = min(pmd_addr_end(address, end), vma->vm_end);
+
+ for (; address < walk_end; address += PAGE_SIZE, ptep++) {
+ pte_t pte = ptep_get(ptep);
+ struct page *page;
+
+ ret = follow_one_pte(vma, address, ptep, pte, flags, &page);
+ if (!ret && page) {
+ struct folio *folio = page_folio(page);
+ unsigned long batch;
+
+ pte_t batch_pte = pte;
+
+ batch = follow_pte_batch(vma, address, walk_end, folio,
+ ptep, &batch_pte, flags);
+ ret = follow_page_pte_commit(vma, address, folio, page,
+ batch_pte, batch, flags,
+ pages ? pages + nr : NULL);
+ if (!ret) {
+ nr += batch;
+ /*
+ * The loop's own increment covers one PTE; skip
+ * the rest of the batch.
+ */
+ ptep += batch - 1;
+ address += (batch - 1) * PAGE_SIZE;
+ continue;
+ }
}
- }
- if (flags & FOLL_TOUCH) {
- if ((flags & FOLL_WRITE) &&
- !pte_dirty(pte) && !folio_test_dirty(folio))
- folio_mark_dirty(folio);
+
/*
- * pte_mkyoung() would be more correct here, but atomic care
- * is needed to avoid losing the dirty bit: it is easier to use
- * folio_mark_accessed().
+ * no_page_table() may look up the page cache, so it cannot run
+ * under the PTE lock.
*/
- folio_mark_accessed(folio);
+ if (!ret && pte_none(pte))
+ need_no_page_table = true;
+ break;
}
-out:
- pte_unmap_unlock(ptep, ptl);
- return page;
-no_page:
- pte_unmap_unlock(ptep, ptl);
- if (!pte_none(pte))
- return NULL;
- return no_page_table(vma, flags, address);
+
+ pte_unmap_unlock(orig_ptep, ptl);
+
+ if (nr)
+ return nr;
+ if (need_no_page_table)
+ return no_page_table(vma, flags, address);
+ return ret;
}
-static struct page *follow_pmd_mask(struct vm_area_struct *vma,
- unsigned long address, pud_t *pudp,
- unsigned int flags,
- unsigned long *page_mask)
+static long follow_pmd_mask(struct vm_area_struct *vma,
+ unsigned long address, unsigned long end, pud_t *pudp,
+ unsigned int flags, struct page **pages)
{
pmd_t *pmd, pmdval;
spinlock_t *ptl;
- struct page *page;
+ long ret;
struct mm_struct *mm = vma->vm_mm;
pmd = pmd_offset(pudp, address);
@@ -912,7 +1033,7 @@ static struct page *follow_pmd_mask(struct vm_area_struct *vma,
if (!pmd_present(pmdval))
return no_page_table(vma, flags, address);
if (likely(!pmd_leaf(pmdval)))
- return follow_page_pte(vma, address, pmd, flags);
+ return follow_page_pte(vma, address, end, pmd, flags, pages);
if (pmd_protnone(pmdval) && !gup_can_follow_protnone(vma, flags))
return no_page_table(vma, flags, address);
@@ -925,28 +1046,35 @@ static struct page *follow_pmd_mask(struct vm_area_struct *vma,
}
if (unlikely(!pmd_leaf(pmdval))) {
spin_unlock(ptl);
- return follow_page_pte(vma, address, pmd, flags);
+ return follow_page_pte(vma, address, end, pmd, flags, pages);
}
if (pmd_trans_huge(pmdval) && (flags & FOLL_SPLIT_PMD)) {
spin_unlock(ptl);
split_huge_pmd(vma, pmd, address);
/* If pmd was left empty, stuff a page table in there quickly */
- return pte_alloc(mm, pmd) ? ERR_PTR(-ENOMEM) :
- follow_page_pte(vma, address, pmd, flags);
+ return pte_alloc(mm, pmd) ? -ENOMEM :
+ follow_page_pte(vma, address, end, pmd, flags, pages);
}
- page = follow_huge_pmd(vma, address, pmd, flags, page_mask);
+ ret = follow_huge_pmd(vma, address, end, pmd, flags, pages);
spin_unlock(ptl);
- return page;
+
+ /*
+ * The ref is already held, so the page cannot go away: fill the
+ * array and flush caches without the pmd lock.
+ */
+ if (ret > 0 && pages)
+ gup_fill_pages(vma, address, pages[0], ret, pages);
+
+ return ret;
}
-static struct page *follow_pud_mask(struct vm_area_struct *vma,
- unsigned long address, p4d_t *p4dp,
- unsigned int flags,
- unsigned long *page_mask)
+static long follow_pud_mask(struct vm_area_struct *vma,
+ unsigned long address, unsigned long end, p4d_t *p4dp,
+ unsigned int flags, struct page **pages)
{
pud_t *pudp, pud;
spinlock_t *ptl;
- struct page *page;
+ long ret;
struct mm_struct *mm = vma->vm_mm;
pudp = pud_offset(p4dp, address);
@@ -955,22 +1083,29 @@ static struct page *follow_pud_mask(struct vm_area_struct *vma,
return no_page_table(vma, flags, address);
if (pud_leaf(pud)) {
ptl = pud_lock(mm, pudp);
- page = follow_huge_pud(vma, address, pudp, flags, page_mask);
+ ret = follow_huge_pud(vma, address, end, pudp, flags, pages);
spin_unlock(ptl);
- if (page)
- return page;
+ /*
+ * The ref is already held, so the page cannot go away: fill
+ * the array and flush caches without the lock. A 1 GB folio
+ * can be up to HPAGE_PUD_NR pages, too long to flush under a
+ * spinlock.
+ */
+ if (ret > 0 && pages)
+ gup_fill_pages(vma, address, pages[0], ret, pages);
+ if (ret)
+ return ret;
return no_page_table(vma, flags, address);
}
if (unlikely(pud_bad(pud)))
return no_page_table(vma, flags, address);
- return follow_pmd_mask(vma, address, pudp, flags, page_mask);
+ return follow_pmd_mask(vma, address, end, pudp, flags, pages);
}
-static struct page *follow_p4d_mask(struct vm_area_struct *vma,
- unsigned long address, pgd_t *pgdp,
- unsigned int flags,
- unsigned long *page_mask)
+static long follow_p4d_mask(struct vm_area_struct *vma,
+ unsigned long address, unsigned long end, pgd_t *pgdp,
+ unsigned int flags, struct page **pages)
{
p4d_t *p4dp, p4d;
@@ -981,15 +1116,18 @@ static struct page *follow_p4d_mask(struct vm_area_struct *vma,
if (!p4d_present(p4d) || p4d_bad(p4d))
return no_page_table(vma, flags, address);
- return follow_pud_mask(vma, address, p4dp, flags, page_mask);
+ return follow_pud_mask(vma, address, end, p4dp, flags, pages);
}
/**
- * follow_page_mask - look up a page descriptor from a user-virtual address
+ * follow_page_mask - look up pages at a user-virtual address
* @vma: vm_area_struct mapping @address
* @address: virtual address to look up
+ * @end: virtual address at which to stop batching contiguous pages
* @flags: flags modifying lookup behaviour
- * @page_mask: a pointer to output page_mask
+ * @pages: array to receive the pages, refcounted per @flags, or NULL to
+ * walk the page tables (e.g. to fault pages in) without collecting
+ * or refcounting them
*
* @flags can have FOLL_ flags set, defined in <linux/mm.h>
*
@@ -998,33 +1136,32 @@ static struct page *follow_p4d_mask(struct vm_area_struct *vma,
* trigger a fault with FAULT_FLAG_UNSHARE set. Note that unsharing is only
* relevant with FOLL_PIN and !FOLL_WRITE.
*
- * On output, @page_mask is set according to the size of the page.
- *
- * Return: the mapped (struct page *), %NULL if no mapping exists, or
- * an error pointer if there is a mapping to something not represented
- * by a page descriptor (see also vm_normal_page()).
+ * Return: the number of contiguous pages starting at @address that were
+ * placed into @pages (if non-NULL), which may be fewer than the pages
+ * requested via @end; 0 if no mapping exists at @address; or a negative
+ * errno for a mapping to something not represented by a page descriptor
+ * (see also vm_normal_page()).
*/
-static struct page *follow_page_mask(struct vm_area_struct *vma,
- unsigned long address, unsigned int flags,
- unsigned long *page_mask)
+static long follow_page_mask(struct vm_area_struct *vma,
+ unsigned long address, unsigned long end,
+ unsigned int flags, struct page **pages)
{
pgd_t *pgd;
struct mm_struct *mm = vma->vm_mm;
- struct page *page;
+ long ret;
vma_pgtable_walk_begin(vma);
- *page_mask = 0;
pgd = pgd_offset(mm, address);
if (pgd_none(*pgd) || unlikely(pgd_bad(*pgd)))
- page = no_page_table(vma, flags, address);
+ ret = no_page_table(vma, flags, address);
else
- page = follow_p4d_mask(vma, address, pgd, flags, page_mask);
+ ret = follow_p4d_mask(vma, address, end, pgd, flags, pages);
vma_pgtable_walk_end(vma);
- return page;
+ return ret;
}
static int get_gate_page(struct mm_struct *mm, unsigned long address,
@@ -1358,7 +1495,6 @@ static long __get_user_pages(struct mm_struct *mm,
{
long ret = 0, i = 0;
struct vm_area_struct *vma = NULL;
- unsigned long page_mask = 0;
if (!nr_pages)
return 0;
@@ -1373,7 +1509,7 @@ static long __get_user_pages(struct mm_struct *mm,
do {
struct page *page;
- unsigned int page_increm;
+ long nr;
/* first iteration or cross vma bound */
if (!vma || start >= vma->vm_end) {
@@ -1400,8 +1536,12 @@ static long __get_user_pages(struct mm_struct *mm,
pages ? &page : NULL);
if (ret)
goto out;
- page_mask = 0;
- goto next_page;
+ gup_fill_pages(vma, start, page, 1,
+ pages ? pages + i : NULL);
+ i++;
+ start += PAGE_SIZE;
+ nr_pages--;
+ continue;
}
if (!vma) {
@@ -1423,10 +1563,11 @@ static long __get_user_pages(struct mm_struct *mm,
}
cond_resched();
- page = follow_page_mask(vma, start, gup_flags, &page_mask);
- if (!page || PTR_ERR(page) == -EMLINK) {
+ nr = follow_page_mask(vma, start, start + nr_pages * PAGE_SIZE,
+ gup_flags, pages ? &pages[i] : NULL);
+ if (!nr || nr == -EMLINK) {
ret = faultin_page(vma, start, gup_flags,
- PTR_ERR(page) == -EMLINK, locked);
+ nr == -EMLINK, locked);
switch (ret) {
case 0:
goto retry;
@@ -1440,70 +1581,30 @@ static long __get_user_pages(struct mm_struct *mm,
goto out;
}
BUG();
- } else if (PTR_ERR(page) == -EEXIST) {
+ } else if (nr == -EEXIST) {
/*
* Proper page table entry exists, but no corresponding
* struct page. If the caller expects **pages to be
* filled in, bail out now, because that can't be done
- * for this page.
+ * for this page. Otherwise advance by the one page
+ * follow_page_mask() looked at.
*/
if (pages) {
- ret = PTR_ERR(page);
+ ret = nr;
goto out;
}
- } else if (IS_ERR(page)) {
- ret = PTR_ERR(page);
+ nr = 1;
+ } else if (nr < 0) {
+ ret = nr;
goto out;
}
-next_page:
- page_increm = 1 + (~(start >> PAGE_SHIFT) & page_mask);
- if (page_increm > nr_pages)
- page_increm = nr_pages;
-
- if (pages) {
- struct page *subpage;
- unsigned int j;
- /*
- * This must be a large folio (and doesn't need to
- * be the whole folio; it can be part of it), do
- * the refcount work for all the subpages too.
- *
- * NOTE: here the page may not be the head page
- * e.g. when start addr is not thp-size aligned.
- * try_grab_folio() should have taken care of tail
- * pages.
- */
- if (page_increm > 1) {
- struct folio *folio = page_folio(page);
-
- /*
- * Since we already hold refcount on the
- * large folio, this should never fail.
- */
- if (try_grab_folio(folio, page_increm - 1,
- gup_flags)) {
- /*
- * Release the 1st page ref if the
- * folio is problematic, fail hard.
- */
- gup_put_folio(folio, 1, gup_flags);
- ret = -EFAULT;
- goto out;
- }
- }
-
- for (j = 0; j < page_increm; j++) {
- subpage = page + j;
- pages[i + j] = subpage;
- flush_anon_page(vma, subpage, start + j * PAGE_SIZE);
- flush_dcache_page(subpage);
- }
- }
+ /* Check that we didn't pin more pages than the caller will free. */
+ VM_WARN_ON_ONCE(nr > nr_pages);
- i += page_increm;
- start += page_increm * PAGE_SIZE;
- nr_pages -= page_increm;
+ i += nr;
+ start += nr * PAGE_SIZE;
+ nr_pages -= nr;
} while (nr_pages);
out:
return i ? i : ret;
|
| 33/3 | 2026/08/12 16:56 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 11086 output: 155 thoughts: 2162 Thoughts: **KMSAN Assessment of GUP Batching Patch**
Okay, let's break this down. This patch is diving into `mm/gup.c`, the heart of Get User Pages, and it's all about optimizing how we walk and batch page tables. Specifically, it seems to be refactoring the functions that handle that page table walking and batching: `follow_page_mask`, `follow_huge_pud`, `follow_huge_pmd`, and `follow_page_pte`. It's introducing new functions like `follow_page_pte_commit`, `follow_one_pte`, and `follow_pte_batch` to handle PTE processing in batches. The return types are also changed from `struct page *` to `long` to reflect either the number of pages processed or an error code. `__get_user_pages` is updated to utilize the new batched approach.
The immediate concern is always uninitialized memory. Are we creating any risks here? My initial assessment is that these changes are mainly about the mechanics of *pinning* pages and potentially refcounting them and does not send any new structures to the user. There's no fancy new data being allocated, filled, and then handed over to user space where uninitialized values might leak. I also don't see any conditional logic that *depends* on the value of potentially uninitialized variables in any new code or branches.
However, GUP itself is interesting. GUP *does* pin user pages. If there is a bug, incorrect pinning *could* lead to corruption, use-after-free, or out-of-bounds access. Those can all be caught by KASAN. We are looking for something that KMSAN would catch *better* than KASAN. It's a core MM mechanism, so bugs here are more likely to crash, page fault, or cause data corruption. Now, KMSAN detects use of uninitialized memory, right? Let's consider.
The `pages` array passed to `follow_page_mask` is where the returned pages end up. It gets filled by `gup_fill_pages`. The risk is *if* `gup_fill_pages` fails to fill the whole array but returns a positive number (success), that would mean that the caller would have uninitialized pointers in the `pages` array. Let's look at `gup_fill_pages`:
```c
+static void gup_fill_pages(struct vm_area_struct *vma, unsigned long address,
+ struct page *page, unsigned long nr, struct page **pages)
+{
+ unsigned long i;
+
+ if (!pages)
+ return;
+
+ for (i = 0; i < nr; i++) {
+ struct page *subpage = page + i;
+
+ pages[i] = subpage;
+ flush_anon_page(vma, subpage, address + i * PAGE_SIZE);
+ flush_dcache_page(subpage);
+ }
+}
```
It fills `pages[0]` through `pages[nr - 1]`. If `follow_page_mask` returns `nr`, it *should* mean `nr` pages have been filled. In `__get_user_pages`, we're iterating and incrementing the array, `i += nr`. The caller allocates `pages` and expects `pages[0]` through `pages[i-1]` to be initialized. But is there a scenario where `follow_page_mask` returns `nr > 0` but doesn't fill the entire array?
Here's where it gets interesting. `follow_huge_pud`:
```c
+ if (pages)
+ pages[0] = page;
+
+ return nr;
```
It only sets `pages[0] = page`! What if `nr > 1`? And the callsite is in `follow_pud_mask`:
```c
if (pud_leaf(pud)) {
ptl = pud_lock(mm, pudp);
- page = follow_huge_pud(vma, address, pudp, flags, page_mask);
+ ret = follow_huge_pud(vma, address, end, pudp, flags, pages);
spin_unlock(ptl);
- if (page)
- return page;
+ /*
+ * The ref is already held, so the page cannot go away: fill
+ * the array and flush caches without the lock. A 1 GB folio
+ * can be
|
| 34/3 | 2026/08/12 16:57 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch refactors Get User Pages (GUP) to batch page table walking and refcounting. It modifies core memory management routines but does not introduce new data structures copied to userspace, nor does it handle complex parsing where uninitialized memory could be leaked. Any bugs introduced by these changes (such as returning an incorrect number of processed pages, leading to uninitialized pointers in the `pages` array) would result in wild pointer dereferences, out-of-bounds accesses, or page reference imbalances. These types of errors are effectively caught by KASAN and standard kernel debugging tools. Therefore, a dedicated KMSAN session is not necessary.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch refactors Get User Pages (GUP) to batch page table walking and refcounting. It modifies core memory management routines but does not introduce new data structures copied to userspace, nor does it handle complex parsing where uninitialized memory could be leaked. Any bugs introduced by these changes (such as returning an incorrect number of processed pages, leading to uninitialized pointers in the `pages` array) would result in wild pointer dereferences, out-of-bounds accesses, or page reference imbalances. These types of errors are effectively caught by KASAN and standard kernel debugging tools. Therefore, a dedicated KMSAN session is not necessary.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|