Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5b643f9c authored by Theodore Ts'o's avatar Theodore Ts'o
Browse files

ext4 crypto: optimize filename encryption



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: default avatarTheodore Ts'o <tytso@mit.edu>
parent e2608180
Loading
Loading
Loading
Loading
+64 −91
Original line number Diff line number Diff line
@@ -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 && iname->name[0] != '_') {
		if (iname->len > 43)
			return -ENOENT;
		ret = digest_decode(iname->name, iname->len, buf);
		ext4fs_dirhash(buf, ret, hinfo);
		return 0;
	}

	/* First encrypt the plaintext name */
	ret = ext4_fname_crypto_alloc_buffer(ctx, iname->len, &tmp);
	if (ctx->has_valid_key) {
		ret = ext4_fname_crypto_alloc_buffer(ctx, iname->len,
						     &fname->crypto_buf);
		if (ret < 0)
		return ret;

	ret = ext4_fname_encrypt(ctx, iname, &tmp);
	if (ret >= 0) {
		ext4fs_dirhash(tmp.name, tmp.len, hinfo);
			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;
	}

	ext4_fname_crypto_free_buffer(&tmp);
	return ret;
	if (!lookup) {
		ret = -EACCES;
		goto out;
	}

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)
{
	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;
	/* 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;
	}
	} 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);
	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 errout;
			}
			cstr->len = ret;
		goto out;
	}
	fname->crypto_buf.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;
		}
		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;
	}
	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;
	ret = 0;
out:
	ext4_put_fname_crypto_ctx(&ctx);
	return ret;
}

void ext4_fname_free_filename(struct ext4_filename *fname)
{
	kfree(fname->crypto_buf.name);
	fname->crypto_buf.name = NULL;
	fname->usr_fname = NULL;
	fname->disk_name.name = NULL;
}
+41 −22
Original line number Diff line number Diff line
@@ -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 ext4_filename *fname);
static inline void ext4_update_dx_flag(struct inode *inode)
{
	if (!EXT4_HAS_COMPAT_FEATURE(inode->i_sb,
@@ -2317,10 +2332,11 @@ 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,
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);
@@ -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);
+15 −16
Original line number Diff line number Diff line
@@ -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 @@ 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;
@@ -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 @@ 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)
@@ -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;

+110 −184

File changed.

Preview size limit exceeded, changes collapsed.