All Virtuozzo development lists (kernel + QEMU)
 help / color / mirror / Atom feed
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

  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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.