F2FS normally disables inline data for encrypted regular files because the inline payload is stored in the inode block and does not pass through the regular fscrypt data I/O path. This wastes space for small encrypted files on filesystems that otherwise use inline_data.
Add encrypted inline data support for encrypted regular files. When the encrypted_inline_data on-disk feature is enabled, inline payloads of encrypted regular files are stored as ciphertext in the inode block. They are decrypted into page-cache plaintext on read and encrypted before being copied back into the inode block on write. F2FS keeps the on-disk format decision separate from fscrypt key capability. It uses fscrypt_supports_data_unit_inplace() when deciding whether a new file may keep inline_data. It calls fscrypt_prepare_data_unit_inplace() only when the encrypted inline payload is actually read or written. Update inline-data size checks to use the encrypted inline capacity, since the stored payload is rounded to the fscrypt contents alignment. If an encrypted inline-data file is truncated from a non-zero offset, convert it to normal data blocks first and then use the normal truncate path. Recovery copies inline payloads as on-disk bytes. Signed-off-by: LiaoYuanhong-vivo <[email protected]> --- Changes in v2: - Use fscrypt capability checking in f2fs_may_inline_data(); prepare software crypto only in encrypted inline read/write paths. - Use the encrypted inline-data capacity across F2FS size checks. - Convert encrypted inline data before non-zero truncation and propagate inline read/decrypt errors. fs/f2fs/Kconfig | 14 +++++ fs/f2fs/data.c | 8 +-- fs/f2fs/f2fs.h | 37 ++++++++++++- fs/f2fs/file.c | 24 ++++++++- fs/f2fs/inline.c | 131 ++++++++++++++++++++++++++++++++++++++++++----- fs/f2fs/super.c | 12 +++++ fs/f2fs/sysfs.c | 8 +++ 7 files changed, 214 insertions(+), 20 deletions(-) diff --git a/fs/f2fs/Kconfig b/fs/f2fs/Kconfig index 5916a02fb46d..0220f23be56d 100644 --- a/fs/f2fs/Kconfig +++ b/fs/f2fs/Kconfig @@ -92,6 +92,20 @@ config F2FS_FAULT_INJECTION If unsure, say N. +config F2FS_FS_ENCRYPTED_INLINE_DATA + bool "F2FS encrypted inline data support" + depends on F2FS_FS && FS_ENCRYPTION + help + Allow encrypted regular files to keep inline data inside the inode + while encrypting that inode-managed payload in software. + + This does not change normal data block encryption. Normal data + blocks continue to use the existing fscrypt path, such as blk-crypto + when inline encryption is enabled. + + Filesystems carrying the encrypted_inline_data incompat feature + require this option in order to be mounted correctly. + config F2FS_FS_COMPRESSION bool "F2FS compression feature" depends on F2FS_FS diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c index 657fd5986c73..9371ffa7c96d 100644 --- a/fs/f2fs/data.c +++ b/fs/f2fs/data.c @@ -3700,7 +3700,7 @@ static int prepare_write_begin(struct f2fs_sb_info *sbi, /* f2fs_lock_op avoids race between write CP and convert_inline_page */ if (f2fs_has_inline_data(inode)) { - if (pos + len > MAX_INLINE_DATA(inode)) + if (pos + len > f2fs_max_inline_data(inode)) flag = F2FS_GET_BLOCK_DEFAULT; f2fs_map_lock(sbi, &lc, flag); locked = true; @@ -3720,8 +3720,10 @@ static int prepare_write_begin(struct f2fs_sb_info *sbi, set_new_dnode(&dn, inode, ifolio, ifolio, 0); if (f2fs_has_inline_data(inode)) { - if (pos + len <= MAX_INLINE_DATA(inode)) { - f2fs_do_read_inline_data(folio, ifolio); + if (pos + len <= f2fs_max_inline_data(inode)) { + err = f2fs_do_read_inline_data(folio, ifolio); + if (err) + goto out; set_inode_flag(inode, FI_DATA_EXIST); if (inode->i_nlink) folio_set_f2fs_inline(ifolio); diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 832b2f8beb11..0a2d75baf23e 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -276,6 +276,7 @@ struct f2fs_mount_info { #define F2FS_FEATURE_RO 0x00004000 #define F2FS_FEATURE_DEVICE_ALIAS 0x00008000 #define F2FS_FEATURE_PACKED_SSA 0x00010000 +#define F2FS_FEATURE_ENCRYPTED_INLINE_DATA 0x00020000 #define __F2FS_HAS_FEATURE(raw_super, mask) \ ((raw_super->feature & cpu_to_le32(mask)) != 0) @@ -4502,7 +4503,7 @@ extern struct kmem_cache *f2fs_inode_entry_slab; bool f2fs_may_inline_data(struct inode *inode); bool f2fs_sanity_check_inline_data(struct inode *inode, struct folio *ifolio); bool f2fs_may_inline_dentry(struct inode *inode); -void f2fs_do_read_inline_data(struct folio *folio, struct folio *ifolio); +int f2fs_do_read_inline_data(struct folio *folio, struct folio *ifolio); void f2fs_truncate_inline_inode(struct inode *inode, struct folio *ifolio, u64 from); int f2fs_read_inline_data(struct inode *inode, struct folio *folio); @@ -4595,6 +4596,39 @@ static inline bool f2fs_encrypted_file(struct inode *inode) return IS_ENCRYPTED(inode) && S_ISREG(inode->i_mode); } +static inline bool f2fs_sb_has_encrypted_inline_data(struct f2fs_sb_info *sbi); + +static inline bool f2fs_uses_encrypted_inline_data(struct inode *inode) +{ +#ifdef CONFIG_F2FS_FS_ENCRYPTED_INLINE_DATA + /* + * When the filesystem allows encrypted inline data, inline payloads + * in encrypted regular files are interpreted as ciphertext. + */ + return f2fs_sb_has_encrypted_inline_data(F2FS_I_SB(inode)) && + f2fs_encrypted_file(inode); +#else + return false; +#endif +} + +static inline unsigned int f2fs_max_inline_data(struct inode *inode) +{ + unsigned int max_bytes = MAX_INLINE_DATA(inode); + + /* + * Encrypted inline data is rounded up to the fscrypt contents + * alignment before being stored back into the inode. This is an + * on-disk layout constraint, so it must not depend on whether the + * inode's key has been prepared yet. + */ +#ifdef CONFIG_F2FS_FS_ENCRYPTED_INLINE_DATA + if (f2fs_uses_encrypted_inline_data(inode)) + max_bytes = round_down(max_bytes, FSCRYPT_CONTENTS_ALIGNMENT); +#endif + return max_bytes; +} + static inline void f2fs_set_encrypted_inode(struct inode *inode) { #ifdef CONFIG_FS_ENCRYPTION @@ -4827,6 +4861,7 @@ F2FS_FEATURE_FUNCS(compression, COMPRESSION); F2FS_FEATURE_FUNCS(readonly, RO); F2FS_FEATURE_FUNCS(device_alias, DEVICE_ALIAS); F2FS_FEATURE_FUNCS(packed_ssa, PACKED_SSA); +F2FS_FEATURE_FUNCS(encrypted_inline_data, ENCRYPTED_INLINE_DATA); #ifdef CONFIG_BLK_DEV_ZONED static inline bool f2fs_zone_is_seq(struct f2fs_sb_info *sbi, int devi, diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index 71385ca4163d..ec243bb9039b 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -825,12 +825,32 @@ int f2fs_do_truncate_blocks(struct inode *inode, u64 from, bool lock) } if (f2fs_has_inline_data(inode)) { + if (f2fs_uses_encrypted_inline_data(inode) && from) { + f2fs_folio_put(ifolio, true); + if (lock) + f2fs_unlock_op(sbi, &lc); + + err = f2fs_convert_inline_inode(inode); + + if (lock) + f2fs_lock_op(sbi, &lc); + if (err) + goto out; + + ifolio = f2fs_get_inode_folio(sbi, inode->i_ino); + if (IS_ERR(ifolio)) { + err = PTR_ERR(ifolio); + goto out; + } + goto truncate_blocks; + } f2fs_truncate_inline_inode(inode, ifolio, from); f2fs_folio_put(ifolio, true); truncate_page = true; goto out; } +truncate_blocks: set_new_dnode(&dn, inode, ifolio, NULL, 0); err = f2fs_get_dnode_of_data(&dn, free_from, LOOKUP_NODE_RA); if (err) { @@ -1147,7 +1167,7 @@ int f2fs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, if (attr->ia_valid & ATTR_SIZE) { loff_t old_size = i_size_read(inode); - if (attr->ia_size > MAX_INLINE_DATA(inode)) { + if (attr->ia_size > f2fs_max_inline_data(inode)) { /* * should convert inline inode before i_size_write to * keep smaller than inline_data size with inline flag. @@ -5007,7 +5027,7 @@ static int f2fs_preallocate_blocks(struct kiocb *iocb, struct iov_iter *iter, if (f2fs_has_inline_data(inode)) { /* If the data will fit inline, don't bother. */ - if (pos + count <= MAX_INLINE_DATA(inode)) + if (pos + count <= f2fs_max_inline_data(inode)) return 0; ret = f2fs_convert_inline_inode(inode); if (ret) diff --git a/fs/f2fs/inline.c b/fs/f2fs/inline.c index 099f72089701..37d6509ea01a 100644 --- a/fs/f2fs/inline.c +++ b/fs/f2fs/inline.c @@ -21,7 +21,7 @@ static bool support_inline_data(struct inode *inode) return false; if (!S_ISREG(inode->i_mode) && !S_ISLNK(inode->i_mode)) return false; - if (i_size_read(inode) > MAX_INLINE_DATA(inode)) + if (i_size_read(inode) > f2fs_max_inline_data(inode)) return false; return true; } @@ -31,6 +31,9 @@ bool f2fs_may_inline_data(struct inode *inode) if (!support_inline_data(inode)) return false; + if (f2fs_uses_encrypted_inline_data(inode)) + return fscrypt_supports_data_unit_inplace(inode); + return !f2fs_post_read_required(inode); } @@ -65,7 +68,9 @@ bool f2fs_sanity_check_inline_data(struct inode *inode, struct folio *ifolio) * been synchronized to inmem fields. */ return (S_ISREG(inode->i_mode) && - (file_is_encrypt(inode) || file_is_verity(inode) || + ((file_is_encrypt(inode) && + !f2fs_sb_has_encrypted_inline_data(F2FS_I_SB(inode))) || + file_is_verity(inode) || (F2FS_I(inode)->i_flags & F2FS_COMPR_FL))); } @@ -80,22 +85,66 @@ bool f2fs_may_inline_dentry(struct inode *inode) return true; } -void f2fs_do_read_inline_data(struct folio *folio, struct folio *ifolio) +int f2fs_do_read_inline_data(struct folio *folio, struct folio *ifolio) { struct inode *inode = folio->mapping->host; + unsigned int len = min_t(loff_t, i_size_read(inode), + f2fs_max_inline_data(inode)); if (folio_test_uptodate(folio)) - return; + return 0; f2fs_bug_on(F2FS_I_SB(inode), folio->index); - folio_zero_segment(folio, MAX_INLINE_DATA(inode), folio_size(folio)); + if (f2fs_uses_encrypted_inline_data(inode)) { + struct page *tmp_page; + void *kaddr; + int err; + + folio_zero_segment(folio, 0, folio_size(folio)); - /* Copy the whole inline data block */ - memcpy_to_folio(folio, 0, inline_data_addr(inode, ifolio), - MAX_INLINE_DATA(inode)); + /* + * Decrypt through a temporary page because inline data occupies + * only a byte range inside the inode folio. + */ + tmp_page = alloc_page(GFP_NOFS | __GFP_ZERO); + if (!tmp_page) + return -ENOMEM; + + len = round_up(len, FSCRYPT_CONTENTS_ALIGNMENT); + if (len) { + err = fscrypt_prepare_data_unit_inplace(inode); + if (err) { + __free_page(tmp_page); + return err; + } + memcpy_to_page(tmp_page, 0, inline_data_addr(inode, ifolio), + len); + err = fscrypt_crypt_data_unit_inplace(inode, tmp_page, + len, 0, 0, + false); + if (err) { + __free_page(tmp_page); + return err; + } + } + + kaddr = kmap_local_page(tmp_page); + memcpy_to_folio(folio, 0, kaddr, + min_t(loff_t, i_size_read(inode), + f2fs_max_inline_data(inode))); + kunmap_local(kaddr); + __free_page(tmp_page); + } else { + folio_zero_segment(folio, MAX_INLINE_DATA(inode), + folio_size(folio)); + /* Copy the whole inline data block */ + memcpy_to_folio(folio, 0, inline_data_addr(inode, ifolio), + MAX_INLINE_DATA(inode)); + } if (!folio_test_uptodate(folio)) folio_mark_uptodate(folio); + return 0; } void f2fs_truncate_inline_inode(struct inode *inode, struct folio *ifolio, @@ -119,6 +168,7 @@ void f2fs_truncate_inline_inode(struct inode *inode, struct folio *ifolio, int f2fs_read_inline_data(struct inode *inode, struct folio *folio) { struct folio *ifolio; + int ret = 0; ifolio = f2fs_get_inode_folio(F2FS_I_SB(inode), inode->i_ino); if (IS_ERR(ifolio)) { @@ -134,7 +184,13 @@ int f2fs_read_inline_data(struct inode *inode, struct folio *folio) if (folio->index) folio_zero_segment(folio, 0, folio_size(folio)); else - f2fs_do_read_inline_data(folio, ifolio); + ret = f2fs_do_read_inline_data(folio, ifolio); + + if (!folio->index && ret) { + f2fs_folio_put(ifolio, true); + folio_unlock(folio); + return ret; + } if (!folio_test_uptodate(folio)) folio_mark_uptodate(folio); @@ -186,7 +242,9 @@ int f2fs_convert_inline_folio(struct dnode_of_data *dn, struct folio *folio) f2fs_bug_on(F2FS_F_SB(folio), folio_test_writeback(folio)); - f2fs_do_read_inline_data(folio, dn->inode_folio); + err = f2fs_do_read_inline_data(folio, dn->inode_folio); + if (err) + return err; folio_mark_dirty(folio); /* clear dirty state */ @@ -267,6 +325,8 @@ int f2fs_write_inline_data(struct inode *inode, struct folio *folio) { struct f2fs_sb_info *sbi = F2FS_I_SB(inode); struct folio *ifolio; + void *inline_addr; + int err = 0; ifolio = f2fs_get_inode_folio(sbi, inode->i_ino); if (IS_ERR(ifolio)) @@ -280,8 +340,50 @@ int f2fs_write_inline_data(struct inode *inode, struct folio *folio) f2fs_bug_on(F2FS_I_SB(inode), folio->index); f2fs_folio_wait_writeback(ifolio, NODE, true, true); - memcpy_from_folio(inline_data_addr(inode, ifolio), - folio, 0, MAX_INLINE_DATA(inode)); + inline_addr = inline_data_addr(inode, ifolio); + + if (f2fs_uses_encrypted_inline_data(inode)) { + struct page *tmp_page; + void *kaddr; + unsigned int len = min_t(loff_t, i_size_read(inode), + f2fs_max_inline_data(inode)); + + tmp_page = alloc_page(GFP_NOFS | __GFP_ZERO); + if (!tmp_page) { + err = -ENOMEM; + goto out; + } + + len = round_up(len, FSCRYPT_CONTENTS_ALIGNMENT); + if (len) { + err = fscrypt_prepare_data_unit_inplace(inode); + if (err) { + __free_page(tmp_page); + goto out; + } + kaddr = kmap_local_page(tmp_page); + memcpy_from_folio(kaddr, folio, 0, + min_t(loff_t, i_size_read(inode), + f2fs_max_inline_data(inode))); + kunmap_local(kaddr); + err = fscrypt_crypt_data_unit_inplace(inode, tmp_page, + len, 0, 0, + true); + } + if (!err) { + memset(inline_addr, 0, MAX_INLINE_DATA(inode)); + if (len) { + kaddr = kmap_local_page(tmp_page); + memcpy(inline_addr, kaddr, len); + kunmap_local(kaddr); + } + } + __free_page(tmp_page); + if (err) + goto out; + } else { + memcpy_from_folio(inline_addr, folio, 0, MAX_INLINE_DATA(inode)); + } folio_mark_dirty(ifolio); f2fs_clear_page_cache_dirty_tag(folio); @@ -290,8 +392,9 @@ int f2fs_write_inline_data(struct inode *inode, struct folio *folio) set_inode_flag(inode, FI_DATA_EXIST); folio_clear_f2fs_inline(ifolio); +out: f2fs_folio_put(ifolio, true); - return 0; + return err; } int f2fs_recover_inline_data(struct inode *inode, struct folio *nfolio) @@ -826,7 +929,7 @@ int f2fs_inline_data_fiemap(struct inode *inode, return PTR_ERR(ifolio); f2fs_folio_wait_writeback(ifolio, NODE, true, true); } - ilen = min_t(size_t, MAX_INLINE_DATA(inode), i_size_read(inode)); + ilen = min_t(size_t, f2fs_max_inline_data(inode), i_size_read(inode)); if (start >= ilen) goto out; if (start + len < ilen) diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index c6afdbd6e1cd..9eddcde7939c 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -1549,6 +1549,18 @@ static int f2fs_check_opt_consistency(struct fs_context *fc, return -EINVAL; } + if (f2fs_sb_has_encrypted_inline_data(sbi)) { + if (!IS_ENABLED(CONFIG_F2FS_FS_ENCRYPTED_INLINE_DATA)) { + f2fs_err(sbi, + "encrypted_inline_data requires CONFIG_F2FS_FS_ENCRYPTED_INLINE_DATA"); + return -EINVAL; + } + if (!f2fs_sb_has_encrypt(sbi)) { + f2fs_err(sbi, "encrypted inline_data requires encryption feature"); + return -EINVAL; + } + } + /* * The BLKZONED feature indicates that the drive was formatted with * zone alignment optimization. This is optional for host-aware diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c index 665687244c93..600eaee75926 100644 --- a/fs/f2fs/sysfs.c +++ b/fs/f2fs/sysfs.c @@ -1399,6 +1399,9 @@ F2FS_FEATURE_RO_ATTR(pin_file); F2FS_FEATURE_RO_ATTR(linear_lookup); #endif F2FS_FEATURE_RO_ATTR(packed_ssa); +#ifdef CONFIG_F2FS_FS_ENCRYPTED_INLINE_DATA +F2FS_FEATURE_RO_ATTR(encrypted_inline_data); +#endif F2FS_FEATURE_RO_ATTR(fserror); #define ATTR_LIST(name) (&f2fs_attr_##name.attr) @@ -1567,6 +1570,9 @@ static struct attribute *f2fs_feat_attrs[] = { BASE_ATTR_LIST(linear_lookup), #endif BASE_ATTR_LIST(packed_ssa), +#ifdef CONFIG_F2FS_FS_ENCRYPTED_INLINE_DATA + BASE_ATTR_LIST(encrypted_inline_data), +#endif BASE_ATTR_LIST(fserror), NULL, }; @@ -1604,6 +1610,7 @@ F2FS_SB_FEATURE_RO_ATTR(compression, COMPRESSION); F2FS_SB_FEATURE_RO_ATTR(readonly, RO); F2FS_SB_FEATURE_RO_ATTR(device_alias, DEVICE_ALIAS); F2FS_SB_FEATURE_RO_ATTR(packed_ssa, PACKED_SSA); +F2FS_SB_FEATURE_RO_ATTR(encrypted_inline_data, ENCRYPTED_INLINE_DATA); static struct attribute *f2fs_sb_feat_attrs[] = { ATTR_LIST(sb_encryption), @@ -1622,6 +1629,7 @@ static struct attribute *f2fs_sb_feat_attrs[] = { ATTR_LIST(sb_readonly), ATTR_LIST(sb_device_alias), ATTR_LIST(sb_packed_ssa), + ATTR_LIST(sb_encrypted_inline_data), NULL, }; ATTRIBUTE_GROUPS(f2fs_sb_feat); -- 2.34.1 _______________________________________________ Linux-f2fs-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel
