mirror of
https://github.com/torvalds/linux.git
synced 2026-05-29 17:43:52 +02:00
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:
parent
a85dc37186
commit
9b2301bf8d
289
mm/mremap.c
289
mm/mremap.c
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user