All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/3] f2fs: support encrypted inline data
@ 2026-06-02 13:40 ` LiaoYuanhong-vivo via Linux-f2fs-devel
  0 siblings, 0 replies; 11+ messages in thread
From: LiaoYuanhong-vivo @ 2026-06-02 13:40 UTC (permalink / raw)
  To: Jaegeuk Kim, Chao Yu, Jonathan Corbet, Shuah Khan, Eric Biggers,
	Theodore Y. Ts'o, open list:F2FS FILE SYSTEM, open list,
	open list:DOCUMENTATION,
	open list:FSCRYPT: FILE SYSTEM LEVEL ENCRYPTION SUPPORT
  Cc: LiaoYuanhong-vivo

F2FS currently disables inline data for encrypted regular files because the
inline payload is stored in the inode block and does not go through the
regular bio-based fscrypt path.  This wastes space for small encrypted
files on Android devices using F2FS inlinecrypt.

This series adds an encrypted_inline_data on-disk feature for F2FS.
With this feature enabled, encrypted regular files may keep small contents
in the inode block.  The inline payload is encrypted before being stored in
the inode and decrypted back into page-cache plaintext on read.

The fscrypt changes are scoped to filesystem-managed data-unit crypto.
F2FS first asks fscrypt whether the inode's key/policy supports this path.
It prepares the software transform only when encrypted inline payloads are
read or written.  Raw-key inlinecrypt support is limited to per-mode
policies (DIRECT_KEY, IV_INO_LBLK_64, and IV_INO_LBLK_32).  Per-file
inlinecrypt keys are not supported for encrypted inline data, to avoid
per-file software tfm memory growth.  Hardware-wrapped keys are not
supported for encrypted inline data.

I tested this on an Android F2FS /data device with inlinecrypt.  The
workload created 10000 encrypted files under the same fscrypt policy.

  Size  encrypted inline_data  Inline sample  fs_used_delta_kb  Avg B/file
  1K    enabled                200/200        46344             4745.63
  4K    enabled                0/200          85280             8732.67
  1K    disabled               0/200          88808             9093.94
  4K    disabled               0/200          80728             8266.55

For the 1K workload, encrypted inline data saved 42464 KiB for 10000 files,
about 4348 bytes per file, or a 47.8% reduction in filesystem used space.
A raw inode check of a sampled file confirmed that the inline region did
not contain plaintext.  The 4K control workload did not retain inline data,
as expected.

This is Android-focused, but the use case is meaningful in practice.  Real
phones can have more than 200000 encrypted files smaller than 4K under
/data.  Avoiding one 4K data block for a large fraction of those files can
save several hundred MiB, and in some cases close to 1GiB.

If keeping this limited to raw-key inlinecrypt is not the right tradeoff,
I'd appreciate suggestions on how encrypted inline data could be supported
with hardware-wrapped keys.

Changes in v2:
- Split fscrypt capability checking from software transform preparation.
- Limit raw-key inlinecrypt support to per-mode policies; per-file
  inlinecrypt keys and hardware-wrapped keys are unsupported.
- Use one data-unit helper and process inline payloads by fscrypt data-unit
  size.
- Update F2FS inline-data paths and documentation.

LiaoYuanhong-vivo (3):
  fscrypt: prepare software keys for filesystem-managed data units
  f2fs: support encrypted inline data
  Documentation: f2fs: document encrypted inline data

 Documentation/ABI/testing/sysfs-fs-f2fs |   5 +-
 Documentation/filesystems/f2fs.rst      |  34 +++++
 fs/crypto/crypto.c                      |  43 ++++++
 fs/crypto/fscrypt_private.h             |   3 +-
 fs/crypto/keysetup.c                    | 167 ++++++++++++++++++++++++
 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 ++
 include/linux/fscrypt.h                 |  24 ++++
 13 files changed, 487 insertions(+), 23 deletions(-)

-- 
2.34.1

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [f2fs-dev] [PATCH v2 0/3] f2fs: support encrypted inline data
@ 2026-06-02 13:40 ` LiaoYuanhong-vivo via Linux-f2fs-devel
  0 siblings, 0 replies; 11+ messages in thread
From: LiaoYuanhong-vivo via Linux-f2fs-devel @ 2026-06-02 13:40 UTC (permalink / raw)
  To: Jaegeuk Kim, Chao Yu, Jonathan Corbet, Shuah Khan, Eric Biggers,
	Theodore Y. Ts'o, open list:F2FS FILE SYSTEM, open list,
	open list:DOCUMENTATION,
	open list:FSCRYPT: FILE SYSTEM LEVEL ENCRYPTION SUPPORT
  Cc: LiaoYuanhong-vivo

F2FS currently disables inline data for encrypted regular files because the
inline payload is stored in the inode block and does not go through the
regular bio-based fscrypt path.  This wastes space for small encrypted
files on Android devices using F2FS inlinecrypt.

This series adds an encrypted_inline_data on-disk feature for F2FS.
With this feature enabled, encrypted regular files may keep small contents
in the inode block.  The inline payload is encrypted before being stored in
the inode and decrypted back into page-cache plaintext on read.

The fscrypt changes are scoped to filesystem-managed data-unit crypto.
F2FS first asks fscrypt whether the inode's key/policy supports this path.
It prepares the software transform only when encrypted inline payloads are
read or written.  Raw-key inlinecrypt support is limited to per-mode
policies (DIRECT_KEY, IV_INO_LBLK_64, and IV_INO_LBLK_32).  Per-file
inlinecrypt keys are not supported for encrypted inline data, to avoid
per-file software tfm memory growth.  Hardware-wrapped keys are not
supported for encrypted inline data.

I tested this on an Android F2FS /data device with inlinecrypt.  The
workload created 10000 encrypted files under the same fscrypt policy.

  Size  encrypted inline_data  Inline sample  fs_used_delta_kb  Avg B/file
  1K    enabled                200/200        46344             4745.63
  4K    enabled                0/200          85280             8732.67
  1K    disabled               0/200          88808             9093.94
  4K    disabled               0/200          80728             8266.55

For the 1K workload, encrypted inline data saved 42464 KiB for 10000 files,
about 4348 bytes per file, or a 47.8% reduction in filesystem used space.
A raw inode check of a sampled file confirmed that the inline region did
not contain plaintext.  The 4K control workload did not retain inline data,
as expected.

This is Android-focused, but the use case is meaningful in practice.  Real
phones can have more than 200000 encrypted files smaller than 4K under
/data.  Avoiding one 4K data block for a large fraction of those files can
save several hundred MiB, and in some cases close to 1GiB.

If keeping this limited to raw-key inlinecrypt is not the right tradeoff,
I'd appreciate suggestions on how encrypted inline data could be supported
with hardware-wrapped keys.

Changes in v2:
- Split fscrypt capability checking from software transform preparation.
- Limit raw-key inlinecrypt support to per-mode policies; per-file
  inlinecrypt keys and hardware-wrapped keys are unsupported.
- Use one data-unit helper and process inline payloads by fscrypt data-unit
  size.
- Update F2FS inline-data paths and documentation.

LiaoYuanhong-vivo (3):
  fscrypt: prepare software keys for filesystem-managed data units
  f2fs: support encrypted inline data
  Documentation: f2fs: document encrypted inline data

 Documentation/ABI/testing/sysfs-fs-f2fs |   5 +-
 Documentation/filesystems/f2fs.rst      |  34 +++++
 fs/crypto/crypto.c                      |  43 ++++++
 fs/crypto/fscrypt_private.h             |   3 +-
 fs/crypto/keysetup.c                    | 167 ++++++++++++++++++++++++
 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 ++
 include/linux/fscrypt.h                 |  24 ++++
 13 files changed, 487 insertions(+), 23 deletions(-)

-- 
2.34.1


_______________________________________________
Linux-f2fs-devel mailing list
Linux-f2fs-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH v2 1/3] fscrypt: prepare software keys for filesystem-managed data units
  2026-06-02 13:40 ` [f2fs-dev] " LiaoYuanhong-vivo via Linux-f2fs-devel
  (?)
@ 2026-06-02 13:41 ` LiaoYuanhong-vivo
  -1 siblings, 0 replies; 11+ messages in thread
From: LiaoYuanhong-vivo @ 2026-06-02 13:41 UTC (permalink / raw)
  To: Eric Biggers, Theodore Y. Ts'o, Jaegeuk Kim,
	open list:FSCRYPT: FILE SYSTEM LEVEL ENCRYPTION SUPPORT,
	open list
  Cc: LiaoYuanhong-vivo

Some filesystems store small data regions inside filesystem metadata rather
than submitting them through the normal bio path.  F2FS inline data is one
such case.  When encrypted file contents normally use blk-crypto, these
filesystem-managed regions still need software fscrypt handling because no
data bio is submitted for them.

Add helpers for this case.  fscrypt_supports_data_unit_inplace() lets a
filesystem check whether an inode's current key and policy can support this
path without preparing a software transform.
fscrypt_prepare_data_unit_inplace() prepares the transform when the
filesystem actually needs to read or write such a region.
fscrypt_crypt_data_unit_inplace() encrypts or decrypts an in-page region by
fscrypt data-unit size.

For raw-key inlinecrypt, support is limited to per-mode key policies
(DIRECT_KEY, IV_INO_LBLK_64, and IV_INO_LBLK_32), so the software transform
is shared per mode instead of being allocated per file.  Hardware-wrapped
keys and per-file inlinecrypt keys are not supported for this path.

Signed-off-by: LiaoYuanhong-vivo <liaoyuanhong@vivo.com>
---
Changes in v2:
- Split capability checking from software transform preparation.
- Limit raw-key inlinecrypt support to per-mode policies; reject per-file
  and hardware-wrapped keys.
- Use one direction-aware helper and process regions by fscrypt data-unit
  size.

 fs/crypto/crypto.c          |  47 ++++++++++
 fs/crypto/fscrypt_private.h |   3 +-
 fs/crypto/keysetup.c        | 178 ++++++++++++++++++++++++++++++++++++
 include/linux/fscrypt.h     |  24 ++++++
 4 files changed, 251 insertions(+), 1 deletion(-)

diff --git a/fs/crypto/crypto.c b/fs/crypto/crypto.c
index 570a2231c945..a85ea313b0d9 100644
--- a/fs/crypto/crypto.c
+++ b/fs/crypto/crypto.c
@@ -208,6 +208,53 @@ struct page *fscrypt_encrypt_pagecache_blocks(struct folio *folio,
 }
 EXPORT_SYMBOL(fscrypt_encrypt_pagecache_blocks);
 
+/**
+ * fscrypt_crypt_data_unit_inplace() - En/decrypt data units in-place
+ * @inode:   The inode to which these data units belong
+ * @page:    The page containing the data units to en/decrypt
+ * @len:     Size of the region to en/decrypt.  This must be a multiple of
+ *	     FSCRYPT_CONTENTS_ALIGNMENT.
+ * @offs:    Byte offset within @page at which the region begins
+ * @index:   Fscrypt data unit index of the start of the region
+ * @encrypt: True to encrypt, false to decrypt
+ *
+ * Return: 0 on success; -errno on failure
+ */
+int fscrypt_crypt_data_unit_inplace(const struct inode *inode,
+				    struct page *page, unsigned int len,
+				    unsigned int offs, u64 index, bool encrypt)
+{
+	const struct fscrypt_inode_info *ci = fscrypt_get_inode_info_raw(inode);
+	unsigned int du_size;
+	unsigned int i;
+	int err;
+
+	/*
+	 * Pairs with the smp_store_release() that publishes ->tfm after the
+	 * software transform has been fully initialized.
+	 */
+	if (!ci || !smp_load_acquire(&ci->ci_enc_key.tfm))
+		return -EOPNOTSUPP;
+	du_size = 1U << ci->ci_data_unit_bits;
+
+	if (WARN_ON_ONCE(len <= 0))
+		return -EINVAL;
+	if (WARN_ON_ONCE(!IS_ALIGNED(len | offs, FSCRYPT_CONTENTS_ALIGNMENT)))
+		return -EINVAL;
+
+	for (i = 0; i < len; i += du_size, index++) {
+		unsigned int todo = min(du_size, len - i);
+
+		err = fscrypt_crypt_data_unit(ci,
+				encrypt ? FS_ENCRYPT : FS_DECRYPT,
+				index, page, page, todo, offs + i);
+		if (err)
+			return err;
+	}
+	return 0;
+}
+EXPORT_SYMBOL(fscrypt_crypt_data_unit_inplace);
+
 /**
  * fscrypt_encrypt_block_inplace() - Encrypt a filesystem block in-place
  * @inode:     The inode to which this block belongs
diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h
index 8d3c278a7591..b5c0b881fd4b 100644
--- a/fs/crypto/fscrypt_private.h
+++ b/fs/crypto/fscrypt_private.h
@@ -236,7 +236,8 @@ struct fscrypt_symlink_data {
  * @tfm: crypto API transform object
  * @blk_key: key for blk-crypto
  *
- * Normally only one of the fields will be non-NULL.
+ * Most users need only one prepared form.  Inline-crypto users that also need
+ * filesystem-layer software crypto for non-bio data regions may prepare both.
  */
 struct fscrypt_prepared_key {
 	struct crypto_sync_skcipher *tfm;
diff --git a/fs/crypto/keysetup.c b/fs/crypto/keysetup.c
index ce327bfdada4..2cb629d4383c 100644
--- a/fs/crypto/keysetup.c
+++ b/fs/crypto/keysetup.c
@@ -400,6 +400,184 @@ static int fscrypt_setup_v2_file_key(struct fscrypt_inode_info *ci,
 	return 0;
 }
 
+static int fscrypt_prepare_data_unit_inplace_software_key(
+					struct fscrypt_prepared_key *prep_key,
+					const u8 *raw_key,
+					const struct fscrypt_inode_info *ci)
+{
+	struct crypto_sync_skcipher *tfm;
+
+	/* Pairs with the smp_store_release() below. */
+	if (smp_load_acquire(&prep_key->tfm))
+		return 0;
+	tfm = fscrypt_allocate_skcipher(ci->ci_mode, raw_key, ci->ci_inode);
+	if (IS_ERR(tfm))
+		return PTR_ERR(tfm);
+	/* Pairs with the smp_load_acquire() above and other ->tfm readers. */
+	smp_store_release(&prep_key->tfm, tfm);
+	return 0;
+}
+
+static int fscrypt_prepare_per_mode_data_unit_inplace_key(
+					struct fscrypt_inode_info *ci,
+					struct fscrypt_master_key *mk,
+					struct fscrypt_prepared_key *keys,
+					u8 hkdf_context, bool include_fs_uuid)
+{
+	const struct super_block *sb = ci->ci_inode->i_sb;
+	struct fscrypt_mode *mode = ci->ci_mode;
+	const u8 mode_num = mode - fscrypt_modes;
+	struct fscrypt_prepared_key *prep_key;
+	u8 mode_key[FSCRYPT_MAX_RAW_KEY_SIZE];
+	u8 hkdf_info[sizeof(mode_num) + sizeof(sb->s_uuid)];
+	unsigned int hkdf_infolen = 0;
+	int err;
+
+	if (WARN_ON_ONCE(mode_num > FSCRYPT_MODE_MAX))
+		return -EINVAL;
+
+	prep_key = &keys[mode_num];
+
+	BUILD_BUG_ON(sizeof(mode_num) != 1);
+	BUILD_BUG_ON(sizeof(sb->s_uuid) != 16);
+	BUILD_BUG_ON(sizeof(hkdf_info) != 17);
+	hkdf_info[hkdf_infolen++] = mode_num;
+	if (include_fs_uuid) {
+		memcpy(&hkdf_info[hkdf_infolen], &sb->s_uuid,
+		       sizeof(sb->s_uuid));
+		hkdf_infolen += sizeof(sb->s_uuid);
+	}
+
+	fscrypt_hkdf_expand(&mk->mk_secret.hkdf, hkdf_context, hkdf_info,
+			    hkdf_infolen, mode_key, mode->keysize);
+	err = fscrypt_prepare_data_unit_inplace_software_key(prep_key,
+							    mode_key, ci);
+	memzero_explicit(mode_key, mode->keysize);
+	if (!err)
+		ci->ci_enc_key = *prep_key;
+	return err;
+}
+
+/**
+ * fscrypt_supports_data_unit_inplace() - check data-unit crypto support
+ * @inode: an encrypted regular file inode
+ *
+ * Check whether filesystem-managed data regions can use fscrypt contents
+ * encryption for this inode.  Per-file inlinecrypt keys are intentionally
+ * unsupported to avoid per-file software tfm memory growth.  Hardware-wrapped
+ * keys are unsupported because the software contents key is not available.
+ */
+bool fscrypt_supports_data_unit_inplace(const struct inode *inode)
+{
+	struct fscrypt_inode_info *ci = fscrypt_get_inode_info_raw(inode);
+	struct fscrypt_master_key *mk;
+	u8 flags;
+	bool supported;
+
+	if (!ci)
+		return false;
+	/*
+	 * Pairs with the smp_store_release() that publishes ->tfm after the
+	 * software transform has been fully initialized.
+	 */
+	if (smp_load_acquire(&ci->ci_enc_key.tfm))
+		return true;
+	if (!fscrypt_using_inline_encryption(ci))
+		return false;
+
+	mk = ci->ci_master_key;
+	if (!mk)
+		return false;
+
+	down_read(&mk->mk_sem);
+	if (!mk->mk_present || mk->mk_secret.is_hw_wrapped ||
+	    ci->ci_policy.version != FSCRYPT_POLICY_V2) {
+		supported = false;
+		goto out;
+	}
+
+	flags = ci->ci_policy.v2.flags;
+	supported = flags & (FSCRYPT_POLICY_FLAG_DIRECT_KEY |
+			     FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 |
+			     FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32);
+out:
+	up_read(&mk->mk_sem);
+	return supported;
+}
+EXPORT_SYMBOL_GPL(fscrypt_supports_data_unit_inplace);
+
+/**
+ * fscrypt_prepare_data_unit_inplace() - prepare software data-unit crypto
+ * @inode: an encrypted regular file inode
+ *
+ * Prepare the software transform used by filesystem-managed data regions that
+ * need fscrypt contents encryption but do not go through a data bio.  If the
+ * inode already uses filesystem-layer encryption, the normal contents key is
+ * already prepared.  If the inode uses blk-crypto with a raw per-mode key, this
+ * prepares the software form of that same per-mode contents key.
+ */
+int fscrypt_prepare_data_unit_inplace(const struct inode *inode)
+{
+	struct fscrypt_inode_info *ci = fscrypt_get_inode_info_raw(inode);
+	struct fscrypt_master_key *mk;
+	u8 flags;
+	int err = 0;
+
+	if (!ci)
+		return -ENOKEY;
+	/*
+	 * Pairs with the smp_store_release() that publishes ->tfm after the
+	 * software transform has been fully initialized.
+	 */
+	if (smp_load_acquire(&ci->ci_enc_key.tfm))
+		return 0;
+	if (!fscrypt_using_inline_encryption(ci))
+		return -EOPNOTSUPP;
+
+	mk = ci->ci_master_key;
+	if (!mk)
+		return -EOPNOTSUPP;
+
+	down_read(&mk->mk_sem);
+	if (!mk->mk_present) {
+		err = -ENOKEY;
+		goto out_unlock;
+	}
+	if (mk->mk_secret.is_hw_wrapped ||
+	    ci->ci_policy.version != FSCRYPT_POLICY_V2) {
+		err = -EOPNOTSUPP;
+		goto out_unlock;
+	}
+
+	mutex_lock(&fscrypt_mode_key_setup_mutex);
+	/* Pairs with fscrypt_prepare_data_unit_inplace_software_key(). */
+	if (smp_load_acquire(&ci->ci_enc_key.tfm))
+		goto out_mutex;
+
+	flags = ci->ci_policy.v2.flags;
+	if (flags & FSCRYPT_POLICY_FLAG_DIRECT_KEY) {
+		err = fscrypt_prepare_per_mode_data_unit_inplace_key(ci, mk,
+				mk->mk_direct_keys, HKDF_CONTEXT_DIRECT_KEY,
+				false);
+	} else if (flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64) {
+		err = fscrypt_prepare_per_mode_data_unit_inplace_key(ci, mk,
+				mk->mk_iv_ino_lblk_64_keys,
+				HKDF_CONTEXT_IV_INO_LBLK_64_KEY, true);
+	} else if (flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32) {
+		err = fscrypt_prepare_per_mode_data_unit_inplace_key(ci, mk,
+				mk->mk_iv_ino_lblk_32_keys,
+				HKDF_CONTEXT_IV_INO_LBLK_32_KEY, true);
+	} else {
+		err = -EOPNOTSUPP;
+	}
+out_mutex:
+	mutex_unlock(&fscrypt_mode_key_setup_mutex);
+out_unlock:
+	up_read(&mk->mk_sem);
+	return err;
+}
+EXPORT_SYMBOL_GPL(fscrypt_prepare_data_unit_inplace);
+
 /*
  * Check whether the size of the given master key (@mk) is appropriate for the
  * encryption settings which a particular file will use (@ci).
diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h
index 54712ec61ffb..2702bf1018c1 100644
--- a/include/linux/fscrypt.h
+++ b/include/linux/fscrypt.h
@@ -346,6 +346,12 @@ void fscrypt_enqueue_decrypt_work(struct work_struct *);
 
 struct page *fscrypt_encrypt_pagecache_blocks(struct folio *folio,
 		size_t len, size_t offs, gfp_t gfp_flags);
+
+bool fscrypt_supports_data_unit_inplace(const struct inode *inode);
+int fscrypt_prepare_data_unit_inplace(const struct inode *inode);
+int fscrypt_crypt_data_unit_inplace(const struct inode *inode,
+				    struct page *page, unsigned int len,
+				    unsigned int offs, u64 index, bool encrypt);
 int fscrypt_encrypt_block_inplace(const struct inode *inode, struct page *page,
 				  unsigned int len, unsigned int offs,
 				  u64 lblk_num);
@@ -519,6 +525,24 @@ static inline struct page *fscrypt_encrypt_pagecache_blocks(struct folio *folio,
 	return ERR_PTR(-EOPNOTSUPP);
 }
 
+static inline bool fscrypt_supports_data_unit_inplace(const struct inode *inode)
+{
+	return false;
+}
+
+static inline int fscrypt_prepare_data_unit_inplace(const struct inode *inode)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline int fscrypt_crypt_data_unit_inplace(const struct inode *inode,
+						  struct page *page, unsigned int len,
+						  unsigned int offs, u64 index,
+						  bool encrypt)
+{
+	return -EOPNOTSUPP;
+}
+
 static inline int fscrypt_encrypt_block_inplace(const struct inode *inode,
 						struct page *page,
 						unsigned int len,
-- 
2.34.1

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v2 2/3] f2fs: support encrypted inline data
  2026-06-02 13:40 ` [f2fs-dev] " LiaoYuanhong-vivo via Linux-f2fs-devel
@ 2026-06-02 13:41   ` LiaoYuanhong-vivo via Linux-f2fs-devel
  -1 siblings, 0 replies; 11+ messages in thread
From: LiaoYuanhong-vivo @ 2026-06-02 13:41 UTC (permalink / raw)
  To: Jaegeuk Kim, Chao Yu, open list:F2FS FILE SYSTEM, open list
  Cc: LiaoYuanhong-vivo

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 <liaoyuanhong@vivo.com>
---
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

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [f2fs-dev] [PATCH v2 2/3] f2fs: support encrypted inline data
@ 2026-06-02 13:41   ` LiaoYuanhong-vivo via Linux-f2fs-devel
  0 siblings, 0 replies; 11+ messages in thread
From: LiaoYuanhong-vivo via Linux-f2fs-devel @ 2026-06-02 13:41 UTC (permalink / raw)
  To: Jaegeuk Kim, Chao Yu, open list:F2FS FILE SYSTEM, open list
  Cc: LiaoYuanhong-vivo

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 <liaoyuanhong@vivo.com>
---
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
Linux-f2fs-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v2 3/3] Documentation: f2fs: document encrypted inline data
  2026-06-02 13:40 ` [f2fs-dev] " LiaoYuanhong-vivo via Linux-f2fs-devel
@ 2026-06-02 13:41   ` LiaoYuanhong-vivo via Linux-f2fs-devel
  -1 siblings, 0 replies; 11+ messages in thread
From: LiaoYuanhong-vivo @ 2026-06-02 13:41 UTC (permalink / raw)
  To: Jaegeuk Kim, Chao Yu, Jonathan Corbet, Shuah Khan,
	open list:F2FS FILE SYSTEM, open list, open list:DOCUMENTATION
  Cc: LiaoYuanhong-vivo

Document the F2FS encrypted_inline_data feature, including the on-disk
feature requirement, the CONFIG_F2FS_FS_ENCRYPTED_INLINE_DATA dependency,
how inline payloads are encrypted and decrypted, and the truncate behavior.

Also document the supported key combinations.  Files using filesystem-layer
encryption reuse the normal software transform.  Raw-key inlinecrypt is
supported only for per-mode policies, while per-file inlinecrypt keys and
hardware-wrapped keys are not supported for encrypted inline data.

List encrypted_inline_data in the supported F2FS feature sysfs
documentation.

Signed-off-by: LiaoYuanhong-vivo <liaoyuanhong@vivo.com>
---
Changes in v2:
- Document raw-key inlinecrypt support as limited to per-mode policies.
- Document unsupported per-file inlinecrypt and hardware-wrapped key cases.
- Clarify when fscrypt prepares the software transform for inline payloads.

 Documentation/ABI/testing/sysfs-fs-f2fs |  5 ++--
 Documentation/filesystems/f2fs.rst      | 34 +++++++++++++++++++++++++
 2 files changed, 37 insertions(+), 2 deletions(-)

diff --git a/Documentation/ABI/testing/sysfs-fs-f2fs b/Documentation/ABI/testing/sysfs-fs-f2fs
index 27d5e88facbe..dad483fb2fc1 100644
--- a/Documentation/ABI/testing/sysfs-fs-f2fs
+++ b/Documentation/ABI/testing/sysfs-fs-f2fs
@@ -258,7 +258,8 @@ Description:	Expand /sys/fs/f2fs/<disk>/features to meet sysfs rule.
 		encryption, block_zoned (aka blkzoned), extra_attr,
 		project_quota (aka projquota), inode_checksum,
 		flexible_inline_xattr, quota_ino, inode_crtime, lost_found,
-		verity, sb_checksum, casefold, readonly, compression.
+		verity, sb_checksum, casefold, readonly, compression,
+		encrypted_inline_data.
 		Note that, pin_file is moved into /sys/fs/f2fs/features/.
 
 What:		/sys/fs/f2fs/features/
@@ -271,7 +272,7 @@ Description:	Shows all enabled kernel features.
 		inode_crtime, lost_found, verity, sb_checksum,
 		casefold, readonly, compression, test_dummy_encryption_v2,
 		atomic_write, pin_file, encrypted_casefold, linear_lookup,
-		fserror.
+		fserror, encrypted_inline_data.
 
 What:		/sys/fs/f2fs/<disk>/inject_rate
 Date:		May 2016
diff --git a/Documentation/filesystems/f2fs.rst b/Documentation/filesystems/f2fs.rst
index 5bc37a1c4e51..feffad89db01 100644
--- a/Documentation/filesystems/f2fs.rst
+++ b/Documentation/filesystems/f2fs.rst
@@ -420,6 +420,40 @@ lookup_mode=%s		 Control the directory lookup behavior for casefolded
 			     ================== ========================================
 ======================== ============================================================
 
+Encrypted inline data
+=====================
+
+F2FS normally disables inline data for encrypted regular files, since inline
+data is stored inside the inode block and does not pass through the regular
+block I/O path.  When a filesystem is formatted with the encrypted_inline_data
+feature, encrypted regular files may keep small file contents in the inode
+block.  The inline payload is encrypted with fscrypt contents-key semantics
+before it is written to the inode, and it is decrypted back to page-cache
+plaintext when it is read.
+
+This feature requires the encrypt feature on disk and kernel support for
+CONFIG_F2FS_FS_ENCRYPTED_INLINE_DATA.  It is intended to be used together with
+the inline_data mount option.  Files using filesystem-layer encryption reuse the
+normal software contents-key transform.  When the normal encrypted file
+contents path uses blk-crypto with a raw per-mode key, fscrypt can prepare a
+software contents-key transform when the filesystem-managed inline payload is
+read or written.
+Per-file inlinecrypt keys are not supported for encrypted inline data, to avoid
+per-file software transform memory growth.  Hardware-wrapped keys are not
+supported for encrypted inline data, so F2FS does not create encrypted inline
+payloads for such files and existing unsupported inline payloads fail rather
+than being interpreted with the wrong key.
+
+Encrypted inline data is stored in fscrypt contents-aligned units.  Therefore,
+the maximum plaintext size that can stay inline may be slightly smaller than the
+ordinary inline data capacity.  If an encrypted inline-data file is truncated
+from a non-zero offset, F2FS first converts the inline payload to normal data
+blocks and then applies the truncate operation.
+
+Recovery copies inline payloads as on-disk bytes.  Encryption and decryption are
+performed only when moving data between the inode inline area and page-cache
+plaintext.
+
 Debugfs Entries
 ===============
 
-- 
2.34.1

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [f2fs-dev] [PATCH v2 3/3] Documentation: f2fs: document encrypted inline data
@ 2026-06-02 13:41   ` LiaoYuanhong-vivo via Linux-f2fs-devel
  0 siblings, 0 replies; 11+ messages in thread
From: LiaoYuanhong-vivo via Linux-f2fs-devel @ 2026-06-02 13:41 UTC (permalink / raw)
  To: Jaegeuk Kim, Chao Yu, Jonathan Corbet, Shuah Khan,
	open list:F2FS FILE SYSTEM, open list, open list:DOCUMENTATION
  Cc: LiaoYuanhong-vivo

Document the F2FS encrypted_inline_data feature, including the on-disk
feature requirement, the CONFIG_F2FS_FS_ENCRYPTED_INLINE_DATA dependency,
how inline payloads are encrypted and decrypted, and the truncate behavior.

Also document the supported key combinations.  Files using filesystem-layer
encryption reuse the normal software transform.  Raw-key inlinecrypt is
supported only for per-mode policies, while per-file inlinecrypt keys and
hardware-wrapped keys are not supported for encrypted inline data.

List encrypted_inline_data in the supported F2FS feature sysfs
documentation.

Signed-off-by: LiaoYuanhong-vivo <liaoyuanhong@vivo.com>
---
Changes in v2:
- Document raw-key inlinecrypt support as limited to per-mode policies.
- Document unsupported per-file inlinecrypt and hardware-wrapped key cases.
- Clarify when fscrypt prepares the software transform for inline payloads.

 Documentation/ABI/testing/sysfs-fs-f2fs |  5 ++--
 Documentation/filesystems/f2fs.rst      | 34 +++++++++++++++++++++++++
 2 files changed, 37 insertions(+), 2 deletions(-)

diff --git a/Documentation/ABI/testing/sysfs-fs-f2fs b/Documentation/ABI/testing/sysfs-fs-f2fs
index 27d5e88facbe..dad483fb2fc1 100644
--- a/Documentation/ABI/testing/sysfs-fs-f2fs
+++ b/Documentation/ABI/testing/sysfs-fs-f2fs
@@ -258,7 +258,8 @@ Description:	Expand /sys/fs/f2fs/<disk>/features to meet sysfs rule.
 		encryption, block_zoned (aka blkzoned), extra_attr,
 		project_quota (aka projquota), inode_checksum,
 		flexible_inline_xattr, quota_ino, inode_crtime, lost_found,
-		verity, sb_checksum, casefold, readonly, compression.
+		verity, sb_checksum, casefold, readonly, compression,
+		encrypted_inline_data.
 		Note that, pin_file is moved into /sys/fs/f2fs/features/.
 
 What:		/sys/fs/f2fs/features/
@@ -271,7 +272,7 @@ Description:	Shows all enabled kernel features.
 		inode_crtime, lost_found, verity, sb_checksum,
 		casefold, readonly, compression, test_dummy_encryption_v2,
 		atomic_write, pin_file, encrypted_casefold, linear_lookup,
-		fserror.
+		fserror, encrypted_inline_data.
 
 What:		/sys/fs/f2fs/<disk>/inject_rate
 Date:		May 2016
diff --git a/Documentation/filesystems/f2fs.rst b/Documentation/filesystems/f2fs.rst
index 5bc37a1c4e51..feffad89db01 100644
--- a/Documentation/filesystems/f2fs.rst
+++ b/Documentation/filesystems/f2fs.rst
@@ -420,6 +420,40 @@ lookup_mode=%s		 Control the directory lookup behavior for casefolded
 			     ================== ========================================
 ======================== ============================================================
 
+Encrypted inline data
+=====================
+
+F2FS normally disables inline data for encrypted regular files, since inline
+data is stored inside the inode block and does not pass through the regular
+block I/O path.  When a filesystem is formatted with the encrypted_inline_data
+feature, encrypted regular files may keep small file contents in the inode
+block.  The inline payload is encrypted with fscrypt contents-key semantics
+before it is written to the inode, and it is decrypted back to page-cache
+plaintext when it is read.
+
+This feature requires the encrypt feature on disk and kernel support for
+CONFIG_F2FS_FS_ENCRYPTED_INLINE_DATA.  It is intended to be used together with
+the inline_data mount option.  Files using filesystem-layer encryption reuse the
+normal software contents-key transform.  When the normal encrypted file
+contents path uses blk-crypto with a raw per-mode key, fscrypt can prepare a
+software contents-key transform when the filesystem-managed inline payload is
+read or written.
+Per-file inlinecrypt keys are not supported for encrypted inline data, to avoid
+per-file software transform memory growth.  Hardware-wrapped keys are not
+supported for encrypted inline data, so F2FS does not create encrypted inline
+payloads for such files and existing unsupported inline payloads fail rather
+than being interpreted with the wrong key.
+
+Encrypted inline data is stored in fscrypt contents-aligned units.  Therefore,
+the maximum plaintext size that can stay inline may be slightly smaller than the
+ordinary inline data capacity.  If an encrypted inline-data file is truncated
+from a non-zero offset, F2FS first converts the inline payload to normal data
+blocks and then applies the truncate operation.
+
+Recovery copies inline payloads as on-disk bytes.  Encryption and decryption are
+performed only when moving data between the inode inline area and page-cache
+plaintext.
+
 Debugfs Entries
 ===============
 
-- 
2.34.1


_______________________________________________
Linux-f2fs-devel mailing list
Linux-f2fs-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH v2 0/3] f2fs: support encrypted inline data
  2026-06-02 13:40 ` [f2fs-dev] " LiaoYuanhong-vivo via Linux-f2fs-devel
@ 2026-06-11 12:50   ` LiaoYuanhong-vivo via Linux-f2fs-devel
  -1 siblings, 0 replies; 11+ messages in thread
From: LiaoYuanhong-vivo @ 2026-06-11 12:50 UTC (permalink / raw)
  To: ebiggers
  Cc: chao, corbet, jaegeuk, linux-doc, linux-f2fs-devel, linux-fscrypt,
	linux-kernel, skhan, tytso, liaoyuanhong

Hi,

Gentle ping on this series.

v2 tries to address the previous concerns by avoiding per-file software
tfm growth, preparing the software transform lazily, and explicitly
disabling unsupported key combinations.

The main remaining limitation is hardware-wrapped keys. If this makes
the feature unlikely to be accepted, please let me know. Otherwise, I
would appreciate any review comments on the current direction.

If maintainers have any feasible direction in mind, I would also
appreciate hearing it.

Thanks,
Liao Yuanhong

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [f2fs-dev] [PATCH v2 0/3] f2fs: support encrypted inline data
@ 2026-06-11 12:50   ` LiaoYuanhong-vivo via Linux-f2fs-devel
  0 siblings, 0 replies; 11+ messages in thread
From: LiaoYuanhong-vivo via Linux-f2fs-devel @ 2026-06-11 12:50 UTC (permalink / raw)
  To: ebiggers
  Cc: corbet, tytso, liaoyuanhong, linux-doc, linux-kernel,
	linux-f2fs-devel, linux-fscrypt, skhan, jaegeuk

Hi,

Gentle ping on this series.

v2 tries to address the previous concerns by avoiding per-file software
tfm growth, preparing the software transform lazily, and explicitly
disabling unsupported key combinations.

The main remaining limitation is hardware-wrapped keys. If this makes
the feature unlikely to be accepted, please let me know. Otherwise, I
would appreciate any review comments on the current direction.

If maintainers have any feasible direction in mind, I would also
appreciate hearing it.

Thanks,
Liao Yuanhong


_______________________________________________
Linux-f2fs-devel mailing list
Linux-f2fs-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v2 0/3] f2fs: support encrypted inline data
  2026-06-11 12:50   ` [f2fs-dev] " LiaoYuanhong-vivo via Linux-f2fs-devel
@ 2026-06-15  3:04     ` Jaegeuk Kim via Linux-f2fs-devel
  -1 siblings, 0 replies; 11+ messages in thread
From: Jaegeuk Kim @ 2026-06-15  3:04 UTC (permalink / raw)
  To: LiaoYuanhong-vivo
  Cc: ebiggers, chao, corbet, linux-doc, linux-f2fs-devel,
	linux-fscrypt, linux-kernel, skhan, tytso

On 06/11, LiaoYuanhong-vivo wrote:
> Hi,
> 
> Gentle ping on this series.
> 
> v2 tries to address the previous concerns by avoiding per-file software
> tfm growth, preparing the software transform lazily, and explicitly
> disabling unsupported key combinations.
> 
> The main remaining limitation is hardware-wrapped keys. If this makes
> the feature unlikely to be accepted, please let me know. Otherwise, I
> would appreciate any review comments on the current direction.

Yeah, that'd be a big win, if we have the hardware-wrapped key support. By
any chance, can you add it in the patch set?

> 
> If maintainers have any feasible direction in mind, I would also
> appreciate hearing it.
> 
> Thanks,
> Liao Yuanhong

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [f2fs-dev] [PATCH v2 0/3] f2fs: support encrypted inline data
@ 2026-06-15  3:04     ` Jaegeuk Kim via Linux-f2fs-devel
  0 siblings, 0 replies; 11+ messages in thread
From: Jaegeuk Kim via Linux-f2fs-devel @ 2026-06-15  3:04 UTC (permalink / raw)
  To: LiaoYuanhong-vivo
  Cc: corbet, tytso, linux-doc, linux-kernel, linux-f2fs-devel,
	ebiggers, linux-fscrypt, skhan

On 06/11, LiaoYuanhong-vivo wrote:
> Hi,
> 
> Gentle ping on this series.
> 
> v2 tries to address the previous concerns by avoiding per-file software
> tfm growth, preparing the software transform lazily, and explicitly
> disabling unsupported key combinations.
> 
> The main remaining limitation is hardware-wrapped keys. If this makes
> the feature unlikely to be accepted, please let me know. Otherwise, I
> would appreciate any review comments on the current direction.

Yeah, that'd be a big win, if we have the hardware-wrapped key support. By
any chance, can you add it in the patch set?

> 
> If maintainers have any feasible direction in mind, I would also
> appreciate hearing it.
> 
> Thanks,
> Liao Yuanhong


_______________________________________________
Linux-f2fs-devel mailing list
Linux-f2fs-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2026-06-15  3:04 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-02 13:40 [PATCH v2 0/3] f2fs: support encrypted inline data LiaoYuanhong-vivo
2026-06-02 13:40 ` [f2fs-dev] " LiaoYuanhong-vivo via Linux-f2fs-devel
2026-06-02 13:41 ` [PATCH v2 1/3] fscrypt: prepare software keys for filesystem-managed data units LiaoYuanhong-vivo
2026-06-02 13:41 ` [PATCH v2 2/3] f2fs: support encrypted inline data LiaoYuanhong-vivo
2026-06-02 13:41   ` [f2fs-dev] " LiaoYuanhong-vivo via Linux-f2fs-devel
2026-06-02 13:41 ` [PATCH v2 3/3] Documentation: f2fs: document " LiaoYuanhong-vivo
2026-06-02 13:41   ` [f2fs-dev] " LiaoYuanhong-vivo via Linux-f2fs-devel
2026-06-11 12:50 ` [PATCH v2 0/3] f2fs: support " LiaoYuanhong-vivo
2026-06-11 12:50   ` [f2fs-dev] " LiaoYuanhong-vivo via Linux-f2fs-devel
2026-06-15  3:04   ` Jaegeuk Kim
2026-06-15  3:04     ` [f2fs-dev] " Jaegeuk Kim via Linux-f2fs-devel

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.