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 fded02f..ad5e328 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 9a83f14..866831e 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 095c7a2..cd944a7 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;
 
diff --cc fs/ext4/namei.c
Simple merge