diff options
author | Eryu Guan <guaneryu@gmail.com> | 2013-12-03 21:22:21 -0500 |
---|---|---|
committer | Theodore Ts'o <tytso@mit.edu> | 2013-12-03 21:22:21 -0500 |
commit | 5946d089379a35dda0e531710b48fca05446a196 (patch) | |
tree | a65ec2c5eb73e97159c272c83061fd79289c1e67 /fs/ext4/extents.c | |
parent | 4e8d2139802ce4f41936a687f06c560b12115247 (diff) |
ext4: check for overlapping extents in ext4_valid_extent_entries()
A corrupted ext4 may have out of order leaf extents, i.e.
extent: lblk 0--1023, len 1024, pblk 9217, flags: LEAF UNINIT
extent: lblk 1000--2047, len 1024, pblk 10241, flags: LEAF UNINIT
^^^^ overlap with previous extent
Reading such extent could hit BUG_ON() in ext4_es_cache_extent().
BUG_ON(end < lblk);
The problem is that __read_extent_tree_block() tries to cache holes as
well but assumes 'lblk' is greater than 'prev' and passes underflowed
length to ext4_es_cache_extent(). Fix it by checking for overlapping
extents in ext4_valid_extent_entries().
I hit this when fuzz testing ext4, and am able to reproduce it by
modifying the on-disk extent by hand.
Also add the check for (ee_block + len - 1) in ext4_valid_extent() to
make sure the value is not overflow.
Ran xfstests on patched ext4 and no regression.
Cc: Lukáš Czerner <lczerner@redhat.com>
Signed-off-by: Eryu Guan <guaneryu@gmail.com>
Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
Cc: stable@vger.kernel.org
Diffstat (limited to 'fs/ext4/extents.c')
-rw-r--r-- | fs/ext4/extents.c | 19 |
1 files changed, 18 insertions, 1 deletions
diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index 35f65cf4f318..267c9fb53bf9 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c | |||
@@ -360,8 +360,10 @@ static int ext4_valid_extent(struct inode *inode, struct ext4_extent *ext) | |||
360 | { | 360 | { |
361 | ext4_fsblk_t block = ext4_ext_pblock(ext); | 361 | ext4_fsblk_t block = ext4_ext_pblock(ext); |
362 | int len = ext4_ext_get_actual_len(ext); | 362 | int len = ext4_ext_get_actual_len(ext); |
363 | ext4_lblk_t lblock = le32_to_cpu(ext->ee_block); | ||
364 | ext4_lblk_t last = lblock + len - 1; | ||
363 | 365 | ||
364 | if (len == 0) | 366 | if (lblock > last) |
365 | return 0; | 367 | return 0; |
366 | return ext4_data_block_valid(EXT4_SB(inode->i_sb), block, len); | 368 | return ext4_data_block_valid(EXT4_SB(inode->i_sb), block, len); |
367 | } | 369 | } |
@@ -387,11 +389,26 @@ static int ext4_valid_extent_entries(struct inode *inode, | |||
387 | if (depth == 0) { | 389 | if (depth == 0) { |
388 | /* leaf entries */ | 390 | /* leaf entries */ |
389 | struct ext4_extent *ext = EXT_FIRST_EXTENT(eh); | 391 | struct ext4_extent *ext = EXT_FIRST_EXTENT(eh); |
392 | struct ext4_super_block *es = EXT4_SB(inode->i_sb)->s_es; | ||
393 | ext4_fsblk_t pblock = 0; | ||
394 | ext4_lblk_t lblock = 0; | ||
395 | ext4_lblk_t prev = 0; | ||
396 | int len = 0; | ||
390 | while (entries) { | 397 | while (entries) { |
391 | if (!ext4_valid_extent(inode, ext)) | 398 | if (!ext4_valid_extent(inode, ext)) |
392 | return 0; | 399 | return 0; |
400 | |||
401 | /* Check for overlapping extents */ | ||
402 | lblock = le32_to_cpu(ext->ee_block); | ||
403 | len = ext4_ext_get_actual_len(ext); | ||
404 | if ((lblock <= prev) && prev) { | ||
405 | pblock = ext4_ext_pblock(ext); | ||
406 | es->s_last_error_block = cpu_to_le64(pblock); | ||
407 | return 0; | ||
408 | } | ||
393 | ext++; | 409 | ext++; |
394 | entries--; | 410 | entries--; |
411 | prev = lblock + len - 1; | ||
395 | } | 412 | } |
396 | } else { | 413 | } else { |
397 | struct ext4_extent_idx *ext_idx = EXT_FIRST_INDEX(eh); | 414 | struct ext4_extent_idx *ext_idx = EXT_FIRST_INDEX(eh); |