]> git.hungrycats.org Git - linux/commitdiff
btrfs: fix data corruption while reading compressed inline extents followed by other...
authorZygo Blaxell <zblaxell@thirteen.furryterror.org>
Wed, 28 Sep 2016 04:43:53 +0000 (00:43 -0400)
committerZygo Blaxell <zblaxell@thirteen.furryterror.org>
Wed, 28 Sep 2016 04:56:59 +0000 (00:56 -0400)
rsync -S causes a large number of small writes separated by small seeks
to form sparse holes in files that contain runs of zero bytes.  This can
lead btrfs to write a file with an inline extent followed by other data,
like this:

Filesystem type is: 9123683e
File size of share/locale/bs/LC_MESSAGES/glib20.mo is 12368 (4 blocks of 4096 bytes)
 ext:     logical_offset:        physical_offset: length:   expected: flags:
   0:        0..    4095:          0..      4095:   4096:             not_aligned,inline
   1:        1..       3:    1884391..   1884393:      3:          1: last,encoded,eof
share/locale/bs/LC_MESSAGES/glib20.mo: 2 extents found

Typically the inline extent size is less than the page size.

When the inline extent is not compressed, btrfs_get_extent copies the
inline extent data and then memsets the remainder of the page to zero.

When the inline extent is compressed, uncompress_inline passes the
ram_bytes field from the extent to btrfs_decompress as the size of the
buffer.  Unfortunately, ram_bytes is less than PAGE_CACHE_SIZE, and in
cases where there are other extents following the initial inline extent,
the difference between the two sizes is filled with uninitialized data
that ends up in userspace.

Fix this by passing PAGE_CACHE_SIZE to btrfs_decompress as the buffer
size.

fs/btrfs/inode.c

index a39eaa894ddd2ac2de07819355615be8813b30ac..512b7137c4815c8167f1e0f79d31a779dbc31579 100644 (file)
@@ -6762,7 +6762,16 @@ static noinline int uncompress_inline(struct btrfs_path *path,
 
        read_extent_buffer(leaf, tmp, ptr, inline_size);
 
-       max_size = min_t(unsigned long, PAGE_CACHE_SIZE, max_size);
+       /*
+        * We can't have max_size > PAGE_CACHE_SIZE because we only
+        * allocated one page.  We can't have max_size < PAGE_CACHE_SIZE
+        * because we might extend the file past the end of the page,
+        * so we need to memset the end of the buffer to zero.  Since
+        * max_size can't be anything other than PAGE_CACHE_SIZE,
+        * just set it to that value.
+        */
+       WARN_ON(max_size > PAGE_CACHE_SIZE);
+       max_size = PAGE_CACHE_SIZE;
        ret = btrfs_decompress(compress_type, tmp, page,
                               extent_offset, inline_size, max_size);
        kfree(tmp);