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
This commit is contained in:
parent
af2b6eaa10
commit
2787c00032
5 changed files with 68 additions and 50 deletions
|
@ -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
|
* Test whether a case-insensitive directory entry matches the filename
|
||||||
* being searched for.
|
* 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
|
* Returns: 0 if the directory entry matches, more than 0 if it
|
||||||
* doesn't match or less than zero on error.
|
* doesn't match or less than zero on error.
|
||||||
*/
|
*/
|
||||||
int f2fs_ci_compare(const struct inode *parent, const struct qstr *name,
|
static int f2fs_ci_compare(const struct inode *parent, const struct qstr *name,
|
||||||
const struct qstr *entry, bool quick)
|
u8 *de_name, size_t de_name_len, bool quick)
|
||||||
{
|
{
|
||||||
const struct super_block *sb = parent->i_sb;
|
const struct super_block *sb = parent->i_sb;
|
||||||
const struct unicode_map *um = sb->s_encoding;
|
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;
|
int ret;
|
||||||
|
|
||||||
if (quick)
|
if (IS_ENCRYPTED(parent)) {
|
||||||
ret = utf8_strncasecmp_folded(um, name, entry);
|
const struct fscrypt_str encrypted_name =
|
||||||
else
|
FSTR_INIT(de_name, de_name_len);
|
||||||
ret = utf8_strncasecmp(um, name, entry);
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
else
|
||||||
|
ret = utf8_strncasecmp(um, name, &entry);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
/* Handle invalid character sequence as either an error
|
/* Handle invalid character sequence as either an error
|
||||||
* or as an opaque byte sequence.
|
* or as an opaque byte sequence.
|
||||||
*/
|
*/
|
||||||
if (sb_has_enc_strict_mode(sb))
|
if (sb_has_enc_strict_mode(sb))
|
||||||
return -EINVAL;
|
ret = -EINVAL;
|
||||||
|
else if (name->len != entry.len)
|
||||||
if (name->len != entry->len)
|
ret = 1;
|
||||||
return 1;
|
else
|
||||||
|
ret = !!memcmp(name->name, entry.name, entry.len);
|
||||||
return !!memcmp(name->name, entry->name, name->len);
|
|
||||||
}
|
}
|
||||||
|
out:
|
||||||
|
kfree(decrypted_name.name);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,24 +191,24 @@ static inline bool f2fs_match_name(struct f2fs_dentry_ptr *d,
|
||||||
{
|
{
|
||||||
#ifdef CONFIG_UNICODE
|
#ifdef CONFIG_UNICODE
|
||||||
struct inode *parent = d->inode;
|
struct inode *parent = d->inode;
|
||||||
struct super_block *sb = parent->i_sb;
|
u8 *name;
|
||||||
struct qstr entry;
|
int len;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (de->hash_code != namehash)
|
if (de->hash_code != namehash)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
#ifdef CONFIG_UNICODE
|
#ifdef CONFIG_UNICODE
|
||||||
entry.name = d->filename[bit_pos];
|
name = d->filename[bit_pos];
|
||||||
entry.len = de->name_len;
|
len = le16_to_cpu(de->name_len);
|
||||||
|
|
||||||
if (sb->s_encoding && IS_CASEFOLDED(parent)) {
|
if (needs_casefold(parent)) {
|
||||||
if (cf_str->name) {
|
if (cf_str->name) {
|
||||||
struct qstr cf = {.name = cf_str->name,
|
struct qstr cf = {.name = cf_str->name,
|
||||||
.len = cf_str->len};
|
.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);
|
false);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -616,13 +634,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,
|
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)
|
struct inode *inode, nid_t ino, umode_t mode)
|
||||||
{
|
{
|
||||||
unsigned int bit_pos;
|
unsigned int bit_pos;
|
||||||
unsigned int level;
|
unsigned int level;
|
||||||
unsigned int current_depth;
|
unsigned int current_depth;
|
||||||
unsigned long bidx, block;
|
unsigned long bidx, block;
|
||||||
f2fs_hash_t dentry_hash;
|
|
||||||
unsigned int nbucket, nblock;
|
unsigned int nbucket, nblock;
|
||||||
struct page *dentry_page = NULL;
|
struct page *dentry_page = NULL;
|
||||||
struct f2fs_dentry_block *dentry_blk = NULL;
|
struct f2fs_dentry_block *dentry_blk = NULL;
|
||||||
|
@ -632,7 +650,6 @@ int f2fs_add_regular_entry(struct inode *dir, const struct qstr *new_name,
|
||||||
|
|
||||||
level = 0;
|
level = 0;
|
||||||
slots = GET_DENTRY_SLOTS(new_name->len);
|
slots = GET_DENTRY_SLOTS(new_name->len);
|
||||||
dentry_hash = f2fs_dentry_hash(dir, new_name, NULL);
|
|
||||||
|
|
||||||
current_depth = F2FS_I(dir)->i_current_depth;
|
current_depth = F2FS_I(dir)->i_current_depth;
|
||||||
if (F2FS_I(dir)->chash == dentry_hash) {
|
if (F2FS_I(dir)->chash == dentry_hash) {
|
||||||
|
@ -718,17 +735,19 @@ int f2fs_add_dentry(struct inode *dir, struct fscrypt_name *fname,
|
||||||
struct inode *inode, nid_t ino, umode_t mode)
|
struct inode *inode, nid_t ino, umode_t mode)
|
||||||
{
|
{
|
||||||
struct qstr new_name;
|
struct qstr new_name;
|
||||||
|
f2fs_hash_t dentry_hash;
|
||||||
int err = -EAGAIN;
|
int err = -EAGAIN;
|
||||||
|
|
||||||
new_name.name = fname_name(fname);
|
new_name.name = fname_name(fname);
|
||||||
new_name.len = fname_len(fname);
|
new_name.len = fname_len(fname);
|
||||||
|
|
||||||
if (f2fs_has_inline_dentry(dir))
|
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);
|
inode, ino, mode);
|
||||||
|
dentry_hash = f2fs_dentry_hash(dir, &new_name, fname);
|
||||||
if (err == -EAGAIN)
|
if (err == -EAGAIN)
|
||||||
err = f2fs_add_regular_entry(dir, &new_name, fname->usr_fname,
|
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);
|
f2fs_update_time(F2FS_I_SB(dir), REQ_TIME);
|
||||||
return err;
|
return err;
|
||||||
|
|
|
@ -3077,11 +3077,6 @@ int f2fs_update_extension_list(struct f2fs_sb_info *sbi, const char *name,
|
||||||
bool hot, bool set);
|
bool hot, bool set);
|
||||||
struct dentry *f2fs_get_parent(struct dentry *child);
|
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
|
* dir.c
|
||||||
*/
|
*/
|
||||||
|
@ -3115,7 +3110,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,
|
const struct qstr *name, f2fs_hash_t name_hash,
|
||||||
unsigned int bit_pos);
|
unsigned int bit_pos);
|
||||||
int f2fs_add_regular_entry(struct inode *dir, const struct qstr *new_name,
|
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);
|
struct inode *inode, nid_t ino, umode_t mode);
|
||||||
int f2fs_add_dentry(struct inode *dir, struct fscrypt_name *fname,
|
int f2fs_add_dentry(struct inode *dir, struct fscrypt_name *fname,
|
||||||
struct inode *inode, nid_t ino, umode_t mode);
|
struct inode *inode, nid_t ino, umode_t mode);
|
||||||
|
@ -3148,7 +3143,7 @@ int f2fs_sanity_check_ckpt(struct f2fs_sb_info *sbi);
|
||||||
* hash.c
|
* hash.c
|
||||||
*/
|
*/
|
||||||
f2fs_hash_t f2fs_dentry_hash(const struct inode *dir,
|
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
|
* node.c
|
||||||
|
@ -3658,7 +3653,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,
|
int f2fs_make_empty_inline_dir(struct inode *inode, struct inode *parent,
|
||||||
struct page *ipage);
|
struct page *ipage);
|
||||||
int f2fs_add_inline_entry(struct inode *dir, const struct qstr *new_name,
|
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 inode *inode, nid_t ino, umode_t mode);
|
||||||
void f2fs_delete_inline_entry(struct f2fs_dir_entry *dentry,
|
void f2fs_delete_inline_entry(struct f2fs_dir_entry *dentry,
|
||||||
struct page *page, struct inode *dir,
|
struct page *page, struct inode *dir,
|
||||||
|
|
|
@ -68,8 +68,9 @@ static void str2hashbuf(const unsigned char *msg, size_t len,
|
||||||
*buf++ = pad;
|
*buf++ = pad;
|
||||||
}
|
}
|
||||||
|
|
||||||
static f2fs_hash_t __f2fs_dentry_hash(const struct qstr *name_info,
|
static f2fs_hash_t __f2fs_dentry_hash(const struct inode *dir,
|
||||||
struct fscrypt_name *fname)
|
const struct qstr *name_info,
|
||||||
|
const struct fscrypt_name *fname)
|
||||||
{
|
{
|
||||||
__u32 hash;
|
__u32 hash;
|
||||||
f2fs_hash_t f2fs_hash;
|
f2fs_hash_t f2fs_hash;
|
||||||
|
@ -79,12 +80,17 @@ static f2fs_hash_t __f2fs_dentry_hash(const struct qstr *name_info,
|
||||||
size_t len = name_info->len;
|
size_t len = name_info->len;
|
||||||
|
|
||||||
/* encrypted bigname case */
|
/* encrypted bigname case */
|
||||||
if (fname && !fname->disk_name.name)
|
if (fname && fname->is_ciphertext_name)
|
||||||
return cpu_to_le32(fname->hash);
|
return cpu_to_le32(fname->hash);
|
||||||
|
|
||||||
if (is_dot_dotdot(name_info))
|
if (is_dot_dotdot(name_info))
|
||||||
return 0;
|
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 */
|
/* Initialize the default seed for the hash checksum functions */
|
||||||
buf[0] = 0x67452301;
|
buf[0] = 0x67452301;
|
||||||
buf[1] = 0xefcdab89;
|
buf[1] = 0xefcdab89;
|
||||||
|
@ -106,7 +112,7 @@ static f2fs_hash_t __f2fs_dentry_hash(const struct qstr *name_info,
|
||||||
}
|
}
|
||||||
|
|
||||||
f2fs_hash_t f2fs_dentry_hash(const struct inode *dir,
|
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
|
#ifdef CONFIG_UNICODE
|
||||||
struct f2fs_sb_info *sbi = F2FS_SB(dir->i_sb);
|
struct f2fs_sb_info *sbi = F2FS_SB(dir->i_sb);
|
||||||
|
@ -114,27 +120,30 @@ f2fs_hash_t f2fs_dentry_hash(const struct inode *dir,
|
||||||
int r, dlen;
|
int r, dlen;
|
||||||
unsigned char *buff;
|
unsigned char *buff;
|
||||||
struct qstr folded;
|
struct qstr folded;
|
||||||
|
const struct qstr *name = fname ? fname->usr_fname : name_info;
|
||||||
|
|
||||||
if (!name_info->len || !IS_CASEFOLDED(dir))
|
if (!name_info->len || !IS_CASEFOLDED(dir))
|
||||||
goto opaque_seq;
|
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);
|
buff = f2fs_kzalloc(sbi, sizeof(char) * PATH_MAX, GFP_KERNEL);
|
||||||
if (!buff)
|
if (!buff)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
dlen = utf8_casefold(um, name, buff, PATH_MAX);
|
||||||
dlen = utf8_casefold(um, name_info, buff, PATH_MAX);
|
|
||||||
if (dlen < 0) {
|
if (dlen < 0) {
|
||||||
kvfree(buff);
|
kvfree(buff);
|
||||||
goto opaque_seq;
|
goto opaque_seq;
|
||||||
}
|
}
|
||||||
folded.name = buff;
|
folded.name = buff;
|
||||||
folded.len = dlen;
|
folded.len = dlen;
|
||||||
r = __f2fs_dentry_hash(&folded, fname);
|
r = __f2fs_dentry_hash(dir, &folded, fname);
|
||||||
|
|
||||||
kvfree(buff);
|
kvfree(buff);
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
opaque_seq:
|
opaque_seq:
|
||||||
#endif
|
#endif
|
||||||
return __f2fs_dentry_hash(name_info, fname);
|
return __f2fs_dentry_hash(dir, name_info, fname);
|
||||||
}
|
}
|
||||||
|
|
|
@ -483,8 +483,8 @@ static int f2fs_add_inline_entries(struct inode *dir, void *inline_dentry)
|
||||||
ino = le32_to_cpu(de->ino);
|
ino = le32_to_cpu(de->ino);
|
||||||
fake_mode = f2fs_get_de_type(de) << S_SHIFT;
|
fake_mode = f2fs_get_de_type(de) << S_SHIFT;
|
||||||
|
|
||||||
err = f2fs_add_regular_entry(dir, &new_name, NULL, NULL,
|
err = f2fs_add_regular_entry(dir, &new_name, NULL,
|
||||||
ino, fake_mode);
|
de->hash_code, NULL, ino, fake_mode);
|
||||||
if (err)
|
if (err)
|
||||||
goto punch_dentry_pages;
|
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,
|
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 inode *inode, nid_t ino, umode_t mode)
|
||||||
{
|
{
|
||||||
struct f2fs_sb_info *sbi = F2FS_I_SB(dir);
|
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;
|
struct f2fs_dentry_ptr d;
|
||||||
int slots = GET_DENTRY_SLOTS(new_name->len);
|
int slots = GET_DENTRY_SLOTS(new_name->len);
|
||||||
struct page *page = NULL;
|
struct page *page = NULL;
|
||||||
|
const struct qstr *orig_name = fname->usr_fname;
|
||||||
int err = 0;
|
int err = 0;
|
||||||
|
|
||||||
ipage = f2fs_get_node_page(sbi, dir->i_ino);
|
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);
|
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);
|
f2fs_update_dentry(ino, mode, &d, new_name, name_hash, bit_pos);
|
||||||
|
|
||||||
set_page_dirty(ipage);
|
set_page_dirty(ipage);
|
||||||
|
|
|
@ -3298,12 +3298,6 @@ static int f2fs_setup_casefold(struct f2fs_sb_info *sbi)
|
||||||
struct unicode_map *encoding;
|
struct unicode_map *encoding;
|
||||||
__u16 encoding_flags;
|
__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,
|
if (f2fs_sb_read_encoding(sbi->raw_super, &encoding_info,
|
||||||
&encoding_flags)) {
|
&encoding_flags)) {
|
||||||
f2fs_err(sbi,
|
f2fs_err(sbi,
|
||||||
|
|
Loading…
Reference in a new issue