[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