diff options
| author | Sona Sarmadi <sona.sarmadi@enea.com> | 2016-04-06 08:53:36 +0200 |
|---|---|---|
| committer | Tudor Florea <tudor.florea@enea.com> | 2016-04-07 03:12:56 +0200 |
| commit | 291c45aaebb29078e32ff38f7b9998fd9fdfe167 (patch) | |
| tree | 48f9f2a87949def8757f1b9868209c336febb3fc | |
| parent | 95e2d54d188fa653d9075782a3e431925829c297 (diff) | |
| download | meta-hierofalcon-291c45aaebb29078e32ff38f7b9998fd9fdfe167.tar.gz | |
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 <sona.sarmadi@enea.com>
Signed-off-by: Tudor Florea <tudor.florea@enea.com>
| -rw-r--r-- | recipes-kernel/linux/linux-hierofalcon/Btrfs-CVE-2015-8374.patch | 293 | ||||
| -rw-r--r-- | recipes-kernel/linux/linux-hierofalcon_3.19.bb | 1 | ||||
| -rw-r--r-- | recipes-kernel/linux/linux-hierofalcon_4.1.bb | 1 |
3 files changed, 295 insertions, 0 deletions
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 @@ | |||
| 1 | From f1008f6d21ec52d533f7473e2e46218408fb4580 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Filipe Manana <fdmanana@suse.com> | ||
| 3 | Date: Fri, 16 Oct 2015 12:34:25 +0100 | ||
| 4 | Subject: Btrfs: fix truncation of compressed and inlined extents | ||
| 5 | |||
| 6 | commit 0305cd5f7fca85dae392b9ba85b116896eb7c1c7 upstream. | ||
| 7 | |||
| 8 | When truncating a file to a smaller size which consists of an inline | ||
| 9 | extent that is compressed, we did not discard (or made unusable) the | ||
| 10 | data between the new file size and the old file size, wasting metadata | ||
| 11 | space and allowing for the truncated data to be leaked and the data | ||
| 12 | corruption/loss mentioned below. | ||
| 13 | We were also not correctly decrementing the number of bytes used by the | ||
| 14 | inode, we were setting it to zero, giving a wrong report for callers of | ||
| 15 | the stat(2) syscall. The fsck tool also reported an error about a mismatch | ||
| 16 | between the nbytes of the file versus the real space used by the file. | ||
| 17 | |||
| 18 | Now because we weren't discarding the truncated region of the file, it | ||
| 19 | was possible for a caller of the clone ioctl to actually read the data | ||
| 20 | that was truncated, allowing for a security breach without requiring root | ||
| 21 | access to the system, using only standard filesystem operations. The | ||
| 22 | scenario is the following: | ||
| 23 | |||
| 24 | 1) User A creates a file which consists of an inline and compressed | ||
| 25 | extent with a size of 2000 bytes - the file is not accessible to | ||
| 26 | any other users (no read, write or execution permission for anyone | ||
| 27 | else); | ||
| 28 | |||
| 29 | 2) The user truncates the file to a size of 1000 bytes; | ||
| 30 | |||
| 31 | 3) User A makes the file world readable; | ||
| 32 | |||
| 33 | 4) User B creates a file consisting of an inline extent of 2000 bytes; | ||
| 34 | |||
| 35 | 5) User B issues a clone operation from user A's file into its own | ||
| 36 | file (using a length argument of 0, clone the whole range); | ||
| 37 | |||
| 38 | 6) User B now gets to see the 1000 bytes that user A truncated from | ||
| 39 | its file before it made its file world readbale. User B also lost | ||
| 40 | the bytes in the range [1000, 2000[ bytes from its own file, but | ||
| 41 | that might be ok if his/her intention was reading stale data from | ||
| 42 | user A that was never supposed to be public. | ||
| 43 | |||
| 44 | Note that this contrasts with the case where we truncate a file from 2000 | ||
| 45 | bytes to 1000 bytes and then truncate it back from 1000 to 2000 bytes. In | ||
| 46 | this case reading any byte from the range [1000, 2000[ will return a value | ||
| 47 | of 0x00, instead of the original data. | ||
| 48 | |||
| 49 | This problem exists since the clone ioctl was added and happens both with | ||
| 50 | and without my recent data loss and file corruption fixes for the clone | ||
| 51 | ioctl (patch "Btrfs: fix file corruption and data loss after cloning | ||
| 52 | inline extents"). | ||
| 53 | |||
| 54 | So fix this by truncating the compressed inline extents as we do for the | ||
| 55 | non-compressed case, which involves decompressing, if the data isn't already | ||
| 56 | in the page cache, compressing the truncated version of the extent, writing | ||
| 57 | the compressed content into the inline extent and then truncate it. | ||
| 58 | |||
| 59 | The following test case for fstests reproduces the problem. In order for | ||
| 60 | the test to pass both this fix and my previous fix for the clone ioctl | ||
| 61 | that forbids cloning a smaller inline extent into a larger one, | ||
| 62 | which is titled "Btrfs: fix file corruption and data loss after cloning | ||
| 63 | inline extents", are needed. Without that other fix the test fails in a | ||
| 64 | different way that does not leak the truncated data, instead part of | ||
| 65 | destination file gets replaced with zeroes (because the destination file | ||
| 66 | has a larger inline extent than the source). | ||
| 67 | |||
| 68 | seq=`basename $0` | ||
| 69 | seqres=$RESULT_DIR/$seq | ||
| 70 | echo "QA output created by $seq" | ||
| 71 | tmp=/tmp/$$ | ||
| 72 | status=1 # failure is the default! | ||
| 73 | trap "_cleanup; exit \$status" 0 1 2 3 15 | ||
| 74 | |||
| 75 | _cleanup() | ||
| 76 | { | ||
| 77 | rm -f $tmp.* | ||
| 78 | } | ||
| 79 | |||
| 80 | # get standard environment, filters and checks | ||
| 81 | . ./common/rc | ||
| 82 | . ./common/filter | ||
| 83 | |||
| 84 | # real QA test starts here | ||
| 85 | _need_to_be_root | ||
| 86 | _supported_fs btrfs | ||
| 87 | _supported_os Linux | ||
| 88 | _require_scratch | ||
| 89 | _require_cloner | ||
| 90 | |||
| 91 | rm -f $seqres.full | ||
| 92 | |||
| 93 | _scratch_mkfs >>$seqres.full 2>&1 | ||
| 94 | _scratch_mount "-o compress" | ||
| 95 | |||
| 96 | # Create our test files. File foo is going to be the source of a clone operation | ||
| 97 | # and consists of a single inline extent with an uncompressed size of 512 bytes, | ||
| 98 | # while file bar consists of a single inline extent with an uncompressed size of | ||
| 99 | # 256 bytes. For our test's purpose, it's important that file bar has an inline | ||
| 100 | # extent with a size smaller than foo's inline extent. | ||
| 101 | $XFS_IO_PROG -f -c "pwrite -S 0xa1 0 128" \ | ||
| 102 | -c "pwrite -S 0x2a 128 384" \ | ||
| 103 | $SCRATCH_MNT/foo | _filter_xfs_io | ||
| 104 | $XFS_IO_PROG -f -c "pwrite -S 0xbb 0 256" $SCRATCH_MNT/bar | _filter_xfs_io | ||
| 105 | |||
| 106 | # Now durably persist all metadata and data. We do this to make sure that we get | ||
| 107 | # on disk an inline extent with a size of 512 bytes for file foo. | ||
| 108 | sync | ||
| 109 | |||
| 110 | # Now truncate our file foo to a smaller size. Because it consists of a | ||
| 111 | # compressed and inline extent, btrfs did not shrink the inline extent to the | ||
| 112 | # new size (if the extent was not compressed, btrfs would shrink it to 128 | ||
| 113 | # bytes), it only updates the inode's i_size to 128 bytes. | ||
| 114 | $XFS_IO_PROG -c "truncate 128" $SCRATCH_MNT/foo | ||
| 115 | |||
| 116 | # Now clone foo's inline extent into bar. | ||
| 117 | # This clone operation should fail with errno EOPNOTSUPP because the source | ||
| 118 | # file consists only of an inline extent and the file's size is smaller than | ||
| 119 | # the inline extent of the destination (128 bytes < 256 bytes). However the | ||
| 120 | # clone ioctl was not prepared to deal with a file that has a size smaller | ||
| 121 | # than the size of its inline extent (something that happens only for compressed | ||
| 122 | # inline extents), resulting in copying the full inline extent from the source | ||
| 123 | # file into the destination file. | ||
| 124 | # | ||
| 125 | # Note that btrfs' clone operation for inline extents consists of removing the | ||
| 126 | # inline extent from the destination inode and copy the inline extent from the | ||
| 127 | # source inode into the destination inode, meaning that if the destination | ||
| 128 | # inode's inline extent is larger (N bytes) than the source inode's inline | ||
| 129 | # extent (M bytes), some bytes (N - M bytes) will be lost from the destination | ||
| 130 | # file. Btrfs could copy the source inline extent's data into the destination's | ||
| 131 | # inline extent so that we would not lose any data, but that's currently not | ||
| 132 | # done due to the complexity that would be needed to deal with such cases | ||
| 133 | # (specially when one or both extents are compressed), returning EOPNOTSUPP, as | ||
| 134 | # it's normally not a very common case to clone very small files (only case | ||
| 135 | # where we get inline extents) and copying inline extents does not save any | ||
| 136 | # space (unlike for normal, non-inlined extents). | ||
| 137 | $CLONER_PROG -s 0 -d 0 -l 0 $SCRATCH_MNT/foo $SCRATCH_MNT/bar | ||
| 138 | |||
| 139 | # Now because the above clone operation used to succeed, and due to foo's inline | ||
| 140 | # extent not being shinked by the truncate operation, our file bar got the whole | ||
| 141 | # inline extent copied from foo, making us lose the last 128 bytes from bar | ||
| 142 | # which got replaced by the bytes in range [128, 256[ from foo before foo was | ||
| 143 | # truncated - in other words, data loss from bar and being able to read old and | ||
| 144 | # stale data from foo that should not be possible to read anymore through normal | ||
| 145 | # filesystem operations. Contrast with the case where we truncate a file from a | ||
| 146 | # size N to a smaller size M, truncate it back to size N and then read the range | ||
| 147 | # [M, N[, we should always get the value 0x00 for all the bytes in that range. | ||
| 148 | |||
| 149 | # We expected the clone operation to fail with errno EOPNOTSUPP and therefore | ||
| 150 | # not modify our file's bar data/metadata. So its content should be 256 bytes | ||
| 151 | # long with all bytes having the value 0xbb. | ||
| 152 | # | ||
| 153 | # Without the btrfs bug fix, the clone operation succeeded and resulted in | ||
| 154 | # leaking truncated data from foo, the bytes that belonged to its range | ||
| 155 | # [128, 256[, and losing data from bar in that same range. So reading the | ||
| 156 | # file gave us the following content: | ||
| 157 | # | ||
| 158 | # 0000000 a1 a1 a1 a1 a1 a1 a1 a1 a1 a1 a1 a1 a1 a1 a1 a1 | ||
| 159 | # * | ||
| 160 | # 0000200 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a | ||
| 161 | # * | ||
| 162 | # 0000400 | ||
| 163 | echo "File bar's content after the clone operation:" | ||
| 164 | od -t x1 $SCRATCH_MNT/bar | ||
| 165 | |||
| 166 | # Also because the foo's inline extent was not shrunk by the truncate | ||
| 167 | # operation, btrfs' fsck, which is run by the fstests framework everytime a | ||
| 168 | # test completes, failed reporting the following error: | ||
| 169 | # | ||
| 170 | # root 5 inode 257 errors 400, nbytes wrong | ||
| 171 | |||
| 172 | status=0 | ||
| 173 | exit | ||
| 174 | CVE: CVE-2015-8374 | ||
| 175 | Upstream-Status: Backport | ||
| 176 | |||
| 177 | Signed-off-by: Filipe Manana <fdmanana@suse.com> | ||
| 178 | Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> | ||
| 179 | Signed-off-by: Sona Sarmadi <sona.sarmadi@enea.com> | ||
| 180 | --- | ||
| 181 | fs/btrfs/inode.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++---------- | ||
| 182 | 1 file changed, 68 insertions(+), 14 deletions(-) | ||
| 183 | |||
| 184 | diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c | ||
| 185 | index e3b39f0..88680af 100644 | ||
| 186 | --- a/fs/btrfs/inode.c | ||
| 187 | +++ b/fs/btrfs/inode.c | ||
| 188 | @@ -4184,6 +4184,47 @@ static int truncate_space_check(struct btrfs_trans_handle *trans, | ||
| 189 | |||
| 190 | } | ||
| 191 | |||
| 192 | +static int truncate_inline_extent(struct inode *inode, | ||
| 193 | + struct btrfs_path *path, | ||
| 194 | + struct btrfs_key *found_key, | ||
| 195 | + const u64 item_end, | ||
| 196 | + const u64 new_size) | ||
| 197 | +{ | ||
| 198 | + struct extent_buffer *leaf = path->nodes[0]; | ||
| 199 | + int slot = path->slots[0]; | ||
| 200 | + struct btrfs_file_extent_item *fi; | ||
| 201 | + u32 size = (u32)(new_size - found_key->offset); | ||
| 202 | + struct btrfs_root *root = BTRFS_I(inode)->root; | ||
| 203 | + | ||
| 204 | + fi = btrfs_item_ptr(leaf, slot, struct btrfs_file_extent_item); | ||
| 205 | + | ||
| 206 | + if (btrfs_file_extent_compression(leaf, fi) != BTRFS_COMPRESS_NONE) { | ||
| 207 | + loff_t offset = new_size; | ||
| 208 | + loff_t page_end = ALIGN(offset, PAGE_CACHE_SIZE); | ||
| 209 | + | ||
| 210 | + /* | ||
| 211 | + * Zero out the remaining of the last page of our inline extent, | ||
| 212 | + * instead of directly truncating our inline extent here - that | ||
| 213 | + * would be much more complex (decompressing all the data, then | ||
| 214 | + * compressing the truncated data, which might be bigger than | ||
| 215 | + * the size of the inline extent, resize the extent, etc). | ||
| 216 | + * We release the path because to get the page we might need to | ||
| 217 | + * read the extent item from disk (data not in the page cache). | ||
| 218 | + */ | ||
| 219 | + btrfs_release_path(path); | ||
| 220 | + return btrfs_truncate_page(inode, offset, page_end - offset, 0); | ||
| 221 | + } | ||
| 222 | + | ||
| 223 | + btrfs_set_file_extent_ram_bytes(leaf, fi, size); | ||
| 224 | + size = btrfs_file_extent_calc_inline_size(size); | ||
| 225 | + btrfs_truncate_item(root, path, size, 1); | ||
| 226 | + | ||
| 227 | + if (test_bit(BTRFS_ROOT_REF_COWS, &root->state)) | ||
| 228 | + inode_sub_bytes(inode, item_end + 1 - new_size); | ||
| 229 | + | ||
| 230 | + return 0; | ||
| 231 | +} | ||
| 232 | + | ||
| 233 | /* | ||
| 234 | * this can truncate away extent items, csum items and directory items. | ||
| 235 | * It starts at a high offset and removes keys until it can't find | ||
| 236 | @@ -4378,27 +4419,40 @@ search_again: | ||
| 237 | * special encodings | ||
| 238 | */ | ||
| 239 | if (!del_item && | ||
| 240 | - btrfs_file_extent_compression(leaf, fi) == 0 && | ||
| 241 | btrfs_file_extent_encryption(leaf, fi) == 0 && | ||
| 242 | btrfs_file_extent_other_encoding(leaf, fi) == 0) { | ||
| 243 | - u32 size = new_size - found_key.offset; | ||
| 244 | - | ||
| 245 | - if (test_bit(BTRFS_ROOT_REF_COWS, &root->state)) | ||
| 246 | - inode_sub_bytes(inode, item_end + 1 - | ||
| 247 | - new_size); | ||
| 248 | |||
| 249 | /* | ||
| 250 | - * update the ram bytes to properly reflect | ||
| 251 | - * the new size of our item | ||
| 252 | + * Need to release path in order to truncate a | ||
| 253 | + * compressed extent. So delete any accumulated | ||
| 254 | + * extent items so far. | ||
| 255 | */ | ||
| 256 | - btrfs_set_file_extent_ram_bytes(leaf, fi, size); | ||
| 257 | - size = | ||
| 258 | - btrfs_file_extent_calc_inline_size(size); | ||
| 259 | - btrfs_truncate_item(root, path, size, 1); | ||
| 260 | + if (btrfs_file_extent_compression(leaf, fi) != | ||
| 261 | + BTRFS_COMPRESS_NONE && pending_del_nr) { | ||
| 262 | + err = btrfs_del_items(trans, root, path, | ||
| 263 | + pending_del_slot, | ||
| 264 | + pending_del_nr); | ||
| 265 | + if (err) { | ||
| 266 | + btrfs_abort_transaction(trans, | ||
| 267 | + root, | ||
| 268 | + err); | ||
| 269 | + goto error; | ||
| 270 | + } | ||
| 271 | + pending_del_nr = 0; | ||
| 272 | + } | ||
| 273 | + | ||
| 274 | + err = truncate_inline_extent(inode, path, | ||
| 275 | + &found_key, | ||
| 276 | + item_end, | ||
| 277 | + new_size); | ||
| 278 | + if (err) { | ||
| 279 | + btrfs_abort_transaction(trans, | ||
| 280 | + root, err); | ||
| 281 | + goto error; | ||
| 282 | + } | ||
| 283 | } else if (test_bit(BTRFS_ROOT_REF_COWS, | ||
| 284 | &root->state)) { | ||
| 285 | - inode_sub_bytes(inode, item_end + 1 - | ||
| 286 | - found_key.offset); | ||
| 287 | + inode_sub_bytes(inode, item_end + 1 - new_size); | ||
| 288 | } | ||
| 289 | } | ||
| 290 | delete: | ||
| 291 | -- | ||
| 292 | cgit v0.12 | ||
| 293 | |||
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 | |||
| 35 | file://ipc-CVE-2015-7613.patch \ | 35 | file://ipc-CVE-2015-7613.patch \ |
| 36 | file://net-unix-CVE-2013-7446.patch \ | 36 | file://net-unix-CVE-2013-7446.patch \ |
| 37 | file://ALSA-CVE-2016-2546.patch \ | 37 | file://ALSA-CVE-2016-2546.patch \ |
| 38 | file://Btrfs-CVE-2015-8374.patch \ | ||
| 38 | " | 39 | " |
| 39 | 40 | ||
| 40 | S = "${WORKDIR}/git" | 41 | 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 | |||
| 36 | file://usb-CVE-2015-8816.patch \ | 36 | file://usb-CVE-2015-8816.patch \ |
| 37 | file://bpf-CVE-2016-2383.patch \ | 37 | file://bpf-CVE-2016-2383.patch \ |
| 38 | file://ALSA-CVE-2016-2546.patch \ | 38 | file://ALSA-CVE-2016-2546.patch \ |
| 39 | file://Btrfs-CVE-2015-8374.patch \ | ||
| 39 | " | 40 | " |
| 40 | 41 | ||
| 41 | S = "${WORKDIR}/git" | 42 | S = "${WORKDIR}/git" |
