From: no-reply@sashiko.virtuozzo.com
To: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>,
Konstantin Khorenko <khorenko@virtuozzo.com>
Cc: devel@openvz.org
Subject: Re: [Devel] [PATCH RFC PATCH hci-8.1 3/4] mm: move MADV_DOEXEC memory instead of copying it
Date: Fri, 9 Oct 2026 15:08:03 +0000 [thread overview]
Message-ID: <010701a12134ffd9-15e9d8a6-e2c7-4488-bbd3-34e6181d87f0-000000@eu-central-1.amazonses.com> (raw)
In-Reply-To: <20261009-vfio_v1-v1-3-1901cd6c3c74@virtuozzo.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] mm: vma_dup() copies vm_file without get_file() for private /dev/zero VMAs
- [Low] mm: vma_dup() discards ksm_madvise() flags so VM_MERGEABLE stays on moved VMA
- [Low] mm: padata workers serialize on anon_vma write lock in move_ptes()
--
commit b89406aa304220e73194db3c04662412c6cbfe44
Author: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
mm: move MADV_DOEXEC memory instead of copying it
This commit changes MADV_DOEXEC preservation across exec from a
fork-style copy to a move of the page tables into the new mm. vma_dup()
now clones the anon_vma chain, unmerges KSM, drops mlock state, and calls
move_page_range_exec(). The parallel copy code and the exec-specific
pinned-page exception in the rmap dup path are removed.
Link: https://virtuozzo.atlassian.net/browse/VSTOR-103860
> diff --git a/mm/mmap.c b/mm/mmap.c
> --- a/mm/mmap.c
> +++ b/mm/mmap.c
[ ... ]
> int vma_dup(struct vm_area_struct *old_vma, struct mm_struct *mm)
> {
> struct vm_area_struct *vma;
> unsigned long npages;
> + vm_flags_t flags = old_vma->vm_flags;
> int ret = -ENOMEM;
[ ... ]
> + /* KSM tracks mappings by mm and address; unmerge before moving them. */
> + ret = ksm_madvise(old_vma, old_vma->vm_start, old_vma->vm_end,
> + MADV_UNMERGEABLE, &flags);
> + if (ret)
> + return ret;
[Severity: Low]
Is the result of this ksm_madvise() call ever applied?
ksm_madvise() clears VM_MERGEABLE only through its vm_flags output
argument:
mm/ksm.c:ksm_madvise() {
...
*vm_flags &= ~VM_MERGEABLE;
...
}
Here that argument is the local variable flags, which is never written
back to old_vma->vm_flags and is not used afterwards.
vm_area_dup() then copies the original vm_flags into the new VMA, and the
later vm_flags_clear() on the new VMA does not include VM_MERGEABLE:
> + vm_flags_clear(vma, VM_LOCKED_MASK | VM_UFFD_MISSING | VM_UFFD_WP |
> + VM_UFFD_MINOR | VM_EXEC_KEEP);
[ ... ]
> + if (!test_bit(MMF_VM_MERGEABLE, &mm->flags))
> + ksm_fork(mm, old_vma->vm_mm);
> + return move_page_range_exec(vma, old_vma);
For a VMA with both MADV_DOEXEC and MADV_MERGEABLE, ksm_fork() registers
the new mm with ksmd while the moved VMA still has VM_MERGEABLE set.
Could ksmd then scan and re-merge pages in the moved VMA?
The commit message says the patch will "unmerge KSM". Is the intent to
leave the moved VMA KSM-eligible, as fork() does, or to disable KSM on it?
If the latter, should VM_MERGEABLE be cleared on the new VMA, for example
by adding it to the vm_flags_clear() call?
> + /*
> + * Transfer the reservation to the linked destination VMA. If moving
> + * page tables fails partway through, exec cannot resume the old image:
> + * it must terminate and tear down both mms. Each mm then frees its
> + * remaining mappings, and the destination releases the reservation.
> */
> - __vm_flags_mod(old_vma, 0, VM_ACCOUNT);
> + vm_flags_clear(old_vma, VM_ACCOUNT);
[ ... ]
> vma = vm_area_dup(old_vma);
> if (!vma)
> goto fail_nomem;
[Severity: Critical]
Can this leave two VMAs holding a single reference on vm_file?
vm_area_dup() memcpy()s the VMA, including vm_file, and takes no file
reference. Nothing later in vma_dup() calls get_file() or clears vm_file,
and vma_link() then calls vma_link_file() on the new VMA.
The MADV_DOEXEC gate in madvise_vma_behavior() only requires
vma_is_anonymous() and rejects VM_EXEC, VM_SHARED and VM_STACK. It does
not reject vm_file != NULL. A private mapping of /dev/zero passes that
gate, because mmap_zero() calls vma_set_anonymous() and leaves vm_file
set:
drivers/char/mem.c:mmap_zero() {
...
if (vma->vm_flags & VM_SHARED)
return shmem_zero_setup(vma);
vma_set_anonymous(vma);
return 0;
}
The call chain would then be:
exec_mmap()
vma_dup_some()
vma_dup()
vm_area_dup() /* copies vm_file, no get_file() */
vma_link() /* new VMA now in the file's i_mmap */
Each mm's teardown then reaches remove_vma():
mm/vma.c:remove_vma() {
...
if (vma->vm_file)
fput(vma->vm_file);
}
That would call fput() twice for one reference. Should vma_dup() either
take get_file() when vma->vm_file is set, or reject VMAs with a vm_file in
the MADV_DOEXEC check?
The old vma_dup() had the same vm_area_dup() path. However, this patch
rewrites the function and builds the new move semantics on it, so it seemed
worth raising here.
> diff --git a/mm/mremap.c b/mm/mremap.c
[ ... ]
> static int move_page_range_chunk(unsigned long addr, unsigned long end, void *arg)
> {
> ...
> return move_page_tables(args->old_vma, addr, args->new_vma, addr,
> len, true, false) == len ? 0 : -ENOMEM;
> }
[Severity: Low]
Is the 16-thread padata job in move_page_range_exec() able to scale here?
Every worker calls move_page_tables() with need_rmap_locks set to true.
For each PMD extent, move_ptes() then does:
move_page_range_chunk()
move_page_tables(..., need_rmap_locks = true, ...)
move_ptes()
take_rmap_locks()
anon_vma_lock_write(vma->anon_vma)
The anon_vma write lock is held across the per-PMD PTE move loop and the
TLB flush, and is dropped only in drop_rmap_locks() at the out label. All
workers operate on the same old_vma, so they take the same anon_vma root
rwsem for write.
Does this mostly serialize the workers, so that padata_do_multithreaded()
with max_threads = 16 gives little speedup over a single thread? I don't
see a correctness problem. Has the performance relative to the old
copy_page_range_mt() path been measured?
--
Sashiko AI review · https://sashiko.vzint.dev/#/patchset/20261009-vfio_v1-v1-0-1901cd6c3c74@virtuozzo.com?part=3
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
next prev parent reply other threads:[~2026-10-09 15:09 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-09 14:00 [Devel] [PATCH RFC hci-8.1 0/4] mm: keep DMA-pinned MADV_DOEXEC memory working across exec Andrey Drobyshev
2026-10-09 14:00 ` [Devel] [PATCH RFC PATCH hci-8.1 1/4] mm: support moving anonymous page tables between address spaces Andrey Drobyshev
2026-10-09 15:02 ` no-reply
2026-10-09 14:00 ` [Devel] [PATCH RFC PATCH hci-8.1 2/4] exec: protect preserved memory against surviving shared-mm tasks Andrey Drobyshev
2026-10-09 14:57 ` no-reply
2026-10-09 14:00 ` [Devel] [PATCH RFC PATCH hci-8.1 3/4] mm: move MADV_DOEXEC memory instead of copying it Andrey Drobyshev
2026-10-09 15:08 ` no-reply [this message]
2026-10-09 14:00 ` [Devel] [PATCH RFC PATCH hci-8.1 4/4] selftests/mm: test memory preservation across exec Andrey Drobyshev
2026-10-09 15:07 ` no-reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=010701a12134ffd9-15e9d8a6-e2c7-4488-bbd3-34e6181d87f0-000000@eu-central-1.amazonses.com \
--to=no-reply@sashiko.virtuozzo.com \
--cc=andrey.drobyshev@virtuozzo.com \
--cc=devel@openvz.org \
--cc=khorenko@virtuozzo.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox