[PATCH 40/46] mm/ksm: check large swap-cache folios before mapping them
Lorenzo Stoakes (ARM)
ljs at kernel.org
Tue Oct 27 02:00:41 PDT 2026
On Tue, Oct 27, 2026 at 12:18:00AM +0100, David Hildenbrand (Arm) wrote:
> From: Chris Mason <mason at kernel.org>
>
> KSM folios are always small, but their shared swap slots can be read
> back as part of a large folio in the swap cache. ksm_might_need_to_copy()
> skips large folios, so another process can map a page with an incompatible
> anon_vma or page index. Reverse mapping then cannot find the new
> mapping, preventing migration or reclaim.
>
> Apply the same compatibility checks to large folios as to small ones.
> Pass the page corresponding to the swap entry and copy only that page
> when necessary. In do_swap_page()'s copy path, decrement only that slot's
> swap count, because the source swap-cache folio can now be large.
>
> Fix generated by an LLM and sent by Chris Mason in private.
>
> Fixes: f1879e8a0c60 ("mm, swap: never bypass the swap cache even for SWP_SYNCHRONOUS_IO")
> Cc: stable at vger.kernel.org
> Signed-off-by: Chris Mason <mason at kernel.org>
> Signed-off-by: David Hildenbrand (Arm) <david at kernel.org>
As reviewed off-list :)
All very, very horrifying, but I convinced myself that swapped-in large folios
containing previously-order-0 KSM pages _are_ real and _can_ hurt you...
Reviewed-by: Lorenzo Stoakes (ARM) <ljs at kernel.org>
> ---
> include/linux/ksm.h | 6 +++---
> mm/ksm.c | 22 ++++++++++++----------
> mm/memory.c | 5 +++--
> mm/swapfile.c | 3 ++-
> 4 files changed, 20 insertions(+), 16 deletions(-)
>
> diff --git a/include/linux/ksm.h b/include/linux/ksm.h
> index d39d0d5483a23..a0d358a1feb63 100644
> --- a/include/linux/ksm.h
> +++ b/include/linux/ksm.h
> @@ -91,8 +91,8 @@ static inline void ksm_exit(struct mm_struct *mm)
> * We'd like to make this conditional on vma->vm_flags & VM_MERGEABLE,
> * but what if the vma was unmerged while the page was swapped out?
> */
> -struct folio *ksm_might_need_to_copy(struct folio *folio,
> - struct vm_area_struct *vma, unsigned long addr);
> +struct folio *ksm_might_need_to_copy(struct folio *folio, struct page *page,
> + struct vm_area_struct *vma, unsigned long addr);
>
> void rmap_walk_ksm(struct folio *folio, struct rmap_walk_control *rwc);
> void folio_migrate_ksm(struct folio *newfolio, struct folio *folio);
> @@ -145,7 +145,7 @@ static inline int ksm_madvise(struct vm_area_struct *vma, unsigned long start,
> }
>
> static inline struct folio *ksm_might_need_to_copy(struct folio *folio,
> - struct vm_area_struct *vma, unsigned long addr)
> + struct page *page, struct vm_area_struct *vma, unsigned long addr)
> {
> return folio;
> }
> diff --git a/mm/ksm.c b/mm/ksm.c
> index e050b1bdf810d..e95f6b6723602 100644
> --- a/mm/ksm.c
> +++ b/mm/ksm.c
> @@ -3138,19 +3138,20 @@ void __ksm_exit(struct mm_struct *mm)
> }
>
> static bool folio_anon_rmap_compatible(const struct folio *folio,
> - struct vm_area_struct *vma, unsigned long addr)
> + const struct page *page, struct vm_area_struct *vma,
> + unsigned long addr)
> {
> const struct anon_vma *anon_vma = folio_anon_vma(folio);
> struct anon_vma_chain *avc;
>
> VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
> - VM_WARN_ON_ONCE_FOLIO(folio_test_large(folio), folio);
> + VM_WARN_ON_ONCE_FOLIO(page_folio(page) != folio, folio);
> VM_WARN_ON_ONCE_FOLIO(folio_test_ksm(folio), folio);
> vma_assert_stabilised(vma);
>
> if (!anon_vma)
> return true;
> - if (folio->index != linear_anon_page_index(vma, addr))
> + if (page_pgoff(folio, page) != linear_anon_page_index(vma, addr))
> return false;
>
> /*
> @@ -3167,20 +3168,21 @@ static bool folio_anon_rmap_compatible(const struct folio *folio,
> return false;
> }
>
> -struct folio *ksm_might_need_to_copy(struct folio *folio,
> - struct vm_area_struct *vma, unsigned long addr)
> +struct folio *ksm_might_need_to_copy(struct folio *folio, struct page *page,
> + struct vm_area_struct *vma, unsigned long addr)
> {
> - struct page *page = folio_page(folio, 0);
> struct folio *new_folio;
>
> - if (folio_test_large(folio))
> - return folio;
> -
> + /*
> + * A KSM folio is small, but the slot that it left behind can be swapped
> + * in as a part of a large folio by one of the processes that shared it.
> + * So test, and copy, the page of the slot.
> + */
> if (folio_test_ksm(folio)) {
> if (folio_stable_node(folio) &&
> !(ksm_run & KSM_RUN_UNMERGE))
> return folio; /* no need to copy it */
> - } else if (folio_anon_rmap_compatible(folio, vma, addr)) {
> + } else if (folio_anon_rmap_compatible(folio, page, vma, addr)) {
> return folio; /* still no need to copy it */
> }
> if (PageHWPoison(page))
> diff --git a/mm/memory.c b/mm/memory.c
> index 230a2f4dafa13..be4c05db37d33 100644
> --- a/mm/memory.c
> +++ b/mm/memory.c
> @@ -5096,7 +5096,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
> * folio->index of non-ksm folios would be nonlinear inside the
> * anon VMA -- the ksm flag is lost on actual swapout.
> */
> - folio = ksm_might_need_to_copy(folio, vma, vmf->address);
> + folio = ksm_might_need_to_copy(folio, page, vma, vmf->address);
> if (unlikely(!folio)) {
> ret = VM_FAULT_OOM;
> folio = swapcache;
> @@ -5278,7 +5278,8 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
> if (unlikely(folio != swapcache)) {
> folio_add_new_anon_rmap(folio, vma, address, RMAP_EXCLUSIVE);
> folio_add_lru_vma(folio, vma);
> - folio_put_swap(swapcache, NULL);
> + folio_put_swap(swapcache,
> + folio_file_page(swapcache, swp_offset(entry)));
> } else if (!folio_test_anon(folio)) {
> /*
> * We currently only expect !anon folios that are fully
> diff --git a/mm/swapfile.c b/mm/swapfile.c
> index 254ce86fa923e..71ef9e2efb3a9 100644
> --- a/mm/swapfile.c
> +++ b/mm/swapfile.c
> @@ -2514,7 +2514,8 @@ static int unuse_pte(struct vm_area_struct *vma, pmd_t *pmd,
> return 0;
>
> swapcache = folio;
> - folio = ksm_might_need_to_copy(folio, vma, addr);
> + folio = ksm_might_need_to_copy(folio,
> + folio_file_page(folio, swp_offset(entry)), vma, addr);
> if (unlikely(!folio))
> return -ENOMEM;
> else if (unlikely(folio == ERR_PTR(-EHWPOISON))) {
>
> --
> 2.43.0
>
--
Cheers, Lorenzo
More information about the kexec
mailing list