| From c5c7b8ddfbf8cb3b2291e515a34ab1b8982f5a2d Mon Sep 17 00:00:00 2001 |
| From: Jan Kara <jack@suse.cz> |
| Date: Sun, 15 Jun 2014 23:46:28 -0400 |
| Subject: ext4: Fix buffer double free in ext4_alloc_branch() |
| |
| From: Jan Kara <jack@suse.cz> |
| |
| commit c5c7b8ddfbf8cb3b2291e515a34ab1b8982f5a2d upstream. |
| |
| Error recovery in ext4_alloc_branch() calls ext4_forget() even for |
| buffer corresponding to indirect block it did not allocate. This leads |
| to brelse() being called twice for that buffer (once from ext4_forget() |
| and once from cleanup in ext4_ind_map_blocks()) leading to buffer use |
| count misaccounting. Eventually (but often much later because there |
| are other users of the buffer) we will see messages like: |
| VFS: brelse: Trying to free free buffer |
| |
| Another manifestation of this problem is an error: |
| JBD2 unexpected failure: jbd2_journal_revoke: !buffer_revoked(bh); |
| inconsistent data on disk |
| |
| The fix is easy - don't forget buffer we did not allocate. Also add an |
| explanatory comment because the indexing at ext4_alloc_branch() is |
| somewhat subtle. |
| |
| Signed-off-by: Jan Kara <jack@suse.cz> |
| Signed-off-by: Theodore Ts'o <tytso@mit.edu> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| fs/ext4/indirect.c | 8 +++++++- |
| 1 file changed, 7 insertions(+), 1 deletion(-) |
| |
| --- a/fs/ext4/indirect.c |
| +++ b/fs/ext4/indirect.c |
| @@ -389,7 +389,13 @@ static int ext4_alloc_branch(handle_t *h |
| return 0; |
| failed: |
| for (; i >= 0; i--) { |
| - if (i != indirect_blks && branch[i].bh) |
| + /* |
| + * We want to ext4_forget() only freshly allocated indirect |
| + * blocks. Buffer for new_blocks[i-1] is at branch[i].bh and |
| + * buffer at branch[0].bh is indirect block / inode already |
| + * existing before ext4_alloc_branch() was called. |
| + */ |
| + if (i > 0 && i != indirect_blks && branch[i].bh) |
| ext4_forget(handle, 1, inode, branch[i].bh, |
| branch[i].bh->b_blocknr); |
| ext4_free_blocks(handle, inode, NULL, new_blocks[i], |