AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/08/28 19:39 flow patch-triage
1m Args:
null

Results:
{
  "EnableConfigs": [
    "TRANSPARENT_HUGEPAGE",
    "DEVICE_PRIVATE",
    "USERFAULTFD"
  ],
  "FocusSymbols": [
    "split_huge_pmd_locked",
    "__split_huge_pmd",
    "split_huge_pmd_address"
  ],
  "KMSANReasoning": "The patch refactors huge PMD splitting logic (__split_huge_pmd_locked) in mm/huge_memory.c by breaking it down into helper functions (huge_zero_pmd_can_split, unmap_huge_pmd_entry, split_huge_pmd_to_ptes, split_present_huge_pmd, split_non_present_huge_pmd) and grouping PMD split parameters in a local stack structure (struct split_pmd_state).\n\nAll instances of 'struct split_pmd_state' are stack-allocated with designated initializers, ensuring all unset fields are zero-initialized. There are no kernel-to-user copy operations (e.g., copy_to_user), no uninitialized heap or stack variables read or branched on, and no info-leak risks. Any memory management or synchronization issues in this code path (such as folio refcounting, rmap management, TLB invalidation, or UAF) are caught by standard KASAN, VM_BUG_ON assertions, and standard sanitizers. Therefore, a dedicated KMSAN session is not needed.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch refactors core Transparent Huge Page (THP) PMD split logic in mm/huge_memory.c, separating the handling of present vs non-present (migration, device-private) huge PMDs and reorganizing rmap conversion and unmapping logic. This core MM functionality is reachable via user actions (madvise, mprotect, page migration, etc.) and is worth fuzzing.",
  "WorthFuzzing": true
}

1/1 2026/08/28 19:39 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 37afda2d1094b0a9df200c535bd4b01247a9f711\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Fri Aug 28 19:39:21 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/mm/huge_memory.c b/mm/huge_memory.c\nindex afbb5974bd225..8e0fd11da3d7a 100644\n--- a/mm/huge_memory.c\n+++ b/mm/huge_memory.c\n@@ -3139,193 +3139,173 @@ static void __split_huge_zero_page_pmd(struct vm_area_struct *vma,\n \tpmd_populate(mm, pmd, pgtable);\n }\n \n-static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,\n-\t\tunsigned long haddr, bool freeze)\n+/*\n+ * Only a huge zero PMD in an anonymous VMA is split, into a page table of\n+ * shared zero page mappings. Any other huge zero PMD is simply unmapped.\n+ */\n+static bool huge_zero_pmd_can_split(struct vm_area_struct *vma, pmd_t pmdval)\n {\n-\tstruct mm_struct *mm = vma-\u003evm_mm;\n-\tstruct folio *folio;\n-\tstruct page *page;\n-\tpgtable_t pgtable;\n-\tpmd_t old_pmd, _pmd;\n-\tbool soft_dirty, uffd_wp = false, young = false, write = false;\n-\tbool anon_exclusive = false, dirty = false;\n-\tunsigned long addr;\n-\tpte_t *pte;\n-\tint i;\n-\n-\tVM_BUG_ON(haddr \u0026 ~HPAGE_PMD_MASK);\n-\tVM_BUG_ON_VMA(vma-\u003evm_start \u003e haddr, vma);\n-\tVM_BUG_ON_VMA(vma-\u003evm_end \u003c haddr + HPAGE_PMD_SIZE, vma);\n+\treturn is_huge_zero_pmd(pmdval) \u0026\u0026 vma_is_anonymous(vma);\n+}\n \n-\tVM_WARN_ON_ONCE(!pmd_is_valid_softleaf(*pmd) \u0026\u0026 !pmd_trans_huge(*pmd));\n+/**\n+ * unmap_huge_pmd_entry() - Unmap a huge PMD entry rather than splitting it.\n+ * @vma: The VMA @pmd belongs to.\n+ * @haddr: The PMD-aligned address @pmd maps.\n+ * @pmd: Pointer to the huge PMD entry.\n+ * @folio: The folio @pmd describes, or NULL if it describes none.\n+ * @is_present: Is @pmd a present entry rather than a softleaf entry?\n+ *\n+ * Only anonymous folios are rebuilt at PTE level when a huge PMD entry is\n+ * split. Everything else is unmapped here and faulted back in on the next\n+ * access.\n+ */\n+static void unmap_huge_pmd_entry(struct vm_area_struct *vma,\n+\t\tunsigned long haddr, pmd_t *pmd, struct folio *folio,\n+\t\tbool is_present)\n+{\n+\tstruct mm_struct *mm = vma-\u003evm_mm;\n+\tpmd_t old_pmd;\n \n-\tcount_vm_event(THP_SPLIT_PMD);\n+\told_pmd = pmdp_huge_clear_flush(vma, haddr, pmd);\n+\t/*\n+\t * We are going to unmap this huge page. So\n+\t * just go ahead and zap it\n+\t */\n+\tif (has_deposited_pgtable(vma, old_pmd, folio))\n+\t\tzap_deposited_table(mm, pmd);\n \n-\tif (!vma_is_anonymous(vma)) {\n-\t\told_pmd = pmdp_huge_clear_flush(vma, haddr, pmd);\n-\t\t/*\n-\t\t * We are going to unmap this huge page. So\n-\t\t * just go ahead and zap it\n-\t\t */\n-\t\tif (arch_needs_pgtable_deposit())\n-\t\t\tzap_deposited_table(mm, pmd);\n-\t\tif (vma_is_special_huge(vma))\n-\t\t\treturn;\n-\t\tif (unlikely(pmd_is_migration_entry(old_pmd))) {\n-\t\t\tconst softleaf_t old_entry = softleaf_from_pmd(old_pmd);\n-\n-\t\t\tfolio = softleaf_to_folio(old_entry);\n-\t\t} else if (is_huge_zero_pmd(old_pmd)) {\n-\t\t\treturn;\n-\t\t} else {\n-\t\t\tpage = pmd_page(old_pmd);\n-\t\t\tfolio = page_folio(page);\n-\t\t\tif (!folio_test_dirty(folio) \u0026\u0026 pmd_dirty(old_pmd))\n-\t\t\t\tfolio_mark_dirty(folio);\n-\t\t\tif (!folio_test_referenced(folio) \u0026\u0026 pmd_young(old_pmd))\n-\t\t\t\tfolio_set_referenced(folio);\n-\t\t\tfolio_remove_rmap_pmd(folio, page, vma);\n-\t\t\tadd_mm_counter(mm, mm_counter_file(folio), -HPAGE_PMD_NR);\n-\t\t\tfolio_put(folio);\n-\t\t\treturn;\n-\t\t}\n-\t\tadd_mm_counter(mm, mm_counter_file(folio), -HPAGE_PMD_NR);\n+\tif (!folio)\n \t\treturn;\n-\t}\n \n-\tif (is_huge_zero_pmd(*pmd)) {\n-\t\t/*\n-\t\t * FIXME: Do we want to invalidate secondary mmu by calling\n-\t\t * mmu_notifier_arch_invalidate_secondary_tlbs() see comments below\n-\t\t * inside __split_huge_pmd() ?\n-\t\t *\n-\t\t * We are going from a zero huge page write protected to zero\n-\t\t * small page also write protected so it does not seems useful\n-\t\t * to invalidate secondary mmu at this time.\n-\t\t */\n-\t\treturn __split_huge_zero_page_pmd(vma, haddr, pmd);\n+\tif (is_present) {\n+\t\tstruct page *page = pmd_page(old_pmd);\n+\n+\t\tif (!folio_test_dirty(folio) \u0026\u0026 pmd_dirty(old_pmd))\n+\t\t\tfolio_mark_dirty(folio);\n+\t\tif (!folio_test_referenced(folio) \u0026\u0026 pmd_young(old_pmd))\n+\t\t\tfolio_set_referenced(folio);\n+\t\tfolio_remove_rmap_pmd(folio, page, vma);\n \t}\n \n-\tif (pmd_is_migration_entry(*pmd)) {\n-\t\tsoftleaf_t entry;\n+\tadd_mm_counter(mm, mm_counter_file(folio), -HPAGE_PMD_NR);\n \n-\t\told_pmd = *pmd;\n-\t\tentry = softleaf_from_pmd(old_pmd);\n-\t\tpage = softleaf_to_page(entry);\n-\t\tfolio = page_folio(page);\n+\tif (is_present)\n+\t\tfolio_put(folio);\n+}\n \n-\t\tsoft_dirty = pmd_swp_soft_dirty(old_pmd);\n-\t\tuffd_wp = pmd_swp_uffd(old_pmd);\n+struct split_pmd_state {\n+\tstruct folio *folio;\n+\tstruct page *page;\n+\tbool is_present;\n+\tbool is_device_private;\n+\tbool freeze;\n+\tbool write;\n+\tbool young;\n+\tbool dirty;\n+\tbool soft_dirty;\n+\tbool uffd;\n+\tbool anon_exclusive;\n+};\n \n-\t\twrite = softleaf_is_migration_write(entry);\n-\t\tif (PageAnon(page))\n-\t\t\tanon_exclusive = softleaf_is_migration_read_exclusive(entry);\n-\t\tyoung = softleaf_is_migration_young(entry);\n-\t\tdirty = softleaf_is_migration_dirty(entry);\n-\t} else if (pmd_is_device_private_entry(*pmd)) {\n-\t\tsoftleaf_t entry;\n+/*\n+ * Convert the folio's PMD-level anonymous rmap into PTE-level ones.\n+ *\n+ * Without \"freeze\", we'll simply split the PMD, propagating the\n+ * PageAnonExclusive() flag for each PTE by setting it for\n+ * each subpage -- no need to (temporarily) clear.\n+ *\n+ * With \"freeze\" we want to replace mapped pages by\n+ * migration entries right away. This is only possible if we\n+ * managed to clear PageAnonExclusive() -- see\n+ * set_pmd_migration_entry().\n+ *\n+ * In case we cannot clear PageAnonExclusive(), split the PMD\n+ * only and let try_to_migrate_one() fail later.\n+ *\n+ * See folio_try_share_anon_rmap_pmd(): invalidate PMD first.\n+ *\n+ * Returns: whether the mapping may still be frozen.\n+ */\n+static bool split_huge_pmd_anon_rmap(const struct split_pmd_state *state,\n+\t\tstruct vm_area_struct *vma, unsigned long haddr)\n+{\n+\trmap_t rmap_flags = RMAP_NONE;\n \n-\t\told_pmd = *pmd;\n-\t\tentry = softleaf_from_pmd(old_pmd);\n-\t\tpage = softleaf_to_page(entry);\n-\t\tfolio = page_folio(page);\n+\tif (state-\u003efreeze \u0026\u0026\n+\t    (!state-\u003eanon_exclusive ||\n+\t     !folio_try_share_anon_rmap_pmd(state-\u003efolio, state-\u003epage)))\n+\t\treturn true;\n \n-\t\tsoft_dirty = pmd_swp_soft_dirty(old_pmd);\n-\t\tuffd_wp = pmd_swp_uffd(old_pmd);\n+\tfolio_ref_add(state-\u003efolio, HPAGE_PMD_NR - 1);\n+\tif (state-\u003eanon_exclusive)\n+\t\trmap_flags |= RMAP_EXCLUSIVE;\n+\tfolio_add_anon_rmap_ptes(state-\u003efolio, state-\u003epage, HPAGE_PMD_NR, vma,\n+\t\t\t\t haddr, rmap_flags);\n+\treturn false;\n+}\n \n-\t\twrite = softleaf_is_device_private_write(entry);\n-\t\tanon_exclusive = PageAnonExclusive(page);\n+/*\n+ * Build the leaf entry for the PTE entry describing @pfn, for a huge PMD entry\n+ * which is not restored as a present mapping.\n+ */\n+static softleaf_t split_pmd_make_softleaf(const struct split_pmd_state *state,\n+\t\t\t\t\t  unsigned long pfn)\n+{\n+\tsoftleaf_t entry;\n \n+\tif (state-\u003eis_device_private \u0026\u0026 !state-\u003efreeze) {\n \t\t/*\n-\t\t * Device private THP should be treated the same as regular\n-\t\t * folios w.r.t anon exclusive handling. See the comments for\n-\t\t * folio handling and anon_exclusive below.\n-\t\t */\n-\t\tif (freeze \u0026\u0026 anon_exclusive \u0026\u0026\n-\t\t    folio_try_share_anon_rmap_pmd(folio, page))\n-\t\t\tfreeze = false;\n-\t\tif (!freeze) {\n-\t\t\trmap_t rmap_flags = RMAP_NONE;\n-\n-\t\t\tfolio_ref_add(folio, HPAGE_PMD_NR - 1);\n-\t\t\tif (anon_exclusive)\n-\t\t\t\trmap_flags |= RMAP_EXCLUSIVE;\n-\n-\t\t\tfolio_add_anon_rmap_ptes(folio, page, HPAGE_PMD_NR,\n-\t\t\t\t\t\t vma, haddr, rmap_flags);\n-\t\t}\n-\t} else {\n-\t\t/*\n-\t\t * Up to this point the pmd is present and huge and userland has\n-\t\t * the whole access to the hugepage during the split (which\n-\t\t * happens in place). If we overwrite the pmd with the not-huge\n-\t\t * version pointing to the pte here (which of course we could if\n-\t\t * all CPUs were bug free), userland could trigger a small page\n-\t\t * size TLB miss on the small sized TLB while the hugepage TLB\n-\t\t * entry is still established in the huge TLB. Some CPU doesn't\n-\t\t * like that. See\n-\t\t * http://support.amd.com/TechDocs/41322_10h_Rev_Gd.pdf, Erratum\n-\t\t * 383 on page 105. Intel should be safe but is also warns that\n-\t\t * it's only safe if the permission and cache attributes of the\n-\t\t * two entries loaded in the two TLB is identical (which should\n-\t\t * be the case here). But it is generally safer to never allow\n-\t\t * small and huge TLB entries for the same virtual address to be\n-\t\t * loaded simultaneously. So instead of doing \"pmd_populate();\n-\t\t * flush_pmd_tlb_range();\" we first mark the current pmd\n-\t\t * notpresent (atomically because here the pmd_trans_huge must\n-\t\t * remain set at all times on the pmd until the split is\n-\t\t * complete for this pmd), then we flush the SMP TLB and finally\n-\t\t * we write the non-huge version of the pmd entry with\n-\t\t * pmd_populate.\n+\t\t * anon_exclusive was already propagated to the pages backing\n+\t\t * the PTE entries by split_huge_pmd_anon_rmap(), and accessed\n+\t\t * and dirty bits are not propagated via device private\n+\t\t * entries.\n \t\t */\n-\t\told_pmd = pmdp_invalidate(vma, haddr, pmd);\n-\t\tpage = pmd_page(old_pmd);\n-\t\tfolio = page_folio(page);\n-\t\tif (pmd_dirty(old_pmd)) {\n-\t\t\tdirty = true;\n-\t\t\tfolio_set_dirty(folio);\n-\t\t}\n-\t\twrite = pmd_write(old_pmd);\n-\t\tyoung = pmd_young(old_pmd);\n-\t\tsoft_dirty = pmd_soft_dirty(old_pmd);\n-\t\tuffd_wp = pmd_uffd(old_pmd);\n+\t\tif (state-\u003ewrite)\n+\t\t\treturn make_writable_device_private_entry(pfn);\n+\t\treturn make_readable_device_private_entry(pfn);\n+\t}\n \n-\t\tVM_WARN_ON_FOLIO(!folio_ref_count(folio), folio);\n-\t\tVM_WARN_ON_FOLIO(!folio_test_anon(folio), folio);\n+\tif (state-\u003ewrite)\n+\t\tentry = make_writable_migration_entry(pfn);\n+\telse if (state-\u003eanon_exclusive)\n+\t\tentry = make_readable_exclusive_migration_entry(pfn);\n+\telse\n+\t\tentry = make_readable_migration_entry(pfn);\n \n-\t\t/*\n-\t\t * Without \"freeze\", we'll simply split the PMD, propagating the\n-\t\t * PageAnonExclusive() flag for each PTE by setting it for\n-\t\t * each subpage -- no need to (temporarily) clear.\n-\t\t *\n-\t\t * With \"freeze\" we want to replace mapped pages by\n-\t\t * migration entries right away. This is only possible if we\n-\t\t * managed to clear PageAnonExclusive() -- see\n-\t\t * set_pmd_migration_entry().\n-\t\t *\n-\t\t * In case we cannot clear PageAnonExclusive(), split the PMD\n-\t\t * only and let try_to_migrate_one() fail later.\n-\t\t *\n-\t\t * See folio_try_share_anon_rmap_pmd(): invalidate PMD first.\n-\t\t */\n-\t\tanon_exclusive = PageAnonExclusive(page);\n-\t\tif (freeze \u0026\u0026 anon_exclusive \u0026\u0026\n-\t\t    folio_try_share_anon_rmap_pmd(folio, page))\n-\t\t\tfreeze = false;\n-\t\tif (!freeze) {\n-\t\t\trmap_t rmap_flags = RMAP_NONE;\n-\n-\t\t\tfolio_ref_add(folio, HPAGE_PMD_NR - 1);\n-\t\t\tif (anon_exclusive)\n-\t\t\t\trmap_flags |= RMAP_EXCLUSIVE;\n-\t\t\tfolio_add_anon_rmap_ptes(folio, page, HPAGE_PMD_NR,\n-\t\t\t\t\t\t vma, haddr, rmap_flags);\n-\t\t}\n-\t}\n+\tif (state-\u003eyoung)\n+\t\tentry = make_migration_entry_young(entry);\n+\tif (state-\u003edirty)\n+\t\tentry = make_migration_entry_dirty(entry);\n+\n+\treturn entry;\n+}\n+\n+/*\n+ * Replace an anonymous huge PMD entry with a page table mapping the same\n+ * folio at PTE granularity.\n+ */\n+static void split_huge_pmd_to_ptes(struct vm_area_struct *vma,\n+\t\tunsigned long haddr, pmd_t *pmd, struct split_pmd_state *state)\n+{\n+\t/* Present mappings and device private entries hold a PMD-level rmap. */\n+\tconst bool rmapped = state-\u003eis_present || state-\u003eis_device_private;\n+\tstruct mm_struct *mm = vma-\u003evm_mm;\n+\tstruct page *page = state-\u003epage;\n+\tunsigned long addr;\n+\tpgtable_t pgtable;\n+\tpmd_t _pmd;\n+\tpte_t *pte;\n+\tint i;\n+\n+\tif (rmapped)\n+\t\tstate-\u003efreeze = split_huge_pmd_anon_rmap(state, vma, haddr);\n \n \t/*\n-\t * Withdraw the table only after we mark the pmd entry invalid.\n-\t * This's critical for some architectures (Power).\n+\t * The caller has already invalidated a present entry, and a softleaf\n+\t * entry is not present to begin with. Either way the entry is out of\n+\t * service before we withdraw the deposited page table, which is\n+\t * critical for some architectures (Power).\n \t */\n \tpgtable = pgtable_trans_huge_withdraw(mm, pmd);\n \tpmd_populate(mm, \u0026_pmd, pgtable);\n@@ -3337,55 +3317,17 @@ static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,\n \t * Note that NUMA hinting access restrictions are not transferred to\n \t * avoid any possibility of altering permissions across VMAs.\n \t */\n-\tif (freeze || pmd_is_migration_entry(old_pmd)) {\n-\t\tpte_t entry;\n-\t\tswp_entry_t swp_entry;\n-\n-\t\tfor (i = 0, addr = haddr; i \u003c HPAGE_PMD_NR; i++, addr += PAGE_SIZE) {\n-\t\t\tif (write)\n-\t\t\t\tswp_entry = make_writable_migration_entry(\n-\t\t\t\t\t\t\tpage_to_pfn(page + i));\n-\t\t\telse if (anon_exclusive)\n-\t\t\t\tswp_entry = make_readable_exclusive_migration_entry(\n-\t\t\t\t\t\t\tpage_to_pfn(page + i));\n-\t\t\telse\n-\t\t\t\tswp_entry = make_readable_migration_entry(\n-\t\t\t\t\t\t\tpage_to_pfn(page + i));\n-\t\t\tif (young)\n-\t\t\t\tswp_entry = make_migration_entry_young(swp_entry);\n-\t\t\tif (dirty)\n-\t\t\t\tswp_entry = make_migration_entry_dirty(swp_entry);\n-\t\t\tentry = swp_entry_to_pte(swp_entry);\n-\t\t\tif (soft_dirty)\n+\tif (state-\u003efreeze || !state-\u003eis_present) {\n+\t\tfor (i = 0, addr = haddr; i \u003c HPAGE_PMD_NR;\n+\t\t     i++, addr += PAGE_SIZE) {\n+\t\t\tconst unsigned long pfn = page_to_pfn(page + i);\n+\t\t\tconst softleaf_t leaf =\n+\t\t\t\tsplit_pmd_make_softleaf(state, pfn);\n+\t\t\tpte_t entry = softleaf_to_pte(leaf);\n+\n+\t\t\tif (state-\u003esoft_dirty)\n \t\t\t\tentry = pte_swp_mksoft_dirty(entry);\n-\t\t\tif (uffd_wp)\n-\t\t\t\tentry = pte_swp_mkuffd(entry);\n-\t\t\tVM_WARN_ON(!pte_none(ptep_get(pte + i)));\n-\t\t\tset_pte_at(mm, addr, pte + i, entry);\n-\t\t}\n-\t} else if (pmd_is_device_private_entry(old_pmd)) {\n-\t\tpte_t entry;\n-\t\tswp_entry_t swp_entry;\n-\n-\t\tfor (i = 0, addr = haddr; i \u003c HPAGE_PMD_NR; i++, addr += PAGE_SIZE) {\n-\t\t\t/*\n-\t\t\t * anon_exclusive was already propagated to the relevant\n-\t\t\t * pages corresponding to the pte entries when freeze\n-\t\t\t * is false.\n-\t\t\t */\n-\t\t\tif (write)\n-\t\t\t\tswp_entry = make_writable_device_private_entry(\n-\t\t\t\t\t\t\tpage_to_pfn(page + i));\n-\t\t\telse\n-\t\t\t\tswp_entry = make_readable_device_private_entry(\n-\t\t\t\t\t\t\tpage_to_pfn(page + i));\n-\t\t\t/*\n-\t\t\t * Young and dirty bits are not progated via swp_entry\n-\t\t\t */\n-\t\t\tentry = swp_entry_to_pte(swp_entry);\n-\t\t\tif (soft_dirty)\n-\t\t\t\tentry = pte_swp_mksoft_dirty(entry);\n-\t\t\tif (uffd_wp)\n+\t\t\tif (state-\u003euffd)\n \t\t\t\tentry = pte_swp_mkuffd(entry);\n \t\t\tVM_WARN_ON(!pte_none(ptep_get(pte + i)));\n \t\t\tset_pte_at(mm, addr, pte + i, entry);\n@@ -3394,20 +3336,20 @@ static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,\n \t\tpte_t entry;\n \n \t\tentry = mk_pte(page, READ_ONCE(vma-\u003evm_page_prot));\n-\t\tif (write)\n+\t\tif (state-\u003ewrite)\n \t\t\tentry = pte_mkwrite(entry, vma);\n-\t\tif (!young)\n+\t\tif (!state-\u003eyoung)\n \t\t\tentry = pte_mkold(entry);\n \t\t/* NOTE: this may set soft-dirty too on some archs */\n-\t\tif (dirty)\n+\t\tif (state-\u003edirty)\n \t\t\tentry = pte_mkdirty(entry);\n-\t\tif (soft_dirty)\n+\t\tif (state-\u003esoft_dirty)\n \t\t\tentry = pte_mksoft_dirty(entry);\n-\t\tif (uffd_wp)\n+\t\tif (state-\u003euffd)\n \t\t\tentry = pte_mkuffd(entry);\n \n \t\t/* Restore PAGE_NONE so an RWP marker keeps trapping */\n-\t\tif (userfaultfd_rwp(vma) \u0026\u0026 uffd_wp)\n+\t\tif (userfaultfd_rwp(vma) \u0026\u0026 state-\u003euffd)\n \t\t\tentry = pte_modify(entry, PAGE_NONE);\n \n \t\tfor (i = 0; i \u003c HPAGE_PMD_NR; i++)\n@@ -3417,15 +3359,152 @@ static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,\n \t}\n \tpte_unmap(pte);\n \n-\tif (!pmd_is_migration_entry(*pmd))\n-\t\tfolio_remove_rmap_pmd(folio, page, vma);\n-\tif (freeze)\n+\tif (rmapped)\n+\t\tfolio_remove_rmap_pmd(state-\u003efolio, page, vma);\n+\tif (state-\u003efreeze)\n \t\tput_page(page);\n \n \tsmp_wmb(); /* make pte visible before pmd */\n \tpmd_populate(mm, pmd, pgtable);\n }\n \n+static void split_present_huge_pmd(struct vm_area_struct *vma,\n+\t\tunsigned long haddr, pmd_t *pmd, struct folio *folio,\n+\t\tbool freeze)\n+{\n+\tstruct split_pmd_state state = {\n+\t\t.folio = folio,\n+\t\t.is_present = true,\n+\t\t.freeze = freeze,\n+\t};\n+\n+\t/*\n+\t * Up to this point the pmd is present and huge and userland has the\n+\t * whole access to the hugepage during the split (which happens in\n+\t * place). If we overwrite the pmd with the not-huge version pointing\n+\t * to the pte here (which of course we could if all CPUs were bug\n+\t * free), userland could trigger a small page size TLB miss on the\n+\t * small sized TLB while the hugepage TLB entry is still established in\n+\t * the huge TLB. Some CPU doesn't like that. See\n+\t * http://support.amd.com/TechDocs/41322_10h_Rev_Gd.pdf, Erratum 383 on\n+\t * page 105. Intel should be safe but is also warns that it's only safe\n+\t * if the permission and cache attributes of the two entries loaded in\n+\t * the two TLB is identical (which should be the case here). But it is\n+\t * generally safer to never allow small and huge TLB entries for the\n+\t * same virtual address to be loaded simultaneously. So instead of\n+\t * doing \"pmd_populate(); flush_pmd_tlb_range();\" we first mark the\n+\t * current pmd notpresent (atomically because here the pmd_trans_huge\n+\t * must remain set at all times on the pmd until the split is complete\n+\t * for this pmd), then we flush the SMP TLB and finally we write the\n+\t * non-huge version of the pmd entry with pmd_populate.\n+\t *\n+\t * This must also happen before PageAnonExclusive() is read below, see\n+\t * folio_try_share_anon_rmap_pmd().\n+\t */\n+\tconst pmd_t pmdval = pmdp_invalidate(vma, haddr, pmd);\n+\n+\tstate.page = pmd_page(pmdval);\n+\tstate.write = pmd_write(pmdval);\n+\tstate.young = pmd_young(pmdval);\n+\tstate.dirty = pmd_dirty(pmdval);\n+\tstate.soft_dirty = pmd_soft_dirty(pmdval);\n+\tstate.uffd = pmd_uffd(pmdval);\n+\tstate.anon_exclusive = PageAnonExclusive(state.page);\n+\n+\tif (state.dirty)\n+\t\tfolio_set_dirty(folio);\n+\n+\tVM_WARN_ON_FOLIO(!folio_ref_count(folio), folio);\n+\n+\tsplit_huge_pmd_to_ptes(vma, haddr, pmd, \u0026state);\n+}\n+\n+static void split_non_present_huge_pmd(struct vm_area_struct *vma,\n+\t\tunsigned long haddr, pmd_t *pmd, pmd_t old_pmd,\n+\t\tstruct folio *folio, bool freeze)\n+{\n+\tconst softleaf_t entry = softleaf_from_pmd(old_pmd);\n+\tstruct split_pmd_state state = {\n+\t\t.folio = folio,\n+\t\t.page = softleaf_to_page(entry),\n+\t\t.is_device_private = softleaf_is_device_private(entry),\n+\t\t.freeze = freeze,\n+\t\t.soft_dirty = pmd_swp_soft_dirty(old_pmd),\n+\t\t.uffd = pmd_swp_uffd(old_pmd),\n+\t};\n+\n+\tif (state.is_device_private) {\n+\t\t/*\n+\t\t * Device private folios are treated the same as regular folios\n+\t\t * w.r.t. anon exclusive handling, see\n+\t\t * split_huge_pmd_anon_rmap().\n+\t\t */\n+\t\tstate.write = softleaf_is_device_private_write(entry);\n+\t\tstate.anon_exclusive = PageAnonExclusive(state.page);\n+\t} else {\n+\t\tstate.write = softleaf_is_migration_write(entry);\n+\t\tstate.young = softleaf_is_migration_young(entry);\n+\t\tstate.dirty = softleaf_is_migration_dirty(entry);\n+\t\tstate.anon_exclusive =\n+\t\t\tsoftleaf_is_migration_read_exclusive(entry);\n+\t}\n+\n+\tsplit_huge_pmd_to_ptes(vma, haddr, pmd, \u0026state);\n+}\n+\n+static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,\n+\t\tunsigned long haddr, bool freeze)\n+{\n+\tconst pmd_t old_pmd = *pmd;\n+\tconst bool is_present = pmd_present(old_pmd);\n+\tstruct folio *folio;\n+\n+\tVM_BUG_ON(haddr \u0026 ~HPAGE_PMD_MASK);\n+\tVM_BUG_ON_VMA(vma-\u003evm_start \u003e haddr, vma);\n+\tVM_BUG_ON_VMA(vma-\u003evm_end \u003c haddr + HPAGE_PMD_SIZE, vma);\n+\n+\tVM_WARN_ON_ONCE(!pmd_is_valid_softleaf(old_pmd) \u0026\u0026\n+\t\t\t!pmd_trans_huge(old_pmd));\n+\n+\tcount_vm_event(THP_SPLIT_PMD);\n+\n+\t/*\n+\t * FIXME: Do we want to invalidate secondary mmu by calling\n+\t * mmu_notifier_arch_invalidate_secondary_tlbs() see comments below\n+\t * inside __split_huge_pmd() ?\n+\t *\n+\t * We are going from a zero huge page write protected to zero small\n+\t * page also write protected so it does not seems useful to invalidate\n+\t * secondary mmu at this time.\n+\t */\n+\tif (huge_zero_pmd_can_split(vma, old_pmd)) {\n+\t\t__split_huge_zero_page_pmd(vma, haddr, pmd);\n+\t\treturn;\n+\t}\n+\n+\tfolio = normal_or_softleaf_folio_pmd(vma, haddr, old_pmd, is_present);\n+\n+\t/*\n+\t * A non-present entry which is neither a migration nor a device\n+\t * private entry is corrupt, and pmd_to_softleaf_folio() has already\n+\t * warned about it. Leave it alone rather than act on a PFN which\n+\t * means nothing.\n+\t */\n+\tif (unlikely(!is_present \u0026\u0026 !folio))\n+\t\treturn;\n+\n+\tif (!folio || !folio_test_anon(folio)) {\n+\t\tunmap_huge_pmd_entry(vma, haddr, pmd, folio, is_present);\n+\t\treturn;\n+\t}\n+\n+\tif (is_present)\n+\t\tsplit_present_huge_pmd(vma, haddr, pmd, folio, freeze);\n+\telse\n+\t\tsplit_non_present_huge_pmd(vma, haddr, pmd, old_pmd, folio,\n+\t\t\t\t\t   freeze);\n+}\n+\n void split_huge_pmd_locked(struct vm_area_struct *vma, unsigned long address,\n \t\t\t   pmd_t *pmd, bool freeze)\n {\n"
}

2/1 2026/08/28 19:39 agent patch-evaluator
0m Model:
core

Args:
null

Results:
{
  "EnableConfigs": [
    "TRANSPARENT_HUGEPAGE",
    "DEVICE_PRIVATE",
    "USERFAULTFD"
  ],
  "FocusSymbols": [
    "split_huge_pmd_locked",
    "__split_huge_pmd",
    "split_huge_pmd_address"
  ],
  "Reasoning": "The patch refactors core Transparent Huge Page (THP) PMD split logic in mm/huge_memory.c, separating the handling of present vs non-present (migration, device-private) huge PMDs and reorganizing rmap conversion and unmapping logic. This core MM functionality is reachable via user actions (madvise, mprotect, page migration, etc.) and is worth fuzzing.",
  "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 37afda2d1094b0a9df200c535bd4b01247a9f711
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Aug 28 19:39:21 2026 +0000

    syz-cluster: applied patch under review

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index afbb5974bd225..8e0fd11da3d7a 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3139,193 +3139,173 @@ static void __split_huge_zero_page_pmd(struct vm_area_struct *vma,
 	pmd_populate(mm, pmd, pgtable);
 }
 
-static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,
-		unsigned long haddr, bool freeze)
+/*
+ * Only a huge zero PMD in an anonymous VMA is split, into a page table of
+ * shared zero page mappings. Any other huge zero PMD is simply unmapped.
+ */
+static bool huge_zero_pmd_can_split(struct vm_area_struct *vma, pmd_t pmdval)
 {
-	struct mm_struct *mm = vma->vm_mm;
-	struct folio *folio;
-	struct page *page;
-	pgtable_t pgtable;
-	pmd_t old_pmd, _pmd;
-	bool soft_dirty, uffd_wp = false, young = false, write = false;
-	bool anon_exclusive = false, dirty = false;
-	unsigned long addr;
-	pte_t *pte;
-	int i;
-
-	VM_BUG_ON(haddr & ~HPAGE_PMD_MASK);
-	VM_BUG_ON_VMA(vma->vm_start > haddr, vma);
-	VM_BUG_ON_VMA(vma->vm_end < haddr + HPAGE_PMD_SIZE, vma);
+	return is_huge_zero_pmd(pmdval) && vma_is_anonymous(vma);
+}
 
-	VM_WARN_ON_ONCE(!pmd_is_valid_softleaf(*pmd) && !pmd_trans_huge(*pmd));
+/**
+ * unmap_huge_pmd_entry() - Unmap a huge PMD entry rather than splitting it.
+ * @vma: The VMA @pmd belongs to.
+ * @haddr: The PMD-aligned address @pmd maps.
+ * @pmd: Pointer to the huge PMD entry.
+ * @folio: The folio @pmd describes, or NULL if it describes none.
+ * @is_present: Is @pmd a present entry rather than a softleaf entry?
+ *
+ * Only anonymous folios are rebuilt at PTE level when a huge PMD entry is
+ * split. Everything else is unmapped here and faulted back in on the next
+ * access.
+ */
+static void unmap_huge_pmd_entry(struct vm_area_struct *vma,
+		unsigned long haddr, pmd_t *pmd, struct folio *folio,
+		bool is_present)
+{
+	struct mm_struct *mm = vma->vm_mm;
+	pmd_t old_pmd;
 
-	count_vm_event(THP_SPLIT_PMD);
+	old_pmd = pmdp_huge_clear_flush(vma, haddr, pmd);
+	/*
+	 * We are going to unmap this huge page. So
+	 * just go ahead and zap it
+	 */
+	if (has_deposited_pgtable(vma, old_pmd, folio))
+		zap_deposited_table(mm, pmd);
 
-	if (!vma_is_anonymous(vma)) {
-		old_pmd = pmdp_huge_clear_flush(vma, haddr, pmd);
-		/*
-		 * We are going to unmap this huge page. So
-		 * just go ahead and zap it
-		 */
-		if (arch_needs_pgtable_deposit())
-			zap_deposited_table(mm, pmd);
-		if (vma_is_special_huge(vma))
-			return;
-		if (unlikely(pmd_is_migration_entry(old_pmd))) {
-			const softleaf_t old_entry = softleaf_from_pmd(old_pmd);
-
-			folio = softleaf_to_folio(old_entry);
-		} else if (is_huge_zero_pmd(old_pmd)) {
-			return;
-		} else {
-			page = pmd_page(old_pmd);
-			folio = page_folio(page);
-			if (!folio_test_dirty(folio) && pmd_dirty(old_pmd))
-				folio_mark_dirty(folio);
-			if (!folio_test_referenced(folio) && pmd_young(old_pmd))
-				folio_set_referenced(folio);
-			folio_remove_rmap_pmd(folio, page, vma);
-			add_mm_counter(mm, mm_counter_file(folio), -HPAGE_PMD_NR);
-			folio_put(folio);
-			return;
-		}
-		add_mm_counter(mm, mm_counter_file(folio), -HPAGE_PMD_NR);
+	if (!folio)
 		return;
-	}
 
-	if (is_huge_zero_pmd(*pmd)) {
-		/*
-		 * FIXME: Do we want to invalidate secondary mmu by calling
-		 * mmu_notifier_arch_invalidate_secondary_tlbs() see comments below
-		 * inside __split_huge_pmd() ?
-		 *
-		 * We are going from a zero huge page write protected to zero
-		 * small page also write protected so it does not seems useful
-		 * to invalidate secondary mmu at this time.
-		 */
-		return __split_huge_zero_page_pmd(vma, haddr, pmd);
+	if (is_present) {
+		struct page *page = pmd_page(old_pmd);
+
+		if (!folio_test_dirty(folio) && pmd_dirty(old_pmd))
+			folio_mark_dirty(folio);
+		if (!folio_test_referenced(folio) && pmd_young(old_pmd))
+			folio_set_referenced(folio);
+		folio_remove_rmap_pmd(folio, page, vma);
 	}
 
-	if (pmd_is_migration_entry(*pmd)) {
-		softleaf_t entry;
+	add_mm_counter(mm, mm_counter_file(folio), -HPAGE_PMD_NR);
 
-		old_pmd = *pmd;
-		entry = softleaf_from_pmd(old_pmd);
-		page = softleaf_to_page(entry);
-		folio = page_folio(page);
+	if (is_present)
+		folio_put(folio);
+}
 
-		soft_dirty = pmd_swp_soft_dirty(old_pmd);
-		uffd_wp = pmd_swp_uffd(old_pmd);
+struct split_pmd_state {
+	struct folio *folio;
+	struct page *page;
+	bool is_present;
+	bool is_device_private;
+	bool freeze;
+	bool write;
+	bool young;
+	bool dirty;
+	bool soft_dirty;
+	bool uffd;
+	bool anon_exclusive;
+};
 
-		write = softleaf_is_migration_write(entry);
-		if (PageAnon(page))
-			anon_exclusive = softleaf_is_migration_read_exclusive(entry);
-		young = softleaf_is_migration_young(entry);
-		dirty = softleaf_is_migration_dirty(entry);
-	} else if (pmd_is_device_private_entry(*pmd)) {
-		softleaf_t entry;
+/*
+ * Convert the folio's PMD-level anonymous rmap into PTE-level ones.
+ *
+ * Without "freeze", we'll simply split the PMD, propagating the
+ * PageAnonExclusive() flag for each PTE by setting it for
+ * each subpage -- no need to (temporarily) clear.
+ *
+ * With "freeze" we want to replace mapped pages by
+ * migration entries right away. This is only possible if we
+ * managed to clear PageAnonExclusive() -- see
+ * set_pmd_migration_entry().
+ *
+ * In case we cannot clear PageAnonExclusive(), split the PMD
+ * only and let try_to_migrate_one() fail later.
+ *
+ * See folio_try_share_anon_rmap_pmd(): invalidate PMD first.
+ *
+ * Returns: whether the mapping may still be frozen.
+ */
+static bool split_huge_pmd_anon_rmap(const struct split_pmd_state *state,
+		struct vm_area_struct *vma, unsigned long haddr)
+{
+	rmap_t rmap_flags = RMAP_NONE;
 
-		old_pmd = *pmd;
-		entry = softleaf_from_pmd(old_pmd);
-		page = softleaf_to_page(entry);
-		folio = page_folio(page);
+	if (state->freeze &&
+	    (!state->anon_exclusive ||
+	     !folio_try_share_anon_rmap_pmd(state->folio, state->page)))
+		return true;
 
-		soft_dirty = pmd_swp_soft_dirty(old_pmd);
-		uffd_wp = pmd_swp_uffd(old_pmd);
+	folio_ref_add(state->folio, HPAGE_PMD_NR - 1);
+	if (state->anon_exclusive)
+		rmap_flags |= RMAP_EXCLUSIVE;
+	folio_add_anon_rmap_ptes(state->folio, state->page, HPAGE_PMD_NR, vma,
+				 haddr, rmap_flags);
+	return false;
+}
 
-		write = softleaf_is_device_private_write(entry);
-		anon_exclusive = PageAnonExclusive(page);
+/*
+ * Build the leaf entry for the PTE entry describing @pfn, for a huge PMD entry
+ * which is not restored as a present mapping.
+ */
+static softleaf_t split_pmd_make_softleaf(const struct split_pmd_state *state,
+					  unsigned long pfn)
+{
+	softleaf_t entry;
 
+	if (state->is_device_private && !state->freeze) {
 		/*
-		 * Device private THP should be treated the same as regular
-		 * folios w.r.t anon exclusive handling. See the comments for
-		 * folio handling and anon_exclusive below.
-		 */
-		if (freeze && anon_exclusive &&
-		    folio_try_share_anon_rmap_pmd(folio, page))
-			freeze = false;
-		if (!freeze) {
-			rmap_t rmap_flags = RMAP_NONE;
-
-			folio_ref_add(folio, HPAGE_PMD_NR - 1);
-			if (anon_exclusive)
-				rmap_flags |= RMAP_EXCLUSIVE;
-
-			folio_add_anon_rmap_ptes(folio, page, HPAGE_PMD_NR,
-						 vma, haddr, rmap_flags);
-		}
-	} else {
-		/*
-		 * Up to this point the pmd is present and huge and userland has
-		 * the whole access to the hugepage during the split (which
-		 * happens in place). If we overwrite the pmd with the not-huge
-		 * version pointing to the pte here (which of course we could if
-		 * all CPUs were bug free), userland could trigger a small page
-		 * size TLB miss on the small sized TLB while the hugepage TLB
-		 * entry is still established in the huge TLB. Some CPU doesn't
-		 * like that. See
-		 * http://support.amd.com/TechDocs/41322_10h_Rev_Gd.pdf, Erratum
-		 * 383 on page 105. Intel should be safe but is also warns that
-		 * it's only safe if the permission and cache attributes of the
-		 * two entries loaded in the two TLB is identical (which should
-		 * be the case here). But it is generally safer to never allow
-		 * small and huge TLB entries for the same virtual address to be
-		 * loaded simultaneously. So instead of doing "pmd_populate();
-		 * flush_pmd_tlb_range();" we first mark the current pmd
-		 * notpresent (atomically because here the pmd_trans_huge must
-		 * remain set at all times on the pmd until the split is
-		 * complete for this pmd), then we flush the SMP TLB and finally
-		 * we write the non-huge version of the pmd entry with
-		 * pmd_populate.
+		 * anon_exclusive was already propagated to the pages backing
+		 * the PTE entries by split_huge_pmd_anon_rmap(), and accessed
+		 * and dirty bits are not propagated via device private
+		 * entries.
 		 */
-		old_pmd = pmdp_invalidate(vma, haddr, pmd);
-		page = pmd_page(old_pmd);
-		folio = page_folio(page);
-		if (pmd_dirty(old_pmd)) {
-			dirty = true;
-			folio_set_dirty(folio);
-		}
-		write = pmd_write(old_pmd);
-		young = pmd_young(old_pmd);
-		soft_dirty = pmd_soft_dirty(old_pmd);
-		uffd_wp = pmd_uffd(old_pmd);
+		if (state->write)
+			return make_writable_device_private_entry(pfn);
+		return make_readable_device_private_entry(pfn);
+	}
 
-		VM_WARN_ON_FOLIO(!folio_ref_count(folio), folio);
-		VM_WARN_ON_FOLIO(!folio_test_anon(folio), folio);
+	if (state->write)
+		entry = make_writable_migration_entry(pfn);
+	else if (state->anon_exclusive)
+		entry = make_readable_exclusive_migration_entry(pfn);
+	else
+		entry = make_readable_migration_entry(pfn);
 
-		/*
-		 * Without "freeze", we'll simply split the PMD, propagating the
-		 * PageAnonExclusive() flag for each PTE by setting it for
-		 * each subpage -- no need to (temporarily) clear.
-		 *
-		 * With "freeze" we want to replace mapped pages by
-		 * migration entries right away. This is only possible if we
-		 * managed to clear PageAnonExclusive() -- see
-		 * set_pmd_migration_entry().
-		 *
-		 * In case we cannot clear PageAnonExclusive(), split the PMD
-		 * only and let try_to_migrate_one() fail later.
-		 *
-		 * See folio_try_share_anon_rmap_pmd(): invalidate PMD first.
-		 */
-		anon_exclusive = PageAnonExclusive(page);
-		if (freeze && anon_exclusive &&
-		    folio_try_share_anon_rmap_pmd(folio, page))
-			freeze = false;
-		if (!freeze) {
-			rmap_t rmap_flags = RMAP_NONE;
-
-			folio_ref_add(folio, HPAGE_PMD_NR - 1);
-			if (anon_exclusive)
-				rmap_flags |= RMAP_EXCLUSIVE;
-			folio_add_anon_rmap_ptes(folio, page, HPAGE_PMD_NR,
-						 vma, haddr, rmap_flags);
-		}
-	}
+	if (state->young)
+		entry = make_migration_entry_young(entry);
+	if (state->dirty)
+		entry = make_migration_entry_dirty(entry);
+
+	return entry;
+}
+
+/*
+ * Replace an anonymous huge PMD entry with a page table mapping the same
+ * folio at PTE granularity.
+ */
+static void split_huge_pmd_to_ptes(struct vm_area_struct *vma,
+		unsigned long haddr, pmd_t *pmd, struct split_pmd_state *state)
+{
+	/* Present mappings and device private entries hold a PMD-level rmap. */
+	const bool rmapped = state->is_present || state->is_device_private;
+	struct mm_struct *mm = vma->vm_mm;
+	struct page *page = state->page;
+	unsigned long addr;
+	pgtable_t pgtable;
+	pmd_t _pmd;
+	pte_t *pte;
+	int i;
+
+	if (rmapped)
+		state->freeze = split_huge_pmd_anon_rmap(state, vma, haddr);
 
 	/*
-	 * Withdraw the table only after we mark the pmd entry invalid.
-	 * This's critical for some architectures (Power).
+	 * The caller has already invalidated a present entry, and a softleaf
+	 * entry is not present to begin with. Either way the entry is out of
+	 * service before we withdraw the deposited page table, which is
+	 * critical for some architectures (Power).
 	 */
 	pgtable = pgtable_trans_huge_withdraw(mm, pmd);
 	pmd_populate(mm, &_pmd, pgtable);
@@ -3337,55 +3317,17 @@ static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,
 	 * Note that NUMA hinting access restrictions are not transferred to
 	 * avoid any possibility of altering permissions across VMAs.
 	 */
-	if (freeze || pmd_is_migration_entry(old_pmd)) {
-		pte_t entry;
-		swp_entry_t swp_entry;
-
-		for (i = 0, addr = haddr; i < HPAGE_PMD_NR; i++, addr += PAGE_SIZE) {
-			if (write)
-				swp_entry = make_writable_migration_entry(
-							page_to_pfn(page + i));
-			else if (anon_exclusive)
-				swp_entry = make_readable_exclusive_migration_entry(
-							page_to_pfn(page + i));
-			else
-				swp_entry = make_readable_migration_entry(
-							page_to_pfn(page + i));
-			if (young)
-				swp_entry = make_migration_entry_young(swp_entry);
-			if (dirty)
-				swp_entry = make_migration_entry_dirty(swp_entry);
-			entry = swp_entry_to_pte(swp_entry);
-			if (soft_dirty)
+	if (state->freeze || !state->is_present) {
+		for (i = 0, addr = haddr; i < HPAGE_PMD_NR;
+		     i++, addr += PAGE_SIZE) {
+			const unsigned long pfn = page_to_pfn(page + i);
+			const softleaf_t leaf =
+				split_pmd_make_softleaf(state, pfn);
+			pte_t entry = softleaf_to_pte(leaf);
+
+			if (state->soft_dirty)
 				entry = pte_swp_mksoft_dirty(entry);
-			if (uffd_wp)
-				entry = pte_swp_mkuffd(entry);
-			VM_WARN_ON(!pte_none(ptep_get(pte + i)));
-			set_pte_at(mm, addr, pte + i, entry);
-		}
-	} else if (pmd_is_device_private_entry(old_pmd)) {
-		pte_t entry;
-		swp_entry_t swp_entry;
-
-		for (i = 0, addr = haddr; i < HPAGE_PMD_NR; i++, addr += PAGE_SIZE) {
-			/*
-			 * anon_exclusive was already propagated to the relevant
-			 * pages corresponding to the pte entries when freeze
-			 * is false.
-			 */
-			if (write)
-				swp_entry = make_writable_device_private_entry(
-							page_to_pfn(page + i));
-			else
-				swp_entry = make_readable_device_private_entry(
-							page_to_pfn(page + i));
-			/*
-			 * Young and dirty bits are not progated via swp_entry
-			 */
-			entry = swp_entry_to_pte(swp_entry);
-			if (soft_dirty)
-				entry = pte_swp_mksoft_dirty(entry);
-			if (uffd_wp)
+			if (state->uffd)
 				entry = pte_swp_mkuffd(entry);
 			VM_WARN_ON(!pte_none(ptep_get(pte + i)));
 			set_pte_at(mm, addr, pte + i, entry);
@@ -3394,20 +3336,20 @@ static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,
 		pte_t entry;
 
 		entry = mk_pte(page, READ_ONCE(vma->vm_page_prot));
-		if (write)
+		if (state->write)
 			entry = pte_mkwrite(entry, vma);
-		if (!young)
+		if (!state->young)
 			entry = pte_mkold(entry);
 		/* NOTE: this may set soft-dirty too on some archs */
-		if (dirty)
+		if (state->dirty)
 			entry = pte_mkdirty(entry);
-		if (soft_dirty)
+		if (state->soft_dirty)
 			entry = pte_mksoft_dirty(entry);
-		if (uffd_wp)
+		if (state->uffd)
 			entry = pte_mkuffd(entry);
 
 		/* Restore PAGE_NONE so an RWP marker keeps trapping */
-		if (userfaultfd_rwp(vma) && uffd_wp)
+		if (userfaultfd_rwp(vma) && state->uffd)
 			entry = pte_modify(entry, PAGE_NONE);
 
 		for (i = 0; i < HPAGE_PMD_NR; i++)
@@ -3417,15 +3359,152 @@ static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,
 	}
 	pte_unmap(pte);
 
-	if (!pmd_is_migration_entry(*pmd))
-		folio_remove_rmap_pmd(folio, page, vma);
-	if (freeze)
+	if (rmapped)
+		folio_remove_rmap_pmd(state->folio, page, vma);
+	if (state->freeze)
 		put_page(page);
 
 	smp_wmb(); /* make pte visible before pmd */
 	pmd_populate(mm, pmd, pgtable);
 }
 
+static void split_present_huge_pmd(struct vm_area_struct *vma,
+		unsigned long haddr, pmd_t *pmd, struct folio *folio,
+		bool freeze)
+{
+	struct split_pmd_state state = {
+		.folio = folio,
+		.is_present = true,
+		.freeze = freeze,
+	};
+
+	/*
+	 * Up to this point the pmd is present and huge and userland has the
+	 * whole access to the hugepage during the split (which happens in
+	 * place). If we overwrite the pmd with the not-huge version pointing
+	 * to the pte here (which of course we could if all CPUs were bug
+	 * free), userland could trigger a small page size TLB miss on the
+	 * small sized TLB while the hugepage TLB entry is still established in
+	 * the huge TLB. Some CPU doesn't like that. See
+	 * http://support.amd.com/TechDocs/41322_10h_Rev_Gd.pdf, Erratum 383 on
+	 * page 105. Intel should be safe but is also warns that it's only safe
+	 * if the permission and cache attributes of the two entries loaded in
+	 * the two TLB is identical (which should be the case here). But it is
+	 * generally safer to never allow small and huge TLB entries for the
+	 * same virtual address to be loaded simultaneously. So instead of
+	 * doing "pmd_populate(); flush_pmd_tlb_range();" we first mark the
+	 * current pmd notpresent (atomically because here the pmd_trans_huge
+	 * must remain set at all times on the pmd until the split is complete
+	 * for this pmd), then we flush the SMP TLB and finally we write the
+	 * non-huge version of the pmd entry with pmd_populate.
+	 *
+	 * This must also happen before PageAnonExclusive() is read below, see
+	 * folio_try_share_anon_rmap_pmd().
+	 */
+	const pmd_t pmdval = pmdp_invalidate(vma, haddr, pmd);
+
+	state.page = pmd_page(pmdval);
+	state.write = pmd_write(pmdval);
+	state.young = pmd_young(pmdval);
+	state.dirty = pmd_dirty(pmdval);
+	state.soft_dirty = pmd_soft_dirty(pmdval);
+	state.uffd = pmd_uffd(pmdval);
+	state.anon_exclusive = PageAnonExclusive(state.page);
+
+	if (state.dirty)
+		folio_set_dirty(folio);
+
+	VM_WARN_ON_FOLIO(!folio_ref_count(folio), folio);
+
+	split_huge_pmd_to_ptes(vma, haddr, pmd, &state);
+}
+
+static void split_non_present_huge_pmd(struct vm_area_struct *vma,
+		unsigned long haddr, pmd_t *pmd, pmd_t old_pmd,
+		struct folio *folio, bool freeze)
+{
+	const softleaf_t entry = softleaf_from_pmd(old_pmd);
+	struct split_pmd_state state = {
+		.folio = folio,
+		.page = softleaf_to_page(entry),
+		.is_device_private = softleaf_is_device_private(entry),
+		.freeze = freeze,
+		.soft_dirty = pmd_swp_soft_dirty(old_pmd),
+		.uffd = pmd_swp_uffd(old_pmd),
+	};
+
+	if (state.is_device_private) {
+		/*
+		 * Device private folios are treated the same as regular folios
+		 * w.r.t. anon exclusive handling, see
+		 * split_huge_pmd_anon_rmap().
+		 */
+		state.write = softleaf_is_device_private_write(entry);
+		state.anon_exclusive = PageAnonExclusive(state.page);
+	} else {
+		state.write = softleaf_is_migration_write(entry);
+		state.young = softleaf_is_migration_young(entry);
+		state.dirty = softleaf_is_migration_dirty(entry);
+		state.anon_exclusive =
+			softleaf_is_migration_read_exclusive(entry);
+	}
+
+	split_huge_pmd_to_ptes(vma, haddr, pmd, &state);
+}
+
+static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,
+		unsigned long haddr, bool freeze)
+{
+	const pmd_t old_pmd = *pmd;
+	const bool is_present = pmd_present(old_pmd);
+	struct folio *folio;
+
+	VM_BUG_ON(haddr & ~HPAGE_PMD_MASK);
+	VM_BUG_ON_VMA(vma->vm_start > haddr, vma);
+	VM_BUG_ON_VMA(vma->vm_end < haddr + HPAGE_PMD_SIZE, vma);
+
+	VM_WARN_ON_ONCE(!pmd_is_valid_softleaf(old_pmd) &&
+			!pmd_trans_huge(old_pmd));
+
+	count_vm_event(THP_SPLIT_PMD);
+
+	/*
+	 * FIXME: Do we want to invalidate secondary mmu by calling
+	 * mmu_notifier_arch_invalidate_secondary_tlbs() see comments below
+	 * inside __split_huge_pmd() ?
+	 *
+	 * We are going from a zero huge page write protected to zero small
+	 * page also write protected so it does not seems useful to invalidate
+	 * secondary mmu at this time.
+	 */
+	if (huge_zero_pmd_can_split(vma, old_pmd)) {
+		__split_huge_zero_page_pmd(vma, haddr, pmd);
+		return;
+	}
+
+	folio = normal_or_softleaf_folio_pmd(vma, haddr, old_pmd, is_present);
+
+	/*
+	 * A non-present entry which is neither a migration nor a device
+	 * private entry is corrupt, and pmd_to_softleaf_folio() has already
+	 * warned about it. Leave it alone rather than act on a PFN which
+	 * means nothing.
+	 */
+	if (unlikely(!is_present && !folio))
+		return;
+
+	if (!folio || !folio_test_anon(folio)) {
+		unmap_huge_pmd_entry(vma, haddr, pmd, folio, is_present);
+		return;
+	}
+
+	if (is_present)
+		split_present_huge_pmd(vma, haddr, pmd, folio, freeze);
+	else
+		split_non_present_huge_pmd(vma, haddr, pmd, old_pmd, folio,
+					   freeze);
+}
+
 void split_huge_pmd_locked(struct vm_area_struct *vma, unsigned long address,
 			   pmd_t *pmd, bool freeze)
 {

3/2 2026/08/28 19:39 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 9668
							output: 28
							thoughts: 185
						

Thoughts:
**Analyzing the Huge Pages Patch**

Okay, let's dive into this patch and figure out the fuzzing strategy. First, I need to understand if this even warrants fuzzing attention, and if so, what configurations and focus symbols are critical. My initial approach is to dissect the diff.

The changes are concentrated in `mm/huge_memory.c`, which always piques my interest. Specifically, the patch touches a handful of key functions: `__split_huge_pmd_locked`, `split_huge_pmd_anon_rmap`, `split_pmd_make_softleaf`, `split_huge_pmd_to_ptes`, `split_present_huge_pmd`, `split_non_present_huge_pmd`, `unmap_huge_pmd_entry`, and `huge_zero_pmd_can_split`. This suggests a potential refactoring or improvement related to splitting huge pages, likely impacting memory management in a significant way.

To get a clearer picture, I'll leverage `grepper` or `read-file` to get the context of those functions and the related Kconfig options. It's crucial to understand how these functions interact with the wider system and any configuration knobs that might influence their behavior. Knowing which config options trigger those functions will be the key to designing the right fuzzing strategy.



4/2 2026/08/28 19:39 tool grepper
0m Args:
{
  "Expression": "split_huge_pmd",
  "PathPrefix": "mm/"
}

Results:
{
  "Output": "mm/gup.c=898=static struct page *follow_pmd_mask(struct vm_area_struct *vma,\n--\nmm/gup.c-931-\t\tspin_unlock(ptl);\nmm/gup.c:932:\t\tsplit_huge_pmd(vma, pmd, address);\nmm/gup.c-933-\t\t/* If pmd was left empty, stuff a page table in there quickly */\n--\nmm/huge_memory.c=1908=int copy_huge_pmd(struct mm_struct *dst_mm, struct mm_struct *src_mm,\n--\nmm/huge_memory.c-1990-\t\tspin_unlock(dst_ptl);\nmm/huge_memory.c:1991:\t\t__split_huge_pmd(src_vma, src_pmd, addr, false);\nmm/huge_memory.c-1992-\t\treturn -EAGAIN;\n--\nmm/huge_memory.c=2125=vm_fault_t do_huge_pmd_wp_page(struct vm_fault *vmf)\n--\nmm/huge_memory.c-2214-fallback:\nmm/huge_memory.c:2215:\t__split_huge_pmd(vma, vmf-\u003epmd, vmf-\u003eaddress, false);\nmm/huge_memory.c-2216-\treturn VM_FAULT_FALLBACK;\n--\nmm/huge_memory.c=3197=struct split_pmd_state {\n--\nmm/huge_memory.c-3229- */\nmm/huge_memory.c:3230:static bool split_huge_pmd_anon_rmap(const struct split_pmd_state *state,\nmm/huge_memory.c-3231-\t\tstruct vm_area_struct *vma, unsigned long haddr)\n--\nmm/huge_memory.c=3252=static softleaf_t split_pmd_make_softleaf(const struct split_pmd_state *state,\n--\nmm/huge_memory.c-3259-\t\t * anon_exclusive was already propagated to the pages backing\nmm/huge_memory.c:3260:\t\t * the PTE entries by split_huge_pmd_anon_rmap(), and accessed\nmm/huge_memory.c-3261-\t\t * and dirty bits are not propagated via device private\n--\nmm/huge_memory.c-3287- */\nmm/huge_memory.c:3288:static void split_huge_pmd_to_ptes(struct vm_area_struct *vma,\nmm/huge_memory.c-3289-\t\tunsigned long haddr, pmd_t *pmd, struct split_pmd_state *state)\n--\nmm/huge_memory.c-3301-\tif (rmapped)\nmm/huge_memory.c:3302:\t\tstate-\u003efreeze = split_huge_pmd_anon_rmap(state, vma, haddr);\nmm/huge_memory.c-3303-\n--\nmm/huge_memory.c=3371=static void split_present_huge_pmd(struct vm_area_struct *vma,\n--\nmm/huge_memory.c-3418-\nmm/huge_memory.c:3419:\tsplit_huge_pmd_to_ptes(vma, haddr, pmd, \u0026state);\nmm/huge_memory.c-3420-}\n--\nmm/huge_memory.c=3422=static void split_non_present_huge_pmd(struct vm_area_struct *vma,\n--\nmm/huge_memory.c-3439-\t\t * w.r.t. anon exclusive handling, see\nmm/huge_memory.c:3440:\t\t * split_huge_pmd_anon_rmap().\nmm/huge_memory.c-3441-\t\t */\n--\nmm/huge_memory.c-3451-\nmm/huge_memory.c:3452:\tsplit_huge_pmd_to_ptes(vma, haddr, pmd, \u0026state);\nmm/huge_memory.c-3453-}\nmm/huge_memory.c-3454-\nmm/huge_memory.c:3455:static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,\nmm/huge_memory.c-3456-\t\tunsigned long haddr, bool freeze)\n--\nmm/huge_memory.c-3473-\t * mmu_notifier_arch_invalidate_secondary_tlbs() see comments below\nmm/huge_memory.c:3474:\t * inside __split_huge_pmd() ?\nmm/huge_memory.c-3475-\t *\n--\nmm/huge_memory.c-3507-\nmm/huge_memory.c:3508:void split_huge_pmd_locked(struct vm_area_struct *vma, unsigned long address,\nmm/huge_memory.c-3509-\t\t\t   pmd_t *pmd, bool freeze)\n--\nmm/huge_memory.c-3512-\tif (pmd_trans_huge(*pmd) || pmd_is_valid_softleaf(*pmd))\nmm/huge_memory.c:3513:\t\t__split_huge_pmd_locked(vma, pmd, address, freeze);\nmm/huge_memory.c-3514-}\nmm/huge_memory.c-3515-\nmm/huge_memory.c:3516:void __split_huge_pmd(struct vm_area_struct *vma, pmd_t *pmd,\nmm/huge_memory.c-3517-\t\tunsigned long address, bool freeze)\n--\nmm/huge_memory.c-3526-\tptl = pmd_lock(vma-\u003evm_mm, pmd);\nmm/huge_memory.c:3527:\tsplit_huge_pmd_locked(vma, range.start, pmd, freeze);\nmm/huge_memory.c-3528-\tspin_unlock(ptl);\n--\nmm/huge_memory.c-3531-\nmm/huge_memory.c:3532:void split_huge_pmd_address(struct vm_area_struct *vma, unsigned long address,\nmm/huge_memory.c-3533-\t\tbool freeze)\n--\nmm/huge_memory.c-3539-\nmm/huge_memory.c:3540:\t__split_huge_pmd(vma, pmd, address, freeze);\nmm/huge_memory.c-3541-}\nmm/huge_memory.c-3542-\nmm/huge_memory.c:3543:static inline void split_huge_pmd_if_needed(struct vm_area_struct *vma, unsigned long address)\nmm/huge_memory.c-3544-{\n--\nmm/huge_memory.c-3551-\t\t\t ALIGN(address, HPAGE_PMD_SIZE)))\nmm/huge_memory.c:3552:\t\tsplit_huge_pmd_address(vma, address, false);\nmm/huge_memory.c-3553-}\n--\nmm/huge_memory.c=3555=void vma_adjust_trans_huge(struct vm_area_struct *vma,\n--\nmm/huge_memory.c-3560-\t/* Check if we need to split start first. */\nmm/huge_memory.c:3561:\tsplit_huge_pmd_if_needed(vma, start);\nmm/huge_memory.c-3562-\nmm/huge_memory.c-3563-\t/* Check if we need to split end next. */\nmm/huge_memory.c:3564:\tsplit_huge_pmd_if_needed(vma, end);\nmm/huge_memory.c-3565-\n--\nmm/huge_memory.c-3567-\tif (next)\nmm/huge_memory.c:3568:\t\tsplit_huge_pmd_if_needed(next, end);\nmm/huge_memory.c-3569-}\n--\nmm/memory.c=2123=static inline unsigned long zap_pmd_range(struct mmu_gather *tlb,\n--\nmm/memory.c-2135-\t\t\tif (next - addr != HPAGE_PMD_SIZE)\nmm/memory.c:2136:\t\t\t\t__split_huge_pmd(vma, pmd, addr, false);\nmm/memory.c-2137-\t\t\telse if (zap_huge_pmd(tlb, vma, pmd, addr)) {\n--\nmm/memory.c=6390=static inline vm_fault_t wp_huge_pmd(struct vm_fault *vmf)\n--\nmm/memory.c-6415-\t/* COW or write-notify handled on pte level: split pmd. */\nmm/memory.c:6416:\t__split_huge_pmd(vma, vmf-\u003epmd, vmf-\u003eaddress, false);\nmm/memory.c-6417-\n--\nmm/migrate_device.c=912=static int migrate_vma_split_unmapped_folio(struct migrate_vma *migrate,\n--\nmm/migrate_device.c-921-\t/*\nmm/migrate_device.c:922:\t * take a reference, since split_huge_pmd_address() with freeze = true\nmm/migrate_device.c-923-\t * drops a reference at the end.\n--\nmm/migrate_device.c-925-\tfolio_get(folio);\nmm/migrate_device.c:926:\tsplit_huge_pmd_address(migrate-\u003evma, addr, true);\nmm/migrate_device.c-927-\tret = folio_split_unmapped(folio, 0);\n--\nmm/migrate_device.c=976=static void migrate_vma_insert_page(struct migrate_vma *migrate,\n--\nmm/migrate_device.c-1021-\t\t\t\tgoto abort;\nmm/migrate_device.c:1022:\t\t\tsplit_huge_pmd(vma, pmdp, addr);\nmm/migrate_device.c-1023-\t\t} else if (pmd_leaf(*pmdp))\n--\nmm/mprotect.c=504=static inline long change_pmd_range(struct mmu_gather *tlb,\n--\nmm/mprotect.c-532-\t\t\t    pgtable_split_needed(vma, cp_flags)) {\nmm/mprotect.c:533:\t\t\t\t__split_huge_pmd(vma, pmd, addr, false);\nmm/mprotect.c-534-\t\t\t\t/*\n--\nmm/mremap.c=804=unsigned long move_page_tables(struct pagetable_move_control *pmc)\n--\nmm/mremap.c-866-\t\t\t\tcontinue;\nmm/mremap.c:867:\t\t\tsplit_huge_pmd(pmc-\u003eold, old_pmd, pmc-\u003eold_addr);\nmm/mremap.c-868-\t\t} else if (IS_ENABLED(CONFIG_HAVE_MOVE_PMD) \u0026\u0026\n--\nmm/pagewalk.c=96=static int walk_pmd_range(pud_t *pud, unsigned long addr, unsigned long end,\n--\nmm/pagewalk.c-167-\t\tif (walk-\u003evma)\nmm/pagewalk.c:168:\t\t\tsplit_huge_pmd(walk-\u003evma, pmd, addr);\nmm/pagewalk.c-169-\t\telse if (pmd_leaf(*pmd) || !pmd_present(*pmd))\n--\nmm/rmap.c=2201=static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,\n--\nmm/rmap.c-2289-\t\t\t\t */\nmm/rmap.c:2290:\t\t\t\tsplit_huge_pmd_locked(vma, pvmw.address,\nmm/rmap.c-2291-\t\t\t\t\t\t      pvmw.pmd, false);\n--\nmm/rmap.c=2465=static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma,\n--\nmm/rmap.c-2518-\t\t\t\t/*\nmm/rmap.c:2519:\t\t\t\t * split_huge_pmd_locked() might leave the\nmm/rmap.c-2520-\t\t\t\t * folio mapped through PTEs. Retry the walk\n--\nmm/rmap.c-2523-\t\t\t\t */\nmm/rmap.c:2524:\t\t\t\tsplit_huge_pmd_locked(vma, pvmw.address,\nmm/rmap.c-2525-\t\t\t\t\t\t      pvmw.pmd, true);\n--\nmm/userfaultfd.c=2020=static ssize_t move_pages(struct userfaultfd_ctx *ctx, unsigned long dst_start,\n--\nmm/userfaultfd.c-2126-\t\t\t\tspin_unlock(ptl);\nmm/userfaultfd.c:2127:\t\t\t\tsplit_huge_pmd(src_vma, src_pmd, src_addr);\nmm/userfaultfd.c-2128-\t\t\t\t/* The folio will be split by move_pages_pte() */\n"
}

5/2 2026/08/28 19:39 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 13571
							output: 27
							thoughts: 44
						

Thoughts:
**Investigating Huge Pages and Split PMDs**

Okay, so I need to figure out which Kconfig options are responsible for controlling the behavior of `mm/huge_memory.c`, particularly around transparent huge pages (THP) and split huge page PMDs. My first instinct is to dive into the core directories. I know these configurations are often defined in Makefiles and Kconfigs. So, I should start by looking into `mm/Makefile` and then `mm/Kconfig`. Those are typically where the conditional compilation and dependencies are defined in the kernel build system, so it's the logical starting point for understanding how these features are enabled or disabled and under what circumstances. I'm assuming that the relevant options will be used in build logic and maybe even within `#ifdef` statements inside `huge_memory.c` itself.



6/2 2026/08/28 19:39 tool grepper
0m Args:
{
  "Expression": "huge_memory\\.o",
  "PathPrefix": "mm/"
}

Results:
{
  "Output": "mm/Makefile=100=obj-$(CONFIG_DEVICE_MIGRATION) += migrate_device.o\nmm/Makefile:101:obj-$(CONFIG_TRANSPARENT_HUGEPAGE) += huge_memory.o khugepaged.o\nmm/Makefile-102-obj-$(CONFIG_PAGE_COUNTER) += page_counter.o\n"
}

7/2 2026/08/28 19:39 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 13912
							output: 30
							thoughts: 24
						

Thoughts:
Here's my thought process, as if I were considering this myself:

**Kconfig Dive for THP**

Okay, so I need to quickly see where `TRANSPARENT_HUGEPAGE` is defined in the Kconfig hierarchy. I'm going straight to `mm/Kconfig`, that's the obvious place to start for memory management options. It's the most logical location for a setting related to huge pages, given that `mm` is the memory management subsystem. Let's see what's defined in there for this particular feature. Hopefully it's straightforward and I can quickly grasp its dependencies and defaults.



8/2 2026/08/28 19:39 tool grepper
0m Args:
{
  "Expression": "config TRANSPARENT_HUGEPAGE",
  "PathPrefix": "mm/"
}

Results:
{
  "Output": "mm/Kconfig=832=config MM_ID\n--\nmm/Kconfig-834-\nmm/Kconfig:835:menuconfig TRANSPARENT_HUGEPAGE\nmm/Kconfig-836-\tbool \"Transparent Hugepage Support\"\n--\nmm/Kconfig=853=choice\n--\nmm/Kconfig-858-\nmm/Kconfig:859:\tconfig TRANSPARENT_HUGEPAGE_ALWAYS\nmm/Kconfig-860-\t\tbool \"always\"\n--\nmm/Kconfig-865-\nmm/Kconfig:866:\tconfig TRANSPARENT_HUGEPAGE_MADVISE\nmm/Kconfig-867-\t\tbool \"madvise\"\n--\nmm/Kconfig-874-\nmm/Kconfig:875:\tconfig TRANSPARENT_HUGEPAGE_NEVER\nmm/Kconfig-876-\t\tbool \"never\"\n--\nmm/Kconfig=882=choice\n--\nmm/Kconfig-891-\nmm/Kconfig:892:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_NEVER\nmm/Kconfig-893-\t\tbool \"never\"\n--\nmm/Kconfig-901-\nmm/Kconfig:902:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_ALWAYS\nmm/Kconfig-903-\t\tbool \"always\"\n--\nmm/Kconfig-909-\nmm/Kconfig:910:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_WITHIN_SIZE\nmm/Kconfig-911-\t\tbool \"within_size\"\n--\nmm/Kconfig-917-\nmm/Kconfig:918:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_ADVISE\nmm/Kconfig-919-\t\tbool \"advise\"\n--\nmm/Kconfig=927=choice\n--\nmm/Kconfig-936-\nmm/Kconfig:937:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_NEVER\nmm/Kconfig-938-\t\tbool \"never\"\n--\nmm/Kconfig-945-\nmm/Kconfig:946:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_ALWAYS\nmm/Kconfig-947-\t\tbool \"always\"\n--\nmm/Kconfig-953-\nmm/Kconfig:954:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_WITHIN_SIZE\nmm/Kconfig-955-\t\tbool \"within_size\"\n--\nmm/Kconfig-961-\nmm/Kconfig:962:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_ADVISE\nmm/Kconfig-963-\t\tbool \"advise\"\n"
}

9/2 2026/08/28 19:39 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 14813
							output: 32
							thoughts: 30
						

Thoughts:
Here's my thought process, as if I were considering this myself:

**Investigating the TRANSPARENT_HUGEPAGE Dependencies**

Okay, so I need to understand the dependencies for `TRANSPARENT_HUGEPAGE` in the kernel configuration. My initial instinct is to jump right into `mm/Kconfig`, specifically around line 835. That's where I'd expect to find the relevant configuration options and any conditional logic that controls whether `TRANSPARENT_HUGEPAGE` is enabled. It's a pretty standard location for that kind of detail in the memory management subsystem. Time to dig in and see what other options this relies on, and what the implications are for my area of expertise.



10/2 2026/08/28 19:39 tool read-file
0m Args:
{
  "File": "mm/Kconfig",
  "FirstLine": 830,
  "LineCount": 40
}

Results:
{
  "Contents": " 830:\t\t  memory constrained.\n 831:\t\n 832:\tconfig MM_ID\n 833:\t\tdef_bool n\n 834:\t\n 835:\tmenuconfig TRANSPARENT_HUGEPAGE\n 836:\t\tbool \"Transparent Hugepage Support\"\n 837:\t\tdepends on HAVE_ARCH_TRANSPARENT_HUGEPAGE \u0026\u0026 !PREEMPT_RT\n 838:\t\tselect COMPACTION\n 839:\t\tselect XARRAY_MULTI\n 840:\t\tselect MM_ID\n 841:\t\thelp\n 842:\t\t  Transparent Hugepages allows the kernel to use huge pages and\n 843:\t\t  huge tlb transparently to the applications whenever possible.\n 844:\t\t  This feature can improve computing performance to certain\n 845:\t\t  applications by speeding up page faults during memory\n 846:\t\t  allocation, by reducing the number of tlb misses and by speeding\n 847:\t\t  up the pagetable walking.\n 848:\t\n 849:\t\t  If memory constrained on embedded, you may want to say N.\n 850:\t\n 851:\tif TRANSPARENT_HUGEPAGE\n 852:\t\n 853:\tchoice\n 854:\t\tprompt \"Transparent Hugepage Support sysfs defaults\"\n 855:\t\tdefault TRANSPARENT_HUGEPAGE_ALWAYS\n 856:\t\thelp\n 857:\t\t  Selects the sysfs defaults for Transparent Hugepage Support.\n 858:\t\n 859:\t\tconfig TRANSPARENT_HUGEPAGE_ALWAYS\n 860:\t\t\tbool \"always\"\n 861:\t\thelp\n 862:\t\t  Enabling Transparent Hugepage always, can increase the\n 863:\t\t  memory footprint of applications without a guaranteed\n 864:\t\t  benefit but it will work automatically for all applications.\n 865:\t\n 866:\t\tconfig TRANSPARENT_HUGEPAGE_MADVISE\n 867:\t\t\tbool \"madvise\"\n 868:\t\thelp\n 869:\t\t  Enabling Transparent Hugepage madvise, will only provide a\n"
}

11/2 2026/08/28 19:39 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 15580
							output: 36
							thoughts: 175
						

Thoughts:
**Analyzing Huge Page Splitting in `mm/huge_memory.c`**

Okay, let's dive into this. I need to get a clear picture of the huge page splitting functionality. First, I'll take a look at the key functions that are modified – the ones that are most likely at the heart of the changes. The ones that immediately stand out are: `__split_huge_pmd_locked`, `split_huge_pmd_locked`, `split_present_huge_pmd`, `split_non_present_huge_pmd`, `split_huge_pmd_to_ptes`, `unmap_huge_pmd_entry`, `split_pmd_make_softleaf`, and `huge_zero_pmd_can_split`. This gives me a good starting point.

Now, I want to understand how these functions are being *used*. So, I'll identify the externally visible entrypoints or non-static functions within `mm/huge_memory.c` that call these key functions. This will help me trace the control flow and see how the splitting is initiated and orchestrated.

Finally, I'll home in on a relevant section of the code. Let's start by examining the code around lines 3500-3600 in `mm/huge_memory.c`. I'm expecting this area to contain the implementation of one or more of the splitting-related functions, or perhaps the core logic that orchestrates the splitting process. I'll read through this and see what insights I can glean. This should give me a solid grasp on what's going on and where the changes might be located.



12/2 2026/08/28 19:39 tool read-file
0m Args:
{
  "File": "mm/huge_memory.c",
  "FirstLine": 3500,
  "LineCount": 75
}

Results:
{
  "Contents": "3500:\t\n3501:\t\tif (is_present)\n3502:\t\t\tsplit_present_huge_pmd(vma, haddr, pmd, folio, freeze);\n3503:\t\telse\n3504:\t\t\tsplit_non_present_huge_pmd(vma, haddr, pmd, old_pmd, folio,\n3505:\t\t\t\t\t\t   freeze);\n3506:\t}\n3507:\t\n3508:\tvoid split_huge_pmd_locked(struct vm_area_struct *vma, unsigned long address,\n3509:\t\t\t\t   pmd_t *pmd, bool freeze)\n3510:\t{\n3511:\t\tVM_WARN_ON_ONCE(!IS_ALIGNED(address, HPAGE_PMD_SIZE));\n3512:\t\tif (pmd_trans_huge(*pmd) || pmd_is_valid_softleaf(*pmd))\n3513:\t\t\t__split_huge_pmd_locked(vma, pmd, address, freeze);\n3514:\t}\n3515:\t\n3516:\tvoid __split_huge_pmd(struct vm_area_struct *vma, pmd_t *pmd,\n3517:\t\t\tunsigned long address, bool freeze)\n3518:\t{\n3519:\t\tspinlock_t *ptl;\n3520:\t\tstruct mmu_notifier_range range;\n3521:\t\n3522:\t\tmmu_notifier_range_init(\u0026range, MMU_NOTIFY_CLEAR, 0, vma-\u003evm_mm,\n3523:\t\t\t\t\taddress \u0026 HPAGE_PMD_MASK,\n3524:\t\t\t\t\t(address \u0026 HPAGE_PMD_MASK) + HPAGE_PMD_SIZE);\n3525:\t\tmmu_notifier_invalidate_range_start(\u0026range);\n3526:\t\tptl = pmd_lock(vma-\u003evm_mm, pmd);\n3527:\t\tsplit_huge_pmd_locked(vma, range.start, pmd, freeze);\n3528:\t\tspin_unlock(ptl);\n3529:\t\tmmu_notifier_invalidate_range_end(\u0026range);\n3530:\t}\n3531:\t\n3532:\tvoid split_huge_pmd_address(struct vm_area_struct *vma, unsigned long address,\n3533:\t\t\tbool freeze)\n3534:\t{\n3535:\t\tpmd_t *pmd = mm_find_pmd(vma-\u003evm_mm, address);\n3536:\t\n3537:\t\tif (!pmd)\n3538:\t\t\treturn;\n3539:\t\n3540:\t\t__split_huge_pmd(vma, pmd, address, freeze);\n3541:\t}\n3542:\t\n3543:\tstatic inline void split_huge_pmd_if_needed(struct vm_area_struct *vma, unsigned long address)\n3544:\t{\n3545:\t\t/*\n3546:\t\t * If the new address isn't hpage aligned and it could previously\n3547:\t\t * contain an hugepage: check if we need to split an huge pmd.\n3548:\t\t */\n3549:\t\tif (!IS_ALIGNED(address, HPAGE_PMD_SIZE) \u0026\u0026\n3550:\t\t    range_in_vma(vma, ALIGN_DOWN(address, HPAGE_PMD_SIZE),\n3551:\t\t\t\t ALIGN(address, HPAGE_PMD_SIZE)))\n3552:\t\t\tsplit_huge_pmd_address(vma, address, false);\n3553:\t}\n3554:\t\n3555:\tvoid vma_adjust_trans_huge(struct vm_area_struct *vma,\n3556:\t\t\t\t   unsigned long start,\n3557:\t\t\t\t   unsigned long end,\n3558:\t\t\t\t   struct vm_area_struct *next)\n3559:\t{\n3560:\t\t/* Check if we need to split start first. */\n3561:\t\tsplit_huge_pmd_if_needed(vma, start);\n3562:\t\n3563:\t\t/* Check if we need to split end next. */\n3564:\t\tsplit_huge_pmd_if_needed(vma, end);\n3565:\t\n3566:\t\t/* If we're incrementing next-\u003evm_start, we might need to split it. */\n3567:\t\tif (next)\n3568:\t\t\tsplit_huge_pmd_if_needed(next, end);\n3569:\t}\n3570:\t\n3571:\tstatic void unmap_folio(struct folio *folio)\n3572:\t{\n3573:\t\tenum ttu_flags ttu_flags = TTU_RMAP_LOCKED | TTU_SYNC |\n3574:\t\t\tTTU_BATCH_FLUSH;\n"
}

13/2 2026/08/28 19:39 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 17434
							output: 29
							thoughts: 84
						

Thoughts:
**Analyzing Huge Page Splitting Functions**

Alright, let's dive in. I need to get a clear picture of the landscape of huge page splitting functions. My initial focus is on the existing callers and symbols related to this. Okay, so I've got `split_huge_pmd_locked`, `__split_huge_pmd`, and `split_huge_pmd_address` in mind, those are the first candidates. The name `split_huge_pmd` itself feels like it might be a wrapper, an inlined function, or something similar to other calls. Time to be thorough and check both for the exact signature `void split_huge_pmd` and the use of the macro `#define split_huge_pmd`. This should give me a solid view of its role and usage. Gotta cover all the bases to avoid missing any hidden details in the split logic!



14/2 2026/08/28 19:39 tool grepper
0m Args:
{
  "Expression": "define split_huge_pmd",
  "PathPrefix": "include/"
}

Results:
{
  "Output": "include/linux/huge_mm.h=446=static inline bool pmd_is_huge(pmd_t pmd)\n--\ninclude/linux/huge_mm.h-460-\ninclude/linux/huge_mm.h:461:#define split_huge_pmd(__vma, __pmd, __address)\t\t\t\t\\\ninclude/linux/huge_mm.h-462-\tdo {\t\t\t\t\t\t\t\t\\\n--\ninclude/linux/huge_mm.h=687=static inline void deferred_split_folio(struct folio *folio, bool partially_mapped)\n--\ninclude/linux/huge_mm.h-690-\ninclude/linux/huge_mm.h:691:#define split_huge_pmd(__vma, __pmd, __address)\t\\\ninclude/linux/huge_mm.h-692-\tdo { } while (0)\n"
}

15/2 2026/08/28 19:40 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 17963
							output: 37
							thoughts: 16
						

Thoughts:
Here's my understanding of the `split_huge_pmd` macro, from a high-level perspective, as though these were my own thoughts:

**Dissecting the `split_huge_pmd` Macro**

Okay, let's dive into this `split_huge_pmd` macro. From the name itself, I'm expecting it deals with breaking down a huge page – likely a large physical memory page, like a 2MB or 1GB page – into smaller, more manageable units, probably 4KB pages. I need to understand the mechanics behind this: how it happens, why it's necessary, and what considerations come into play. I'll need to think about things like: the state of the page table entries (PTEs) involved before and after the split, the implications for the page tables, and any potential locking or synchronization involved to ensure atomicity and correctness during the operation, and ensure things like cache coherency are maintained. I'll be looking for where the current huge page is mapped in the page tables, and how it is updated to show these new 4KB pages.



16/2 2026/08/28 19:40 tool read-file
0m Args:
{
  "File": "include/linux/huge_mm.h",
  "FirstLine": 455,
  "LineCount": 20
}

Results:
{
  "Contents": " 455:\t\t\treturn true;\n 456:\t\t}\n 457:\t\n 458:\t\treturn false;\n 459:\t}\n 460:\t\n 461:\t#define split_huge_pmd(__vma, __pmd, __address)\t\t\t\t\\\n 462:\t\tdo {\t\t\t\t\t\t\t\t\\\n 463:\t\t\tpmd_t *____pmd = (__pmd);\t\t\t\t\\\n 464:\t\t\tif (pmd_is_huge(*____pmd))\t\t\t\t\\\n 465:\t\t\t\t__split_huge_pmd(__vma, __pmd, __address,\t\\\n 466:\t\t\t\t\t\t false);\t\t\t\\\n 467:\t\t}  while (0)\n 468:\t\n 469:\tvoid split_huge_pmd_address(struct vm_area_struct *vma, unsigned long address,\n 470:\t\t\tbool freeze);\n 471:\t\n 472:\tvoid __split_huge_pud(struct vm_area_struct *vma, pud_t *pud,\n 473:\t\t\tunsigned long address);\n 474:\t\n"
}

17/2 2026/08/28 19:40 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 18557
							output: 167
							thoughts: 1034
						

Thoughts:
**Fuzzing Target: Transparent Huge Page (THP) PMD Splitting**

Okay, let's break this down. My goal is to determine the viability of fuzzing the THP PMD splitting logic within the kernel. My initial focus is on the functions within `mm/huge_memory.c` directly involved in PMD splitting. Specifically, I see three non-static functions: `split_huge_pmd_locked`, `__split_huge_pmd`, and `split_huge_pmd_address`. These are my primary entry points for investigation.

Looking closer, the patch touches several related functions. `__split_huge_pmd_locked` and various static helpers are modified. These include `split_huge_pmd_anon_rmap`, `split_pmd_make_softleaf`, `split_huge_pmd_to_ptes`, `split_present_huge_pmd`, `split_non_present_huge_pmd`, `unmap_huge_pmd_entry`, and `huge_zero_pmd_can_split`. The pattern here is clear: `__split_huge_pmd_locked`, and then the others are static helpers for this. `split_huge_pmd_locked` and the others are called by several other functions.

Is this worth the effort? Absolutely. This code is fundamental to how THPs are handled. The refactoring appears to touch core aspects of memory management, including splitting THPs into smaller PTEs, dealing with anonymous folios, handling flags, managing the reverse map (rmap), and addressing zero PMD splitting. This code is *directly* exposed to user space. It is involved in critical operations like `madvise`, VMA splitting, `unmap`, `mprotect`, page migration, copy-on-write (COW), and `userfaultfd`. Any flaw here has the potential to cause critical issues: memory corruption, use-after-free conditions, TLB shootdown problems, data loss, or even kernel panics. High value.

Now, the necessary configurations. `CONFIG_TRANSPARENT_HUGEPAGE` is essential, obviously. Digging deeper, it depends on `HAVE_ARCH_TRANSPARENT_HUGEPAGE` and excludes `PREEMPT_RT`. It also selects `COMPACTION`, `XARRAY_MULTI`, and `MM_ID`. I also need to check if there are other relevant configs for device-private THPs, migration, or `userfaultfd`. I confirm that `CONFIG_TRANSPARENT_HUGEPAGE`, `CONFIG_DEVICE_PRIVATE`, and `CONFIG_USERFAULTFD` could come into play depending on the specific features being exercised. `CONFIG_MIGRATION` is typically selected in conjunction with THP/compaction/NUMA, which should be included for a complete fuzzer configuration. `TRANSPARENT_HUGEPAGE` is the primary, but those other two are potentially important.

Regarding the "FocusSymbols," I need to confirm these aren't static or inlined. The key symbols are `split_huge_pmd_locked`, `__split_huge_pmd`, and `split_huge_pmd_address`. I see that `__split_huge_pmd_locked` *is* static (a helper), which leaves me with `split_huge_pmd_locked`, `__split_huge_pmd`, and `split_huge_pmd_address` as the public APIs to target.

In summary, this is a clear "yes" for fuzzing. We're looking at core memory management logic exposed to user space, so the potential for impactful findings is very high. The focus should be on `split_huge_pmd_locked`, `__split_huge_pmd`, and `split_huge_pmd_address`, with `CONFIG_TRANSPARENT_HUGEPAGE`, `CONFIG_DEVICE_PRIVATE`, and `CONFIG_USERFAULTFD` being potentially relevant configurations.



18/2 2026/08/28 19:40 tool set-results
0m Args:
{
  "EnableConfigs": [
    "TRANSPARENT_HUGEPAGE",
    "DEVICE_PRIVATE",
    "USERFAULTFD"
  ],
  "FocusSymbols": [
    "split_huge_pmd_locked",
    "__split_huge_pmd",
    "split_huge_pmd_address"
  ],
  "Reasoning": "The patch refactors core Transparent Huge Page (THP) PMD split logic in mm/huge_memory.c, separating the handling of present vs non-present (migration, device-private) huge PMDs and reorganizing rmap conversion and unmapping logic. This core MM functionality is reachable via user actions (madvise, mprotect, page migration, etc.) and is worth fuzzing.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "TRANSPARENT_HUGEPAGE",
    "DEVICE_PRIVATE",
    "USERFAULTFD"
  ],
  "FocusSymbols": [
    "split_huge_pmd_locked",
    "__split_huge_pmd",
    "split_huge_pmd_address"
  ],
  "Reasoning": "The patch refactors core Transparent Huge Page (THP) PMD split logic in mm/huge_memory.c, separating the handling of present vs non-present (migration, device-private) huge PMDs and reorganizing rmap conversion and unmapping logic. This core MM functionality is reachable via user actions (madvise, mprotect, page migration, etc.) and is worth fuzzing.",
  "WorthFuzzing": true
}

19/1 2026/08/28 19:40 action If
0m Args:
{
  "WorthFuzzing": true
}

Results:
null

20/2 2026/08/28 19:40 agent kmsan-evaluator
0m Model:
core

Args:
null

Results:
{
  "KMSANReasoning": "The patch refactors huge PMD splitting logic (__split_huge_pmd_locked) in mm/huge_memory.c by breaking it down into helper functions (huge_zero_pmd_can_split, unmap_huge_pmd_entry, split_huge_pmd_to_ptes, split_present_huge_pmd, split_non_present_huge_pmd) and grouping PMD split parameters in a local stack structure (struct split_pmd_state).\n\nAll instances of 'struct split_pmd_state' are stack-allocated with designated initializers, ensuring all unset fields are zero-initialized. There are no kernel-to-user copy operations (e.g., copy_to_user), no uninitialized heap or stack variables read or branched on, and no info-leak risks. Any memory management or synchronization issues in this code path (such as folio refcounting, rmap management, TLB invalidation, or UAF) are caught by standard KASAN, VM_BUG_ON assertions, and standard sanitizers. Therefore, a dedicated KMSAN session is not needed.",
  "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 37afda2d1094b0a9df200c535bd4b01247a9f711
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Aug 28 19:39:21 2026 +0000

    syz-cluster: applied patch under review

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index afbb5974bd225..8e0fd11da3d7a 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3139,193 +3139,173 @@ static void __split_huge_zero_page_pmd(struct vm_area_struct *vma,
 	pmd_populate(mm, pmd, pgtable);
 }
 
-static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,
-		unsigned long haddr, bool freeze)
+/*
+ * Only a huge zero PMD in an anonymous VMA is split, into a page table of
+ * shared zero page mappings. Any other huge zero PMD is simply unmapped.
+ */
+static bool huge_zero_pmd_can_split(struct vm_area_struct *vma, pmd_t pmdval)
 {
-	struct mm_struct *mm = vma->vm_mm;
-	struct folio *folio;
-	struct page *page;
-	pgtable_t pgtable;
-	pmd_t old_pmd, _pmd;
-	bool soft_dirty, uffd_wp = false, young = false, write = false;
-	bool anon_exclusive = false, dirty = false;
-	unsigned long addr;
-	pte_t *pte;
-	int i;
-
-	VM_BUG_ON(haddr & ~HPAGE_PMD_MASK);
-	VM_BUG_ON_VMA(vma->vm_start > haddr, vma);
-	VM_BUG_ON_VMA(vma->vm_end < haddr + HPAGE_PMD_SIZE, vma);
+	return is_huge_zero_pmd(pmdval) && vma_is_anonymous(vma);
+}
 
-	VM_WARN_ON_ONCE(!pmd_is_valid_softleaf(*pmd) && !pmd_trans_huge(*pmd));
+/**
+ * unmap_huge_pmd_entry() - Unmap a huge PMD entry rather than splitting it.
+ * @vma: The VMA @pmd belongs to.
+ * @haddr: The PMD-aligned address @pmd maps.
+ * @pmd: Pointer to the huge PMD entry.
+ * @folio: The folio @pmd describes, or NULL if it describes none.
+ * @is_present: Is @pmd a present entry rather than a softleaf entry?
+ *
+ * Only anonymous folios are rebuilt at PTE level when a huge PMD entry is
+ * split. Everything else is unmapped here and faulted back in on the next
+ * access.
+ */
+static void unmap_huge_pmd_entry(struct vm_area_struct *vma,
+		unsigned long haddr, pmd_t *pmd, struct folio *folio,
+		bool is_present)
+{
+	struct mm_struct *mm = vma->vm_mm;
+	pmd_t old_pmd;
 
-	count_vm_event(THP_SPLIT_PMD);
+	old_pmd = pmdp_huge_clear_flush(vma, haddr, pmd);
+	/*
+	 * We are going to unmap this huge page. So
+	 * just go ahead and zap it
+	 */
+	if (has_deposited_pgtable(vma, old_pmd, folio))
+		zap_deposited_table(mm, pmd);
 
-	if (!vma_is_anonymous(vma)) {
-		old_pmd = pmdp_huge_clear_flush(vma, haddr, pmd);
-		/*
-		 * We are going to unmap this huge page. So
-		 * just go ahead and zap it
-		 */
-		if (arch_needs_pgtable_deposit())
-			zap_deposited_table(mm, pmd);
-		if (vma_is_special_huge(vma))
-			return;
-		if (unlikely(pmd_is_migration_entry(old_pmd))) {
-			const softleaf_t old_entry = softleaf_from_pmd(old_pmd);
-
-			folio = softleaf_to_folio(old_entry);
-		} else if (is_huge_zero_pmd(old_pmd)) {
-			return;
-		} else {
-			page = pmd_page(old_pmd);
-			folio = page_folio(page);
-			if (!folio_test_dirty(folio) && pmd_dirty(old_pmd))
-				folio_mark_dirty(folio);
-			if (!folio_test_referenced(folio) && pmd_young(old_pmd))
-				folio_set_referenced(folio);
-			folio_remove_rmap_pmd(folio, page, vma);
-			add_mm_counter(mm, mm_counter_file(folio), -HPAGE_PMD_NR);
-			folio_put(folio);
-			return;
-		}
-		add_mm_counter(mm, mm_counter_file(folio), -HPAGE_PMD_NR);
+	if (!folio)
 		return;
-	}
 
-	if (is_huge_zero_pmd(*pmd)) {
-		/*
-		 * FIXME: Do we want to invalidate secondary mmu by calling
-		 * mmu_notifier_arch_invalidate_secondary_tlbs() see comments below
-		 * inside __split_huge_pmd() ?
-		 *
-		 * We are going from a zero huge page write protected to zero
-		 * small page also write protected so it does not seems useful
-		 * to invalidate secondary mmu at this time.
-		 */
-		return __split_huge_zero_page_pmd(vma, haddr, pmd);
+	if (is_present) {
+		struct page *page = pmd_page(old_pmd);
+
+		if (!folio_test_dirty(folio) && pmd_dirty(old_pmd))
+			folio_mark_dirty(folio);
+		if (!folio_test_referenced(folio) && pmd_young(old_pmd))
+			folio_set_referenced(folio);
+		folio_remove_rmap_pmd(folio, page, vma);
 	}
 
-	if (pmd_is_migration_entry(*pmd)) {
-		softleaf_t entry;
+	add_mm_counter(mm, mm_counter_file(folio), -HPAGE_PMD_NR);
 
-		old_pmd = *pmd;
-		entry = softleaf_from_pmd(old_pmd);
-		page = softleaf_to_page(entry);
-		folio = page_folio(page);
+	if (is_present)
+		folio_put(folio);
+}
 
-		soft_dirty = pmd_swp_soft_dirty(old_pmd);
-		uffd_wp = pmd_swp_uffd(old_pmd);
+struct split_pmd_state {
+	struct folio *folio;
+	struct page *page;
+	bool is_present;
+	bool is_device_private;
+	bool freeze;
+	bool write;
+	bool young;
+	bool dirty;
+	bool soft_dirty;
+	bool uffd;
+	bool anon_exclusive;
+};
 
-		write = softleaf_is_migration_write(entry);
-		if (PageAnon(page))
-			anon_exclusive = softleaf_is_migration_read_exclusive(entry);
-		young = softleaf_is_migration_young(entry);
-		dirty = softleaf_is_migration_dirty(entry);
-	} else if (pmd_is_device_private_entry(*pmd)) {
-		softleaf_t entry;
+/*
+ * Convert the folio's PMD-level anonymous rmap into PTE-level ones.
+ *
+ * Without "freeze", we'll simply split the PMD, propagating the
+ * PageAnonExclusive() flag for each PTE by setting it for
+ * each subpage -- no need to (temporarily) clear.
+ *
+ * With "freeze" we want to replace mapped pages by
+ * migration entries right away. This is only possible if we
+ * managed to clear PageAnonExclusive() -- see
+ * set_pmd_migration_entry().
+ *
+ * In case we cannot clear PageAnonExclusive(), split the PMD
+ * only and let try_to_migrate_one() fail later.
+ *
+ * See folio_try_share_anon_rmap_pmd(): invalidate PMD first.
+ *
+ * Returns: whether the mapping may still be frozen.
+ */
+static bool split_huge_pmd_anon_rmap(const struct split_pmd_state *state,
+		struct vm_area_struct *vma, unsigned long haddr)
+{
+	rmap_t rmap_flags = RMAP_NONE;
 
-		old_pmd = *pmd;
-		entry = softleaf_from_pmd(old_pmd);
-		page = softleaf_to_page(entry);
-		folio = page_folio(page);
+	if (state->freeze &&
+	    (!state->anon_exclusive ||
+	     !folio_try_share_anon_rmap_pmd(state->folio, state->page)))
+		return true;
 
-		soft_dirty = pmd_swp_soft_dirty(old_pmd);
-		uffd_wp = pmd_swp_uffd(old_pmd);
+	folio_ref_add(state->folio, HPAGE_PMD_NR - 1);
+	if (state->anon_exclusive)
+		rmap_flags |= RMAP_EXCLUSIVE;
+	folio_add_anon_rmap_ptes(state->folio, state->page, HPAGE_PMD_NR, vma,
+				 haddr, rmap_flags);
+	return false;
+}
 
-		write = softleaf_is_device_private_write(entry);
-		anon_exclusive = PageAnonExclusive(page);
+/*
+ * Build the leaf entry for the PTE entry describing @pfn, for a huge PMD entry
+ * which is not restored as a present mapping.
+ */
+static softleaf_t split_pmd_make_softleaf(const struct split_pmd_state *state,
+					  unsigned long pfn)
+{
+	softleaf_t entry;
 
+	if (state->is_device_private && !state->freeze) {
 		/*
-		 * Device private THP should be treated the same as regular
-		 * folios w.r.t anon exclusive handling. See the comments for
-		 * folio handling and anon_exclusive below.
-		 */
-		if (freeze && anon_exclusive &&
-		    folio_try_share_anon_rmap_pmd(folio, page))
-			freeze = false;
-		if (!freeze) {
-			rmap_t rmap_flags = RMAP_NONE;
-
-			folio_ref_add(folio, HPAGE_PMD_NR - 1);
-			if (anon_exclusive)
-				rmap_flags |= RMAP_EXCLUSIVE;
-
-			folio_add_anon_rmap_ptes(folio, page, HPAGE_PMD_NR,
-						 vma, haddr, rmap_flags);
-		}
-	} else {
-		/*
-		 * Up to this point the pmd is present and huge and userland has
-		 * the whole access to the hugepage during the split (which
-		 * happens in place). If we overwrite the pmd with the not-huge
-		 * version pointing to the pte here (which of course we could if
-		 * all CPUs were bug free), userland could trigger a small page
-		 * size TLB miss on the small sized TLB while the hugepage TLB
-		 * entry is still established in the huge TLB. Some CPU doesn't
-		 * like that. See
-		 * http://support.amd.com/TechDocs/41322_10h_Rev_Gd.pdf, Erratum
-		 * 383 on page 105. Intel should be safe but is also warns that
-		 * it's only safe if the permission and cache attributes of the
-		 * two entries loaded in the two TLB is identical (which should
-		 * be the case here). But it is generally safer to never allow
-		 * small and huge TLB entries for the same virtual address to be
-		 * loaded simultaneously. So instead of doing "pmd_populate();
-		 * flush_pmd_tlb_range();" we first mark the current pmd
-		 * notpresent (atomically because here the pmd_trans_huge must
-		 * remain set at all times on the pmd until the split is
-		 * complete for this pmd), then we flush the SMP TLB and finally
-		 * we write the non-huge version of the pmd entry with
-		 * pmd_populate.
+		 * anon_exclusive was already propagated to the pages backing
+		 * the PTE entries by split_huge_pmd_anon_rmap(), and accessed
+		 * and dirty bits are not propagated via device private
+		 * entries.
 		 */
-		old_pmd = pmdp_invalidate(vma, haddr, pmd);
-		page = pmd_page(old_pmd);
-		folio = page_folio(page);
-		if (pmd_dirty(old_pmd)) {
-			dirty = true;
-			folio_set_dirty(folio);
-		}
-		write = pmd_write(old_pmd);
-		young = pmd_young(old_pmd);
-		soft_dirty = pmd_soft_dirty(old_pmd);
-		uffd_wp = pmd_uffd(old_pmd);
+		if (state->write)
+			return make_writable_device_private_entry(pfn);
+		return make_readable_device_private_entry(pfn);
+	}
 
-		VM_WARN_ON_FOLIO(!folio_ref_count(folio), folio);
-		VM_WARN_ON_FOLIO(!folio_test_anon(folio), folio);
+	if (state->write)
+		entry = make_writable_migration_entry(pfn);
+	else if (state->anon_exclusive)
+		entry = make_readable_exclusive_migration_entry(pfn);
+	else
+		entry = make_readable_migration_entry(pfn);
 
-		/*
-		 * Without "freeze", we'll simply split the PMD, propagating the
-		 * PageAnonExclusive() flag for each PTE by setting it for
-		 * each subpage -- no need to (temporarily) clear.
-		 *
-		 * With "freeze" we want to replace mapped pages by
-		 * migration entries right away. This is only possible if we
-		 * managed to clear PageAnonExclusive() -- see
-		 * set_pmd_migration_entry().
-		 *
-		 * In case we cannot clear PageAnonExclusive(), split the PMD
-		 * only and let try_to_migrate_one() fail later.
-		 *
-		 * See folio_try_share_anon_rmap_pmd(): invalidate PMD first.
-		 */
-		anon_exclusive = PageAnonExclusive(page);
-		if (freeze && anon_exclusive &&
-		    folio_try_share_anon_rmap_pmd(folio, page))
-			freeze = false;
-		if (!freeze) {
-			rmap_t rmap_flags = RMAP_NONE;
-
-			folio_ref_add(folio, HPAGE_PMD_NR - 1);
-			if (anon_exclusive)
-				rmap_flags |= RMAP_EXCLUSIVE;
-			folio_add_anon_rmap_ptes(folio, page, HPAGE_PMD_NR,
-						 vma, haddr, rmap_flags);
-		}
-	}
+	if (state->young)
+		entry = make_migration_entry_young(entry);
+	if (state->dirty)
+		entry = make_migration_entry_dirty(entry);
+
+	return entry;
+}
+
+/*
+ * Replace an anonymous huge PMD entry with a page table mapping the same
+ * folio at PTE granularity.
+ */
+static void split_huge_pmd_to_ptes(struct vm_area_struct *vma,
+		unsigned long haddr, pmd_t *pmd, struct split_pmd_state *state)
+{
+	/* Present mappings and device private entries hold a PMD-level rmap. */
+	const bool rmapped = state->is_present || state->is_device_private;
+	struct mm_struct *mm = vma->vm_mm;
+	struct page *page = state->page;
+	unsigned long addr;
+	pgtable_t pgtable;
+	pmd_t _pmd;
+	pte_t *pte;
+	int i;
+
+	if (rmapped)
+		state->freeze = split_huge_pmd_anon_rmap(state, vma, haddr);
 
 	/*
-	 * Withdraw the table only after we mark the pmd entry invalid.
-	 * This's critical for some architectures (Power).
+	 * The caller has already invalidated a present entry, and a softleaf
+	 * entry is not present to begin with. Either way the entry is out of
+	 * service before we withdraw the deposited page table, which is
+	 * critical for some architectures (Power).
 	 */
 	pgtable = pgtable_trans_huge_withdraw(mm, pmd);
 	pmd_populate(mm, &_pmd, pgtable);
@@ -3337,55 +3317,17 @@ static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,
 	 * Note that NUMA hinting access restrictions are not transferred to
 	 * avoid any possibility of altering permissions across VMAs.
 	 */
-	if (freeze || pmd_is_migration_entry(old_pmd)) {
-		pte_t entry;
-		swp_entry_t swp_entry;
-
-		for (i = 0, addr = haddr; i < HPAGE_PMD_NR; i++, addr += PAGE_SIZE) {
-			if (write)
-				swp_entry = make_writable_migration_entry(
-							page_to_pfn(page + i));
-			else if (anon_exclusive)
-				swp_entry = make_readable_exclusive_migration_entry(
-							page_to_pfn(page + i));
-			else
-				swp_entry = make_readable_migration_entry(
-							page_to_pfn(page + i));
-			if (young)
-				swp_entry = make_migration_entry_young(swp_entry);
-			if (dirty)
-				swp_entry = make_migration_entry_dirty(swp_entry);
-			entry = swp_entry_to_pte(swp_entry);
-			if (soft_dirty)
+	if (state->freeze || !state->is_present) {
+		for (i = 0, addr = haddr; i < HPAGE_PMD_NR;
+		     i++, addr += PAGE_SIZE) {
+			const unsigned long pfn = page_to_pfn(page + i);
+			const softleaf_t leaf =
+				split_pmd_make_softleaf(state, pfn);
+			pte_t entry = softleaf_to_pte(leaf);
+
+			if (state->soft_dirty)
 				entry = pte_swp_mksoft_dirty(entry);
-			if (uffd_wp)
-				entry = pte_swp_mkuffd(entry);
-			VM_WARN_ON(!pte_none(ptep_get(pte + i)));
-			set_pte_at(mm, addr, pte + i, entry);
-		}
-	} else if (pmd_is_device_private_entry(old_pmd)) {
-		pte_t entry;
-		swp_entry_t swp_entry;
-
-		for (i = 0, addr = haddr; i < HPAGE_PMD_NR; i++, addr += PAGE_SIZE) {
-			/*
-			 * anon_exclusive was already propagated to the relevant
-			 * pages corresponding to the pte entries when freeze
-			 * is false.
-			 */
-			if (write)
-				swp_entry = make_writable_device_private_entry(
-							page_to_pfn(page + i));
-			else
-				swp_entry = make_readable_device_private_entry(
-							page_to_pfn(page + i));
-			/*
-			 * Young and dirty bits are not progated via swp_entry
-			 */
-			entry = swp_entry_to_pte(swp_entry);
-			if (soft_dirty)
-				entry = pte_swp_mksoft_dirty(entry);
-			if (uffd_wp)
+			if (state->uffd)
 				entry = pte_swp_mkuffd(entry);
 			VM_WARN_ON(!pte_none(ptep_get(pte + i)));
 			set_pte_at(mm, addr, pte + i, entry);
@@ -3394,20 +3336,20 @@ static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,
 		pte_t entry;
 
 		entry = mk_pte(page, READ_ONCE(vma->vm_page_prot));
-		if (write)
+		if (state->write)
 			entry = pte_mkwrite(entry, vma);
-		if (!young)
+		if (!state->young)
 			entry = pte_mkold(entry);
 		/* NOTE: this may set soft-dirty too on some archs */
-		if (dirty)
+		if (state->dirty)
 			entry = pte_mkdirty(entry);
-		if (soft_dirty)
+		if (state->soft_dirty)
 			entry = pte_mksoft_dirty(entry);
-		if (uffd_wp)
+		if (state->uffd)
 			entry = pte_mkuffd(entry);
 
 		/* Restore PAGE_NONE so an RWP marker keeps trapping */
-		if (userfaultfd_rwp(vma) && uffd_wp)
+		if (userfaultfd_rwp(vma) && state->uffd)
 			entry = pte_modify(entry, PAGE_NONE);
 
 		for (i = 0; i < HPAGE_PMD_NR; i++)
@@ -3417,15 +3359,152 @@ static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,
 	}
 	pte_unmap(pte);
 
-	if (!pmd_is_migration_entry(*pmd))
-		folio_remove_rmap_pmd(folio, page, vma);
-	if (freeze)
+	if (rmapped)
+		folio_remove_rmap_pmd(state->folio, page, vma);
+	if (state->freeze)
 		put_page(page);
 
 	smp_wmb(); /* make pte visible before pmd */
 	pmd_populate(mm, pmd, pgtable);
 }
 
+static void split_present_huge_pmd(struct vm_area_struct *vma,
+		unsigned long haddr, pmd_t *pmd, struct folio *folio,
+		bool freeze)
+{
+	struct split_pmd_state state = {
+		.folio = folio,
+		.is_present = true,
+		.freeze = freeze,
+	};
+
+	/*
+	 * Up to this point the pmd is present and huge and userland has the
+	 * whole access to the hugepage during the split (which happens in
+	 * place). If we overwrite the pmd with the not-huge version pointing
+	 * to the pte here (which of course we could if all CPUs were bug
+	 * free), userland could trigger a small page size TLB miss on the
+	 * small sized TLB while the hugepage TLB entry is still established in
+	 * the huge TLB. Some CPU doesn't like that. See
+	 * http://support.amd.com/TechDocs/41322_10h_Rev_Gd.pdf, Erratum 383 on
+	 * page 105. Intel should be safe but is also warns that it's only safe
+	 * if the permission and cache attributes of the two entries loaded in
+	 * the two TLB is identical (which should be the case here). But it is
+	 * generally safer to never allow small and huge TLB entries for the
+	 * same virtual address to be loaded simultaneously. So instead of
+	 * doing "pmd_populate(); flush_pmd_tlb_range();" we first mark the
+	 * current pmd notpresent (atomically because here the pmd_trans_huge
+	 * must remain set at all times on the pmd until the split is complete
+	 * for this pmd), then we flush the SMP TLB and finally we write the
+	 * non-huge version of the pmd entry with pmd_populate.
+	 *
+	 * This must also happen before PageAnonExclusive() is read below, see
+	 * folio_try_share_anon_rmap_pmd().
+	 */
+	const pmd_t pmdval = pmdp_invalidate(vma, haddr, pmd);
+
+	state.page = pmd_page(pmdval);
+	state.write = pmd_write(pmdval);
+	state.young = pmd_young(pmdval);
+	state.dirty = pmd_dirty(pmdval);
+	state.soft_dirty = pmd_soft_dirty(pmdval);
+	state.uffd = pmd_uffd(pmdval);
+	state.anon_exclusive = PageAnonExclusive(state.page);
+
+	if (state.dirty)
+		folio_set_dirty(folio);
+
+	VM_WARN_ON_FOLIO(!folio_ref_count(folio), folio);
+
+	split_huge_pmd_to_ptes(vma, haddr, pmd, &state);
+}
+
+static void split_non_present_huge_pmd(struct vm_area_struct *vma,
+		unsigned long haddr, pmd_t *pmd, pmd_t old_pmd,
+		struct folio *folio, bool freeze)
+{
+	const softleaf_t entry = softleaf_from_pmd(old_pmd);
+	struct split_pmd_state state = {
+		.folio = folio,
+		.page = softleaf_to_page(entry),
+		.is_device_private = softleaf_is_device_private(entry),
+		.freeze = freeze,
+		.soft_dirty = pmd_swp_soft_dirty(old_pmd),
+		.uffd = pmd_swp_uffd(old_pmd),
+	};
+
+	if (state.is_device_private) {
+		/*
+		 * Device private folios are treated the same as regular folios
+		 * w.r.t. anon exclusive handling, see
+		 * split_huge_pmd_anon_rmap().
+		 */
+		state.write = softleaf_is_device_private_write(entry);
+		state.anon_exclusive = PageAnonExclusive(state.page);
+	} else {
+		state.write = softleaf_is_migration_write(entry);
+		state.young = softleaf_is_migration_young(entry);
+		state.dirty = softleaf_is_migration_dirty(entry);
+		state.anon_exclusive =
+			softleaf_is_migration_read_exclusive(entry);
+	}
+
+	split_huge_pmd_to_ptes(vma, haddr, pmd, &state);
+}
+
+static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,
+		unsigned long haddr, bool freeze)
+{
+	const pmd_t old_pmd = *pmd;
+	const bool is_present = pmd_present(old_pmd);
+	struct folio *folio;
+
+	VM_BUG_ON(haddr & ~HPAGE_PMD_MASK);
+	VM_BUG_ON_VMA(vma->vm_start > haddr, vma);
+	VM_BUG_ON_VMA(vma->vm_end < haddr + HPAGE_PMD_SIZE, vma);
+
+	VM_WARN_ON_ONCE(!pmd_is_valid_softleaf(old_pmd) &&
+			!pmd_trans_huge(old_pmd));
+
+	count_vm_event(THP_SPLIT_PMD);
+
+	/*
+	 * FIXME: Do we want to invalidate secondary mmu by calling
+	 * mmu_notifier_arch_invalidate_secondary_tlbs() see comments below
+	 * inside __split_huge_pmd() ?
+	 *
+	 * We are going from a zero huge page write protected to zero small
+	 * page also write protected so it does not seems useful to invalidate
+	 * secondary mmu at this time.
+	 */
+	if (huge_zero_pmd_can_split(vma, old_pmd)) {
+		__split_huge_zero_page_pmd(vma, haddr, pmd);
+		return;
+	}
+
+	folio = normal_or_softleaf_folio_pmd(vma, haddr, old_pmd, is_present);
+
+	/*
+	 * A non-present entry which is neither a migration nor a device
+	 * private entry is corrupt, and pmd_to_softleaf_folio() has already
+	 * warned about it. Leave it alone rather than act on a PFN which
+	 * means nothing.
+	 */
+	if (unlikely(!is_present && !folio))
+		return;
+
+	if (!folio || !folio_test_anon(folio)) {
+		unmap_huge_pmd_entry(vma, haddr, pmd, folio, is_present);
+		return;
+	}
+
+	if (is_present)
+		split_present_huge_pmd(vma, haddr, pmd, folio, freeze);
+	else
+		split_non_present_huge_pmd(vma, haddr, pmd, old_pmd, folio,
+					   freeze);
+}
+
 void split_huge_pmd_locked(struct vm_area_struct *vma, unsigned long address,
 			   pmd_t *pmd, bool freeze)
 {

21/3 2026/08/28 19:40 llm kmsan-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 9665
							output: 266
							thoughts: 1576
						

Thoughts:
**KMSAN Analysis: Determining if Dedicated Fuzzing is Necessary**

Okay, let's break this down. My goal here is to determine whether this patch, which refactors `__split_huge_pmd_locked` in `mm/huge_memory.c`, requires a dedicated KMSAN fuzzing session, or if we can rely on KASAN and standard detectors.

First, let's refresh: KMSAN is designed to catch reads of uninitialized memory and kernel-to-user memory leaks. KASAN handles the usual suspects: out-of-bounds accesses, use-after-free, etc., and LOCKDEP is there for locking issues.

Now, looking at the patch, it seems like a deep dive into how huge page splitting is handled. It refactors the `__split_huge_pmd_locked` function and the way it works with folios and page table entries (PMDs and PTEs).

I see these key changes:

1.  A check, `huge_zero_pmd_can_split`, to ensure the PMD is a zero PMD and the VMA is anonymous.
2.  `unmap_huge_pmd_entry`, which cleans up huge PMD entries, zeros table entries, and deals with folio state (dirty/referenced/etc.).
3.  The introduction of `struct split_pmd_state` to encapsulate the state during the splitting process, including the `folio`, whether the PMD is `is_present`, as well as flags like `write`, `young`, `dirty`, etc.
4.  Functions like `split_present_huge_pmd` and `split_non_present_huge_pmd` initialize the `split_pmd_state` structure. Critically, these initializations use C's designated initializer syntax (`= { ... }`), which means *any* members *not explicitly* initialized will be zeroed. So, the compiler is handling the default values, ensuring that no member of `struct split_pmd_state` has an uninitialized value. The `split_present_huge_pmd` specifically sets the fields it needs, and the others use the zero-initialization.

    *   For the non-present case, all the flags not explicitly set are zero-initialized. Also, additional fields are explicitly set depending on whether the entry is device-private or not.
5.  `__split_huge_pmd_locked` itself checks the `huge_zero_pmd_can_split`, gets the `folio`, and then calls either `unmap_huge_pmd_entry`, `split_present_huge_pmd`, or `split_non_present_huge_pmd`.

My focus is on potential uninitialized variables, leaks to userspace, or padding issues that could lead to vulnerabilities.

*   I see that the `split_pmd_state` struct is always initialized with a designated initializer; this eliminates the risk of uninitialized fields.
*   I don't see any direct copying of data to userspace (`copy_to_user`, etc.), which would be a leak risk.
*   The code manipulates PMD and PTEs, which are key for THP splitting. Potential memory safety issues here would manifest as incorrect page table entries, refcounting problems (like use-after-free), or TLB flush errors – all of which are the domain of standard mm assertions, refcounting mechanisms, and KASAN.
*   Crucially, KMSAN *isn't* designed to detect issues with page table entries themselves, as they are not general uninitialized memory but hardware descriptors or physical addresses.

So, let's run through the KMSAN-specific checks:

1.  Kernel structs sent to userspace with uninitialized or padding issues? No.
2.  Conditional logic with potentially uninitialized variables? No.
3.  Allocation of complex data structures with uninitialized fields? No – `split_pmd_state` is fully initialized, and stack-local.
4.  Bounds checks that could lead to accessing uninitialized bytes? No.

Given this analysis, and that:

*   The risks are primarily about page table handling, refcounting, and general memory management bugs, all caught by KASAN, LOCKDEP and `VM_BUG_ON`.
*   All stack structures are *fully* initialized.
*   There's no uninitialized memory usage or info-leak risk.

I am confident that `NeedsKMSAN=false` for this patch. I'll verify the tools and then call `set-results`.



22/3 2026/08/28 19:40 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch refactors huge PMD splitting logic (__split_huge_pmd_locked) in mm/huge_memory.c by breaking it down into helper functions (huge_zero_pmd_can_split, unmap_huge_pmd_entry, split_huge_pmd_to_ptes, split_present_huge_pmd, split_non_present_huge_pmd) and grouping PMD split parameters in a local stack structure (struct split_pmd_state).\n\nAll instances of 'struct split_pmd_state' are stack-allocated with designated initializers, ensuring all unset fields are zero-initialized. There are no kernel-to-user copy operations (e.g., copy_to_user), no uninitialized heap or stack variables read or branched on, and no info-leak risks. Any memory management or synchronization issues in this code path (such as folio refcounting, rmap management, TLB invalidation, or UAF) are caught by standard KASAN, VM_BUG_ON assertions, and standard sanitizers. Therefore, a dedicated KMSAN session is not needed.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch refactors huge PMD splitting logic (__split_huge_pmd_locked) in mm/huge_memory.c by breaking it down into helper functions (huge_zero_pmd_can_split, unmap_huge_pmd_entry, split_huge_pmd_to_ptes, split_present_huge_pmd, split_non_present_huge_pmd) and grouping PMD split parameters in a local stack structure (struct split_pmd_state).\n\nAll instances of 'struct split_pmd_state' are stack-allocated with designated initializers, ensuring all unset fields are zero-initialized. There are no kernel-to-user copy operations (e.g., copy_to_user), no uninitialized heap or stack variables read or branched on, and no info-leak risks. Any memory management or synchronization issues in this code path (such as folio refcounting, rmap management, TLB invalidation, or UAF) are caught by standard KASAN, VM_BUG_ON assertions, and standard sanitizers. Therefore, a dedicated KMSAN session is not needed.",
  "NeedsKMSAN": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)