public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH] fscrypt: add software key support for filesystem-managed data
@ 2026-04-21  7:57 LiaoYuanhong-vivo
  2026-04-22 23:27 ` Eric Biggers
  0 siblings, 1 reply; 3+ messages in thread
From: LiaoYuanhong-vivo @ 2026-04-21  7:57 UTC (permalink / raw)
  To: ebiggers; +Cc: tytso, jaegeuk, liaoyuanhong, linux-fscrypt, linux-kernel

Some filesystems store small file contents in filesystem-managed regions
rather than in regular data blocks submitted through bios. One example is
F2FS inline_data, where the payload is stored inside the inode node block.
Such regions still need to follow the inode's fscrypt contents encryption
semantics, but they cannot rely on blk-crypto because they are not
submitted as standalone file data bios.

As a result, when blk-crypto is enabled, mechanisms such as inline_data are
typically disabled outright. However, it is desirable to re-enable such
space-saving features while still preserving the required encryption
semantics.

To support this, add fscrypt_crypt_fs_layer_page_inplace(), a helper that
encrypts or decrypts a caller-provided page region in place using
filesystem-layer software crypto and the inode's contents encryption
policy.

This support is limited to v2 encryption policies. v1 policies do not
provide the key setup model used here, so this path returns -EOPNOTSUPP for
v1. Hardware-wrapped keys are not supported either, since deriving a
software skcipher key requires software-accessible key material, which
conflicts with the hardware-wrapped key model.

When the inode's normal contents path uses blk-crypto, fscrypt may not have
a software skcipher key prepared for the inode contents key. Add an
optional filesystem-layer prepared key to fscrypt_inode_info. This key is
derived using the same v2 contents-encryption KDF as the normal contents
key, but is prepared as a software skcipher key and is used only by the new
filesystem-layer helper.

Signed-off-by: LiaoYuanhong-vivo <liaoyuanhong@vivo.com>
---
 fs/crypto/crypto.c          |  89 ++++++++++++++++++++++
 fs/crypto/fscrypt_private.h |  20 +++++
 fs/crypto/keysetup.c        | 143 ++++++++++++++++++++++++++++++++----
 include/linux/fscrypt.h     |  38 ++++++++++
 4 files changed, 277 insertions(+), 13 deletions(-)

diff --git a/fs/crypto/crypto.c b/fs/crypto/crypto.c
index 570a2231c945..63a5e0ad957c 100644
--- a/fs/crypto/crypto.c
+++ b/fs/crypto/crypto.c
@@ -144,6 +144,95 @@ int fscrypt_crypt_data_unit(const struct fscrypt_inode_info *ci,
 	return err;
 }
 
+static const struct fscrypt_prepared_key *
+fscrypt_fs_layer_key(const struct fscrypt_inode_info *ci)
+{
+#ifdef CONFIG_FS_ENCRYPTION_INLINE_CRYPT
+	if (!fscrypt_fs_layer_key_prepared(ci))
+		return NULL;
+	return &ci->ci_fs_layer_key;
+#else
+	return NULL;
+#endif
+}
+
+static int fscrypt_crypt_fs_layer_page_prepare(const struct inode *inode,
+				    const struct fscrypt_inode_info **ci_ret,
+				    const struct fscrypt_prepared_key **prep_key_ret)
+{
+	struct fscrypt_inode_info *ci = fscrypt_get_inode_info(inode);
+	const struct fscrypt_prepared_key *prep_key;
+	int err;
+
+	if (!ci)
+		return -ENOKEY;
+
+	err = fscrypt_prepare_fs_layer_key(ci);
+	if (err)
+		return err;
+	prep_key = fscrypt_fs_layer_key(ci);
+	if (!prep_key || !prep_key->tfm)
+		return -ENOKEY;
+
+	*ci_ret = ci;
+	*prep_key_ret = prep_key;
+	return 0;
+}
+
+static int
+fscrypt_crypt_page_inplace_with_key(const struct fscrypt_inode_info *ci,
+				    const struct fscrypt_prepared_key *prep_key,
+				    struct page *page, unsigned int len,
+				    unsigned int offs, u64 dun, bool encrypt)
+{
+	struct crypto_sync_skcipher *tfm = prep_key->tfm;
+
+	SYNC_SKCIPHER_REQUEST_ON_STACK(req, tfm);
+	union fscrypt_iv iv;
+	struct scatterlist sg;
+	int err;
+
+	if (WARN_ON_ONCE(len <= 0))
+		return -EINVAL;
+	if (WARN_ON_ONCE(len % FSCRYPT_CONTENTS_ALIGNMENT != 0))
+		return -EINVAL;
+
+	fscrypt_generate_iv(&iv, dun, ci);
+
+	skcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
+				       CRYPTO_TFM_REQ_MAY_SLEEP, NULL, NULL);
+	sg_init_table(&sg, 1);
+	sg_set_page(&sg, page, len, offs);
+	skcipher_request_set_crypt(req, &sg, &sg, len, &iv);
+	if (encrypt)
+		err = crypto_skcipher_encrypt(req);
+	else
+		err = crypto_skcipher_decrypt(req);
+	if (err)
+		fscrypt_err(ci->ci_inode,
+			    "%scryption failed for data unit %llu: %d",
+			    (encrypt ? "En" : "De"), dun, err);
+	return err;
+}
+
+int fscrypt_crypt_fs_layer_page_inplace(const struct inode *inode,
+					struct page *page, unsigned int len,
+					unsigned int offs, u64 dun,
+					bool encrypt)
+{
+	const struct fscrypt_inode_info *ci;
+	const struct fscrypt_prepared_key *prep_key;
+	int err;
+
+	err = fscrypt_crypt_fs_layer_page_prepare(inode, &ci, &prep_key);
+	if (err)
+		return err;
+
+	return fscrypt_crypt_page_inplace_with_key(ci, prep_key, page, len, offs,
+					       dun, encrypt);
+}
+EXPORT_SYMBOL_GPL(fscrypt_crypt_fs_layer_page_inplace);
+
 /**
  * fscrypt_encrypt_pagecache_blocks() - Encrypt data from a pagecache folio
  * @folio: the locked pagecache folio containing the data to encrypt
diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h
index 8d3c278a7591..760e781f3921 100644
--- a/fs/crypto/fscrypt_private.h
+++ b/fs/crypto/fscrypt_private.h
@@ -266,6 +266,13 @@ struct fscrypt_inode_info {
 	 * the traditional filesystem-layer encryption.
 	 */
 	u8 ci_inlinecrypt : 1;
+
+	/*
+	 * Optional filesystem-layer software key for data regions that cannot
+	 * be handled by blk-crypto.
+	 */
+	struct fscrypt_prepared_key ci_fs_layer_key;
+	u8 ci_owns_fs_layer_key : 1;
 #endif
 
 	/* True if ci_dirhash_key is initialized */
@@ -323,6 +330,18 @@ struct fscrypt_inode_info {
 	u8 ci_nonce[FSCRYPT_FILE_NONCE_SIZE];
 };
 
+#ifdef CONFIG_FS_ENCRYPTION_INLINE_CRYPT
+static inline bool
+fscrypt_fs_layer_key_prepared(const struct fscrypt_inode_info *ci)
+{
+	/*
+	 * Pairs with the smp_store_release() in
+	 * fscrypt_prepare_software_key().
+	 */
+	return smp_load_acquire(&ci->ci_fs_layer_key.tfm);
+}
+#endif
+
 typedef enum {
 	FS_DECRYPT = 0,
 	FS_ENCRYPT,
@@ -722,6 +741,7 @@ void fscrypt_destroy_prepared_key(struct super_block *sb,
 
 int fscrypt_set_per_file_enc_key(struct fscrypt_inode_info *ci,
 				 const u8 *raw_key);
+int fscrypt_prepare_fs_layer_key(struct fscrypt_inode_info *ci);
 
 void fscrypt_derive_dirhash_key(struct fscrypt_inode_info *ci,
 				const struct fscrypt_master_key *mk);
diff --git a/fs/crypto/keysetup.c b/fs/crypto/keysetup.c
index ce327bfdada4..3a68175aa664 100644
--- a/fs/crypto/keysetup.c
+++ b/fs/crypto/keysetup.c
@@ -144,6 +144,130 @@ 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;
+
+	tfm = fscrypt_allocate_skcipher(ci->ci_mode, raw_key, ci->ci_inode);
+	if (IS_ERR(tfm))
+		return PTR_ERR(tfm);
+	/*
+	 * Pairs with fscrypt_is_key_prepared() and
+	 * fscrypt_fs_layer_key_prepared().
+	 */
+	smp_store_release(&prep_key->tfm, tfm);
+	return 0;
+}
+
+#ifdef CONFIG_FS_ENCRYPTION_INLINE_CRYPT
+static int
+fscrypt_derive_v2_fs_layer_key(const struct fscrypt_inode_info *ci,
+			       const struct fscrypt_master_key *mk,
+			       u8 *raw_key)
+{
+	const struct super_block *sb = ci->ci_inode->i_sb;
+	const u8 mode_num = ci->ci_mode - fscrypt_modes;
+	u8 hkdf_info[sizeof(mode_num) + sizeof(sb->s_uuid)];
+	u8 hkdf_context;
+	unsigned int hkdf_infolen = 0;
+	bool include_fs_uuid = false;
+
+	if (ci->ci_policy.v2.flags & FSCRYPT_POLICY_FLAG_DIRECT_KEY) {
+		hkdf_context = HKDF_CONTEXT_DIRECT_KEY;
+	} else if (ci->ci_policy.v2.flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64) {
+		hkdf_context = HKDF_CONTEXT_IV_INO_LBLK_64_KEY;
+		include_fs_uuid = true;
+	} else if (ci->ci_policy.v2.flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32) {
+		hkdf_context = HKDF_CONTEXT_IV_INO_LBLK_32_KEY;
+		include_fs_uuid = true;
+	} else {
+		fscrypt_hkdf_expand(&mk->mk_secret.hkdf,
+				    HKDF_CONTEXT_PER_FILE_ENC_KEY,
+				    ci->ci_nonce, FSCRYPT_FILE_NONCE_SIZE,
+				    raw_key, ci->ci_mode->keysize);
+		return 0;
+	}
+
+	/* Keep this per-mode KDF in sync with setup_per_mode_enc_key(). */
+	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, raw_key, ci->ci_mode->keysize);
+	return 0;
+}
+
+static int
+fscrypt_derive_fs_layer_key(const struct fscrypt_inode_info *ci,
+			    const struct fscrypt_master_key *mk,
+			    u8 *raw_key)
+{
+	switch (ci->ci_policy.version) {
+	case FSCRYPT_POLICY_V1:
+		return -EOPNOTSUPP;
+	case FSCRYPT_POLICY_V2:
+		return fscrypt_derive_v2_fs_layer_key(ci, mk, raw_key);
+	default:
+		WARN_ON_ONCE(1);
+		return -EINVAL;
+	}
+}
+
+int fscrypt_prepare_fs_layer_key(struct fscrypt_inode_info *ci)
+{
+	struct fscrypt_master_key *mk = ci->ci_master_key;
+	u8 raw_key[FSCRYPT_MAX_RAW_KEY_SIZE];
+	int err = 0;
+
+	if (!fscrypt_using_inline_encryption(ci))
+		return -EOPNOTSUPP;
+	if (fscrypt_fs_layer_key_prepared(ci))
+		return 0;
+	if (!mk)
+		return -EOPNOTSUPP;
+
+	down_read(&mk->mk_sem);
+	if (!mk->mk_present) {
+		err = -ENOKEY;
+		goto out_unlock_key;
+	}
+	if (mk->mk_secret.is_hw_wrapped) {
+		err = -EOPNOTSUPP;
+		goto out_unlock_key;
+	}
+
+	mutex_lock(&fscrypt_mode_key_setup_mutex);
+	/* Another thread may have prepared the fs-layer key while we waited. */
+	if (fscrypt_fs_layer_key_prepared(ci))
+		goto out_unlock_mutex;
+	err = fscrypt_derive_fs_layer_key(ci, mk, raw_key);
+	if (!err) {
+		ci->ci_owns_fs_layer_key = true;
+		err = fscrypt_prepare_software_key(&ci->ci_fs_layer_key,
+						   raw_key, ci);
+	}
+	memzero_explicit(raw_key, ci->ci_mode->keysize);
+out_unlock_mutex:
+	mutex_unlock(&fscrypt_mode_key_setup_mutex);
+out_unlock_key:
+	up_read(&mk->mk_sem);
+	return err;
+}
+#else
+int fscrypt_prepare_fs_layer_key(struct fscrypt_inode_info *ci)
+{
+	return -EOPNOTSUPP;
+}
+#endif
+
 /*
  * 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 +277,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. */
@@ -558,6 +670,11 @@ static void put_crypt_info(struct fscrypt_inode_info *ci)
 	else if (ci->ci_owns_key)
 		fscrypt_destroy_prepared_key(ci->ci_inode->i_sb,
 					     &ci->ci_enc_key);
+#ifdef CONFIG_FS_ENCRYPTION_INLINE_CRYPT
+	if (ci->ci_owns_fs_layer_key)
+		fscrypt_destroy_prepared_key(ci->ci_inode->i_sb,
+					     &ci->ci_fs_layer_key);
+#endif
 
 	mk = ci->ci_master_key;
 	if (mk) {
diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h
index 54712ec61ffb..ede451614461 100644
--- a/include/linux/fscrypt.h
+++ b/include/linux/fscrypt.h
@@ -344,8 +344,38 @@ static inline void fscrypt_prepare_dentry(struct dentry *dentry,
 /* crypto.c */
 void fscrypt_enqueue_decrypt_work(struct work_struct *);
 
+/**
+ * fscrypt_crypt_fs_layer_page_inplace() - encrypt or decrypt one page region
+ *                                         in place
+ * @inode: encrypted inode whose contents encryption policy is used
+ * @page: page containing the region to encrypt or decrypt
+ * @len: length of the region in bytes
+ * @offs: byte offset of the region within @page
+ * @dun: data unit number to use as the IV/index
+ * @encrypt: true to encrypt, false to decrypt
+ *
+ * Encrypt or decrypt @len bytes in @page at @offs using @inode's contents
+ * encryption semantics, but always using filesystem-layer software crypto.
+ * If @inode's normal contents path uses blk-crypto, this may require fscrypt
+ * to derive and prepare an additional filesystem-layer software key.
+ *
+ * This is intended for filesystem-managed data regions that are not submitted
+ * through a bio and therefore cannot be encrypted or decrypted by blk-crypto.
+ * The caller must ensure that @offs and @len stay within @page and satisfy the
+ * block-size requirements of @inode's encryption mode.
+ *
+ * Return: 0 on success, -EINVAL for invalid arguments, -ENOKEY if the inode's
+ * key is unavailable, -EOPNOTSUPP if filesystem-layer software crypto is
+ * unsupported for this inode/key, or another negative error from the crypto
+ * API.
+ */
+int fscrypt_crypt_fs_layer_page_inplace(const struct inode *inode,
+					struct page *page, unsigned int len,
+					unsigned int offs, u64 dun,
+					bool encrypt);
 struct page *fscrypt_encrypt_pagecache_blocks(struct folio *folio,
 		size_t len, size_t offs, gfp_t gfp_flags);
+
 int fscrypt_encrypt_block_inplace(const struct inode *inode, struct page *page,
 				  unsigned int len, unsigned int offs,
 				  u64 lblk_num);
@@ -541,6 +571,14 @@ static inline int fscrypt_decrypt_block_inplace(const struct inode *inode,
 	return -EOPNOTSUPP;
 }
 
+static inline int
+fscrypt_crypt_fs_layer_page_inplace(const struct inode *inode,
+				    struct page *page, unsigned int len,
+				    unsigned int offs, u64 dun, bool encrypt)
+{
+	return -EOPNOTSUPP;
+}
+
 static inline bool fscrypt_is_bounce_page(struct page *page)
 {
 	return false;
-- 
2.34.1


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

end of thread, other threads:[~2026-04-23 10:41 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-21  7:57 [PATCH] fscrypt: add software key support for filesystem-managed data LiaoYuanhong-vivo
2026-04-22 23:27 ` Eric Biggers
2026-04-23 10:41   ` Liao Yuanhong

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox