1 ext4: fix data corruption in data=journal mode
3 From: Jan Kara <jack@suse.cz>
5 ext4_journalled_write_end() did not propely handle all the cases when
6 generic_perform_write() did not copy all the data into the target page
7 and could mark buffers with uninitialized contents as uptodate and dirty
8 leading to possible data corruption (which would be quickly fixed by
9 generic_perform_write() retrying the write but still). Fix the problem
10 by carefully handling the case when the page that is written to is not
13 CC: stable@vger.kernel.org
14 Reported-by: Al Viro <viro@ZenIV.linux.org.uk>
15 Signed-off-by: Jan Kara <jack@suse.cz>
16 Signed-off-by: Theodore Ts'o <tytso@mit.edu>
18 fs/ext4/inode.c | 23 +++++++++++++----------
19 1 file changed, 13 insertions(+), 10 deletions(-)
21 diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
22 index 88d57af1b516..4bb9136b0221 100644
25 @@ -1385,7 +1385,9 @@ static int ext4_write_end(struct file *file,
26 * set the buffer to be dirty, since in data=journalled mode we need
27 * to call ext4_handle_dirty_metadata() instead.
29 -static void zero_new_buffers(struct page *page, unsigned from, unsigned to)
30 +static void ext4_journalled_zero_new_buffers(handle_t *handle,
32 + unsigned from, unsigned to)
34 unsigned int block_start = 0, block_end;
35 struct buffer_head *head, *bh;
36 @@ -1402,7 +1404,7 @@ static void zero_new_buffers(struct page *page, unsigned from, unsigned to)
37 size = min(to, block_end) - start;
39 zero_user(page, start, size);
40 - set_buffer_uptodate(bh);
41 + write_end_fn(handle, bh);
45 @@ -1434,15 +1436,16 @@ static int ext4_journalled_write_end(struct file *file,
46 if (ext4_has_inline_data(inode))
47 copied = ext4_write_inline_data_end(inode, pos, len,
51 - if (!PageUptodate(page))
53 - zero_new_buffers(page, from+copied, to);
56 + else if (unlikely(copied < len) && !PageUptodate(page)) {
58 + ext4_journalled_zero_new_buffers(handle, page, from, to);
60 + if (unlikely(copied < len))
61 + ext4_journalled_zero_new_buffers(handle, page,
63 ret = ext4_walk_page_buffers(handle, page_buffers(page), from,
64 - to, &partial, write_end_fn);
65 + from + copied, &partial,
68 SetPageUptodate(page);