simple mmap() cleanups
Subsystems (2)
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(-)
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
@@ -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
Andrew Morton
maintainerRe: [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 ifvm_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 ifmmap()is going to fail, it should avoiddo_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.
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 ifvm_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 ifmmap()is going to fail, it should avoiddo_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
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 ifvm_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 ifmmap()is going to fail, it should avoiddo_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.
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
@@ -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
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 ifmmap()is going to fail, it should avoiddo_munmap()if possible.Note: we check
VM_GROWSat the end to ensure thatdo_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
Signed-off-by Oleg Nesterov <[email protected]>
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@@ -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
PATCH 2/3 mm: do_mmap_pgoff: cleanup the usage of file_inode()
In reply to Oleg NesterovSimple 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
@@ -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
PATCH 3/3 mm: mmap_region: kill correct_wcount/inode, use allow_write_access()
In reply to Oleg Nesterovcorrect_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
@@ -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