| From 0eb21f9828dbd55ae6af91db9d13f5bcef487c40 Mon Sep 17 00:00:00 2001 |
| From: Jan Kara <jack@suse.cz> |
| Date: Mon, 2 Dec 2019 18:02:12 +0100 |
| Subject: [PATCH] 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: Paul Gortmaker <paul.gortmaker@windriver.com> |
| |
| diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c |
| index 447974a4fe66..0146ceb40631 100644 |
| --- a/fs/ext4/namei.c |
| +++ b/fs/ext4/namei.c |
| @@ -2784,7 +2784,7 @@ bool ext4_empty_dir(struct inode *inode) |
| { |
| unsigned int offset; |
| struct buffer_head *bh; |
| - struct ext4_dir_entry_2 *de, *de1; |
| + struct ext4_dir_entry_2 *de; |
| struct super_block *sb; |
| |
| if (ext4_has_inline_data(inode)) { |
| @@ -2809,19 +2809,25 @@ bool ext4_empty_dir(struct inode *inode) |
| return true; |
| |
| de = (struct ext4_dir_entry_2 *) bh->b_data; |
| - de1 = ext4_next_entry(de, sb->s_blocksize); |
| - if (le32_to_cpu(de->inode) != inode->i_ino || |
| - le32_to_cpu(de1->inode) == 0 || |
| - strcmp(".", de->name) || strcmp("..", de1->name)) { |
| - ext4_warning_inode(inode, "directory missing '.' and/or '..'"); |
| + if (ext4_check_dir_entry(inode, NULL, de, bh, bh->b_data, bh->b_size, |
| + 0) || |
| + le32_to_cpu(de->inode) != inode->i_ino || strcmp(".", de->name)) { |
| + ext4_warning_inode(inode, "directory missing '.'"); |
| + brelse(bh); |
| + return true; |
| + } |
| + offset = ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize); |
| + de = ext4_next_entry(de, sb->s_blocksize); |
| + if (ext4_check_dir_entry(inode, NULL, de, bh, bh->b_data, bh->b_size, |
| + offset) || |
| + le32_to_cpu(de->inode) == 0 || strcmp("..", de->name)) { |
| + ext4_warning_inode(inode, "directory missing '..'"); |
| brelse(bh); |
| return true; |
| } |
| - offset = ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize) + |
| - ext4_rec_len_from_disk(de1->rec_len, sb->s_blocksize); |
| - de = ext4_next_entry(de1, sb->s_blocksize); |
| + offset += ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize); |
| while (offset < inode->i_size) { |
| - if ((void *) de >= (void *) (bh->b_data+sb->s_blocksize)) { |
| + if (!(offset & (sb->s_blocksize - 1))) { |
| unsigned int lblock; |
| brelse(bh); |
| lblock = offset >> EXT4_BLOCK_SIZE_BITS(sb); |
| @@ -2832,12 +2838,11 @@ bool ext4_empty_dir(struct inode *inode) |
| } |
| if (IS_ERR(bh)) |
| return true; |
| - de = (struct ext4_dir_entry_2 *) bh->b_data; |
| } |
| + de = (struct ext4_dir_entry_2 *) (bh->b_data + |
| + (offset & (sb->s_blocksize - 1))); |
| if (ext4_check_dir_entry(inode, NULL, de, bh, |
| bh->b_data, bh->b_size, offset)) { |
| - de = (struct ext4_dir_entry_2 *)(bh->b_data + |
| - sb->s_blocksize); |
| offset = (offset | (sb->s_blocksize - 1)) + 1; |
| continue; |
| } |
| @@ -2846,7 +2851,6 @@ bool ext4_empty_dir(struct inode *inode) |
| return false; |
| } |
| offset += ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize); |
| - de = ext4_next_entry(de, sb->s_blocksize); |
| } |
| brelse(bh); |
| return true; |
| -- |
| 2.7.4 |
| |