ext4: fix kernel oops caused by spurious casefold flag
authorTheodore Ts'o <tytso@mit.edu>
Tue, 3 Sep 2019 05:43:17 +0000 (01:43 -0400)
committerTheodore Ts'o <tytso@mit.edu>
Tue, 3 Sep 2019 05:43:17 +0000 (01:43 -0400)
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.

Signed-off-by: Theodore Ts'o <tytso@mit.edu>
fs/ext4/dir.c
fs/ext4/hash.c
fs/ext4/inode.c
fs/ext4/namei.c

index 86054f31fe4d35499350683192171a154d542766..9fdd2b269d6172a6856ef0fb98419bf7ed599671 100644 (file)
@@ -668,14 +668,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)
@@ -685,7 +686,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);
index d358bfcb6b3f302882494f4a67122e9ee4e426e5..3e133793a5a3466077d84e807b9dd4c729b51cec 100644 (file)
@@ -280,7 +280,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;
index e567f0229d4ef10cdab646e20be57417d278023c..4e271b509af18c1a36d937ba61ae144e339e900d 100644 (file)
@@ -5067,6 +5067,9 @@ struct inode *__ext4_iget(struct super_block *sb, unsigned long ino,
                                 "iget: 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, function, line, 0,
+                                "casefold flag without casefold feature");
        brelse(iloc.bh);
 
        unlock_new_inode(inode);
index 1290295340758c1b47d211f853ee4b6433888658..a427d2031a8dacb1f4a0379da72c85d3bfbd746a 100644 (file)
@@ -1312,7 +1312,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;
        }
@@ -2183,7 +2183,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