ext4 crypto: optimize filename encryption
authorTheodore Ts'o <tytso@mit.edu>
Mon, 18 May 2015 17:14:47 +0000 (13:14 -0400)
committerTheodore Ts'o <tytso@mit.edu>
Mon, 18 May 2015 17:14:47 +0000 (13:14 -0400)
Encrypt the filename as soon it is passed in by the user.  This avoids
our needing to encrypt the filename 2 or 3 times while in the process
of creating a filename.

Similarly, when looking up a directory entry, encrypt the filename
early, or if the encryption key is not available, base-64 decode the
file syystem so that the hash value and the last 16 bytes of the
encrypted filename is available in the new struct ext4_filename data
structure.

Signed-off-by: Theodore Ts'o <tytso@mit.edu>
fs/ext4/crypto_fname.c
fs/ext4/ext4.h
fs/ext4/inline.c
fs/ext4/namei.c

index fded02f7229921a8693909ade14c2e7b16095f5e..ad5e32867de051e0c9e37ca68420d0133df88b15 100644 (file)
@@ -611,109 +611,82 @@ int ext4_fname_usr_to_disk(struct ext4_fname_crypto_ctx *ctx,
        return -EACCES;
 }
 
-/*
- * Calculate the htree hash from a filename from user space
- */
-int ext4_fname_usr_to_hash(struct ext4_fname_crypto_ctx *ctx,
-                           const struct qstr *iname,
-                           struct dx_hash_info *hinfo)
+int ext4_fname_setup_filename(struct inode *dir, const struct qstr *iname,
+                             int lookup, struct ext4_filename *fname)
 {
-       struct ext4_str tmp;
-       int ret = 0;
-       char buf[EXT4_FNAME_CRYPTO_DIGEST_SIZE+1];
+       struct ext4_fname_crypto_ctx *ctx;
+       int ret = 0, bigname = 0;
+
+       memset(fname, 0, sizeof(struct ext4_filename));
+       fname->usr_fname = iname;
 
-       if (!ctx ||
+       ctx = ext4_get_fname_crypto_ctx(dir, EXT4_NAME_LEN);
+       if (IS_ERR(ctx))
+               return PTR_ERR(ctx);
+       if ((ctx == NULL) ||
            ((iname->name[0] == '.') &&
             ((iname->len == 1) ||
              ((iname->name[1] == '.') && (iname->len == 2))))) {
-               ext4fs_dirhash(iname->name, iname->len, hinfo);
-               return 0;
+               fname->disk_name.name = (unsigned char *) iname->name;
+               fname->disk_name.len = iname->len;
+               goto out;
        }
-
-       if (!ctx->has_valid_key && iname->name[0] == '_') {
-               if (iname->len != 33)
-                       return -ENOENT;
-               ret = digest_decode(iname->name+1, iname->len, buf);
-               if (ret != 24)
-                       return -ENOENT;
-               memcpy(&hinfo->hash, buf, 4);
-               memcpy(&hinfo->minor_hash, buf + 4, 4);
-               return 0;
+       if (ctx->has_valid_key) {
+               ret = ext4_fname_crypto_alloc_buffer(ctx, iname->len,
+                                                    &fname->crypto_buf);
+               if (ret < 0)
+                       goto out;
+               ret = ext4_fname_encrypt(ctx, iname, &fname->crypto_buf);
+               if (ret < 0)
+                       goto out;
+               fname->disk_name.name = fname->crypto_buf.name;
+               fname->disk_name.len = fname->crypto_buf.len;
+               ret = 0;
+               goto out;
        }
-
-       if (!ctx->has_valid_key && iname->name[0] != '_') {
-               if (iname->len > 43)
-                       return -ENOENT;
-               ret = digest_decode(iname->name, iname->len, buf);
-               ext4fs_dirhash(buf, ret, hinfo);
-               return 0;
+       if (!lookup) {
+               ret = -EACCES;
+               goto out;
        }
 
-       /* First encrypt the plaintext name */
-       ret = ext4_fname_crypto_alloc_buffer(ctx, iname->len, &tmp);
-       if (ret < 0)
-               return ret;
-
-       ret = ext4_fname_encrypt(ctx, iname, &tmp);
-       if (ret >= 0) {
-               ext4fs_dirhash(tmp.name, tmp.len, hinfo);
-               ret = 0;
+       /* We don't have the key and we are doing a lookup; decode the
+        * user-supplied name
+        */
+       if (iname->name[0] == '_')
+               bigname = 1;
+       if ((bigname && (iname->len != 33)) ||
+           (!bigname && (iname->len > 43))) {
+               ret = -ENOENT;
        }
-
-       ext4_fname_crypto_free_buffer(&tmp);
+       fname->crypto_buf.name = kmalloc(32, GFP_KERNEL);
+       if (fname->crypto_buf.name == NULL) {
+               ret = -ENOMEM;
+               goto out;
+       }
+       ret = digest_decode(iname->name + bigname, iname->len - bigname,
+                           fname->crypto_buf.name);
+       if (ret < 0) {
+               ret = -ENOENT;
+               goto out;
+       }
+       fname->crypto_buf.len = ret;
+       if (bigname) {
+               memcpy(&fname->hinfo.hash, fname->crypto_buf.name, 4);
+               memcpy(&fname->hinfo.minor_hash, fname->crypto_buf.name + 4, 4);
+       } else {
+               fname->disk_name.name = fname->crypto_buf.name;
+               fname->disk_name.len = fname->crypto_buf.len;
+       }
+       ret = 0;
+out:
+       ext4_put_fname_crypto_ctx(&ctx);
        return ret;
 }
 
-int ext4_fname_match(struct ext4_fname_crypto_ctx *ctx, struct ext4_str *cstr,
-                    int len, const char * const name,
-                    struct ext4_dir_entry_2 *de)
+void ext4_fname_free_filename(struct ext4_filename *fname)
 {
-       int ret = -ENOENT;
-       int bigname = (*name == '_');
-
-       if (ctx->has_valid_key) {
-               if (cstr->name == NULL) {
-                       struct qstr istr;
-
-                       ret = ext4_fname_crypto_alloc_buffer(ctx, len, cstr);
-                       if (ret < 0)
-                               goto errout;
-                       istr.name = name;
-                       istr.len = len;
-                       ret = ext4_fname_encrypt(ctx, &istr, cstr);
-                       if (ret < 0)
-                               goto errout;
-               }
-       } else {
-               if (cstr->name == NULL) {
-                       cstr->name = kmalloc(32, GFP_KERNEL);
-                       if (cstr->name == NULL)
-                               return -ENOMEM;
-                       if ((bigname && (len != 33)) ||
-                           (!bigname && (len > 43)))
-                               goto errout;
-                       ret = digest_decode(name+bigname, len-bigname,
-                                           cstr->name);
-                       if (ret < 0) {
-                               ret = -ENOENT;
-                               goto errout;
-                       }
-                       cstr->len = ret;
-               }
-               if (bigname) {
-                       if (de->name_len < 16)
-                               return 0;
-                       ret = memcmp(de->name + de->name_len - 16,
-                                    cstr->name + 8, 16);
-                       return (ret == 0) ? 1 : 0;
-               }
-       }
-       if (de->name_len != cstr->len)
-               return 0;
-       ret = memcmp(de->name, cstr->name, cstr->len);
-       return (ret == 0) ? 1 : 0;
-errout:
-       kfree(cstr->name);
-       cstr->name = NULL;
-       return ret;
+       kfree(fname->crypto_buf.name);
+       fname->crypto_buf.name = NULL;
+       fname->usr_fname = NULL;
+       fname->disk_name.name = NULL;
 }
index 9a83f149ac85525b821a4d24ba5387b93b3229d6..866831e7c136d2a32da4817085ee22d07c09add7 100644 (file)
@@ -1838,6 +1838,17 @@ struct dx_hash_info
  */
 #define HASH_NB_ALWAYS         1
 
+struct ext4_filename {
+       const struct qstr *usr_fname;
+       struct ext4_str disk_name;
+       struct dx_hash_info hinfo;
+#ifdef CONFIG_EXT4_FS_ENCRYPTION
+       struct ext4_str crypto_buf;
+#endif
+};
+
+#define fname_name(p) ((p)->disk_name.name)
+#define fname_len(p)  ((p)->disk_name.len)
 
 /*
  * Describe an inode's exact location on disk and in memory
@@ -2098,21 +2109,16 @@ int ext4_fname_disk_to_usr(struct ext4_fname_crypto_ctx *ctx,
 int ext4_fname_usr_to_disk(struct ext4_fname_crypto_ctx *ctx,
                           const struct qstr *iname,
                           struct ext4_str *oname);
-int ext4_fname_usr_to_hash(struct ext4_fname_crypto_ctx *ctx,
-                          const struct qstr *iname,
-                          struct dx_hash_info *hinfo);
 int ext4_fname_crypto_namelen_on_disk(struct ext4_fname_crypto_ctx *ctx,
                                      u32 namelen);
-int ext4_fname_match(struct ext4_fname_crypto_ctx *ctx, struct ext4_str *cstr,
-                    int len, const char * const name,
-                    struct ext4_dir_entry_2 *de);
-
-
 #ifdef CONFIG_EXT4_FS_ENCRYPTION
 void ext4_put_fname_crypto_ctx(struct ext4_fname_crypto_ctx **ctx);
 struct ext4_fname_crypto_ctx *ext4_get_fname_crypto_ctx(struct inode *inode,
                                                        u32 max_len);
 void ext4_fname_crypto_free_buffer(struct ext4_str *crypto_str);
+int ext4_fname_setup_filename(struct inode *dir, const struct qstr *iname,
+                             int lookup, struct ext4_filename *fname);
+void ext4_fname_free_filename(struct ext4_filename *fname);
 #else
 static inline
 void ext4_put_fname_crypto_ctx(struct ext4_fname_crypto_ctx **ctx) { }
@@ -2123,6 +2129,16 @@ struct ext4_fname_crypto_ctx *ext4_get_fname_crypto_ctx(struct inode *inode,
        return NULL;
 }
 static inline void ext4_fname_crypto_free_buffer(struct ext4_str *p) { }
+static inline int ext4_fname_setup_filename(struct inode *dir,
+                                    const struct qstr *iname,
+                                    int lookup, struct ext4_filename *fname)
+{
+       fname->usr_fname = iname;
+       fname->disk_name.name = (unsigned char *) iname->name;
+       fname->disk_name.len = iname->len;
+       return 0;
+}
+static inline void ext4_fname_free_filename(struct ext4_filename *fname) { }
 #endif
 
 
@@ -2156,14 +2172,13 @@ extern void ext4_htree_free_dir_info(struct dir_private_info *p);
 extern int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                             struct buffer_head *bh,
                             void *buf, int buf_size,
-                            const char *name, int namelen,
+                            struct ext4_filename *fname,
                             struct ext4_dir_entry_2 **dest_de);
 int ext4_insert_dentry(struct inode *dir,
-                       struct inode *inode,
-                       struct ext4_dir_entry_2 *de,
-                       int buf_size,
-                      const struct qstr *iname,
-                       const char *name, int namelen);
+                      struct inode *inode,
+                      struct ext4_dir_entry_2 *de,
+                      int buf_size,
+                      struct ext4_filename *fname);
 static inline void ext4_update_dx_flag(struct inode *inode)
 {
        if (!EXT4_HAS_COMPAT_FEATURE(inode->i_sb,
@@ -2317,13 +2332,14 @@ extern int ext4_orphan_add(handle_t *, struct inode *);
 extern int ext4_orphan_del(handle_t *, struct inode *);
 extern int ext4_htree_fill_tree(struct file *dir_file, __u32 start_hash,
                                __u32 start_minor_hash, __u32 *next_hash);
-extern int search_dir(struct buffer_head *bh,
-                     char *search_buf,
-                     int buf_size,
-                     struct inode *dir,
-                     const struct qstr *d_name,
-                     unsigned int offset,
-                     struct ext4_dir_entry_2 **res_dir);
+extern int ext4_search_dir(struct buffer_head *bh,
+                          char *search_buf,
+                          int buf_size,
+                          struct inode *dir,
+                          struct ext4_filename *fname,
+                          const struct qstr *d_name,
+                          unsigned int offset,
+                          struct ext4_dir_entry_2 **res_dir);
 extern int ext4_generic_delete_entry(handle_t *handle,
                                     struct inode *dir,
                                     struct ext4_dir_entry_2 *de_del,
@@ -2768,7 +2784,9 @@ extern int ext4_da_write_inline_data_begin(struct address_space *mapping,
 extern int ext4_da_write_inline_data_end(struct inode *inode, loff_t pos,
                                         unsigned len, unsigned copied,
                                         struct page *page);
-extern int ext4_try_add_inline_entry(handle_t *handle, struct dentry *dentry,
+extern int ext4_try_add_inline_entry(handle_t *handle,
+                                    struct ext4_filename *fname,
+                                    struct dentry *dentry,
                                     struct inode *inode);
 extern int ext4_try_create_inline_dir(handle_t *handle,
                                      struct inode *parent,
@@ -2782,6 +2800,7 @@ extern int htree_inlinedir_to_tree(struct file *dir_file,
                                   __u32 start_hash, __u32 start_minor_hash,
                                   int *has_inline_data);
 extern struct buffer_head *ext4_find_inline_entry(struct inode *dir,
+                                       struct ext4_filename *fname,
                                        const struct qstr *d_name,
                                        struct ext4_dir_entry_2 **res_dir,
                                        int *has_inline_data);
index 095c7a258d97d0bf05b26de9bf42c99fc01b143d..cd944a7a99cdcf0d5f53c7823003f3f6d7df6667 100644 (file)
@@ -995,20 +995,18 @@ void ext4_show_inline_dir(struct inode *dir, struct buffer_head *bh,
  * and -EEXIST if directory entry already exists.
  */
 static int ext4_add_dirent_to_inline(handle_t *handle,
+                                    struct ext4_filename *fname,
                                     struct dentry *dentry,
                                     struct inode *inode,
                                     struct ext4_iloc *iloc,
                                     void *inline_start, int inline_size)
 {
        struct inode    *dir = d_inode(dentry->d_parent);
-       const char      *name = dentry->d_name.name;
-       int             namelen = dentry->d_name.len;
        int             err;
        struct ext4_dir_entry_2 *de;
 
-       err = ext4_find_dest_de(dir, inode, iloc->bh,
-                               inline_start, inline_size,
-                               name, namelen, &de);
+       err = ext4_find_dest_de(dir, inode, iloc->bh, inline_start,
+                               inline_size, fname, &de);
        if (err)
                return err;
 
@@ -1016,8 +1014,7 @@ static int ext4_add_dirent_to_inline(handle_t *handle,
        err = ext4_journal_get_write_access(handle, iloc->bh);
        if (err)
                return err;
-       ext4_insert_dentry(dir, inode, de, inline_size, &dentry->d_name,
-                          name, namelen);
+       ext4_insert_dentry(dir, inode, de, inline_size, fname);
 
        ext4_show_inline_dir(dir, iloc->bh, inline_start, inline_size);
 
@@ -1248,8 +1245,8 @@ out:
  * If succeeds, return 0. If not, extended the inline dir and copied data to
  * the new created block.
  */
-int ext4_try_add_inline_entry(handle_t *handle, struct dentry *dentry,
-                             struct inode *inode)
+int ext4_try_add_inline_entry(handle_t *handle, struct ext4_filename *fname,
+                             struct dentry *dentry, struct inode *inode)
 {
        int ret, inline_size;
        void *inline_start;
@@ -1268,7 +1265,7 @@ int ext4_try_add_inline_entry(handle_t *handle, struct dentry *dentry,
                                                 EXT4_INLINE_DOTDOT_SIZE;
        inline_size = EXT4_MIN_INLINE_DATA_SIZE - EXT4_INLINE_DOTDOT_SIZE;
 
-       ret = ext4_add_dirent_to_inline(handle, dentry, inode, &iloc,
+       ret = ext4_add_dirent_to_inline(handle, fname, dentry, inode, &iloc,
                                        inline_start, inline_size);
        if (ret != -ENOSPC)
                goto out;
@@ -1289,8 +1286,9 @@ int ext4_try_add_inline_entry(handle_t *handle, struct dentry *dentry,
        if (inline_size) {
                inline_start = ext4_get_inline_xattr_pos(dir, &iloc);
 
-               ret = ext4_add_dirent_to_inline(handle, dentry, inode, &iloc,
-                                               inline_start, inline_size);
+               ret = ext4_add_dirent_to_inline(handle, fname, dentry,
+                                               inode, &iloc, inline_start,
+                                               inline_size);
 
                if (ret != -ENOSPC)
                        goto out;
@@ -1611,6 +1609,7 @@ out:
 }
 
 struct buffer_head *ext4_find_inline_entry(struct inode *dir,
+                                       struct ext4_filename *fname,
                                        const struct qstr *d_name,
                                        struct ext4_dir_entry_2 **res_dir,
                                        int *has_inline_data)
@@ -1632,8 +1631,8 @@ struct buffer_head *ext4_find_inline_entry(struct inode *dir,
        inline_start = (void *)ext4_raw_inode(&iloc)->i_block +
                                                EXT4_INLINE_DOTDOT_SIZE;
        inline_size = EXT4_MIN_INLINE_DATA_SIZE - EXT4_INLINE_DOTDOT_SIZE;
-       ret = search_dir(iloc.bh, inline_start, inline_size,
-                        dir, d_name, 0, res_dir);
+       ret = ext4_search_dir(iloc.bh, inline_start, inline_size,
+                             dir, fname, d_name, 0, res_dir);
        if (ret == 1)
                goto out_find;
        if (ret < 0)
@@ -1645,8 +1644,8 @@ struct buffer_head *ext4_find_inline_entry(struct inode *dir,
        inline_start = ext4_get_inline_xattr_pos(dir, &iloc);
        inline_size = ext4_get_inline_size(dir) - EXT4_MIN_INLINE_DATA_SIZE;
 
-       ret = search_dir(iloc.bh, inline_start, inline_size,
-                        dir, d_name, 0, res_dir);
+       ret = ext4_search_dir(iloc.bh, inline_start, inline_size,
+                             dir, fname, d_name, 0, res_dir);
        if (ret == 1)
                goto out_find;
 
index 814f3beb436965f116b7555ee8cf9ac30c3f0165..56c60cb2d3fe952514a4be77ccec7f2fa08507f2 100644 (file)
@@ -248,7 +248,7 @@ static void dx_set_count(struct dx_entry *entries, unsigned value);
 static void dx_set_limit(struct dx_entry *entries, unsigned value);
 static unsigned dx_root_limit(struct inode *dir, unsigned infosize);
 static unsigned dx_node_limit(struct inode *dir);
-static struct dx_frame *dx_probe(const struct qstr *d_name,
+static struct dx_frame *dx_probe(struct ext4_filename *fname,
                                 struct inode *dir,
                                 struct dx_hash_info *hinfo,
                                 struct dx_frame *frame);
@@ -267,10 +267,10 @@ static int ext4_htree_next_block(struct inode *dir, __u32 hash,
                                 struct dx_frame *frames,
                                 __u32 *start_hash);
 static struct buffer_head * ext4_dx_find_entry(struct inode *dir,
-               const struct qstr *d_name,
+               struct ext4_filename *fname,
                struct ext4_dir_entry_2 **res_dir);
-static int ext4_dx_add_entry(handle_t *handle, struct dentry *dentry,
-                            struct inode *inode);
+static int ext4_dx_add_entry(handle_t *handle, struct ext4_filename *fname,
+                            struct dentry *dentry, struct inode *inode);
 
 /* checksumming functions */
 void initialize_dirent_tail(struct ext4_dir_entry_tail *t,
@@ -724,7 +724,7 @@ struct stats dx_show_entries(struct dx_hash_info *hinfo, struct inode *dir,
  * back to userspace.
  */
 static struct dx_frame *
-dx_probe(const struct qstr *d_name, struct inode *dir,
+dx_probe(struct ext4_filename *fname, struct inode *dir,
         struct dx_hash_info *hinfo, struct dx_frame *frame_in)
 {
        unsigned count, indirect;
@@ -746,32 +746,14 @@ dx_probe(const struct qstr *d_name, struct inode *dir,
                             root->info.hash_version);
                goto fail;
        }
+       if (fname)
+               hinfo = &fname->hinfo;
        hinfo->hash_version = root->info.hash_version;
        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;
-#ifdef CONFIG_EXT4_FS_ENCRYPTION
-       if (d_name) {
-               struct ext4_fname_crypto_ctx *ctx = NULL;
-               int res;
-
-               /* Check if the directory is encrypted */
-               ctx = ext4_get_fname_crypto_ctx(dir, EXT4_NAME_LEN);
-               if (IS_ERR(ctx)) {
-                       ret_err = ERR_PTR(PTR_ERR(ctx));
-                       goto fail;
-               }
-               res = ext4_fname_usr_to_hash(ctx, d_name, hinfo);
-               if (res < 0) {
-                       ret_err = ERR_PTR(res);
-                       goto fail;
-               }
-               ext4_put_fname_crypto_ctx(&ctx);
-       }
-#else
-       if (d_name)
-               ext4fs_dirhash(d_name->name, d_name->len, hinfo);
-#endif
+       if (fname && fname_name(fname))
+               ext4fs_dirhash(fname_name(fname), fname_len(fname), hinfo);
        hash = hinfo->hash;
 
        if (root->info.unused_flags & 1) {
@@ -1155,12 +1137,13 @@ errout:
 
 static inline int search_dirblock(struct buffer_head *bh,
                                  struct inode *dir,
+                                 struct ext4_filename *fname,
                                  const struct qstr *d_name,
                                  unsigned int offset,
                                  struct ext4_dir_entry_2 **res_dir)
 {
-       return search_dir(bh, bh->b_data, dir->i_sb->s_blocksize, dir,
-                         d_name, offset, res_dir);
+       return ext4_search_dir(bh, bh->b_data, dir->i_sb->s_blocksize, dir,
+                              fname, d_name, offset, res_dir);
 }
 
 /*
@@ -1242,54 +1225,54 @@ static void dx_insert_block(struct dx_frame *frame, u32 hash, ext4_lblk_t block)
  * `len <= EXT4_NAME_LEN' is guaranteed by caller.
  * `de != NULL' is guaranteed by caller.
  */
-static inline int ext4_match(struct ext4_fname_crypto_ctx *ctx,
-                            struct ext4_str *fname_crypto_str,
-                            int len, const char * const name,
+static inline int ext4_match(struct ext4_filename *fname,
                             struct ext4_dir_entry_2 *de)
 {
-       int res;
+       const void *name = fname_name(fname);
+       u32 len = fname_len(fname);
 
        if (!de->inode)
                return 0;
 
 #ifdef CONFIG_EXT4_FS_ENCRYPTION
-       if (ctx)
-               return ext4_fname_match(ctx, fname_crypto_str, len, name, de);
+       if (unlikely(!name)) {
+               if (fname->usr_fname->name[0] == '_') {
+                       int ret;
+                       if (de->name_len < 16)
+                               return 0;
+                       ret = memcmp(de->name + de->name_len - 16,
+                                    fname->crypto_buf.name + 8, 16);
+                       return (ret == 0) ? 1 : 0;
+               }
+               name = fname->crypto_buf.name;
+               len = fname->crypto_buf.len;
+       }
 #endif
-       if (len != de->name_len)
+       if (de->name_len != len)
                return 0;
-       res = memcmp(name, de->name, len);
-       return (res == 0) ? 1 : 0;
+       return (memcmp(de->name, name, len) == 0) ? 1 : 0;
 }
 
 /*
  * Returns 0 if not found, -1 on failure, and 1 on success
  */
-int search_dir(struct buffer_head *bh, char *search_buf, int buf_size,
-              struct inode *dir, const struct qstr *d_name,
-              unsigned int offset, struct ext4_dir_entry_2 **res_dir)
+int ext4_search_dir(struct buffer_head *bh, char *search_buf, int buf_size,
+                   struct inode *dir, struct ext4_filename *fname,
+                   const struct qstr *d_name,
+                   unsigned int offset, struct ext4_dir_entry_2 **res_dir)
 {
        struct ext4_dir_entry_2 * de;
        char * dlimit;
        int de_len;
-       const char *name = d_name->name;
-       int namelen = d_name->len;
-       struct ext4_fname_crypto_ctx *ctx = NULL;
-       struct ext4_str fname_crypto_str = {.name = NULL, .len = 0};
        int res;
 
-       ctx = ext4_get_fname_crypto_ctx(dir, EXT4_NAME_LEN);
-       if (IS_ERR(ctx))
-               return -1;
-
        de = (struct ext4_dir_entry_2 *)search_buf;
        dlimit = search_buf + buf_size;
        while ((char *) de < dlimit) {
                /* this code is executed quadratically often */
                /* do minimal checking `by hand' */
                if ((char *) de + de->name_len <= dlimit) {
-                       res = ext4_match(ctx, &fname_crypto_str, namelen,
-                                        name, de);
+                       res = ext4_match(fname, de);
                        if (res < 0) {
                                res = -1;
                                goto return_result;
@@ -1322,8 +1305,6 @@ int search_dir(struct buffer_head *bh, char *search_buf, int buf_size,
 
        res = 0;
 return_result:
-       ext4_put_fname_crypto_ctx(&ctx);
-       ext4_fname_crypto_free_buffer(&fname_crypto_str);
        return res;
 }
 
@@ -1370,7 +1351,8 @@ static struct buffer_head * ext4_find_entry (struct inode *dir,
                                   buffer */
        int num = 0;
        ext4_lblk_t  nblocks;
-       int i, namelen;
+       int i, namelen, retval;
+       struct ext4_filename fname;
 
        *res_dir = NULL;
        sb = dir->i_sb;
@@ -1378,14 +1360,18 @@ static struct buffer_head * ext4_find_entry (struct inode *dir,
        if (namelen > EXT4_NAME_LEN)
                return NULL;
 
+       retval = ext4_fname_setup_filename(dir, d_name, 1, &fname);
+       if (retval)
+               return ERR_PTR(retval);
+
        if (ext4_has_inline_data(dir)) {
                int has_inline_data = 1;
-               ret = ext4_find_inline_entry(dir, d_name, res_dir,
+               ret = ext4_find_inline_entry(dir, &fname, d_name, res_dir,
                                             &has_inline_data);
                if (has_inline_data) {
                        if (inlined)
                                *inlined = 1;
-                       return ret;
+                       goto cleanup_and_exit;
                }
        }
 
@@ -1400,14 +1386,14 @@ static struct buffer_head * ext4_find_entry (struct inode *dir,
                goto restart;
        }
        if (is_dx(dir)) {
-               bh = ext4_dx_find_entry(dir, d_name, res_dir);
+               ret = ext4_dx_find_entry(dir, &fname, res_dir);
                /*
                 * On success, or if the error was file not found,
                 * return.  Otherwise, fall back to doing a search the
                 * old fashioned way.
                 */
-               if (!IS_ERR(bh) || PTR_ERR(bh) != ERR_BAD_DX_DIR)
-                       return bh;
+               if (!IS_ERR(ret) || PTR_ERR(ret) != ERR_BAD_DX_DIR)
+                       goto cleanup_and_exit;
                dxtrace(printk(KERN_DEBUG "ext4_find_entry: dx failed, "
                               "falling back\n"));
        }
@@ -1438,8 +1424,10 @@ restart:
                                num++;
                                bh = ext4_getblk(NULL, dir, b++, 0);
                                if (unlikely(IS_ERR(bh))) {
-                                       if (ra_max == 0)
-                                               return bh;
+                                       if (ra_max == 0) {
+                                               ret = bh;
+                                               goto cleanup_and_exit;
+                                       }
                                        break;
                                }
                                bh_use[ra_max] = bh;
@@ -1469,7 +1457,7 @@ restart:
                        goto next;
                }
                set_buffer_verified(bh);
-               i = search_dirblock(bh, dir, d_name,
+               i = search_dirblock(bh, dir, &fname, d_name,
                            block << EXT4_BLOCK_SIZE_BITS(sb), res_dir);
                if (i == 1) {
                        EXT4_I(dir)->i_dir_start_lookup = block;
@@ -1500,15 +1488,17 @@ cleanup_and_exit:
        /* Clean up the read-ahead blocks */
        for (; ra_ptr < ra_max; ra_ptr++)
                brelse(bh_use[ra_ptr]);
+       ext4_fname_free_filename(&fname);
        return ret;
 }
 
-static struct buffer_head * ext4_dx_find_entry(struct inode *dir, const struct qstr *d_name,
-                      struct ext4_dir_entry_2 **res_dir)
+static struct buffer_head * ext4_dx_find_entry(struct inode *dir,
+                       struct ext4_filename *fname,
+                       struct ext4_dir_entry_2 **res_dir)
 {
        struct super_block * sb = dir->i_sb;
-       struct dx_hash_info     hinfo;
        struct dx_frame frames[2], *frame;
+       const struct qstr *d_name = fname->usr_fname;
        struct buffer_head *bh;
        ext4_lblk_t block;
        int retval;
@@ -1516,7 +1506,7 @@ static struct buffer_head * ext4_dx_find_entry(struct inode *dir, const struct q
 #ifdef CONFIG_EXT4_FS_ENCRYPTION
        *res_dir = NULL;
 #endif
-       frame = dx_probe(d_name, dir, &hinfo, frames);
+       frame = dx_probe(fname, dir, NULL, frames);
        if (IS_ERR(frame))
                return (struct buffer_head *) frame;
        do {
@@ -1525,7 +1515,7 @@ static struct buffer_head * ext4_dx_find_entry(struct inode *dir, const struct q
                if (IS_ERR(bh))
                        goto errout;
 
-               retval = search_dirblock(bh, dir, d_name,
+               retval = search_dirblock(bh, dir, fname, d_name,
                                         block << EXT4_BLOCK_SIZE_BITS(sb),
                                         res_dir);
                if (retval == 1)
@@ -1537,7 +1527,7 @@ static struct buffer_head * ext4_dx_find_entry(struct inode *dir, const struct q
                }
 
                /* Check to see if we should continue to search */
-               retval = ext4_htree_next_block(dir, hinfo.hash, frame,
+               retval = ext4_htree_next_block(dir, fname->hinfo.hash, frame,
                                               frames, NULL);
                if (retval < 0) {
                        ext4_warning(sb,
@@ -1796,32 +1786,16 @@ journal_error:
 int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                      struct buffer_head *bh,
                      void *buf, int buf_size,
-                     const char *name, int namelen,
+                     struct ext4_filename *fname,
                      struct ext4_dir_entry_2 **dest_de)
 {
        struct ext4_dir_entry_2 *de;
-       unsigned short reclen = EXT4_DIR_REC_LEN(namelen);
+       unsigned short reclen = EXT4_DIR_REC_LEN(fname_len(fname));
        int nlen, rlen;
        unsigned int offset = 0;
        char *top;
-       struct ext4_fname_crypto_ctx *ctx = NULL;
-       struct ext4_str fname_crypto_str = {.name = NULL, .len = 0};
        int res;
 
-       ctx = ext4_get_fname_crypto_ctx(dir, EXT4_NAME_LEN);
-       if (IS_ERR(ctx))
-               return -1;
-
-       if (ctx != NULL) {
-               /* Calculate record length needed to store the entry */
-               res = ext4_fname_crypto_namelen_on_disk(ctx, namelen);
-               if (res < 0) {
-                       ext4_put_fname_crypto_ctx(&ctx);
-                       return res;
-               }
-               reclen = EXT4_DIR_REC_LEN(res);
-       }
-
        de = (struct ext4_dir_entry_2 *)buf;
        top = buf + buf_size - reclen;
        while ((char *) de <= top) {
@@ -1831,7 +1805,7 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                        goto return_result;
                }
                /* Provide crypto context and crypto buffer to ext4 match */
-               res = ext4_match(ctx, &fname_crypto_str, namelen, name, de);
+               res = ext4_match(fname, de);
                if (res < 0)
                        goto return_result;
                if (res > 0) {
@@ -1853,8 +1827,6 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
                res = 0;
        }
 return_result:
-       ext4_put_fname_crypto_ctx(&ctx);
-       ext4_fname_crypto_free_buffer(&fname_crypto_str);
        return res;
 }
 
@@ -1862,39 +1834,10 @@ int ext4_insert_dentry(struct inode *dir,
                       struct inode *inode,
                       struct ext4_dir_entry_2 *de,
                       int buf_size,
-                      const struct qstr *iname,
-                      const char *name, int namelen)
+                      struct ext4_filename *fname)
 {
 
        int nlen, rlen;
-       struct ext4_fname_crypto_ctx *ctx = NULL;
-       struct ext4_str fname_crypto_str = {.name = NULL, .len = 0};
-       struct ext4_str tmp_str;
-       int res;
-
-       ctx = ext4_get_fname_crypto_ctx(dir, EXT4_NAME_LEN);
-       if (IS_ERR(ctx))
-               return -EIO;
-       /* By default, the input name would be written to the disk */
-       tmp_str.name = (unsigned char *)name;
-       tmp_str.len = namelen;
-       if (ctx != NULL) {
-               /* Directory is encrypted */
-               res = ext4_fname_crypto_alloc_buffer(ctx, EXT4_NAME_LEN,
-                                                    &fname_crypto_str);
-               if (res < 0) {
-                       ext4_put_fname_crypto_ctx(&ctx);
-                       return -ENOMEM;
-               }
-               res = ext4_fname_usr_to_disk(ctx, iname, &fname_crypto_str);
-               if (res < 0) {
-                       ext4_put_fname_crypto_ctx(&ctx);
-                       ext4_fname_crypto_free_buffer(&fname_crypto_str);
-                       return res;
-               }
-               tmp_str.name = fname_crypto_str.name;
-               tmp_str.len = fname_crypto_str.len;
-       }
 
        nlen = EXT4_DIR_REC_LEN(de->name_len);
        rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
@@ -1908,11 +1851,8 @@ int ext4_insert_dentry(struct inode *dir,
        de->file_type = EXT4_FT_UNKNOWN;
        de->inode = cpu_to_le32(inode->i_ino);
        ext4_set_de_type(inode->i_sb, de, inode->i_mode);
-       de->name_len = tmp_str.len;
-
-       memcpy(de->name, tmp_str.name, tmp_str.len);
-       ext4_put_fname_crypto_ctx(&ctx);
-       ext4_fname_crypto_free_buffer(&fname_crypto_str);
+       de->name_len = fname_len(fname);
+       memcpy(de->name, fname_name(fname), fname_len(fname));
        return 0;
 }
 
@@ -1924,13 +1864,11 @@ int ext4_insert_dentry(struct inode *dir,
  * space.  It will return -ENOSPC if no space is available, and -EIO
  * and -EEXIST if directory entry already exists.
  */
-static int add_dirent_to_buf(handle_t *handle, struct dentry *dentry,
+static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
+                            struct inode *dir,
                             struct inode *inode, struct ext4_dir_entry_2 *de,
                             struct buffer_head *bh)
 {
-       struct inode    *dir = d_inode(dentry->d_parent);
-       const char      *name = dentry->d_name.name;
-       int             namelen = dentry->d_name.len;
        unsigned int    blocksize = dir->i_sb->s_blocksize;
        int             csum_size = 0;
        int             err;
@@ -1939,9 +1877,8 @@ static int add_dirent_to_buf(handle_t *handle, struct dentry *dentry,
                csum_size = sizeof(struct ext4_dir_entry_tail);
 
        if (!de) {
-               err = ext4_find_dest_de(dir, inode,
-                                       bh, bh->b_data, blocksize - csum_size,
-                                       name, namelen, &de);
+               err = ext4_find_dest_de(dir, inode, bh, bh->b_data,
+                                       blocksize - csum_size, fname, &de);
                if (err)
                        return err;
        }
@@ -1954,8 +1891,7 @@ static int add_dirent_to_buf(handle_t *handle, struct dentry *dentry,
 
        /* By now the buffer is marked for journaling. Due to crypto operations,
         * the following function call may fail */
-       err = ext4_insert_dentry(dir, inode, de, blocksize, &dentry->d_name,
-                                name, namelen);
+       err = ext4_insert_dentry(dir, inode, de, blocksize, fname);
        if (err < 0)
                return err;
 
@@ -1985,17 +1921,11 @@ static int add_dirent_to_buf(handle_t *handle, struct dentry *dentry,
  * This converts a one block unindexed directory to a 3 block indexed
  * directory, and adds the dentry to the indexed directory.
  */
-static int make_indexed_dir(handle_t *handle, struct dentry *dentry,
+static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,
+                           struct dentry *dentry,
                            struct inode *inode, struct buffer_head *bh)
 {
        struct inode    *dir = d_inode(dentry->d_parent);
-#ifdef CONFIG_EXT4_FS_ENCRYPTION
-       struct ext4_fname_crypto_ctx *ctx = NULL;
-       int res;
-#else
-       const char      *name = dentry->d_name.name;
-       int             namelen = dentry->d_name.len;
-#endif
        struct buffer_head *bh2;
        struct dx_root  *root;
        struct dx_frame frames[2], *frame;
@@ -2006,17 +1936,10 @@ static int make_indexed_dir(handle_t *handle, struct dentry *dentry,
        unsigned        len;
        int             retval;
        unsigned        blocksize;
-       struct dx_hash_info hinfo;
        ext4_lblk_t  block;
        struct fake_dirent *fde;
        int csum_size = 0;
 
-#ifdef CONFIG_EXT4_FS_ENCRYPTION
-       ctx = ext4_get_fname_crypto_ctx(dir, EXT4_NAME_LEN);
-       if (IS_ERR(ctx))
-               return PTR_ERR(ctx);
-#endif
-
        if (ext4_has_metadata_csum(inode->i_sb))
                csum_size = sizeof(struct ext4_dir_entry_tail);
 
@@ -2078,22 +2001,12 @@ static int make_indexed_dir(handle_t *handle, struct dentry *dentry,
        dx_set_limit(entries, dx_root_limit(dir, sizeof(root->info)));
 
        /* Initialize as for dx_probe */
-       hinfo.hash_version = root->info.hash_version;
-       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;
-#ifdef CONFIG_EXT4_FS_ENCRYPTION
-       res = ext4_fname_usr_to_hash(ctx, &dentry->d_name, &hinfo);
-       if (res < 0) {
-               ext4_put_fname_crypto_ctx(&ctx);
-               ext4_mark_inode_dirty(handle, dir);
-               brelse(bh);
-               return res;
-       }
-       ext4_put_fname_crypto_ctx(&ctx);
-#else
-       ext4fs_dirhash(name, namelen, &hinfo);
-#endif
+       fname->hinfo.hash_version = root->info.hash_version;
+       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;
+       ext4fs_dirhash(fname_name(fname), fname_len(fname), &fname->hinfo);
+
        memset(frames, 0, sizeof(frames));
        frame = frames;
        frame->entries = entries;
@@ -2108,14 +2021,14 @@ static int make_indexed_dir(handle_t *handle, struct dentry *dentry,
        if (retval)
                goto out_frames;        
 
-       de = do_split(handle,dir, &bh, frame, &hinfo);
+       de = do_split(handle,dir, &bh, frame, &fname->hinfo);
        if (IS_ERR(de)) {
                retval = PTR_ERR(de);
                goto out_frames;
        }
        dx_release(frames);
 
-       retval = add_dirent_to_buf(handle, dentry, inode, de, bh);
+       retval = add_dirent_to_buf(handle, fname, dir, inode, de, bh);
        brelse(bh);
        return retval;
 out_frames:
@@ -2147,6 +2060,7 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
        struct ext4_dir_entry_2 *de;
        struct ext4_dir_entry_tail *t;
        struct super_block *sb;
+       struct ext4_filename fname;
        int     retval;
        int     dx_fallback=0;
        unsigned blocksize;
@@ -2161,10 +2075,15 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
        if (!dentry->d_name.len)
                return -EINVAL;
 
+       retval = ext4_fname_setup_filename(dir, &dentry->d_name, 0, &fname);
+       if (retval)
+               return retval;
+
        if (ext4_has_inline_data(dir)) {
-               retval = ext4_try_add_inline_entry(handle, dentry, inode);
+               retval = ext4_try_add_inline_entry(handle, &fname,
+                                                  dentry, inode);
                if (retval < 0)
-                       return retval;
+                       goto out;
                if (retval == 1) {
                        retval = 0;
                        goto out;
@@ -2172,7 +2091,7 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
        }
 
        if (is_dx(dir)) {
-               retval = ext4_dx_add_entry(handle, dentry, inode);
+               retval = ext4_dx_add_entry(handle, &fname, dentry, inode);
                if (!retval || (retval != ERR_BAD_DX_DIR))
                        goto out;
                ext4_clear_inode_flag(dir, EXT4_INODE_INDEX);
@@ -2182,24 +2101,31 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
        blocks = dir->i_size >> sb->s_blocksize_bits;
        for (block = 0; block < blocks; block++) {
                bh = ext4_read_dirblock(dir, block, DIRENT);
-               if (IS_ERR(bh))
-                       return PTR_ERR(bh);
-
-               retval = add_dirent_to_buf(handle, dentry, inode, NULL, bh);
+               if (IS_ERR(bh)) {
+                       retval = PTR_ERR(bh);
+                       bh = NULL;
+                       goto out;
+               }
+               retval = add_dirent_to_buf(handle, &fname, dir, inode,
+                                          NULL, bh);
                if (retval != -ENOSPC)
                        goto out;
 
                if (blocks == 1 && !dx_fallback &&
                    EXT4_HAS_COMPAT_FEATURE(sb, EXT4_FEATURE_COMPAT_DIR_INDEX)) {
-                       retval = make_indexed_dir(handle, dentry, inode, bh);
+                       retval = make_indexed_dir(handle, &fname, dentry,
+                                                 inode, bh);
                        bh = NULL; /* make_indexed_dir releases bh */
                        goto out;
                }
                brelse(bh);
        }
        bh = ext4_append(handle, dir, &block);
-       if (IS_ERR(bh))
-               return PTR_ERR(bh);
+       if (IS_ERR(bh)) {
+               retval = PTR_ERR(bh);
+               bh = NULL;
+               goto out;
+       }
        de = (struct ext4_dir_entry_2 *) bh->b_data;
        de->inode = 0;
        de->rec_len = ext4_rec_len_to_disk(blocksize - csum_size, blocksize);
@@ -2209,8 +2135,9 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
                initialize_dirent_tail(t, blocksize);
        }
 
-       retval = add_dirent_to_buf(handle, dentry, inode, de, bh);
+       retval = add_dirent_to_buf(handle, &fname, dir, inode, de, bh);
 out:
+       ext4_fname_free_filename(&fname);
        brelse(bh);
        if (retval == 0)
                ext4_set_inode_state(inode, EXT4_STATE_NEWENTRY);
@@ -2220,19 +2147,18 @@ out:
 /*
  * Returns 0 for success, or a negative error value
  */
-static int ext4_dx_add_entry(handle_t *handle, struct dentry *dentry,
-                            struct inode *inode)
+static int ext4_dx_add_entry(handle_t *handle, struct ext4_filename *fname,
+                            struct dentry *dentry, struct inode *inode)
 {
        struct dx_frame frames[2], *frame;
        struct dx_entry *entries, *at;
-       struct dx_hash_info hinfo;
        struct buffer_head *bh;
        struct inode *dir = d_inode(dentry->d_parent);
        struct super_block *sb = dir->i_sb;
        struct ext4_dir_entry_2 *de;
        int err;
 
-       frame = dx_probe(&dentry->d_name, dir, &hinfo, frames);
+       frame = dx_probe(fname, dir, NULL, frames);
        if (IS_ERR(frame))
                return PTR_ERR(frame);
        entries = frame->entries;
@@ -2249,7 +2175,7 @@ static int ext4_dx_add_entry(handle_t *handle, struct dentry *dentry,
        if (err)
                goto journal_error;
 
-       err = add_dirent_to_buf(handle, dentry, inode, NULL, bh);
+       err = add_dirent_to_buf(handle, fname, dir, inode, NULL, bh);
        if (err != -ENOSPC)
                goto cleanup;
 
@@ -2345,12 +2271,12 @@ static int ext4_dx_add_entry(handle_t *handle, struct dentry *dentry,
                        goto cleanup;
                }
        }
-       de = do_split(handle, dir, &bh, frame, &hinfo);
+       de = do_split(handle, dir, &bh, frame, &fname->hinfo);
        if (IS_ERR(de)) {
                err = PTR_ERR(de);
                goto cleanup;
        }
-       err = add_dirent_to_buf(handle, dentry, inode, de, bh);
+       err = add_dirent_to_buf(handle, fname, dir, inode, de, bh);
        goto cleanup;
 
 journal_error: