Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ext4: improve error handling from ext4_dirhash()

The ext4_dirhash() will *almost* never fail, especially when the hash
tree feature was first introduced. However, with the addition of
support of encrypted, casefolded file names, that function can most
certainly fail today.

So make sure the callers of ext4_dirhash() properly check for
failures, and reflect the errors back up to their callers.

Cc: stable@kernel.org
Link: https://lore.kernel.org/r/20230506142419.984260-1-tytso@mit.edu
Reported-by: syzbot+394aa8a792cb99dbc837@syzkaller.appspotmail.com
Reported-by: syzbot+344aaa8697ebd232bfc8@syzkaller.appspotmail.com
Link: https://syzkaller.appspot.com/bug?id=db56459ea4ac4a676ae4b4678f633e55da005a9b
Signed-off-by: Theodore Ts'o <tytso@mit.edu>

+42 -17
+5 -1
fs/ext4/hash.c
··· 277 277 } 278 278 default: 279 279 hinfo->hash = 0; 280 - return -1; 280 + hinfo->minor_hash = 0; 281 + ext4_warning(dir->i_sb, 282 + "invalid/unsupported hash tree version %u", 283 + hinfo->hash_version); 284 + return -EINVAL; 281 285 } 282 286 hash = hash & ~1; 283 287 if (hash == (EXT4_HTREE_EOF_32BIT << 1))
+37 -16
fs/ext4/namei.c
··· 674 674 len = de->name_len; 675 675 if (!IS_ENCRYPTED(dir)) { 676 676 /* Directory is not encrypted */ 677 - ext4fs_dirhash(dir, de->name, 677 + (void) ext4fs_dirhash(dir, de->name, 678 678 de->name_len, &h); 679 679 printk("%*.s:(U)%x.%u ", len, 680 680 name, h.hash, ··· 709 709 if (IS_CASEFOLDED(dir)) 710 710 h.hash = EXT4_DIRENT_HASH(de); 711 711 else 712 - ext4fs_dirhash(dir, de->name, 713 - de->name_len, &h); 712 + (void) ext4fs_dirhash(dir, 713 + de->name, 714 + de->name_len, &h); 714 715 printk("%*.s:(E)%x.%u ", len, name, 715 716 h.hash, (unsigned) ((char *) de 716 717 - base)); ··· 721 720 #else 722 721 int len = de->name_len; 723 722 char *name = de->name; 724 - ext4fs_dirhash(dir, de->name, de->name_len, &h); 723 + (void) ext4fs_dirhash(dir, de->name, 724 + de->name_len, &h); 725 725 printk("%*.s:%x.%u ", len, name, h.hash, 726 726 (unsigned) ((char *) de - base)); 727 727 #endif ··· 851 849 hinfo->seed = EXT4_SB(dir->i_sb)->s_hash_seed; 852 850 /* hash is already computed for encrypted casefolded directory */ 853 851 if (fname && fname_name(fname) && 854 - !(IS_ENCRYPTED(dir) && IS_CASEFOLDED(dir))) 855 - ext4fs_dirhash(dir, fname_name(fname), fname_len(fname), hinfo); 852 + !(IS_ENCRYPTED(dir) && IS_CASEFOLDED(dir))) { 853 + int ret = ext4fs_dirhash(dir, fname_name(fname), 854 + fname_len(fname), hinfo); 855 + if (ret < 0) { 856 + ret_err = ERR_PTR(ret); 857 + goto fail; 858 + } 859 + } 856 860 hash = hinfo->hash; 857 861 858 862 if (root->info.unused_flags & 1) { ··· 1119 1111 hinfo->minor_hash = 0; 1120 1112 } 1121 1113 } else { 1122 - ext4fs_dirhash(dir, de->name, de->name_len, hinfo); 1114 + err = ext4fs_dirhash(dir, de->name, 1115 + de->name_len, hinfo); 1116 + if (err < 0) { 1117 + count = err; 1118 + goto errout; 1119 + } 1123 1120 } 1124 1121 if ((hinfo->hash < start_hash) || 1125 1122 ((hinfo->hash == start_hash) && ··· 1326 1313 if (de->name_len && de->inode) { 1327 1314 if (ext4_hash_in_dirent(dir)) 1328 1315 h.hash = EXT4_DIRENT_HASH(de); 1329 - else 1330 - ext4fs_dirhash(dir, de->name, de->name_len, &h); 1316 + else { 1317 + int err = ext4fs_dirhash(dir, de->name, 1318 + de->name_len, &h); 1319 + if (err < 0) 1320 + return err; 1321 + } 1331 1322 map_tail--; 1332 1323 map_tail->hash = h.hash; 1333 1324 map_tail->offs = ((char *) de - base)>>2; ··· 1469 1452 hinfo->hash_version = DX_HASH_SIPHASH; 1470 1453 hinfo->seed = NULL; 1471 1454 if (cf_name->name) 1472 - ext4fs_dirhash(dir, cf_name->name, cf_name->len, hinfo); 1455 + return ext4fs_dirhash(dir, cf_name->name, cf_name->len, hinfo); 1473 1456 else 1474 - ext4fs_dirhash(dir, iname->name, iname->len, hinfo); 1475 - return 0; 1457 + return ext4fs_dirhash(dir, iname->name, iname->len, hinfo); 1476 1458 } 1477 1459 #endif 1478 1460 ··· 2314 2298 fname->hinfo.seed = EXT4_SB(dir->i_sb)->s_hash_seed; 2315 2299 2316 2300 /* casefolded encrypted hashes are computed on fname setup */ 2317 - if (!ext4_hash_in_dirent(dir)) 2318 - ext4fs_dirhash(dir, fname_name(fname), 2319 - fname_len(fname), &fname->hinfo); 2320 - 2301 + if (!ext4_hash_in_dirent(dir)) { 2302 + int err = ext4fs_dirhash(dir, fname_name(fname), 2303 + fname_len(fname), &fname->hinfo); 2304 + if (err < 0) { 2305 + brelse(bh2); 2306 + brelse(bh); 2307 + return err; 2308 + } 2309 + } 2321 2310 memset(frames, 0, sizeof(frames)); 2322 2311 frame = frames; 2323 2312 frame->entries = entries;