Skip to contentSkip to conversation

mremap fix/cleanups

PATCH In review Oleg Nesterov 3 participants 8 messages Jul 1, 2015 – Jul 15, 2015

Subsystems (5)
lore
Share & tools
lore
#0 lore raw

v2 cover mremap fix/cleanups

Actually this is resend, not v2. Added the acks I got from David (thanks!).

2/5 (acked by Pavel and Kirill) was sent separately and ignored too, I think it fits this series.

(and I think that the recently added arch_remap() hook is not
the right thing, we can remove it with these changes).

Andrew, should I resend the

	[PATCH 0/3] special_mapping_fault() is broken
	http://marc.info/?l=linux-kernel&m=143492093907585

series too?

Oleg.

#1 lore raw

PATCH v2 1/5 mremap: don't leak new_vma if f_op->mremap() fails

move_vma() can't just return if f_op->mremap() fails, we should unmap the new vma like we do if move_page_tables() fails. To avoid the code duplication this patch moves the "move entries back" under the new "if (err)" branch.

---
 mm/mremap.c |   16 +++++++---------
 1 files changed, 7 insertions(+), 9 deletions(-)
mm/mremap.c +7 -9
index a7c93ec..f54a43f 100644
@@ -276,6 +276,12 @@ static unsigned long move_vma(struct vm_area_struct *vma,
 	moved_len = move_page_tables(vma, old_addr, new_vma, new_addr, old_len,
 				     need_rmap_locks);
 	if (moved_len < old_len) {
+		err = -ENOMEM;
+	} else if (vma->vm_file && vma->vm_file->f_op->mremap) {
+		err = vma->vm_file->f_op->mremap(vma->vm_file, new_vma);
+	}
+
+	if (unlikely(err)) {
 		/*
 		 * On error, move entries back from new area to old,
 		 * which will succeed since page tables still there,
@@ -286,16 +292,8 @@ static unsigned long move_vma(struct vm_area_struct *vma,
 		vma = new_vma;
 		old_len = new_len;
 		old_addr = new_addr;
-		new_addr = -ENOMEM;
+		new_addr = err;
 	} else {
-		if (vma->vm_file && vma->vm_file->f_op->mremap) {
-			err = vma->vm_file->f_op->mremap(vma->vm_file, new_vma);
-			if (err < 0) {
-				move_page_tables(new_vma, new_addr, vma,
-						 old_addr, moved_len, true);
-				return err;
-			}
-		}
 		arch_remap(mm, old_addr, old_addr + old_len,
 			   new_addr, new_addr + new_len);
 	}
Signature · 1 line
1.5.5.1
#2 lore raw

PATCH v2 2/5 mm: move ->mremap() from file_operations to vm_operations_struct

In reply to Oleg Nesterov

vma->vm_ops->mremap() looks more natural and clean in move_vma(), and this way ->mremap() can have more users. Say, vdso.

While at it, s/aio_ring_remap/aio_ring_mremap/.

Note: this is the minimal change before ->mremap() finds another user in file_operations; this method should have more arguments, and it can be used to kill arch_remap().

---
 fs/aio.c           |   25 ++++++++++++++++---------
 include/linux/fs.h |    1 -
 include/linux/mm.h |    1 +
 mm/mremap.c        |    4 ++--
 4 files changed, 19 insertions(+), 12 deletions(-)
fs/aio.c +16 -9
index 480440f..c122624 100644
@@ -308,15 +308,9 @@ static void aio_free_ring(struct kioctx *ctx)
 	}
 }
 
-static int aio_ring_mmap(struct file *file, struct vm_area_struct *vma)
-{
-	vma->vm_flags |= VM_DONTEXPAND;
-	vma->vm_ops = &generic_file_vm_ops;
-	return 0;
-}
-
-static int aio_ring_remap(struct file *file, struct vm_area_struct *vma)
+static int aio_ring_mremap(struct vm_area_struct *vma)
 {
+	struct file *file = vma->vm_file;
 	struct mm_struct *mm = vma->vm_mm;
 	struct kioctx_table *table;
 	int i, res = -EINVAL;
@@ -342,9 +336,22 @@ static int aio_ring_remap(struct file *file, struct vm_area_struct *vma)
 	return res;
 }
 
+static const struct vm_operations_struct aio_ring_vm_ops = {
+	.mremap		= aio_ring_mremap,
+	.fault		= filemap_fault,
+	.map_pages	= filemap_map_pages,
+	.page_mkwrite	= filemap_page_mkwrite,
+};
+
+static int aio_ring_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	vma->vm_flags |= VM_DONTEXPAND;
+	vma->vm_ops = &aio_ring_vm_ops;
+	return 0;
+}
+
 static const struct file_operations aio_ring_fops = {
 	.mmap = aio_ring_mmap,
-	.mremap = aio_ring_remap,
 };
 
 #if IS_ENABLED(CONFIG_MIGRATION)
include/linux/fs.h +0 -1
index 35ec87e..42aac09 100644
@@ -1582,7 +1582,6 @@ struct file_operations {
 	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
 	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
 	int (*mmap) (struct file *, struct vm_area_struct *);
-	int (*mremap)(struct file *, struct vm_area_struct *);
 	int (*open) (struct inode *, struct file *);
 	int (*flush) (struct file *, fl_owner_t id);
 	int (*release) (struct inode *, struct file *);
include/linux/mm.h +1 -0
index 0755b9f..bb51ced 100644
@@ -244,6 +244,7 @@ struct vm_fault {
 struct vm_operations_struct {
 	void (*open)(struct vm_area_struct * area);
 	void (*close)(struct vm_area_struct * area);
+	int (*mremap)(struct vm_area_struct * area);
 	int (*fault)(struct vm_area_struct *vma, struct vm_fault *vmf);
 	void (*map_pages)(struct vm_area_struct *vma, struct vm_fault *vmf);
 
mm/mremap.c +2 -2
index f54a43f..3310378 100644
@@ -277,8 +277,8 @@ static unsigned long move_vma(struct vm_area_struct *vma,
 				     need_rmap_locks);
 	if (moved_len < old_len) {
 		err = -ENOMEM;
-	} else if (vma->vm_file && vma->vm_file->f_op->mremap) {
-		err = vma->vm_file->f_op->mremap(vma->vm_file, new_vma);
+	} else if (vma->vm_ops && vma->vm_ops->mremap) {
+		err = vma->vm_ops->mremap(new_vma);
 	}
 
 	if (unlikely(err)) {
Signature · 1 line
1.5.5.1
#3 lore raw

PATCH v2 3/5 mremap: don't do mm_populate(new_addr) on failure

In reply to Oleg Nesterov

move_vma() sets *locked even if move_page_tables() or ->mremap() fails, change sys_mremap() to check "ret & ~PAGE_MASK".

I think we should simply remove the VM_LOCKED code in move_vma(), that is why this patch doesn't change move_vma(). But this needs more cleanups.

---
 mm/mremap.c |    4 +++-
 1 files changed, 3 insertions(+), 1 deletions(-)
mm/mremap.c +3 -1
index 3310378..7dcf7b4 100644
@@ -578,8 +578,10 @@ SYSCALL_DEFINE5(mremap, unsigned long, addr, unsigned long, old_len,
 		ret = move_vma(vma, addr, old_len, new_len, new_addr, &locked);
 	}
 out:
-	if (ret & ~PAGE_MASK)
+	if (ret & ~PAGE_MASK) {
 		vm_unacct_memory(charged);
+		locked = 0;
+	}
 	up_write(&current->mm->mmap_sem);
 	if (locked && new_len > old_len)
 		mm_populate(new_addr + old_len, new_len - old_len);
Signature · 1 line
1.5.5.1
#4 lore raw

PATCH v2 4/5 mremap: don't do uneccesary checks if new_len == old_len

In reply to Oleg Nesterov

The "new_len > old_len" branch in vma_to_resize() looks very confusing. It only covers the VM_DONTEXPAND/pgoff checks but everything below is equally unneeded if new_len == old_len.

Change this code to return if "new_len == old_len", new_len < old_len is not possible, otherwise the code below is wrong anyway.

---
 mm/mremap.c |   21 +++++++++++----------
 1 files changed, 11 insertions(+), 10 deletions(-)
mm/mremap.c +11 -10
index 7dcf7b4..d3f42be 100644
@@ -346,6 +346,7 @@ static struct vm_area_struct *vma_to_resize(unsigned long addr,
 {
 	struct mm_struct *mm = current->mm;
 	struct vm_area_struct *vma = find_vma(mm, addr);
+	unsigned long pgoff;
 
 	if (!vma || vma->vm_start > addr)
 		return ERR_PTR(-EFAULT);
@@ -357,17 +358,17 @@ static struct vm_area_struct *vma_to_resize(unsigned long addr,
 	if (old_len > vma->vm_end - addr)
 		return ERR_PTR(-EFAULT);
 
+	if (new_len == old_len)
+		return vma;
+
 	/* Need to be careful about a growing mapping */
-	if (new_len > old_len) {
-		unsigned long pgoff;
-
-		if (vma->vm_flags & (VM_DONTEXPAND | VM_PFNMAP))
-			return ERR_PTR(-EFAULT);
-		pgoff = (addr - vma->vm_start) >> PAGE_SHIFT;
-		pgoff += vma->vm_pgoff;
-		if (pgoff + (new_len >> PAGE_SHIFT) < pgoff)
-			return ERR_PTR(-EINVAL);
-	}
+	pgoff = (addr - vma->vm_start) >> PAGE_SHIFT;
+	pgoff += vma->vm_pgoff;
+	if (pgoff + (new_len >> PAGE_SHIFT) < pgoff)
+		return ERR_PTR(-EINVAL);
+
+	if (vma->vm_flags & (VM_DONTEXPAND | VM_PFNMAP))
+		return ERR_PTR(-EFAULT);
 
 	if (vma->vm_flags & VM_LOCKED) {
 		unsigned long locked, lock_limit;
Signature · 1 line
1.5.5.1
#5 lore raw

PATCH v2 5/5 mremap: simplify the "overlap" check in mremap_to()

In reply to Oleg Nesterov

Minor, but this check is overcomplicated. Two half-intervals do NOT overlap if END1 <= START2 || END2 <= START1, mremap_to() just needs to negate this check.

---
 mm/mremap.c |    9 ++-------
 1 files changed, 2 insertions(+), 7 deletions(-)
mm/mremap.c +2 -7
index d3f42be..5a71cce 100644
@@ -407,13 +407,8 @@ static unsigned long mremap_to(unsigned long addr, unsigned long old_len,
 	if (new_len > TASK_SIZE || new_addr > TASK_SIZE - new_len)
 		goto out;
 
-	/* Check if the location we're moving into overlaps the
-	 * old location at all, and fail if it does.
-	 */
-	if ((new_addr <= addr) && (new_addr+new_len) > addr)
-		goto out;
-
-	if ((addr <= new_addr) && (addr+old_len) > new_addr)
+	/* Ensure the old/new locations do not overlap */
+	if (addr + old_len > new_addr && new_addr + new_len > addr)
 		goto out;
 
 	ret = do_munmap(mm, new_addr, new_len);
Signature · 1 line
1.5.5.1
#6 lore raw
In reply to Oleg Nesterov
Quoted 9 lines · Oleg Nesterov

Actually this is resend, not v2. Added the acks I got from David (thanks!).

2/5 (acked by Pavel and Kirill) was sent separately and ignored too, I think it fits this series.

(and I think that the recently added arch_remap() hook is not
the right thing, we can remove it with these changes).

This series applies cleanly to next-20150715 and has acks, was it deferred for some reason?

maintainer
#7 lore raw
Quoted 8 lines · On Wed, 15 Jul 2015 16:34:35 -0700 (PDT) David Rientjes <[email protected]>…
Quoted 6 lines · Oleg Nesterov

Actually this is resend, not v2. Added the acks I got from David (thanks!).

2/5 (acked by Pavel and Kirill) was sent separately and ignored too, I think it fits this series.

Quoted 6 lines
Quoted 3 lines

(and I think that the recently added arch_remap() hook is not
the right thing, we can remove it with these changes).

This series applies cleanly to next-20150715 and has acks, was it deferred for some reason?

I applied

mremap-dont-leak-new_vma-if-f_op-mremap-fails.patch mm-move-mremap-from-file_operations-to-vm_operations_struct.patch mremap-dont-do-mm_populatenew_addr-on-failure.patch mremap-dont-do-uneccesary-checks-if-new_len-==-old_len.patch mremap-simplify-the-overlap-check-in-mremap_to.patch

Check spam folder ;)