[Devel] [PATCH hci-8.1 6/8] oracle/mm: zap the preserved ranges of the old mm after copying them

Konstantin Khorenko khorenko at virtuozzo.com
Wed Oct 21 19:44:53 MSK 2026


On 10/14/26 19:24, no-reply at sashiko.virtuozzo.com wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] mm: zap_vma_mt() clears old_mm PTEs without vma_start_write()
> --
> 
> commit f34df816bcbe51e2402a6b8ae419dea8ef679f49
> Author: Konstantin Khorenko <khorenko at virtuozzo.com>
> 
> oracle/mm: zap the preserved ranges of the old mm after copying them
> 
> After the VM_EXEC_KEEP ranges are copied into the new mm, this patch zaps
> them from the old mm under its write lock. This is meant to leave the
> copies as the only mappings of the preserved pages. A new zap_vma_mt()
> helper spreads the zap of an anonymous VMA over several threads.
> 
> Link: https://virtuozzo.atlassian.net/browse/VSTOR-150231
> 
>> diff --git a/fs/exec.c b/fs/exec.c
>> --- a/fs/exec.c
>> +++ b/fs/exec.c
> 
> [ ... ]
> 
>> @@ -997,7 +997,15 @@ static int vma_dup_some(struct mm_struct *old_mm, struct mm_struct *new_mm)
>>  		}
>>  
>>  	mmap_write_unlock(new_mm);
>> -	return ret;
>> +	if (ret)
>> +		return ret;
>> +
>> +	/* Whoever keeps old_mm alive past the exec must not reach the pages through it. */
>> +	vma_iter_set(&vmi, 0);
>> +	for_each_vma(vmi, vma)
>> +		if (vma->vm_flags & VM_EXEC_KEEP)
>> +			zap_vma_mt(vma);
>> +	return 0;
>>  }
> 
> [Severity: Medium]
> Can this zap race with a per-VMA lock holder on old_mm?

  No. The old VMA is write-locked before the copy and stays so through
  the zap.

  vma_dup() clears VM_ACCOUNT on the source with
  vm_flags_clear(old_vma, VM_ACCOUNT) before copy_page_range_mt(), and
  vm_flags_clear() starts with vma_start_write(). That waits for the
  per-VMA readers already inside, such as uffd_move_lock() or
  uffd_lock_vma(), and makes lock_vma_under_rcu() fail for new ones until
  the mmap lock is dropped. exec_mmap() keeps old_mm write-locked from
  vma_dup_some() until after zap_vma_mt(), so the lock taken in vma_dup()
  still covers the zap.

  An UFFDIO_MOVE from a task that holds the uffd therefore falls back to
  mmap_read_lock() and waits for the exec. Whatever it does to old_mm
  afterwards no longer reaches the preserved pages: they are zapped by
  then.

  The write lock is a side effect of vm_flags_clear(), though. "mm: allow
  MADV_DOEXEC on shared PFN mappings of device memory" makes that clear
  conditional and adds an explicit vma_start_write(old_vma) for this
  reason.

> 
> The loop calls zap_vma_mt() on old_mm VMAs without calling
> vma_start_write(vma) first:
> 
>     vma_dup_some()
>       for_each_vma(vmi, vma)
>         zap_vma_mt(vma)
> 
> mmap_write_lock() alone does not drain holders that already took the
> per-VMA read lock through lock_vma_under_rcu().
> 
> One such holder is UFFDIO_MOVE:
> 
>     uffd_move_lock()
>       *src_vmap = lock_vma_under_rcu(mm, src_start);
> 
> This path never takes mmap_lock. A task that owns a userfaultfd context
> bound to old_mm has mm_users on old_mm, and its own t->mm is a different
> mm. reserve_preserved_mem() only rejects tasks whose t->mm == mm, so such
> a task is not excluded.
> 
> It could therefore run folio_move_anon_rmap() or a fault in old_mm while
> the zap clears the same PTEs. That would reintroduce the problems the
> commit message describes, since the preserved pages still carry
> PageAnonExclusive in both mms.
> 
> Would it make sense to call vma_start_write(vma) before zapping each
> VM_EXEC_KEEP VMA, either in vma_dup_some() or at the start of
> zap_vma_mt()?
> 
>> diff --git a/mm/memory.c b/mm/memory.c
>> --- a/mm/memory.c
>> +++ b/mm/memory.c
> 
> [ ... ]
> 
>> +void zap_vma_mt(struct vm_area_struct *vma)
>> +{
>> +#ifdef CONFIG_PADATA
>> +	struct padata_mt_job job = {
> 
> [ ... ]
> 
>> +	mmap_assert_write_locked(vma->vm_mm);
>> +	if (vma_is_anonymous(vma)) {
>> +		lru_add_drain();
>> +		mmu_notifier_range_init(&range, MMU_NOTIFY_CLEAR, 0, vma->vm_mm,
>> +					vma->vm_start, vma->vm_end);
>> +		update_hiwater_rss(vma->vm_mm);
>> +		mmu_notifier_invalidate_range_start(&range);
>> +		padata_do_multithreaded(&job);
>> +		mmu_notifier_invalidate_range_end(&range);
>> +		return;
>> +	}
>> +#endif
>> +	zap_page_range_single(vma, vma->vm_start, vma->vm_end - vma->vm_start,
>> +			      NULL);
>> +}
> 
> [Severity: Medium]
> The same concern applies here. zap_vma_mt() only asserts the mmap write
> lock with mmap_assert_write_locked() before clearing PTEs, either in the
> padata worker threads through zap_vma_chunk() or in
> zap_page_range_single().
> 
> It never calls vma_start_write(vma).
> 
> For the anonymous case, can PTEs be cleared while a per-VMA locked fault
> or UFFDIO_MOVE is still operating on the same VMA? Both the padata path
> and the zap_page_range_single() path appear to be exposed.
> 



More information about the Devel mailing list