From 291c45aaebb29078e32ff38f7b9998fd9fdfe167 Mon Sep 17 00:00:00 2001 From: Sona Sarmadi Date: Wed, 6 Apr 2016 08:53:36 +0200 Subject: kernel/Btrfs: CVE-2015-8374 Fixes an information-leak vulnerability in the kernel when it truncated a file to a smaller size which consisted of an inline extent that was compressed. A caller of the clone ioctl could exploit this flaw by using only standard file-system operations without root access to read the truncated data. Reference: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-8374 Reference to upstream patch: https://git.kernel.org/cgit/linux/kernel/git/stable/linux-stable.git/ patch/?id=f1008f6d21ec52d533f7473e2e46218408fb4580 Signed-off-by: Sona Sarmadi Signed-off-by: Tudor Florea --- .../linux-hierofalcon/Btrfs-CVE-2015-8374.patch | 293 +++++++++++++++++++++ recipes-kernel/linux/linux-hierofalcon_3.19.bb | 1 + recipes-kernel/linux/linux-hierofalcon_4.1.bb | 1 + 3 files changed, 295 insertions(+) create mode 100644 recipes-kernel/linux/linux-hierofalcon/Btrfs-CVE-2015-8374.patch diff --git a/recipes-kernel/linux/linux-hierofalcon/Btrfs-CVE-2015-8374.patch b/recipes-kernel/linux/linux-hierofalcon/Btrfs-CVE-2015-8374.patch new file mode 100644 index 0000000..8ea1ac6 --- /dev/null +++ b/recipes-kernel/linux/linux-hierofalcon/Btrfs-CVE-2015-8374.patch @@ -0,0 +1,293 @@ +From f1008f6d21ec52d533f7473e2e46218408fb4580 Mon Sep 17 00:00:00 2001 +From: Filipe Manana +Date: Fri, 16 Oct 2015 12:34:25 +0100 +Subject: Btrfs: fix truncation of compressed and inlined extents + +commit 0305cd5f7fca85dae392b9ba85b116896eb7c1c7 upstream. + +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 +CVE: CVE-2015-8374 +Upstream-Status: Backport + +Signed-off-by: Filipe Manana +Signed-off-by: Greg Kroah-Hartman +Signed-off-by: Sona Sarmadi +--- + fs/btrfs/inode.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++---------- + 1 file changed, 68 insertions(+), 14 deletions(-) + +diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c +index e3b39f0..88680af 100644 +--- a/fs/btrfs/inode.c ++++ b/fs/btrfs/inode.c +@@ -4184,6 +4184,47 @@ static int truncate_space_check(struct btrfs_trans_handle *trans, + + } + ++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: +-- +cgit v0.12 + diff --git a/recipes-kernel/linux/linux-hierofalcon_3.19.bb b/recipes-kernel/linux/linux-hierofalcon_3.19.bb index d56f1ff..55ccec7 100644 --- a/recipes-kernel/linux/linux-hierofalcon_3.19.bb +++ b/recipes-kernel/linux/linux-hierofalcon_3.19.bb @@ -35,6 +35,7 @@ SRC_URI = "git://git.yoctoproject.org/linux-yocto-3.19;branch="standard/qemuarm6 file://ipc-CVE-2015-7613.patch \ file://net-unix-CVE-2013-7446.patch \ file://ALSA-CVE-2016-2546.patch \ + file://Btrfs-CVE-2015-8374.patch \ " S = "${WORKDIR}/git" diff --git a/recipes-kernel/linux/linux-hierofalcon_4.1.bb b/recipes-kernel/linux/linux-hierofalcon_4.1.bb index 2141668..f528b53 100644 --- a/recipes-kernel/linux/linux-hierofalcon_4.1.bb +++ b/recipes-kernel/linux/linux-hierofalcon_4.1.bb @@ -36,6 +36,7 @@ SRC_URI = "git://git.yoctoproject.org/linux-yocto-4.1;branch="standard/qemuarm64 file://usb-CVE-2015-8816.patch \ file://bpf-CVE-2016-2383.patch \ file://ALSA-CVE-2016-2546.patch \ + file://Btrfs-CVE-2015-8374.patch \ " S = "${WORKDIR}/git" -- cgit v1.2.3-54-g00ecf