From b9d82f9977542be9d2bf7095ccae1991da78b638 Mon Sep 17 00:00:00 2001 From: Theodore Ts'o Date: Sat, 2 May 2015 22:03:52 -0400 Subject: [PATCH] Check in crypto patches for post 4.1 merge window --- crypto-add-filename-padding | 212 +++++ crypto-fname-speedup | 564 +++++++++++++ do-not-select-from-ext4_fs_encryption | 44 + optimize-ext4-encryption | 1080 +++++++++++++++++++++++++ remove-duplicated-encryption-mode-definitions | 37 + series | 8 + stop-allocating-pages | 245 ++++++ 7 files changed, 2190 insertions(+) create mode 100644 crypto-add-filename-padding create mode 100644 crypto-fname-speedup create mode 100644 do-not-select-from-ext4_fs_encryption create mode 100644 optimize-ext4-encryption create mode 100644 remove-duplicated-encryption-mode-definitions create mode 100644 stop-allocating-pages diff --git a/crypto-add-filename-padding b/crypto-add-filename-padding new file mode 100644 index 00000000..154762da --- /dev/null +++ b/crypto-add-filename-padding @@ -0,0 +1,212 @@ +ext4 crypto: add padding to filenames before encrypting + +This obscures the length of the filenames, to decrease the amount of +information leakage. By default, we pad the filenames to the next 4 +byte boundaries. This costs nothing, since the directory entries are +aligned to 4 byte boundaries anyway. Filenames can also be padded to +8, 16, or 32 bytes, which will consume more directory space. + +Change-Id: Ibb7a0fb76d2c48e2061240a709358ff40b14f322 +Signed-off-by: Theodore Ts'o +--- + fs/ext4/crypto_fname.c | 12 ++++++++++-- + fs/ext4/crypto_key.c | 1 + + fs/ext4/crypto_policy.c | 14 +++++++++----- + fs/ext4/ext4.h | 1 + + fs/ext4/ext4_crypto.h | 11 ++++++++++- + 5 files changed, 31 insertions(+), 8 deletions(-) + +diff --git a/fs/ext4/crypto_fname.c b/fs/ext4/crypto_fname.c +index 7a877e6..fded02f 100644 +--- a/fs/ext4/crypto_fname.c ++++ b/fs/ext4/crypto_fname.c +@@ -66,6 +66,7 @@ static int ext4_fname_encrypt(struct ext4_fname_crypto_ctx *ctx, + int res = 0; + char iv[EXT4_CRYPTO_BLOCK_SIZE]; + struct scatterlist sg[1]; ++ int padding = 4 << (ctx->flags & EXT4_POLICY_FLAGS_PAD_MASK); + char *workbuf; + + if (iname->len <= 0 || iname->len > ctx->lim) +@@ -73,6 +74,7 @@ static int ext4_fname_encrypt(struct ext4_fname_crypto_ctx *ctx, + + ciphertext_len = (iname->len < EXT4_CRYPTO_BLOCK_SIZE) ? + EXT4_CRYPTO_BLOCK_SIZE : iname->len; ++ ciphertext_len = ext4_fname_crypto_round_up(ciphertext_len, padding); + ciphertext_len = (ciphertext_len > ctx->lim) + ? ctx->lim : ciphertext_len; + +@@ -101,7 +103,7 @@ static int ext4_fname_encrypt(struct ext4_fname_crypto_ctx *ctx, + /* Create encryption request */ + sg_init_table(sg, 1); + sg_set_page(sg, ctx->workpage, PAGE_SIZE, 0); +- ablkcipher_request_set_crypt(req, sg, sg, iname->len, iv); ++ ablkcipher_request_set_crypt(req, sg, sg, ciphertext_len, iv); + res = crypto_ablkcipher_encrypt(req); + if (res == -EINPROGRESS || res == -EBUSY) { + BUG_ON(req->base.data != &ecr); +@@ -356,6 +358,7 @@ struct ext4_fname_crypto_ctx *ext4_get_fname_crypto_ctx( + if (IS_ERR(ctx)) + return ctx; + ++ ctx->flags = ei->i_crypt_policy_flags; + if (ctx->has_valid_key) { + if (ctx->key.mode != EXT4_ENCRYPTION_MODE_AES_256_CTS) { + printk_once(KERN_WARNING +@@ -468,6 +471,7 @@ int ext4_fname_crypto_namelen_on_disk(struct ext4_fname_crypto_ctx *ctx, + u32 namelen) + { + u32 ciphertext_len; ++ int padding = 4 << (ctx->flags & EXT4_POLICY_FLAGS_PAD_MASK); + + if (ctx == NULL) + return -EIO; +@@ -475,6 +479,7 @@ int ext4_fname_crypto_namelen_on_disk(struct ext4_fname_crypto_ctx *ctx, + return -EACCES; + ciphertext_len = (namelen < EXT4_CRYPTO_BLOCK_SIZE) ? + EXT4_CRYPTO_BLOCK_SIZE : namelen; ++ ciphertext_len = ext4_fname_crypto_round_up(ciphertext_len, padding); + ciphertext_len = (ciphertext_len > ctx->lim) + ? ctx->lim : ciphertext_len; + return (int) ciphertext_len; +@@ -490,10 +495,13 @@ int ext4_fname_crypto_alloc_buffer(struct ext4_fname_crypto_ctx *ctx, + u32 ilen, struct ext4_str *crypto_str) + { + unsigned int olen; ++ int padding = 4 << (ctx->flags & EXT4_POLICY_FLAGS_PAD_MASK); + + if (!ctx) + return -EIO; +- olen = ext4_fname_crypto_round_up(ilen, EXT4_CRYPTO_BLOCK_SIZE); ++ if (padding < EXT4_CRYPTO_BLOCK_SIZE) ++ padding = EXT4_CRYPTO_BLOCK_SIZE; ++ olen = ext4_fname_crypto_round_up(ilen, padding); + crypto_str->len = olen; + if (olen < EXT4_FNAME_CRYPTO_DIGEST_SIZE*2) + olen = EXT4_FNAME_CRYPTO_DIGEST_SIZE*2; +diff --git a/fs/ext4/crypto_key.c b/fs/ext4/crypto_key.c +index c8392af..52170d0 100644 +--- a/fs/ext4/crypto_key.c ++++ b/fs/ext4/crypto_key.c +@@ -110,6 +110,7 @@ int ext4_generate_encryption_key(struct inode *inode) + } + res = 0; + ++ ei->i_crypt_policy_flags = ctx.flags; + if (S_ISREG(inode->i_mode)) + crypt_key->mode = ctx.contents_encryption_mode; + else if (S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode)) +diff --git a/fs/ext4/crypto_policy.c b/fs/ext4/crypto_policy.c +index 30eaf9e..a6d6291 100644 +--- a/fs/ext4/crypto_policy.c ++++ b/fs/ext4/crypto_policy.c +@@ -37,6 +37,8 @@ static int ext4_is_encryption_context_consistent_with_policy( + return 0; + return (memcmp(ctx.master_key_descriptor, policy->master_key_descriptor, + EXT4_KEY_DESCRIPTOR_SIZE) == 0 && ++ (ctx.flags == ++ policy->flags) && + (ctx.contents_encryption_mode == + policy->contents_encryption_mode) && + (ctx.filenames_encryption_mode == +@@ -56,25 +58,25 @@ static int ext4_create_encryption_context_from_policy( + printk(KERN_WARNING + "%s: Invalid contents encryption mode %d\n", __func__, + policy->contents_encryption_mode); +- res = -EINVAL; +- goto out; ++ return -EINVAL; + } + if (!ext4_valid_filenames_enc_mode(policy->filenames_encryption_mode)) { + printk(KERN_WARNING + "%s: Invalid filenames encryption mode %d\n", __func__, + policy->filenames_encryption_mode); +- res = -EINVAL; +- goto out; ++ return -EINVAL; + } ++ if (policy->flags & ~EXT4_POLICY_FLAGS_VALID) ++ return -EINVAL; + ctx.contents_encryption_mode = policy->contents_encryption_mode; + ctx.filenames_encryption_mode = policy->filenames_encryption_mode; ++ ctx.flags = policy->flags; + BUILD_BUG_ON(sizeof(ctx.nonce) != EXT4_KEY_DERIVATION_NONCE_SIZE); + get_random_bytes(ctx.nonce, EXT4_KEY_DERIVATION_NONCE_SIZE); + + res = ext4_xattr_set(inode, EXT4_XATTR_INDEX_ENCRYPTION, + EXT4_XATTR_NAME_ENCRYPTION_CONTEXT, &ctx, + sizeof(ctx), 0); +-out: + if (!res) + ext4_set_inode_flag(inode, EXT4_INODE_ENCRYPT); + return res; +@@ -115,6 +117,7 @@ int ext4_get_policy(struct inode *inode, struct ext4_encryption_policy *policy) + policy->version = 0; + policy->contents_encryption_mode = ctx.contents_encryption_mode; + policy->filenames_encryption_mode = ctx.filenames_encryption_mode; ++ policy->flags = ctx.flags; + memcpy(&policy->master_key_descriptor, ctx.master_key_descriptor, + EXT4_KEY_DESCRIPTOR_SIZE); + return 0; +@@ -176,6 +179,7 @@ int ext4_inherit_context(struct inode *parent, struct inode *child) + EXT4_ENCRYPTION_MODE_AES_256_XTS; + ctx.filenames_encryption_mode = + EXT4_ENCRYPTION_MODE_AES_256_CTS; ++ ctx.flags = 0; + memset(ctx.master_key_descriptor, 0x42, + EXT4_KEY_DESCRIPTOR_SIZE); + res = 0; +diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h +index dfb1138..bca1bdc 100644 +--- a/fs/ext4/ext4.h ++++ b/fs/ext4/ext4.h +@@ -911,6 +911,7 @@ struct ext4_inode_info { + + /* on-disk additional length */ + __u16 i_extra_isize; ++ char i_crypt_policy_flags; + + /* Indicate the inline data space. */ + u16 i_inline_off; +diff --git a/fs/ext4/ext4_crypto.h b/fs/ext4/ext4_crypto.h +index c2ba35a..d75159c 100644 +--- a/fs/ext4/ext4_crypto.h ++++ b/fs/ext4/ext4_crypto.h +@@ -20,12 +20,20 @@ struct ext4_encryption_policy { + char version; + char contents_encryption_mode; + char filenames_encryption_mode; ++ char flags; + char master_key_descriptor[EXT4_KEY_DESCRIPTOR_SIZE]; + } __attribute__((__packed__)); + + #define EXT4_ENCRYPTION_CONTEXT_FORMAT_V1 1 + #define EXT4_KEY_DERIVATION_NONCE_SIZE 16 + ++#define EXT4_POLICY_FLAGS_PAD_4 0x00 ++#define EXT4_POLICY_FLAGS_PAD_8 0x01 ++#define EXT4_POLICY_FLAGS_PAD_16 0x02 ++#define EXT4_POLICY_FLAGS_PAD_32 0x03 ++#define EXT4_POLICY_FLAGS_PAD_MASK 0x03 ++#define EXT4_POLICY_FLAGS_VALID 0x03 ++ + /** + * Encryption context for inode + * +@@ -41,7 +49,7 @@ struct ext4_encryption_context { + char format; + char contents_encryption_mode; + char filenames_encryption_mode; +- char reserved; ++ char flags; + char master_key_descriptor[EXT4_KEY_DESCRIPTOR_SIZE]; + char nonce[EXT4_KEY_DERIVATION_NONCE_SIZE]; + } __attribute__((__packed__)); +@@ -120,6 +128,7 @@ struct ext4_fname_crypto_ctx { + struct crypto_hash *htfm; + struct page *workpage; + struct ext4_encryption_key key; ++ unsigned flags : 8; + unsigned has_valid_key : 1; + unsigned ctfm_key_is_ready : 1; + }; diff --git a/crypto-fname-speedup b/crypto-fname-speedup new file mode 100644 index 00000000..3eff77f5 --- /dev/null +++ b/crypto-fname-speedup @@ -0,0 +1,564 @@ +ext4 crypto: simplify and speed up filename encryption + +Avoid using SHA-1 when calculating the user-visible filename when the +encryption key is available, and avoid decrypting lots of filenames +when searching for a directory entry in a directory block. + +Change-Id: If4655f144784978ba0305b597bfa1c8d7bb69e63 +Signed-off-by: Theodore Ts'o +--- + fs/ext4/crypto_fname.c | 268 +++++++++++++++++++++++++------------------------ + fs/ext4/dir.c | 2 +- + fs/ext4/ext4.h | 9 +- + fs/ext4/namei.c | 72 ++----------- + fs/ext4/symlink.c | 2 +- + 5 files changed, 149 insertions(+), 204 deletions(-) + +diff --git a/fs/ext4/crypto_fname.c b/fs/ext4/crypto_fname.c +index ca2f594..7a877e6 100644 +--- a/fs/ext4/crypto_fname.c ++++ b/fs/ext4/crypto_fname.c +@@ -198,106 +198,57 @@ static int ext4_fname_decrypt(struct ext4_fname_crypto_ctx *ctx, + return oname->len; + } + ++static const char *lookup_table = ++ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,"; ++ + /** + * ext4_fname_encode_digest() - + * + * Encodes the input digest using characters from the set [a-zA-Z0-9_+]. + * The encoded string is roughly 4/3 times the size of the input string. + */ +-int ext4_fname_encode_digest(char *dst, char *src, u32 len) ++static int digest_encode(const char *src, int len, char *dst) + { +- static const char *lookup_table = +- "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_+"; +- u32 current_chunk, num_chunks, i; +- char tmp_buf[3]; +- u32 c0, c1, c2, c3; +- +- current_chunk = 0; +- num_chunks = len/3; +- for (i = 0; i < num_chunks; i++) { +- c0 = src[3*i] & 0x3f; +- c1 = (((src[3*i]>>6)&0x3) | ((src[3*i+1] & 0xf)<<2)) & 0x3f; +- c2 = (((src[3*i+1]>>4)&0xf) | ((src[3*i+2] & 0x3)<<4)) & 0x3f; +- c3 = (src[3*i+2]>>2) & 0x3f; +- dst[4*i] = lookup_table[c0]; +- dst[4*i+1] = lookup_table[c1]; +- dst[4*i+2] = lookup_table[c2]; +- dst[4*i+3] = lookup_table[c3]; +- } +- if (i*3 < len) { +- memset(tmp_buf, 0, 3); +- memcpy(tmp_buf, &src[3*i], len-3*i); +- c0 = tmp_buf[0] & 0x3f; +- c1 = (((tmp_buf[0]>>6)&0x3) | ((tmp_buf[1] & 0xf)<<2)) & 0x3f; +- c2 = (((tmp_buf[1]>>4)&0xf) | ((tmp_buf[2] & 0x3)<<4)) & 0x3f; +- c3 = (tmp_buf[2]>>2) & 0x3f; +- dst[4*i] = lookup_table[c0]; +- dst[4*i+1] = lookup_table[c1]; +- dst[4*i+2] = lookup_table[c2]; +- dst[4*i+3] = lookup_table[c3]; ++ int i = 0, bits = 0, ac = 0; ++ char *cp = dst; ++ ++ while (i < len) { ++ ac += (((unsigned char) src[i]) << bits); ++ bits += 8; ++ do { ++ *cp++ = lookup_table[ac & 0x3f]; ++ ac >>= 6; ++ bits -= 6; ++ } while (bits >= 6); + i++; + } +- return (i * 4); ++ if (bits) ++ *cp++ = lookup_table[ac & 0x3f]; ++ return cp - dst; + } + +-/** +- * ext4_fname_hash() - +- * +- * This function computes the hash of the input filename, and sets the output +- * buffer to the *encoded* digest. It returns the length of the digest as its +- * return value. Errors are returned as negative numbers. We trust the caller +- * to allocate sufficient memory to oname string. +- */ +-static int ext4_fname_hash(struct ext4_fname_crypto_ctx *ctx, +- const struct ext4_str *iname, +- struct ext4_str *oname) ++static int digest_decode(const char *src, int len, char *dst) + { +- struct scatterlist sg; +- struct hash_desc desc = { +- .tfm = (struct crypto_hash *)ctx->htfm, +- .flags = CRYPTO_TFM_REQ_MAY_SLEEP +- }; +- int res = 0; +- +- if (iname->len <= EXT4_FNAME_CRYPTO_DIGEST_SIZE) { +- res = ext4_fname_encode_digest(oname->name, iname->name, +- iname->len); +- oname->len = res; +- return res; +- } +- +- sg_init_one(&sg, iname->name, iname->len); +- res = crypto_hash_init(&desc); +- if (res) { +- printk(KERN_ERR +- "%s: Error initializing crypto hash; res = [%d]\n", +- __func__, res); +- goto out; +- } +- res = crypto_hash_update(&desc, &sg, iname->len); +- if (res) { +- printk(KERN_ERR +- "%s: Error updating crypto hash; res = [%d]\n", +- __func__, res); +- goto out; +- } +- res = crypto_hash_final(&desc, +- &oname->name[EXT4_FNAME_CRYPTO_DIGEST_SIZE]); +- if (res) { +- printk(KERN_ERR +- "%s: Error finalizing crypto hash; res = [%d]\n", +- __func__, res); +- goto out; ++ int i = 0, bits = 0, ac = 0; ++ const char *p; ++ char *cp = dst; ++ ++ while (i < len) { ++ p = strchr(lookup_table, src[i]); ++ if (p == NULL || src[i] == 0) ++ return -2; ++ ac += (p - lookup_table) << bits; ++ bits += 6; ++ if (bits >= 8) { ++ *cp++ = ac & 0xff; ++ ac >>= 8; ++ bits -= 8; ++ } ++ i++; + } +- /* Encode the digest as a printable string--this will increase the +- * size of the digest */ +- oname->name[0] = 'I'; +- res = ext4_fname_encode_digest(oname->name+1, +- &oname->name[EXT4_FNAME_CRYPTO_DIGEST_SIZE], +- EXT4_FNAME_CRYPTO_DIGEST_SIZE) + 1; +- oname->len = res; +-out: +- return res; ++ if (ac) ++ return -1; ++ return cp - dst; + } + + /** +@@ -571,9 +522,13 @@ void ext4_fname_crypto_free_buffer(struct ext4_str *crypto_str) + * ext4_fname_disk_to_usr() - converts a filename from disk space to user space + */ + int _ext4_fname_disk_to_usr(struct ext4_fname_crypto_ctx *ctx, +- const struct ext4_str *iname, +- struct ext4_str *oname) ++ struct dx_hash_info *hinfo, ++ const struct ext4_str *iname, ++ struct ext4_str *oname) + { ++ char buf[24]; ++ int ret; ++ + if (ctx == NULL) + return -EIO; + if (iname->len < 3) { +@@ -587,18 +542,33 @@ int _ext4_fname_disk_to_usr(struct ext4_fname_crypto_ctx *ctx, + } + if (ctx->has_valid_key) + return ext4_fname_decrypt(ctx, iname, oname); +- else +- return ext4_fname_hash(ctx, iname, oname); ++ ++ if (iname->len <= EXT4_FNAME_CRYPTO_DIGEST_SIZE) { ++ ret = digest_encode(iname->name, iname->len, oname->name); ++ oname->len = ret; ++ return ret; ++ } ++ if (hinfo) { ++ memcpy(buf, &hinfo->hash, 4); ++ memcpy(buf+4, &hinfo->minor_hash, 4); ++ } else ++ memset(buf, 0, 8); ++ memcpy(buf + 8, iname->name + iname->len - 16, 16); ++ oname->name[0] = '_'; ++ ret = digest_encode(buf, 24, oname->name+1); ++ oname->len = ret + 1; ++ return ret + 1; + } + + int ext4_fname_disk_to_usr(struct ext4_fname_crypto_ctx *ctx, ++ struct dx_hash_info *hinfo, + const struct ext4_dir_entry_2 *de, + struct ext4_str *oname) + { + struct ext4_str iname = {.name = (unsigned char *) de->name, + .len = de->name_len }; + +- return _ext4_fname_disk_to_usr(ctx, &iname, oname); ++ return _ext4_fname_disk_to_usr(ctx, hinfo, &iname, oname); + } + + +@@ -640,10 +610,11 @@ int ext4_fname_usr_to_hash(struct ext4_fname_crypto_ctx *ctx, + const struct qstr *iname, + struct dx_hash_info *hinfo) + { +- struct ext4_str tmp, tmp2; ++ struct ext4_str tmp; + int ret = 0; ++ char buf[EXT4_FNAME_CRYPTO_DIGEST_SIZE+1]; + +- if (!ctx || !ctx->has_valid_key || ++ if (!ctx || + ((iname->name[0] == '.') && + ((iname->len == 1) || + ((iname->name[1] == '.') && (iname->len == 2))))) { +@@ -651,59 +622,90 @@ int ext4_fname_usr_to_hash(struct ext4_fname_crypto_ctx *ctx, + return 0; + } + ++ 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 (ret < 0) + return ret; + + ret = ext4_fname_encrypt(ctx, iname, &tmp); +- if (ret < 0) +- goto out; +- +- tmp2.len = (4 * ((EXT4_FNAME_CRYPTO_DIGEST_SIZE + 2) / 3)) + 1; +- tmp2.name = kmalloc(tmp2.len + 1, GFP_KERNEL); +- if (tmp2.name == NULL) { +- ret = -ENOMEM; +- goto out; ++ if (ret >= 0) { ++ ext4fs_dirhash(tmp.name, tmp.len, hinfo); ++ ret = 0; + } + +- ret = ext4_fname_hash(ctx, &tmp, &tmp2); +- if (ret > 0) +- ext4fs_dirhash(tmp2.name, tmp2.len, hinfo); +- ext4_fname_crypto_free_buffer(&tmp2); +-out: + ext4_fname_crypto_free_buffer(&tmp); + return ret; + } + +-/** +- * ext4_fname_disk_to_htree() - converts a filename from disk space to htree-access string +- */ +-int ext4_fname_disk_to_hash(struct ext4_fname_crypto_ctx *ctx, +- const struct ext4_dir_entry_2 *de, +- struct dx_hash_info *hinfo) ++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) + { +- struct ext4_str iname = {.name = (unsigned char *) de->name, +- .len = de->name_len}; +- struct ext4_str tmp; +- int ret; ++ int ret = -ENOENT; ++ int bigname = (*name == '_'); + +- if (!ctx || +- ((iname.name[0] == '.') && +- ((iname.len == 1) || +- ((iname.name[1] == '.') && (iname.len == 2))))) { +- ext4fs_dirhash(iname.name, iname.len, hinfo); +- return 0; ++ 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; ++ } + } +- +- tmp.len = (4 * ((EXT4_FNAME_CRYPTO_DIGEST_SIZE + 2) / 3)) + 1; +- tmp.name = kmalloc(tmp.len + 1, GFP_KERNEL); +- if (tmp.name == NULL) +- return -ENOMEM; +- +- ret = ext4_fname_hash(ctx, &iname, &tmp); +- if (ret > 0) +- ext4fs_dirhash(tmp.name, tmp.len, hinfo); +- ext4_fname_crypto_free_buffer(&tmp); ++ 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; + } +diff --git a/fs/ext4/dir.c b/fs/ext4/dir.c +index 61db51a5..5665d82 100644 +--- a/fs/ext4/dir.c ++++ b/fs/ext4/dir.c +@@ -249,7 +249,7 @@ static int ext4_readdir(struct file *file, struct dir_context *ctx) + } else { + /* Directory is encrypted */ + err = ext4_fname_disk_to_usr(enc_ctx, +- de, &fname_crypto_str); ++ NULL, de, &fname_crypto_str); + if (err < 0) + goto errout; + if (!dir_emit(ctx, +diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h +index 0179654f..dfb1138 100644 +--- a/fs/ext4/ext4.h ++++ b/fs/ext4/ext4.h +@@ -2093,9 +2093,11 @@ u32 ext4_fname_crypto_round_up(u32 size, u32 blksize); + int ext4_fname_crypto_alloc_buffer(struct ext4_fname_crypto_ctx *ctx, + u32 ilen, struct ext4_str *crypto_str); + int _ext4_fname_disk_to_usr(struct ext4_fname_crypto_ctx *ctx, ++ struct dx_hash_info *hinfo, + const struct ext4_str *iname, + struct ext4_str *oname); + int ext4_fname_disk_to_usr(struct ext4_fname_crypto_ctx *ctx, ++ struct dx_hash_info *hinfo, + const struct ext4_dir_entry_2 *de, + struct ext4_str *oname); + int ext4_fname_usr_to_disk(struct ext4_fname_crypto_ctx *ctx, +@@ -2104,11 +2106,12 @@ int ext4_fname_usr_to_disk(struct ext4_fname_crypto_ctx *ctx, + int ext4_fname_usr_to_hash(struct ext4_fname_crypto_ctx *ctx, + const struct qstr *iname, + struct dx_hash_info *hinfo); +-int ext4_fname_disk_to_hash(struct ext4_fname_crypto_ctx *ctx, +- const struct ext4_dir_entry_2 *de, +- 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); +diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c +index 4f87127..5ea7371 100644 +--- a/fs/ext4/namei.c ++++ b/fs/ext4/namei.c +@@ -640,7 +640,7 @@ static struct stats dx_show_leaf(struct inode *dir, + ext4_put_fname_crypto_ctx(&ctx); + ctx = NULL; + } +- res = ext4_fname_disk_to_usr(ctx, de, ++ res = ext4_fname_disk_to_usr(ctx, NULL, de, + &fname_crypto_str); + if (res < 0) { + printk(KERN_WARNING "Error " +@@ -653,15 +653,8 @@ static struct stats dx_show_leaf(struct inode *dir, + name = fname_crypto_str.name; + len = fname_crypto_str.len; + } +- res = ext4_fname_disk_to_hash(ctx, de, +- &h); +- if (res < 0) { +- printk(KERN_WARNING "Error " +- "converting filename " +- "from disk to htree" +- "\n"); +- h.hash = 0xDEADBEEF; +- } ++ ext4fs_dirhash(de->name, de->name_len, ++ &h); + printk("%*.s:(E)%x.%u ", len, name, + h.hash, (unsigned) ((char *) de + - base)); +@@ -1008,15 +1001,7 @@ static int htree_dirblock_to_tree(struct file *dir_file, + /* silently ignore the rest of the block */ + break; + } +-#ifdef CONFIG_EXT4_FS_ENCRYPTION +- err = ext4_fname_disk_to_hash(ctx, de, hinfo); +- if (err < 0) { +- count = err; +- goto errout; +- } +-#else + ext4fs_dirhash(de->name, de->name_len, hinfo); +-#endif + if ((hinfo->hash < start_hash) || + ((hinfo->hash == start_hash) && + (hinfo->minor_hash < start_minor_hash))) +@@ -1032,7 +1017,7 @@ static int htree_dirblock_to_tree(struct file *dir_file, + &tmp_str); + } else { + /* Directory is encrypted */ +- err = ext4_fname_disk_to_usr(ctx, de, ++ err = ext4_fname_disk_to_usr(ctx, hinfo, de, + &fname_crypto_str); + if (err < 0) { + count = err; +@@ -1193,26 +1178,10 @@ static int dx_make_map(struct inode *dir, struct ext4_dir_entry_2 *de, + int count = 0; + char *base = (char *) de; + struct dx_hash_info h = *hinfo; +-#ifdef CONFIG_EXT4_FS_ENCRYPTION +- struct ext4_fname_crypto_ctx *ctx = NULL; +- int err; +- +- ctx = ext4_get_fname_crypto_ctx(dir, EXT4_NAME_LEN); +- if (IS_ERR(ctx)) +- return PTR_ERR(ctx); +-#endif + + while ((char *) de < base + blocksize) { + if (de->name_len && de->inode) { +-#ifdef CONFIG_EXT4_FS_ENCRYPTION +- err = ext4_fname_disk_to_hash(ctx, de, &h); +- if (err < 0) { +- ext4_put_fname_crypto_ctx(&ctx); +- return err; +- } +-#else + ext4fs_dirhash(de->name, de->name_len, &h); +-#endif + map_tail--; + map_tail->hash = h.hash; + map_tail->offs = ((char *) de - base)>>2; +@@ -1223,9 +1192,6 @@ static int dx_make_map(struct inode *dir, struct ext4_dir_entry_2 *de, + /* XXX: do we need to check rec_len == 0 case? -Chris */ + de = ext4_next_entry(de, blocksize); + } +-#ifdef CONFIG_EXT4_FS_ENCRYPTION +- ext4_put_fname_crypto_ctx(&ctx); +-#endif + return count; + } + +@@ -1287,16 +1253,8 @@ static inline int ext4_match(struct ext4_fname_crypto_ctx *ctx, + return 0; + + #ifdef CONFIG_EXT4_FS_ENCRYPTION +- if (ctx) { +- /* Directory is encrypted */ +- res = ext4_fname_disk_to_usr(ctx, de, fname_crypto_str); +- if (res < 0) +- return res; +- if (len != res) +- return 0; +- res = memcmp(name, fname_crypto_str->name, len); +- return (res == 0) ? 1 : 0; +- } ++ if (ctx) ++ return ext4_fname_match(ctx, fname_crypto_str, len, name, de); + #endif + if (len != de->name_len) + return 0; +@@ -1324,16 +1282,6 @@ int search_dir(struct buffer_head *bh, char *search_buf, int buf_size, + if (IS_ERR(ctx)) + return -1; + +- if (ctx != NULL) { +- /* Allocate buffer to hold maximum name length */ +- res = ext4_fname_crypto_alloc_buffer(ctx, EXT4_NAME_LEN, +- &fname_crypto_str); +- if (res < 0) { +- ext4_put_fname_crypto_ctx(&ctx); +- return -1; +- } +- } +- + de = (struct ext4_dir_entry_2 *)search_buf; + dlimit = search_buf + buf_size; + while ((char *) de < dlimit) { +@@ -1872,14 +1820,6 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode, + return res; + } + reclen = EXT4_DIR_REC_LEN(res); +- +- /* Allocate buffer to hold maximum name length */ +- res = ext4_fname_crypto_alloc_buffer(ctx, EXT4_NAME_LEN, +- &fname_crypto_str); +- if (res < 0) { +- ext4_put_fname_crypto_ctx(&ctx); +- return -1; +- } + } + + de = (struct ext4_dir_entry_2 *)buf; +diff --git a/fs/ext4/symlink.c b/fs/ext4/symlink.c +index 136ca0e..ce2ed28 100644 +--- a/fs/ext4/symlink.c ++++ b/fs/ext4/symlink.c +@@ -74,7 +74,7 @@ static void *ext4_follow_link(struct dentry *dentry, struct nameidata *nd) + goto errout; + } + pstr.name = paddr; +- res = _ext4_fname_disk_to_usr(ctx, &cstr, &pstr); ++ res = _ext4_fname_disk_to_usr(ctx, NULL, &cstr, &pstr); + if (res < 0) + goto errout; + /* Null-terminate the name */ diff --git a/do-not-select-from-ext4_fs_encryption b/do-not-select-from-ext4_fs_encryption new file mode 100644 index 00000000..8bebc242 --- /dev/null +++ b/do-not-select-from-ext4_fs_encryption @@ -0,0 +1,44 @@ +ext4 crypto: Do not select from EXT4_FS_ENCRYPTION + +From: Herbert Xu + +This patch adds a tristate EXT4_ENCRYPTION to do the selections +for EXT4_FS_ENCRYPTION because selecting from a bool causes all +the selected options to be built-in, even if EXT4 itself is a +module. + +Signed-off-by: Herbert Xu +Signed-off-by: Theodore Ts'o +--- +diff --git a/fs/ext4/Kconfig b/fs/ext4/Kconfig +index 18228c2..024f228 100644 +--- a/fs/ext4/Kconfig ++++ b/fs/ext4/Kconfig +@@ -64,8 +64,8 @@ config EXT4_FS_SECURITY + If you are not using a security module that requires using + extended attributes for file security labels, say N. + +-config EXT4_FS_ENCRYPTION +- bool "Ext4 Encryption" ++config EXT4_ENCRYPTION ++ tristate "Ext4 Encryption" + depends on EXT4_FS + select CRYPTO_AES + select CRYPTO_CBC +@@ -81,6 +81,11 @@ config EXT4_FS_ENCRYPTION + efficient since it avoids caching the encrypted and + decrypted pages in the page cache. + ++config EXT4_FS_ENCRYPTION ++ bool ++ default y ++ depends on EXT4_ENCRYPTION ++ + config EXT4_DEBUG + bool "EXT4 debugging support" + depends on EXT4_FS +-- +Email: Herbert Xu +Home Page: http://gondor.apana.org.au/~herbert/ +PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt + diff --git a/optimize-ext4-encryption b/optimize-ext4-encryption new file mode 100644 index 00000000..e7f99779 --- /dev/null +++ b/optimize-ext4-encryption @@ -0,0 +1,1080 @@ +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. + +Change-Id: I39e9f44302443f6c6dba95516a3fb9274863e7ee +Signed-off-by: Theodore Ts'o +--- + fs/ext4/crypto_fname.c | 155 +++++++++++++++++------------------------ + fs/ext4/ext4.h | 63 +++++++++++------ + fs/ext4/inline.c | 31 ++++----- + fs/ext4/namei.c | 294 +++++++++++++++++++++++++++++------------------------------------------------- + 4 files changed, 230 insertions(+), 313 deletions(-) + +diff --git a/fs/ext4/crypto_fname.c b/fs/ext4/crypto_fname.c +index fded02f..ad5e328 100644 +--- a/fs/ext4/crypto_fname.c ++++ b/fs/ext4/crypto_fname.c +@@ -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; + } +diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h +index bca1bdc..7bd482d 100644 +--- a/fs/ext4/ext4.h ++++ b/fs/ext4/ext4.h +@@ -1844,6 +1844,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 +@@ -2104,21 +2115,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) { } +@@ -2129,6 +2135,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 + + +@@ -2162,14 +2178,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, +@@ -2323,13 +2338,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, +@@ -2775,7 +2791,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, +@@ -2789,6 +2807,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); +diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c +index feb2caf..4ce3b2e 100644 +--- a/fs/ext4/inline.c ++++ b/fs/ext4/inline.c +@@ -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 = dentry->d_parent->d_inode; +- 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 --git a/fs/ext4/namei.c b/fs/ext4/namei.c +index 5ea7371..752d4af 100644 +--- a/fs/ext4/namei.c ++++ b/fs/ext4/namei.c +@@ -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 = dentry->d_parent->d_inode; +- 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 = dentry->d_parent->d_inode; +-#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 = dentry->d_parent->d_inode; + 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: diff --git a/remove-duplicated-encryption-mode-definitions b/remove-duplicated-encryption-mode-definitions new file mode 100644 index 00000000..9e5c46ca --- /dev/null +++ b/remove-duplicated-encryption-mode-definitions @@ -0,0 +1,37 @@ +ext4 crypto: remove duplicated encryption mode definitions + +From: Chanho Park + +This patch removes duplicated encryption modes which were already in +ext4.h. They were duplicated from commit 3edc18d and commit f542fb. + +Cc: Theodore Ts'o +Cc: Michael Halcrow +Cc: Andreas Dilger +Signed-off-by: Chanho Park +Signed-off-by: Theodore Ts'o +--- + fs/ext4/ext4.h | 6 ------ + 1 file changed, 6 deletions(-) + +diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h +index ef267ad..07378a4 100644 +--- a/fs/ext4/ext4.h ++++ b/fs/ext4/ext4.h +@@ -1066,12 +1066,6 @@ extern void ext4_set_bits(void *bm, int cur, int len); + /* Metadata checksum algorithm codes */ + #define EXT4_CRC32C_CHKSUM 1 + +-/* Encryption algorithms */ +-#define EXT4_ENCRYPTION_MODE_INVALID 0 +-#define EXT4_ENCRYPTION_MODE_AES_256_XTS 1 +-#define EXT4_ENCRYPTION_MODE_AES_256_GCM 2 +-#define EXT4_ENCRYPTION_MODE_AES_256_CBC 3 +- + /* + * Structure of the super block + */ +-- +1.9.1 + + diff --git a/series b/series index 9821349c..f7f8fb0c 100644 --- a/series +++ b/series @@ -1,5 +1,13 @@ # BASE 6ddb2447846a8e +crypto-fname-speedup +crypto-add-filename-padding +do-not-select-from-ext4_fs_encryption +remove-duplicated-encryption-mode-definitions + +# for 4.1 +optimize-ext4-encryption +stop-allocating-pages ########################################## # unstable patches diff --git a/stop-allocating-pages b/stop-allocating-pages new file mode 100644 index 00000000..ec4595ca --- /dev/null +++ b/stop-allocating-pages @@ -0,0 +1,245 @@ +ext4 crypto: don't allocate a page when encrypting/decrypting file names + +Change-Id: I415e4e4c56ab6c7779576fc287b5c4c8d7cdb12b +Signed-off-by: Theodore Ts'o +--- + fs/ext4/crypto_fname.c | 72 ++++++++++++++++++++---------------------------------------------------- + fs/ext4/dir.c | 3 +++ + fs/ext4/ext4_crypto.h | 1 - + fs/ext4/namei.c | 4 ++++ + fs/ext4/symlink.c | 1 + + 5 files changed, 28 insertions(+), 53 deletions(-) + +diff --git a/fs/ext4/crypto_fname.c b/fs/ext4/crypto_fname.c +index ad5e328..23d7f1d 100644 +--- a/fs/ext4/crypto_fname.c ++++ b/fs/ext4/crypto_fname.c +@@ -65,9 +65,9 @@ static int ext4_fname_encrypt(struct ext4_fname_crypto_ctx *ctx, + struct crypto_ablkcipher *tfm = ctx->ctfm; + int res = 0; + char iv[EXT4_CRYPTO_BLOCK_SIZE]; +- struct scatterlist sg[1]; ++ struct scatterlist src_sg, dst_sg; + int padding = 4 << (ctx->flags & EXT4_POLICY_FLAGS_PAD_MASK); +- char *workbuf; ++ char *workbuf, buf[32], *alloc_buf = NULL; + + if (iname->len <= 0 || iname->len > ctx->lim) + return -EIO; +@@ -78,20 +78,27 @@ static int ext4_fname_encrypt(struct ext4_fname_crypto_ctx *ctx, + ciphertext_len = (ciphertext_len > ctx->lim) + ? ctx->lim : ciphertext_len; + ++ if (ciphertext_len <= sizeof(buf)) { ++ workbuf = buf; ++ } else { ++ alloc_buf = kmalloc(ciphertext_len, GFP_NOFS); ++ if (!alloc_buf) ++ return -ENOMEM; ++ workbuf = alloc_buf; ++ } ++ + /* Allocate request */ + req = ablkcipher_request_alloc(tfm, GFP_NOFS); + if (!req) { + printk_ratelimited( + KERN_ERR "%s: crypto_request_alloc() failed\n", __func__); ++ kfree(alloc_buf); + return -ENOMEM; + } + ablkcipher_request_set_callback(req, + CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP, + ext4_dir_crypt_complete, &ecr); + +- /* Map the workpage */ +- workbuf = kmap(ctx->workpage); +- + /* Copy the input */ + memcpy(workbuf, iname->name, iname->len); + if (iname->len < ciphertext_len) +@@ -101,21 +108,16 @@ static int ext4_fname_encrypt(struct ext4_fname_crypto_ctx *ctx, + memset(iv, 0, EXT4_CRYPTO_BLOCK_SIZE); + + /* Create encryption request */ +- sg_init_table(sg, 1); +- sg_set_page(sg, ctx->workpage, PAGE_SIZE, 0); +- ablkcipher_request_set_crypt(req, sg, sg, ciphertext_len, iv); ++ sg_init_one(&src_sg, workbuf, ciphertext_len); ++ sg_init_one(&dst_sg, oname->name, ciphertext_len); ++ ablkcipher_request_set_crypt(req, &src_sg, &dst_sg, ciphertext_len, iv); + res = crypto_ablkcipher_encrypt(req); + if (res == -EINPROGRESS || res == -EBUSY) { + BUG_ON(req->base.data != &ecr); + wait_for_completion(&ecr.completion); + res = ecr.res; + } +- if (res >= 0) { +- /* Copy the result to output */ +- memcpy(oname->name, workbuf, ciphertext_len); +- res = ciphertext_len; +- } +- kunmap(ctx->workpage); ++ kfree(alloc_buf); + ablkcipher_request_free(req); + if (res < 0) { + printk_ratelimited( +@@ -139,11 +141,10 @@ static int ext4_fname_decrypt(struct ext4_fname_crypto_ctx *ctx, + struct ext4_str tmp_in[2], tmp_out[1]; + struct ablkcipher_request *req = NULL; + DECLARE_EXT4_COMPLETION_RESULT(ecr); +- struct scatterlist sg[1]; ++ struct scatterlist src_sg, dst_sg; + struct crypto_ablkcipher *tfm = ctx->ctfm; + int res = 0; + char iv[EXT4_CRYPTO_BLOCK_SIZE]; +- char *workbuf; + + if (iname->len <= 0 || iname->len > ctx->lim) + return -EIO; +@@ -163,31 +164,19 @@ static int ext4_fname_decrypt(struct ext4_fname_crypto_ctx *ctx, + CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP, + ext4_dir_crypt_complete, &ecr); + +- /* Map the workpage */ +- workbuf = kmap(ctx->workpage); +- +- /* Copy the input */ +- memcpy(workbuf, iname->name, iname->len); +- + /* Initialize IV */ + memset(iv, 0, EXT4_CRYPTO_BLOCK_SIZE); + + /* Create encryption request */ +- sg_init_table(sg, 1); +- sg_set_page(sg, ctx->workpage, PAGE_SIZE, 0); +- ablkcipher_request_set_crypt(req, sg, sg, iname->len, iv); ++ sg_init_one(&src_sg, iname->name, iname->len); ++ sg_init_one(&dst_sg, oname->name, oname->len); ++ ablkcipher_request_set_crypt(req, &src_sg, &dst_sg, iname->len, iv); + res = crypto_ablkcipher_decrypt(req); + if (res == -EINPROGRESS || res == -EBUSY) { + BUG_ON(req->base.data != &ecr); + wait_for_completion(&ecr.completion); + res = ecr.res; + } +- if (res >= 0) { +- /* Copy the result to output */ +- memcpy(oname->name, workbuf, iname->len); +- res = iname->len; +- } +- kunmap(ctx->workpage); + ablkcipher_request_free(req); + if (res < 0) { + printk_ratelimited( +@@ -267,8 +256,6 @@ void ext4_free_fname_crypto_ctx(struct ext4_fname_crypto_ctx *ctx) + crypto_free_ablkcipher(ctx->ctfm); + if (ctx->htfm && !IS_ERR(ctx->htfm)) + crypto_free_hash(ctx->htfm); +- if (ctx->workpage && !IS_ERR(ctx->workpage)) +- __free_page(ctx->workpage); + kfree(ctx); + } + +@@ -322,7 +309,6 @@ struct ext4_fname_crypto_ctx *ext4_alloc_fname_crypto_ctx( + ctx->ctfm_key_is_ready = 0; + ctx->ctfm = NULL; + ctx->htfm = NULL; +- ctx->workpage = NULL; + return ctx; + } + +@@ -390,24 +376,6 @@ struct ext4_fname_crypto_ctx *ext4_get_fname_crypto_ctx( + ext4_put_fname_crypto_ctx(&ctx); + return ERR_PTR(-ENOMEM); + } +- if (ctx->workpage == NULL) +- ctx->workpage = alloc_page(GFP_NOFS); +- if (IS_ERR(ctx->workpage)) { +- res = PTR_ERR(ctx->workpage); +- printk( +- KERN_DEBUG "%s: error (%d) allocating work page\n", +- __func__, res); +- ctx->workpage = NULL; +- ext4_put_fname_crypto_ctx(&ctx); +- return ERR_PTR(res); +- } +- if (ctx->workpage == NULL) { +- printk( +- KERN_DEBUG "%s: could not allocate work page\n", +- __func__); +- ext4_put_fname_crypto_ctx(&ctx); +- return ERR_PTR(-ENOMEM); +- } + ctx->lim = max_ciphertext_len; + crypto_ablkcipher_clear_flags(ctx->ctfm, ~0); + crypto_tfm_set_flags(crypto_ablkcipher_tfm(ctx->ctfm), +diff --git a/fs/ext4/dir.c b/fs/ext4/dir.c +index 5665d82..d799d5d 100644 +--- a/fs/ext4/dir.c ++++ b/fs/ext4/dir.c +@@ -247,9 +247,12 @@ static int ext4_readdir(struct file *file, struct dir_context *ctx) + get_dtype(sb, de->file_type))) + goto done; + } else { ++ int save_len = fname_crypto_str.len; ++ + /* Directory is encrypted */ + err = ext4_fname_disk_to_usr(enc_ctx, + NULL, de, &fname_crypto_str); ++ fname_crypto_str.len = save_len; + if (err < 0) + goto errout; + if (!dir_emit(ctx, +diff --git a/fs/ext4/ext4_crypto.h b/fs/ext4/ext4_crypto.h +index d75159c..e9e4365 100644 +--- a/fs/ext4/ext4_crypto.h ++++ b/fs/ext4/ext4_crypto.h +@@ -126,7 +126,6 @@ struct ext4_fname_crypto_ctx { + char tmp_buf[EXT4_CRYPTO_BLOCK_SIZE]; + struct crypto_ablkcipher *ctfm; + struct crypto_hash *htfm; +- struct page *workpage; + struct ext4_encryption_key key; + unsigned flags : 8; + unsigned has_valid_key : 1; +diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c +index 752d4af..bf66e56 100644 +--- a/fs/ext4/namei.c ++++ b/fs/ext4/namei.c +@@ -998,6 +998,8 @@ static int htree_dirblock_to_tree(struct file *dir_file, + hinfo->hash, hinfo->minor_hash, de, + &tmp_str); + } else { ++ int save_len = fname_crypto_str.len; ++ + /* Directory is encrypted */ + err = ext4_fname_disk_to_usr(ctx, hinfo, de, + &fname_crypto_str); +@@ -1008,6 +1010,7 @@ static int htree_dirblock_to_tree(struct file *dir_file, + err = ext4_htree_store_dirent(dir_file, + hinfo->hash, hinfo->minor_hash, de, + &fname_crypto_str); ++ fname_crypto_str.len = save_len; + } + if (err != 0) { + count = err; +@@ -3132,6 +3135,7 @@ static int ext4_symlink(struct inode *dir, + istr.name = (const unsigned char *) symname; + istr.len = len; + ostr.name = sd->encrypted_path; ++ ostr.len = disk_link.len; + err = ext4_fname_usr_to_disk(ctx, &istr, &ostr); + ext4_put_fname_crypto_ctx(&ctx); + if (err < 0) +diff --git a/fs/ext4/symlink.c b/fs/ext4/symlink.c +index ce2ed28..3428c51 100644 +--- a/fs/ext4/symlink.c ++++ b/fs/ext4/symlink.c +@@ -74,6 +74,7 @@ static void *ext4_follow_link(struct dentry *dentry, struct nameidata *nd) + goto errout; + } + pstr.name = paddr; ++ pstr.len = plen; + res = _ext4_fname_disk_to_usr(ctx, NULL, &cstr, &pstr); + if (res < 0) + goto errout; -- 2.11.4.GIT