ext4: Fix buffer double free in ext4_alloc_branch()
[firefly-linux-kernel-4.4.55.git] / fs / ext4 / indirect.c
index 594009f5f523f0fd2228a72f1aa593a6f3ebf66f..f85bafd474dc8cd9dee7d743d48047b50589068a 100644 (file)
@@ -389,7 +389,13 @@ static int ext4_alloc_branch(handle_t *handle, struct inode *inode,
        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],
@@ -639,8 +645,7 @@ out:
  * VFS code falls back into buffered path in that case so we are safe.
  */
 ssize_t ext4_ind_direct_IO(int rw, struct kiocb *iocb,
-                          const struct iovec *iov, loff_t offset,
-                          unsigned long nr_segs)
+                          struct iov_iter *iter, loff_t offset)
 {
        struct file *file = iocb->ki_filp;
        struct inode *inode = file->f_mapping->host;
@@ -648,7 +653,7 @@ ssize_t ext4_ind_direct_IO(int rw, struct kiocb *iocb,
        handle_t *handle;
        ssize_t ret;
        int orphan = 0;
-       size_t count = iov_length(iov, nr_segs);
+       size_t count = iov_iter_count(iter);
        int retries = 0;
 
        if (rw == WRITE) {
@@ -687,18 +692,17 @@ retry:
                        goto locked;
                }
                ret = __blockdev_direct_IO(rw, iocb, inode,
-                                inode->i_sb->s_bdev, iov,
-                                offset, nr_segs,
+                                inode->i_sb->s_bdev, iter, offset,
                                 ext4_get_block, NULL, NULL, 0);
                inode_dio_done(inode);
        } else {
 locked:
-               ret = blockdev_direct_IO(rw, iocb, inode, iov,
-                                offset, nr_segs, ext4_get_block);
+               ret = blockdev_direct_IO(rw, iocb, inode, iter,
+                                offset, ext4_get_block);
 
                if (unlikely((rw & WRITE) && ret < 0)) {
                        loff_t isize = i_size_read(inode);
-                       loff_t end = offset + iov_length(iov, nr_segs);
+                       loff_t end = offset + count;
 
                        if (end > isize)
                                ext4_truncate_failed_write(inode);