Skip to content

Commit 3ba733f

Browse files
jankaratytso
authored andcommitted
ext4: avoid cycles in directory h-tree
A maliciously corrupted filesystem can contain cycles in the h-tree stored inside a directory. That can easily lead to the kernel corrupting tree nodes that were already verified under its hands while doing a node split and consequently accessing unallocated memory. Fix the problem by verifying traversed block numbers are unique. Cc: [email protected] Signed-off-by: Jan Kara <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Theodore Ts'o <[email protected]>
1 parent 46c116b commit 3ba733f

File tree

1 file changed

+19
-3
lines changed

1 file changed

+19
-3
lines changed

fs/ext4/namei.c

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -777,12 +777,14 @@ static struct dx_frame *
777777
dx_probe(struct ext4_filename *fname, struct inode *dir,
778778
struct dx_hash_info *hinfo, struct dx_frame *frame_in)
779779
{
780-
unsigned count, indirect;
780+
unsigned count, indirect, level, i;
781781
struct dx_entry *at, *entries, *p, *q, *m;
782782
struct dx_root *root;
783783
struct dx_frame *frame = frame_in;
784784
struct dx_frame *ret_err = ERR_PTR(ERR_BAD_DX_DIR);
785785
u32 hash;
786+
ext4_lblk_t block;
787+
ext4_lblk_t blocks[EXT4_HTREE_LEVEL];
786788

787789
memset(frame_in, 0, EXT4_HTREE_LEVEL * sizeof(frame_in[0]));
788790
frame->bh = ext4_read_dirblock(dir, 0, INDEX);
@@ -854,6 +856,8 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,
854856
}
855857

856858
dxtrace(printk("Look up %x", hash));
859+
level = 0;
860+
blocks[0] = 0;
857861
while (1) {
858862
count = dx_get_count(entries);
859863
if (!count || count > dx_get_limit(entries)) {
@@ -882,15 +886,27 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,
882886
dx_get_block(at)));
883887
frame->entries = entries;
884888
frame->at = at;
885-
if (!indirect--)
889+
890+
block = dx_get_block(at);
891+
for (i = 0; i <= level; i++) {
892+
if (blocks[i] == block) {
893+
ext4_warning_inode(dir,
894+
"dx entry: tree cycle block %u points back to block %u",
895+
blocks[level], block);
896+
goto fail;
897+
}
898+
}
899+
if (++level > indirect)
886900
return frame;
901+
blocks[level] = block;
887902
frame++;
888-
frame->bh = ext4_read_dirblock(dir, dx_get_block(at), INDEX);
903+
frame->bh = ext4_read_dirblock(dir, block, INDEX);
889904
if (IS_ERR(frame->bh)) {
890905
ret_err = (struct dx_frame *) frame->bh;
891906
frame->bh = NULL;
892907
goto fail;
893908
}
909+
894910
entries = ((struct dx_node *) frame->bh->b_data)->entries;
895911

896912
if (dx_get_limit(entries) != dx_node_limit(dir)) {

0 commit comments

Comments
 (0)