| From 349fa7d6e1935f49bf4161c4900711b2989180a9 Mon Sep 17 00:00:00 2001 |
| From: Eric Biggers <ebiggers@google.com> |
| Date: Thu, 12 Apr 2018 11:48:09 -0400 |
| Subject: ext4: prevent right-shifting extents beyond EXT_MAX_BLOCKS |
| |
| From: Eric Biggers <ebiggers@google.com> |
| |
| commit 349fa7d6e1935f49bf4161c4900711b2989180a9 upstream. |
| |
| During the "insert range" fallocate operation, extents starting at the |
| range offset are shifted "right" (to a higher file offset) by the range |
| length. But, as shown by syzbot, it's not validated that this doesn't |
| cause extents to be shifted beyond EXT_MAX_BLOCKS. In that case |
| ->ee_block can wrap around, corrupting the extent tree. |
| |
| Fix it by returning an error if the space between the end of the last |
| extent and EXT4_MAX_BLOCKS is smaller than the range being inserted. |
| |
| This bug can be reproduced by running the following commands when the |
| current directory is on an ext4 filesystem with a 4k block size: |
| |
| fallocate -l 8192 file |
| fallocate --keep-size -o 0xfffffffe000 -l 4096 -n file |
| fallocate --insert-range -l 8192 file |
| |
| Then after unmounting the filesystem, e2fsck reports corruption. |
| |
| Reported-by: syzbot+06c885be0edcdaeab40c@syzkaller.appspotmail.com |
| Fixes: 331573febb6a ("ext4: Add support FALLOC_FL_INSERT_RANGE for fallocate") |
| Cc: stable@vger.kernel.org # v4.2+ |
| Signed-off-by: Eric Biggers <ebiggers@google.com> |
| Signed-off-by: Theodore Ts'o <tytso@mit.edu> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| fs/ext4/extents.c | 16 +++++++++++----- |
| 1 file changed, 11 insertions(+), 5 deletions(-) |
| |
| --- a/fs/ext4/extents.c |
| +++ b/fs/ext4/extents.c |
| @@ -5380,8 +5380,9 @@ ext4_ext_shift_extents(struct inode *ino |
| stop = le32_to_cpu(extent->ee_block); |
| |
| /* |
| - * In case of left shift, Don't start shifting extents until we make |
| - * sure the hole is big enough to accommodate the shift. |
| + * For left shifts, make sure the hole on the left is big enough to |
| + * accommodate the shift. For right shifts, make sure the last extent |
| + * won't be shifted beyond EXT_MAX_BLOCKS. |
| */ |
| if (SHIFT == SHIFT_LEFT) { |
| path = ext4_find_extent(inode, start - 1, &path, |
| @@ -5401,9 +5402,14 @@ ext4_ext_shift_extents(struct inode *ino |
| |
| if ((start == ex_start && shift > ex_start) || |
| (shift > start - ex_end)) { |
| - ext4_ext_drop_refs(path); |
| - kfree(path); |
| - return -EINVAL; |
| + ret = -EINVAL; |
| + goto out; |
| + } |
| + } else { |
| + if (shift > EXT_MAX_BLOCKS - |
| + (stop + ext4_ext_get_actual_len(extent))) { |
| + ret = -EINVAL; |
| + goto out; |
| } |
| } |
| |