mm-fix-pageanonexclusive-clearing-racing-with-concurrent-rcu-gup-fast.patch removed from -mm tree
The quilt patch titled
Subject: mm: fix PageAnonExclusive clearing racing with concurrent RCU GUP-fast
has been removed from the -mm tree. Its filename was
mm-fix-pageanonexclusive-clearing-racing-with-concurrent-rcu-gup-fast.patch
This patch was dropped because it was merged into the mm-stable branch of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm
------------------------------------------------------
From: David Hildenbrand <[email protected]>
Subject: mm: fix PageAnonExclusive clearing racing with concurrent RCU GUP-fast
Date: Thu, 1 Sep 2022 10:35:59 +0200
commit 6c287605fd56 ("mm: remember exclusively mapped anonymous pages with PG_anon_exclusive") made sure that when PageAnonExclusive() has to be cleared during temporary unmapping of a page, that the PTE is cleared/invalidated and that the TLB is flushed.
What we want to achieve in all cases is that we cannot end up with a pin on an anonymous page that may be shared, because such pins would be unreliable and could result in memory corruptions when the mapped page and the pin go out of sync due to a write fault.
That TLB flush handling was inspired by an outdated comment in mm/ksm.c:write_protect_page(), which similarly required the TLB flush in the past to synchronize with GUP-fast. However, ever since general RCU GUP fast was introduced in commit 2667f50e8b81 ("mm: introduce a general RCU get_user_pages_fast()"), a TLB flush is no longer sufficient to handle concurrent GUP-fast in all cases -- it only handles traditional IPI-based GUP-fast correctly.
Peter Xu (thankfully) questioned whether that TLB flush is really required. On architectures that send an IPI broadcast on TLB flush, it works as expected. To synchronize with RCU GUP-fast properly, we're conceptually fine, however, we have to enforce a certain memory order and are missing memory barriers.
Let's document that, avoid the TLB flush where possible and use proper explicit memory barriers where required. We shouldn't really care about the additional memory barriers here, as we're not on extremely hot paths -- and we're getting rid of some TLB flushes.
We use a smp_mb() pair for handling concurrent pinning and a smp_rmb()/smp_wmb() pair for handling the corner case of only temporary PTE changes but permanent PageAnonExclusive changes.
One extreme example, whereby GUP-fast takes a R/O pin and KSM wants to convert an exclusive anonymous page to a KSM page, and that page is already mapped write-protected (-> no PTE change) would be:
Thread 0 (KSM) Thread 1 (GUP-fast)
(B1) Read the PTE
# (B2) skipped without FOLL_WRITE
(A1) Clear PTE
smp_mb()
(A2) Check pinned
(B3) Pin the mapped page smp_mb()
(A3) Clear PageAnonExclusive
smp_wmb()
(A4) Restore PTE
(B4) Check if the PTE changed smp_rmb()
(B5) Check PageAnonExclusive
Thread 1 will properly detect that PageAnonExclusive was cleared and back off.
Note that we don't need a memory barrier between checking if the page is pinned and clearing PageAnonExclusive, because stores are not speculated.
The possible issues due to reordering are of theoretical nature so far and attempts to reproduce the race failed.
Especially the "no PTE change" case isn't the common case, because we'd need an exclusive anonymous page that's mapped R/O and the PTE is clean in KSM code -- and using KSM with page pinning isn't extremely common. Further, the clear+TLB flush we used for now implies a memory barrier. So the problematic missing part should be the missing memory barrier after pinning but before checking if the PTE changed.
---
include/linux/mm.h | 9 ++++- include/linux/rmap.h | 66 +++++++++++++++++++++++++++++++++++++---- mm/gup.c | 7 ++++ mm/huge_memory.c | 3 + mm/ksm.c | 1 mm/rmap.c | 11 +++--- 6 files changed, 85 insertions(+), 12 deletions(-)
include/linux/mm.h renamed from include/linux/mm.h~mm-fix-pageanonexclusive-clearing-racing-with-concurrent-rcu-gup-fast +7 -2
@@ -2999,8 +2999,8 @@ static inline int vm_fault_to_errno(vm_f* PageAnonExclusive() has to protect against concurrent GUP: * * Ordinary GUP: Using the PT lock * * GUP-fast and fork(): mm->write_protect_seq -* * GUP-fast and KSM or temporary unmapping (swap, migration): -* clear/invalidate+flush of the page table entry +* * GUP-fast and KSM or temporary unmapping (swap, migration): see +* page_try_share_anon_rmap() * * Must be called with the (sub)page that's actually referenced via the * page table entry, which might not necessarily be the head page for a
@@ -3021,6 +3021,11 @@ static inline bool gup_must_unshare(unsi*/ if (!PageAnon (page))return false; + +/* Paired with a memory barrier in page_try_share_anon_rmap(). */ +if (IS_ENABLED (CONFIG_HAVE_FAST_GUP )) +smp_rmb (); +/* * Note that PageKsm() pages cannot be exclusive, and consequently, * cannot get pinned.
include/linux/rmap.h renamed from include/linux/rmap.h~mm-fix-pageanonexclusive-clearing-racing-with-concurrent-rcu-gup-fast +61 -5
80 lines not on this page · raw message
mm/gup.c renamed from mm/gup.c~mm-fix-pageanonexclusive-clearing-racing-with-concurrent-rcu-gup-fast +7 -0
@@ -158,6 +158,13 @@ struct folio *try_grab_folio(struct pageelse folio_ref_add (folio, refs * (GUP_PIN_COUNTING_BIAS -1 )); +/* +* Adjust the pincount before re-checking the PTE for changes. +* This is essentially a smp_mb() and is paired with a memory +* barrier in page_try_share_anon_rmap(). +*/ +smp_mb__after_atomic (); +node_stat_mod_folio (folio,NR_FOLL_PIN_ACQUIRED , refs);return folio;
mm/huge_memory.c renamed from mm/huge_memory.c~mm-fix-pageanonexclusive-clearing-racing-with-concurrent-rcu-gup-fast +3 -0
@@ -2148,6 +2148,8 @@ static void __split_huge_pmd_locked(stru* * In case we cannot clear PageAnonExclusive(), split the PMD * only and let try_to_migrate_one() fail later. +* +* See page_try_share_anon_rmap(): invalidate PMD first. */ anon_exclusive =PageAnon (page) &&PageAnonExclusive (page);if (freeze && anon_exclusive &&page_try_share_anon_rmap (page))
@@ -3181,6 +3183,7 @@ int set_pmd_migration_entry(struct page_flush_cache_range (vma, address, address +HPAGE_PMD_SIZE ); pmdval =pmdp_invalidate (vma, address, pvmw->pmd); +/* See page_try_share_anon_rmap(): invalidate PMD first. */ anon_exclusive =PageAnon (page) &&PageAnonExclusive (page);if (anon_exclusive &&page_try_share_anon_rmap (page)) {set_pmd_at (mm, address, pvmw->pmd, pmdval);
mm/ksm.c renamed from mm/ksm.c~mm-fix-pageanonexclusive-clearing-racing-with-concurrent-rcu-gup-fast +1 -0
@@ -1095,6 +1095,7 @@ static int write_protect_page(struct vm_ goto out_unlock; } +/* See page_try_share_anon_rmap(): clear PTE first. */ if (anon_exclusive &&page_try_share_anon_rmap (page)) {set_pte_at (mm, pvmw.address, pvmw.pte, entry); goto out_unlock;
mm/rmap.c renamed from mm/rmap.c~mm-fix-pageanonexclusive-clearing-racing-with-concurrent-rcu-gup-fast +6 -5
@@ -1574,11 +1574,8 @@ static bool try_to_unmap_one(struct foli pteval =huge_ptep_clear_flush (vma, address, pvmw.pte); }else {flush_cache_page (vma, address,pte_pfn (*pvmw.pte)); -/* -* Nuke the page table entry. When having to clear -* PageAnonExclusive(), we always have to flush. -*/ -if (should_defer_flush (mm, flags) && !anon_exclusive) { +/* Nuke the page table entry. */ +if (should_defer_flush (mm, flags)) {/* * We clear the PTE but do not flush so potentially * a remote CPU could still be writing to the folio.
@@ -1709,6 +1706,8 @@ static bool try_to_unmap_one(struct folipage_vma_mapped_walk_done (&pvmw);break ; } + +/* See page_try_share_anon_rmap(): clear PTE first. */ if (anon_exclusive &&page_try_share_anon_rmap (subpage)) {swap_free (entry);
@@ -2040,6 +2039,8 @@ static bool try_to_migrate_one(struct fo }VM_BUG_ON_PAGE (pte_write (pteval) &&folio_test_anon (folio) && !anon_exclusive, subpage); + +/* See page_try_share_anon_rmap(): clear PTE first. */ if (anon_exclusive &&page_try_share_anon_rmap (subpage)) {if (folio_test_hugetlb (folio))
_
Patches currently in -mm which might be from [email protected] are
mm-gup-replace-foll_numa-by-gup_can_follow_protnone.patch mm-gup-use-gup_can_follow_protnone-also-in-gup-fast.patch mm-fixup-documentation-regarding-pte_numa-and-prot_numa.patch