diff options
author | Jan Kara | 2019-12-02 11:02:12 -0600 |
---|---|---|
committer | Greg Kroah-Hartman | 2019-12-31 05:37:59 -0600 |
commit | 11755d82e1adeb898976f3fb48ed6c8c67a9ed45 (patch) | |
tree | 679621de083a31e5fda48f72050ac9c0f2c039e5 | |
parent | c67a2906487c75e9415d9ea1c6ca622a9e63a769 (diff) | |
download | kernel-11755d82e1adeb898976f3fb48ed6c8c67a9ed45.tar.gz kernel-11755d82e1adeb898976f3fb48ed6c8c67a9ed45.tar.xz kernel-11755d82e1adeb898976f3fb48ed6c8c67a9ed45.zip |
ext4: fix ext4_empty_dir() for directories with holes
commit 64d4ce892383b2ad6d782e080d25502f91bf2a38 upstream.
Function ext4_empty_dir() doesn't correctly handle directories with
holes and crashes on bh->b_data dereference when bh is NULL. Reorganize
the loop to use 'offset' variable all the times instead of comparing
pointers to current direntry with bh->b_data pointer. Also add more
strict checking of '.' and '..' directory entries to avoid entering loop
in possibly invalid state on corrupted filesystems.
References: CVE-2019-19037
CC: stable@vger.kernel.org
Fixes: 4e19d6b65fb4 ("ext4: allow directory holes")
Signed-off-by: Jan Kara <jack@suse.cz>
Link: https://lore.kernel.org/r/20191202170213.4761-2-jack@suse.cz
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | fs/ext4/namei.c | 32 |
1 files changed, 18 insertions, 14 deletions
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index b4e0c270def4..0b5c36bd5418 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c | |||
@@ -2702,7 +2702,7 @@ bool ext4_empty_dir(struct inode *inode) | |||
2702 | { | 2702 | { |
2703 | unsigned int offset; | 2703 | unsigned int offset; |
2704 | struct buffer_head *bh; | 2704 | struct buffer_head *bh; |
2705 | struct ext4_dir_entry_2 *de, *de1; | 2705 | struct ext4_dir_entry_2 *de; |
2706 | struct super_block *sb; | 2706 | struct super_block *sb; |
2707 | 2707 | ||
2708 | if (ext4_has_inline_data(inode)) { | 2708 | if (ext4_has_inline_data(inode)) { |
@@ -2727,19 +2727,25 @@ bool ext4_empty_dir(struct inode *inode) | |||
2727 | return true; | 2727 | return true; |
2728 | 2728 | ||
2729 | de = (struct ext4_dir_entry_2 *) bh->b_data; | 2729 | de = (struct ext4_dir_entry_2 *) bh->b_data; |
2730 | de1 = ext4_next_entry(de, sb->s_blocksize); | 2730 | if (ext4_check_dir_entry(inode, NULL, de, bh, bh->b_data, bh->b_size, |
2731 | if (le32_to_cpu(de->inode) != inode->i_ino || | 2731 | 0) || |
2732 | le32_to_cpu(de1->inode) == 0 || | 2732 | le32_to_cpu(de->inode) != inode->i_ino || strcmp(".", de->name)) { |
2733 | strcmp(".", de->name) || strcmp("..", de1->name)) { | 2733 | ext4_warning_inode(inode, "directory missing '.'"); |
2734 | ext4_warning_inode(inode, "directory missing '.' and/or '..'"); | 2734 | brelse(bh); |
2735 | return true; | ||
2736 | } | ||
2737 | offset = ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize); | ||
2738 | de = ext4_next_entry(de, sb->s_blocksize); | ||
2739 | if (ext4_check_dir_entry(inode, NULL, de, bh, bh->b_data, bh->b_size, | ||
2740 | offset) || | ||
2741 | le32_to_cpu(de->inode) == 0 || strcmp("..", de->name)) { | ||
2742 | ext4_warning_inode(inode, "directory missing '..'"); | ||
2735 | brelse(bh); | 2743 | brelse(bh); |
2736 | return true; | 2744 | return true; |
2737 | } | 2745 | } |
2738 | offset = ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize) + | 2746 | offset += ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize); |
2739 | ext4_rec_len_from_disk(de1->rec_len, sb->s_blocksize); | ||
2740 | de = ext4_next_entry(de1, sb->s_blocksize); | ||
2741 | while (offset < inode->i_size) { | 2747 | while (offset < inode->i_size) { |
2742 | if ((void *) de >= (void *) (bh->b_data+sb->s_blocksize)) { | 2748 | if (!(offset & (sb->s_blocksize - 1))) { |
2743 | unsigned int lblock; | 2749 | unsigned int lblock; |
2744 | brelse(bh); | 2750 | brelse(bh); |
2745 | lblock = offset >> EXT4_BLOCK_SIZE_BITS(sb); | 2751 | lblock = offset >> EXT4_BLOCK_SIZE_BITS(sb); |
@@ -2750,12 +2756,11 @@ bool ext4_empty_dir(struct inode *inode) | |||
2750 | } | 2756 | } |
2751 | if (IS_ERR(bh)) | 2757 | if (IS_ERR(bh)) |
2752 | return true; | 2758 | return true; |
2753 | de = (struct ext4_dir_entry_2 *) bh->b_data; | ||
2754 | } | 2759 | } |
2760 | de = (struct ext4_dir_entry_2 *) (bh->b_data + | ||
2761 | (offset & (sb->s_blocksize - 1))); | ||
2755 | if (ext4_check_dir_entry(inode, NULL, de, bh, | 2762 | if (ext4_check_dir_entry(inode, NULL, de, bh, |
2756 | bh->b_data, bh->b_size, offset)) { | 2763 | bh->b_data, bh->b_size, offset)) { |
2757 | de = (struct ext4_dir_entry_2 *)(bh->b_data + | ||
2758 | sb->s_blocksize); | ||
2759 | offset = (offset | (sb->s_blocksize - 1)) + 1; | 2764 | offset = (offset | (sb->s_blocksize - 1)) + 1; |
2760 | continue; | 2765 | continue; |
2761 | } | 2766 | } |
@@ -2764,7 +2769,6 @@ bool ext4_empty_dir(struct inode *inode) | |||
2764 | return false; | 2769 | return false; |
2765 | } | 2770 | } |
2766 | offset += ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize); | 2771 | offset += ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize); |
2767 | de = ext4_next_entry(de, sb->s_blocksize); | ||
2768 | } | 2772 | } |
2769 | brelse(bh); | 2773 | brelse(bh); |
2770 | return true; | 2774 | return true; |