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

Reply via email to