diff --git a/fs/ext4/dir.c b/fs/ext4/dir.c index d4164c507a90..efe55c41ada7 100644 --- a/fs/ext4/dir.c +++ b/fs/ext4/dir.c @@ -67,6 +67,31 @@ static bool is_fake_dir_entry(struct ext4_dir_entry_2 *de) return false; } +/* + * Helper function to report out-of-buffer directory entries. + */ +static int __ext4_early_overrun(const char *function, unsigned int line, + struct inode *dir, struct file *filp, + struct buffer_head *bh, char *buf, + int size, unsigned int offset, int rlen) +{ + char msg[128]; + int n; + + n = scnprintf(msg, sizeof(msg), "directory entry overrun: offset=%u", + offset); + if (rlen == -1) + scnprintf(msg + n, sizeof(msg) - n, " size=%d", size); + else + scnprintf(msg + n, sizeof(msg) - n, " length=%d size=%d", + rlen, size); + if (filp) + ext4_error_file(filp, function, line, bh->b_blocknr, msg); + else + ext4_error_inode(dir, function, line, bh->b_blocknr, msg); + return 1; +} + /* * Return 0 if the directory entry is OK, and 1 if there is a problem * @@ -81,12 +106,25 @@ int __ext4_check_dir_entry(const char *function, unsigned int line, struct buffer_head *bh, char *buf, int size, unsigned int offset) { + bool fake, has_csum = ext4_has_feature_metadata_csum(dir->i_sb); const char *error_msg = NULL; - const int rlen = ext4_rec_len_from_disk(de->rec_len, - dir->i_sb->s_blocksize); - const int next_offset = ((char *) de - buf) + rlen; - bool fake = is_fake_dir_entry(de); - bool has_csum = ext4_has_feature_metadata_csum(dir->i_sb); + int rlen = -1, next_offset; + + /* Check whether the shortest possible dirent may be read + * from within the specified buffer at specified offset. + */ + if (offset + offsetof(struct ext4_dir_entry_2, name) > size) + return __ext4_early_overrun(function, line, dir, filp, + bh, buf, size, offset, rlen); + + /* Next, likewise for the dirent of a claimed length. */ + rlen = ext4_rec_len_from_disk(de->rec_len, dir->i_sb->s_blocksize); + if (offset + rlen > size) + return __ext4_early_overrun(function, line, dir, filp, + bh, buf, size, offset, rlen); + + next_offset = ((char *) de - buf) + rlen; + fake = is_fake_dir_entry(de); if (unlikely(rlen < ext4_dir_rec_len(1, fake ? NULL : dir))) error_msg = "rec_len is smaller than minimal"; diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index a178ac229489..8aa0d68dae71 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -1461,7 +1461,7 @@ int ext4_search_dir(struct buffer_head *bh, char *search_buf, int buf_size, char * dlimit; int de_len; - de = (struct ext4_dir_entry_2 *)search_buf; + de = (struct ext4_dir_entry_2 *)search_buf + offset; dlimit = search_buf + buf_size; while ((char *) de < dlimit - EXT4_BASE_DIR_LEN) { /* this code is executed quadratically often */