[linux] 03/03: Btrfs: fix truncation of compressed and inlined extents (CVE-2015-8374)

debian-kernel at lists.debian.org debian-kernel at lists.debian.org
Thu Dec 3 00:51:59 UTC 2015


This is an automated email from the git hooks/post-receive script.

benh pushed a commit to branch sid
in repository linux.

commit 4668d13b75f7c8801c17edacace97afafadc1194
Author: Ben Hutchings <ben at decadent.org.uk>
Date:   Thu Dec 3 00:50:36 2015 +0000

    Btrfs: fix truncation of compressed and inlined extents (CVE-2015-8374)
---
 debian/changelog                                   |   1 +
 ...runcation-of-compressed-and-inlined-exten.patch | 283 +++++++++++++++++++++
 debian/patches/series                              |   1 +
 3 files changed, 285 insertions(+)

diff --git a/debian/changelog b/debian/changelog
index 91c0645..f0c8fd3 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -16,6 +16,7 @@ linux (4.2.6-2) UNRELEASED; urgency=medium
   * isdn_ppp: Add checks for allocation failure in isdn_ppp_open()
   * ppp, slip: Validate VJ compression slot parameters completely
     (CVE-2015-7799)
+  * Btrfs: fix truncation of compressed and inlined extents (CVE-2015-8374)
 
  -- Salvatore Bonaccorso <carnil at debian.org>  Tue, 10 Nov 2015 22:05:58 +0100
 
diff --git a/debian/patches/bugfix/all/btrfs-fix-truncation-of-compressed-and-inlined-exten.patch b/debian/patches/bugfix/all/btrfs-fix-truncation-of-compressed-and-inlined-exten.patch
new file mode 100644
index 0000000..7cd8401
--- /dev/null
+++ b/debian/patches/bugfix/all/btrfs-fix-truncation-of-compressed-and-inlined-exten.patch
@@ -0,0 +1,283 @@
+From: Filipe Manana <fdmanana at suse.com>
+Date: Fri, 16 Oct 2015 12:34:25 +0100
+Subject: Btrfs: fix truncation of compressed and inlined extents
+Origin: https://git.kernel.org/linus/0305cd5f7fca85dae392b9ba85b116896eb7c1c7
+
+When truncating a file to a smaller size which consists of an inline
+extent that is compressed, we did not discard (or made unusable) the
+data between the new file size and the old file size, wasting metadata
+space and allowing for the truncated data to be leaked and the data
+corruption/loss mentioned below.
+We were also not correctly decrementing the number of bytes used by the
+inode, we were setting it to zero, giving a wrong report for callers of
+the stat(2) syscall. The fsck tool also reported an error about a mismatch
+between the nbytes of the file versus the real space used by the file.
+
+Now because we weren't discarding the truncated region of the file, it
+was possible for a caller of the clone ioctl to actually read the data
+that was truncated, allowing for a security breach without requiring root
+access to the system, using only standard filesystem operations. The
+scenario is the following:
+
+   1) User A creates a file which consists of an inline and compressed
+      extent with a size of 2000 bytes - the file is not accessible to
+      any other users (no read, write or execution permission for anyone
+      else);
+
+   2) The user truncates the file to a size of 1000 bytes;
+
+   3) User A makes the file world readable;
+
+   4) User B creates a file consisting of an inline extent of 2000 bytes;
+
+   5) User B issues a clone operation from user A's file into its own
+      file (using a length argument of 0, clone the whole range);
+
+   6) User B now gets to see the 1000 bytes that user A truncated from
+      its file before it made its file world readbale. User B also lost
+      the bytes in the range [1000, 2000[ bytes from its own file, but
+      that might be ok if his/her intention was reading stale data from
+      user A that was never supposed to be public.
+
+Note that this contrasts with the case where we truncate a file from 2000
+bytes to 1000 bytes and then truncate it back from 1000 to 2000 bytes. In
+this case reading any byte from the range [1000, 2000[ will return a value
+of 0x00, instead of the original data.
+
+This problem exists since the clone ioctl was added and happens both with
+and without my recent data loss and file corruption fixes for the clone
+ioctl (patch "Btrfs: fix file corruption and data loss after cloning
+inline extents").
+
+So fix this by truncating the compressed inline extents as we do for the
+non-compressed case, which involves decompressing, if the data isn't already
+in the page cache, compressing the truncated version of the extent, writing
+the compressed content into the inline extent and then truncate it.
+
+The following test case for fstests reproduces the problem. In order for
+the test to pass both this fix and my previous fix for the clone ioctl
+that forbids cloning a smaller inline extent into a larger one,
+which is titled "Btrfs: fix file corruption and data loss after cloning
+inline extents", are needed. Without that other fix the test fails in a
+different way that does not leak the truncated data, instead part of
+destination file gets replaced with zeroes (because the destination file
+has a larger inline extent than the source).
+
+  seq=`basename $0`
+  seqres=$RESULT_DIR/$seq
+  echo "QA output created by $seq"
+  tmp=/tmp/$$
+  status=1	# failure is the default!
+  trap "_cleanup; exit \$status" 0 1 2 3 15
+
+  _cleanup()
+  {
+      rm -f $tmp.*
+  }
+
+  # get standard environment, filters and checks
+  . ./common/rc
+  . ./common/filter
+
+  # real QA test starts here
+  _need_to_be_root
+  _supported_fs btrfs
+  _supported_os Linux
+  _require_scratch
+  _require_cloner
+
+  rm -f $seqres.full
+
+  _scratch_mkfs >>$seqres.full 2>&1
+  _scratch_mount "-o compress"
+
+  # Create our test files. File foo is going to be the source of a clone operation
+  # and consists of a single inline extent with an uncompressed size of 512 bytes,
+  # while file bar consists of a single inline extent with an uncompressed size of
+  # 256 bytes. For our test's purpose, it's important that file bar has an inline
+  # extent with a size smaller than foo's inline extent.
+  $XFS_IO_PROG -f -c "pwrite -S 0xa1 0 128"   \
+          -c "pwrite -S 0x2a 128 384" \
+          $SCRATCH_MNT/foo | _filter_xfs_io
+  $XFS_IO_PROG -f -c "pwrite -S 0xbb 0 256" $SCRATCH_MNT/bar | _filter_xfs_io
+
+  # Now durably persist all metadata and data. We do this to make sure that we get
+  # on disk an inline extent with a size of 512 bytes for file foo.
+  sync
+
+  # Now truncate our file foo to a smaller size. Because it consists of a
+  # compressed and inline extent, btrfs did not shrink the inline extent to the
+  # new size (if the extent was not compressed, btrfs would shrink it to 128
+  # bytes), it only updates the inode's i_size to 128 bytes.
+  $XFS_IO_PROG -c "truncate 128" $SCRATCH_MNT/foo
+
+  # Now clone foo's inline extent into bar.
+  # This clone operation should fail with errno EOPNOTSUPP because the source
+  # file consists only of an inline extent and the file's size is smaller than
+  # the inline extent of the destination (128 bytes < 256 bytes). However the
+  # clone ioctl was not prepared to deal with a file that has a size smaller
+  # than the size of its inline extent (something that happens only for compressed
+  # inline extents), resulting in copying the full inline extent from the source
+  # file into the destination file.
+  #
+  # Note that btrfs' clone operation for inline extents consists of removing the
+  # inline extent from the destination inode and copy the inline extent from the
+  # source inode into the destination inode, meaning that if the destination
+  # inode's inline extent is larger (N bytes) than the source inode's inline
+  # extent (M bytes), some bytes (N - M bytes) will be lost from the destination
+  # file. Btrfs could copy the source inline extent's data into the destination's
+  # inline extent so that we would not lose any data, but that's currently not
+  # done due to the complexity that would be needed to deal with such cases
+  # (specially when one or both extents are compressed), returning EOPNOTSUPP, as
+  # it's normally not a very common case to clone very small files (only case
+  # where we get inline extents) and copying inline extents does not save any
+  # space (unlike for normal, non-inlined extents).
+  $CLONER_PROG -s 0 -d 0 -l 0 $SCRATCH_MNT/foo $SCRATCH_MNT/bar
+
+  # Now because the above clone operation used to succeed, and due to foo's inline
+  # extent not being shinked by the truncate operation, our file bar got the whole
+  # inline extent copied from foo, making us lose the last 128 bytes from bar
+  # which got replaced by the bytes in range [128, 256[ from foo before foo was
+  # truncated - in other words, data loss from bar and being able to read old and
+  # stale data from foo that should not be possible to read anymore through normal
+  # filesystem operations. Contrast with the case where we truncate a file from a
+  # size N to a smaller size M, truncate it back to size N and then read the range
+  # [M, N[, we should always get the value 0x00 for all the bytes in that range.
+
+  # We expected the clone operation to fail with errno EOPNOTSUPP and therefore
+  # not modify our file's bar data/metadata. So its content should be 256 bytes
+  # long with all bytes having the value 0xbb.
+  #
+  # Without the btrfs bug fix, the clone operation succeeded and resulted in
+  # leaking truncated data from foo, the bytes that belonged to its range
+  # [128, 256[, and losing data from bar in that same range. So reading the
+  # file gave us the following content:
+  #
+  # 0000000 a1 a1 a1 a1 a1 a1 a1 a1 a1 a1 a1 a1 a1 a1 a1 a1
+  # *
+  # 0000200 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a
+  # *
+  # 0000400
+  echo "File bar's content after the clone operation:"
+  od -t x1 $SCRATCH_MNT/bar
+
+  # Also because the foo's inline extent was not shrunk by the truncate
+  # operation, btrfs' fsck, which is run by the fstests framework everytime a
+  # test completes, failed reporting the following error:
+  #
+  #  root 5 inode 257 errors 400, nbytes wrong
+
+  status=0
+  exit
+
+Cc: stable at vger.kernel.org
+Signed-off-by: Filipe Manana <fdmanana at suse.com>
+---
+ fs/btrfs/inode.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++----------
+ 1 file changed, 68 insertions(+), 14 deletions(-)
+
+--- a/fs/btrfs/inode.c
++++ b/fs/btrfs/inode.c
+@@ -4184,6 +4184,47 @@ static int truncate_space_check(struct b
+ 
+ }
+ 
++static int truncate_inline_extent(struct inode *inode,
++				  struct btrfs_path *path,
++				  struct btrfs_key *found_key,
++				  const u64 item_end,
++				  const u64 new_size)
++{
++	struct extent_buffer *leaf = path->nodes[0];
++	int slot = path->slots[0];
++	struct btrfs_file_extent_item *fi;
++	u32 size = (u32)(new_size - found_key->offset);
++	struct btrfs_root *root = BTRFS_I(inode)->root;
++
++	fi = btrfs_item_ptr(leaf, slot, struct btrfs_file_extent_item);
++
++	if (btrfs_file_extent_compression(leaf, fi) != BTRFS_COMPRESS_NONE) {
++		loff_t offset = new_size;
++		loff_t page_end = ALIGN(offset, PAGE_CACHE_SIZE);
++
++		/*
++		 * Zero out the remaining of the last page of our inline extent,
++		 * instead of directly truncating our inline extent here - that
++		 * would be much more complex (decompressing all the data, then
++		 * compressing the truncated data, which might be bigger than
++		 * the size of the inline extent, resize the extent, etc).
++		 * We release the path because to get the page we might need to
++		 * read the extent item from disk (data not in the page cache).
++		 */
++		btrfs_release_path(path);
++		return btrfs_truncate_page(inode, offset, page_end - offset, 0);
++	}
++
++	btrfs_set_file_extent_ram_bytes(leaf, fi, size);
++	size = btrfs_file_extent_calc_inline_size(size);
++	btrfs_truncate_item(root, path, size, 1);
++
++	if (test_bit(BTRFS_ROOT_REF_COWS, &root->state))
++		inode_sub_bytes(inode, item_end + 1 - new_size);
++
++	return 0;
++}
++
+ /*
+  * this can truncate away extent items, csum items and directory items.
+  * It starts at a high offset and removes keys until it can't find
+@@ -4378,27 +4419,40 @@ search_again:
+ 			 * special encodings
+ 			 */
+ 			if (!del_item &&
+-			    btrfs_file_extent_compression(leaf, fi) == 0 &&
+ 			    btrfs_file_extent_encryption(leaf, fi) == 0 &&
+ 			    btrfs_file_extent_other_encoding(leaf, fi) == 0) {
+-				u32 size = new_size - found_key.offset;
+-
+-				if (test_bit(BTRFS_ROOT_REF_COWS, &root->state))
+-					inode_sub_bytes(inode, item_end + 1 -
+-							new_size);
+ 
+ 				/*
+-				 * update the ram bytes to properly reflect
+-				 * the new size of our item
++				 * Need to release path in order to truncate a
++				 * compressed extent. So delete any accumulated
++				 * extent items so far.
+ 				 */
+-				btrfs_set_file_extent_ram_bytes(leaf, fi, size);
+-				size =
+-				    btrfs_file_extent_calc_inline_size(size);
+-				btrfs_truncate_item(root, path, size, 1);
++				if (btrfs_file_extent_compression(leaf, fi) !=
++				    BTRFS_COMPRESS_NONE && pending_del_nr) {
++					err = btrfs_del_items(trans, root, path,
++							      pending_del_slot,
++							      pending_del_nr);
++					if (err) {
++						btrfs_abort_transaction(trans,
++									root,
++									err);
++						goto error;
++					}
++					pending_del_nr = 0;
++				}
++
++				err = truncate_inline_extent(inode, path,
++							     &found_key,
++							     item_end,
++							     new_size);
++				if (err) {
++					btrfs_abort_transaction(trans,
++								root, err);
++					goto error;
++				}
+ 			} else if (test_bit(BTRFS_ROOT_REF_COWS,
+ 					    &root->state)) {
+-				inode_sub_bytes(inode, item_end + 1 -
+-						found_key.offset);
++				inode_sub_bytes(inode, item_end + 1 - new_size);
+ 			}
+ 		}
+ delete:
diff --git a/debian/patches/series b/debian/patches/series
index c8507db..bf8e787 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -115,3 +115,4 @@ bugfix/all/media-usbvision-fix-crash-on-detecting-device-with-i.patch
 bugfix/all/unix-avoid-use-after-free-in-ep_remove_wait_queue.patch
 bugfix/all/isdn_ppp-add-checks-for-allocation-failure-in-isdn_p.patch
 bugfix/all/ppp-slip-validate-vj-compression-slot-parameters-com.patch
+bugfix/all/btrfs-fix-truncation-of-compressed-and-inlined-exten.patch

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/kernel/linux.git



More information about the Kernel-svn-changes mailing list