Re: [PATCH] mm/arm: pgtable: remove young bit check for pte_valid_user

Russell King (Oracle)
2026-04-09T14:43:59Z

On Thu, Apr 09, 2026 at 02:56:53PM +0100, Will Deacon wrote:
> On Thu, Apr 09, 2026 at 03:54:45PM +0300, Brian Ruley wrote:
> > Fixes cache desync, which can cause undefined instruction,
> > translation and permission faults under heavy memory use.
> >
> > This is an old bug introduced in commit 1971188aa196 ("ARM: 7985/1: mm:
> > implement pte_accessible for faulting mappings"), which included a check
> > for the young bit of a PTE. The underlying assumption was that old pages
> > are not cached, therefore, `__sync_icache_dcache' could be skipped
> > entirely.
> >
> > However, under extreme memory pressure, page migrations happen
> > frequently and the assumption of uncached "old" pages does not hold.
> > Especially for systems that do not have swap, the migrated pages are
> > unequivocally marked old. This presents a problem, as it is possible
> > for the original page to be immediately mapped to another VA that
> > happens to share the same cache index in VIPT I-cache (we found this
> > bug on Cortex-A9). Without cache invalidation, the CPU will see the
> > old mapping whose physical page can now be used for a different
> > purpose, as illustrated below:
> >
> > Core Physical Memory
> > +-------------------------------+ +------------------+
> > | TLB | | |
> > | VA_A 0xb6e6f -> pfn_q | | pfn_q: code |
> > +-------------------------------+ +------------------+
> > | I-cache |
> > | set[VA_A bits] | tag=pfn_q |
> > +-------------------------------+
> >
> > migrate (kcompactd):
> > 1. copy pfn_q --> pfn_r
> > 2. free pfn_q
> > 3. pte: VA_a -> pfn_r
> > 4. pte_mkold(pte) --> !young
> > 5. ICIALLUIS skipped (because !young)
> >
> > pfn_src reused (OOM pressure):
> > pte: VA_B -> pfn_q (different code)
> >
> > bug:
> > Core Physical Memory
> > +-------------------------------+ +------------------+
> > | TLB (empty) | | pfn_r: old code |
> > +-------------------------------+ | pfn_q: new code |
> > | I-cache | +------------------+
> > | set[VA_A bits] | tag=pfn_q |<--- wrong instructions
> > +-------------------------------+
>
> (nit: Do you have pfn_r and pfn_q mixed up in the "Physical Memory" box?)

I don't think so. pfn_r contains the code that _was_ in pfn_q before
the migration happened (the migration copied pfn_q to pfn_r).

Then, a short time later, the page for pfn_r is reallocated, with new
code placed into it, and then a new PTE is established for pfn_q -
however, this should be a young PTE (which will be necessary for the
mapping to be visible to userspace), and thus should cause
__sync_icache_dcache() to be called, resulting in __flush_icache_all()
if it is an executable mapping.

If it isn't an executable mapping (because pfn_q isn't code) then we
will skip the __flush_icache_all() for the new mapping.

However, the I-cache for the old PTE that was pfn_r and now is pfn_q
will not be present in the physical page tables. An attempt to execute
code from that mapping should fault, causing the MM to mark that PTE
young, and, as it's executable, it should result in
__flush_icache_all() being called.

Like you, I can't see any issue here.

I think we need to know exactly what happened to the old PTE entry
and the newer PTE entry that was subsequently established on the
lead-up to the undefined instruction exception.