ext4: optimize match for casefolded encrypted dirs
authorDaniel Rosenberg <drosen@google.com>
Fri, 19 Mar 2021 07:34:14 +0000 (07:34 +0000)
committerTheodore Ts'o <tytso@mit.edu>
Tue, 6 Apr 2021 02:18:36 +0000 (22:18 -0400)
Matching names with casefolded encrypting directories requires
decrypting entries to confirm case since we are case preserving. We can
avoid needing to decrypt if our hash values don't match.

Signed-off-by: Daniel Rosenberg <drosen@google.com>
Link: https://lore.kernel.org/r/20210319073414.1381041-3-drosen@google.com
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
fs/ext4/ext4.h
fs/ext4/namei.c

index a796985..da7cb2c 100644 (file)
@@ -2638,9 +2638,9 @@ extern unsigned ext4_free_clusters_after_init(struct super_block *sb,
 ext4_fsblk_t ext4_inode_to_goal_block(struct inode *);
 
 #ifdef CONFIG_UNICODE
-extern void ext4_fname_setup_ci_filename(struct inode *dir,
+extern int ext4_fname_setup_ci_filename(struct inode *dir,
                                         const struct qstr *iname,
-                                        struct fscrypt_str *fname);
+                                        struct ext4_filename *fname);
 #endif
 
 #ifdef CONFIG_FS_ENCRYPTION
@@ -2671,9 +2671,9 @@ static inline int ext4_fname_setup_filename(struct inode *dir,
        ext4_fname_from_fscrypt_name(fname, &name);
 
 #ifdef CONFIG_UNICODE
-       ext4_fname_setup_ci_filename(dir, iname, &fname->cf_name);
+       err = ext4_fname_setup_ci_filename(dir, iname, fname);
 #endif
-       return 0;
+       return err;
 }
 
 static inline int ext4_fname_prepare_lookup(struct inode *dir,
@@ -2690,9 +2690,9 @@ static inline int ext4_fname_prepare_lookup(struct inode *dir,
        ext4_fname_from_fscrypt_name(fname, &name);
 
 #ifdef CONFIG_UNICODE
-       ext4_fname_setup_ci_filename(dir, &dentry->d_name, &fname->cf_name);
+       err = ext4_fname_setup_ci_filename(dir, &dentry->d_name, fname);
 #endif
-       return 0;
+       return err;
 }
 
 static inline void ext4_fname_free_filename(struct ext4_filename *fname)
@@ -2717,15 +2717,16 @@ static inline int ext4_fname_setup_filename(struct inode *dir,
                                            int lookup,
                                            struct ext4_filename *fname)
 {
+       int err = 0;
        fname->usr_fname = iname;
        fname->disk_name.name = (unsigned char *) iname->name;
        fname->disk_name.len = iname->len;
 
 #ifdef CONFIG_UNICODE
-       ext4_fname_setup_ci_filename(dir, iname, &fname->cf_name);
+       err = ext4_fname_setup_ci_filename(dir, iname, fname);
 #endif
 
-       return 0;
+       return err;
 }
 
 static inline int ext4_fname_prepare_lookup(struct inode *dir,
index 6864650..f2e9cb7 100644 (file)
@@ -816,7 +816,9 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,
        if (hinfo->hash_version <= DX_HASH_TEA)
                hinfo->hash_version += EXT4_SB(dir->i_sb)->s_hash_unsigned;
        hinfo->seed = EXT4_SB(dir->i_sb)->s_hash_seed;
-       if (fname && fname_name(fname))
+       /* hash is already computed for encrypted casefolded directory */
+       if (fname && fname_name(fname) &&
+                               !(IS_ENCRYPTED(dir) && IS_CASEFOLDED(dir)))
                ext4fs_dirhash(dir, fname_name(fname), fname_len(fname), hinfo);
        hash = hinfo->hash;
 
@@ -1367,19 +1369,21 @@ out:
        return ret;
 }
 
-void ext4_fname_setup_ci_filename(struct inode *dir, const struct qstr *iname,
-                                 struct fscrypt_str *cf_name)
+int ext4_fname_setup_ci_filename(struct inode *dir, const struct qstr *iname,
+                                 struct ext4_filename *name)
 {
+       struct fscrypt_str *cf_name = &name->cf_name;
+       struct dx_hash_info *hinfo = &name->hinfo;
        int len;
 
        if (!IS_CASEFOLDED(dir) || !dir->i_sb->s_encoding) {
                cf_name->name = NULL;
-               return;
+               return 0;
        }
 
        cf_name->name = kmalloc(EXT4_NAME_LEN, GFP_NOFS);
        if (!cf_name->name)
-               return;
+               return -ENOMEM;
 
        len = utf8_casefold(dir->i_sb->s_encoding,
                            iname, cf_name->name,
@@ -1387,10 +1391,18 @@ void ext4_fname_setup_ci_filename(struct inode *dir, const struct qstr *iname,
        if (len <= 0) {
                kfree(cf_name->name);
                cf_name->name = NULL;
-               return;
        }
        cf_name->len = (unsigned) len;
+       if (!IS_ENCRYPTED(dir))
+               return 0;
 
+       hinfo->hash_version = DX_HASH_SIPHASH;
+       hinfo->seed = NULL;
+       if (cf_name->name)
+               ext4fs_dirhash(dir, cf_name->name, cf_name->len, hinfo);
+       else
+               ext4fs_dirhash(dir, iname->name, iname->len, hinfo);
+       return 0;
 }
 #endif
 
@@ -1420,16 +1432,12 @@ static bool ext4_match(struct inode *parent,
                        struct qstr cf = {.name = fname->cf_name.name,
                                          .len = fname->cf_name.len};
                        if (IS_ENCRYPTED(parent)) {
-                               struct dx_hash_info hinfo;
-
-                               hinfo.hash_version = DX_HASH_SIPHASH;
-                               hinfo.seed = NULL;
-                               ext4fs_dirhash(parent, fname->cf_name.name,
-                                               fname_len(fname), &hinfo);
-                               if (hinfo.hash != EXT4_DIRENT_HASH(de) ||
-                                               hinfo.minor_hash !=
-                                                   EXT4_DIRENT_MINOR_HASH(de))
+                               if (fname->hinfo.hash != EXT4_DIRENT_HASH(de) ||
+                                       fname->hinfo.minor_hash !=
+                                               EXT4_DIRENT_MINOR_HASH(de)) {
+
                                        return 0;
+                               }
                        }
                        return !ext4_ci_compare(parent, &cf, de->name,
                                                        de->name_len, true);
@@ -2058,15 +2066,11 @@ void ext4_insert_dentry(struct inode *dir,
        de->name_len = fname_len(fname);
        memcpy(de->name, fname_name(fname), fname_len(fname));
        if (ext4_hash_in_dirent(dir)) {
-               struct dx_hash_info hinfo;
+               struct dx_hash_info *hinfo = &fname->hinfo;
 
-               hinfo.hash_version = DX_HASH_SIPHASH;
-               hinfo.seed = NULL;
-               ext4fs_dirhash(dir, fname_usr_name(fname),
-                               fname_len(fname), &hinfo);
-               EXT4_DIRENT_HASHES(de)->hash = cpu_to_le32(hinfo.hash);
+               EXT4_DIRENT_HASHES(de)->hash = cpu_to_le32(hinfo->hash);
                EXT4_DIRENT_HASHES(de)->minor_hash =
-                               cpu_to_le32(hinfo.minor_hash);
+                                               cpu_to_le32(hinfo->minor_hash);
        }
 }
 
@@ -2216,10 +2220,9 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,
        if (fname->hinfo.hash_version <= DX_HASH_TEA)
                fname->hinfo.hash_version += EXT4_SB(dir->i_sb)->s_hash_unsigned;
        fname->hinfo.seed = EXT4_SB(dir->i_sb)->s_hash_seed;
-       if (ext4_hash_in_dirent(dir))
-               ext4fs_dirhash(dir, fname_usr_name(fname),
-                               fname_len(fname), &fname->hinfo);
-       else
+
+       /* casefolded encrypted hashes are computed on fname setup */
+       if (!ext4_hash_in_dirent(dir))
                ext4fs_dirhash(dir, fname_name(fname),
                                fname_len(fname), &fname->hinfo);