Skip to contentSkip to conversation

simple mmap() cleanups

PATCH Superseded Oleg Nesterov 3 participants 9 messages Jul 14, 2013 – Jul 22, 2013

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

cover simple mmap() cleanups

Hello.

Hopefully the simple cleanups, but need the authoritative acks.

I simply can't understand the VM_GROWS* check in mmap_region(), did I miss something subtle? I will appreciate any review.

Can't resist. "(prot&PROT_WRITE) && !(file->f_mode&FMODE_WRITE)" asks for the whitespace cleanup, but this will break the 80-col rule. Perhaps we should relax it...

Oleg.

 mm/mmap.c |   28 ++++++++++------------------
 1 files changed, 10 insertions(+), 18 deletions(-)
#1 lore raw

PATCH 1/3 mm: shift VM_GROWS* check from mmap_region() to do_mmap_pgoff()

mmap() doesn't allow the non-anonymous mappings with VM_GROWS* bit set. In particular this means that mmap_region()->vma_merge(file, vm_flags) must always fail if vm_flags & VM_GROWS. So it does not make sense to check VM_GROWS* after we already allocated the new vma, the only caller, do_mmap_pgoff(), which can pass this flag can do the check itself.

And this looks a bit more correct, mmap_region() already unmapped the old mapping at this stage. But if mmap() is going to fail, it should avoid do_munmap() if possible.

---
 mm/mmap.c |    9 +++------
 1 files changed, 3 insertions(+), 6 deletions(-)
mm/mmap.c +3 -6
index f681e18..6b69352 100644
@@ -1327,6 +1327,9 @@ unsigned long do_mmap_pgoff(struct file *file, unsigned long addr,
 		}
 	}
 
+	/* Only MAP_PRIVATE|MAP_ANONYMOUS can use MAP_GROWS */
+	if ((vm_flags & VM_MAYSHARE) && (vm_flags & (VM_GROWSDOWN|VM_GROWSUP)))
+		return -EINVAL;
 	/*
 	 * Set 'VM_NORESERVE' if we should not account for the
 	 * memory use of this mapping.
@@ -1542,11 +1545,7 @@ munmap_back:
 	vma->vm_pgoff = pgoff;
 	INIT_LIST_HEAD(&vma->anon_vma_chain);
 
-	error = -EINVAL;	/* when rejecting VM_GROWSDOWN|VM_GROWSUP */
-
 	if (file) {
-		if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP))
-			goto free_vma;
 		if (vm_flags & VM_DENYWRITE) {
 			error = deny_write_access(file);
 			if (error)
@@ -1571,8 +1570,6 @@ munmap_back:
 		pgoff = vma->vm_pgoff;
 		vm_flags = vma->vm_flags;
 	} else if (vm_flags & VM_SHARED) {
-		if (unlikely(vm_flags & (VM_GROWSDOWN|VM_GROWSUP)))
-			goto free_vma;
 		error = shmem_zero_setup(vma);
 		if (error)
 			goto free_vma;
Signature · 1 line
1.5.5.1
maintainer
#2 lore raw

Re: [PATCH 1/3] mm: shift VM_GROWS* check from mmap_region() to do_mmap_pgoff()

Quoted 15 lines · On Sun, 14 Jul 2013 18:54:51 +0200 Oleg Nesterov <[email protected]> wrote:

mmap() doesn't allow the non-anonymous mappings with VM_GROWS* bit set. In particular this means that mmap_region()->vma_merge(file, vm_flags) must always fail if vm_flags & VM_GROWS. So it does not make sense to check VM_GROWS* after we already allocated the new vma, the only caller, do_mmap_pgoff(), which can pass this flag can do the check itself.

And this looks a bit more correct, mmap_region() already unmapped the old mapping at this stage. But if mmap() is going to fail, it should avoid do_munmap() if possible.

...

mm/mmap.c unsigned long do_mmap_pgoff(struct file *file, unsigned long addr, · line 1331

Quoted 5 lines
mm/mmap.c +2 -0
@@ -1327,6 +1327,9 @@ unsigned long do_mmap_pgoff(struct file *file, unsigned long addr,
 		}
 	}
 
+	/* Only MAP_PRIVATE|MAP_ANONYMOUS can use MAP_GROWS */
+	if ((vm_flags & VM_MAYSHARE) && (vm_flags & (VM_GROWSDOWN|VM_GROWSUP)))

That is of course vm_flags&(VM_MAYSHARE|VM_GROWSDOWN|VM_GROWSUP), but that perhaps is less clear.

mm/mmap.c unsigned long do_mmap_pgoff(struct file *file, unsigned long addr, · line 1332

Quoted 1 line
mm/mmap.c +1 -0
+		return -EINVAL;

I had to stare for a while but yes, the change looks OK to me.

#3 lore raw

Re: [PATCH 1/3] mm: shift VM_GROWS* check from mmap_region() to do_mmap_pgoff()

Quoted 5 lines · Andrew Morton
Quoted 3 lines · On Sun, 14 Jul 2013 18:54:51 +0200 Oleg Nesterov <[email protected]> wrote:

mmap() doesn't allow the non-anonymous mappings with VM_GROWS* bit set. In particular this means that mmap_region()->vma_merge(file, vm_flags) must always fail if vm_flags & VM_GROWS.

I didn't understand that sentence: if file is non-NULL perhaps?

Quoted 7 lines
Quoted 7 lines

So it does not make sense to
check VM_GROWS* after we already allocated the new vma, the only caller, do_mmap_pgoff(), which can pass this flag can do the check itself.

And this looks a bit more correct, mmap_region() already unmapped the old mapping at this stage. But if mmap() is going to fail, it should avoid do_munmap() if possible.

I agree with the sentiment, but the patch looks wrong to me.

Quoted 9 lines
Quoted 9 lines

...

mm/mmap.c +0 -0
@@ -1327,6 +1327,9 @@ unsigned long do_mmap_pgoff(struct file *file, unsigned long addr,
 		}
 	}
 
Quoted 4 lines

mm/mmap.c unsigned long do_mmap_pgoff(struct file *file, unsigned long addr, · line 1331

Quoted 2 lines
mm/mmap.c +2 -0
+	/* Only MAP_PRIVATE|MAP_ANONYMOUS can use MAP_GROWS */
+	if ((vm_flags & VM_MAYSHARE) && (vm_flags & (VM_GROWSDOWN|VM_GROWSUP)))

That is of course vm_flags&(VM_MAYSHARE|VM_GROWSDOWN|VM_GROWSUP),

Seems very plausible, but I believe you're wrong on that!

Quoted 5 lines

but that perhaps is less clear.

mm/mmap.c unsigned long do_mmap_pgoff(struct file *file, unsigned long addr, · line 1332

Quoted 1 line
mm/mmap.c +1 -0
+		return -EINVAL;

I had to stare for a while but yes, the change looks OK to me.

It did need staring, yes, but it looks NOK to me: this change permits mmap(addr, len, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_GROWSDOWN, fd, off) where fd is for a real file: we never allowed MAP_GROWSDOWN on private (or shared) mappings of real files before, and I think we should not now.

Hugh

#4 lore raw

v2 cover mm: shift VM_GROWS* check from mmap_region() to do_mmap_pgoff()

Andrew,

This patch replaces the buggy

mm-shift-vm_grows-check-from-mmap_region-to-do_mmap_pgoff.patch

I do not send the *-fix.patch because I'd like to update the changelog.

However it needs the explicit ack from Hugh.

Quoted 9 lines · Hugh Dickins
Quoted 5 lines · Andrew Morton
Quoted 3 lines · On Sun, 14 Jul 2013 18:54:51 +0200 Oleg Nesterov <[email protected]> wrote:

mmap() doesn't allow the non-anonymous mappings with VM_GROWS* bit set. In particular this means that mmap_region()->vma_merge(file, vm_flags) must always fail if vm_flags & VM_GROWS.

I didn't understand that sentence: if file is non-NULL perhaps?

Yes, this looks confusing, sorry.

I meant, vma_merge() must fail if "vm_flags & VM_GROWS" is set incorrectly. is_mergeable_vma() compares both vm_file and vm_flags.

Even if file == NULL (at this stage), "VM_SHARED | VM_GROWS" is not correct too, and vma_merge() can't succeed.

And, to clarify, I only mentioned this because I tried to convince myself that this change (if correct) can't make any difference except "avoid the not-really-correct do_munmap".

Quoted 9 lines
Quoted 7 lines
Quoted 7 lines

So it does not make sense to
check VM_GROWS* after we already allocated the new vma, the only caller, do_mmap_pgoff(), which can pass this flag can do the check itself.

And this looks a bit more correct, mmap_region() already unmapped the old mapping at this stage. But if mmap() is going to fail, it should avoid do_munmap() if possible.

I agree with the sentiment, but the patch looks wrong to me.

Heh. You are right of course.

Quoted 3 lines

It did need staring, yes, but it looks NOK to me: this change permits mmap(addr, len, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_GROWSDOWN, fd, off) where fd is for a real file:

And note that "Only MAP_PRIVATE|MAP_ANONYMOUS can use MAP_GROWS" even tries to document that "MAP_PRIVATE && file" is not allowed too.

I have no idea how I managed to forget that MAP_PRIVATE never sets VM_MAYSHARE.

Thanks a lot Hugh.

Oleg.

#5 lore raw

PATCH v2 1/1 mm: shift VM_GROWS* check from mmap_region() to do_mmap_pgoff()

mmap() doesn't allow the non-anonymous mappings with VM_GROWS* bit set. In particular this means that mmap_region()->vma_merge(file, vm_flags) must always fail if "vm_flags & VM_GROWS" is set incorrectly.

So it does not make sense to check VM_GROWS* after we already allocated the new vma, the only caller, do_mmap_pgoff(), which can pass this flag can do the check itself.

And this looks a bit more correct, mmap_region() already unmapped the old mapping at this stage. But if mmap() is going to fail, it should avoid do_munmap() if possible.

Note: we check VM_GROWS at the end to ensure that do_mmap_pgoff() won't return EINVAL in the case when it currently returns another error code.

Many thanks to Hugh who nacked the buggy v1.

---
 mm/mmap.c |   10 ++++------
 1 files changed, 4 insertions(+), 6 deletions(-)
mm/mmap.c +4 -6
index fbad7b0..92d9f54 100644
@@ -1302,6 +1302,8 @@ unsigned long do_mmap_pgoff(struct file *file, unsigned long addr,
 
 			if (!file->f_op || !file->f_op->mmap)
 				return -ENODEV;
+			if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP))
+				return -EINVAL;
 			break;
 
 		default:
@@ -1310,6 +1312,8 @@ unsigned long do_mmap_pgoff(struct file *file, unsigned long addr,
 	} else {
 		switch (flags & MAP_TYPE) {
 		case MAP_SHARED:
+			if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP))
+				return -EINVAL;
 			/*
 			 * Ignore pgoff.
 			 */
@@ -1544,11 +1548,7 @@ munmap_back:
 	vma->vm_pgoff = pgoff;
 	INIT_LIST_HEAD(&vma->anon_vma_chain);
 
-	error = -EINVAL;	/* when rejecting VM_GROWSDOWN|VM_GROWSUP */
-
 	if (file) {
-		if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP))
-			goto free_vma;
 		if (vm_flags & VM_DENYWRITE) {
 			error = deny_write_access(file);
 			if (error)
@@ -1573,8 +1573,6 @@ munmap_back:
 		pgoff = vma->vm_pgoff;
 		vm_flags = vma->vm_flags;
 	} else if (vm_flags & VM_SHARED) {
-		if (unlikely(vm_flags & (VM_GROWSDOWN|VM_GROWSUP)))
-			goto free_vma;
 		error = shmem_zero_setup(vma);
 		if (error)
 			goto free_vma;
Signature · 1 line
1.5.5.1
Acked-by on v2 1/1
#6 lore raw

Re: [PATCH v2 1/1] mm: shift VM_GROWS* check from mmap_region() to do_mmap_pgoff()

Quoted 8 lines · Oleg Nesterov

mmap() doesn't allow the non-anonymous mappings with VM_GROWS* bit set. In particular this means that mmap_region()->vma_merge(file, vm_flags) must always fail if "vm_flags & VM_GROWS" is set incorrectly.

So it does not make sense to check VM_GROWS* after we already allocated the new vma, the only caller, do_mmap_pgoff(), which can pass this flag can do the check itself.

Quoted 8 lines

And this looks a bit more correct, mmap_region() already unmapped the old mapping at this stage. But if mmap() is going to fail, it should avoid do_munmap() if possible.

Note: we check VM_GROWS at the end to ensure that do_mmap_pgoff() won't return EINVAL in the case when it currently returns another error code.

Many thanks to Hugh who nacked the buggy v1.

Aw shucks, no need for that!

Quoted 2 lines

Looks fine to me now, thanks Oleg; as do your other two.

Quoted 49 lines
---
 mm/mmap.c |   10 ++++------
 1 files changed, 4 insertions(+), 6 deletions(-)
mm/mmap.c +4 -6
index fbad7b0..92d9f54 100644
@@ -1302,6 +1302,8 @@ unsigned long do_mmap_pgoff(struct file *file, unsigned long addr,
 
 			if (!file->f_op || !file->f_op->mmap)
 				return -ENODEV;
+			if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP))
+				return -EINVAL;
 			break;
 
 		default:
@@ -1310,6 +1312,8 @@ unsigned long do_mmap_pgoff(struct file *file, unsigned long addr,
 	} else {
 		switch (flags & MAP_TYPE) {
 		case MAP_SHARED:
+			if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP))
+				return -EINVAL;
 			/*
 			 * Ignore pgoff.
 			 */
@@ -1544,11 +1548,7 @@ munmap_back:
 	vma->vm_pgoff = pgoff;
 	INIT_LIST_HEAD(&vma->anon_vma_chain);
 
-	error = -EINVAL;	/* when rejecting VM_GROWSDOWN|VM_GROWSUP */
-
 	if (file) {
-		if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP))
-			goto free_vma;
 		if (vm_flags & VM_DENYWRITE) {
 			error = deny_write_access(file);
 			if (error)
@@ -1573,8 +1573,6 @@ munmap_back:
 		pgoff = vma->vm_pgoff;
 		vm_flags = vma->vm_flags;
 	} else if (vm_flags & VM_SHARED) {
-		if (unlikely(vm_flags & (VM_GROWSDOWN|VM_GROWSUP)))
-			goto free_vma;
 		error = shmem_zero_setup(vma);
 		if (error)
 			goto free_vma;
Signature · 1 line
1.5.5.1
#7 lore raw

PATCH 2/3 mm: do_mmap_pgoff: cleanup the usage of file_inode()

In reply to Oleg Nesterov

Simple cleanup. Move "struct inode *inode" variable into "if (file)" block to simplify the code and avoid the unnecessary check.

---
 mm/mmap.c |    5 ++---
 1 files changed, 2 insertions(+), 3 deletions(-)
mm/mmap.c +2 -3
index 6b69352..759dce7 100644
@@ -1202,7 +1202,6 @@ unsigned long do_mmap_pgoff(struct file *file, unsigned long addr,
 			unsigned long *populate)
 {
 	struct mm_struct * mm = current->mm;
-	struct inode *inode;
 	vm_flags_t vm_flags;
 
 	*populate = 0;
@@ -1265,9 +1264,9 @@ unsigned long do_mmap_pgoff(struct file *file, unsigned long addr,
 			return -EAGAIN;
 	}
 
-	inode = file ? file_inode(file) : NULL;
-
 	if (file) {
+		struct inode *inode = file_inode(file);
+
 		switch (flags & MAP_TYPE) {
 		case MAP_SHARED:
 			if ((prot&PROT_WRITE) && !(file->f_mode&FMODE_WRITE))
Signature · 1 line
1.5.5.1
#8 lore raw

PATCH 3/3 mm: mmap_region: kill correct_wcount/inode, use allow_write_access()

In reply to Oleg Nesterov

correct_wcount and inode in mmap_region() just complicate the code. This boolean was needed previously, when deny_write_access() was called before vma_merge(), now we can simply check VM_DENYWRITE and do allow_write_access() if it is set.

allow_write_access() checks file != NULL, so this is safe even if it was possible to use VM_DENYWRITE && !file. Just we need to ensure we use the same file which was deny_write_access()'ed, so the patch also moves "file = vma->vm_file" down after allow_write_access().

---
 mm/mmap.c |   14 +++++---------
 1 files changed, 5 insertions(+), 9 deletions(-)
mm/mmap.c +5 -9
index 759dce7..7be923a 100644
@@ -1476,11 +1476,9 @@ unsigned long mmap_region(struct file *file, unsigned long addr,
 {
 	struct mm_struct *mm = current->mm;
 	struct vm_area_struct *vma, *prev;
-	int correct_wcount = 0;
 	int error;
 	struct rb_node **rb_link, *rb_parent;
 	unsigned long charged = 0;
-	struct inode *inode =  file ? file_inode(file) : NULL;
 
 	/* Check against address space limit. */
 	if (!may_expand_vm(mm, len >> PAGE_SHIFT)) {
@@ -1549,7 +1547,6 @@ munmap_back:
 			error = deny_write_access(file);
 			if (error)
 				goto free_vma;
-			correct_wcount = 1;
 		}
 		vma->vm_file = get_file(file);
 		error = file->f_op->mmap(file, vma);
@@ -1590,11 +1587,10 @@ munmap_back:
 	}
 
 	vma_link(mm, vma, prev, rb_link, rb_parent);
-	file = vma->vm_file;
-
 	/* Once vma denies write, undo our temporary denial count */
-	if (correct_wcount)
-		atomic_inc(&inode->i_writecount);
+	if (vm_flags & VM_DENYWRITE)
+		allow_write_access(file);
+	file = vma->vm_file;
 out:
 	perf_event_mmap(vma);
 
@@ -1613,8 +1609,8 @@ out:
 	return addr;
 
 unmap_and_free_vma:
-	if (correct_wcount)
-		atomic_inc(&inode->i_writecount);
+	if (vm_flags & VM_DENYWRITE)
+		allow_write_access(file);
 	vma->vm_file = NULL;
 	fput(file);
 
Signature · 1 line
1.5.5.1