mm: don't expose page to fast gup before it's ready
Subsystems (10)
PATCH 1/1 mm: don't expose page to fast gup before it's ready
We don't want to expose page before it's properly setup. During page setup, we may call page_add_new_anon_rmap() which uses non- atomic bit op. If page is exposed before it's done, we could overwrite page flags that are set by get_user_pages_fast() or it's callers. Here is a non-fatal scenario (there might be other fatal problems that I didn't look into):
CPU 1 CPU1
set_pte_at() get_user_pages_fast()page_add_new_anon_rmap() gup_pte_range()
__SetPageSwapBacked() SetPageReferenced()
Fix the problem by delaying set_pte_at() until page is ready.
--- mm/memory.c | 2 +- mm/swapfile.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-)
mm/memory.c +1 -1
@@ -3010,7 +3010,6 @@ int do_swap_page(struct vm_fault *vmf)flush_icache_page (vma, page);if (pte_swp_soft_dirty (vmf->orig_pte)) pte =pte_mksoft_dirty (pte); -set_pte_at (vma->vm_mm, vmf->address, vmf->pte, pte); vmf->orig_pte = pte;/* ksm created a completely new copy */
@@ -3023,6 +3022,7 @@ int do_swap_page(struct vm_fault *vmf)mem_cgroup_commit_charge (page, memcg, true, false);activate_page (page); } +set_pte_at (vma->vm_mm, vmf->address, vmf->pte, pte);swap_free (entry); if (mem_cgroup_swap_full(page) ||
mm/swapfile.c +2 -2
@@ -1800,8 +1800,6 @@ static int unuse_pte(struct vm_area_struct *vma, pmd_t *pmd,dec_mm_counter (vma->vm_mm,MM_SWAPENTS );inc_mm_counter (vma->vm_mm,MM_ANONPAGES );get_page (page); -set_pte_at (vma->vm_mm, addr, pte, -pte_mkold (mk_pte (page, vma->vm_page_prot)));if (page == swapcache) {page_add_anon_rmap (page, vma, addr, false);mem_cgroup_commit_charge (page, memcg, true, false);
@@ -1810,6 +1808,8 @@ static int unuse_pte(struct vm_area_struct *vma, pmd_t *pmd,mem_cgroup_commit_charge (page, memcg, false, false);lru_cache_add_active_or_unevictable (page, vma); } +set_pte_at (vma->vm_mm, addr, pte, +pte_mkold (mk_pte (page, vma->vm_page_prot)));swap_free (entry);/* * Move the page to the active list so it is not
Signature · 7 lines
2.16.0.rc0.223.g4a4ac83678-goog -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 7 lines · Yu Zhao
We don't want to expose page before it's properly setup. During page setup, we may call
page_add_new_anon_rmap()which uses non- atomic bit op. If page is exposed before it's done, we could overwrite page flags that are set byget_user_pages_fast()or it's callers. Here is a non-fatal scenario (there might be other fatal problems that I didn't look into):
Quoted 6 lines
CPU 1 CPU1
set_pte_at()get_user_pages_fast()page_add_new_anon_rmap()gup_pte_range()
__SetPageSwapBacked()SetPageReferenced()Fix the problem by delaying
set_pte_at()until page is ready.
Have you seen this race happening in real workloads or this is a code review based fix or a theoretical issue? I am primarily asking because the code is like that at least throughout git era and I do not remember any issue like this. If you can really trigger this tiny race window then we should mark the fix for stable.
Also what prevents reordering here? There do not seem to be any barriers to prevent __SetPageSwapBacked leak after set_pte_at with your patch.
Quoted 56 lines
Signed-off-by Yu Zhao <[email protected]>--- mm/memory.c | 2 +- mm/swapfile.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-)mm/memory.c +1
-1@@ -3010,7 +3010,6 @@ int do_swap_page(struct vm_fault *vmf)flush_icache_page (vma, page);if (pte_swp_soft_dirty (vmf->orig_pte)) pte =pte_mksoft_dirty (pte); -set_pte_at (vma->vm_mm, vmf->address, vmf->pte, pte); vmf->orig_pte = pte;/* ksm created a completely new copy */ @@ -3023,6 +3022,7 @@ int do_swap_page(struct vm_fault *vmf)mem_cgroup_commit_charge (page, memcg, true, false);activate_page (page); } +set_pte_at (vma->vm_mm, vmf->address, vmf->pte, pte);swap_free (entry); if (mem_cgroup_swap_full(page) ||mm/swapfile.c +2
-2@@ -1800,8 +1800,6 @@ static int unuse_pte(struct vm_area_struct *vma, pmd_t *pmd,dec_mm_counter (vma->vm_mm,MM_SWAPENTS );inc_mm_counter (vma->vm_mm,MM_ANONPAGES );get_page (page); -set_pte_at (vma->vm_mm, addr, pte, -pte_mkold (mk_pte (page, vma->vm_page_prot)));if (page == swapcache) {page_add_anon_rmap (page, vma, addr, false);mem_cgroup_commit_charge (page, memcg, true, false);@@ -1810,6 +1808,8 @@ static int unuse_pte(struct vm_area_struct *vma, pmd_t *pmd,mem_cgroup_commit_charge (page, memcg, false, false);lru_cache_add_active_or_unevictable (page, vma); } +set_pte_at (vma->vm_mm, addr, pte, +pte_mkold (mk_pte (page, vma->vm_page_prot)));swap_free (entry);/* * Move the page to the active list so it is not Signature · 7 lines
2.16.0.rc0.223.g4a4ac83678-goog -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Signature · 8 lines
Michal Hocko SUSE Labs -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 13 lines · Michal Hocko
Quoted 12 lines · Yu Zhao
We don't want to expose page before it's properly setup. During page setup, we may call
page_add_new_anon_rmap()which uses non- atomic bit op. If page is exposed before it's done, we could overwrite page flags that are set byget_user_pages_fast()or it's callers. Here is a non-fatal scenario (there might be other fatal problems that I didn't look into):CPU 1 CPU1
set_pte_at()get_user_pages_fast()page_add_new_anon_rmap()gup_pte_range()
__SetPageSwapBacked()SetPageReferenced()
Quoted 7 lines
Quoted 1 line
Fix the problem by delaying
set_pte_at()until page is ready.Have you seen this race happening in real workloads or this is a code review based fix or a theoretical issue? I am primarily asking because the code is like that at least throughout git era and I do not remember any issue like this. If you can really trigger this tiny race window then we should mark the fix for stable.
I didn't observe the race directly. But I did get few crashes when trying to access mem_cgroup of pages returned by get_user_pages_fast(). Those page were charged and they showed valid mem_cgroup in kdumps. So this led me to think the problem came from premature set_pte_at().
I think the fact that nobody complained about this problem is because the race only happens when using ksm+swap, and it might not cause any fatal problem even so. Nevertheless, it's nice to have set_pte_at() done consistently after rmap is added and page is charged.
Quoted 2 lines
Also what prevents reordering here? There do not seem to be any barriers to prevent
__SetPageSwapBackedleak afterset_pte_atwith your patch.
I assumed mem_cgroup_commit_charge() acted as full barrier. Since you explicitly asked the question, I realized my assumption doesn't hold when memcg is disabled. So we do need something to prevent reordering in my patch. And it brings up the question whether we want to add more barrier to other places that call page_add_new_anon_rmap() and set_pte_at().
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Andrew Morton
maintainerQuoted 35 lines · On Tue, 9 Jan 2018 02:10:50 -0800 Yu Zhao <[email protected]> wrote:
Quoted 13 lines · Michal Hocko
Quoted 12 lines · Yu Zhao
We don't want to expose page before it's properly setup. During page setup, we may call
page_add_new_anon_rmap()which uses non- atomic bit op. If page is exposed before it's done, we could overwrite page flags that are set byget_user_pages_fast()or it's callers. Here is a non-fatal scenario (there might be other fatal problems that I didn't look into):CPU 1 CPU1
set_pte_at()get_user_pages_fast()page_add_new_anon_rmap()gup_pte_range()
__SetPageSwapBacked()SetPageReferenced()Quoted 7 lines
Quoted 1 line
Fix the problem by delaying
set_pte_at()until page is ready.Have you seen this race happening in real workloads or this is a code review based fix or a theoretical issue? I am primarily asking because the code is like that at least throughout git era and I do not remember any issue like this. If you can really trigger this tiny race window then we should mark the fix for stable.
I didn't observe the race directly. But I did get few crashes when trying to access
mem_cgroupof pages returned byget_user_pages_fast(). Those page were charged and they showed validmem_cgroupin kdumps. So this led me to think the problem came from prematureset_pte_at().I think the fact that nobody complained about this problem is because the race only happens when using ksm+swap, and it might not cause any fatal problem even so. Nevertheless, it's nice to have
set_pte_at()done consistently after rmap is added and page is charged.Quoted 2 lines
Also what prevents reordering here? There do not seem to be any barriers to prevent
__SetPageSwapBackedleak afterset_pte_atwith your patch.
Quoted 6 lines
I assumed
mem_cgroup_commit_charge()acted as full barrier. Since you explicitly asked the question, I realized my assumption doesn't hold when memcg is disabled. So we do need something to prevent reordering in my patch. And it brings up the question whether we want to add more barrier to other places that callpage_add_new_anon_rmap()andset_pte_at().
No progress here? I have the patch marked "to be updated", hence it is stuck. Please let's get it finished off for 4.17-rc1.
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Andrew Morton
maintainerQuoted 9 lines · On Tue, 9 Jan 2018 02:10:50 -0800 Yu Zhao <[email protected]> wrote:
Quoted 2 lines
Also what prevents reordering here? There do not seem to be any barriers to prevent
__SetPageSwapBackedleak afterset_pte_atwith your patch.I assumed
mem_cgroup_commit_charge()acted as full barrier. Since you explicitly asked the question, I realized my assumption doesn't hold when memcg is disabled. So we do need something to prevent reordering in my patch. And it brings up the question whether we want to add more barrier to other places that callpage_add_new_anon_rmap()andset_pte_at().
Is a new version of this patch planned?
Quoted 5 lines · Andrew Morton
Quoted 3 lines · On Tue, 9 Jan 2018 02:10:50 -0800 Yu Zhao <[email protected]> wrote:
Quoted 2 lines
Also what prevents reordering here? There do not seem to be any barriers to prevent
__SetPageSwapBackedleak afterset_pte_atwith your patch.
Quoted 8 lines
Quoted 6 lines
I assumed
mem_cgroup_commit_charge()acted as full barrier. Since you explicitly asked the question, I realized my assumption doesn't hold when memcg is disabled. So we do need something to prevent reordering in my patch. And it brings up the question whether we want to add more barrier to other places that callpage_add_new_anon_rmap()andset_pte_at().Is a new version of this patch planned?
Sorry for the late reply. The last time I tried, I didn't come up with a better fix because:
1) as Michal pointed out, we need to make sure the fast gup sees all changes made before set_pte_at();
2) pairing smp_wmb() in set_pte/pmd_at() with smp_rmb() in gup seems the best way to prevent any potential ordering related problems in the future;
3) but this slows down the paths that don't require the smp_mwb() unnecessarily.
I didn't give it further thought because the problem doesn't seem fatal at the time. Now the fast gup has changed and the problem is serious:
CPU 1 CPU 1
set_pte_at get_user_pages_fastpage_add_new_anon_rmap gup_pte_range__SetPageSwapBacked (fetch)
try_get_compound_head page_ref_add_unless__SetPageSwapBacked (store)
Or the similar problem could happen to __do_huge_pmd_anonymous_page(), for the reason of missing smp_wmb() between the non-atomic bit op and set_pmd_at().
We could simply replace __SetPageSwapBacked() with its atomic version. But 2) seems more preferable to me because it addresses my original problem:
Quoted 4 lines
Quoted 4 lines
I didn't observe the race directly. But I did get few crashes when trying to access
mem_cgroupof pages returned byget_user_pages_fast(). Those page were charged and they showed validmem_cgroupin kdumps. So this led me to think the problem came from prematureset_pte_at().
Thoughts? Thanks.
PATCH v2 1/1 mm: don't expose page to fast gup prematurely
We don't want to expose page to fast gup running on a remote CPU before all local non-atomic ops on page flags are visible first.
For anon page that isn't in swap cache, we need to make sure all prior non-atomic ops, especially __SetPageSwapBacked() in page_add_new_anon_rmap(), are order before set_pte_at() to prevent the following race:
CPU 1 CPU1
set_pte_at() get_user_pages_fast()page_add_new_anon_rmap() gup_pte_range()
__SetPageSwapBacked() SetPageReferenced()
This demonstrates a non-fatal scenario. Though I haven't directly observed any fatal ones, they can exist, e.g., PG_lock set by fast gup caller and then overwritten by __SetPageSwapBacked().
For anon page that is in swap cache and file page including tmpfs, we don't need smp_wmb() before set_pte_at(). We've already exposed them after adding them to swap and file caches. xas_lock_irq() and xas_unlock_irq() are used during the process, which guarantees __SetPageUptodate() and other non-atomic ops are ordered before set_pte_at(). (Using non-atomic ops thereafter is a bug, obviously).
The smp_wmb() is open-coded rather than inserted at the bottom of page_add_new_anon_rmap() because there is one place that calls the function doesn't need the barrier (do_huge_pmd_wp_page_fallback()).
Alternatively, we can use atomic ops instead. There seems at least as many __SetPageUptodate() and __SetPageSwapBacked() to change.
--- kernel/events/uprobes.c | 2 ++ mm/huge_memory.c | 4 ++++ mm/khugepaged.c | 2 ++ mm/memory.c | 10 +++++++++- mm/migrate.c | 2 ++ mm/swapfile.c | 6 ++++-- mm/userfaultfd.c | 2 ++ 7 files changed, 25 insertions(+), 3 deletions(-)
kernel/events/uprobes.c +2 -0
@@ -194,6 +194,8 @@ static int __replace_page(struct vm_area_struct *vma, unsigned long addr,flush_cache_page (vma, addr,pte_pfn (*pvmw.pte));ptep_clear_flush_notify (vma, addr, pvmw.pte); +/* commit non-atomic ops before exposing to fast gup */ +smp_wmb ();set_pte_at_notify (mm, addr, pvmw.pte,mk_pte (new_page, vma->vm_page_prot));
mm/huge_memory.c +4 -0
@@ -616,6 +616,8 @@ static vm_fault_t __do_huge_pmd_anonymous_page(struct vm_fault *vmf,mem_cgroup_commit_charge (page, memcg, false, true);lru_cache_add_active_or_unevictable (page, vma);pgtable_trans_huge_deposit (vma->vm_mm, vmf->pmd, pgtable); +/* commit non-atomic ops before exposing to fast gup */ +smp_wmb ();set_pmd_at (vma->vm_mm, haddr, vmf->pmd, entry);add_mm_counter (vma->vm_mm,MM_ANONPAGES ,HPAGE_PMD_NR );mm_inc_nr_ptes (vma->vm_mm);
@@ -1423,6 +1425,8 @@ vm_fault_t do_huge_pmd_wp_page(struct vm_fault *vmf, pmd_t orig_pmd)page_add_new_anon_rmap (new_page, vma, haddr, true);mem_cgroup_commit_charge (new_page, memcg, false, true);lru_cache_add_active_or_unevictable (new_page, vma); +/* commit non-atomic ops before exposing to fast gup */ +smp_wmb ();set_pmd_at (vma->vm_mm, haddr, vmf->pmd, entry);update_mmu_cache_pmd (vma, vmf->address, vmf->pmd);if (!page) {
mm/khugepaged.c +2 -0
@@ -1081,6 +1081,8 @@ static void collapse_huge_page(struct mm_struct *mm,count_memcg_events (memcg,THP_COLLAPSE_ALLOC ,1 );lru_cache_add_active_or_unevictable (new_page, vma);pgtable_trans_huge_deposit (mm, pmd, pgtable); +/* commit non-atomic ops before exposing to fast gup */ +smp_wmb ();set_pmd_at (mm, address, pmd, _pmd);update_mmu_cache_pmd (vma, address, pmd);spin_unlock (pmd_ptl);
mm/memory.c +9 -1
@@ -2363,6 +2363,8 @@ static vm_fault_t wp_page_copy(struct vm_fault *vmf)* mmu page tables (such as kvm shadow page tables), we want the * new page to be mapped directly into the secondary page table. */ +/* commit non-atomic ops before exposing to fast gup */ +smp_wmb ();set_pte_at_notify (mm, vmf->address, vmf->pte, entry);update_mmu_cache (vma, vmf->address, vmf->pte);if (old_page) {
@@ -2873,7 +2875,6 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)flush_icache_page (vma, page);if (pte_swp_soft_dirty (vmf->orig_pte)) pte =pte_mksoft_dirty (pte); -set_pte_at (vma->vm_mm, vmf->address, vmf->pte, pte);arch_do_swap_page (vma->vm_mm, vma, vmf->address, pte, vmf->orig_pte); vmf->orig_pte = pte;
@@ -2882,12 +2883,15 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)page_add_new_anon_rmap (page, vma, vmf->address, false);mem_cgroup_commit_charge (page, memcg, false, false);lru_cache_add_active_or_unevictable (page, vma); +/* commit non-atomic ops before exposing to fast gup */ +smp_wmb (); }else {do_page_add_anon_rmap (page, vma, vmf->address, exclusive);mem_cgroup_commit_charge (page, memcg, true, false);activate_page (page); } +set_pte_at (vma->vm_mm, vmf->address, vmf->pte, pte);swap_free (entry);if (mem_cgroup_swap_full (page) || (vma->vm_flags &VM_LOCKED ) ||PageMlocked (page))
@@ -3030,6 +3034,8 @@ static vm_fault_t do_anonymous_page(struct vm_fault *vmf)page_add_new_anon_rmap (page, vma, vmf->address, false);mem_cgroup_commit_charge (page, memcg, false, false);lru_cache_add_active_or_unevictable (page, vma); +/* commit non-atomic ops before exposing to fast gup */ +smp_wmb (); setpte:set_pte_at (vma->vm_mm, vmf->address, vmf->pte, entry);
@@ -3293,6 +3299,8 @@ vm_fault_t alloc_set_pte(struct vm_fault *vmf, struct mem_cgroup *memcg,page_add_new_anon_rmap (page, vma, vmf->address, false);mem_cgroup_commit_charge (page, memcg, false, false);lru_cache_add_active_or_unevictable (page, vma); +/* commit non-atomic ops before exposing to fast gup */ +smp_wmb (); }else {inc_mm_counter_fast (vma->vm_mm,mm_counter_file (page));page_add_file_rmap (page, false);
mm/migrate.c +2 -0
@@ -2689,6 +2689,8 @@ static void migrate_vma_insert_page(struct migrate_vma *migrate,lru_cache_add_active_or_unevictable (page, vma);get_page (page); +/* commit non-atomic ops before exposing to fast gup */ +smp_wmb ();if (flush) {flush_cache_page (vma, addr,pte_pfn (*ptep));ptep_clear_flush_notify (vma, addr, ptep);
mm/swapfile.c +4 -2
@@ -1880,8 +1880,6 @@ static int unuse_pte(struct vm_area_struct *vma, pmd_t *pmd,dec_mm_counter (vma->vm_mm,MM_SWAPENTS );inc_mm_counter (vma->vm_mm,MM_ANONPAGES );get_page (page); -set_pte_at (vma->vm_mm, addr, pte, -pte_mkold (mk_pte (page, vma->vm_page_prot)));if (page == swapcache) {page_add_anon_rmap (page, vma, addr, false);mem_cgroup_commit_charge (page, memcg, true, false);
@@ -1889,7 +1887,11 @@ static int unuse_pte(struct vm_area_struct *vma, pmd_t *pmd,page_add_new_anon_rmap (page, vma, addr, false);mem_cgroup_commit_charge (page, memcg, false, false);lru_cache_add_active_or_unevictable (page, vma); +/* commit non-atomic ops before exposing to fast gup */ +smp_wmb (); } +set_pte_at (vma->vm_mm, addr, pte, +pte_mkold (mk_pte (page, vma->vm_page_prot)));swap_free (entry);/* * Move the page to the active list so it is not
mm/userfaultfd.c +2 -0
@@ -92,6 +92,8 @@ static int mcopy_atomic_pte(struct mm_struct *dst_mm,mem_cgroup_commit_charge (page, memcg, false, false);lru_cache_add_active_or_unevictable (page, dst_vma); +/* commit non-atomic ops before exposing to fast gup */ +smp_wmb ();set_pte_at (dst_mm, dst_addr, dst_pte, _dst_pte);/* No need to invalidate - it was non-present before */
Signature · 1 line
2.23.0.237.gc6a4ce50a0-goog
Re: [PATCH v2] mm: don't expose page to fast gup prematurely
Quoted 8 lines · Yu Zhao
We don't want to expose page to fast gup running on a remote CPU before all local non-atomic ops on page flags are visible first.
For anon page that isn't in swap cache, we need to make sure all prior non-atomic ops, especially
__SetPageSwapBacked()inpage_add_new_anon_rmap(), are order beforeset_pte_at()to prevent the following race:
Quoted 4 lines
CPU 1 CPU1
set_pte_at()get_user_pages_fast()page_add_new_anon_rmap()gup_pte_range()
__SetPageSwapBacked()SetPageReferenced()
Is there a particular codepath that has what you listed for CPU? After quick look, I only saw that we page_add_new_anon_rmap() called before set_pte_at().
Signature · 1 line
Kirill A. Shutemov
Re: [PATCH v2] mm: don't expose page to fast gup prematurely
Quoted 9 lines · Kirill A. Shutemov
Quoted 8 lines · Yu Zhao
We don't want to expose page to fast gup running on a remote CPU before all local non-atomic ops on page flags are visible first.
For anon page that isn't in swap cache, we need to make sure all prior non-atomic ops, especially
__SetPageSwapBacked()inpage_add_new_anon_rmap(), are order beforeset_pte_at()to prevent the following race:
Quoted 8 lines
Quoted 4 lines
CPU 1 CPU1
set_pte_at()get_user_pages_fast()page_add_new_anon_rmap()gup_pte_range()
__SetPageSwapBacked()SetPageReferenced()Is there a particular codepath that has what you listed for CPU? After quick look, I only saw that we
page_add_new_anon_rmap()called beforeset_pte_at().
I think so. One in do_swap_page() and another in unuse_pte(). Both are on KSM paths. Am I referencing a stale copy of the source?
Re: [PATCH v2] mm: don't expose page to fast gup prematurely
Quoted 15 lines · Yu Zhao
Quoted 14 lines · Kirill A. Shutemov
Quoted 12 lines · Yu Zhao
We don't want to expose page to fast gup running on a remote CPU before all local non-atomic ops on page flags are visible first.
For anon page that isn't in swap cache, we need to make sure all prior non-atomic ops, especially
__SetPageSwapBacked()inpage_add_new_anon_rmap(), are order beforeset_pte_at()to prevent the following race:CPU 1 CPU1
set_pte_at()get_user_pages_fast()page_add_new_anon_rmap()gup_pte_range()
__SetPageSwapBacked()SetPageReferenced()
Quoted 6 lines
Quoted 3 lines
Is there a particular codepath that has what you listed for CPU? After quick look, I only saw that we
page_add_new_anon_rmap()called beforeset_pte_at().I think so. One in
do_swap_page()and another inunuse_pte(). Both are on KSM paths. Am I referencing a stale copy of the source?
I *think* it is a bug. Setting a pte before adding the page to rmap may lead to rmap (like try_to_unmap() or something) to miss the VMA.
Do I miss something?
Signature · 1 line
Kirill A. Shutemov
Re: [PATCH v2] mm: don't expose page to fast gup prematurely
Quoted 20 lines · Kirill A. Shutemov
Quoted 19 lines · Yu Zhao
Quoted 17 lines · Kirill A. Shutemov
Quoted 8 lines · Yu Zhao
We don't want to expose page to fast gup running on a remote CPU before all local non-atomic ops on page flags are visible first.
For anon page that isn't in swap cache, we need to make sure all prior non-atomic ops, especially
__SetPageSwapBacked()inpage_add_new_anon_rmap(), are order beforeset_pte_at()to prevent the following race:Quoted 4 lines
CPU 1 CPU1
set_pte_at()get_user_pages_fast()page_add_new_anon_rmap()gup_pte_range()
__SetPageSwapBacked()SetPageReferenced()Is there a particular codepath that has what you listed for CPU? After quick look, I only saw that we
page_add_new_anon_rmap()called beforeset_pte_at().
Quoted 7 lines
Quoted 2 lines
I think so. One in
do_swap_page()and another inunuse_pte(). Both are on KSM paths. Am I referencing a stale copy of the source?I *think* it is a bug. Setting a pte before adding the page to rmap may lead to rmap (like
try_to_unmap()or something) to miss the VMA.Do I miss something?
We have the pages locked in those two places, so for try_to_unmap() and the rest of page_vma_mapped_walk() users, they will block on the page lock:
CPU 1 CPU 2 lock_page() set_pte_at() unlock_page() lock_page() try_to_unmap() page_vma_mapped_walk() pte_present() without holding ptl unlock_page()
For others that don't use page_vma_mapped_walk(), they should either lock pages or grab ptl before checking pte_present().
AFAIK, the fast gup is the only one doesn't fall into the either category.
PATCH v3 1/4 mm: remove unnecessary smp_wmb() in collapse_huge_page()
In reply to Yu Zhao__SetPageUptodate() always has a built-in smp_wmb() to make sure user data copied to a new page appears before set_pmd_at().
--- mm/khugepaged.c | 7 ------- 1 file changed, 7 deletions(-)
mm/khugepaged.c +0 -7
@@ -1067,13 +1067,6 @@ static void collapse_huge_page(struct mm_struct *mm, _pmd =mk_huge_pmd (new_page, vma->vm_page_prot); _pmd =maybe_pmd_mkwrite (pmd_mkdirty (_pmd), vma); -/* -* spin_lock() below is not the equivalent of smp_wmb(), so -* this is needed to avoid the copy_huge_page writes to become -* visible after the set_pmd_at() write. -*/ -smp_wmb (); -spin_lock (pmd_ptl);BUG_ON (!pmd_none (*pmd));page_add_new_anon_rmap (new_page, vma, address, true);
Signature · 1 line
2.23.0.351.gc4317032e6-goog
PATCH v3 2/4 mm: don't expose hugetlb page to fast gup prematurely
We don't want to expose a hugetlb page to the fast gup running on a remote CPU before the local non-atomic op __SetPageUptodate() is visible first.
For a hugetlb page, there is no memory barrier between the non-atomic op and set_huge_pte_at(). Therefore, the page can appear to the fast gup before the flag does. There is no evidence this would cause any problem, but there is no point risking the race either.
This patch simply replace 3 uses of the non-atomic op with its atomic version though out mm/hugetlb.c. The only one left in hugetlbfs_fallocate() is safe because huge_add_to_page_cache() serves as a valid write barrier.
--- mm/hugetlb.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
mm/hugetlb.c +4 -4
@@ -3693,7 +3693,7 @@ static vm_fault_t hugetlb_cow(struct mm_struct *mm, struct vm_area_struct *vma,copy_user_huge_page (new_page, old_page, address, vma,pages_per_huge_page (h)); -__SetPageUptodate (new_page); +SetPageUptodate (new_page);mmu_notifier_range_init (&range,MMU_NOTIFY_CLEAR ,0 , vma, mm, haddr, haddr +huge_page_size (h));
@@ -3879,7 +3879,7 @@ static vm_fault_t hugetlb_no_page(struct mm_struct *mm, goto out; }clear_huge_page (page, address,pages_per_huge_page (h)); -__SetPageUptodate (page); +SetPageUptodate (page); new_page = true;if (vma->vm_flags &VM_MAYSHARE ) {
@@ -4180,11 +4180,11 @@ int hugetlb_mcopy_atomic_pte(struct mm_struct *dst_mm, }/* -* The memory barrier inside __SetPageUptodate makes sure that +* The memory barrier inside SetPageUptodate makes sure that * preceding stores to the page contents become visible before * the set_pte_at() write. */ -__SetPageUptodate (page); +SetPageUptodate (page); mapping = dst_vma->vm_file->f_mapping; idx =vma_hugecache_offset (h, dst_vma, dst_addr);
Signature · 1 line
2.23.0.351.gc4317032e6-goog
PATCH v3 3/4 mm: don't expose non-hugetlb page to fast gup prematurely
In reply to Yu ZhaoWe don't want to expose a non-hugetlb page to the fast gup running on a remote CPU before all local non-atomic ops on the page flags are visible first.
For an anon page that isn't in swap cache, we need to make sure all prior non-atomic ops, especially __SetPageSwapBacked() in page_add_new_anon_rmap(), are ordered before set_pte_at() to prevent the following race:
CPU 1 CPU1 set_pte_at() get_user_pages_fast() page_add_new_anon_rmap() gup_pte_range() __SetPageSwapBacked() SetPageReferenced()
This demonstrates a non-fatal scenario. Though haven't been directly observed, the fatal ones can exist, e.g., PG_lock set by fast gup caller and then overwritten by __SetPageSwapBacked().
For an anon page that is already in swap cache or a file page, we don't need smp_wmb() before set_pte_at() because adding to swap or file cach serves as a valid write barrier. Using non-atomic ops thereafter is a bug, obviously.
smp_wmb() is added following 11 of total 12 page_add_new_anon_rmap() call sites, with the only exception being do_huge_pmd_wp_page_fallback() because of an existing smp_wmb().
--- kernel/events/uprobes.c | 2 ++ mm/huge_memory.c | 6 ++++++ mm/khugepaged.c | 2 ++ mm/memory.c | 10 +++++++++- mm/migrate.c | 2 ++ mm/swapfile.c | 6 ++++-- mm/userfaultfd.c | 2 ++ 7 files changed, 27 insertions(+), 3 deletions(-)
kernel/events/uprobes.c +2 -0
@@ -194,6 +194,8 @@ static int __replace_page(struct vm_area_struct *vma, unsigned long addr,flush_cache_page (vma, addr,pte_pfn (*pvmw.pte));ptep_clear_flush_notify (vma, addr, pvmw.pte); +/* commit non-atomic ops before exposing to fast gup */ +smp_wmb ();set_pte_at_notify (mm, addr, pvmw.pte,mk_pte (new_page, vma->vm_page_prot));
mm/huge_memory.c +6 -0
@@ -616,6 +616,8 @@ static vm_fault_t __do_huge_pmd_anonymous_page(struct vm_fault *vmf,mem_cgroup_commit_charge (page, memcg, false, true);lru_cache_add_active_or_unevictable (page, vma);pgtable_trans_huge_deposit (vma->vm_mm, vmf->pmd, pgtable); +/* commit non-atomic ops before exposing to fast gup */ +smp_wmb ();set_pmd_at (vma->vm_mm, haddr, vmf->pmd, entry);add_mm_counter (vma->vm_mm,MM_ANONPAGES ,HPAGE_PMD_NR );mm_inc_nr_ptes (vma->vm_mm);
@@ -1276,7 +1278,9 @@ static vm_fault_t do_huge_pmd_wp_page_fallback(struct vm_fault *vmf, }kfree (pages); +/* commit non-atomic ops before exposing to fast gup */ smp_wmb ();/* make pte visible before pmd */ +pmd_populate (vma->vm_mm, vmf->pmd, pgtable);page_remove_rmap (page, true);spin_unlock (vmf->ptl);
@@ -1423,6 +1427,8 @@ vm_fault_t do_huge_pmd_wp_page(struct vm_fault *vmf, pmd_t orig_pmd)page_add_new_anon_rmap (new_page, vma, haddr, true);mem_cgroup_commit_charge (new_page, memcg, false, true);lru_cache_add_active_or_unevictable (new_page, vma); +/* commit non-atomic ops before exposing to fast gup */ +smp_wmb ();set_pmd_at (vma->vm_mm, haddr, vmf->pmd, entry);update_mmu_cache_pmd (vma, vmf->address, vmf->pmd);if (!page) {
mm/khugepaged.c +2 -0
@@ -1074,6 +1074,8 @@ static void collapse_huge_page(struct mm_struct *mm,count_memcg_events (memcg,THP_COLLAPSE_ALLOC ,1 );lru_cache_add_active_or_unevictable (new_page, vma);pgtable_trans_huge_deposit (mm, pmd, pgtable); +/* commit non-atomic ops before exposing to fast gup */ +smp_wmb ();set_pmd_at (mm, address, pmd, _pmd);update_mmu_cache_pmd (vma, address, pmd);spin_unlock (pmd_ptl);
mm/memory.c +9 -1
@@ -2367,6 +2367,8 @@ static vm_fault_t wp_page_copy(struct vm_fault *vmf)* mmu page tables (such as kvm shadow page tables), we want the * new page to be mapped directly into the secondary page table. */ +/* commit non-atomic ops before exposing to fast gup */ +smp_wmb ();set_pte_at_notify (mm, vmf->address, vmf->pte, entry);update_mmu_cache (vma, vmf->address, vmf->pte);if (old_page) {
@@ -2877,7 +2879,6 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)flush_icache_page (vma, page);if (pte_swp_soft_dirty (vmf->orig_pte)) pte =pte_mksoft_dirty (pte); -set_pte_at (vma->vm_mm, vmf->address, vmf->pte, pte);arch_do_swap_page (vma->vm_mm, vma, vmf->address, pte, vmf->orig_pte); vmf->orig_pte = pte;
@@ -2886,12 +2887,15 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)page_add_new_anon_rmap (page, vma, vmf->address, false);mem_cgroup_commit_charge (page, memcg, false, false);lru_cache_add_active_or_unevictable (page, vma); +/* commit non-atomic ops before exposing to fast gup */ +smp_wmb (); }else {do_page_add_anon_rmap (page, vma, vmf->address, exclusive);mem_cgroup_commit_charge (page, memcg, true, false);activate_page (page); } +set_pte_at (vma->vm_mm, vmf->address, vmf->pte, pte);swap_free (entry);if (mem_cgroup_swap_full (page) || (vma->vm_flags &VM_LOCKED ) ||PageMlocked (page))
@@ -3034,6 +3038,8 @@ static vm_fault_t do_anonymous_page(struct vm_fault *vmf)page_add_new_anon_rmap (page, vma, vmf->address, false);mem_cgroup_commit_charge (page, memcg, false, false);lru_cache_add_active_or_unevictable (page, vma); +/* commit non-atomic ops before exposing to fast gup */ +smp_wmb (); setpte:set_pte_at (vma->vm_mm, vmf->address, vmf->pte, entry);
@@ -3297,6 +3303,8 @@ vm_fault_t alloc_set_pte(struct vm_fault *vmf, struct mem_cgroup *memcg,page_add_new_anon_rmap (page, vma, vmf->address, false);mem_cgroup_commit_charge (page, memcg, false, false);lru_cache_add_active_or_unevictable (page, vma); +/* commit non-atomic ops before exposing to fast gup */ +smp_wmb (); }else {inc_mm_counter_fast (vma->vm_mm,mm_counter_file (page));page_add_file_rmap (page, false);
mm/migrate.c +2 -0
@@ -2783,6 +2783,8 @@ static void migrate_vma_insert_page(struct migrate_vma *migrate,lru_cache_add_active_or_unevictable (page, vma);get_page (page); +/* commit non-atomic ops before exposing to fast gup */ +smp_wmb ();if (flush) {flush_cache_page (vma, addr,pte_pfn (*ptep));ptep_clear_flush_notify (vma, addr, ptep);
mm/swapfile.c +4 -2
@@ -1880,8 +1880,6 @@ static int unuse_pte(struct vm_area_struct *vma, pmd_t *pmd,dec_mm_counter (vma->vm_mm,MM_SWAPENTS );inc_mm_counter (vma->vm_mm,MM_ANONPAGES );get_page (page); -set_pte_at (vma->vm_mm, addr, pte, -pte_mkold (mk_pte (page, vma->vm_page_prot)));if (page == swapcache) {page_add_anon_rmap (page, vma, addr, false);mem_cgroup_commit_charge (page, memcg, true, false);
@@ -1889,7 +1887,11 @@ static int unuse_pte(struct vm_area_struct *vma, pmd_t *pmd,page_add_new_anon_rmap (page, vma, addr, false);mem_cgroup_commit_charge (page, memcg, false, false);lru_cache_add_active_or_unevictable (page, vma); +/* commit non-atomic ops before exposing to fast gup */ +smp_wmb (); } +set_pte_at (vma->vm_mm, addr, pte, +pte_mkold (mk_pte (page, vma->vm_page_prot)));swap_free (entry);/* * Move the page to the active list so it is not
mm/userfaultfd.c +2 -0
@@ -92,6 +92,8 @@ static int mcopy_atomic_pte(struct mm_struct *dst_mm,mem_cgroup_commit_charge (page, memcg, false, false);lru_cache_add_active_or_unevictable (page, dst_vma); +/* commit non-atomic ops before exposing to fast gup */ +smp_wmb ();set_pte_at (dst_mm, dst_addr, dst_pte, _dst_pte);/* No need to invalidate - it was non-present before */
Signature · 1 line
2.23.0.351.gc4317032e6-goog
Peter Zijlstra
maintainerRe: [PATCH v3 3/4] mm: don't expose non-hugetlb page to fast gup prematurely
Quoted 18 lines · Yu Zhao
We don't want to expose a non-hugetlb page to the fast gup running on a remote CPU before all local non-atomic ops on the page flags are visible first.
For an anon page that isn't in swap cache, we need to make sure all prior non-atomic ops, especially
__SetPageSwapBacked()inpage_add_new_anon_rmap(), are ordered beforeset_pte_at()to prevent the following race:CPU 1 CPU1 set_pte_at() get_user_pages_fast() page_add_new_anon_rmap() gup_pte_range() __SetPageSwapBacked() SetPageReferenced()This demonstrates a non-fatal scenario. Though haven't been directly observed, the fatal ones can exist, e.g.,
PG_lockset by fast gup caller and then overwritten by__SetPageSwapBacked().
Quoted 9 lines
For an anon page that is already in swap cache or a file page, we don't need
smp_wmb()beforeset_pte_at()because adding to swap or file cach serves as a valid write barrier. Using non-atomic ops thereafter is a bug, obviously.
smp_wmb()is added following 11 of total 12page_add_new_anon_rmap()call sites, with the only exception beingdo_huge_pmd_wp_page_fallback()because of an existingsmp_wmb().
I'm thinking this patch make stuff rather fragile.. Should we instead stick the barrier in set_p*d_at() instead? Or rather, make that store a store-release?
Re: [PATCH v3 3/4] mm: don't expose non-hugetlb page to fast gup prematurely
Quoted 24 lines · Peter Zijlstra
Quoted 23 lines · Yu Zhao
We don't want to expose a non-hugetlb page to the fast gup running on a remote CPU before all local non-atomic ops on the page flags are visible first.
For an anon page that isn't in swap cache, we need to make sure all prior non-atomic ops, especially
__SetPageSwapBacked()inpage_add_new_anon_rmap(), are ordered beforeset_pte_at()to prevent the following race:CPU 1 CPU1 set_pte_at() get_user_pages_fast() page_add_new_anon_rmap() gup_pte_range() __SetPageSwapBacked() SetPageReferenced()This demonstrates a non-fatal scenario. Though haven't been directly observed, the fatal ones can exist, e.g.,
PG_lockset by fast gup caller and then overwritten by__SetPageSwapBacked().For an anon page that is already in swap cache or a file page, we don't need
smp_wmb()beforeset_pte_at()because adding to swap or file cach serves as a valid write barrier. Using non-atomic ops thereafter is a bug, obviously.
Quoted 8 lines
Quoted 4 lines
smp_wmb()is added following 11 of total 12page_add_new_anon_rmap()call sites, with the only exception beingdo_huge_pmd_wp_page_fallback()because of an existingsmp_wmb().I'm thinking this patch make stuff rather fragile.. Should we instead stick the barrier in set_p*d_at() instead? Or rather, make that store a store-release?
I prefer it this way too, but I suspected the majority would be concerned with the performance implications, especially those looping set_pte_at()s in mm/huge_memory.c.
If we have a consensus that smp_wmb() would be better off wrapped together with set_p*d_at() in a macro, I'd be glad to make the change.
And it seems to me applying smp_store_release() would have to happen in each arch, which might be just as fragile.
Re: [PATCH v3 3/4] mm: don't expose non-hugetlb page to fast gup prematurely
Quoted 30 lines · Yu Zhao
Quoted 29 lines · Peter Zijlstra
Quoted 27 lines · Yu Zhao
We don't want to expose a non-hugetlb page to the fast gup running on a remote CPU before all local non-atomic ops on the page flags are visible first.
For an anon page that isn't in swap cache, we need to make sure all prior non-atomic ops, especially
__SetPageSwapBacked()inpage_add_new_anon_rmap(), are ordered beforeset_pte_at()to prevent the following race:CPU 1 CPU1 set_pte_at() get_user_pages_fast() page_add_new_anon_rmap() gup_pte_range() __SetPageSwapBacked() SetPageReferenced()This demonstrates a non-fatal scenario. Though haven't been directly observed, the fatal ones can exist, e.g.,
PG_lockset by fast gup caller and then overwritten by__SetPageSwapBacked().For an anon page that is already in swap cache or a file page, we don't need
smp_wmb()beforeset_pte_at()because adding to swap or file cach serves as a valid write barrier. Using non-atomic ops thereafter is a bug, obviously.
smp_wmb()is added following 11 of total 12page_add_new_anon_rmap()call sites, with the only exception beingdo_huge_pmd_wp_page_fallback()because of an existingsmp_wmb().
Quoted 7 lines
Quoted 3 lines
I'm thinking this patch make stuff rather fragile.. Should we instead stick the barrier in set_p*d_at() instead? Or rather, make that store a store-release?
I prefer it this way too, but I suspected the majority would be concerned with the performance implications, especially those looping set_pte_at()s in
mm/huge_memory.c.
We can rename current set_pte_at() to __set_pte_at() or something and leave it in places where barrier is not needed. The new set_pte_at()( will be used in the rest of the places with the barrier inside.
BTW, have you looked at other levels of page table hierarchy. Do we have the same issue for PMD/PUD/... pages?
Signature · 1 line
Kirill A. Shutemov
John Hubbard
reviewerRe: [PATCH v3 3/4] mm: don't expose non-hugetlb page to fast gup prematurely
Quoted 3 lines · Kirill A. Shutemov
Quoted 2 lines · Yu Zhao
Quoted 1 line · Peter Zijlstra
On Tue, Sep 24, 2019 at 05:24:58PM -0600, Yu Zhao wrote:
...
Quoted 11 lines
Quoted 7 lines
Quoted 3 lines
I'm thinking this patch make stuff rather fragile.. Should we instead stick the barrier in set_p*d_at() instead? Or rather, make that store a store-release?
I prefer it this way too, but I suspected the majority would be concerned with the performance implications, especially those looping set_pte_at()s in
mm/huge_memory.c.We can rename current
set_pte_at()to__set_pte_at()or something and leave it in places where barrier is not needed. The new set_pte_at()( will be used in the rest of the places with the barrier inside.
+1, sounds nice. I was unhappy about the wide-ranging changes that would have to be maintained. So this seems much better.
Quoted 4 lines
BTW, have you looked at other levels of page table hierarchy. Do we have the same issue for PMD/PUD/... pages?
Along the lines of "what other memory barriers might be missing for get_user_pages_fast(), I'm also concerned that the synchronization between get_user_pages_fast() and freeing the page tables might be technically broken, due to missing memory barriers on the get_user_pages_fast() side. Details:
gup_fast() disables interrupts, but I think it also needs some sort of memory barrier(s), in order to prevent reads of the page table (gup_pgd_range, etc) from speculatively happening before the interrupts are disabled.
Leonardo Bras's recent patchset brought this to my attention. Here, he's recommending adding atomic counting inc/dec before and after the gup_fast() irq disable/enable points:
https://lore.kernel.org/r/[email protected]
...and that lead to noticing a general lack of barriers there.
thanks,
Signature · 2 lines
John Hubbard NVIDIA
Re: [PATCH v3 3/4] mm: don't expose non-hugetlb page to fast gup prematurely
Quoted 13 lines · John Hubbard
Quoted 3 lines · Kirill A. Shutemov
Quoted 2 lines · Yu Zhao
Quoted 1 line · Peter Zijlstra
On Tue, Sep 24, 2019 at 05:24:58PM -0600, Yu Zhao wrote:
...
Quoted 8 lines
Quoted 7 lines
Quoted 3 lines
I'm thinking this patch make stuff rather fragile.. Should we instead stick the barrier in set_p*d_at() instead? Or rather, make that store a store-release?
I prefer it this way too, but I suspected the majority would be concerned with the performance implications, especially those looping set_pte_at()s in
mm/huge_memory.c.
Quoted 6 lines
Quoted 3 lines
We can rename current
set_pte_at()to__set_pte_at()or something and leave it in places where barrier is not needed. The new set_pte_at()( will be used in the rest of the places with the barrier inside.+1, sounds nice. I was unhappy about the wide-ranging changes that would have to be maintained. So this seems much better.
Just to be clear that doing so will add unnecessary barriers to one of the two paths that share set_pte_at().
Quoted 4 lines
Quoted 3 lines
BTW, have you looked at other levels of page table hierarchy. Do we have the same issue for PMD/PUD/... pages?
Quoted 8 lines
Along the lines of "what other memory barriers might be missing for
get_user_pages_fast(), I'm also concerned that the synchronization betweenget_user_pages_fast()and freeing the page tables might be technically broken, due to missing memory barriers on theget_user_pages_fast()side. Details:
gup_fast()disables interrupts, but I think it also needs some sort of memory barrier(s), in order to prevent reads of the page table (gup_pgd_range, etc) from speculatively happening before the interrupts are disabled.
I was under impression switching back from interrupt context is a full barrier (otherwise wouldn't we be vulnerable to some side channel attacks?), so the reader side wouldn't need explicit rmb.
John Hubbard
reviewerRe: [PATCH v3 3/4] mm: don't expose non-hugetlb page to fast gup prematurely
Quoted 18 lines · Yu Zhao
Quoted 17 lines · John Hubbard
Quoted 3 lines · Kirill A. Shutemov
Quoted 2 lines · Yu Zhao
Quoted 1 line · Peter Zijlstra
On Tue, Sep 24, 2019 at 05:24:58PM -0600, Yu Zhao wrote:
...
Quoted 11 lines
Quoted 7 lines
Quoted 3 lines
I'm thinking this patch make stuff rather fragile.. Should we instead stick the barrier in set_p*d_at() instead? Or rather, make that store a store-release?
I prefer it this way too, but I suspected the majority would be concerned with the performance implications, especially those looping set_pte_at()s in
mm/huge_memory.c.We can rename current
set_pte_at()to__set_pte_at()or something and leave it in places where barrier is not needed. The new set_pte_at()( will be used in the rest of the places with the barrier inside.
Quoted 5 lines
Quoted 2 lines
+1, sounds nice. I was unhappy about the wide-ranging changes that would have to be maintained. So this seems much better.
Just to be clear that doing so will add unnecessary barriers to one of the two paths that share
set_pte_at().
Good point, maybe there's a better place to do it...
Quoted 10 lines
Quoted 10 lines
Quoted 4 lines
BTW, have you looked at other levels of page table hierarchy. Do we have the same issue for PMD/PUD/... pages?
Along the lines of "what other memory barriers might be missing for
get_user_pages_fast(), I'm also concerned that the synchronization betweenget_user_pages_fast()and freeing the page tables might be technically broken, due to missing memory barriers on theget_user_pages_fast()side. Details:
Quoted 8 lines
Quoted 3 lines
gup_fast()disables interrupts, but I think it also needs some sort of memory barrier(s), in order to prevent reads of the page table (gup_pgd_range, etc) from speculatively happening before the interrupts are disabled.I was under impression switching back from interrupt context is a full barrier (otherwise wouldn't we be vulnerable to some side channel attacks?), so the reader side wouldn't need explicit rmb.
Documentation/memory-barriers.txt points out:
INTERRUPT DISABLING FUNCTIONS
-----------------------------
Functions that disable interrupts (ACQUIRE equivalent) and enable interrupts (RELEASE equivalent) will act as compiler barriers only. So if memory or I/O barriers are required in such a situation, they must be provided from some other means.
btw, I'm really sorry I missed your responses over the last 3 or 4 days. I just tracked down something in our email system that was sometimes moving some emails to spam (just few enough to escape immediate attention, argghh!). I think I killed it off for good now. I wasn't ignoring you. :)
thanks,
Signature · 2 lines
John Hubbard NVIDIA
Re: [PATCH v3 3/4] mm: don't expose non-hugetlb page to fast gup prematurely
Quoted 57 lines · John Hubbard
Quoted 18 lines · Yu Zhao
Quoted 17 lines · John Hubbard
Quoted 3 lines · Kirill A. Shutemov
Quoted 2 lines · Yu Zhao
Quoted 1 line · Peter Zijlstra
On Tue, Sep 24, 2019 at 05:24:58PM -0600, Yu Zhao wrote:
...
Quoted 11 lines
Quoted 7 lines
Quoted 3 lines
I'm thinking this patch make stuff rather fragile.. Should we instead stick the barrier in set_p*d_at() instead? Or rather, make that store a store-release?
I prefer it this way too, but I suspected the majority would be concerned with the performance implications, especially those looping set_pte_at()s in
mm/huge_memory.c.We can rename current
set_pte_at()to__set_pte_at()or something and leave it in places where barrier is not needed. The new set_pte_at()( will be used in the rest of the places with the barrier inside.Quoted 5 lines
Quoted 2 lines
+1, sounds nice. I was unhappy about the wide-ranging changes that would have to be maintained. So this seems much better.
Just to be clear that doing so will add unnecessary barriers to one of the two paths that share
set_pte_at().Good point, maybe there's a better place to do it...
Quoted 10 lines
Quoted 10 lines
Quoted 4 lines
BTW, have you looked at other levels of page table hierarchy. Do we have the same issue for PMD/PUD/... pages?
Along the lines of "what other memory barriers might be missing for
get_user_pages_fast(), I'm also concerned that the synchronization betweenget_user_pages_fast()and freeing the page tables might be technically broken, due to missing memory barriers on theget_user_pages_fast()side. Details:Quoted 8 lines
Quoted 3 lines
gup_fast()disables interrupts, but I think it also needs some sort of memory barrier(s), in order to prevent reads of the page table (gup_pgd_range, etc) from speculatively happening before the interrupts are disabled.I was under impression switching back from interrupt context is a full barrier (otherwise wouldn't we be vulnerable to some side channel attacks?), so the reader side wouldn't need explicit rmb.
Documentation/memory-barriers.txtpoints out:INTERRUPT DISABLING FUNCTIONS
-----------------------------Functions that disable interrupts (ACQUIRE equivalent) and enable interrupts (RELEASE equivalent) will act as compiler barriers only. So if memory or I/O barriers are required in such a situation, they must be provided from some other means.
Quoted 4 lines
btw, I'm really sorry I missed your responses over the last 3 or 4 days. I just tracked down something in our email system that was sometimes moving some emails to spam (just few enough to escape immediate attention, argghh!). I think I killed it off for good now. I wasn't ignoring you. :)
Thanks, John. I agree with all you said, including the irq disabling function not being a sufficient smp_rmb().
I was hoping somebody could clarify whether ipi handlers used by tlb flush are sufficient to prevent CPU 1 from seeing any stale data from freed page tables on all supported archs.
CPU 1 CPU 2
flush remote tlb by ipi wait for the ipi hanlder
<ipi handler>
free page table
disable irq
walk page table
enable irq
I think they should because otherwise tlb flush wouldn't work if CPU 1 still sees stale data from the freed page table, unless there is a really strange CPU cache design I'm not aware of.
Quoting comments from x86 ipi handler flush_tlb_func_common():
* read active_mm's tlb_gen. We don't need any explicit barriers
* because all x86 flush operations are serializing and the
* atomic64_read operation won't be reordered by the compiler.
For ppc64 ipi hander radix__flush_tlb_range(), there is an "eieio" instruction:
https://www.ibm.com/support/knowledgecenter/en/ssw_aix_72/assembler/idalangref_eieio_instrs.html
I'm not sure why it's not "sync" -- I'd guess something implicitly works as "sync" already (or it's a bug).
I didn't find an ipi handler for tlb flush on arm64. There should be one, otherwise fast gup on arm64 would be broken. Mark?
Re: [PATCH v3 3/4] mm: don't expose non-hugetlb page to fast gup prematurely
In reply to John HubbardQuoted 5 lines · John Hubbard
Quoted 3 lines · Kirill A. Shutemov
BTW, have you looked at other levels of page table hierarchy. Do we have the same issue for PMD/PUD/... pages?
Quoted 8 lines
Along the lines of "what other memory barriers might be missing for
get_user_pages_fast(), I'm also concerned that the synchronization betweenget_user_pages_fast()and freeing the page tables might be technically broken, due to missing memory barriers on theget_user_pages_fast()side. Details:
gup_fast()disables interrupts, but I think it also needs some sort of memory barrier(s), in order to prevent reads of the page table (gup_pgd_range, etc) from speculatively happening before the interrupts are disabled.
Could you be more specific about the race scenario please? I thought that the unmap path will be serialized by the pte lock.
Signature · 2 lines
Michal Hocko SUSE Labs
Re: [PATCH v3 3/4] mm: don't expose non-hugetlb page to fast gup prematurely
Quoted 11 lines · Michal Hocko
Quoted 10 lines · John Hubbard
Quoted 3 lines · Kirill A. Shutemov
BTW, have you looked at other levels of page table hierarchy. Do we have the same issue for PMD/PUD/... pages?
Along the lines of "what other memory barriers might be missing for
get_user_pages_fast(), I'm also concerned that the synchronization betweenget_user_pages_fast()and freeing the page tables might be technically broken, due to missing memory barriers on theget_user_pages_fast()side. Details:
Quoted 6 lines
Quoted 3 lines
gup_fast()disables interrupts, but I think it also needs some sort of memory barrier(s), in order to prevent reads of the page table (gup_pgd_range, etc) from speculatively happening before the interrupts are disabled.Could you be more specific about the race scenario please? I thought that the unmap path will be serialized by the pte lock.
Yes, the unmap path is protected by ptl, but the fast gup isn't. Please correct me if I'm wrong, John. This is the hypothetical race:
CPU 1 (gup) CPU 2 (zap)
speculatively load a pmd val
zap the pte table pointed by the pmd val flush tlb by ipi
<handle ipi>
free the pte tablelocal_irq_disable()
use the stale pmd val
use-after-free the pte tablelocal_irq_enable()
I don't think it would happen because the interrupt context on CPU 1 would act as a full mb and enforce a reload of the pmd val. But I'm not entirely sure.
John Hubbard
reviewerRe: [PATCH v3 3/4] mm: don't expose non-hugetlb page to fast gup prematurely
In reply to Michal HockoQuoted 11 lines · Michal Hocko
Quoted 10 lines · John Hubbard
Quoted 3 lines · Kirill A. Shutemov
BTW, have you looked at other levels of page table hierarchy. Do we have the same issue for PMD/PUD/... pages?
Along the lines of "what other memory barriers might be missing for
get_user_pages_fast(), I'm also concerned that the synchronization betweenget_user_pages_fast()and freeing the page tables might be technically broken, due to missing memory barriers on theget_user_pages_fast()side. Details:
Quoted 7 lines
Quoted 3 lines
gup_fast()disables interrupts, but I think it also needs some sort of memory barrier(s), in order to prevent reads of the page table (gup_pgd_range, etc) from speculatively happening before the interrupts are disabled.Could you be more specific about the race scenario please? I thought that the unmap path will be serialized by the pte lock.
I don't see a pte lock anywhere here.
This case is really pretty far out there, but without run-time memory barriers I don't think we can completely rule it out:
CPU 0 CPU 1
-------- ---------------
get_user_pages_fast()
do_unmap()
unmap_region()
free_pgtables()
/*
* speculative reads, re-ordered
* by the CPU at run time, not
* compile time. The function calls
* are not really in this order, but
* the corresponding reads could be.
*/
gup_pgd_range()
gup_p4d_range()
gup_pud_range()
gup_pmd_range()
pmd = READ_ONCE(*pmdp)
/* This is stale */ tlb_finish_mmu()
tlb_flush_mmu()
tlb_flush_mmu_tlbonly()
tlb_flush()
flush_tlb_mm
flush_tlb_mm_range
flush_tlb_others
native_flush_tlb_others
smp_call_function_many: IPIs
...blocks until CPU1 reenables
interrupts
local_irq_disable()
...continue the page walk based
on stale/freed data...
thanks,
Signature · 2 lines
John Hubbard NVIDIA
John Hubbard
reviewerRe: [PATCH v3 3/4] mm: don't expose non-hugetlb page to fast gup prematurely
Quoted 58 lines · John Hubbard
Quoted 11 lines · Michal Hocko
Quoted 10 lines · John Hubbard
Quoted 3 lines · Kirill A. Shutemov
BTW, have you looked at other levels of page table hierarchy. Do we have the same issue for PMD/PUD/... pages?
Along the lines of "what other memory barriers might be missing for
get_user_pages_fast(), I'm also concerned that the synchronization betweenget_user_pages_fast()and freeing the page tables might be technically broken, due to missing memory barriers on theget_user_pages_fast()side. Details:Quoted 7 lines
Quoted 3 lines
gup_fast()disables interrupts, but I think it also needs some sort of memory barrier(s), in order to prevent reads of the page table (gup_pgd_range, etc) from speculatively happening before the interrupts are disabled.Could you be more specific about the race scenario please? I thought that the unmap path will be serialized by the pte lock.
I don't see a pte lock anywhere here.
This case is really pretty far out there, but without run-time memory barriers I don't think we can completely rule it out:
CPU 0 CPU 1 -------- ---------------
get_user_pages_fast()
do_unmap()
unmap_region()
free_pgtables()
/*
* speculative reads, re-ordered
* by the CPU at run time, not
* compile time. The function calls
* are not really in this order, but
* the corresponding reads could be. */
gup_pgd_range()gup_p4d_range()gup_pud_range()gup_pmd_range()
pmd =READ_ONCE(*pmdp)/* This is stale */
tlb_finish_mmu()
tlb_flush_mmu()
tlb_flush_mmu_tlbonly()
tlb_flush()
flush_tlb_mm
flush_tlb_mm_range
flush_tlb_others
native_flush_tlb_others
smp_call_function_many: IPIs
...blocks until CPU1 reenables
interrupts
Quoted 4 lines
local_irq_disable()...continue the page walk based on stale/freed data...
...and note that I just asked Leonardo Bras (+CC) to include "the" fix[1] for that (I proposed a snippet that attempts a fix anyway), in his upcoming feature that speeds up PPC THP operations.
So I hope that doesn't muddy this thread too much, but the other one is awfully quiet and I was worried that the overlapping changes might go unnoticed.
[1] https://lore.kernel.org/r/[email protected]
thanks,
Signature · 2 lines
John Hubbard NVIDIA
Re: [PATCH v3 3/4] mm: don't expose non-hugetlb page to fast gup prematurely
In reply to John HubbardQuoted 58 lines · John Hubbard
Quoted 11 lines · Michal Hocko
Quoted 10 lines · John Hubbard
Quoted 3 lines · Kirill A. Shutemov
BTW, have you looked at other levels of page table hierarchy. Do we have the same issue for PMD/PUD/... pages?
Along the lines of "what other memory barriers might be missing for
get_user_pages_fast(), I'm also concerned that the synchronization betweenget_user_pages_fast()and freeing the page tables might be technically broken, due to missing memory barriers on theget_user_pages_fast()side. Details:Quoted 7 lines
Quoted 3 lines
gup_fast()disables interrupts, but I think it also needs some sort of memory barrier(s), in order to prevent reads of the page table (gup_pgd_range, etc) from speculatively happening before the interrupts are disabled.Could you be more specific about the race scenario please? I thought that the unmap path will be serialized by the pte lock.
I don't see a pte lock anywhere here.
This case is really pretty far out there, but without run-time memory barriers I don't think we can completely rule it out:
CPU 0 CPU 1
-------- ---------------
get_user_pages_fast()
do_unmap()
unmap_region()
free_pgtables()/* * speculative reads, re-ordered * by the CPU at run time, not * compile time. The function calls * are not really in this order, but * the corresponding reads could be. */ gup_pgd_range() gup_p4d_range() gup_pud_range() gup_pmd_range() pmd = READ_ONCE(*pmdp) /* This is stale */
tlb_finish_mmu()
tlb_flush_mmu()
tlb_flush_mmu_tlbonly()
tlb_flush()
flush_tlb_mm
flush_tlb_mm_range
flush_tlb_others
native_flush_tlb_others
smp_call_function_many: IPIs
...blocks until CPU1 reenables
interrupts
Quoted 3 lines
local_irq_disable()
...continue the page walk based
on stale/freed data...
Yes, but then we have:
head = try_get_compound_head(page, 1);
which has an atomic operation providing barrier in it and then we have:
if (unlikely(pte_val(pte) != pte_val(*ptep))) {
put_page(head);
goto pte_unmap;
}So we reload PTE again and check the value didn't change. Which should prevent the race you explain above. Or do I miss anything?
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
John Hubbard
reviewerRe: [PATCH v3 3/4] mm: don't expose non-hugetlb page to fast gup prematurely
Quoted 4 lines · Jan Kara
Quoted 3 lines · John Hubbard
Quoted 2 lines · Michal Hocko
Quoted 1 line · John Hubbard
On 9/26/19 3:20 AM, Kirill A. Shutemov wrote:
...
Quoted 43 lines
Quoted 33 lines
CPU 0 CPU 1
-------- ---------------
get_user_pages_fast()
do_unmap()
unmap_region()
free_pgtables()/* * speculative reads, re-ordered * by the CPU at run time, not * compile time. The function calls * are not really in this order, but * the corresponding reads could be. */ gup_pgd_range() gup_p4d_range() gup_pud_range() gup_pmd_range() pmd = READ_ONCE(*pmdp) /* This is stale */
tlb_finish_mmu()
tlb_flush_mmu()
tlb_flush_mmu_tlbonly()
tlb_flush()
flush_tlb_mm
flush_tlb_mm_range
flush_tlb_others
native_flush_tlb_others
smp_call_function_many: IPIs
...blocks until CPU1 reenables
interruptsQuoted 3 lines
local_irq_disable()
...continue the page walk based
on stale/freed data...Yes, but then we have:
head = try_get_compound_head(page, 1);which has an atomic operation providing barrier in it and then we have:
Quoted 7 lines
if (unlikely(pte_val(pte) != pte_val(*ptep))) { put_page(head); goto pte_unmap; }So we reload PTE again and check the value didn't change. Which should prevent the race you explain above. Or do I miss anything?
Well, a couple of questions:
1. Is there *really* a memory barrier in try_get_compound_head()? Because I only see a READ_ONCE compile barrier, which would mean that run time reordering is still possible.
2. Your code above is after the "pmd = READ_ONCE(*pmdp)" line, so by then, it's already found the pte based on reading a stale pmd. So checking the pte seems like it's checking the wrong thing--it's too late, for this case, right?
thanks,
Signature · 2 lines
John Hubbard NVIDIA
Re: [PATCH v3 3/4] mm: don't expose non-hugetlb page to fast gup prematurely
Quoted 58 lines · John Hubbard
Quoted 4 lines · Jan Kara
Quoted 3 lines · John Hubbard
Quoted 2 lines · Michal Hocko
Quoted 1 line · John Hubbard
On 9/26/19 3:20 AM, Kirill A. Shutemov wrote:
...
Quoted 43 lines
Quoted 33 lines
CPU 0 CPU 1
-------- ---------------
get_user_pages_fast()
do_unmap()
unmap_region()
free_pgtables()/* * speculative reads, re-ordered * by the CPU at run time, not * compile time. The function calls * are not really in this order, but * the corresponding reads could be. */ gup_pgd_range() gup_p4d_range() gup_pud_range() gup_pmd_range() pmd = READ_ONCE(*pmdp) /* This is stale */
tlb_finish_mmu()
tlb_flush_mmu()
tlb_flush_mmu_tlbonly()
tlb_flush()
flush_tlb_mm
flush_tlb_mm_range
flush_tlb_others
native_flush_tlb_others
smp_call_function_many: IPIs
...blocks until CPU1 reenables
interruptsQuoted 3 lines
local_irq_disable()
...continue the page walk based
on stale/freed data...Yes, but then we have:
head = try_get_compound_head(page, 1);which has an atomic operation providing barrier in it and then we have:
Quoted 7 lines
if (unlikely(pte_val(pte) != pte_val(*ptep))) { put_page(head); goto pte_unmap; }So we reload PTE again and check the value didn't change. Which should prevent the race you explain above. Or do I miss anything?
Quoted 5 lines
Well, a couple of questions:
1. Is there *really* a memory barrier in
try_get_compound_head()? Because I only see aREAD_ONCEcompile barrier, which would mean that run time reordering is still possible.
try_get_compound_head() has page_cache_add_speculative() which is atomic_add_unless() which is guaranteed to provide ordering.
Quoted 4 lines
2. Your code above is after the "pmd =
READ_ONCE(*pmdp)" line, so by then, it's already found the pte based on reading a stale pmd. So checking the pte seems like it's checking the wrong thing--it's too late, for this case, right?
Well, if PMD is getting freed, all PTEs in it should be cleared by that time, shouldn't they? So although we read from stale PMD, either we already see cleared PTE or the check pte_val(pte) != pte_val(*ptep) will fail and so we never actually succeed in getting stale PTE entry (at least unless the page table page that used to be PMD can get freed and reused
- which is not the case in the example you've shown above).
So I still don't see a problem. That being said I don't feel being expert in this particular area. I just always thought GUP prevents races like this by the scheme I describe so I'd like to see what I'm missing :).
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Peter Zijlstra
maintainerRe: [PATCH v3 3/4] mm: don't expose non-hugetlb page to fast gup prematurely
Quoted 10 lines · Jan Kara
Quoted 5 lines · John Hubbard
Well, a couple of questions:
1. Is there *really* a memory barrier in
try_get_compound_head()? Because I only see aREAD_ONCEcompile barrier, which would mean that run time reordering is still possible.
try_get_compound_head()haspage_cache_add_speculative()which isatomic_add_unless()which is guaranteed to provide ordering.
atomic_add_unless() only provides ordering on SUCCESS, now looking at the code in question that seems sufficient, so all should be well. Just wanted to let you know that if atomic_add_unless() FAILs there is no implied ordering what so ever.
Re: [PATCH v3 3/4] mm: don't expose non-hugetlb page to fast gup prematurely
Quoted 9 lines · Peter Zijlstra
Quoted 8 lines · Jan Kara
Quoted 5 lines · John Hubbard
Well, a couple of questions:
1. Is there *really* a memory barrier in
try_get_compound_head()? Because I only see aREAD_ONCEcompile barrier, which would mean that run time reordering is still possible.
Quoted 7 lines
Quoted 2 lines
try_get_compound_head()haspage_cache_add_speculative()which isatomic_add_unless()which is guaranteed to provide ordering.
atomic_add_unless()only provides ordering on SUCCESS, now looking at the code in question that seems sufficient, so all should be well. Just wanted to let you know that ifatomic_add_unless()FAILs there is no implied ordering what so ever.
Yeah, right, thanks for correction!
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
John Hubbard
reviewerRe: [PATCH v3 3/4] mm: don't expose non-hugetlb page to fast gup prematurely
In reply to Jan KaraQuoted 12 lines · Jan Kara
Quoted 10 lines · John Hubbard
Quoted 4 lines · Jan Kara
Quoted 3 lines · John Hubbard
Quoted 2 lines · Michal Hocko
Quoted 1 line · John Hubbard
On 9/26/19 3:20 AM, Kirill A. Shutemov wrote:
...
2. Your code above is after the "pmd =READ_ONCE(*pmdp)" line, so by then, it's already found the pte based on reading a stale pmd. So checking the pte seems like it's checking the wrong thing--it's too late, for this case, right?
Quoted 7 lines
Well, if PMD is getting freed, all PTEs in it should be cleared by that time, shouldn't they? So although we read from stale PMD, either we already see cleared PTE or the check
pte_val(pte)!=pte_val(*ptep)will fail and so we never actually succeed in getting stale PTE entry (at least unless the page table page that used to be PMD can get freed and reused
- which is not the case in the example you've shown above).
Right, that's not what the example shows, but there is nothing here to prevent the page table pages from being freed and re-used.
Quoted 4 lines
So I still don't see a problem. That being said I don't feel being expert in this particular area. I just always thought GUP prevents races like this by the scheme I describe so I'd like to see what I'm missing :).
I'm very much still "in training" here, so I hope I'm not wasting everyone's time. But I feel confident in stating at least this much:
There are two distinct lockless synchronization mechanisms here, each protecting against a different issue, and it's important not to conflate them and think that one protects against the other. I still see a hole in (2) below. The mechanisms are:
1) Protection against a page (not the page table itself) getting freed while get_user_pages*() is trying to take a reference to it. This is avoided by the try-get and the re-checking of pte values that you mention above. It's an elegant little thing, too. :)
2) Protection against page tables getting freed while a get_user_pages_fast() call is in progress. This relies on disabling interrupts in gup_fast(), while firing interrupts in the freeing path (via tlb flushing, IPIs). And on memory barriers (doh--missing!) to avoid leaking memory references outside of the irq disabling.
This one has a problem, because Documentation/memory-barriers.txt points out:
INTERRUPT DISABLING FUNCTIONS
-----------------------------
Functions that disable interrupts (ACQUIRE equivalent) and enable interrupts (RELEASE equivalent) will act as compiler barriers only. So if memory or I/O barriers are required in such a situation, they must be provided from some other means.
...and so I'm suggesting that we need something approximately like this:
mm/gup.c +2 -0
@@ -2415,7 +2415,9 @@ int get_user_pages_fast(unsigned long start, int nr_pages,if (IS_ENABLED (CONFIG_HAVE_FAST_GUP ) &&gup_fast_permitted (start, end)) {local_irq_disable (); +smp_mb ();gup_pgd_range (addr, end, gup_flags, pages, &nr); +smp_mb ();local_irq_enable (); ret = nr;
thanks,
Signature · 2 lines
John Hubbard NVIDIA
Re: [PATCH v3 3/4] mm: don't expose non-hugetlb page to fast gup prematurely
Quoted 40 lines · John Hubbard
Quoted 12 lines · Jan Kara
Quoted 10 lines · John Hubbard
Quoted 4 lines · Jan Kara
Quoted 3 lines · John Hubbard
Quoted 2 lines · Michal Hocko
Quoted 1 line · John Hubbard
On 9/26/19 3:20 AM, Kirill A. Shutemov wrote:
...
2. Your code above is after the "pmd =READ_ONCE(*pmdp)" line, so by then, it's already found the pte based on reading a stale pmd. So checking the pte seems like it's checking the wrong thing--it's too late, for this case, right?Quoted 6 lines
Well, if PMD is getting freed, all PTEs in it should be cleared by that time, shouldn't they? So although we read from stale PMD, either we already see cleared PTE or the check
pte_val(pte)!=pte_val(*ptep)will fail and so we never actually succeed in getting stale PTE entry (at least unless the page table page that used to be PMD can get freed and reused
- which is not the case in the example you've shown above).Right, that's not what the example shows, but there is nothing here to prevent the page table pages from being freed and re-used.
Quoted 3 lines
So I still don't see a problem. That being said I don't feel being expert in this particular area. I just always thought GUP prevents races like this by the scheme I describe so I'd like to see what I'm missing :).
I'm very much still "in training" here, so I hope I'm not wasting everyone's time. But I feel confident in stating at least this much:
There are two distinct lockless synchronization mechanisms here, each protecting against a different issue, and it's important not to conflate them and think that one protects against the other. I still see a hole in
(2) below. The mechanisms are:1) Protection against a page (not the page table itself) getting freed while get_user_pages*() is trying to take a reference to it. This is avoided by the try-get and the re-checking of pte values that you mention above. It's an elegant little thing, too. :)
Quoted 5 lines
2) Protection against page tables getting freed while a
get_user_pages_fast()call is in progress. This relies on disabling interrupts ingup_fast(), while firing interrupts in the freeing path (via tlb flushing, IPIs). And on memory barriers (doh--missing!) to avoid leaking memory references outside of the irq disabling.
OK, so you are concerned that the page table walk of pgd->p4d->pud->pmd will get prefetched by the CPU before interrupts are disabled, somewhere in the middle of the walk IPI flushing TLBs on the cpu will happen allowing munmap() on another CPU to proceed and free page tables so the rest of the walk will happen on freed and possibly reused pages. Do I understand you right?
Realistically, I don't think this can happen as I'd expect the CPU to throw away the speculation state on interrupt. But that's just my expectation and I agree that I don't see anything in Documentation/memory-barriers.txt that would prevent what you are concerned about. Let's ask Paul :)
Paul, we are discussing here a possible races between mm/gup.c:__get_user_pages_fast() and mm/mmap.c:unmap_region(). The first has a code like:
local_irq_save(flags);
load pgd from current->mm
load p4d from pgd
load pud from p4d
load pmd from pud
...local_irq_restore(flags);
while the second has code like:
unmap_region()
walk pgd
walk p4d
walk pud
walk pmd
clear ptes
flush tlb
free page tables
Now the serialization between these two relies on the fact that flushing TLBs from unmap_region() requires IPI to be served on each CPU so in naive understanding unmap_region() shouldn't be able to get to 'free unused page tables' part until __get_user_pages_fast() enables interrupts again. Now as John points out we don't see anything in Documentation/memory-barriers.txt that would actually guarantee this. So do we really need something like smp_rmb() after disabling interrupts in __get_user_pages_fast() or is the race John is concerned about impossible? Thanks!
Quoted 18 lines
This one has a problem, because
Documentation/memory-barriers.txtpoints out:INTERRUPT DISABLING FUNCTIONS
-----------------------------Functions that disable interrupts (ACQUIRE equivalent) and enable interrupts (RELEASE equivalent) will act as compiler barriers only. So if memory or I/O barriers are required in such a situation, they must be provided from some other means.
...and so I'm suggesting that we need something approximately like this:
mm/gup.c +0
-0
mm/gup.c int get_user_pages_fast(unsigned long start, int nr_pages, · line 2422
Quoted 8 lines
mm/gup.c +2
-0@@ -2415,7 +2415,9 @@ int get_user_pages_fast(unsigned long start, int nr_pages,if (IS_ENABLED (CONFIG_HAVE_FAST_GUP ) &&gup_fast_permitted (start, end)) {local_irq_disable (); +smp_mb ();gup_pgd_range (addr, end, gup_flags, pages, &nr); +smp_mb ();local_irq_enable (); ret = nr;
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
John Hubbard
reviewerRe: [PATCH v3 3/4] mm: don't expose non-hugetlb page to fast gup prematurely
Quoted 8 lines · Jan Kara
OK, so you are concerned that the page table walk of
pgd->p4d->pud->pmdwill get prefetched by the CPU before interrupts are disabled, somewhere in the middle of the walk IPI flushing TLBs on the cpu will happen allowingmunmap()on another CPU to proceed and free page tables so the rest of the walk will happen on freed and possibly reused pages. Do I understand you right?
Yes, that's it.
thanks,
Signature · 2 lines
John Hubbard NVIDIA
Quoted 66 lines
Realistically, I don't think this can happen as I'd expect the CPU to throw away the speculation state on interrupt. But that's just my expectation and I agree that I don't see anything in
Documentation/memory-barriers.txtthat would prevent what you are concerned about. Let's ask Paul :)Paul, we are discussing here a possible races between mm/gup.c:__get_user_pages_fast() and mm/mmap.c:unmap_region(). The first has a code like:
local_irq_save(flags);
load pgd fromcurrent->mm
load p4d from pgd
load pud from p4d
load pmd from pud
...local_irq_restore(flags);while the second has code like:
unmap_region()
walk pgd
walk p4d
walk pud
walk pmd
clear ptes
flush tlb
free page tablesNow the serialization between these two relies on the fact that flushing TLBs from
unmap_region()requires IPI to be served on each CPU so in naive understandingunmap_region()shouldn't be able to get to 'free unused page tables' part until__get_user_pages_fast()enables interrupts again. Now as John points out we don't see anything inDocumentation/memory-barriers.txtthat would actually guarantee this. So do we really need something likesmp_rmb()after disabling interrupts in__get_user_pages_fast()or is the race John is concerned about impossible? Thanks!Quoted 18 lines
This one has a problem, because
Documentation/memory-barriers.txtpoints out:INTERRUPT DISABLING FUNCTIONS
-----------------------------Functions that disable interrupts (ACQUIRE equivalent) and enable interrupts (RELEASE equivalent) will act as compiler barriers only. So if memory or I/O barriers are required in such a situation, they must be provided from some other means.
...and so I'm suggesting that we need something approximately like this:
mm/gup.c +0
-0mm/gup.c int get_user_pages_fast(unsigned long start, int nr_pages, · line 2422
Quoted 8 lines
mm/gup.c +2
-0@@ -2415,7 +2415,9 @@ int get_user_pages_fast(unsigned long start, int nr_pages,if (IS_ENABLED (CONFIG_HAVE_FAST_GUP ) &&gup_fast_permitted (start, end)) {local_irq_disable (); +smp_mb ();gup_pgd_range (addr, end, gup_flags, pages, &nr); +smp_mb ();local_irq_enable (); ret = nr;Honza
PATCH v3 4/4 mm: remove unnecessary smp_wmb() in __SetPageUptodate()
In reply to Yu Zhaosmp_wmb()s added in the previous patch guarantee that the user data appears before a page is exposed by set_pte_at(). So there is no need for __SetPageUptodate() to have a built-in one.
There are total 13 __SetPageUptodate() for the non-hugetlb case. 12 of them reuse smp_wmb()s added in the previous patch.
The one in shmem_mfill_atomic_pte() doesn't need a explicit write barrier because of the following shmem_add_to_page_cache().
--- include/linux/page-flags.h | 6 +++++- kernel/events/uprobes.c | 2 +- mm/huge_memory.c | 11 +++-------- mm/khugepaged.c | 2 +- mm/memory.c | 13 ++++--------- mm/migrate.c | 7 +------ mm/swapfile.c | 2 +- mm/userfaultfd.c | 7 +------ 8 files changed, 17 insertions(+), 33 deletions(-)
include/linux/page-flags.h +5 -1
@@ -508,10 +508,14 @@ static inline int PageUptodate(struct page *page)return ret; } +/* +* Only use this function when there is a following write barrier, e.g., +* an explicit smp_wmb() and/or the page will be added to page or swap +* cache locked. +*/ static __always_inline void __SetPageUptodate(struct page *page) {VM_BUG_ON_PAGE (PageTail (page), page); -smp_wmb ();__set_bit (PG_uptodate, &page->flags); }
kernel/events/uprobes.c +1 -1
@@ -194,7 +194,7 @@ static int __replace_page(struct vm_area_struct *vma, unsigned long addr,flush_cache_page (vma, addr,pte_pfn (*pvmw.pte));ptep_clear_flush_notify (vma, addr, pvmw.pte); -/* commit non-atomic ops before exposing to fast gup */ +/* commit non-atomic ops and user data */ smp_wmb ();set_pte_at_notify (mm, addr, pvmw.pte,mk_pte (new_page, vma->vm_page_prot));
mm/huge_memory.c +3 -8
@@ -580,11 +580,6 @@ static vm_fault_t __do_huge_pmd_anonymous_page(struct vm_fault *vmf, }clear_huge_page (page, vmf->address,HPAGE_PMD_NR ); -/* -* The memory barrier inside __SetPageUptodate makes sure that -* clear_huge_page writes become visible before the set_pmd_at() -* write. -*/ __SetPageUptodate (page); vmf->ptl =pmd_lock (vma->vm_mm, vmf->pmd);
@@ -616,7 +611,7 @@ static vm_fault_t __do_huge_pmd_anonymous_page(struct vm_fault *vmf,mem_cgroup_commit_charge (page, memcg, false, true);lru_cache_add_active_or_unevictable (page, vma);pgtable_trans_huge_deposit (vma->vm_mm, vmf->pmd, pgtable); -/* commit non-atomic ops before exposing to fast gup */ +/* commit non-atomic ops and user data */ smp_wmb ();set_pmd_at (vma->vm_mm, haddr, vmf->pmd, entry);add_mm_counter (vma->vm_mm,MM_ANONPAGES ,HPAGE_PMD_NR );
@@ -1278,7 +1273,7 @@ static vm_fault_t do_huge_pmd_wp_page_fallback(struct vm_fault *vmf, }kfree (pages); -/* commit non-atomic ops before exposing to fast gup */ +/* commit non-atomic ops and user data */ smp_wmb ();/* make pte visible before pmd */ pmd_populate (vma->vm_mm, vmf->pmd, pgtable);
@@ -1427,7 +1422,7 @@ vm_fault_t do_huge_pmd_wp_page(struct vm_fault *vmf, pmd_t orig_pmd)page_add_new_anon_rmap (new_page, vma, haddr, true);mem_cgroup_commit_charge (new_page, memcg, false, true);lru_cache_add_active_or_unevictable (new_page, vma); -/* commit non-atomic ops before exposing to fast gup */ +/* commit non-atomic ops and user data */ smp_wmb ();set_pmd_at (vma->vm_mm, haddr, vmf->pmd, entry);update_mmu_cache_pmd (vma, vmf->address, vmf->pmd);
mm/khugepaged.c +1 -1
@@ -1074,7 +1074,7 @@ static void collapse_huge_page(struct mm_struct *mm,count_memcg_events (memcg,THP_COLLAPSE_ALLOC ,1 );lru_cache_add_active_or_unevictable (new_page, vma);pgtable_trans_huge_deposit (mm, pmd, pgtable); -/* commit non-atomic ops before exposing to fast gup */ +/* commit non-atomic ops and user data */ smp_wmb ();set_pmd_at (mm, address, pmd, _pmd);update_mmu_cache_pmd (vma, address, pmd);
mm/memory.c +4 -9
@@ -2367,7 +2367,7 @@ static vm_fault_t wp_page_copy(struct vm_fault *vmf)* mmu page tables (such as kvm shadow page tables), we want the * new page to be mapped directly into the secondary page table. */ -/* commit non-atomic ops before exposing to fast gup */ +/* commit non-atomic ops and user data */ smp_wmb ();set_pte_at_notify (mm, vmf->address, vmf->pte, entry);update_mmu_cache (vma, vmf->address, vmf->pte);
@@ -2887,7 +2887,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)page_add_new_anon_rmap (page, vma, vmf->address, false);mem_cgroup_commit_charge (page, memcg, false, false);lru_cache_add_active_or_unevictable (page, vma); -/* commit non-atomic ops before exposing to fast gup */ +/* commit non-atomic ops and user data */ smp_wmb (); }else {do_page_add_anon_rmap (page, vma, vmf->address, exclusive);
@@ -3006,11 +3006,6 @@ static vm_fault_t do_anonymous_page(struct vm_fault *vmf) false)) goto oom_free_page; -/* -* The memory barrier inside __SetPageUptodate makes sure that -* preceeding stores to the page contents become visible before -* the set_pte_at() write. -*/ __SetPageUptodate (page); entry =mk_pte (page, vma->vm_page_prot);
@@ -3038,7 +3033,7 @@ static vm_fault_t do_anonymous_page(struct vm_fault *vmf)page_add_new_anon_rmap (page, vma, vmf->address, false);mem_cgroup_commit_charge (page, memcg, false, false);lru_cache_add_active_or_unevictable (page, vma); -/* commit non-atomic ops before exposing to fast gup */ +/* commit non-atomic ops and user data */ smp_wmb (); setpte:set_pte_at (vma->vm_mm, vmf->address, vmf->pte, entry);
@@ -3303,7 +3298,7 @@ vm_fault_t alloc_set_pte(struct vm_fault *vmf, struct mem_cgroup *memcg,page_add_new_anon_rmap (page, vma, vmf->address, false);mem_cgroup_commit_charge (page, memcg, false, false);lru_cache_add_active_or_unevictable (page, vma); -/* commit non-atomic ops before exposing to fast gup */ +/* commit non-atomic ops and user data */ smp_wmb (); }else {inc_mm_counter_fast (vma->vm_mm,mm_counter_file (page));
mm/migrate.c +1 -6
@@ -2729,11 +2729,6 @@ static void migrate_vma_insert_page(struct migrate_vma *migrate,if (mem_cgroup_try_charge (page, vma->vm_mm,GFP_KERNEL , &memcg, false)) goto abort; -/* -* The memory barrier inside __SetPageUptodate makes sure that -* preceding stores to the page contents become visible before -* the set_pte_at() write. -*/ __SetPageUptodate (page);if (is_zone_device_page (page)) {
@@ -2783,7 +2778,7 @@ static void migrate_vma_insert_page(struct migrate_vma *migrate,lru_cache_add_active_or_unevictable (page, vma);get_page (page); -/* commit non-atomic ops before exposing to fast gup */ +/* commit non-atomic ops and user data */ smp_wmb ();if (flush) {flush_cache_page (vma, addr,pte_pfn (*ptep));
mm/swapfile.c +1 -1
@@ -1887,7 +1887,7 @@ static int unuse_pte(struct vm_area_struct *vma, pmd_t *pmd,page_add_new_anon_rmap (page, vma, addr, false);mem_cgroup_commit_charge (page, memcg, false, false);lru_cache_add_active_or_unevictable (page, vma); -/* commit non-atomic ops before exposing to fast gup */ +/* commit non-atomic ops and user data */ smp_wmb (); } set_pte_at(vma->vm_mm, addr, pte,
mm/userfaultfd.c +1 -6
@@ -58,11 +58,6 @@ static int mcopy_atomic_pte(struct mm_struct *dst_mm, *pagep =NULL ; } -/* -* The memory barrier inside __SetPageUptodate makes sure that -* preceeding stores to the page contents become visible before -* the set_pte_at() write. -*/ __SetPageUptodate (page); ret = -ENOMEM ;
@@ -92,7 +87,7 @@ static int mcopy_atomic_pte(struct mm_struct *dst_mm,mem_cgroup_commit_charge (page, memcg, false, false);lru_cache_add_active_or_unevictable (page, dst_vma); -/* commit non-atomic ops before exposing to fast gup */ +/* commit non-atomic ops and user data */ smp_wmb ();set_pte_at (dst_mm, dst_addr, dst_pte, _dst_pte);
Signature · 1 line
2.23.0.351.gc4317032e6-goog
Re: [PATCH v3 4/4] mm: remove unnecessary smp_wmb() in __SetPageUptodate()
include/linux/page-flags.h static inline int PageUptodate(struct page *page) · line 520
Quoted 11 lines · Yu Zhao
include/linux/page-flags.h +5
-1+/* +* Only use this function when there is a following write barrier, e.g., +* an explicit smp_wmb() and/or the page will be added to page or swap +* cache locked. +*/ static __always_inline void __SetPageUptodate(struct page *page) {VM_BUG_ON_PAGE (PageTail (page), page); -smp_wmb ();__set_bit (PG_uptodate, &page->flags); }
Isn't this now the same as __SETPAGEFLAG(uptodate, Uptodate, PF_NO_TAIL)?
Re: [PATCH v3 4/4] mm: remove unnecessary smp_wmb() in __SetPageUptodate()
Quoted 14 lines · Matthew Wilcox
include/linux/page-flags.h static inline int PageUptodate(struct page *page) · line 520
Quoted 11 lines · Yu Zhao
include/linux/page-flags.h +5
-1+/* +* Only use this function when there is a following write barrier, e.g., +* an explicit smp_wmb() and/or the page will be added to page or swap +* cache locked. +*/ static __always_inline void __SetPageUptodate(struct page *page) {VM_BUG_ON_PAGE (PageTail (page), page); -smp_wmb ();__set_bit (PG_uptodate, &page->flags); }Isn't this now the same as
__SETPAGEFLAG(uptodate, Uptodate, PF_NO_TAIL)?
Indeed. I'll use the macro in the next version.