Commit 6767d019 authored by Jan Kara's avatar Jan Kara Committed by Ben Hutchings

ext4: fix data corruption for mmap writes

commit a056bdaa upstream.

mpage_submit_page() can race with another process growing i_size and
writing data via mmap to the written-back page. As mpage_submit_page()
samples i_size too early, it may happen that ext4_bio_write_page()
zeroes out too large tail of the page and thus corrupts user data.

Fix the problem by sampling i_size only after the page has been
write-protected in page tables by clear_page_dirty_for_io() call.
Reported-by: default avatarMichael Zimmer <michael@swarm64.com>
Fixes: cb20d518Signed-off-by: default avatarJan Kara <jack@suse.cz>
Signed-off-by: default avatarTheodore Ts'o <tytso@mit.edu>
[bwh: Backported to 3.16: adjust context]
Signed-off-by: default avatarBen Hutchings <ben@decadent.org.uk>
parent b56427ec
...@@ -1994,15 +1994,29 @@ static int ext4_writepage(struct page *page, ...@@ -1994,15 +1994,29 @@ static int ext4_writepage(struct page *page,
static int mpage_submit_page(struct mpage_da_data *mpd, struct page *page) static int mpage_submit_page(struct mpage_da_data *mpd, struct page *page)
{ {
int len; int len;
loff_t size = i_size_read(mpd->inode); loff_t size;
int err; int err;
BUG_ON(page->index != mpd->first_page); BUG_ON(page->index != mpd->first_page);
clear_page_dirty_for_io(page);
/*
* We have to be very careful here! Nothing protects writeback path
* against i_size changes and the page can be writeably mapped into
* page tables. So an application can be growing i_size and writing
* data through mmap while writeback runs. clear_page_dirty_for_io()
* write-protects our page in page tables and the page cannot get
* written to again until we release page lock. So only after
* clear_page_dirty_for_io() we are safe to sample i_size for
* ext4_bio_write_page() to zero-out tail of the written page. We rely
* on the barrier provided by TestClearPageDirty in
* clear_page_dirty_for_io() to make sure i_size is really sampled only
* after page tables are updated.
*/
size = i_size_read(mpd->inode);
if (page->index == size >> PAGE_CACHE_SHIFT) if (page->index == size >> PAGE_CACHE_SHIFT)
len = size & ~PAGE_CACHE_MASK; len = size & ~PAGE_CACHE_MASK;
else else
len = PAGE_CACHE_SIZE; len = PAGE_CACHE_SIZE;
clear_page_dirty_for_io(page);
err = ext4_bio_write_page(&mpd->io_submit, page, len, mpd->wbc, false); err = ext4_bio_write_page(&mpd->io_submit, page, len, mpd->wbc, false);
if (!err) if (!err)
mpd->wbc->nr_to_write--; mpd->wbc->nr_to_write--;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment