| From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 |
| From: Daniel Rosenberg <drosen@google.com> |
| Date: Mon, 30 Sep 2019 12:53:51 -0700 |
| Subject: FROMLIST: f2fs: Handle casefolding with Encryption |
| |
| This expands f2fs's casefolding support to include encrypted |
| directories. For encrypted directories, we use the siphash of the |
| casefolded name. This ensures there is no direct way to go from an |
| unencrypted name to the stored hash on disk without knowledge of the |
| encryption policy keys. |
| |
| Additionally, we switch to using the vfs layer's casefolding support |
| instead of storing this information inside of f2fs's private data. |
| |
| Signed-off-by: Daniel Rosenberg <drosen@google.com> |
| Note: Fixed some missing type conversions, crypto length issue |
| and hash check for ciphertext name |
| Test: Boots, /data/media is case insensitive |
| Bug: 138322712 |
| Link: https://lore.kernel.org/linux-f2fs-devel/20200208013552.241832-1-drosen@google.com/T/#t |
| Change-Id: I8f1e324472668e27d3e059cc80e4c981ce89dd9b |
| --- |
| fs/f2fs/dir.c | 65 +++++++++++++++++++++++++++++++----------------- |
| fs/f2fs/f2fs.h | 11 +++----- |
| fs/f2fs/hash.c | 25 +++++++++++++------ |
| fs/f2fs/inline.c | 9 ++++--- |
| fs/f2fs/super.c | 6 ----- |
| 5 files changed, 67 insertions(+), 49 deletions(-) |
| |
| diff --git a/fs/f2fs/dir.c b/fs/f2fs/dir.c |
| --- a/fs/f2fs/dir.c |
| +++ b/fs/f2fs/dir.c |
| @@ -108,34 +108,52 @@ static struct f2fs_dir_entry *find_in_block(struct inode *dir, |
| * Test whether a case-insensitive directory entry matches the filename |
| * being searched for. |
| * |
| + * Only called for encrypted names if the key is available. |
| + * |
| * Returns: 0 if the directory entry matches, more than 0 if it |
| * doesn't match or less than zero on error. |
| */ |
| -int f2fs_ci_compare(const struct inode *parent, const struct qstr *name, |
| - const struct qstr *entry, bool quick) |
| +static int f2fs_ci_compare(const struct inode *parent, const struct qstr *name, |
| + u8 *de_name, size_t de_name_len, bool quick) |
| { |
| const struct super_block *sb = parent->i_sb; |
| const struct unicode_map *um = sb->s_encoding; |
| + struct fscrypt_str decrypted_name = FSTR_INIT(NULL, de_name_len); |
| + struct qstr entry = QSTR_INIT(de_name, de_name_len); |
| int ret; |
| |
| + if (IS_ENCRYPTED(parent)) { |
| + const struct fscrypt_str encrypted_name = |
| + FSTR_INIT(de_name, de_name_len); |
| + |
| + decrypted_name.name = kmalloc(de_name_len, GFP_KERNEL); |
| + if (!decrypted_name.name) |
| + return -ENOMEM; |
| + ret = fscrypt_fname_disk_to_usr(parent, 0, 0, &encrypted_name, |
| + &decrypted_name); |
| + if (ret < 0) |
| + goto out; |
| + entry.name = decrypted_name.name; |
| + entry.len = decrypted_name.len; |
| + } |
| + |
| if (quick) |
| - ret = utf8_strncasecmp_folded(um, name, entry); |
| + ret = utf8_strncasecmp_folded(um, name, &entry); |
| else |
| - ret = utf8_strncasecmp(um, name, entry); |
| - |
| + ret = utf8_strncasecmp(um, name, &entry); |
| if (ret < 0) { |
| /* Handle invalid character sequence as either an error |
| * or as an opaque byte sequence. |
| */ |
| if (sb_has_enc_strict_mode(sb)) |
| - return -EINVAL; |
| - |
| - if (name->len != entry->len) |
| - return 1; |
| - |
| - return !!memcmp(name->name, entry->name, name->len); |
| + ret = -EINVAL; |
| + else if (name->len != entry.len) |
| + ret = 1; |
| + else |
| + ret = !!memcmp(name->name, entry.name, entry.len); |
| } |
| - |
| +out: |
| + kfree(decrypted_name.name); |
| return ret; |
| } |
| |
| @@ -173,24 +191,24 @@ static inline bool f2fs_match_name(struct f2fs_dentry_ptr *d, |
| { |
| #ifdef CONFIG_UNICODE |
| struct inode *parent = d->inode; |
| - struct super_block *sb = parent->i_sb; |
| - struct qstr entry; |
| + u8 *name; |
| + int len; |
| #endif |
| |
| if (de->hash_code != namehash) |
| return false; |
| |
| #ifdef CONFIG_UNICODE |
| - entry.name = d->filename[bit_pos]; |
| - entry.len = de->name_len; |
| + name = d->filename[bit_pos]; |
| + len = le16_to_cpu(de->name_len); |
| |
| - if (sb->s_encoding && IS_CASEFOLDED(parent)) { |
| + if (needs_casefold(parent)) { |
| if (cf_str->name) { |
| struct qstr cf = {.name = cf_str->name, |
| .len = cf_str->len}; |
| - return !f2fs_ci_compare(parent, &cf, &entry, true); |
| + return !f2fs_ci_compare(parent, &cf, name, len, true); |
| } |
| - return !f2fs_ci_compare(parent, fname->usr_fname, &entry, |
| + return !f2fs_ci_compare(parent, fname->usr_fname, name, len, |
| false); |
| } |
| #endif |
| @@ -614,13 +632,13 @@ void f2fs_update_dentry(nid_t ino, umode_t mode, struct f2fs_dentry_ptr *d, |
| |
| int f2fs_add_regular_entry(struct inode *dir, const struct qstr *new_name, |
| const struct qstr *orig_name, |
| + f2fs_hash_t dentry_hash, |
| struct inode *inode, nid_t ino, umode_t mode) |
| { |
| unsigned int bit_pos; |
| unsigned int level; |
| unsigned int current_depth; |
| unsigned long bidx, block; |
| - f2fs_hash_t dentry_hash; |
| unsigned int nbucket, nblock; |
| struct page *dentry_page = NULL; |
| struct f2fs_dentry_block *dentry_blk = NULL; |
| @@ -630,7 +648,6 @@ int f2fs_add_regular_entry(struct inode *dir, const struct qstr *new_name, |
| |
| level = 0; |
| slots = GET_DENTRY_SLOTS(new_name->len); |
| - dentry_hash = f2fs_dentry_hash(dir, new_name, NULL); |
| |
| current_depth = F2FS_I(dir)->i_current_depth; |
| if (F2FS_I(dir)->chash == dentry_hash) { |
| @@ -716,17 +733,19 @@ int f2fs_add_dentry(struct inode *dir, struct fscrypt_name *fname, |
| struct inode *inode, nid_t ino, umode_t mode) |
| { |
| struct qstr new_name; |
| + f2fs_hash_t dentry_hash; |
| int err = -EAGAIN; |
| |
| new_name.name = fname_name(fname); |
| new_name.len = fname_len(fname); |
| |
| if (f2fs_has_inline_dentry(dir)) |
| - err = f2fs_add_inline_entry(dir, &new_name, fname->usr_fname, |
| + err = f2fs_add_inline_entry(dir, &new_name, fname, |
| inode, ino, mode); |
| + dentry_hash = f2fs_dentry_hash(dir, &new_name, fname); |
| if (err == -EAGAIN) |
| err = f2fs_add_regular_entry(dir, &new_name, fname->usr_fname, |
| - inode, ino, mode); |
| + dentry_hash, inode, ino, mode); |
| |
| f2fs_update_time(F2FS_I_SB(dir), REQ_TIME); |
| return err; |
| diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h |
| --- a/fs/f2fs/f2fs.h |
| +++ b/fs/f2fs/f2fs.h |
| @@ -3105,11 +3105,6 @@ int f2fs_update_extension_list(struct f2fs_sb_info *sbi, const char *name, |
| bool hot, bool set); |
| struct dentry *f2fs_get_parent(struct dentry *child); |
| |
| -extern int f2fs_ci_compare(const struct inode *parent, |
| - const struct qstr *name, |
| - const struct qstr *entry, |
| - bool quick); |
| - |
| /* |
| * dir.c |
| */ |
| @@ -3143,7 +3138,7 @@ void f2fs_update_dentry(nid_t ino, umode_t mode, struct f2fs_dentry_ptr *d, |
| const struct qstr *name, f2fs_hash_t name_hash, |
| unsigned int bit_pos); |
| int f2fs_add_regular_entry(struct inode *dir, const struct qstr *new_name, |
| - const struct qstr *orig_name, |
| + const struct qstr *orig_name, f2fs_hash_t dentry_hash, |
| struct inode *inode, nid_t ino, umode_t mode); |
| int f2fs_add_dentry(struct inode *dir, struct fscrypt_name *fname, |
| struct inode *inode, nid_t ino, umode_t mode); |
| @@ -3176,7 +3171,7 @@ int f2fs_sanity_check_ckpt(struct f2fs_sb_info *sbi); |
| * hash.c |
| */ |
| f2fs_hash_t f2fs_dentry_hash(const struct inode *dir, |
| - const struct qstr *name_info, struct fscrypt_name *fname); |
| + const struct qstr *name_info, const struct fscrypt_name *fname); |
| |
| /* |
| * node.c |
| @@ -3688,7 +3683,7 @@ struct f2fs_dir_entry *f2fs_find_in_inline_dir(struct inode *dir, |
| int f2fs_make_empty_inline_dir(struct inode *inode, struct inode *parent, |
| struct page *ipage); |
| int f2fs_add_inline_entry(struct inode *dir, const struct qstr *new_name, |
| - const struct qstr *orig_name, |
| + const struct fscrypt_name *fname, |
| struct inode *inode, nid_t ino, umode_t mode); |
| void f2fs_delete_inline_entry(struct f2fs_dir_entry *dentry, |
| struct page *page, struct inode *dir, |
| diff --git a/fs/f2fs/hash.c b/fs/f2fs/hash.c |
| --- a/fs/f2fs/hash.c |
| +++ b/fs/f2fs/hash.c |
| @@ -67,8 +67,9 @@ static void str2hashbuf(const unsigned char *msg, size_t len, |
| *buf++ = pad; |
| } |
| |
| -static f2fs_hash_t __f2fs_dentry_hash(const struct qstr *name_info, |
| - struct fscrypt_name *fname) |
| +static f2fs_hash_t __f2fs_dentry_hash(const struct inode *dir, |
| + const struct qstr *name_info, |
| + const struct fscrypt_name *fname) |
| { |
| __u32 hash; |
| f2fs_hash_t f2fs_hash; |
| @@ -78,12 +79,17 @@ static f2fs_hash_t __f2fs_dentry_hash(const struct qstr *name_info, |
| size_t len = name_info->len; |
| |
| /* encrypted bigname case */ |
| - if (fname && !fname->disk_name.name) |
| + if (fname && fname->is_ciphertext_name) |
| return cpu_to_le32(fname->hash); |
| |
| if (is_dot_dotdot(name_info)) |
| return 0; |
| |
| + if (IS_CASEFOLDED(dir) && IS_ENCRYPTED(dir)) { |
| + f2fs_hash = cpu_to_le32(fscrypt_fname_siphash(dir, name_info)); |
| + return f2fs_hash; |
| + } |
| + |
| /* Initialize the default seed for the hash checksum functions */ |
| buf[0] = 0x67452301; |
| buf[1] = 0xefcdab89; |
| @@ -105,7 +111,7 @@ static f2fs_hash_t __f2fs_dentry_hash(const struct qstr *name_info, |
| } |
| |
| f2fs_hash_t f2fs_dentry_hash(const struct inode *dir, |
| - const struct qstr *name_info, struct fscrypt_name *fname) |
| + const struct qstr *name_info, const struct fscrypt_name *fname) |
| { |
| #ifdef CONFIG_UNICODE |
| struct f2fs_sb_info *sbi = F2FS_SB(dir->i_sb); |
| @@ -113,27 +119,30 @@ f2fs_hash_t f2fs_dentry_hash(const struct inode *dir, |
| int r, dlen; |
| unsigned char *buff; |
| struct qstr folded; |
| + const struct qstr *name = fname ? fname->usr_fname : name_info; |
| |
| if (!name_info->len || !IS_CASEFOLDED(dir)) |
| goto opaque_seq; |
| |
| + if (IS_ENCRYPTED(dir) && !fscrypt_has_encryption_key(dir)) |
| + goto opaque_seq; |
| + |
| buff = f2fs_kzalloc(sbi, sizeof(char) * PATH_MAX, GFP_KERNEL); |
| if (!buff) |
| return -ENOMEM; |
| - |
| - dlen = utf8_casefold(um, name_info, buff, PATH_MAX); |
| + dlen = utf8_casefold(um, name, buff, PATH_MAX); |
| if (dlen < 0) { |
| kvfree(buff); |
| goto opaque_seq; |
| } |
| folded.name = buff; |
| folded.len = dlen; |
| - r = __f2fs_dentry_hash(&folded, fname); |
| + r = __f2fs_dentry_hash(dir, &folded, fname); |
| |
| kvfree(buff); |
| return r; |
| |
| opaque_seq: |
| #endif |
| - return __f2fs_dentry_hash(name_info, fname); |
| + return __f2fs_dentry_hash(dir, name_info, fname); |
| } |
| diff --git a/fs/f2fs/inline.c b/fs/f2fs/inline.c |
| --- a/fs/f2fs/inline.c |
| +++ b/fs/f2fs/inline.c |
| @@ -483,8 +483,8 @@ static int f2fs_add_inline_entries(struct inode *dir, void *inline_dentry) |
| ino = le32_to_cpu(de->ino); |
| fake_mode = f2fs_get_de_type(de) << S_SHIFT; |
| |
| - err = f2fs_add_regular_entry(dir, &new_name, NULL, NULL, |
| - ino, fake_mode); |
| + err = f2fs_add_regular_entry(dir, &new_name, NULL, |
| + de->hash_code, NULL, ino, fake_mode); |
| if (err) |
| goto punch_dentry_pages; |
| |
| @@ -596,7 +596,7 @@ int f2fs_try_convert_inline_dir(struct inode *dir, struct dentry *dentry) |
| } |
| |
| int f2fs_add_inline_entry(struct inode *dir, const struct qstr *new_name, |
| - const struct qstr *orig_name, |
| + const struct fscrypt_name *fname, |
| struct inode *inode, nid_t ino, umode_t mode) |
| { |
| struct f2fs_sb_info *sbi = F2FS_I_SB(dir); |
| @@ -607,6 +607,7 @@ int f2fs_add_inline_entry(struct inode *dir, const struct qstr *new_name, |
| struct f2fs_dentry_ptr d; |
| int slots = GET_DENTRY_SLOTS(new_name->len); |
| struct page *page = NULL; |
| + const struct qstr *orig_name = fname->usr_fname; |
| int err = 0; |
| |
| ipage = f2fs_get_node_page(sbi, dir->i_ino); |
| @@ -637,7 +638,7 @@ int f2fs_add_inline_entry(struct inode *dir, const struct qstr *new_name, |
| |
| f2fs_wait_on_page_writeback(ipage, NODE, true, true); |
| |
| - name_hash = f2fs_dentry_hash(dir, new_name, NULL); |
| + name_hash = f2fs_dentry_hash(dir, new_name, fname); |
| f2fs_update_dentry(ino, mode, &d, new_name, name_hash, bit_pos); |
| |
| set_page_dirty(ipage); |
| diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c |
| --- a/fs/f2fs/super.c |
| +++ b/fs/f2fs/super.c |
| @@ -3320,12 +3320,6 @@ static int f2fs_setup_casefold(struct f2fs_sb_info *sbi) |
| struct unicode_map *encoding; |
| __u16 encoding_flags; |
| |
| - if (f2fs_sb_has_encrypt(sbi)) { |
| - f2fs_err(sbi, |
| - "Can't mount with encoding and encryption"); |
| - return -EINVAL; |
| - } |
| - |
| if (f2fs_sb_read_encoding(sbi->raw_super, &encoding_info, |
| &encoding_flags)) { |
| f2fs_err(sbi, |