diff options
author | Theodore Ts'o <tytso@mit.edu> | 2018-08-27 09:22:45 -0400 |
---|---|---|
committer | Theodore Ts'o <tytso@mit.edu> | 2018-08-27 09:22:45 -0400 |
commit | 4d982e25d0bdc83d8c64e66fdeca0b89240b3b85 (patch) | |
tree | f1775363ff05e10010530d4fc5a2201f7fae02d6 | |
parent | b50282f3241acee880514212d88b6049fb5039c8 (diff) |
ext4: avoid divide by zero fault when deleting corrupted inline directories
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
-rw-r--r-- | fs/ext4/dir.c | 20 | ||||
-rw-r--r-- | fs/ext4/inline.c | 4 |
2 files changed, 12 insertions, 12 deletions
diff --git a/fs/ext4/dir.c b/fs/ext4/dir.c index e2902d394f1b..f93f9881ec18 100644 --- a/fs/ext4/dir.c +++ b/fs/ext4/dir.c | |||
@@ -76,7 +76,7 @@ int __ext4_check_dir_entry(const char *function, unsigned int line, | |||
76 | else if (unlikely(rlen < EXT4_DIR_REC_LEN(de->name_len))) | 76 | else if (unlikely(rlen < EXT4_DIR_REC_LEN(de->name_len))) |
77 | error_msg = "rec_len is too small for name_len"; | 77 | error_msg = "rec_len is too small for name_len"; |
78 | else if (unlikely(((char *) de - buf) + rlen > size)) | 78 | else if (unlikely(((char *) de - buf) + rlen > size)) |
79 | error_msg = "directory entry across range"; | 79 | error_msg = "directory entry overrun"; |
80 | else if (unlikely(le32_to_cpu(de->inode) > | 80 | else if (unlikely(le32_to_cpu(de->inode) > |
81 | le32_to_cpu(EXT4_SB(dir->i_sb)->s_es->s_inodes_count))) | 81 | le32_to_cpu(EXT4_SB(dir->i_sb)->s_es->s_inodes_count))) |
82 | error_msg = "inode out of bounds"; | 82 | error_msg = "inode out of bounds"; |
@@ -85,18 +85,16 @@ int __ext4_check_dir_entry(const char *function, unsigned int line, | |||
85 | 85 | ||
86 | if (filp) | 86 | if (filp) |
87 | ext4_error_file(filp, function, line, bh->b_blocknr, | 87 | ext4_error_file(filp, function, line, bh->b_blocknr, |
88 | "bad entry in directory: %s - offset=%u(%u), " | 88 | "bad entry in directory: %s - offset=%u, " |
89 | "inode=%u, rec_len=%d, name_len=%d", | 89 | "inode=%u, rec_len=%d, name_len=%d, size=%d", |
90 | error_msg, (unsigned) (offset % size), | 90 | error_msg, offset, le32_to_cpu(de->inode), |
91 | offset, le32_to_cpu(de->inode), | 91 | rlen, de->name_len, size); |
92 | rlen, de->name_len); | ||
93 | else | 92 | else |
94 | ext4_error_inode(dir, function, line, bh->b_blocknr, | 93 | ext4_error_inode(dir, function, line, bh->b_blocknr, |
95 | "bad entry in directory: %s - offset=%u(%u), " | 94 | "bad entry in directory: %s - offset=%u, " |
96 | "inode=%u, rec_len=%d, name_len=%d", | 95 | "inode=%u, rec_len=%d, name_len=%d, size=%d", |
97 | error_msg, (unsigned) (offset % size), | 96 | error_msg, offset, le32_to_cpu(de->inode), |
98 | offset, le32_to_cpu(de->inode), | 97 | rlen, de->name_len, size); |
99 | rlen, de->name_len); | ||
100 | 98 | ||
101 | return 1; | 99 | return 1; |
102 | } | 100 | } |
diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c index 3543fe80a3c4..7b4736022761 100644 --- a/fs/ext4/inline.c +++ b/fs/ext4/inline.c | |||
@@ -1753,6 +1753,7 @@ bool empty_inline_dir(struct inode *dir, int *has_inline_data) | |||
1753 | { | 1753 | { |
1754 | int err, inline_size; | 1754 | int err, inline_size; |
1755 | struct ext4_iloc iloc; | 1755 | struct ext4_iloc iloc; |
1756 | size_t inline_len; | ||
1756 | void *inline_pos; | 1757 | void *inline_pos; |
1757 | unsigned int offset; | 1758 | unsigned int offset; |
1758 | struct ext4_dir_entry_2 *de; | 1759 | struct ext4_dir_entry_2 *de; |
@@ -1780,8 +1781,9 @@ bool empty_inline_dir(struct inode *dir, int *has_inline_data) | |||
1780 | goto out; | 1781 | goto out; |
1781 | } | 1782 | } |
1782 | 1783 | ||
1784 | inline_len = ext4_get_inline_size(dir); | ||
1783 | offset = EXT4_INLINE_DOTDOT_SIZE; | 1785 | offset = EXT4_INLINE_DOTDOT_SIZE; |
1784 | while (offset < dir->i_size) { | 1786 | while (offset < inline_len) { |
1785 | de = ext4_get_inline_entry(dir, &iloc, offset, | 1787 | de = ext4_get_inline_entry(dir, &iloc, offset, |
1786 | &inline_pos, &inline_size); | 1788 | &inline_pos, &inline_size); |
1787 | if (ext4_check_dir_entry(dir, NULL, de, | 1789 | if (ext4_check_dir_entry(dir, NULL, de, |