mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-11 04:18:39 +08:00
Fix read/truncate race
do_generic_mapping_read currently samples the i_size at the start and doesn't do so again unless it needs to call ->readpage to load a page. After ->readpage it has to re-sample i_size as a truncate may have caused that page to be filled with zeros, and the read() call should not see these. However there are other activities that might cause ->readpage to be called on a page between the time that do_generic_mapping_read samples i_size and when it finds that it has an uptodate page. These include at least read-ahead and possibly another thread performing a read. So do_generic_mapping_read must sample i_size *after* it has an uptodate page. Thus the current sampling at the start and after a read can be replaced with a sampling before the copy-out. The same change applied to __generic_file_splice_read. Note that this fixes any race with truncate_complete_page, but does not fix a possible race with truncate_partial_page. If a partial truncate happens after do_generic_mapping_read samples i_size and before the copy_out, the nuls that truncate_partial_page place in the page could be copied out incorrectly. I think the best fix for that is to *not* zero out parts of the page in truncate_partial_page, but rather to zero out the tail of a page when increasing i_size. Signed-off-by: Neil Brown <neilb@suse.de> Cc: Jens Axboe <jens.axboe@oracle.com> Acked-by: Nick Piggin <npiggin@suse.de> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
parent
e21ea246bc
commit
a32ea1e1f9
72
mm/filemap.c
72
mm/filemap.c
@ -867,13 +867,11 @@ void do_generic_mapping_read(struct address_space *mapping,
|
||||
{
|
||||
struct inode *inode = mapping->host;
|
||||
unsigned long index;
|
||||
unsigned long end_index;
|
||||
unsigned long offset;
|
||||
unsigned long last_index;
|
||||
unsigned long next_index;
|
||||
unsigned long prev_index;
|
||||
unsigned int prev_offset;
|
||||
loff_t isize;
|
||||
struct page *cached_page;
|
||||
int error;
|
||||
struct file_ra_state ra = *_ra;
|
||||
@ -886,27 +884,12 @@ void do_generic_mapping_read(struct address_space *mapping,
|
||||
last_index = (*ppos + desc->count + PAGE_CACHE_SIZE-1) >> PAGE_CACHE_SHIFT;
|
||||
offset = *ppos & ~PAGE_CACHE_MASK;
|
||||
|
||||
isize = i_size_read(inode);
|
||||
if (!isize)
|
||||
goto out;
|
||||
|
||||
end_index = (isize - 1) >> PAGE_CACHE_SHIFT;
|
||||
for (;;) {
|
||||
struct page *page;
|
||||
unsigned long end_index;
|
||||
loff_t isize;
|
||||
unsigned long nr, ret;
|
||||
|
||||
/* nr is the maximum number of bytes to copy from this page */
|
||||
nr = PAGE_CACHE_SIZE;
|
||||
if (index >= end_index) {
|
||||
if (index > end_index)
|
||||
goto out;
|
||||
nr = ((isize - 1) & ~PAGE_CACHE_MASK) + 1;
|
||||
if (nr <= offset) {
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
nr = nr - offset;
|
||||
|
||||
cond_resched();
|
||||
if (index == next_index)
|
||||
next_index = page_cache_readahead(mapping, &ra, filp,
|
||||
@ -921,6 +904,32 @@ find_page:
|
||||
if (!PageUptodate(page))
|
||||
goto page_not_up_to_date;
|
||||
page_ok:
|
||||
/*
|
||||
* i_size must be checked after we know the page is Uptodate.
|
||||
*
|
||||
* Checking i_size after the check allows us to calculate
|
||||
* the correct value for "nr", which means the zero-filled
|
||||
* part of the page is not copied back to userspace (unless
|
||||
* another truncate extends the file - this is desired though).
|
||||
*/
|
||||
|
||||
isize = i_size_read(inode);
|
||||
end_index = (isize - 1) >> PAGE_CACHE_SHIFT;
|
||||
if (unlikely(!isize || index > end_index)) {
|
||||
page_cache_release(page);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* nr is the maximum number of bytes to copy from this page */
|
||||
nr = PAGE_CACHE_SIZE;
|
||||
if (index == end_index) {
|
||||
nr = ((isize - 1) & ~PAGE_CACHE_MASK) + 1;
|
||||
if (nr <= offset) {
|
||||
page_cache_release(page);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
nr = nr - offset;
|
||||
|
||||
/* If users can be writing to this page using arbitrary
|
||||
* virtual addresses, take care about potential aliasing
|
||||
@ -1007,31 +1016,6 @@ readpage:
|
||||
unlock_page(page);
|
||||
}
|
||||
|
||||
/*
|
||||
* i_size must be checked after we have done ->readpage.
|
||||
*
|
||||
* Checking i_size after the readpage allows us to calculate
|
||||
* the correct value for "nr", which means the zero-filled
|
||||
* part of the page is not copied back to userspace (unless
|
||||
* another truncate extends the file - this is desired though).
|
||||
*/
|
||||
isize = i_size_read(inode);
|
||||
end_index = (isize - 1) >> PAGE_CACHE_SHIFT;
|
||||
if (unlikely(!isize || index > end_index)) {
|
||||
page_cache_release(page);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* nr is the maximum number of bytes to copy from this page */
|
||||
nr = PAGE_CACHE_SIZE;
|
||||
if (index == end_index) {
|
||||
nr = ((isize - 1) & ~PAGE_CACHE_MASK) + 1;
|
||||
if (nr <= offset) {
|
||||
page_cache_release(page);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
nr = nr - offset;
|
||||
goto page_ok;
|
||||
|
||||
readpage_error:
|
||||
|
Loading…
Reference in New Issue
Block a user