ext4: fix data corruption for mmap writes
authorJan Kara <jack@suse.cz>
Fri, 26 May 2017 21:45:45 +0000 (17:45 -0400)
committerTheodore Ts'o <tytso@mit.edu>
Fri, 26 May 2017 21:45:45 +0000 (17:45 -0400)
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: Michael Zimmer <michael@swarm64.com>
CC: stable@vger.kernel.org
Fixes: cb20d5188366f04d96d2e07b1240cc92170ade40
Signed-off-by: Jan Kara <jack@suse.cz>
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
fs/ext4/inode.c

index 074aeba78259bad14b282ea50026279d4bccd28c..657a98fa80b672664954ab243de231dc71264bbb 100644 (file)
@@ -2124,15 +2124,29 @@ static int ext4_writepage(struct page *page,
 static int mpage_submit_page(struct mpage_da_data *mpd, struct page *page)
 {
        int len;
 static int mpage_submit_page(struct mpage_da_data *mpd, struct page *page)
 {
        int len;
-       loff_t size = i_size_read(mpd->inode);
+       loff_t size;
        int err;
 
        BUG_ON(page->index != mpd->first_page);
        int err;
 
        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_SHIFT)
                len = size & ~PAGE_MASK;
        else
                len = PAGE_SIZE;
        if (page->index == size >> PAGE_SHIFT)
                len = size & ~PAGE_MASK;
        else
                len = PAGE_SIZE;
-       clear_page_dirty_for_io(page);
        err = ext4_bio_write_page(&mpd->io_submit, page, len, mpd->wbc, false);
        if (!err)
                mpd->wbc->nr_to_write--;
        err = ext4_bio_write_page(&mpd->io_submit, page, len, mpd->wbc, false);
        if (!err)
                mpd->wbc->nr_to_write--;