From d375fed0c6ee1ff79fc8036158bdbb139f21fd38 Mon Sep 17 00:00:00 2001 From: Theodore Ts'o Date: Tue, 3 Sep 2019 01:43:17 -0400 Subject: [PATCH] ext4: fix kernel oops caused by spurious casefold flag If an directory has the a casefold flag set without the casefold feature set, s_encoding will not be initialized, and this will cause the kernel to dereference a NULL pointer. In addition to adding checks to avoid these kernel oops, attempts to load inodes with the casefold flag when the casefold feature is not enable will cause the file system to be declared corrupted. [Jaegeuk Kim: use EXT4_ERROR_INODE] Signed-off-by: Theodore Ts'o --- fs/ext4/dir.c | 7 ++++--- fs/ext4/hash.c | 2 +- fs/ext4/inode.c | 3 +++ fs/ext4/namei.c | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/fs/ext4/dir.c b/fs/ext4/dir.c index 0a427e18584a..4e7780481b9a 100644 --- a/fs/ext4/dir.c +++ b/fs/ext4/dir.c @@ -667,14 +667,15 @@ static int ext4_d_compare(const struct dentry *dentry, unsigned int len, const char *str, const struct qstr *name) { struct qstr qstr = {.name = str, .len = len }; + struct inode *inode = dentry->d_parent->d_inode; - if (!IS_CASEFOLDED(dentry->d_parent->d_inode)) { + if (!IS_CASEFOLDED(inode) || !EXT4_SB(inode->i_sb)->s_encoding) { if (len != name->len) return -1; return memcmp(str, name->name, len); } - return ext4_ci_compare(dentry->d_parent->d_inode, name, &qstr, false); + return ext4_ci_compare(inode, name, &qstr, false); } static int ext4_d_hash(const struct dentry *dentry, struct qstr *str) @@ -684,7 +685,7 @@ static int ext4_d_hash(const struct dentry *dentry, struct qstr *str) unsigned char *norm; int len, ret = 0; - if (!IS_CASEFOLDED(dentry->d_inode)) + if (!IS_CASEFOLDED(dentry->d_inode) || !um) return 0; norm = kmalloc(PATH_MAX, GFP_ATOMIC); diff --git a/fs/ext4/hash.c b/fs/ext4/hash.c index 58aff05f0659..b934206040b7 100644 --- a/fs/ext4/hash.c +++ b/fs/ext4/hash.c @@ -278,7 +278,7 @@ int ext4fs_dirhash(const struct inode *dir, const char *name, int len, unsigned char *buff; struct qstr qstr = {.name = name, .len = len }; - if (len && IS_CASEFOLDED(dir)) { + if (len && IS_CASEFOLDED(dir) && um) { buff = kzalloc(sizeof(char) * PATH_MAX, GFP_KERNEL); if (!buff) return -ENOMEM; diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index 6c05c95fa590..6c8e565f3c8a 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -5042,6 +5042,9 @@ struct inode *ext4_iget(struct super_block *sb, unsigned long ino) EXT4_ERROR_INODE(inode, "bogus i_mode (%o)", inode->i_mode); goto bad_inode; } + if (IS_CASEFOLDED(inode) && !ext4_has_feature_casefold(inode->i_sb)) + EXT4_ERROR_INODE(inode, + "casefold flag without casefold feature"); brelse(iloc.bh); unlock_new_inode(inode); diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 29219b7d4925..1531ba79e744 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -1294,7 +1294,7 @@ void ext4_fname_setup_ci_filename(struct inode *dir, const struct qstr *iname, { int len; - if (!IS_CASEFOLDED(dir)) { + if (!IS_CASEFOLDED(dir) || !EXT4_SB(dir->i_sb)->s_encoding) { cf_name->name = NULL; return; } @@ -2175,7 +2175,7 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry, #ifdef CONFIG_UNICODE if (ext4_has_strict_mode(sbi) && IS_CASEFOLDED(dir) && - utf8_validate(sbi->s_encoding, &dentry->d_name)) + sbi->s_encoding && utf8_validate(sbi->s_encoding, &dentry->d_name)) return -EINVAL; #endif