| From 4d982e25d0bdc83d8c64e66fdeca0b89240b3b85 Mon Sep 17 00:00:00 2001 |
| From: Theodore Ts'o <tytso@mit.edu> |
| Date: Mon, 27 Aug 2018 09:22:45 -0400 |
| Subject: ext4: avoid divide by zero fault when deleting corrupted inline directories |
| |
| From: Theodore Ts'o <tytso@mit.edu> |
| |
| commit 4d982e25d0bdc83d8c64e66fdeca0b89240b3b85 upstream. |
| |
| A specially crafted file system can trick empty_inline_dir() into |
| reading past the last valid entry in a inline directory, and then run |
| into the end of xattr marker. This will trigger a divide by zero |
| fault. Fix this by using the size of the inline directory instead of |
| dir->i_size. |
| |
| Also clean up error reporting in __ext4_check_dir_entry so that the |
| message is clearer and more understandable --- and avoids the division |
| by zero trap if the size passed in is zero. (I'm not sure why we |
| coded it that way in the first place; printing offset % size is |
| actually more confusing and less useful.) |
| |
| https://bugzilla.kernel.org/show_bug.cgi?id=200933 |
| |
| Signed-off-by: Theodore Ts'o <tytso@mit.edu> |
| Reported-by: Wen Xu <wen.xu@gatech.edu> |
| Cc: stable@vger.kernel.org |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| fs/ext4/dir.c | 20 +++++++++----------- |
| fs/ext4/inline.c | 4 +++- |
| 2 files changed, 12 insertions(+), 12 deletions(-) |
| |
| --- a/fs/ext4/dir.c |
| +++ b/fs/ext4/dir.c |
| @@ -75,7 +75,7 @@ int __ext4_check_dir_entry(const char *f |
| else if (unlikely(rlen < EXT4_DIR_REC_LEN(de->name_len))) |
| error_msg = "rec_len is too small for name_len"; |
| else if (unlikely(((char *) de - buf) + rlen > size)) |
| - error_msg = "directory entry across range"; |
| + error_msg = "directory entry overrun"; |
| else if (unlikely(le32_to_cpu(de->inode) > |
| le32_to_cpu(EXT4_SB(dir->i_sb)->s_es->s_inodes_count))) |
| error_msg = "inode out of bounds"; |
| @@ -84,18 +84,16 @@ int __ext4_check_dir_entry(const char *f |
| |
| if (filp) |
| ext4_error_file(filp, function, line, bh->b_blocknr, |
| - "bad entry in directory: %s - offset=%u(%u), " |
| - "inode=%u, rec_len=%d, name_len=%d", |
| - error_msg, (unsigned) (offset % size), |
| - offset, le32_to_cpu(de->inode), |
| - rlen, de->name_len); |
| + "bad entry in directory: %s - offset=%u, " |
| + "inode=%u, rec_len=%d, name_len=%d, size=%d", |
| + error_msg, offset, le32_to_cpu(de->inode), |
| + rlen, de->name_len, size); |
| else |
| ext4_error_inode(dir, function, line, bh->b_blocknr, |
| - "bad entry in directory: %s - offset=%u(%u), " |
| - "inode=%u, rec_len=%d, name_len=%d", |
| - error_msg, (unsigned) (offset % size), |
| - offset, le32_to_cpu(de->inode), |
| - rlen, de->name_len); |
| + "bad entry in directory: %s - offset=%u, " |
| + "inode=%u, rec_len=%d, name_len=%d, size=%d", |
| + error_msg, offset, le32_to_cpu(de->inode), |
| + rlen, de->name_len, size); |
| |
| return 1; |
| } |
| --- a/fs/ext4/inline.c |
| +++ b/fs/ext4/inline.c |
| @@ -1759,6 +1759,7 @@ bool empty_inline_dir(struct inode *dir, |
| { |
| int err, inline_size; |
| struct ext4_iloc iloc; |
| + size_t inline_len; |
| void *inline_pos; |
| unsigned int offset; |
| struct ext4_dir_entry_2 *de; |
| @@ -1786,8 +1787,9 @@ bool empty_inline_dir(struct inode *dir, |
| goto out; |
| } |
| |
| + inline_len = ext4_get_inline_size(dir); |
| offset = EXT4_INLINE_DOTDOT_SIZE; |
| - while (offset < dir->i_size) { |
| + while (offset < inline_len) { |
| de = ext4_get_inline_entry(dir, &iloc, offset, |
| &inline_pos, &inline_size); |
| if (ext4_check_dir_entry(dir, NULL, de, |