Re: [PATCH] Fix incorrect flush address in direct page table reclaim
Michal Hocko <[email protected]> Tue, 4 Aug 2026 09:10:09 +0200
| Newsgroups | gmane.linux.kernel.stable,gmane.linux.kernel.mm |
|---|---|
| Message-ID | <anGQUYvgDYj1Z0ti@tiehlicka> |
On Mon 03-08-26 17:37:08, [email protected] wrote: > From: Andy Lutomirski <[email protected]> > > When zap_pte_range reclaims a page table, it does: > > pte_free_tlb(tlb, pmd_pgtable(pmdval), addr); > > and this is unconditionally wrong: if this code executes, addr *always* > points one past the end of the range covered by the table. The addr > parameter is used to flush the TLB (really the paging-structure-cache) > to drop references to the to-be-freed table, and any architecture that > cares about the parameter will flush the wrong address. (But they'll > still free the correct page). > > I think it's worth contemplating why the kernel works at all. > > If we hit the offending line of code, we will first clear the PMD entry > (line 1954, zap_empty_pte_table), then we will issue pending flushes if > force_flush is set (tlb_flush_mmu_tlbonly(tlb)), then we will skip the > retry on line 1979 (phew!), and then we will do the offending > pte_free_tlb call. *Or* we will clear the PMD entry immediately before > pte_free_tlb (line 1983, zap_pte_table_if_empty). > > If we have any pending flushes (i.e. we actually zapped any last-level > entries) at the time we clear the PMD entry, then the flush really ought > to flush all references to the table (Linus certainly seems to think it > will on all architectures [0]). > > The condition under which we have no accumulated flushes at the time of > the clear is very complex (the whole zap_pte_range function has absurdly > complex control flow). If we do hit the bad case, then we will end up > clearing the PMD entry after the last time the range is flushed, and any > CPU is free to cache a reference to the (empty) page table. If this > happens due to an ordinary read or write, it would segfault, so it would > be rare. But the cache could be speculatively filled as well. Then > we'll flush the wrong address and then free and possibly reuse the > table. > > On x86, even flushing the wrong address works on non-KPTI Intel systems > because INVLPG flushes *all* paging-structure-caches, not just the ones > for the target address. But INVPCID does not, and flush_tlb_one_user > will use INVPCID if it's available. And then we're toast. AMD systems > are more susceptible: we set the EFER.TCE bit, which makes even INVLPG > only flush the target address. > > P.S. IMO zap_pte_range is a mess. The control flow is excessively > complex. The direct_reclaim variable itself has a confused meaning -- > for the first part of the function it means, approximately, "we should > free the table if can_reclaim_pt". But, later on, it means "we ALREADY > reclaimed the table". And the goto retry on line 1979 is IMO just > asking for trouble if the condition ever changes such that it might > happen after clearing the PMD. > > I think this might fix an issue in ripgrep reported here: > https://github.com/BurntSushi/ripgrep/issues/3494 > > [0] https://lore.kernel.org/all/CA+55aFzBggoXtNXQeng5d_mRoDnaMBE5Y+URs+PHR67nUpMtaw@mail.gmail.com/T/#u > > Fixes: 4c640eb4181c ("mm: move pte table reclaim code to memory.c") > Cc: David Hildenbrand (Red Hat) <[email protected]> > Cc: Qi Zheng <[email protected]> > Cc: Liam Howlett <[email protected]> > Cc: "Liam R. Howlett" <[email protected]> > Cc: Lorenzo Stoakes <[email protected]> > Cc: Michal Hocko <[email protected]> > Cc: Mike Rapoport <[email protected]> > Cc: Suren Baghdasaryan <[email protected]> > Cc: Vlastimil Babka <[email protected]> > Cc: Andrew Morton <[email protected]>.org> > Cc: [email protected] > Signed-off-by: Andy Lutomirski <[email protected]> Very well spotted. It was really easy to miss that change during the rework. Acked-by: Michal Hocko <[email protected]> Thanks! > --- > mm/memory.c | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > > diff --git a/mm/memory.c b/mm/memory.c > index 86a973119bd4..13b70861c8a3 100644 > --- a/mm/memory.c > +++ b/mm/memory.c > @@ -1981,7 +1981,7 @@ static unsigned long zap_pte_range(struct mmu_gather *tlb, > > if (can_reclaim_pt) { > if (direct_reclaim || zap_pte_table_if_empty(mm, pmd, start, &pmdval)) { > - pte_free_tlb(tlb, pmd_pgtable(pmdval), addr); > + pte_free_tlb(tlb, pmd_pgtable(pmdval), start); > mm_dec_nr_ptes(mm); > } > } > -- -- Michal Hocko SUSE Labs