mm/rmap: fix potential out-of-bounds page table access during batched unmap
Subsystems (4)
PATCH v3 1/1 mm/rmap: fix potential out-of-bounds page table access during batched unmap
From: Lance Yang <[email protected]>
As pointed out by David[1], the batched unmap logic in try_to_unmap_one() may read past the end of a PTE table when a large folio's PTE mappings are not fully contained within a single page table.
While this scenario might be rare, an issue triggerable from userspace must be fixed regardless of its likelihood. This patch fixes the out-of-bounds access by refactoring the logic into a new helper, folio_unmap_pte_batch().
The new helper correctly calculates the safe batch size by capping the scan at both the VMA and PMD boundaries. To simplify the code, it also supports partial batching (i.e., any number of pages from 1 up to the calculated safe maximum), as there is no strong reason to special-case for fully mapped folios.
[1] https://lore.kernel.org/linux-mm/[email protected]
---
v2 -> v3:
- Tweak changelog (per Barry and David)
- Pick AB from Barry - thanks!
- https://lore.kernel.org/linux-mm/[email protected]
v1 -> v2:
- Update subject and changelog (per Barry)
- https://lore.kernel.org/linux-mm/[email protected]
mm/rmap.c | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-)
mm/rmap.c +28 -18
@@ -1845,23 +1845,32 @@ void folio_remove_rmap_pud(struct folio *folio, struct page *page,#endif } -/* We support batch unmapping of PTEs for lazyfree large folios */ -static inline bool can_batch_unmap_folio_ptes (unsigned long addr, -struct folio *folio,pte_t *ptep) +static inline unsigned int folio_unmap_pte_batch (struct folio *folio, +struct page_vma_mapped_walk *pvmw, +enum ttu_flags flags,pte_t pte) {const fpb_t fpb_flags =FPB_IGNORE_DIRTY |FPB_IGNORE_SOFT_DIRTY ; -int max_nr =folio_nr_pages (folio); -pte_t pte =ptep_get (ptep); +unsigned long end_addr, addr = pvmw->address; +struct vm_area_struct *vma = pvmw->vma; +unsigned int max_nr; + +if (flags &TTU_HWPOISON ) +return 1 ; +if (!folio_test_large (folio)) +return 1 ; +/* We may only batch within a single VMA and a single page table. */ + end_addr =pmd_addr_end (addr, vma->vm_end); + max_nr = (end_addr - addr) >>PAGE_SHIFT ; + +/* We only support lazyfree batching for now ... */ if (!folio_test_anon (folio) ||folio_test_swapbacked (folio)) -return false; +return 1 ;if (pte_unused (pte)) -return false; -if (pte_pfn (pte) !=folio_pfn (folio)) -return false; +return 1 ; -return folio_pte_batch (folio, addr, ptep, pte, max_nr, fpb_flags,NULL , -NULL ,NULL ) == max_nr; +return folio_pte_batch (folio, addr, pvmw->pte, pte, max_nr, fpb_flags, +NULL ,NULL ,NULL ); }/*
@@ -2024,9 +2033,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,if (pte_dirty (pteval))folio_mark_dirty (folio); }else if (likely (pte_present (pteval))) { -if (folio_test_large (folio) && !(flags &TTU_HWPOISON ) && -can_batch_unmap_folio_ptes (address, folio, pvmw.pte)) - nr_pages =folio_nr_pages (folio); + nr_pages =folio_unmap_pte_batch (folio, &pvmw, flags, pteval); end_addr = address + nr_pages *PAGE_SIZE ;flush_cache_range (vma, address, end_addr);
@@ -2206,13 +2213,16 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,hugetlb_remove_rmap (folio); }else {folio_remove_rmap_ptes (folio, subpage, nr_pages, vma); -folio_ref_sub (folio, nr_pages -1 ); }if (vma->vm_flags &VM_LOCKED )mlock_drain_local (); -folio_put (folio); -/* We have already batched the entire folio */ -if (nr_pages >1 ) +folio_put_refs (folio, nr_pages); + +/* +* If we are sure that we batched the entire folio and cleared +* all PTEs, we can just optimize and stop right here. +*/ +if (nr_pages ==folio_nr_pages (folio)) goto walk_done;continue ; walk_abort:
Signature · 1 line
2.49.0
Quoted 16 lines · Lance Yang
From: Lance Yang <[email protected]>
As pointed out by David[1], the batched unmap logic in
try_to_unmap_one()may read past the end of a PTE table when a large folio's PTE mappings are not fully contained within a single page table.While this scenario might be rare, an issue triggerable from userspace must be fixed regardless of its likelihood. This patch fixes the out-of-bounds access by refactoring the logic into a new helper,
folio_unmap_pte_batch().The new helper correctly calculates the safe batch size by capping the scan at both the VMA and PMD boundaries. To simplify the code, it also supports partial batching (i.e., any number of pages from 1 up to the calculated safe maximum), as there is no strong reason to special-case for fully mapped folios.
Quoted 8 lines
[1] https://lore.kernel.org/linux-mm/[email protected]
Fixes 354dffd29575 ("mm: support batched unmap for lazyfree large folios during reclamation")Cc <[email protected]>Acked-by Barry Song <[email protected]>Suggested-by David Hildenbrand <[email protected]>Suggested-by Barry Song <[email protected]>Signed-off-by Lance Yang <[email protected]>
This LGTM:
Quoted 48 lines
---
v2 -> v3:
- Tweak changelog (per Barry and David)
- Pick AB from Barry - thanks!
- https://lore.kernel.org/linux-mm/[email protected]v1 -> v2:
- Update subject and changelog (per Barry)
- https://lore.kernel.org/linux-mm/[email protected]mm/rmap.c | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-)mm/rmap.c +17
-6@@ -1845,23 +1845,32 @@ void folio_remove_rmap_pud(struct folio *folio, struct page *page,#endif } -/* We support batch unmapping of PTEs for lazyfree large folios */ -static inline bool can_batch_unmap_folio_ptes (unsigned long addr, -struct folio *folio,pte_t *ptep) +static inline unsigned int folio_unmap_pte_batch (struct folio *folio, +struct page_vma_mapped_walk *pvmw, +enum ttu_flags flags,pte_t pte) {const fpb_t fpb_flags =FPB_IGNORE_DIRTY |FPB_IGNORE_SOFT_DIRTY ; -int max_nr =folio_nr_pages (folio); -pte_t pte =ptep_get (ptep); +unsigned long end_addr, addr = pvmw->address; +struct vm_area_struct *vma = pvmw->vma; +unsigned int max_nr; + +if (flags &TTU_HWPOISON ) +return 1 ; +if (!folio_test_large (folio)) +return 1 ; +/* We may only batch within a single VMA and a single page table. */ + end_addr =pmd_addr_end (addr, vma->vm_end); + max_nr = (end_addr - addr) >>PAGE_SHIFT ; + +/* We only support lazyfree batching for now ... */ if (!folio_test_anon (folio) ||folio_test_swapbacked (folio)) -return false; +return 1 ;
mm/rmap.c void folio_remove_rmap_pud(struct folio *folio, struct page *page, · line 1873
Quoted 10 lines
mm/rmap.c +3
-5if (pte_unused (pte)) -return false; -if (pte_pfn (pte) !=folio_pfn (folio)) -return false; +return 1 ; -return folio_pte_batch (folio, addr, ptep, pte, max_nr, fpb_flags,NULL , -NULL ,NULL ) == max_nr; +return folio_pte_batch (folio, addr, pvmw->pte, pte, max_nr, fpb_flags, +NULL ,NULL ,NULL );
I guess this will conflict with David's changes, but maybe in this simpler case and given this was existing code a bit less? Anyway let's see.
Quoted 38 lines
mm/rmap.c +8
-8}/* @@ -2024,9 +2033,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,if (pte_dirty (pteval))folio_mark_dirty (folio); }else if (likely (pte_present (pteval))) { -if (folio_test_large (folio) && !(flags &TTU_HWPOISON ) && -can_batch_unmap_folio_ptes (address, folio, pvmw.pte)) - nr_pages =folio_nr_pages (folio); + nr_pages =folio_unmap_pte_batch (folio, &pvmw, flags, pteval); end_addr = address + nr_pages *PAGE_SIZE ;flush_cache_range (vma, address, end_addr);@@ -2206,13 +2213,16 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,hugetlb_remove_rmap (folio); }else {folio_remove_rmap_ptes (folio, subpage, nr_pages, vma); -folio_ref_sub (folio, nr_pages -1 ); }if (vma->vm_flags &VM_LOCKED )mlock_drain_local (); -folio_put (folio); -/* We have already batched the entire folio */ -if (nr_pages >1 ) +folio_put_refs (folio, nr_pages); + +/* +* If we are sure that we batched the entire folio and cleared +* all PTEs, we can just optimize and stop right here. +*/ +if (nr_pages ==folio_nr_pages (folio)) goto walk_done;continue ; walk_abort: --2.49.0
David Hildenbrand
Acked-byQuoted 84 lines · Lorenzo Stoakes
Quoted 16 lines · Lance Yang
From: Lance Yang <[email protected]>
As pointed out by David[1], the batched unmap logic in
try_to_unmap_one()may read past the end of a PTE table when a large folio's PTE mappings are not fully contained within a single page table.While this scenario might be rare, an issue triggerable from userspace must be fixed regardless of its likelihood. This patch fixes the out-of-bounds access by refactoring the logic into a new helper,
folio_unmap_pte_batch().The new helper correctly calculates the safe batch size by capping the scan at both the VMA and PMD boundaries. To simplify the code, it also supports partial batching (i.e., any number of pages from 1 up to the calculated safe maximum), as there is no strong reason to special-case for fully mapped folios.
Quoted 8 lines
[1] https://lore.kernel.org/linux-mm/[email protected]
Fixes 354dffd29575 ("mm: support batched unmap for lazyfree large folios during reclamation")Cc <[email protected]>Acked-by Barry Song <[email protected]>Suggested-by David Hildenbrand <[email protected]>Suggested-by Barry Song <[email protected]>Signed-off-by Lance Yang <[email protected]>This LGTM:
Reviewed-by Lorenzo Stoakes <[email protected]>Quoted 54 lines
---
v2 -> v3:
- Tweak changelog (per Barry and David)
- Pick AB from Barry - thanks!
- https://lore.kernel.org/linux-mm/[email protected]v1 -> v2:
- Update subject and changelog (per Barry)
- https://lore.kernel.org/linux-mm/[email protected]mm/rmap.c | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-)mm/rmap.c +18
-9@@ -1845,23 +1845,32 @@ void folio_remove_rmap_pud(struct folio *folio, struct page *page,#endif } -/* We support batch unmapping of PTEs for lazyfree large folios */ -static inline bool can_batch_unmap_folio_ptes (unsigned long addr, -struct folio *folio,pte_t *ptep) +static inline unsigned int folio_unmap_pte_batch (struct folio *folio, +struct page_vma_mapped_walk *pvmw, +enum ttu_flags flags,pte_t pte) {const fpb_t fpb_flags =FPB_IGNORE_DIRTY |FPB_IGNORE_SOFT_DIRTY ; -int max_nr =folio_nr_pages (folio); -pte_t pte =ptep_get (ptep); +unsigned long end_addr, addr = pvmw->address; +struct vm_area_struct *vma = pvmw->vma; +unsigned int max_nr; + +if (flags &TTU_HWPOISON ) +return 1 ; +if (!folio_test_large (folio)) +return 1 ; +/* We may only batch within a single VMA and a single page table. */ + end_addr =pmd_addr_end (addr, vma->vm_end); + max_nr = (end_addr - addr) >>PAGE_SHIFT ; + +/* We only support lazyfree batching for now ... */ if (!folio_test_anon (folio) ||folio_test_swapbacked (folio)) -return false; +return 1 ;if (pte_unused (pte)) -return false; -if (pte_pfn (pte) !=folio_pfn (folio)) -return false; +return 1 ;
Quoted 7 lines
mm/rmap.c void folio_remove_rmap_pud(struct folio *folio, struct page *page, · line 1873
Quoted 4 lines
mm/rmap.c +2
-2-return folio_pte_batch (folio, addr, ptep, pte, max_nr, fpb_flags,NULL , -NULL ,NULL ) == max_nr; +return folio_pte_batch (folio, addr, pvmw->pte, pte, max_nr, fpb_flags, +NULL ,NULL ,NULL );I guess this will conflict with David's changes, but maybe in this simpler case and given this was existing code a bit less? Anyway let's see.
It's a fix, so this is fine to go in first. (I already rebased on top of mm/mm-new where this is in)
Signature · 3 lines
Cheers, David / dhildenb
Quoted 16 lines · Lance Yang
From: Lance Yang <[email protected]>
As pointed out by David[1], the batched unmap logic in
try_to_unmap_one()may read past the end of a PTE table when a large folio's PTE mappings are not fully contained within a single page table.While this scenario might be rare, an issue triggerable from userspace must be fixed regardless of its likelihood. This patch fixes the out-of-bounds access by refactoring the logic into a new helper,
folio_unmap_pte_batch().The new helper correctly calculates the safe batch size by capping the scan at both the VMA and PMD boundaries. To simplify the code, it also supports partial batching (i.e., any number of pages from 1 up to the calculated safe maximum), as there is no strong reason to special-case for fully mapped folios.
Quoted 6 lines
[1] https://lore.kernel.org/linux-mm/[email protected]
Fixes 354dffd29575 ("mm: support batched unmap for lazyfree large folios during reclamation")Cc <[email protected]>Acked-by Barry Song <[email protected]>Suggested-by David Hildenbrand <[email protected]>
Realized this now: This should probably be a "Reported-by:" with the "Closes:" and and a link to my mail.
Signature · 3 lines
Cheers, David / dhildenb
Lance Yang
reviewerQuoted 17 lines · David Hildenbrand
Quoted 16 lines · Lance Yang
From: Lance Yang <[email protected]>
As pointed out by David[1], the batched unmap logic in
try_to_unmap_one()may read past the end of a PTE table when a large folio's PTE mappings are not fully contained within a single page table.While this scenario might be rare, an issue triggerable from userspace must be fixed regardless of its likelihood. This patch fixes the out-of-bounds access by refactoring the logic into a new helper,
folio_unmap_pte_batch().The new helper correctly calculates the safe batch size by capping the scan at both the VMA and PMD boundaries. To simplify the code, it also supports partial batching (i.e., any number of pages from 1 up to the calculated safe maximum), as there is no strong reason to special-case for fully mapped folios.
Quoted 8 lines
Quoted 6 lines
[1] https://lore.kernel.org/linux-mm/ [email protected]
Fixes 354dffd29575 ("mm: support batched unmap for lazyfree large folios during reclamation")Cc <[email protected]>Acked-by Barry Song <[email protected]>Suggested-by David Hildenbrand <[email protected]>Realized this now: This should probably be a "Reported-by:" with the "Closes:" and and a link to my mail.
Got it. Both tags (Reported-by/Closes) will be in the next commit ;)