ext4: don't treat fhandle lookup of ea_inode as FS corruption

[ Upstream commit 642335f3ea2b3fd6dba03e57e01fa9587843a497 ]

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:
Jann Horn
2024-11-29 21:20:53 +01:00
committed by Greg Kroah-Hartman
parent 806908d5d9
commit dd3edffae8

View File

@@ -4681,22 +4681,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,
@@ -4708,7 +4729,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;
@@ -4737,10 +4757,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;
}
@@ -5012,13 +5032,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;