On Tue, Oct 27, 2026 at 04:26:39PM +0000, Lorenzo Stoakes (ARM) wrote: > > Thread A (Parent) | Thread B (Parent) | Thread C (Parent) > ---------------------------|-------------------------|---------------------- > < Fork > | | < has TLB entry > > copy_present_ptes() | < no TLB entry > | > wrprotect parent page X | | > -> 2 mappings, refs | *ptr = x | > | < Page fault > | > | do_wp_page() | > | < shared, so CoW > | > | wp_page_copy() | > | copy X -> new pg N | > | | *ptr = x; > | | [ OOPS! stale TLB, > | | lands in X not N ] > | ptep_clear_flush() | > | < TLB flush > | > | parent PTE -> N | > | [ X now child-only ]| > | | ASSERT(*ptr == x); > flush_tlb_mm(oldmm) | | // fails! N lacks x > < far too late > | | >
Sigh, I drew this entirely manually, then had the LLM overlords 'correct' it, but it also made it less clear :). Thread C ends up writing to what-becomes-the-child-page X, whereas the parent ends up with page N after CoW. So a thread in the parent writes to a page of ordinary memory and then it disappears under them, and the child ends up with corruption in their page. -- Cheers, Lorenzo
