Skip to content

Commit 48a3431

Browse files
jankaratytso
authored andcommitted
ext4: fix checksum errors with indexed dirs
DIR_INDEX has been introduced as a compat ext4 feature. That means that even kernels / tools that don't understand the feature may modify the filesystem. This works because for kernels not understanding indexed dir format, internal htree nodes appear just as empty directory entries. Index dir aware kernels then check the htree structure is still consistent before using the data. This all worked reasonably well until metadata checksums were introduced. The problem is that these effectively made DIR_INDEX only ro-compatible because internal htree nodes store checksums in a different place than normal directory blocks. Thus any modification ignorant to DIR_INDEX (or just clearing EXT4_INDEX_FL from the inode) will effectively cause checksum mismatch and trigger kernel errors. So we have to be more careful when dealing with indexed directories on filesystems with checksumming enabled. 1) We just disallow loading any directory inodes with EXT4_INDEX_FL when DIR_INDEX is not enabled. This is harsh but it should be very rare (it means someone disabled DIR_INDEX on existing filesystem and didn't run e2fsck), e2fsck can fix the problem, and we don't want to answer the difficult question: "Should we rather corrupt the directory more or should we ignore that DIR_INDEX feature is not set?" 2) When we find out htree structure is corrupted (but the filesystem and the directory should in support htrees), we continue just ignoring htree information for reading but we refuse to add new entries to the directory to avoid corrupting it more. Link: https://lore.kernel.org/r/[email protected] Fixes: dbe8944 ("ext4: Calculate and verify checksums for htree nodes") Reviewed-by: Andreas Dilger <[email protected]> Signed-off-by: Jan Kara <[email protected]> Signed-off-by: Theodore Ts'o <[email protected]> Cc: [email protected]
1 parent 4f97a68 commit 48a3431

File tree

4 files changed

+31
-7
lines changed

4 files changed

+31
-7
lines changed

fs/ext4/dir.c

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,14 @@ static int ext4_readdir(struct file *file, struct dir_context *ctx)
129129
if (err != ERR_BAD_DX_DIR) {
130130
return err;
131131
}
132-
/*
133-
* We don't set the inode dirty flag since it's not
134-
* critical that it get flushed back to the disk.
135-
*/
136-
ext4_clear_inode_flag(file_inode(file),
137-
EXT4_INODE_INDEX);
132+
/* Can we just clear INDEX flag to ignore htree information? */
133+
if (!ext4_has_metadata_csum(sb)) {
134+
/*
135+
* We don't set the inode dirty flag since it's not
136+
* critical that it gets flushed back to the disk.
137+
*/
138+
ext4_clear_inode_flag(inode, EXT4_INODE_INDEX);
139+
}
138140
}
139141

140142
if (ext4_has_inline_data(inode)) {

fs/ext4/ext4.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2544,8 +2544,11 @@ void ext4_insert_dentry(struct inode *inode,
25442544
struct ext4_filename *fname);
25452545
static inline void ext4_update_dx_flag(struct inode *inode)
25462546
{
2547-
if (!ext4_has_feature_dir_index(inode->i_sb))
2547+
if (!ext4_has_feature_dir_index(inode->i_sb)) {
2548+
/* ext4_iget() should have caught this... */
2549+
WARN_ON_ONCE(ext4_has_feature_metadata_csum(inode->i_sb));
25482550
ext4_clear_inode_flag(inode, EXT4_INODE_INDEX);
2551+
}
25492552
}
25502553
static const unsigned char ext4_filetype_table[] = {
25512554
DT_UNKNOWN, DT_REG, DT_DIR, DT_CHR, DT_BLK, DT_FIFO, DT_SOCK, DT_LNK

fs/ext4/inode.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4644,6 +4644,18 @@ struct inode *__ext4_iget(struct super_block *sb, unsigned long ino,
46444644
ret = -EFSCORRUPTED;
46454645
goto bad_inode;
46464646
}
4647+
/*
4648+
* If dir_index is not enabled but there's dir with INDEX flag set,
4649+
* we'd normally treat htree data as empty space. But with metadata
4650+
* checksumming that corrupts checksums so forbid that.
4651+
*/
4652+
if (!ext4_has_feature_dir_index(sb) && ext4_has_metadata_csum(sb) &&
4653+
ext4_test_inode_flag(inode, EXT4_INODE_INDEX)) {
4654+
ext4_error_inode(inode, function, line, 0,
4655+
"iget: Dir with htree data on filesystem without dir_index feature.");
4656+
ret = -EFSCORRUPTED;
4657+
goto bad_inode;
4658+
}
46474659
ei->i_disksize = inode->i_size;
46484660
#ifdef CONFIG_QUOTA
46494661
ei->i_reserved_quota = 0;

fs/ext4/namei.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2213,6 +2213,13 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
22132213
retval = ext4_dx_add_entry(handle, &fname, dir, inode);
22142214
if (!retval || (retval != ERR_BAD_DX_DIR))
22152215
goto out;
2216+
/* Can we just ignore htree data? */
2217+
if (ext4_has_metadata_csum(sb)) {
2218+
EXT4_ERROR_INODE(dir,
2219+
"Directory has corrupted htree index.");
2220+
retval = -EFSCORRUPTED;
2221+
goto out;
2222+
}
22162223
ext4_clear_inode_flag(dir, EXT4_INODE_INDEX);
22172224
dx_fallback++;
22182225
ext4_mark_inode_dirty(handle, dir);

0 commit comments

Comments
 (0)