ext4: don't treat fhandle lookup of ea_inode as FS corruption
[ Upstream commit642335f3ea] A file handle that userspace provides to open_by_handle_at() can legitimately contain an outdated inode number that has since been reused for another purpose - that's why the file handle also contains a generation number. But if the inode number has been reused for an ea_inode, check_igot_inode() will notice, __ext4_iget() will go through ext4_error_inode(), and if the inode was newly created, it will also be marked as bad by iget_failed(). This all happens before the point where the inode generation is checked. ext4_error_inode() is supposed to only be used on filesystem corruption; it should not be used when userspace just got unlucky with a stale file handle. So when this happens, let __ext4_iget() just return an error. Fixes:b3e6bcb945("ext4: add EA_INODE checking to ext4_iget()") Signed-off-by: Jann Horn <jannh@google.com> Reviewed-by: Jan Kara <jack@suse.cz> Link: https://patch.msgid.link/20241129-ext4-ignore-ea-fhandle-v1-1-e532c0d1cee0@google.com Signed-off-by: Theodore Ts'o <tytso@mit.edu> Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
committed by
Greg Kroah-Hartman
parent
d5cba7730d
commit
480faed292
+48
-20
@@ -4692,22 +4692,43 @@ static inline void ext4_inode_set_iversion_queried(struct inode *inode, u64 val)
|
||||
inode_set_iversion_queried(inode, val);
|
||||
}
|
||||
|
||||
static const char *check_igot_inode(struct inode *inode, ext4_iget_flags flags)
|
||||
|
||||
static int check_igot_inode(struct inode *inode, ext4_iget_flags flags,
|
||||
const char *function, unsigned int line)
|
||||
{
|
||||
const char *err_str;
|
||||
|
||||
if (flags & EXT4_IGET_EA_INODE) {
|
||||
if (!(EXT4_I(inode)->i_flags & EXT4_EA_INODE_FL))
|
||||
return "missing EA_INODE flag";
|
||||
if (!(EXT4_I(inode)->i_flags & EXT4_EA_INODE_FL)) {
|
||||
err_str = "missing EA_INODE flag";
|
||||
goto error;
|
||||
}
|
||||
if (ext4_test_inode_state(inode, EXT4_STATE_XATTR) ||
|
||||
EXT4_I(inode)->i_file_acl)
|
||||
return "ea_inode with extended attributes";
|
||||
EXT4_I(inode)->i_file_acl) {
|
||||
err_str = "ea_inode with extended attributes";
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
if ((EXT4_I(inode)->i_flags & EXT4_EA_INODE_FL))
|
||||
return "unexpected EA_INODE flag";
|
||||
if ((EXT4_I(inode)->i_flags & EXT4_EA_INODE_FL)) {
|
||||
/*
|
||||
* open_by_handle_at() could provide an old inode number
|
||||
* that has since been reused for an ea_inode; this does
|
||||
* not indicate filesystem corruption
|
||||
*/
|
||||
if (flags & EXT4_IGET_HANDLE)
|
||||
return -ESTALE;
|
||||
err_str = "unexpected EA_INODE flag";
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
if (is_bad_inode(inode) && !(flags & EXT4_IGET_BAD))
|
||||
return "unexpected bad inode w/o EXT4_IGET_BAD";
|
||||
return NULL;
|
||||
if (is_bad_inode(inode) && !(flags & EXT4_IGET_BAD)) {
|
||||
err_str = "unexpected bad inode w/o EXT4_IGET_BAD";
|
||||
goto error;
|
||||
}
|
||||
return 0;
|
||||
|
||||
error:
|
||||
ext4_error_inode(inode, function, line, 0, err_str);
|
||||
return -EFSCORRUPTED;
|
||||
}
|
||||
|
||||
struct inode *__ext4_iget(struct super_block *sb, unsigned long ino,
|
||||
@@ -4719,7 +4740,6 @@ struct inode *__ext4_iget(struct super_block *sb, unsigned long ino,
|
||||
struct ext4_inode_info *ei;
|
||||
struct ext4_super_block *es = EXT4_SB(sb)->s_es;
|
||||
struct inode *inode;
|
||||
const char *err_str;
|
||||
journal_t *journal = EXT4_SB(sb)->s_journal;
|
||||
long ret;
|
||||
loff_t size;
|
||||
@@ -4748,10 +4768,10 @@ struct inode *__ext4_iget(struct super_block *sb, unsigned long ino,
|
||||
if (!inode)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
if (!(inode->i_state & I_NEW)) {
|
||||
if ((err_str = check_igot_inode(inode, flags)) != NULL) {
|
||||
ext4_error_inode(inode, function, line, 0, err_str);
|
||||
ret = check_igot_inode(inode, flags, function, line);
|
||||
if (ret) {
|
||||
iput(inode);
|
||||
return ERR_PTR(-EFSCORRUPTED);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
return inode;
|
||||
}
|
||||
@@ -5023,13 +5043,21 @@ struct inode *__ext4_iget(struct super_block *sb, unsigned long ino,
|
||||
ret = -EFSCORRUPTED;
|
||||
goto bad_inode;
|
||||
}
|
||||
if ((err_str = check_igot_inode(inode, flags)) != NULL) {
|
||||
ext4_error_inode(inode, function, line, 0, err_str);
|
||||
ret = -EFSCORRUPTED;
|
||||
goto bad_inode;
|
||||
ret = check_igot_inode(inode, flags, function, line);
|
||||
/*
|
||||
* -ESTALE here means there is nothing inherently wrong with the inode,
|
||||
* it's just not an inode we can return for an fhandle lookup.
|
||||
*/
|
||||
if (ret == -ESTALE) {
|
||||
brelse(iloc.bh);
|
||||
unlock_new_inode(inode);
|
||||
iput(inode);
|
||||
return ERR_PTR(-ESTALE);
|
||||
}
|
||||
|
||||
if (ret)
|
||||
goto bad_inode;
|
||||
brelse(iloc.bh);
|
||||
|
||||
unlock_new_inode(inode);
|
||||
return inode;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user