diff options
author | David Howells <dhowells@redhat.com> | 2010-01-15 20:01:39 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2010-01-16 15:15:40 -0500 |
commit | 7e6608724c640924aad1d556d17df33ebaa6124d (patch) | |
tree | b2d90646f5ee52f5b7807bbc0abe09db9adbe478 /fs | |
parent | 81759b5b221107488bda99fe7beeb7b734f61133 (diff) |
nommu: fix shared mmap after truncate shrinkage problems
Fix a problem in NOMMU mmap with ramfs whereby a shared mmap can happen
over the end of a truncation. The problem is that
ramfs_nommu_check_mappings() checks that the reduced file size against the
VMA tree, but not the vm_region tree.
The following sequence of events can cause the problem:
fd = open("/tmp/x", O_RDWR|O_TRUNC|O_CREAT, 0600);
ftruncate(fd, 32 * 1024);
a = mmap(NULL, 32 * 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
b = mmap(NULL, 16 * 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
munmap(a, 32 * 1024);
ftruncate(fd, 16 * 1024);
c = mmap(NULL, 32 * 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
Mapping 'a' creates a vm_region covering 32KB of the file. Mapping 'b'
sees that the vm_region from 'a' is covering the region it wants and so
shares it, pinning it in memory.
Mapping 'a' then goes away and the file is truncated to the end of VMA
'b'. However, the region allocated by 'a' is still in effect, and has
_not_ been reduced.
Mapping 'c' is then created, and because there's a vm_region covering the
desired region, get_unmapped_area() is _not_ called to repeat the check,
and the mapping is granted, even though the pages from the latter half of
the mapping have been discarded.
However:
d = mmap(NULL, 16 * 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
Mapping 'd' should work, and should end up sharing the region allocated by
'a'.
To deal with this, we shrink the vm_region struct during the truncation,
lest do_mmap_pgoff() take it as licence to share the full region
automatically without calling the get_unmapped_area() file op again.
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>
Diffstat (limited to 'fs')
-rw-r--r-- | fs/ramfs/file-nommu.c | 31 |
1 files changed, 1 insertions, 30 deletions
diff --git a/fs/ramfs/file-nommu.c b/fs/ramfs/file-nommu.c index 266531343aae..1739a4aba25f 100644 --- a/fs/ramfs/file-nommu.c +++ b/fs/ramfs/file-nommu.c | |||
@@ -123,35 +123,6 @@ add_error: | |||
123 | 123 | ||
124 | /*****************************************************************************/ | 124 | /*****************************************************************************/ |
125 | /* | 125 | /* |
126 | * check that file shrinkage doesn't leave any VMAs dangling in midair | ||
127 | */ | ||
128 | static int ramfs_nommu_check_mappings(struct inode *inode, | ||
129 | size_t newsize, size_t size) | ||
130 | { | ||
131 | struct vm_area_struct *vma; | ||
132 | struct prio_tree_iter iter; | ||
133 | |||
134 | down_write(&nommu_region_sem); | ||
135 | |||
136 | /* search for VMAs that fall within the dead zone */ | ||
137 | vma_prio_tree_foreach(vma, &iter, &inode->i_mapping->i_mmap, | ||
138 | newsize >> PAGE_SHIFT, | ||
139 | (size + PAGE_SIZE - 1) >> PAGE_SHIFT | ||
140 | ) { | ||
141 | /* found one - only interested if it's shared out of the page | ||
142 | * cache */ | ||
143 | if (vma->vm_flags & VM_SHARED) { | ||
144 | up_write(&nommu_region_sem); | ||
145 | return -ETXTBSY; /* not quite true, but near enough */ | ||
146 | } | ||
147 | } | ||
148 | |||
149 | up_write(&nommu_region_sem); | ||
150 | return 0; | ||
151 | } | ||
152 | |||
153 | /*****************************************************************************/ | ||
154 | /* | ||
155 | * | 126 | * |
156 | */ | 127 | */ |
157 | static int ramfs_nommu_resize(struct inode *inode, loff_t newsize, loff_t size) | 128 | static int ramfs_nommu_resize(struct inode *inode, loff_t newsize, loff_t size) |
@@ -169,7 +140,7 @@ static int ramfs_nommu_resize(struct inode *inode, loff_t newsize, loff_t size) | |||
169 | 140 | ||
170 | /* check that a decrease in size doesn't cut off any shared mappings */ | 141 | /* check that a decrease in size doesn't cut off any shared mappings */ |
171 | if (newsize < size) { | 142 | if (newsize < size) { |
172 | ret = ramfs_nommu_check_mappings(inode, newsize, size); | 143 | ret = nommu_shrink_inode_mappings(inode, size, newsize); |
173 | if (ret < 0) | 144 | if (ret < 0) |
174 | return ret; | 145 | return ret; |
175 | } | 146 | } |