Commit 9b2301bf authored by Lorenzo Stoakes's avatar Lorenzo Stoakes Committed by Andrew Morton
Browse files

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: default avatarLorenzo Stoakes <lorenzo.stoakes@oracle.com>
Reviewed-by: default avatarVlastimil 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: default avatarAndrew Morton <akpm@linux-foundation.org>
parent a85dc371
Loading
Loading
Loading
Loading
+138 −149
Original line number Diff line number Diff line
@@ -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;

	/* 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 (vrm_will_map_new(vrm))
		return remap_is_valid(vrm);
	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)
/*
 * 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)

{
	struct mm_struct *mm = current->mm;
	unsigned long addr = vrm->addr;
	unsigned long flags = vrm->flags;

	/* 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);
	/* 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)