OpenVZ / Virtuozzo kernel development (devel@openvz.org)
 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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox