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

ext4: add EA_INODE checking to ext4_iget()

Add a new flag, EXT4_IGET_EA_INODE which indicates whether the inode
is expected to have the EA_INODE flag or not. If the flag is not
set/clear as expected, then fail the iget() operation and mark the
file system as corrupted.

This commit also makes the ext4_iget() always perform the
is_bad_inode() check even when the inode is already inode cache. This
allows us to remove the is_bad_inode() check from the callers of
ext4_iget() in the ea_inode code.

Reported-by: syzbot+cbb68193bdb95af4340a@syzkaller.appspotmail.com
Reported-by: syzbot+62120febbd1ee3c3c860@syzkaller.appspotmail.com
Reported-by: syzbot+edce54daffee36421b4c@syzkaller.appspotmail.com
Cc: stable@kernel.org
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Link: https://lore.kernel.org/r/20230524034951.779531-2-tytso@mit.edu
Signed-off-by: Theodore Ts'o <tytso@mit.edu>

+35 -35
+2 -1
fs/ext4/ext4.h
··· 2901 2901 EXT4_IGET_NORMAL = 0, 2902 2902 EXT4_IGET_SPECIAL = 0x0001, /* OK to iget a system inode */ 2903 2903 EXT4_IGET_HANDLE = 0x0002, /* Inode # is from a handle */ 2904 - EXT4_IGET_BAD = 0x0004 /* Allow to iget a bad inode */ 2904 + EXT4_IGET_BAD = 0x0004, /* Allow to iget a bad inode */ 2905 + EXT4_IGET_EA_INODE = 0x0008 /* Inode should contain an EA value */ 2905 2906 } ext4_iget_flags; 2906 2907 2907 2908 extern struct inode *__ext4_iget(struct super_block *sb, unsigned long ino,
+26 -5
fs/ext4/inode.c
··· 4641 4641 inode_set_iversion_queried(inode, val); 4642 4642 } 4643 4643 4644 + static const char *check_igot_inode(struct inode *inode, ext4_iget_flags flags) 4645 + 4646 + { 4647 + if (flags & EXT4_IGET_EA_INODE) { 4648 + if (!(EXT4_I(inode)->i_flags & EXT4_EA_INODE_FL)) 4649 + return "missing EA_INODE flag"; 4650 + } else { 4651 + if ((EXT4_I(inode)->i_flags & EXT4_EA_INODE_FL)) 4652 + return "unexpected EA_INODE flag"; 4653 + } 4654 + if (is_bad_inode(inode) && !(flags & EXT4_IGET_BAD)) 4655 + return "unexpected bad inode w/o EXT4_IGET_BAD"; 4656 + return NULL; 4657 + } 4658 + 4644 4659 struct inode *__ext4_iget(struct super_block *sb, unsigned long ino, 4645 4660 ext4_iget_flags flags, const char *function, 4646 4661 unsigned int line) ··· 4665 4650 struct ext4_inode_info *ei; 4666 4651 struct ext4_super_block *es = EXT4_SB(sb)->s_es; 4667 4652 struct inode *inode; 4653 + const char *err_str; 4668 4654 journal_t *journal = EXT4_SB(sb)->s_journal; 4669 4655 long ret; 4670 4656 loff_t size; ··· 4693 4677 inode = iget_locked(sb, ino); 4694 4678 if (!inode) 4695 4679 return ERR_PTR(-ENOMEM); 4696 - if (!(inode->i_state & I_NEW)) 4680 + if (!(inode->i_state & I_NEW)) { 4681 + if ((err_str = check_igot_inode(inode, flags)) != NULL) { 4682 + ext4_error_inode(inode, function, line, 0, err_str); 4683 + iput(inode); 4684 + return ERR_PTR(-EFSCORRUPTED); 4685 + } 4697 4686 return inode; 4687 + } 4698 4688 4699 4689 ei = EXT4_I(inode); 4700 4690 iloc.bh = NULL; ··· 4966 4944 if (IS_CASEFOLDED(inode) && !ext4_has_feature_casefold(inode->i_sb)) 4967 4945 ext4_error_inode(inode, function, line, 0, 4968 4946 "casefold flag without casefold feature"); 4969 - if (is_bad_inode(inode) && !(flags & EXT4_IGET_BAD)) { 4970 - ext4_error_inode(inode, function, line, 0, 4971 - "bad inode without EXT4_IGET_BAD flag"); 4972 - ret = -EUCLEAN; 4947 + if ((err_str = check_igot_inode(inode, flags)) != NULL) { 4948 + ext4_error_inode(inode, function, line, 0, err_str); 4949 + ret = -EFSCORRUPTED; 4973 4950 goto bad_inode; 4974 4951 } 4975 4952
+7 -29
fs/ext4/xattr.c
··· 433 433 return -EFSCORRUPTED; 434 434 } 435 435 436 - inode = ext4_iget(parent->i_sb, ea_ino, EXT4_IGET_NORMAL); 436 + inode = ext4_iget(parent->i_sb, ea_ino, EXT4_IGET_EA_INODE); 437 437 if (IS_ERR(inode)) { 438 438 err = PTR_ERR(inode); 439 439 ext4_error(parent->i_sb, ··· 441 441 err); 442 442 return err; 443 443 } 444 - 445 - if (is_bad_inode(inode)) { 446 - ext4_error(parent->i_sb, 447 - "error while reading EA inode %lu is_bad_inode", 448 - ea_ino); 449 - err = -EIO; 450 - goto error; 451 - } 452 - 453 - if (!(EXT4_I(inode)->i_flags & EXT4_EA_INODE_FL)) { 454 - ext4_error(parent->i_sb, 455 - "EA inode %lu does not have EXT4_EA_INODE_FL flag", 456 - ea_ino); 457 - err = -EINVAL; 458 - goto error; 459 - } 460 - 461 444 ext4_xattr_inode_set_class(inode); 462 445 463 446 /* ··· 461 478 462 479 *ea_inode = inode; 463 480 return 0; 464 - error: 465 - iput(inode); 466 - return err; 467 481 } 468 482 469 483 /* Remove entry from mbcache when EA inode is getting evicted */ ··· 1536 1556 1537 1557 while (ce) { 1538 1558 ea_inode = ext4_iget(inode->i_sb, ce->e_value, 1539 - EXT4_IGET_NORMAL); 1540 - if (!IS_ERR(ea_inode) && 1541 - !is_bad_inode(ea_inode) && 1542 - (EXT4_I(ea_inode)->i_flags & EXT4_EA_INODE_FL) && 1543 - i_size_read(ea_inode) == value_len && 1559 + EXT4_IGET_EA_INODE); 1560 + if (IS_ERR(ea_inode)) 1561 + goto next_entry; 1562 + if (i_size_read(ea_inode) == value_len && 1544 1563 !ext4_xattr_inode_read(ea_inode, ea_data, value_len) && 1545 1564 !ext4_xattr_inode_verify_hashes(ea_inode, NULL, ea_data, 1546 1565 value_len) && ··· 1549 1570 kvfree(ea_data); 1550 1571 return ea_inode; 1551 1572 } 1552 - 1553 - if (!IS_ERR(ea_inode)) 1554 - iput(ea_inode); 1573 + iput(ea_inode); 1574 + next_entry: 1555 1575 ce = mb_cache_entry_find_next(ea_inode_cache, ce); 1556 1576 } 1557 1577 kvfree(ea_data);