Jan Kara 6e4cde
From 8a363970d1dc38c4ec4ad575c862f776f468d057 Mon Sep 17 00:00:00 2001
Jan Kara 6e4cde
From: Theodore Ts'o <tytso@mit.edu>
Jan Kara 6e4cde
Date: Wed, 19 Dec 2018 12:29:13 -0500
Jan Kara 6e4cde
Subject: [PATCH] ext4: avoid declaring fs inconsistent due to invalid file
Jan Kara 6e4cde
 handles
Jan Kara 6e4cde
Git-commit: 8a363970d1dc38c4ec4ad575c862f776f468d057
Jan Kara 6e4cde
Patch-mainline: v5.0-rc1
Jan Kara 6e4cde
References: bsc#1158021 CVE-2019-19319
Jan Kara 6e4cde
Jan Kara 6e4cde
If we receive a file handle, either from NFS or open_by_handle_at(2),
Jan Kara 6e4cde
and it points at an inode which has not been initialized, and the file
Jan Kara 6e4cde
system has metadata checksums enabled, we shouldn't try to get the
Jan Kara 6e4cde
inode, discover the checksum is invalid, and then declare the file
Jan Kara 6e4cde
system as being inconsistent.
Jan Kara 6e4cde
Jan Kara 6e4cde
This can be reproduced by creating a test file system via "mke2fs -t
Jan Kara 6e4cde
ext4 -O metadata_csum /tmp/foo.img 8M", mounting it, cd'ing into that
Jan Kara 6e4cde
directory, and then running the following program.
Jan Kara 6e4cde
Jan Kara 6e4cde
#define _GNU_SOURCE
Jan Kara 6e4cde
#include <fcntl.h>
Jan Kara 6e4cde
Jan Kara 6e4cde
struct handle {
Jan Kara 6e4cde
	struct file_handle fh;
Jan Kara 6e4cde
	unsigned char fid[MAX_HANDLE_SZ];
Jan Kara 6e4cde
};
Jan Kara 6e4cde
Jan Kara 6e4cde
int main(int argc, char **argv)
Jan Kara 6e4cde
{
Jan Kara 6e4cde
	struct handle h = {{8, 1 }, { 12, }};
Jan Kara 6e4cde
Jan Kara 6e4cde
	open_by_handle_at(AT_FDCWD, &h.fh, O_RDONLY);
Jan Kara 6e4cde
	return 0;
Jan Kara 6e4cde
}
Jan Kara 6e4cde
Jan Kara 6e4cde
Google-bug-id: 120690101
Jan Kara 6e4cde
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Jan Kara 6e4cde
Cc: stable@kernel.org
Jan Kara 6e4cde
Acked-by: Jan Kara <jack@suse.cz>
Jan Kara 6e4cde
Jan Kara 6e4cde
---
Jan Kara 6e4cde
 fs/ext4/ext4.h   |   15 +++++++++++++--
Jan Kara 6e4cde
 fs/ext4/ialloc.c |    2 +-
Jan Kara 6e4cde
 fs/ext4/inode.c  |   49 ++++++++++++++++++++++++++++++++++---------------
Jan Kara 6e4cde
 fs/ext4/ioctl.c  |    2 +-
Jan Kara 6e4cde
 fs/ext4/namei.c  |    4 ++--
Jan Kara 6e4cde
 fs/ext4/resize.c |    5 +++--
Jan Kara 6e4cde
 fs/ext4/super.c  |   19 +++++--------------
Jan Kara 6e4cde
 7 files changed, 59 insertions(+), 37 deletions(-)
Jan Kara 6e4cde
Jan Kara 6e4cde
--- a/fs/ext4/ext4.h
Jan Kara 6e4cde
+++ b/fs/ext4/ext4.h
Jan Kara 6e4cde
@@ -2458,8 +2458,19 @@ int do_journal_get_write_access(handle_t
Jan Kara 6e4cde
 #define FALL_BACK_TO_NONDELALLOC 1
Jan Kara 6e4cde
 #define CONVERT_INLINE_DATA	 2
Jan Kara 6e4cde
 
Jan Kara 6e4cde
-extern struct inode *ext4_iget(struct super_block *, unsigned long);
Jan Kara 6e4cde
-extern struct inode *ext4_iget_normal(struct super_block *, unsigned long);
Jan Kara 6e4cde
+typedef enum {
Jan Kara 6e4cde
+	EXT4_IGET_NORMAL =	0,
Jan Kara 6e4cde
+	EXT4_IGET_SPECIAL =	0x0001, /* OK to iget a system inode */
Jan Kara 6e4cde
+	EXT4_IGET_HANDLE = 	0x0002	/* Inode # is from a handle */
Jan Kara 6e4cde
+} ext4_iget_flags;
Jan Kara 6e4cde
+
Jan Kara 6e4cde
+extern struct inode *__ext4_iget(struct super_block *sb, unsigned long ino,
Jan Kara 6e4cde
+				 ext4_iget_flags flags, const char *function,
Jan Kara 6e4cde
+				 unsigned int line);
Jan Kara 6e4cde
+
Jan Kara 6e4cde
+#define ext4_iget(sb, ino, flags) \
Jan Kara 6e4cde
+	__ext4_iget((sb), (ino), (flags), __func__, __LINE__)
Jan Kara 6e4cde
+
Jan Kara 6e4cde
 extern int  ext4_write_inode(struct inode *, struct writeback_control *);
Jan Kara 6e4cde
 extern int  ext4_setattr(struct dentry *, struct iattr *);
Jan Kara 6e4cde
 extern int  ext4_getattr(const struct path *, struct kstat *, u32, unsigned int);
Jan Kara 6e4cde
--- a/fs/ext4/ialloc.c
Jan Kara 6e4cde
+++ b/fs/ext4/ialloc.c
Jan Kara 6e4cde
@@ -1182,7 +1182,7 @@ struct inode *ext4_orphan_get(struct sup
Jan Kara 6e4cde
 	if (!ext4_test_bit(bit, bitmap_bh->b_data))
Jan Kara 6e4cde
 		goto bad_orphan;
Jan Kara 6e4cde
 
Jan Kara 6e4cde
-	inode = ext4_iget(sb, ino);
Jan Kara 6e4cde
+	inode = ext4_iget(sb, ino, EXT4_IGET_NORMAL);
Jan Kara 6e4cde
 	if (IS_ERR(inode)) {
Jan Kara 6e4cde
 		err = PTR_ERR(inode);
Jan Kara 6e4cde
 		ext4_error(sb, "couldn't read orphan inode %lu (err %d)",
Jan Kara 6e4cde
--- a/fs/ext4/inode.c
Jan Kara 6e4cde
+++ b/fs/ext4/inode.c
Jan Kara 6e4cde
@@ -4661,7 +4661,9 @@ int ext4_get_projid(struct inode *inode,
Jan Kara 6e4cde
 	return 0;
Jan Kara 6e4cde
 }
Jan Kara 6e4cde
 
Jan Kara 6e4cde
-struct inode *ext4_iget(struct super_block *sb, unsigned long ino)
Jan Kara 6e4cde
+struct inode *__ext4_iget(struct super_block *sb, unsigned long ino,
Jan Kara 6e4cde
+			  ext4_iget_flags flags, const char *function,
Jan Kara 6e4cde
+			  unsigned int line)
Jan Kara 6e4cde
 {
Jan Kara 6e4cde
 	struct ext4_iloc iloc;
Jan Kara 6e4cde
 	struct ext4_inode *raw_inode;
Jan Kara 6e4cde
@@ -4675,6 +4677,18 @@ struct inode *ext4_iget(struct super_blo
Jan Kara 6e4cde
 	gid_t i_gid;
Jan Kara 6e4cde
 	projid_t i_projid;
Jan Kara 6e4cde
 
Jan Kara 6e4cde
+	if (((flags & EXT4_IGET_NORMAL) &&
Jan Kara 6e4cde
+	     (ino < EXT4_FIRST_INO(sb) && ino != EXT4_ROOT_INO)) ||
Jan Kara 6e4cde
+	    (ino < EXT4_ROOT_INO) ||
Jan Kara 6e4cde
+	    (ino > le32_to_cpu(EXT4_SB(sb)->s_es->s_inodes_count))) {
Jan Kara 6e4cde
+		if (flags & EXT4_IGET_HANDLE)
Jan Kara 6e4cde
+			return ERR_PTR(-ESTALE);
Jan Kara 6e4cde
+		__ext4_error(sb, function, line,
Jan Kara 6e4cde
+			     "inode #%lu: comm %s: iget: illegal inode #",
Jan Kara 6e4cde
+			     ino, current->comm);
Jan Kara 6e4cde
+		return ERR_PTR(-EFSCORRUPTED);
Jan Kara 6e4cde
+	}
Jan Kara 6e4cde
+
Jan Kara 6e4cde
 	inode = iget_locked(sb, ino);
Jan Kara 6e4cde
 	if (!inode)
Jan Kara 6e4cde
 		return ERR_PTR(-ENOMEM);
Jan Kara 6e4cde
@@ -4690,18 +4704,26 @@ struct inode *ext4_iget(struct super_blo
Jan Kara 6e4cde
 	raw_inode = ext4_raw_inode(&iloc);
Jan Kara 6e4cde
 
Jan Kara 6e4cde
 	if ((ino == EXT4_ROOT_INO) && (raw_inode->i_links_count == 0)) {
Jan Kara 6e4cde
-		EXT4_ERROR_INODE(inode, "root inode unallocated");
Jan Kara 6e4cde
+		ext4_error_inode(inode, function, line, 0,
Jan Kara 6e4cde
+				 "iget: root inode unallocated");
Jan Kara 6e4cde
 		ret = -EFSCORRUPTED;
Jan Kara 6e4cde
 		goto bad_inode;
Jan Kara 6e4cde
 	}
Jan Kara 6e4cde
 
Jan Kara 6e4cde
+	if ((flags & EXT4_IGET_HANDLE) &&
Jan Kara 6e4cde
+	    (raw_inode->i_links_count == 0) && (raw_inode->i_mode == 0)) {
Jan Kara 6e4cde
+		ret = -ESTALE;
Jan Kara 6e4cde
+		goto bad_inode;
Jan Kara 6e4cde
+	}
Jan Kara 6e4cde
+
Jan Kara 6e4cde
 	if (EXT4_INODE_SIZE(inode->i_sb) > EXT4_GOOD_OLD_INODE_SIZE) {
Jan Kara 6e4cde
 		ei->i_extra_isize = le16_to_cpu(raw_inode->i_extra_isize);
Jan Kara 6e4cde
 		if (EXT4_GOOD_OLD_INODE_SIZE + ei->i_extra_isize >
Jan Kara 6e4cde
 			EXT4_INODE_SIZE(inode->i_sb) ||
Jan Kara 6e4cde
 		    (ei->i_extra_isize & 3)) {
Jan Kara 6e4cde
-			EXT4_ERROR_INODE(inode,
Jan Kara 6e4cde
-					 "bad extra_isize %u (inode size %u)",
Jan Kara 6e4cde
+			ext4_error_inode(inode, function, line, 0,
Jan Kara 6e4cde
+					 "iget: bad extra_isize %u "
Jan Kara 6e4cde
+					 "(inode size %u)",
Jan Kara 6e4cde
 					 ei->i_extra_isize,
Jan Kara 6e4cde
 					 EXT4_INODE_SIZE(inode->i_sb));
Jan Kara 6e4cde
 			ret = -EFSCORRUPTED;
Jan Kara 6e4cde
@@ -4723,7 +4745,8 @@ struct inode *ext4_iget(struct super_blo
Jan Kara 6e4cde
 	}
Jan Kara 6e4cde
 
Jan Kara 6e4cde
 	if (!ext4_inode_csum_verify(inode, raw_inode, ei)) {
Jan Kara 6e4cde
-		EXT4_ERROR_INODE(inode, "checksum invalid");
Jan Kara 6e4cde
+		ext4_error_inode(inode, function, line, 0,
Jan Kara 6e4cde
+				 "iget: checksum invalid");
Jan Kara 6e4cde
 		ret = -EFSBADCRC;
Jan Kara 6e4cde
 		goto bad_inode;
Jan Kara 6e4cde
 	}
Jan Kara 6e4cde
@@ -4780,7 +4803,8 @@ struct inode *ext4_iget(struct super_blo
Jan Kara 6e4cde
 			((__u64)le16_to_cpu(raw_inode->i_file_acl_high)) << 32;
Jan Kara 6e4cde
 	inode->i_size = ext4_isize(raw_inode);
Jan Kara 6e4cde
 	if ((size = i_size_read(inode)) < 0) {
Jan Kara 6e4cde
-		EXT4_ERROR_INODE(inode, "bad i_size value: %lld", size);
Jan Kara 6e4cde
+		ext4_error_inode(inode, function, line, 0,
Jan Kara 6e4cde
+				 "iget: bad i_size value: %lld", size);
Jan Kara 6e4cde
 		ret = -EFSCORRUPTED;
Jan Kara 6e4cde
 		goto bad_inode;
Jan Kara 6e4cde
 	}
Jan Kara 6e4cde
@@ -4856,7 +4880,8 @@ struct inode *ext4_iget(struct super_blo
Jan Kara 6e4cde
 	ret = 0;
Jan Kara 6e4cde
 	if (ei->i_file_acl &&
Jan Kara 6e4cde
 	    !ext4_data_block_valid(EXT4_SB(sb), ei->i_file_acl, 1)) {
Jan Kara 6e4cde
-		EXT4_ERROR_INODE(inode, "bad extended attribute block %llu",
Jan Kara 6e4cde
+		ext4_error_inode(inode, function, line, 0,
Jan Kara 6e4cde
+				 "iget: bad extended attribute block %llu",
Jan Kara 6e4cde
 				 ei->i_file_acl);
Jan Kara 6e4cde
 		ret = -EFSCORRUPTED;
Jan Kara 6e4cde
 		goto bad_inode;
Jan Kara 6e4cde
@@ -4911,7 +4936,8 @@ struct inode *ext4_iget(struct super_blo
Jan Kara 6e4cde
 		make_bad_inode(inode);
Jan Kara 6e4cde
 	} else {
Jan Kara 6e4cde
 		ret = -EFSCORRUPTED;
Jan Kara 6e4cde
-		EXT4_ERROR_INODE(inode, "bogus i_mode (%o)", inode->i_mode);
Jan Kara 6e4cde
+		ext4_error_inode(inode, function, line, 0,
Jan Kara 6e4cde
+				 "iget: bogus i_mode (%o)", inode->i_mode);
Jan Kara 6e4cde
 		goto bad_inode;
Jan Kara 6e4cde
 	}
Jan Kara 6e4cde
 	brelse(iloc.bh);
Jan Kara 6e4cde
@@ -4924,13 +4950,6 @@ bad_inode:
Jan Kara 6e4cde
 	return ERR_PTR(ret);
Jan Kara 6e4cde
 }
Jan Kara 6e4cde
 
Jan Kara 6e4cde
-struct inode *ext4_iget_normal(struct super_block *sb, unsigned long ino)
Jan Kara 6e4cde
-{
Jan Kara 6e4cde
-	if (ino < EXT4_FIRST_INO(sb) && ino != EXT4_ROOT_INO)
Jan Kara 6e4cde
-		return ERR_PTR(-EFSCORRUPTED);
Jan Kara 6e4cde
-	return ext4_iget(sb, ino);
Jan Kara 6e4cde
-}
Jan Kara 6e4cde
-
Jan Kara 6e4cde
 static int ext4_inode_blocks_set(handle_t *handle,
Jan Kara 6e4cde
 				struct ext4_inode *raw_inode,
Jan Kara 6e4cde
 				struct ext4_inode_info *ei)
Jan Kara 6e4cde
--- a/fs/ext4/ioctl.c
Jan Kara 6e4cde
+++ b/fs/ext4/ioctl.c
Jan Kara 6e4cde
@@ -125,7 +125,7 @@ static long swap_inode_boot_loader(struc
Jan Kara 6e4cde
 	    !inode_owner_or_capable(inode) || !capable(CAP_SYS_ADMIN))
Jan Kara 6e4cde
 		return -EPERM;
Jan Kara 6e4cde
 
Jan Kara 6e4cde
-	inode_bl = ext4_iget(sb, EXT4_BOOT_LOADER_INO);
Jan Kara 6e4cde
+	inode_bl = ext4_iget(sb, EXT4_BOOT_LOADER_INO, EXT4_IGET_SPECIAL);
Jan Kara 6e4cde
 	if (IS_ERR(inode_bl))
Jan Kara 6e4cde
 		return PTR_ERR(inode_bl);
Jan Kara 6e4cde
 	ei_bl = EXT4_I(inode_bl);
Jan Kara 6e4cde
--- a/fs/ext4/namei.c
Jan Kara 6e4cde
+++ b/fs/ext4/namei.c
Jan Kara 6e4cde
@@ -1580,7 +1580,7 @@ static struct dentry *ext4_lookup(struct
Jan Kara 6e4cde
 					 dentry);
Jan Kara 6e4cde
 			return ERR_PTR(-EFSCORRUPTED);
Jan Kara 6e4cde
 		}
Jan Kara 6e4cde
-		inode = ext4_iget_normal(dir->i_sb, ino);
Jan Kara 6e4cde
+		inode = ext4_iget(dir->i_sb, ino, EXT4_IGET_NORMAL);
Jan Kara 6e4cde
 		if (inode == ERR_PTR(-ESTALE)) {
Jan Kara 6e4cde
 			EXT4_ERROR_INODE(dir,
Jan Kara 6e4cde
 					 "deleted inode referenced: %u",
Jan Kara 6e4cde
@@ -1622,7 +1622,7 @@ struct dentry *ext4_get_parent(struct de
Jan Kara 6e4cde
 		return ERR_PTR(-EFSCORRUPTED);
Jan Kara 6e4cde
 	}
Jan Kara 6e4cde
 
Jan Kara 6e4cde
-	return d_obtain_alias(ext4_iget_normal(child->d_sb, ino));
Jan Kara 6e4cde
+	return d_obtain_alias(ext4_iget(child->d_sb, ino, EXT4_IGET_NORMAL));
Jan Kara 6e4cde
 }
Jan Kara 6e4cde
 
Jan Kara 6e4cde
 /*
Jan Kara 6e4cde
--- a/fs/ext4/resize.c
Jan Kara 6e4cde
+++ b/fs/ext4/resize.c
Jan Kara 6e4cde
@@ -1607,7 +1607,7 @@ int ext4_group_add(struct super_block *s
Jan Kara 6e4cde
 				     "No reserved GDT blocks, can't resize");
Jan Kara 6e4cde
 			return -EPERM;
Jan Kara 6e4cde
 		}
Jan Kara 6e4cde
-		inode = ext4_iget(sb, EXT4_RESIZE_INO);
Jan Kara 6e4cde
+		inode = ext4_iget(sb, EXT4_RESIZE_INO, EXT4_IGET_SPECIAL);
Jan Kara 6e4cde
 		if (IS_ERR(inode)) {
Jan Kara 6e4cde
 			ext4_warning(sb, "Error opening resize inode");
Jan Kara 6e4cde
 			return PTR_ERR(inode);
Jan Kara 6e4cde
@@ -1934,7 +1934,8 @@ retry:
Jan Kara 6e4cde
 		}
Jan Kara 6e4cde
 
Jan Kara 6e4cde
 		if (!resize_inode)
Jan Kara 6e4cde
-			resize_inode = ext4_iget(sb, EXT4_RESIZE_INO);
Jan Kara 6e4cde
+			resize_inode = ext4_iget(sb, EXT4_RESIZE_INO,
Jan Kara 6e4cde
+						 EXT4_IGET_SPECIAL);
Jan Kara 6e4cde
 		if (IS_ERR(resize_inode)) {
Jan Kara 6e4cde
 			ext4_warning(sb, "Error opening resize inode");
Jan Kara 6e4cde
 			return PTR_ERR(resize_inode);
Jan Kara 6e4cde
--- a/fs/ext4/super.c
Jan Kara 6e4cde
+++ b/fs/ext4/super.c
Jan Kara 6e4cde
@@ -1089,20 +1089,11 @@ static struct inode *ext4_nfs_get_inode(
Jan Kara 6e4cde
 {
Jan Kara 6e4cde
 	struct inode *inode;
Jan Kara 6e4cde
 
Jan Kara 6e4cde
-	if (ino < EXT4_FIRST_INO(sb) && ino != EXT4_ROOT_INO)
Jan Kara 6e4cde
-		return ERR_PTR(-ESTALE);
Jan Kara 6e4cde
-	if (ino > le32_to_cpu(EXT4_SB(sb)->s_es->s_inodes_count))
Jan Kara 6e4cde
-		return ERR_PTR(-ESTALE);
Jan Kara 6e4cde
-
Jan Kara 6e4cde
-	/* iget isn't really right if the inode is currently unallocated!!
Jan Kara 6e4cde
-	 *
Jan Kara 6e4cde
-	 * ext4_read_inode will return a bad_inode if the inode had been
Jan Kara 6e4cde
-	 * deleted, so we should be safe.
Jan Kara 6e4cde
-	 *
Jan Kara 6e4cde
+	/*
Jan Kara 6e4cde
 	 * Currently we don't know the generation for parent directory, so
Jan Kara 6e4cde
 	 * a generation of 0 means "accept any"
Jan Kara 6e4cde
 	 */
Jan Kara 6e4cde
-	inode = ext4_iget_normal(sb, ino);
Jan Kara 6e4cde
+	inode = ext4_iget(sb, ino, EXT4_IGET_HANDLE);
Jan Kara 6e4cde
 	if (IS_ERR(inode))
Jan Kara 6e4cde
 		return ERR_CAST(inode);
Jan Kara 6e4cde
 	if (generation && inode->i_generation != generation) {
Jan Kara 6e4cde
@@ -4216,7 +4207,7 @@ no_journal:
Jan Kara 6e4cde
 	 * so we can safely mount the rest of the filesystem now.
Jan Kara 6e4cde
 	 */
Jan Kara 6e4cde
 
Jan Kara 6e4cde
-	root = ext4_iget(sb, EXT4_ROOT_INO);
Jan Kara 6e4cde
+	root = ext4_iget(sb, EXT4_ROOT_INO, EXT4_IGET_SPECIAL);
Jan Kara 6e4cde
 	if (IS_ERR(root)) {
Jan Kara 6e4cde
 		ext4_msg(sb, KERN_ERR, "get root inode failed");
Jan Kara 6e4cde
 		ret = PTR_ERR(root);
Jan Kara 6e4cde
@@ -4478,7 +4469,7 @@ static struct inode *ext4_get_journal_in
Jan Kara 6e4cde
 	 * happen if we iget() an unused inode, as the subsequent iput()
Jan Kara 6e4cde
 	 * will try to delete it.
Jan Kara 6e4cde
 	 */
Jan Kara 6e4cde
-	journal_inode = ext4_iget(sb, journal_inum);
Jan Kara 6e4cde
+	journal_inode = ext4_iget(sb, journal_inum, EXT4_IGET_SPECIAL);
Jan Kara 6e4cde
 	if (IS_ERR(journal_inode)) {
Jan Kara 6e4cde
 		ext4_msg(sb, KERN_ERR, "no journal found");
Jan Kara 6e4cde
 		return NULL;
Jan Kara 6e4cde
@@ -5543,7 +5534,7 @@ static int ext4_quota_enable(struct supe
Jan Kara 6e4cde
 	if (!qf_inums[type])
Jan Kara 6e4cde
 		return -EPERM;
Jan Kara 6e4cde
 
Jan Kara 6e4cde
-	qf_inode = ext4_iget(sb, qf_inums[type]);
Jan Kara 6e4cde
+	qf_inode = ext4_iget(sb, qf_inums[type], EXT4_IGET_SPECIAL);
Jan Kara 6e4cde
 	if (IS_ERR(qf_inode)) {
Jan Kara 6e4cde
 		ext4_error(sb, "Bad quota inode # %lu", qf_inums[type]);
Jan Kara 6e4cde
 		return PTR_ERR(qf_inode);