Skip to content

Commit

Permalink
ext4 crypto: optimize filename encryption
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Theodore Ts'o committed May 18, 2015
1 parent e260818 commit 5b643f9
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 313 deletions.
155 changes: 64 additions & 91 deletions fs/ext4/crypto_fname.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
63 changes: 41 additions & 22 deletions fs/ext4/ext4.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) { }
Expand All @@ -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


Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down
31 changes: 15 additions & 16 deletions fs/ext4/inline.c
Original file line number Diff line number Diff line change
Expand Up @@ -995,29 +995,26 @@ 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;

BUFFER_TRACE(iloc->bh, "get_write_access");
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);

Expand Down Expand Up @@ -1248,8 +1245,8 @@ static int ext4_convert_inline_data_nolock(handle_t *handle,
* 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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -1611,6 +1609,7 @@ int ext4_try_create_inline_dir(handle_t *handle, struct inode *parent,
}

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)
Expand All @@ -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)
Expand All @@ -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;

Expand Down
Loading

0 comments on commit 5b643f9

Please sign in to comment.