nommu: fix race between ramfs truncation and shared mmap
authorDavid Howells <dhowells@redhat.com>
Sat, 16 Jan 2010 01:01:36 +0000 (17:01 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sat, 16 Jan 2010 20:15:40 +0000 (12:15 -0800)
Fix the race between the truncation of a ramfs file and an attempt to make
a shared mmap of region of that file.

The problem is that do_mmap_pgoff() calls f_op->get_unmapped_area() to
verify that the file region is made of contiguous pages and to find its
base address - but there isn't any locking to guarantee this region until
vma_prio_tree_insert() is called by add_vma_to_mm().

Note that moving the functionality into f_op->mmap() doesn't help as that
is also called before vma_prio_tree_insert().

Instead make ramfs_nommu_check_mappings() grab nommu_region_sem whilst it
does its checks.  This means that this function will wait whilst mmaps
take place.

Signed-off-by: David Howells <dhowells@redhat.com>
Acked-by: Al Viro <viro@zeniv.linux.org.uk>
Cc: Greg Ungerer <gerg@snapgear.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
fs/ramfs/file-nommu.c

index 2efc57173fd703b74bfc1adaad5289df1ff1f179..266531343aae05bcc5336f05751c44347cceccab 100644 (file)
@@ -131,6 +131,8 @@ static int ramfs_nommu_check_mappings(struct inode *inode,
        struct vm_area_struct *vma;
        struct prio_tree_iter iter;
 
+       down_write(&nommu_region_sem);
+
        /* search for VMAs that fall within the dead zone */
        vma_prio_tree_foreach(vma, &iter, &inode->i_mapping->i_mmap,
                              newsize >> PAGE_SHIFT,
@@ -138,10 +140,13 @@ static int ramfs_nommu_check_mappings(struct inode *inode,
                              ) {
                /* found one - only interested if it's shared out of the page
                 * cache */
-               if (vma->vm_flags & VM_SHARED)
+               if (vma->vm_flags & VM_SHARED) {
+                       up_write(&nommu_region_sem);
                        return -ETXTBSY; /* not quite true, but near enough */
+               }
        }
 
+       up_write(&nommu_region_sem);
        return 0;
 }