[Devel] [PATCH vz10 12/24] oracle/mm: enter COW seqcount/mmu-notifier section in copy_page_range_mt()
Pavel Tikhomirov
ptikhomirov at virtuozzo.com
Fri Jul 24 16:09:25 MSK 2026
Reviewed-by: Pavel Tikhomirov <ptikhomirov at virtuozzo.com>
This is really non-obvious, the logic behind "mirroring" is sound, also
we should report this to oracle, I don't see full miroring there, they have
raw_write_seqcount_begin(), but not mmu_notifier_...
On 7/6/26 12:59, Konstantin Khorenko wrote:
> copy_page_range_mt() is the padata-multithreaded copy used by vma_dup()
> to preserve a VM_EXEC_KEEP (MADV_DOEXEC) VMA across exec. A MADV_DOEXEC
> VMA is private anon, so is_cow_mapping() is true and the copy write
> protects the source PTEs - a permission downgrade. Unlike the normal
> fork path copy_page_range(), this helper jumped straight into
> padata_do_multithreaded() without the COW prologue/epilogue, so it:
>
> - never entered src_mm->write_protect_seq (left it even), which trips
> the DEBUG_VM invariant in folio_needs_cow_for_dma() reached from
> folio_try_dup_anon_rmap_pte() during the copy:
>
> kernel BUG at include/linux/mm.h:2054!
> copy_present_ptes <- copy_pte_range <- copy_page_range_chunk
> <- padata_do_multithreaded <- copy_page_range_mt <- vma_dup
> <- exec_mmap <- begin_new_exec <- load_elf_binary
>
> - never called mmu_notifier_invalidate_range_start/end(), a real latent
> bug: a secondary MMU (KVM, IOMMU SVA, GPU) registered on the old mm
> outlives de_thread() and could keep writable mappings of pages whose
> PTEs the exec copy just wrprotected and shared into the new mm.
>
> Mirror copy_page_range(): for a COW mapping, invalidate secondary MMUs
> and take the write_protect_seq write side around the padata job. The
> exec'ing task is single-threaded by now and is the only user of src_mm,
> so there is no concurrent seqcount writer (fork needs mmap_write_lock)
> or GUP-fast reader; taking the raw write side while holding only
> mmap_read_lock is safe, which is why vma_assert_write_locked() from
> copy_page_range() is intentionally not carried over here.
>
> Fixes: 11e4b1dc50e3 ("oracle/mm: use padata for copying page ranges in vma_dup()")
> Feature: oracle/mm: MADV_DOEXEC madvise() flag
> https://virtuozzo.atlassian.net/browse/VSTOR-137234
> Signed-off-by: Konstantin Khorenko <khorenko at virtuozzo.com>
> ---
> mm/memory.c | 35 ++++++++++++++++++++++++++++++++++-
> 1 file changed, 34 insertions(+), 1 deletion(-)
>
> diff --git a/mm/memory.c b/mm/memory.c
> index 837194e87664..4d04591f8f40 100644
> --- a/mm/memory.c
> +++ b/mm/memory.c
> @@ -1478,6 +1478,10 @@ static int copy_page_range_chunk(unsigned long addr,
> int copy_page_range_mt(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma)
> {
> struct copy_page_range_args args = { dst_vma, src_vma };
> + struct mm_struct *src_mm = src_vma->vm_mm;
> + struct mmu_notifier_range range;
> + bool is_cow;
> + int ret;
> struct padata_mt_job job = {
> .thread_fn = copy_page_range_chunk,
> .fn_arg = &args,
> @@ -1490,7 +1494,36 @@ int copy_page_range_mt(struct vm_area_struct *dst_vma, struct vm_area_struct *sr
>
> BUG_ON(!(src_vma->vm_flags & VM_EXEC_KEEP));
>
> - return padata_do_multithreaded(&job);
> + /*
> + * Mirror copy_page_range(): for a COW mapping the copy write
> + * protects the source PTEs, which is a permission downgrade, so
> + * secondary MMUs must be invalidated, and write_protect_seq must be
> + * entered (odd) so GUP-fast cannot race with the wrprotect
> + * (folio_needs_cow_for_dma() asserts this under CONFIG_DEBUG_VM).
> + *
> + * The exec'ing task is single-threaded by now and the only user of
> + * src_mm, so no concurrent write_protect_seq writer (fork, which
> + * needs mmap_write_lock) or GUP-fast reader can exist; taking the
> + * raw write side while holding only mmap_read_lock is safe here, and
> + * the padata helper threads only read the seqcount.
> + */
> + is_cow = is_cow_mapping(src_vma->vm_flags);
> + if (is_cow) {
> + mmu_notifier_range_init(&range, MMU_NOTIFY_PROTECTION_PAGE,
> + 0, src_mm, src_vma->vm_start,
> + src_vma->vm_end);
> + mmu_notifier_invalidate_range_start(&range);
> + raw_write_seqcount_begin(&src_mm->write_protect_seq);
> + }
> +
> + ret = padata_do_multithreaded(&job);
> +
> + if (is_cow) {
> + raw_write_seqcount_end(&src_mm->write_protect_seq);
> + mmu_notifier_invalidate_range_end(&range);
> + }
> +
> + return ret;
> }
> #endif /* CONFIG_PADATA */
>
--
Best regards, Pavel Tikhomirov
Senior Software Developer, Virtuozzo.
More information about the Devel
mailing list