Commit 1f3862b5 authored by Michael Halcrow's avatar Michael Halcrow Committed by Theodore Ts'o

ext4 crypto: filename encryption modifications

Modifies htree_dirblock_to_tree, dx_make_map, ext4_match search_dir,
and ext4_find_dest_de to support fname crypto.  Filename encryption
feature is not yet enabled at this patch.
Signed-off-by: default avatarUday Savagaonkar <savagaon@google.com>
Signed-off-by: default avatarIldar Muslukhov <ildarm@google.com>
Signed-off-by: default avatarMichael Halcrow <mhalcrow@google.com>
Signed-off-by: default avatarTheodore Ts'o <tytso@mit.edu>
parent b3098486
...@@ -253,8 +253,9 @@ static struct dx_frame *dx_probe(const struct qstr *d_name, ...@@ -253,8 +253,9 @@ static struct dx_frame *dx_probe(const struct qstr *d_name,
struct dx_hash_info *hinfo, struct dx_hash_info *hinfo,
struct dx_frame *frame); struct dx_frame *frame);
static void dx_release(struct dx_frame *frames); static void dx_release(struct dx_frame *frames);
static int dx_make_map(struct ext4_dir_entry_2 *de, unsigned blocksize, static int dx_make_map(struct inode *dir, struct ext4_dir_entry_2 *de,
struct dx_hash_info *hinfo, struct dx_map_entry map[]); unsigned blocksize, struct dx_hash_info *hinfo,
struct dx_map_entry map[]);
static void dx_sort_map(struct dx_map_entry *map, unsigned count); static void dx_sort_map(struct dx_map_entry *map, unsigned count);
static struct ext4_dir_entry_2 *dx_move_dirents(char *from, char *to, static struct ext4_dir_entry_2 *dx_move_dirents(char *from, char *to,
struct dx_map_entry *offsets, int count, unsigned blocksize); struct dx_map_entry *offsets, int count, unsigned blocksize);
...@@ -968,7 +969,8 @@ static int htree_dirblock_to_tree(struct file *dir_file, ...@@ -968,7 +969,8 @@ static int htree_dirblock_to_tree(struct file *dir_file,
struct buffer_head *bh; struct buffer_head *bh;
struct ext4_dir_entry_2 *de, *top; struct ext4_dir_entry_2 *de, *top;
int err = 0, count = 0; int err = 0, count = 0;
struct ext4_str tmp_str; struct ext4_fname_crypto_ctx *ctx = NULL;
struct ext4_str fname_crypto_str = {.name = NULL, .len = 0}, tmp_str;
dxtrace(printk(KERN_INFO "In htree dirblock_to_tree: block %lu\n", dxtrace(printk(KERN_INFO "In htree dirblock_to_tree: block %lu\n",
(unsigned long)block)); (unsigned long)block));
...@@ -980,6 +982,24 @@ static int htree_dirblock_to_tree(struct file *dir_file, ...@@ -980,6 +982,24 @@ static int htree_dirblock_to_tree(struct file *dir_file,
top = (struct ext4_dir_entry_2 *) ((char *) de + top = (struct ext4_dir_entry_2 *) ((char *) de +
dir->i_sb->s_blocksize - dir->i_sb->s_blocksize -
EXT4_DIR_REC_LEN(0)); EXT4_DIR_REC_LEN(0));
#ifdef CONFIG_EXT4_FS_ENCRYPTION
/* Check if the directory is encrypted */
ctx = ext4_get_fname_crypto_ctx(dir, EXT4_NAME_LEN);
if (IS_ERR(ctx)) {
err = PTR_ERR(ctx);
brelse(bh);
return err;
}
if (ctx != NULL) {
err = ext4_fname_crypto_alloc_buffer(ctx, EXT4_NAME_LEN,
&fname_crypto_str);
if (err < 0) {
ext4_put_fname_crypto_ctx(&ctx);
brelse(bh);
return err;
}
}
#endif
for (; de < top; de = ext4_next_entry(de, dir->i_sb->s_blocksize)) { for (; de < top; de = ext4_next_entry(de, dir->i_sb->s_blocksize)) {
if (ext4_check_dir_entry(dir, NULL, de, bh, if (ext4_check_dir_entry(dir, NULL, de, bh,
bh->b_data, bh->b_size, bh->b_data, bh->b_size,
...@@ -988,24 +1008,52 @@ static int htree_dirblock_to_tree(struct file *dir_file, ...@@ -988,24 +1008,52 @@ static int htree_dirblock_to_tree(struct file *dir_file,
/* silently ignore the rest of the block */ /* silently ignore the rest of the block */
break; 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); ext4fs_dirhash(de->name, de->name_len, hinfo);
#endif
if ((hinfo->hash < start_hash) || if ((hinfo->hash < start_hash) ||
((hinfo->hash == start_hash) && ((hinfo->hash == start_hash) &&
(hinfo->minor_hash < start_minor_hash))) (hinfo->minor_hash < start_minor_hash)))
continue; continue;
if (de->inode == 0) if (de->inode == 0)
continue; continue;
if (ctx == NULL) {
/* Directory is not encrypted */
tmp_str.name = de->name; tmp_str.name = de->name;
tmp_str.len = de->name_len; tmp_str.len = de->name_len;
err = ext4_htree_store_dirent(dir_file, err = ext4_htree_store_dirent(dir_file,
hinfo->hash, hinfo->minor_hash, de, &tmp_str); hinfo->hash, hinfo->minor_hash, de,
&tmp_str);
} else {
/* Directory is encrypted */
err = ext4_fname_disk_to_usr(ctx, de,
&fname_crypto_str);
if (err < 0) {
count = err;
goto errout;
}
err = ext4_htree_store_dirent(dir_file,
hinfo->hash, hinfo->minor_hash, de,
&fname_crypto_str);
}
if (err != 0) { if (err != 0) {
brelse(bh); count = err;
return err; goto errout;
} }
count++; count++;
} }
errout:
brelse(bh); brelse(bh);
#ifdef CONFIG_EXT4_FS_ENCRYPTION
ext4_put_fname_crypto_ctx(&ctx);
ext4_fname_crypto_free_buffer(&fname_crypto_str);
#endif
return count; return count;
} }
...@@ -1138,17 +1186,33 @@ static inline int search_dirblock(struct buffer_head *bh, ...@@ -1138,17 +1186,33 @@ static inline int search_dirblock(struct buffer_head *bh,
* Create map of hash values, offsets, and sizes, stored at end of block. * Create map of hash values, offsets, and sizes, stored at end of block.
* Returns number of entries mapped. * Returns number of entries mapped.
*/ */
static int dx_make_map(struct ext4_dir_entry_2 *de, unsigned blocksize, static int dx_make_map(struct inode *dir, struct ext4_dir_entry_2 *de,
struct dx_hash_info *hinfo, unsigned blocksize, struct dx_hash_info *hinfo,
struct dx_map_entry *map_tail) struct dx_map_entry *map_tail)
{ {
int count = 0; int count = 0;
char *base = (char *) de; char *base = (char *) de;
struct dx_hash_info h = *hinfo; 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) { while ((char *) de < base + blocksize) {
if (de->name_len && de->inode) { 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); ext4fs_dirhash(de->name, de->name_len, &h);
#endif
map_tail--; map_tail--;
map_tail->hash = h.hash; map_tail->hash = h.hash;
map_tail->offs = ((char *) de - base)>>2; map_tail->offs = ((char *) de - base)>>2;
...@@ -1159,6 +1223,9 @@ static int dx_make_map(struct ext4_dir_entry_2 *de, unsigned blocksize, ...@@ -1159,6 +1223,9 @@ static int dx_make_map(struct ext4_dir_entry_2 *de, unsigned blocksize,
/* XXX: do we need to check rec_len == 0 case? -Chris */ /* XXX: do we need to check rec_len == 0 case? -Chris */
de = ext4_next_entry(de, blocksize); de = ext4_next_entry(de, blocksize);
} }
#ifdef CONFIG_EXT4_FS_ENCRYPTION
ext4_put_fname_crypto_ctx(&ctx);
#endif
return count; return count;
} }
...@@ -1209,57 +1276,107 @@ static void dx_insert_block(struct dx_frame *frame, u32 hash, ext4_lblk_t block) ...@@ -1209,57 +1276,107 @@ static void dx_insert_block(struct dx_frame *frame, u32 hash, ext4_lblk_t block)
* `len <= EXT4_NAME_LEN' is guaranteed by caller. * `len <= EXT4_NAME_LEN' is guaranteed by caller.
* `de != NULL' is guaranteed by caller. * `de != NULL' is guaranteed by caller.
*/ */
static inline int ext4_match (int len, const char * const name, static inline int ext4_match(struct ext4_fname_crypto_ctx *ctx,
struct ext4_dir_entry_2 * de) struct ext4_str *fname_crypto_str,
int len, const char * const name,
struct ext4_dir_entry_2 *de)
{ {
if (len != de->name_len) int res;
return 0;
if (!de->inode) if (!de->inode)
return 0; return 0;
return !memcmp(name, de->name, len);
#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;
}
#endif
if (len != de->name_len)
return 0;
res = memcmp(name, de->name, len);
return (res == 0) ? 1 : 0;
} }
/* /*
* Returns 0 if not found, -1 on failure, and 1 on success * Returns 0 if not found, -1 on failure, and 1 on success
*/ */
int search_dir(struct buffer_head *bh, int search_dir(struct buffer_head *bh, char *search_buf, int buf_size,
char *search_buf, struct inode *dir, const struct qstr *d_name,
int buf_size, unsigned int offset, struct ext4_dir_entry_2 **res_dir)
struct inode *dir,
const struct qstr *d_name,
unsigned int offset,
struct ext4_dir_entry_2 **res_dir)
{ {
struct ext4_dir_entry_2 * de; struct ext4_dir_entry_2 * de;
char * dlimit; char * dlimit;
int de_len; int de_len;
const char *name = d_name->name; const char *name = d_name->name;
int namelen = d_name->len; 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;
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; de = (struct ext4_dir_entry_2 *)search_buf;
dlimit = search_buf + buf_size; dlimit = search_buf + buf_size;
while ((char *) de < dlimit) { while ((char *) de < dlimit) {
/* this code is executed quadratically often */ /* this code is executed quadratically often */
/* do minimal checking `by hand' */ /* do minimal checking `by hand' */
if ((char *) de + de->name_len <= dlimit) {
if ((char *) de + namelen <= dlimit && res = ext4_match(ctx, &fname_crypto_str, namelen,
ext4_match (namelen, name, de)) { name, de);
/* found a match - just to be sure, do a full check */ if (res < 0) {
if (ext4_check_dir_entry(dir, NULL, de, bh, bh->b_data, res = -1;
bh->b_size, offset)) goto return_result;
return -1; }
if (res > 0) {
/* found a match - just to be sure, do
* a full check */
if (ext4_check_dir_entry(dir, NULL, de, bh,
bh->b_data,
bh->b_size, offset)) {
res = -1;
goto return_result;
}
*res_dir = de; *res_dir = de;
return 1; res = 1;
goto return_result;
}
} }
/* prevent looping on a bad block */ /* prevent looping on a bad block */
de_len = ext4_rec_len_from_disk(de->rec_len, de_len = ext4_rec_len_from_disk(de->rec_len,
dir->i_sb->s_blocksize); dir->i_sb->s_blocksize);
if (de_len <= 0) if (de_len <= 0) {
return -1; res = -1;
goto return_result;
}
offset += de_len; offset += de_len;
de = (struct ext4_dir_entry_2 *) ((char *) de + de_len); de = (struct ext4_dir_entry_2 *) ((char *) de + de_len);
} }
return 0;
res = 0;
return_result:
ext4_put_fname_crypto_ctx(&ctx);
ext4_fname_crypto_free_buffer(&fname_crypto_str);
return res;
} }
static int is_dx_internal_node(struct inode *dir, ext4_lblk_t block, static int is_dx_internal_node(struct inode *dir, ext4_lblk_t block,
...@@ -1448,6 +1565,9 @@ static struct buffer_head * ext4_dx_find_entry(struct inode *dir, const struct q ...@@ -1448,6 +1565,9 @@ static struct buffer_head * ext4_dx_find_entry(struct inode *dir, const struct q
ext4_lblk_t block; ext4_lblk_t block;
int retval; int retval;
#ifdef CONFIG_EXT4_FS_ENCRYPTION
*res_dir = NULL;
#endif
frame = dx_probe(d_name, dir, &hinfo, frames); frame = dx_probe(d_name, dir, &hinfo, frames);
if (IS_ERR(frame)) if (IS_ERR(frame))
return (struct buffer_head *) frame; return (struct buffer_head *) frame;
...@@ -1656,7 +1776,7 @@ static struct ext4_dir_entry_2 *do_split(handle_t *handle, struct inode *dir, ...@@ -1656,7 +1776,7 @@ static struct ext4_dir_entry_2 *do_split(handle_t *handle, struct inode *dir,
/* create map in the end of data2 block */ /* create map in the end of data2 block */
map = (struct dx_map_entry *) (data2 + blocksize); map = (struct dx_map_entry *) (data2 + blocksize);
count = dx_make_map((struct ext4_dir_entry_2 *) data1, count = dx_make_map(dir, (struct ext4_dir_entry_2 *) data1,
blocksize, hinfo, map); blocksize, hinfo, map);
map -= count; map -= count;
dx_sort_map(map, count); dx_sort_map(map, count);
...@@ -1679,7 +1799,8 @@ static struct ext4_dir_entry_2 *do_split(handle_t *handle, struct inode *dir, ...@@ -1679,7 +1799,8 @@ static struct ext4_dir_entry_2 *do_split(handle_t *handle, struct inode *dir,
hash2, split, count-split)); hash2, split, count-split));
/* Fancy dance to stay within two buffers */ /* Fancy dance to stay within two buffers */
de2 = dx_move_dirents(data1, data2, map + split, count - split, blocksize); de2 = dx_move_dirents(data1, data2, map + split, count - split,
blocksize);
de = dx_pack_dirents(data1, blocksize); de = dx_pack_dirents(data1, blocksize);
de->rec_len = ext4_rec_len_to_disk(data1 + (blocksize - csum_size) - de->rec_len = ext4_rec_len_to_disk(data1 + (blocksize - csum_size) -
(char *) de, (char *) de,
...@@ -1735,15 +1856,48 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode, ...@@ -1735,15 +1856,48 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
int nlen, rlen; int nlen, rlen;
unsigned int offset = 0; unsigned int offset = 0;
char *top; 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);
/* 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; de = (struct ext4_dir_entry_2 *)buf;
top = buf + buf_size - reclen; top = buf + buf_size - reclen;
while ((char *) de <= top) { while ((char *) de <= top) {
if (ext4_check_dir_entry(dir, NULL, de, bh, if (ext4_check_dir_entry(dir, NULL, de, bh,
buf, buf_size, offset)) buf, buf_size, offset)) {
return -EIO; res = -EIO;
if (ext4_match(namelen, name, de)) goto return_result;
return -EEXIST; }
/* Provide crypto context and crypto buffer to ext4 match */
res = ext4_match(ctx, &fname_crypto_str, namelen, name, de);
if (res < 0)
goto return_result;
if (res > 0) {
res = -EEXIST;
goto return_result;
}
nlen = EXT4_DIR_REC_LEN(de->name_len); nlen = EXT4_DIR_REC_LEN(de->name_len);
rlen = ext4_rec_len_from_disk(de->rec_len, buf_size); rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
if ((de->inode ? rlen - nlen : rlen) >= reclen) if ((de->inode ? rlen - nlen : rlen) >= reclen)
...@@ -1751,11 +1905,17 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode, ...@@ -1751,11 +1905,17 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
de = (struct ext4_dir_entry_2 *)((char *)de + rlen); de = (struct ext4_dir_entry_2 *)((char *)de + rlen);
offset += rlen; offset += rlen;
} }
if ((char *) de > top)
return -ENOSPC;
if ((char *) de > top)
res = -ENOSPC;
else {
*dest_de = de; *dest_de = de;
return 0; res = 0;
}
return_result:
ext4_put_fname_crypto_ctx(&ctx);
ext4_fname_crypto_free_buffer(&fname_crypto_str);
return res;
} }
int ext4_insert_dentry(struct inode *dir, int ext4_insert_dentry(struct inode *dir,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment