mm/mremap: move remap_is_valid() into check_prep_vma()

Group parameter check logic together, moving check_mremap_params() next to
it.

This puts all such checks into a single place, and invokes them early so
we can simply bail out as soon as we are aware that a condition is not
met.

No functional change intended.

Link: https://lkml.kernel.org/r/4d0669c23531629d8ead42aa701c6237bd6bf012.1752770784.git.lorenzo.stoakes@oracle.com
Signed-off-by: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
Reviewed-by: Vlastimil Babka <vbabka@suse.cz>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Christian Brauner <brauner@kernel.org>
Cc: Jan Kara <jack@suse.cz>
Cc: Jann Horn <jannh@google.com>
Cc: Liam Howlett <liam.howlett@oracle.com>
Cc: Peter Xu <peterx@redhat.com>
Cc: Rik van Riel <riel@surriel.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
This commit is contained in:
Lorenzo Stoakes 2025-07-17 17:55:57 +01:00 committed by Andrew Morton
parent a85dc37186
commit 9b2301bf8d

View File

@ -1306,71 +1306,6 @@ static unsigned long move_vma(struct vma_remap_struct *vrm)
return err ? (unsigned long)err : vrm->new_addr;
}
/*
* remap_is_valid() - Ensure the VMA can be moved or resized to the new length,
* at the given address.
*
* Return 0 on success, error otherwise.
*/
static int remap_is_valid(struct vma_remap_struct *vrm)
{
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma = vrm->vma;
unsigned long addr = vrm->addr;
unsigned long old_len = vrm->old_len;
unsigned long new_len = vrm->new_len;
unsigned long pgoff;
/*
* !old_len is a special case where an attempt is made to 'duplicate'
* a mapping. This makes no sense for private mappings as it will
* instead create a fresh/new mapping unrelated to the original. This
* is contrary to the basic idea of mremap which creates new mappings
* based on the original. There are no known use cases for this
* behavior. As a result, fail such attempts.
*/
if (!old_len && !(vma->vm_flags & (VM_SHARED | VM_MAYSHARE))) {
pr_warn_once("%s (%d): attempted to duplicate a private mapping with mremap. This is not supported.\n",
current->comm, current->pid);
return -EINVAL;
}
if ((vrm->flags & MREMAP_DONTUNMAP) &&
(vma->vm_flags & (VM_DONTEXPAND | VM_PFNMAP)))
return -EINVAL;
/*
* We permit crossing of boundaries for the range being unmapped due to
* a shrink.
*/
if (vrm->remap_type == MREMAP_SHRINK)
old_len = new_len;
/* We can't remap across vm area boundaries */
if (old_len > vma->vm_end - addr)
return -EFAULT;
if (new_len == old_len)
return 0;
/* Need to be careful about a growing mapping */
pgoff = (addr - vma->vm_start) >> PAGE_SHIFT;
pgoff += vma->vm_pgoff;
if (pgoff + (new_len >> PAGE_SHIFT) < pgoff)
return -EINVAL;
if (vma->vm_flags & (VM_DONTEXPAND | VM_PFNMAP))
return -EFAULT;
if (!mlock_future_ok(mm, vma->vm_flags, vrm->delta))
return -EAGAIN;
if (!may_expand_vm(mm, vma->vm_flags, vrm->delta >> PAGE_SHIFT))
return -ENOMEM;
return 0;
}
/*
* The user has requested that the VMA be shrunk (i.e., old_len > new_len), so
* execute this, optionally dropping the mmap lock when we do so.
@ -1497,77 +1432,6 @@ static bool vrm_can_expand_in_place(struct vma_remap_struct *vrm)
return true;
}
/*
* Are the parameters passed to mremap() valid? If so return 0, otherwise return
* error.
*/
static unsigned long check_mremap_params(struct vma_remap_struct *vrm)
{
unsigned long addr = vrm->addr;
unsigned long flags = vrm->flags;
/* Ensure no unexpected flag values. */
if (flags & ~(MREMAP_FIXED | MREMAP_MAYMOVE | MREMAP_DONTUNMAP))
return -EINVAL;
/* Start address must be page-aligned. */
if (offset_in_page(addr))
return -EINVAL;
/*
* We allow a zero old-len as a special case
* for DOS-emu "duplicate shm area" thing. But
* a zero new-len is nonsensical.
*/
if (!vrm->new_len)
return -EINVAL;
/* Is the new length or address silly? */
if (vrm->new_len > TASK_SIZE ||
vrm->new_addr > TASK_SIZE - vrm->new_len)
return -EINVAL;
/* Remainder of checks are for cases with specific new_addr. */
if (!vrm_implies_new_addr(vrm))
return 0;
/* The new address must be page-aligned. */
if (offset_in_page(vrm->new_addr))
return -EINVAL;
/* A fixed address implies a move. */
if (!(flags & MREMAP_MAYMOVE))
return -EINVAL;
/* MREMAP_DONTUNMAP does not allow resizing in the process. */
if (flags & MREMAP_DONTUNMAP && vrm->old_len != vrm->new_len)
return -EINVAL;
/* Target VMA must not overlap source VMA. */
if (vrm_overlaps(vrm))
return -EINVAL;
/*
* move_vma() need us to stay 4 maps below the threshold, otherwise
* it will bail out at the very beginning.
* That is a problem if we have already unmaped the regions here
* (new_addr, and old_addr), because userspace will not know the
* state of the vma's after it gets -ENOMEM.
* So, to avoid such scenario we can pre-compute if the whole
* operation has high chances to success map-wise.
* Worst-scenario case is when both vma's (new_addr and old_addr) get
* split in 3 before unmapping it.
* That means 2 more maps (1 for each) to the ones we already hold.
* Check whether current map count plus 2 still leads us to 4 maps below
* the threshold, otherwise return -ENOMEM here to be more safe.
*/
if ((current->mm->map_count + 2) >= sysctl_max_map_count - 3)
return -ENOMEM;
return 0;
}
/*
* We know we can expand the VMA in-place by delta pages, so do so.
*
@ -1719,9 +1583,26 @@ static bool vrm_will_map_new(struct vma_remap_struct *vrm)
return false;
}
static void notify_uffd(struct vma_remap_struct *vrm, bool failed)
{
struct mm_struct *mm = current->mm;
/* Regardless of success/failure, we always notify of any unmaps. */
userfaultfd_unmap_complete(mm, vrm->uf_unmap_early);
if (failed)
mremap_userfaultfd_fail(vrm->uf);
else
mremap_userfaultfd_complete(vrm->uf, vrm->addr,
vrm->new_addr, vrm->old_len);
userfaultfd_unmap_complete(mm, vrm->uf_unmap);
}
static int check_prep_vma(struct vma_remap_struct *vrm)
{
struct vm_area_struct *vma = vrm->vma;
struct mm_struct *mm = current->mm;
unsigned long addr = vrm->addr;
unsigned long old_len, new_len, pgoff;
if (!vma)
return -EFAULT;
@ -1738,26 +1619,134 @@ static int check_prep_vma(struct vma_remap_struct *vrm)
vrm->remap_type = vrm_remap_type(vrm);
/* For convenience, we set new_addr even if VMA won't move. */
if (!vrm_implies_new_addr(vrm))
vrm->new_addr = vrm->addr;
vrm->new_addr = addr;
if (vrm_will_map_new(vrm))
return remap_is_valid(vrm);
/* Below only meaningful if we expand or move a VMA. */
if (!vrm_will_map_new(vrm))
return 0;
old_len = vrm->old_len;
new_len = vrm->new_len;
/*
* !old_len is a special case where an attempt is made to 'duplicate'
* a mapping. This makes no sense for private mappings as it will
* instead create a fresh/new mapping unrelated to the original. This
* is contrary to the basic idea of mremap which creates new mappings
* based on the original. There are no known use cases for this
* behavior. As a result, fail such attempts.
*/
if (!old_len && !(vma->vm_flags & (VM_SHARED | VM_MAYSHARE))) {
pr_warn_once("%s (%d): attempted to duplicate a private mapping with mremap. This is not supported.\n",
current->comm, current->pid);
return -EINVAL;
}
if ((vrm->flags & MREMAP_DONTUNMAP) &&
(vma->vm_flags & (VM_DONTEXPAND | VM_PFNMAP)))
return -EINVAL;
/*
* We permit crossing of boundaries for the range being unmapped due to
* a shrink.
*/
if (vrm->remap_type == MREMAP_SHRINK)
old_len = new_len;
/* We can't remap across vm area boundaries */
if (old_len > vma->vm_end - addr)
return -EFAULT;
if (new_len == old_len)
return 0;
/* Need to be careful about a growing mapping */
pgoff = (addr - vma->vm_start) >> PAGE_SHIFT;
pgoff += vma->vm_pgoff;
if (pgoff + (new_len >> PAGE_SHIFT) < pgoff)
return -EINVAL;
if (vma->vm_flags & (VM_DONTEXPAND | VM_PFNMAP))
return -EFAULT;
if (!mlock_future_ok(mm, vma->vm_flags, vrm->delta))
return -EAGAIN;
if (!may_expand_vm(mm, vma->vm_flags, vrm->delta >> PAGE_SHIFT))
return -ENOMEM;
return 0;
}
static void notify_uffd(struct vma_remap_struct *vrm, bool failed)
{
struct mm_struct *mm = current->mm;
/*
* Are the parameters passed to mremap() valid? If so return 0, otherwise return
* error.
*/
static unsigned long check_mremap_params(struct vma_remap_struct *vrm)
/* Regardless of success/failure, we always notify of any unmaps. */
userfaultfd_unmap_complete(mm, vrm->uf_unmap_early);
if (failed)
mremap_userfaultfd_fail(vrm->uf);
else
mremap_userfaultfd_complete(vrm->uf, vrm->addr,
vrm->new_addr, vrm->old_len);
userfaultfd_unmap_complete(mm, vrm->uf_unmap);
{
unsigned long addr = vrm->addr;
unsigned long flags = vrm->flags;
/* Ensure no unexpected flag values. */
if (flags & ~(MREMAP_FIXED | MREMAP_MAYMOVE | MREMAP_DONTUNMAP))
return -EINVAL;
/* Start address must be page-aligned. */
if (offset_in_page(addr))
return -EINVAL;
/*
* We allow a zero old-len as a special case
* for DOS-emu "duplicate shm area" thing. But
* a zero new-len is nonsensical.
*/
if (!vrm->new_len)
return -EINVAL;
/* Is the new length or address silly? */
if (vrm->new_len > TASK_SIZE ||
vrm->new_addr > TASK_SIZE - vrm->new_len)
return -EINVAL;
/* Remainder of checks are for cases with specific new_addr. */
if (!vrm_implies_new_addr(vrm))
return 0;
/* The new address must be page-aligned. */
if (offset_in_page(vrm->new_addr))
return -EINVAL;
/* A fixed address implies a move. */
if (!(flags & MREMAP_MAYMOVE))
return -EINVAL;
/* MREMAP_DONTUNMAP does not allow resizing in the process. */
if (flags & MREMAP_DONTUNMAP && vrm->old_len != vrm->new_len)
return -EINVAL;
/* Target VMA must not overlap source VMA. */
if (vrm_overlaps(vrm))
return -EINVAL;
/*
* move_vma() need us to stay 4 maps below the threshold, otherwise
* it will bail out at the very beginning.
* That is a problem if we have already unmaped the regions here
* (new_addr, and old_addr), because userspace will not know the
* state of the vma's after it gets -ENOMEM.
* So, to avoid such scenario we can pre-compute if the whole
* operation has high chances to success map-wise.
* Worst-scenario case is when both vma's (new_addr and old_addr) get
* split in 3 before unmapping it.
* That means 2 more maps (1 for each) to the ones we already hold.
* Check whether current map count plus 2 still leads us to 4 maps below
* the threshold, otherwise return -ENOMEM here to be more safe.
*/
if ((current->mm->map_count + 2) >= sysctl_max_map_count - 3)
return -ENOMEM;
return 0;
}
static unsigned long do_mremap(struct vma_remap_struct *vrm)