ext4: verify dir block before splitting it
authorJan Kara <jack@suse.cz>
Wed, 18 May 2022 09:33:28 +0000 (11:33 +0200)
committerTheodore Ts'o <tytso@mit.edu>
Tue, 24 May 2022 19:34:08 +0000 (15:34 -0400)
Before splitting a directory block verify its directory entries are sane
so that the splitting code does not access memory it should not.

Cc: stable@vger.kernel.org
Signed-off-by: Jan Kara <jack@suse.cz>
Link: https://lore.kernel.org/r/20220518093332.13986-1-jack@suse.cz
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
fs/ext4/namei.c

index 7aca8944901dfb6d941b7ba0ac5ad5b6ac7122b1..7286472e9558affd1955412f094b4d76387fce1e 100644 (file)
@@ -277,9 +277,9 @@ static struct dx_frame *dx_probe(struct ext4_filename *fname,
                                 struct dx_hash_info *hinfo,
                                 struct dx_frame *frame);
 static void dx_release(struct dx_frame *frames);
-static int dx_make_map(struct inode *dir, struct ext4_dir_entry_2 *de,
-                      unsigned blocksize, struct dx_hash_info *hinfo,
-                      struct dx_map_entry map[]);
+static int dx_make_map(struct inode *dir, struct buffer_head *bh,
+                      struct dx_hash_info *hinfo,
+                      struct dx_map_entry *map_tail);
 static void dx_sort_map(struct dx_map_entry *map, unsigned count);
 static struct ext4_dir_entry_2 *dx_move_dirents(struct inode *dir, char *from,
                                        char *to, struct dx_map_entry *offsets,
@@ -1249,15 +1249,23 @@ static inline int search_dirblock(struct buffer_head *bh,
  * Create map of hash values, offsets, and sizes, stored at end of block.
  * Returns number of entries mapped.
  */
-static int dx_make_map(struct inode *dir, struct ext4_dir_entry_2 *de,
-                      unsigned blocksize, struct dx_hash_info *hinfo,
+static int dx_make_map(struct inode *dir, struct buffer_head *bh,
+                      struct dx_hash_info *hinfo,
                       struct dx_map_entry *map_tail)
 {
        int count = 0;
-       char *base = (char *) de;
+       struct ext4_dir_entry_2 *de = (struct ext4_dir_entry_2 *)bh->b_data;
+       unsigned int buflen = bh->b_size;
+       char *base = bh->b_data;
        struct dx_hash_info h = *hinfo;
 
-       while ((char *) de < base + blocksize) {
+       if (ext4_has_metadata_csum(dir->i_sb))
+               buflen -= sizeof(struct ext4_dir_entry_tail);
+
+       while ((char *) de < base + buflen) {
+               if (ext4_check_dir_entry(dir, NULL, de, bh, base, buflen,
+                                        ((char *)de) - base))
+                       return -EFSCORRUPTED;
                if (de->name_len && de->inode) {
                        if (ext4_hash_in_dirent(dir))
                                h.hash = EXT4_DIRENT_HASH(de);
@@ -1270,8 +1278,7 @@ static int dx_make_map(struct inode *dir, struct ext4_dir_entry_2 *de,
                        count++;
                        cond_resched();
                }
-               /* XXX: do we need to check rec_len == 0 case? -Chris */
-               de = ext4_next_entry(de, blocksize);
+               de = ext4_next_entry(de, dir->i_sb->s_blocksize);
        }
        return count;
 }
@@ -1943,8 +1950,11 @@ static struct ext4_dir_entry_2 *do_split(handle_t *handle, struct inode *dir,
 
        /* create map in the end of data2 block */
        map = (struct dx_map_entry *) (data2 + blocksize);
-       count = dx_make_map(dir, (struct ext4_dir_entry_2 *) data1,
-                            blocksize, hinfo, map);
+       count = dx_make_map(dir, *bh, hinfo, map);
+       if (count < 0) {
+               err = count;
+               goto journal_error;
+       }
        map -= count;
        dx_sort_map(map, count);
        /* Ensure that neither split block is over half full */