add patch set-ext4_dax_aops-for-dax-files
[ext4-patch-queue.git] / avoid-buffer-overrun-when-deleting-inline-directories
blob0d52231123726df17358b4d0e7ed467c9616cb68
1 ext4: avoid divide by zero fault when deleting corrupted inline directories
3 A specially crafted file system can trick empty_inline_dir() into
4 reading past the last valid entry in a inline directory, and then run
5 into the end of xattr marker. This will trigger a divide by zero
6 fault.  Fix this by using the size of the inline directory instead of
7 dir->i_size.
9 Also clean up error reporting in __ext4_check_dir_entry so that the
10 message is clearer and more understandable --- and avoids the division
11 by zero trap if the size passed in is zero.  (I'm not sure why we
12 coded it that way in the first place; printing offset % size is
13 actually more confusing and less useful.)
15 https://bugzilla.kernel.org/show_bug.cgi?id=200933
17 Signed-off-by: Theodore Ts'o <tytso@mit.edu>
18 Reported-by: Wen Xu <wen.xu@gatech.edu>
19 Cc: stable@vger.kernel.org
20 diff --git a/fs/ext4/dir.c b/fs/ext4/dir.c
21 index e2902d394f1b..f93f9881ec18 100644
22 --- a/fs/ext4/dir.c
23 +++ b/fs/ext4/dir.c
24 @@ -76,7 +76,7 @@ int __ext4_check_dir_entry(const char *function, unsigned int line,
25         else if (unlikely(rlen < EXT4_DIR_REC_LEN(de->name_len)))
26                 error_msg = "rec_len is too small for name_len";
27         else if (unlikely(((char *) de - buf) + rlen > size))
28 -               error_msg = "directory entry across range";
29 +               error_msg = "directory entry overrun";
30         else if (unlikely(le32_to_cpu(de->inode) >
31                         le32_to_cpu(EXT4_SB(dir->i_sb)->s_es->s_inodes_count)))
32                 error_msg = "inode out of bounds";
33 @@ -85,18 +85,16 @@ int __ext4_check_dir_entry(const char *function, unsigned int line,
35         if (filp)
36                 ext4_error_file(filp, function, line, bh->b_blocknr,
37 -                               "bad entry in directory: %s - offset=%u(%u), "
38 -                               "inode=%u, rec_len=%d, name_len=%d",
39 -                               error_msg, (unsigned) (offset % size),
40 -                               offset, le32_to_cpu(de->inode),
41 -                               rlen, de->name_len);
42 +                               "bad entry in directory: %s - offset=%u, "
43 +                               "inode=%u, rec_len=%d, name_len=%d, size=%d",
44 +                               error_msg, offset, le32_to_cpu(de->inode),
45 +                               rlen, de->name_len, size);
46         else
47                 ext4_error_inode(dir, function, line, bh->b_blocknr,
48 -                               "bad entry in directory: %s - offset=%u(%u), "
49 -                               "inode=%u, rec_len=%d, name_len=%d",
50 -                               error_msg, (unsigned) (offset % size),
51 -                               offset, le32_to_cpu(de->inode),
52 -                               rlen, de->name_len);
53 +                               "bad entry in directory: %s - offset=%u, "
54 +                               "inode=%u, rec_len=%d, name_len=%d, size=%d",
55 +                                error_msg, offset, le32_to_cpu(de->inode),
56 +                                rlen, de->name_len, size);
58         return 1;
59  }
60 diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c
61 index 3543fe80a3c4..7b4736022761 100644
62 --- a/fs/ext4/inline.c
63 +++ b/fs/ext4/inline.c
64 @@ -1753,6 +1753,7 @@ bool empty_inline_dir(struct inode *dir, int *has_inline_data)
65  {
66         int err, inline_size;
67         struct ext4_iloc iloc;
68 +       size_t inline_len;
69         void *inline_pos;
70         unsigned int offset;
71         struct ext4_dir_entry_2 *de;
72 @@ -1780,8 +1781,9 @@ bool empty_inline_dir(struct inode *dir, int *has_inline_data)
73                 goto out;
74         }
76 +       inline_len = ext4_get_inline_size(dir);
77         offset = EXT4_INLINE_DOTDOT_SIZE;
78 -       while (offset < dir->i_size) {
79 +       while (offset < inline_len) {
80                 de = ext4_get_inline_entry(dir, &iloc, offset,
81                                            &inline_pos, &inline_size);
82                 if (ext4_check_dir_entry(dir, NULL, de,