]> Pileus Git - ~andy/linux/commitdiff
ext4: fix warning in ext4_evict_inode()
authorJan Kara <jack@suse.cz>
Thu, 11 Jul 2013 01:31:04 +0000 (21:31 -0400)
committerTheodore Ts'o <tytso@mit.edu>
Thu, 11 Jul 2013 01:31:04 +0000 (21:31 -0400)
The following race can lead to ext4_evict_inode() seeing i_ioend_count
> 0 and thus triggering a sanity check warning:

        CPU1                                    CPU2
ext4_end_bio()                          ext4_evict_inode()
  ext4_finish_bio()
    end_page_writeback();
                                          truncate_inode_pages()
                                            evict page
                                        WARN_ON(i_ioend_count > 0);
  ext4_put_io_end_defer()
    ext4_release_io_end()
      dec i_ioend_count

This is possible use-after-free bug since we decrement i_ioend_count in
possibly released inode.

Since i_ioend_count is used only for sanity checks one possible solution
would be to just remove it but for now I'd like to keep those sanity
checks to help debugging the new ext4 writeback code.

This patch changes ext4_end_bio() to call ext4_put_io_end_defer() before
ext4_finish_bio() in the shortcut case when unwritten extent conversion
isn't needed.  In that case we don't need the io_end so we are safe to
drop it early.

Reported-by: Guenter Roeck <linux@roeck-us.net>
Tested-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Jan Kara <jack@suse.cz>
Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
fs/ext4/page-io.c

index 48786cdb5e6c8ccc9d94039d34432e8019c3d840..d63cc5e9d3b5d93eaf03463c83a8dc6ab54b2741 100644 (file)
@@ -308,6 +308,7 @@ ext4_io_end_t *ext4_get_io_end(ext4_io_end_t *io_end)
        return io_end;
 }
 
+/* BIO completion function for page writeback */
 static void ext4_end_bio(struct bio *bio, int error)
 {
        ext4_io_end_t *io_end = bio->bi_private;
@@ -318,18 +319,6 @@ static void ext4_end_bio(struct bio *bio, int error)
        if (test_bit(BIO_UPTODATE, &bio->bi_flags))
                error = 0;
 
-       if (io_end->flag & EXT4_IO_END_UNWRITTEN) {
-               /*
-                * Link bio into list hanging from io_end. We have to do it
-                * atomically as bio completions can be racing against each
-                * other.
-                */
-               bio->bi_private = xchg(&io_end->bio, bio);
-       } else {
-               ext4_finish_bio(bio);
-               bio_put(bio);
-       }
-
        if (error) {
                struct inode *inode = io_end->inode;
 
@@ -341,7 +330,24 @@ static void ext4_end_bio(struct bio *bio, int error)
                             (unsigned long long)
                             bi_sector >> (inode->i_blkbits - 9));
        }
-       ext4_put_io_end_defer(io_end);
+
+       if (io_end->flag & EXT4_IO_END_UNWRITTEN) {
+               /*
+                * Link bio into list hanging from io_end. We have to do it
+                * atomically as bio completions can be racing against each
+                * other.
+                */
+               bio->bi_private = xchg(&io_end->bio, bio);
+               ext4_put_io_end_defer(io_end);
+       } else {
+               /*
+                * Drop io_end reference early. Inode can get freed once
+                * we finish the bio.
+                */
+               ext4_put_io_end_defer(io_end);
+               ext4_finish_bio(bio);
+               bio_put(bio);
+       }
 }
 
 void ext4_io_submit(struct ext4_io_submit *io)