* [PATCH 0/3] f2fs: support encrypted inline data
@ 2026-05-13 10:04 LiaoYuanhong-vivo
2026-05-13 10:04 ` [PATCH 1/3] fscrypt: prepare software keys for filesystem-managed data units LiaoYuanhong-vivo
2026-05-15 18:41 ` [PATCH 0/3] f2fs: support encrypted inline data Eric Biggers
0 siblings, 2 replies; 3+ messages in thread
From: LiaoYuanhong-vivo @ 2026-05-13 10:04 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: Liao Yuanhong
From: Liao Yuanhong <liaoyuanhong@vivo.com>
F2FS currently avoids inline data for encrypted regular files. This is
because inline data is stored in the inode block, outside the regular
bio-based data path where fscrypt and blk-crypto normally operate.
As a result, devices that enable blk-crypto for encrypted file contents
cannot use F2FS inline data for encrypted regular files, which wastes
space for small files.
This series adds support for keeping small encrypted regular-file
contents as inline data. The f2fs side defines a new on-disk feature,
encrypted_inline_data, under which inline payloads of encrypted regular
files are interpreted as ciphertext. The payload is encrypted before
being stored in the inode block and decrypted back into page-cache
plaintext on read.
The fscrypt side prepares a software contents-key transform even when
normal file contents use blk-crypto, so filesystems can encrypt
filesystem-managed data regions that do not go through bio submission.
The new fscrypt helper operates on fscrypt data units and leaves the
filesystem responsible for deciding which filesystem-managed byte ranges
need this treatment.
The software crypto operation is limited to the inline payload. Since
these files are small enough to remain inline, the expected read/write
performance difference between hardware and software crypto is small,
while the space saving from keeping the data inline is significant.
The feature is guarded by CONFIG_F2FS_FS_ENCRYPTED_INLINE_DATA and by the
F2FS encrypted_inline_data on-disk feature bit. Filesystems with this
feature set are rejected if the kernel lacks the config option.
Hardware-wrapped keys are not supported by this initial version. I would
like to discuss whether this feature should remain disabled for
hardware-wrapped keys, or whether there is an acceptable way to support the
combination in the future.
The f2fs-tools support for formatting filesystems with this feature will be
submitted separately.
Basic testing passed. Encrypted small files can be kept as inline data,
and read/write verification succeeded.
Liao Yuanhong (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 | 27 ++++++
fs/crypto/crypto.c | 63 +++++++++++++
fs/crypto/fscrypt_private.h | 3 +-
fs/crypto/keysetup.c | 59 +++++++++---
fs/f2fs/Kconfig | 14 +++
fs/f2fs/data.c | 8 +-
fs/f2fs/f2fs.h | 37 +++++++-
fs/f2fs/file.c | 24 ++++-
fs/f2fs/inline.c | 119 +++++++++++++++++++++---
fs/f2fs/super.c | 12 +++
fs/f2fs/sysfs.c | 8 ++
include/linux/fscrypt.h | 28 ++++++
13 files changed, 370 insertions(+), 37 deletions(-)
--
2.34.1
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH 1/3] fscrypt: prepare software keys for filesystem-managed data units
2026-05-13 10:04 [PATCH 0/3] f2fs: support encrypted inline data LiaoYuanhong-vivo
@ 2026-05-13 10:04 ` LiaoYuanhong-vivo
2026-05-15 18:41 ` [PATCH 0/3] f2fs: support encrypted inline data Eric Biggers
1 sibling, 0 replies; 3+ messages in thread
From: LiaoYuanhong-vivo @ 2026-05-13 10:04 UTC (permalink / raw)
To: Eric Biggers, Theodore Y. Ts'o, Jaegeuk Kim,
open list:FSCRYPT: FILE SYSTEM LEVEL ENCRYPTION SUPPORT,
open list
Cc: Liao Yuanhong
From: Liao Yuanhong <liaoyuanhong@vivo.com>
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 an encrypted file uses blk-crypto, these regions still
need software fscrypt handling because no data bio is submitted for them.
Add fscrypt_encrypt_data_unit_inplace() and
fscrypt_decrypt_data_unit_inplace(). They use the same data-unit crypto
path as fscrypt_encrypt_block_inplace() and
fscrypt_decrypt_block_inplace(), but take a data-unit index instead of a
filesystem logical block number.
Also add fscrypt_inode_supports_data_unit_inplace() so filesystems can
check whether an inode has a software transform available for this path.
Factor the software skcipher setup into fscrypt_prepare_software_key().
The existing fscrypt_prepare_key() path now reuses it, and inline-crypto
key setup can use it in addition to preparing the blk-crypto key. This
lets an inline-crypto inode keep its normal blk-crypto contents path while
also having a software transform for filesystem-managed data units.
Signed-off-by: Liao Yuanhong <liaoyuanhong@vivo.com>
---
fs/crypto/crypto.c | 63 +++++++++++++++++++++++++++++++++++++
fs/crypto/fscrypt_private.h | 3 +-
fs/crypto/keysetup.c | 59 +++++++++++++++++++++++++---------
include/linux/fscrypt.h | 28 +++++++++++++++++
4 files changed, 138 insertions(+), 15 deletions(-)
diff --git a/fs/crypto/crypto.c b/fs/crypto/crypto.c
index 570a2231c945..c43acbc8b4ea 100644
--- a/fs/crypto/crypto.c
+++ b/fs/crypto/crypto.c
@@ -208,6 +208,44 @@ struct page *fscrypt_encrypt_pagecache_blocks(struct folio *folio,
}
EXPORT_SYMBOL(fscrypt_encrypt_pagecache_blocks);
+/**
+ * fscrypt_encrypt_data_unit_inplace() - Encrypt a data unit in-place
+ * @inode: The inode to which this data unit belongs
+ * @page: The page containing the data unit to encrypt
+ * @len: Size of data unit to encrypt. This must be a multiple of
+ * FSCRYPT_CONTENTS_ALIGNMENT.
+ * @offs: Byte offset within @page at which the data unit begins
+ * @index: Fscrypt data unit index within the file
+ *
+ * Return: 0 on success; -errno on failure
+ */
+int fscrypt_encrypt_data_unit_inplace(const struct inode *inode,
+ struct page *page, unsigned int len,
+ unsigned int offs, u64 index)
+{
+ const struct fscrypt_inode_info *ci = fscrypt_get_inode_info_raw(inode);
+
+ if (!fscrypt_inode_supports_data_unit_inplace(inode))
+ return -EOPNOTSUPP;
+
+ return fscrypt_crypt_data_unit(ci, FS_ENCRYPT, index, page, page, len,
+ offs);
+}
+EXPORT_SYMBOL(fscrypt_encrypt_data_unit_inplace);
+
+bool fscrypt_inode_supports_data_unit_inplace(const struct inode *inode)
+{
+ const struct fscrypt_inode_info *ci = fscrypt_get_inode_info_raw(inode);
+
+ if (!IS_ENABLED(CONFIG_FS_ENCRYPTION_INLINE_CRYPT))
+ return false;
+ if (!ci)
+ return false;
+ /* pairs with smp_store_release() in fscrypt_prepare_software_key() */
+ return smp_load_acquire(&ci->ci_enc_key.tfm);
+}
+EXPORT_SYMBOL(fscrypt_inode_supports_data_unit_inplace);
+
/**
* fscrypt_encrypt_block_inplace() - Encrypt a filesystem block in-place
* @inode: The inode to which this block belongs
@@ -282,6 +320,31 @@ int fscrypt_decrypt_pagecache_blocks(struct folio *folio, size_t len,
}
EXPORT_SYMBOL(fscrypt_decrypt_pagecache_blocks);
+/**
+ * fscrypt_decrypt_data_unit_inplace() - Decrypt a data unit in-place
+ * @inode: The inode to which this data unit belongs
+ * @page: The page containing the data unit to decrypt
+ * @len: Size of data unit to decrypt. This must be a multiple of
+ * FSCRYPT_CONTENTS_ALIGNMENT.
+ * @offs: Byte offset within @page at which the data unit begins
+ * @index: Fscrypt data unit index within the file
+ *
+ * Return: 0 on success; -errno on failure
+ */
+int fscrypt_decrypt_data_unit_inplace(const struct inode *inode,
+ struct page *page, unsigned int len,
+ unsigned int offs, u64 index)
+{
+ const struct fscrypt_inode_info *ci = fscrypt_get_inode_info_raw(inode);
+
+ if (!fscrypt_inode_supports_data_unit_inplace(inode))
+ return -EOPNOTSUPP;
+
+ return fscrypt_crypt_data_unit(ci, FS_DECRYPT, index, page, page, len,
+ offs);
+}
+EXPORT_SYMBOL(fscrypt_decrypt_data_unit_inplace);
+
/**
* fscrypt_decrypt_block_inplace() - Decrypt 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..716911b97d8e 100644
--- a/fs/crypto/keysetup.c
+++ b/fs/crypto/keysetup.c
@@ -144,6 +144,23 @@ fscrypt_allocate_skcipher(struct fscrypt_mode *mode, const u8 *raw_key,
return ERR_PTR(err);
}
+static int fscrypt_prepare_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 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 smp_load_acquire() above */
+ smp_store_release(&prep_key->tfm, tfm);
+ return 0;
+}
+
/*
* Prepare the crypto transform object or blk-crypto key in @prep_key, given the
* raw key, encryption mode (@ci->ci_mode), flag indicating which encryption
@@ -153,24 +170,12 @@ fscrypt_allocate_skcipher(struct fscrypt_mode *mode, const u8 *raw_key,
int fscrypt_prepare_key(struct fscrypt_prepared_key *prep_key,
const u8 *raw_key, const struct fscrypt_inode_info *ci)
{
- struct crypto_sync_skcipher *tfm;
-
if (fscrypt_using_inline_encryption(ci))
return fscrypt_prepare_inline_crypt_key(prep_key, raw_key,
ci->ci_mode->keysize,
false, ci);
- 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() in fscrypt_is_key_prepared().
- * I.e., here we publish ->tfm with a RELEASE barrier so that
- * concurrent tasks can ACQUIRE it. Note that this concurrency is only
- * possible for per-mode keys, not for per-file keys.
- */
- smp_store_release(&prep_key->tfm, tfm);
- return 0;
+ return fscrypt_prepare_software_key(prep_key, raw_key, ci);
}
/* Destroy a crypto transform object and/or blk-crypto key. */
@@ -190,6 +195,20 @@ int fscrypt_set_per_file_enc_key(struct fscrypt_inode_info *ci,
return fscrypt_prepare_key(&ci->ci_enc_key, raw_key, ci);
}
+static int
+fscrypt_prepare_inline_crypt_and_software_key(struct fscrypt_prepared_key *prep_key,
+ const u8 *raw_key,
+ const struct fscrypt_inode_info *ci)
+{
+ int err;
+
+ err = fscrypt_prepare_software_key(prep_key, raw_key, ci);
+ if (err)
+ return err;
+ return fscrypt_prepare_inline_crypt_key(prep_key, raw_key,
+ ci->ci_mode->keysize, false, ci);
+}
+
static int setup_per_mode_enc_key(struct fscrypt_inode_info *ci,
struct fscrypt_master_key *mk,
struct fscrypt_prepared_key *keys,
@@ -255,7 +274,16 @@ static int setup_per_mode_enc_key(struct fscrypt_inode_info *ci,
}
fscrypt_hkdf_expand(&mk->mk_secret.hkdf, hkdf_context, hkdf_info,
hkdf_infolen, mode_key, mode->keysize);
- err = fscrypt_prepare_key(prep_key, mode_key, ci);
+ if (!use_hw_wrapped_key && fscrypt_using_inline_encryption(ci)) {
+ /*
+ * Filesystem-managed regions such as F2FS inline_data need the
+ * same contents key as a software tfm.
+ */
+ err = fscrypt_prepare_inline_crypt_and_software_key(prep_key,
+ mode_key, ci);
+ } else {
+ err = fscrypt_prepare_key(prep_key, mode_key, ci);
+ }
memzero_explicit(mode_key, mode->keysize);
if (err)
goto out_unlock;
@@ -381,6 +409,7 @@ static int fscrypt_setup_v2_file_key(struct fscrypt_inode_info *ci,
FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32) {
err = fscrypt_setup_iv_ino_lblk_32_key(ci, mk);
} else {
+ struct fscrypt_prepared_key *prep_key = &ci->ci_enc_key;
u8 derived_key[FSCRYPT_MAX_RAW_KEY_SIZE];
fscrypt_hkdf_expand(&mk->mk_secret.hkdf,
@@ -388,6 +417,8 @@ static int fscrypt_setup_v2_file_key(struct fscrypt_inode_info *ci,
ci->ci_nonce, FSCRYPT_FILE_NONCE_SIZE,
derived_key, ci->ci_mode->keysize);
err = fscrypt_set_per_file_enc_key(ci, derived_key);
+ if (!err && fscrypt_using_inline_encryption(ci))
+ err = fscrypt_prepare_software_key(prep_key, derived_key, ci);
memzero_explicit(derived_key, ci->ci_mode->keysize);
}
if (err)
diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h
index 54712ec61ffb..3762a7526fcc 100644
--- a/include/linux/fscrypt.h
+++ b/include/linux/fscrypt.h
@@ -346,12 +346,20 @@ 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);
+
+int fscrypt_encrypt_data_unit_inplace(const struct inode *inode,
+ struct page *page, unsigned int len,
+ unsigned int offs, u64 index);
+bool fscrypt_inode_supports_data_unit_inplace(const struct inode *inode);
int fscrypt_encrypt_block_inplace(const struct inode *inode, struct page *page,
unsigned int len, unsigned int offs,
u64 lblk_num);
int fscrypt_decrypt_pagecache_blocks(struct folio *folio, size_t len,
size_t offs);
+int fscrypt_decrypt_data_unit_inplace(const struct inode *inode,
+ struct page *page, unsigned int len,
+ unsigned int offs, u64 index);
int fscrypt_decrypt_block_inplace(const struct inode *inode, struct page *page,
unsigned int len, unsigned int offs,
u64 lblk_num);
@@ -519,6 +527,19 @@ static inline struct page *fscrypt_encrypt_pagecache_blocks(struct folio *folio,
return ERR_PTR(-EOPNOTSUPP);
}
+static inline int fscrypt_encrypt_data_unit_inplace(const struct inode *inode,
+ struct page *page, unsigned int len,
+ unsigned int offs, u64 index)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline bool
+fscrypt_inode_supports_data_unit_inplace(const struct inode *inode)
+{
+ return false;
+}
+
static inline int fscrypt_encrypt_block_inplace(const struct inode *inode,
struct page *page,
unsigned int len,
@@ -533,6 +554,13 @@ static inline int fscrypt_decrypt_pagecache_blocks(struct folio *folio,
return -EOPNOTSUPP;
}
+static inline int fscrypt_decrypt_data_unit_inplace(const struct inode *inode,
+ struct page *page, unsigned int len,
+ unsigned int offs, u64 index)
+{
+ return -EOPNOTSUPP;
+}
+
static inline int fscrypt_decrypt_block_inplace(const struct inode *inode,
struct page *page,
unsigned int len,
--
2.34.1
^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [PATCH 0/3] f2fs: support encrypted inline data
2026-05-13 10:04 [PATCH 0/3] f2fs: support encrypted inline data LiaoYuanhong-vivo
2026-05-13 10:04 ` [PATCH 1/3] fscrypt: prepare software keys for filesystem-managed data units LiaoYuanhong-vivo
@ 2026-05-15 18:41 ` Eric Biggers
1 sibling, 0 replies; 3+ messages in thread
From: Eric Biggers @ 2026-05-15 18:41 UTC (permalink / raw)
To: LiaoYuanhong-vivo
Cc: Jaegeuk Kim, Chao Yu, Jonathan Corbet, Shuah Khan,
Theodore Y. Ts'o, open list:F2FS FILE SYSTEM, open list,
open list:DOCUMENTATION,
open list:FSCRYPT: FILE SYSTEM LEVEL ENCRYPTION SUPPORT
On Wed, May 13, 2026 at 06:04:27PM +0800, LiaoYuanhong-vivo wrote:
> From: Liao Yuanhong <liaoyuanhong@vivo.com>
>
> F2FS currently avoids inline data for encrypted regular files. This is
> because inline data is stored in the inode block, outside the regular
> bio-based data path where fscrypt and blk-crypto normally operate.
> As a result, devices that enable blk-crypto for encrypted file contents
> cannot use F2FS inline data for encrypted regular files, which wastes
> space for small files.
>
> This series adds support for keeping small encrypted regular-file
> contents as inline data. The f2fs side defines a new on-disk feature,
> encrypted_inline_data, under which inline payloads of encrypted regular
> files are interpreted as ciphertext. The payload is encrypted before
> being stored in the inode block and decrypted back into page-cache
> plaintext on read.
>
> The fscrypt side prepares a software contents-key transform even when
> normal file contents use blk-crypto, so filesystems can encrypt
> filesystem-managed data regions that do not go through bio submission.
> The new fscrypt helper operates on fscrypt data units and leaves the
> filesystem responsible for deciding which filesystem-managed byte ranges
> need this treatment.
>
> The software crypto operation is limited to the inline payload. Since
> these files are small enough to remain inline, the expected read/write
> performance difference between hardware and software crypto is small,
> while the space saving from keeping the data inline is significant.
>
> The feature is guarded by CONFIG_F2FS_FS_ENCRYPTED_INLINE_DATA and by the
> F2FS encrypted_inline_data on-disk feature bit. Filesystems with this
> feature set are rejected if the kernel lacks the config option.
>
> Hardware-wrapped keys are not supported by this initial version. I would
> like to discuss whether this feature should remain disabled for
> hardware-wrapped keys, or whether there is an acceptable way to support the
> combination in the future.
>
> The f2fs-tools support for formatting filesystems with this feature will be
> submitted separately.
>
> Basic testing passed. Encrypted small files can be kept as inline data,
> and read/write verification succeeded.
Honestly, I'm not convinced this is worth the complexity and the
additional memory use.
First, it works only in the combination: 'f2fs && inlinecrypt &&
!hw_wrapped_keys'. That really limits how many users would use this.
'f2fs && inlinecrypt' de facto targets it to Android devices rather than
"regular" Linux systems. But at the same time, the "best practice" on
such devices is to use HW-wrapped keys, which has already been widely
adopted. So this would be useful only on devices where the SoC doesn't
support HW-wrapped keys. Its usefulness will go away when support for
HW-wrapped keys is added.
Second, in the per-file key case this makes every file use an additional
1 KiB of memory or so (assuming AES-XTS) to hold the "software key",
just in case the file ever has inline data. That seems problematic, and
maybe not a great direction to be going in right now, given the ongoing
RAM shortage.
There also seem to be quite a few bugs/issues. Sashiko found quite a
few
(https://sashiko.dev/#/message/20260513100431.299904-1-liaoyuanhong%40vivo.com).
But just from a quick readthrough, anything that calls
fscrypt_is_key_prepared() seems to be broken now, as that function isn't
aware that both fields of fscrypt_prepared_key can be needed.
I'm also not seeing what differentiates the new
fscrypt_{en,decrypt}_data_unit_inplace() from the existing
fscrypt_{en,decrypt}_block_inplace(). They seem redundant.
There's already a lot of complexity in fscrypt, with the different
settings and the different ways the filesystems do en/decryption. With
this, plus the concurrent work to add support for extent-based
encryption (for btrfs), it's really quite hard to keep track of
everything. So I have to wonder if this patchset is really worth it.
So, overall, I think this would need a bit more work. But also I'm
wondering if it's actually worthwhile. Do you plan to never enable
HW-wrapped keys, for example? And you're fine with using more RAM?
- Eric
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-05-15 18:41 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-13 10:04 [PATCH 0/3] f2fs: support encrypted inline data LiaoYuanhong-vivo
2026-05-13 10:04 ` [PATCH 1/3] fscrypt: prepare software keys for filesystem-managed data units LiaoYuanhong-vivo
2026-05-15 18:41 ` [PATCH 0/3] f2fs: support encrypted inline data Eric Biggers
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox