public inbox for linux-btrfs@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v6 00/43] btrfs: add fscrypt support
@ 2026-02-06 18:22 Daniel Vacek
  2026-02-06 18:22 ` [PATCH v6 01/43] fscrypt: add per-extent encryption support Daniel Vacek
                   ` (44 more replies)
  0 siblings, 45 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

Hello,

These are the remaining parts from former series [1] from Omar, Sweet Tea
and Josef.  Some bits of it were split into the separate set [2] before.

Notably, at this stage encryption is not supported with RAID5/6 setup
and send is also isabled for now.

There are a few changes since v5:
 * Rebased to btrfs/for-next branch.  Couple things changed in the last
   years.  A few patches were dropped as the code cleaned up or refactored.
   More details in the patches themselves.
 * As suggested by Qu and Dave, the on-disk format of storing the extent
   encryption context was re-designed.  Now, a new tree item with dedicated
   key is inserted instead of extending the file extent item.  As a result
   a special care needs to be taken when removing the encrypted extents
   to also remove the related encryption context item.
 * Fixed bugs found during testing.

Have a nice day,
Daniel

[1] v5 https://lore.kernel.org/linux-btrfs/cover.1706116485.git.josef@toxicpanda.com/
[2] https://lore.kernel.org/linux-btrfs/20251112193611.2536093-1-neelx@suse.com/

Josef Bacik (33):
  fscrypt: add per-extent encryption support
  fscrypt: allow inline encryption for extent based encryption
  fscrypt: add a __fscrypt_file_open helper
  fscrypt: conditionally don't wipe mk secret until the last active user
    is done
  blk-crypto: add a process bio callback
  fscrypt: add a process_bio hook to fscrypt_operations
  fscrypt: add documentation about extent encryption
  btrfs: add infrastructure for safe em freeing
  btrfs: select encryption dependencies if FS_ENCRYPTION
  btrfs: add fscrypt_info and encryption_type to ordered_extent
  btrfs: plumb through setting the fscrypt_info for ordered extents
  btrfs: populate the ordered_extent with the fscrypt context
  btrfs: keep track of fscrypt info and orig_start for dio reads
  btrfs: add extent encryption context tree item type
  btrfs: pass through fscrypt_extent_info to the file extent helpers
  btrfs: implement the fscrypt extent encryption hooks
  btrfs: setup fscrypt_extent_info for new extents
  btrfs: populate ordered_extent with the orig offset
  btrfs: set the bio fscrypt context when applicable
  btrfs: add a bio argument to btrfs_csum_one_bio
  btrfs: limit encrypted writes to 256 segments
  btrfs: implement process_bio cb for fscrypt
  btrfs: implement read repair for encryption
  btrfs: add test_dummy_encryption support
  btrfs: make btrfs_ref_to_path handle encrypted filenames
  btrfs: deal with encrypted symlinks in send
  btrfs: decrypt file names for send
  btrfs: load the inode context before sending writes
  btrfs: set the appropriate free space settings in reconfigure
  btrfs: support encryption with log replay
  btrfs: disable auto defrag on encrypted files
  btrfs: disable encryption on RAID5/6
  btrfs: disable send if we have encryption enabled

Omar Sandoval (6):
  fscrypt: expose fscrypt_nokey_name
  btrfs: start using fscrypt hooks
  btrfs: add inode encryption contexts
  btrfs: add new FEATURE_INCOMPAT_ENCRYPT flag
  btrfs: adapt readdir for encrypted and nokey names
  btrfs: implement fscrypt ioctls

Sweet Tea Dorminy (4):
  btrfs: handle nokey names.
  btrfs: add get_devices hook for fscrypt
  btrfs: set file extent encryption excplicitly
  btrfs: add fscrypt_info and encryption_type to extent_map

 Documentation/filesystems/fscrypt.rst |  41 +++
 block/blk-crypto-fallback.c           |  43 +++
 block/blk-crypto-internal.h           |   8 +
 block/blk-crypto-profile.c            |   2 +
 block/blk-crypto.c                    |   6 +-
 fs/btrfs/Kconfig                      |   3 +
 fs/btrfs/Makefile                     |   1 +
 fs/btrfs/accessors.h                  |   2 +
 fs/btrfs/backref.c                    |  42 ++-
 fs/btrfs/bio.c                        | 146 ++++++++-
 fs/btrfs/bio.h                        |  14 +-
 fs/btrfs/btrfs_inode.h                |   6 +-
 fs/btrfs/compression.c                |   6 +
 fs/btrfs/ctree.h                      |   3 +
 fs/btrfs/defrag.c                     |  14 +
 fs/btrfs/delayed-inode.c              |  25 +-
 fs/btrfs/delayed-inode.h              |   5 +-
 fs/btrfs/dir-item.c                   | 102 +++++-
 fs/btrfs/dir-item.h                   |  10 +-
 fs/btrfs/direct-io.c                  |  28 +-
 fs/btrfs/disk-io.c                    |   3 +-
 fs/btrfs/extent_io.c                  | 115 ++++++-
 fs/btrfs/extent_io.h                  |   3 +
 fs/btrfs/extent_map.c                 | 102 +++++-
 fs/btrfs/extent_map.h                 |  26 ++
 fs/btrfs/file-item.c                  |  28 +-
 fs/btrfs/file-item.h                  |   2 +-
 fs/btrfs/file.c                       |  75 +++++
 fs/btrfs/fs.h                         |   6 +-
 fs/btrfs/fscrypt.c                    | 446 ++++++++++++++++++++++++++
 fs/btrfs/fscrypt.h                    | 108 +++++++
 fs/btrfs/inode.c                      | 408 +++++++++++++++++------
 fs/btrfs/ioctl.c                      |  41 ++-
 fs/btrfs/ordered-data.c               |  35 +-
 fs/btrfs/ordered-data.h               |  14 +
 fs/btrfs/reflink.c                    |  43 ++-
 fs/btrfs/root-tree.c                  |   9 +-
 fs/btrfs/root-tree.h                  |   2 +-
 fs/btrfs/send.c                       | 134 +++++++-
 fs/btrfs/super.c                      |  99 +++++-
 fs/btrfs/super.h                      |   3 +-
 fs/btrfs/sysfs.c                      |   6 +
 fs/btrfs/tree-checker.c               |  67 +++-
 fs/btrfs/tree-log.c                   |  34 +-
 fs/crypto/crypto.c                    |  10 +-
 fs/crypto/fname.c                     |  36 ---
 fs/crypto/fscrypt_private.h           |  42 +++
 fs/crypto/hooks.c                     |  38 ++-
 fs/crypto/inline_crypt.c              |  84 ++++-
 fs/crypto/keyring.c                   |  18 +-
 fs/crypto/keysetup.c                  | 165 ++++++++++
 fs/crypto/policy.c                    |  47 +++
 include/linux/blk-crypto.h            |  15 +-
 include/linux/fscrypt.h               | 125 ++++++++
 include/uapi/linux/btrfs.h            |   1 +
 include/uapi/linux/btrfs_tree.h       |  26 +-
 56 files changed, 2683 insertions(+), 240 deletions(-)
 create mode 100644 fs/btrfs/fscrypt.c
 create mode 100644 fs/btrfs/fscrypt.h

-- 
2.51.0


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

* [PATCH v6 01/43] fscrypt: add per-extent encryption support
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-21 22:11   ` Eric Biggers
  2026-02-06 18:22 ` [PATCH v6 02/43] fscrypt: allow inline encryption for extent based encryption Daniel Vacek
                   ` (43 subsequent siblings)
  44 siblings, 1 reply; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

This adds the code necessary for per-extent encryption.  We will store a
nonce for every extent we create, and then use the inode's policy and
the extents nonce to derive a per-extent key.

This is meant to be flexible, if we choose to expand the on-disk extent
information in the future we have a version number we can use to change
what exists on disk.

The file system indicates it wants to use per-extent encryption by
setting s_cop->has_per_extent_encryption.  This also requires the use of
inline block encryption.

The support is relatively straightforward, the only "extra" bit is we're
deriving a per-extent key to use for the encryption, the inode still
controls the policy and access to the master key.

Since extent based encryption uses a lot of keys, we're requiring the
use of inline block crypto if you use extent-based encryption.  This
enables us to take advantage of the built in pooling and reclamation of
the crypto structures that underpin all of the encryption.

The different encryption related options for fscrypt are too numerous to
support for extent based encryption.  Support for a few of these options
could possibly be added, but since they're niche options simply reject
them for file systems using extent based encryption.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/37c2237bf485e44b0c813716f9506413026ce6dc.1706116485.git.josef@toxicpanda.com/
 * Fixed a merge collision with HW wrapped keydefinition.
 * Adapt to fscrypt changes.
   - Key derivation is void now instead of returning err.
   - Crypt info structure was split from VFS inode
     into FS specific inode structure.
---
 fs/crypto/crypto.c          |  10 ++-
 fs/crypto/fscrypt_private.h |  42 +++++++++
 fs/crypto/inline_crypt.c    |  74 ++++++++++++++++
 fs/crypto/keysetup.c        | 165 ++++++++++++++++++++++++++++++++++++
 fs/crypto/policy.c          |  47 ++++++++++
 include/linux/fscrypt.h     |  67 +++++++++++++++
 6 files changed, 404 insertions(+), 1 deletion(-)

diff --git a/fs/crypto/crypto.c b/fs/crypto/crypto.c
index 07f9cbfe3ea4..8845de2d5af0 100644
--- a/fs/crypto/crypto.c
+++ b/fs/crypto/crypto.c
@@ -42,6 +42,7 @@ static struct workqueue_struct *fscrypt_read_workqueue;
 static DEFINE_MUTEX(fscrypt_init_mutex);
 
 struct kmem_cache *fscrypt_inode_info_cachep;
+struct kmem_cache *fscrypt_extent_info_cachep;
 
 void fscrypt_enqueue_decrypt_work(struct work_struct *work)
 {
@@ -402,12 +403,19 @@ static int __init fscrypt_init(void)
 	if (!fscrypt_inode_info_cachep)
 		goto fail_free_queue;
 
+	fscrypt_extent_info_cachep = KMEM_CACHE(fscrypt_extent_info,
+						SLAB_RECLAIM_ACCOUNT);
+	if (!fscrypt_extent_info_cachep)
+		goto fail_free_inode_info;
+
 	err = fscrypt_init_keyring();
 	if (err)
-		goto fail_free_inode_info;
+		goto fail_free_extent_info;
 
 	return 0;
 
+fail_free_extent_info:
+	kmem_cache_destroy(fscrypt_extent_info_cachep);
 fail_free_inode_info:
 	kmem_cache_destroy(fscrypt_inode_info_cachep);
 fail_free_queue:
diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h
index 4e8e82a9ccf9..409e84851fb0 100644
--- a/fs/crypto/fscrypt_private.h
+++ b/fs/crypto/fscrypt_private.h
@@ -66,6 +66,8 @@
 #define FSCRYPT_CONTEXT_V1	1
 #define FSCRYPT_CONTEXT_V2	2
 
+#define FSCRYPT_EXTENT_CONTEXT_V1	1
+
 /* Keep this in sync with include/uapi/linux/fscrypt.h */
 #define FSCRYPT_MODE_MAX	FSCRYPT_MODE_AES_256_HCTR2
 
@@ -89,6 +91,25 @@ struct fscrypt_context_v2 {
 	u8 nonce[FSCRYPT_FILE_NONCE_SIZE];
 };
 
+/*
+ * fscrypt_extent_context - the encryption context of an extent
+ *
+ * This is the on-disk information stored for an extent.  The nonce is used as a
+ * KDF input in conjuction with the inode context to derive a per-extent key for
+ * encryption.
+ *
+ * With the current implementation, master_key_identifier and encryption mode
+ * must match the inode context.  These are here for future expansion where we
+ * may want the option of mixing different keys and encryption modes for the
+ * same file.
+ */
+struct fscrypt_extent_context {
+	u8 version; /* FSCRYPT_EXTENT_CONTEXT_V1 */
+	u8 encryption_mode;
+	u8 master_key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
+	u8 nonce[FSCRYPT_FILE_NONCE_SIZE];
+};
+
 /*
  * fscrypt_context - the encryption context of an inode
  *
@@ -326,6 +347,25 @@ struct fscrypt_inode_info {
 	u8 ci_nonce[FSCRYPT_FILE_NONCE_SIZE];
 };
 
+/*
+ * fscrypt_extent_info - the "encryption key" for an extent.
+ *
+ * This contains the derived key for the given extent and the nonce for the
+ * extent.
+ */
+struct fscrypt_extent_info {
+	refcount_t refs;
+
+	/* The derived key for this extent. */
+	struct fscrypt_prepared_key prep_key;
+
+	/* The super block that this extent belongs to. */
+	struct super_block *sb;
+
+	/* This is the extent's nonce, loaded from the fscrypt_extent_context */
+	u8 nonce[FSCRYPT_FILE_NONCE_SIZE];
+};
+
 typedef enum {
 	FS_DECRYPT = 0,
 	FS_ENCRYPT,
@@ -333,6 +373,7 @@ typedef enum {
 
 /* crypto.c */
 extern struct kmem_cache *fscrypt_inode_info_cachep;
+extern struct kmem_cache *fscrypt_extent_info_cachep;
 int fscrypt_initialize(struct super_block *sb);
 int fscrypt_crypt_data_unit(const struct fscrypt_inode_info *ci,
 			    fscrypt_direction_t rw, u64 index,
@@ -400,6 +441,7 @@ void fscrypt_init_hkdf(struct hmac_sha512_key *hkdf, const u8 *master_key,
 #define HKDF_CONTEXT_INODE_HASH_KEY	7 /* info=<empty>		*/
 #define HKDF_CONTEXT_KEY_IDENTIFIER_FOR_HW_WRAPPED_KEY \
 					8 /* info=<empty>		*/
+#define HKDF_CONTEXT_PER_EXTENT_ENC_KEY 9 /* info=extent_nonce		*/
 
 void fscrypt_hkdf_expand(const struct hmac_sha512_key *hkdf, u8 context,
 			 const u8 *info, unsigned int infolen,
diff --git a/fs/crypto/inline_crypt.c b/fs/crypto/inline_crypt.c
index ed6e926226b5..0d62bce2114a 100644
--- a/fs/crypto/inline_crypt.c
+++ b/fs/crypto/inline_crypt.c
@@ -314,6 +314,34 @@ void fscrypt_set_bio_crypt_ctx(struct bio *bio, const struct inode *inode,
 }
 EXPORT_SYMBOL_GPL(fscrypt_set_bio_crypt_ctx);
 
+/**
+ * fscrypt_set_bio_crypt_ctx_from_extent() - prepare a file contents bio for
+ *					     inline crypto with extent
+ *					     encryption
+ * @bio: a bio which will eventually be submitted to the file
+ * @ei: the extent's crypto info
+ * @first_lblk: the first file logical block number in the I/O
+ * @gfp_mask: memory allocation flags - these must be a waiting mask so that
+ *					bio_crypt_set_ctx can't fail.
+ *
+ * If the contents of the file should be encrypted (or decrypted) with inline
+ * encryption, then assign the appropriate encryption context to the bio.
+ *
+ * Normally the bio should be newly allocated (i.e. no pages added yet), as
+ * otherwise fscrypt_mergeable_bio() won't work as intended.
+ *
+ * The encryption context will be freed automatically when the bio is freed.
+ */
+void fscrypt_set_bio_crypt_ctx_from_extent(struct bio *bio,
+					   const struct fscrypt_extent_info *ei,
+					   u64 first_lblk, gfp_t gfp_mask)
+{
+	u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE] = { first_lblk };
+
+	bio_crypt_set_ctx(bio, ei->prep_key.blk_key, dun, gfp_mask);
+}
+EXPORT_SYMBOL_GPL(fscrypt_set_bio_crypt_ctx_from_extent);
+
 /* Extract the inode and logical block number from a buffer_head. */
 static bool bh_get_inode_and_lblk_num(const struct buffer_head *bh,
 				      const struct inode **inode_ret,
@@ -406,6 +434,52 @@ bool fscrypt_mergeable_bio(struct bio *bio, const struct inode *inode,
 }
 EXPORT_SYMBOL_GPL(fscrypt_mergeable_bio);
 
+/**
+ * fscrypt_mergeable_extent_bio() - test whether data can be added to a bio
+ * @bio: the bio being built up
+ * @ei: the fscrypt_extent_info for this extent
+ * @next_lblk: the next file logical block number in the I/O
+ *
+ * When building a bio which may contain data which should undergo inline
+ * encryption (or decryption) via fscrypt, filesystems should call this function
+ * to ensure that the resulting bio contains only contiguous data unit numbers.
+ * This will return false if the next part of the I/O cannot be merged with the
+ * bio because either the encryption key would be different or the encryption
+ * data unit numbers would be discontiguous.
+ *
+ * fscrypt_set_bio_crypt_ctx_from_extent() must have already been called on the
+ * bio.
+ *
+ * This function isn't required in cases where crypto-mergeability is ensured in
+ * another way, such as I/O targeting only a single file (and thus a single key)
+ * combined with fscrypt_limit_io_blocks() to ensure DUN contiguity.
+ *
+ * Return: true iff the I/O is mergeable
+ */
+bool fscrypt_mergeable_extent_bio(struct bio *bio,
+				  const struct fscrypt_extent_info *ei,
+				  u64 next_lblk)
+{
+	const struct bio_crypt_ctx *bc = bio->bi_crypt_context;
+	u64 next_dun[BLK_CRYPTO_DUN_ARRAY_SIZE] = { next_lblk };
+
+	if (!ei)
+		return true;
+	if (!bc)
+		return true;
+
+	/*
+	 * Comparing the key pointers is good enough, as all I/O for each key
+	 * uses the same pointer.  I.e., there's currently no need to support
+	 * merging requests where the keys are the same but the pointers differ.
+	 */
+	if (bc->bc_key != ei->prep_key.blk_key)
+		return false;
+
+	return bio_crypt_dun_is_contiguous(bc, bio->bi_iter.bi_size, next_dun);
+}
+EXPORT_SYMBOL_GPL(fscrypt_mergeable_extent_bio);
+
 /**
  * fscrypt_mergeable_bio_bh() - test whether data can be added to a bio
  * @bio: the bio being built up
diff --git a/fs/crypto/keysetup.c b/fs/crypto/keysetup.c
index 40fa05688d3a..44be08988065 100644
--- a/fs/crypto/keysetup.c
+++ b/fs/crypto/keysetup.c
@@ -849,3 +849,168 @@ int fscrypt_drop_inode(struct inode *inode)
 	return !READ_ONCE(ci->ci_master_key->mk_present);
 }
 EXPORT_SYMBOL_GPL(fscrypt_drop_inode);
+
+static struct fscrypt_extent_info *
+setup_extent_info(struct inode *inode, const u8 nonce[FSCRYPT_FILE_NONCE_SIZE])
+{
+	struct fscrypt_extent_info *ei;
+	struct fscrypt_inode_info *ci;
+	struct fscrypt_master_key *mk;
+	u8 derived_key[FSCRYPT_MAX_RAW_KEY_SIZE];
+	int err;
+
+	ci = *fscrypt_inode_info_addr(inode);
+	mk = ci->ci_master_key;
+	if (WARN_ON_ONCE(!mk))
+		return ERR_PTR(-ENOKEY);
+
+	ei = kmem_cache_zalloc(fscrypt_extent_info_cachep, GFP_KERNEL);
+	if (!ei)
+		return ERR_PTR(-ENOMEM);
+
+	refcount_set(&ei->refs, 1);
+	memcpy(ei->nonce, nonce, FSCRYPT_FILE_NONCE_SIZE);
+	ei->sb = inode->i_sb;
+
+	down_read(&mk->mk_sem);
+	/*
+	 * We specifically don't check ->mk_present here because if the inode is
+	 * open and has a reference on the master key then it should be
+	 * available for us to use.
+	 */
+	fscrypt_hkdf_expand(&mk->mk_secret.hkdf,
+			    HKDF_CONTEXT_PER_EXTENT_ENC_KEY, ei->nonce,
+			    FSCRYPT_FILE_NONCE_SIZE, derived_key,
+			    ci->ci_mode->keysize);
+
+	err = fscrypt_prepare_inline_crypt_key(&ei->prep_key, derived_key,
+						ci->ci_mode->keysize, false, ci);
+	memzero_explicit(derived_key, ci->ci_mode->keysize);
+	if (err)
+		goto out_free;
+	up_read(&mk->mk_sem);
+	return ei;
+out_free:
+	up_read(&mk->mk_sem);
+	memzero_explicit(ei, sizeof(*ei));
+	kmem_cache_free(fscrypt_extent_info_cachep, ei);
+	return ERR_PTR(err);
+}
+
+/**
+ * fscrypt_prepare_new_extent() - prepare to create a new extent for a file
+ * @inode: the possibly-encrypted inode
+ *
+ * If the inode is encrypted, setup the fscrypt_extent_info for a new extent.
+ * This will include the nonce and the derived key necessary for the extent to
+ * be encrypted.  This is only meant to be used with inline crypto and on inodes
+ * that need their contents encrypted.
+ *
+ * This doesn't persist the new extents encryption context, this is done later
+ * by calling fscrypt_set_extent_context().
+ *
+ * Return: The newly allocated fscrypt_extent_info on success, -EOPNOTSUPP if
+ *	   we're not encrypted, or another -errno code
+ */
+struct fscrypt_extent_info *fscrypt_prepare_new_extent(struct inode *inode)
+{
+	u8 nonce[FSCRYPT_FILE_NONCE_SIZE];
+
+	if (WARN_ON_ONCE(!*fscrypt_inode_info_addr(inode)))
+		return ERR_PTR(-EOPNOTSUPP);
+	if (WARN_ON_ONCE(!fscrypt_inode_uses_inline_crypto(inode)))
+		return ERR_PTR(-EOPNOTSUPP);
+
+	get_random_bytes(nonce, FSCRYPT_FILE_NONCE_SIZE);
+	return setup_extent_info(inode, nonce);
+}
+EXPORT_SYMBOL_GPL(fscrypt_prepare_new_extent);
+
+/**
+ * fscrypt_load_extent_info() - create an fscrypt_extent_info from the context
+ * @inode: the inode
+ * @ctx: the context buffer
+ * @ctx_size: the size of the context buffer
+ *
+ * Create the fscrypt_extent_info and derive the key based on the
+ * fscrypt_extent_context buffer that is provided.
+ *
+ * Return: The newly allocated fscrypt_extent_info on success, -EOPNOTSUPP if
+ *	   we're not encrypted, or another -errno code
+ */
+struct fscrypt_extent_info *fscrypt_load_extent_info(struct inode *inode,
+						     u8 *ctx, size_t ctx_size)
+{
+	struct fscrypt_extent_context extent_ctx;
+	const struct fscrypt_inode_info *ci = *fscrypt_inode_info_addr(inode);
+	const struct fscrypt_policy_v2 *policy = &ci->ci_policy.v2;
+
+	if (WARN_ON_ONCE(!ci))
+		return ERR_PTR(-EOPNOTSUPP);
+	if (WARN_ON_ONCE(!fscrypt_inode_uses_inline_crypto(inode)))
+		return ERR_PTR(-EOPNOTSUPP);
+	if (ctx_size < sizeof(extent_ctx))
+		return ERR_PTR(-EINVAL);
+
+	memcpy(&extent_ctx, ctx, sizeof(extent_ctx));
+
+	if (extent_ctx.version != FSCRYPT_EXTENT_CONTEXT_V1) {
+		fscrypt_warn(inode, "Invalid extent encryption context version");
+		return ERR_PTR(-EINVAL);
+	}
+
+	/*
+	 * For now we need to validate that the master key and the encryption
+	 * mode matches what is in the inode.
+	 */
+	if (memcmp(extent_ctx.master_key_identifier,
+		   policy->master_key_identifier,
+		   sizeof(extent_ctx.master_key_identifier))) {
+		fscrypt_warn(inode, "Mismatching master key identifier");
+		return ERR_PTR(-EINVAL);
+	}
+
+	if (extent_ctx.encryption_mode != policy->contents_encryption_mode) {
+		fscrypt_warn(inode, "Mismatching encryption mode");
+		return ERR_PTR(-EINVAL);
+	}
+
+	return setup_extent_info(inode, extent_ctx.nonce);
+}
+EXPORT_SYMBOL_GPL(fscrypt_load_extent_info);
+
+/**
+ * fscrypt_put_extent_info() - put a reference to a fscrypt_extent_info
+ * @ei: the fscrypt_extent_info being put or NULL.
+ *
+ * Drop a reference and possibly free the fscrypt_extent_info.
+ *
+ * Might sleep, since this may call blk_crypto_evict_key() which can sleep.
+ */
+void fscrypt_put_extent_info(struct fscrypt_extent_info *ei)
+{
+	if (ei && refcount_dec_and_test(&ei->refs)) {
+		fscrypt_destroy_prepared_key(ei->sb, &ei->prep_key);
+		memzero_explicit(ei, sizeof(*ei));
+		kmem_cache_free(fscrypt_extent_info_cachep, ei);
+	}
+}
+EXPORT_SYMBOL_GPL(fscrypt_put_extent_info);
+
+/**
+ * fscrypt_get_extent_info() - get a reference to a fscrypt_extent_info
+ * @ei: the extent_info to get.
+ *
+ * Get a reference on the fscrypt_extent_info. This is useful for file systems
+ * that need to pass the fscrypt_extent_info through various other structures to
+ * make lifetime tracking simpler.
+ *
+ * Return: the ei with an extra ref, NULL if ei was NULL.
+ */
+struct fscrypt_extent_info *fscrypt_get_extent_info(struct fscrypt_extent_info *ei)
+{
+	if (ei)
+		refcount_inc(&ei->refs);
+	return ei;
+}
+EXPORT_SYMBOL_GPL(fscrypt_get_extent_info);
diff --git a/fs/crypto/policy.c b/fs/crypto/policy.c
index bbb2f5ced988..b37162fedd92 100644
--- a/fs/crypto/policy.c
+++ b/fs/crypto/policy.c
@@ -211,6 +211,12 @@ static bool fscrypt_supported_v1_policy(const struct fscrypt_policy_v1 *policy,
 		return false;
 	}
 
+	if (inode->i_sb->s_cop->has_per_extent_encryption) {
+		fscrypt_warn(inode,
+			     "v1 policies aren't supported on file systems that use extent encryption");
+		return false;
+	}
+
 	return true;
 }
 
@@ -240,6 +246,11 @@ static bool fscrypt_supported_v2_policy(const struct fscrypt_policy_v2 *policy,
 	count += !!(policy->flags & FSCRYPT_POLICY_FLAG_DIRECT_KEY);
 	count += !!(policy->flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64);
 	count += !!(policy->flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32);
+	if (count > 0 && inode->i_sb->s_cop->has_per_extent_encryption) {
+		fscrypt_warn(inode,
+			     "Encryption flags aren't supported on file systems that use extent encryption");
+		return false;
+	}
 	if (count > 1) {
 		fscrypt_warn(inode, "Mutually exclusive encryption flags (0x%02x)",
 			     policy->flags);
@@ -792,6 +803,42 @@ int fscrypt_set_context(struct inode *inode, void *fs_data)
 }
 EXPORT_SYMBOL_GPL(fscrypt_set_context);
 
+/**
+ * fscrypt_set_extent_context() - Set the fscrypt extent context of a new extent
+ * @inode: the inode this extent belongs to
+ * @ei: the fscrypt_extent_info for the given extent
+ * @buf: the buffer to copy the fscrypt extent context into
+ *
+ * This should be called after fscrypt_prepare_new_extent(), using the
+ * fscrypt_extent_info that was created at that point.
+ *
+ * buf must be at most FSCRYPT_SET_CONTEXT_MAX_SIZE.
+ *
+ * Return: the size of the fscrypt_extent_context, errno if the inode has the
+ *	   wrong policy version.
+ */
+ssize_t fscrypt_context_for_new_extent(struct inode *inode,
+				       struct fscrypt_extent_info *ei, u8 *buf)
+{
+	struct fscrypt_extent_context *ctx = (struct fscrypt_extent_context *)buf;
+	const struct fscrypt_inode_info *ci = *fscrypt_inode_info_addr(inode);
+
+	BUILD_BUG_ON(sizeof(struct fscrypt_extent_context) >
+		     FSCRYPT_SET_CONTEXT_MAX_SIZE);
+
+	if (WARN_ON_ONCE(ci->ci_policy.version != 2))
+		return -EINVAL;
+
+	ctx->version = FSCRYPT_EXTENT_CONTEXT_V1;
+	ctx->encryption_mode = ci->ci_policy.v2.contents_encryption_mode;
+	memcpy(ctx->master_key_identifier,
+	       ci->ci_policy.v2.master_key_identifier,
+	       sizeof(ctx->master_key_identifier));
+	memcpy(ctx->nonce, ei->nonce, FSCRYPT_FILE_NONCE_SIZE);
+	return sizeof(struct fscrypt_extent_context);
+}
+EXPORT_SYMBOL_GPL(fscrypt_context_for_new_extent);
+
 /**
  * fscrypt_parse_test_dummy_encryption() - parse the test_dummy_encryption mount option
  * @param: the mount option
diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h
index 516aba5b858b..5a17e4975b06 100644
--- a/include/linux/fscrypt.h
+++ b/include/linux/fscrypt.h
@@ -32,6 +32,7 @@
 
 union fscrypt_policy;
 struct fscrypt_inode_info;
+struct fscrypt_extent_info;
 struct fs_parameter;
 struct seq_file;
 
@@ -103,6 +104,14 @@ struct fscrypt_operations {
 	 */
 	unsigned int supports_subblock_data_units : 1;
 
+	/*
+	 * If set then extent based encryption will be used for this file
+	 * system, and fs/crypto/ will enforce limits on the policies that are
+	 * allowed to be chosen.  Currently this means only plain v2 policies
+	 * are supported.
+	 */
+	unsigned int has_per_extent_encryption : 1;
+
 	/*
 	 * This field exists only for backwards compatibility reasons and should
 	 * only be set by the filesystems that are setting it already.  It
@@ -387,6 +396,8 @@ int fscrypt_ioctl_get_nonce(struct file *filp, void __user *arg);
 int fscrypt_has_permitted_context(struct inode *parent, struct inode *child);
 int fscrypt_context_for_new_inode(void *ctx, struct inode *inode);
 int fscrypt_set_context(struct inode *inode, void *fs_data);
+ssize_t fscrypt_context_for_new_extent(struct inode *inode,
+				       struct fscrypt_extent_info *ei, u8 *buf);
 
 struct fscrypt_dummy_policy {
 	const union fscrypt_policy *policy;
@@ -423,6 +434,11 @@ int fscrypt_prepare_new_inode(struct inode *dir, struct inode *inode,
 void fscrypt_put_encryption_info(struct inode *inode);
 void fscrypt_free_inode(struct inode *inode);
 int fscrypt_drop_inode(struct inode *inode);
+struct fscrypt_extent_info *fscrypt_prepare_new_extent(struct inode *inode);
+void fscrypt_put_extent_info(struct fscrypt_extent_info *ei);
+struct fscrypt_extent_info *fscrypt_get_extent_info(struct fscrypt_extent_info *ei);
+struct fscrypt_extent_info *fscrypt_load_extent_info(struct inode *inode,
+						     u8 *ctx, size_t ctx_size);
 
 /* fname.c */
 int fscrypt_fname_encrypt(const struct inode *inode, const struct qstr *iname,
@@ -636,6 +652,24 @@ fscrypt_free_dummy_policy(struct fscrypt_dummy_policy *dummy_policy)
 {
 }
 
+static inline ssize_t
+fscrypt_context_for_new_extent(struct inode *inode, struct fscrypt_extent_info *ei,
+			       u8 *buf)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline struct fscrypt_extent_info *
+fscrypt_load_extent_info(struct inode *inode, u8 *ctx, size_t ctx_size)
+{
+	return ERR_PTR(-EOPNOTSUPP);
+}
+
+static inline size_t fscrypt_extent_context_size(struct inode *inode)
+{
+	return 0;
+}
+
 /* keyring.c */
 static inline void fscrypt_destroy_keyring(struct super_block *sb)
 {
@@ -688,6 +722,20 @@ static inline int fscrypt_drop_inode(struct inode *inode)
 	return 0;
 }
 
+static inline struct fscrypt_extent_info *
+fscrypt_prepare_new_extent(struct inode *inode)
+{
+	return ERR_PTR(-EOPNOTSUPP);
+}
+
+static inline void fscrypt_put_extent_info(struct fscrypt_extent_info *ei) { }
+
+static inline struct fscrypt_extent_info *
+fscrypt_get_extent_info(struct fscrypt_extent_info *ei)
+{
+	return ei;
+}
+
  /* fname.c */
 static inline int fscrypt_setup_filename(struct inode *dir,
 					 const struct qstr *iname,
@@ -869,6 +917,10 @@ void fscrypt_set_bio_crypt_ctx(struct bio *bio,
 			       const struct inode *inode, u64 first_lblk,
 			       gfp_t gfp_mask);
 
+void fscrypt_set_bio_crypt_ctx_from_extent(struct bio *bio,
+					   const struct fscrypt_extent_info *ei,
+					   u64 first_lblk, gfp_t gfp_mask);
+
 void fscrypt_set_bio_crypt_ctx_bh(struct bio *bio,
 				  const struct buffer_head *first_bh,
 				  gfp_t gfp_mask);
@@ -879,6 +931,10 @@ bool fscrypt_mergeable_bio(struct bio *bio, const struct inode *inode,
 bool fscrypt_mergeable_bio_bh(struct bio *bio,
 			      const struct buffer_head *next_bh);
 
+bool fscrypt_mergeable_extent_bio(struct bio *bio,
+				  const struct fscrypt_extent_info *ei,
+				  u64 next_lblk);
+
 bool fscrypt_dio_supported(struct inode *inode);
 
 u64 fscrypt_limit_io_blocks(const struct inode *inode, u64 lblk, u64 nr_blocks);
@@ -894,6 +950,10 @@ static inline void fscrypt_set_bio_crypt_ctx(struct bio *bio,
 					     const struct inode *inode,
 					     u64 first_lblk, gfp_t gfp_mask) { }
 
+static inline void fscrypt_set_bio_crypt_ctx_from_extent(struct bio *bio,
+					const struct fscrypt_extent_info *ei,
+					u64 first_lblk, gfp_t gfp_mask) { }
+
 static inline void fscrypt_set_bio_crypt_ctx_bh(
 					 struct bio *bio,
 					 const struct buffer_head *first_bh,
@@ -906,6 +966,13 @@ static inline bool fscrypt_mergeable_bio(struct bio *bio,
 	return true;
 }
 
+static inline bool fscrypt_mergeable_extent_bio(struct bio *bio,
+						const struct fscrypt_extent_info *ei,
+						u64 next_lblk)
+{
+	return true;
+}
+
 static inline bool fscrypt_mergeable_bio_bh(struct bio *bio,
 					    const struct buffer_head *next_bh)
 {
-- 
2.51.0


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

* [PATCH v6 02/43] fscrypt: allow inline encryption for extent based encryption
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
  2026-02-06 18:22 ` [PATCH v6 01/43] fscrypt: add per-extent encryption support Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-06 18:22 ` [PATCH v6 03/43] fscrypt: add a __fscrypt_file_open helper Daniel Vacek
                   ` (42 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

Instead of requiring -o inlinecrypt to enable inline encryption, allow
having s_cop->has_per_extent_encryption to indicate that this file
system supports inline encryption.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/ba0289bf103653d5d98ef576756c9a2a66192865.1706116485.git.josef@toxicpanda.com/
 * No changes since.
---
 fs/crypto/inline_crypt.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/fs/crypto/inline_crypt.c b/fs/crypto/inline_crypt.c
index 0d62bce2114a..38a729700552 100644
--- a/fs/crypto/inline_crypt.c
+++ b/fs/crypto/inline_crypt.c
@@ -108,8 +108,11 @@ int fscrypt_select_encryption_impl(struct fscrypt_inode_info *ci,
 	if (ci->ci_mode->blk_crypto_mode == BLK_ENCRYPTION_MODE_INVALID)
 		return 0;
 
-	/* The filesystem must be mounted with -o inlinecrypt */
-	if (!(sb->s_flags & SB_INLINECRYPT))
+	/*
+	 * The filesystem must be mounted with -o inlinecrypt or have
+	 * has_per_extent_encryption enabled.
+	 */
+	if (!(sb->s_flags & SB_INLINECRYPT) && !sb->s_cop->has_per_extent_encryption)
 		return 0;
 
 	/*
-- 
2.51.0


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

* [PATCH v6 03/43] fscrypt: add a __fscrypt_file_open helper
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
  2026-02-06 18:22 ` [PATCH v6 01/43] fscrypt: add per-extent encryption support Daniel Vacek
  2026-02-06 18:22 ` [PATCH v6 02/43] fscrypt: allow inline encryption for extent based encryption Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-06 18:22 ` [PATCH v6 04/43] fscrypt: conditionally don't wipe mk secret until the last active user is done Daniel Vacek
                   ` (41 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

We have fscrypt_file_open() which is meant to be called on files being
opened so that their key is loaded when we start reading data from them.

However for btrfs send we are opening the inode directly without a filp,
so we need a different helper to make sure we can load the fscrypt
context for the inode before reading its contents.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/4a372419c3fe6ad425e1b124c342a054e9d6db23.1706116485.git.josef@toxicpanda.com/
 * Adapted to fscrypt changes.
---
 fs/crypto/hooks.c       | 38 ++++++++++++++++++++++++++++++++------
 include/linux/fscrypt.h |  8 ++++++++
 2 files changed, 40 insertions(+), 6 deletions(-)

diff --git a/fs/crypto/hooks.c b/fs/crypto/hooks.c
index b97de0d1430f..17eb2e844f30 100644
--- a/fs/crypto/hooks.c
+++ b/fs/crypto/hooks.c
@@ -9,6 +9,37 @@
 
 #include "fscrypt_private.h"
 
+/**
+ * __fscrypt_file_open() - prepare for filesystem-internal access to a
+ *			   possibly-encrypted regular file
+ * @dir: the inode for the directory via which the file is being accessed
+ * @inode: the inode being "opened"
+ *
+ * This is like fscrypt_file_open(), but instead of taking the 'struct file'
+ * being opened it takes the parent directory explicitly.  This is intended for
+ * use cases such as "send/receive" which involve the filesystem accessing file
+ * contents without setting up a 'struct file'.
+ *
+ * Return: 0 on success, -ENOKEY if the key is missing, or another -errno code
+ */
+int __fscrypt_file_open(struct inode *dir, struct inode *inode)
+{
+	int err;
+
+	err = fscrypt_require_key(inode);
+	if (err)
+		return err;
+
+	if (!fscrypt_has_permitted_context(dir, inode)) {
+		fscrypt_warn(inode,
+			     "Inconsistent encryption context (parent directory: %lu)",
+			     dir->i_ino);
+		return -EPERM;
+	}
+	return 0;
+}
+EXPORT_SYMBOL_GPL(__fscrypt_file_open);
+
 /**
  * fscrypt_file_open() - prepare to open a possibly-encrypted regular file
  * @inode: the inode being opened
@@ -60,12 +91,7 @@ int fscrypt_file_open(struct inode *inode, struct file *filp)
 	rcu_read_unlock();
 
 	dentry_parent = dget_parent(dentry);
-	if (!fscrypt_has_permitted_context(d_inode(dentry_parent), inode)) {
-		fscrypt_warn(inode,
-			     "Inconsistent encryption context (parent directory: %lu)",
-			     d_inode(dentry_parent)->i_ino);
-		err = -EPERM;
-	}
+	err = __fscrypt_file_open(d_inode(dentry_parent), inode);
 	dput(dentry_parent);
 	return err;
 }
diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h
index 5a17e4975b06..dba5ca122775 100644
--- a/include/linux/fscrypt.h
+++ b/include/linux/fscrypt.h
@@ -471,6 +471,7 @@ int fscrypt_zeroout_range(const struct inode *inode, pgoff_t lblk,
 
 /* hooks.c */
 int fscrypt_file_open(struct inode *inode, struct file *filp);
+int __fscrypt_file_open(struct inode *dir, struct inode *inode);
 int __fscrypt_prepare_link(struct inode *inode, struct inode *dir,
 			   struct dentry *dentry);
 int __fscrypt_prepare_rename(struct inode *old_dir, struct dentry *old_dentry,
@@ -818,6 +819,13 @@ static inline int fscrypt_file_open(struct inode *inode, struct file *filp)
 	return 0;
 }
 
+static inline int __fscrypt_file_open(struct inode *dir, struct inode *inode)
+{
+	if (IS_ENCRYPTED(inode))
+		return -EOPNOTSUPP;
+	return 0;
+}
+
 static inline int __fscrypt_prepare_link(struct inode *inode, struct inode *dir,
 					 struct dentry *dentry)
 {
-- 
2.51.0


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

* [PATCH v6 04/43] fscrypt: conditionally don't wipe mk secret until the last active user is done
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (2 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 03/43] fscrypt: add a __fscrypt_file_open helper Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-06 18:22 ` [PATCH v6 05/43] blk-crypto: add a process_bio callback Daniel Vacek
                   ` (40 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

Previously we were wiping the master key secret when we do
FS_IOC_REMOVE_ENCRYPTION_KEY, and then using the fact that it was
cleared as the mechanism from keeping new users from being setup.  This
works with inode based encryption, as the per-inode key is derived at
setup time, so the secret disappearing doesn't affect any currently open
files from being able to continue working.

However for extent based encryption we do our key derivation at page
writeout and readpage time, which means we need the master key secret to
be available while we still have our file open.

Since the master key lifetime is controlled by a flag, move the clearing
of the secret to the mk_active_users cleanup stage if we have extent
based encryption enabled on this super block.  This counter represents
the actively open files that still exist on the file system, and thus
should still be able to operate normally.  Once the last user is closed
we can clear the secret.  Until then no new users are allowed, and this
allows currently open files to continue to operate until they're closed.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/5f28e46ce99d918a16f5bf4d8190870d0fffefc4.1706116485.git.josef@toxicpanda.com/
 * No changes since.
---
 fs/crypto/keyring.c | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/fs/crypto/keyring.c b/fs/crypto/keyring.c
index 5e939ea3ac28..7ccec27e4e4a 100644
--- a/fs/crypto/keyring.c
+++ b/fs/crypto/keyring.c
@@ -110,6 +110,14 @@ void fscrypt_put_master_key_activeref(struct super_block *sb,
 	WARN_ON_ONCE(mk->mk_present);
 	WARN_ON_ONCE(!list_empty(&mk->mk_decrypted_inodes));
 
+	/* We can't wipe the master key secret until the last activeref is
+	 * dropped on the master key with per-extent encryption since the key
+	 * derivation continues to happen as long as there are active refs.
+	 * Wipe it here now that we're done using it.
+	 */
+	if (sb->s_cop->has_per_extent_encryption)
+		wipe_master_key_secret(&mk->mk_secret);
+
 	for (i = 0; i <= FSCRYPT_MODE_MAX; i++) {
 		fscrypt_destroy_prepared_key(
 				sb, &mk->mk_direct_keys[i]);
@@ -134,7 +142,15 @@ static void fscrypt_initiate_key_removal(struct super_block *sb,
 					 struct fscrypt_master_key *mk)
 {
 	WRITE_ONCE(mk->mk_present, false);
-	wipe_master_key_secret(&mk->mk_secret);
+
+	/*
+	 * Per-extent encryption requires the master key to stick around until
+	 * writeout has completed as we derive the per-extent keys at writeout
+	 * time.  Once the activeref drops to 0 we'll wipe the master secret
+	 * key.
+	 */
+	if (!sb->s_cop->has_per_extent_encryption)
+		wipe_master_key_secret(&mk->mk_secret);
 	fscrypt_put_master_key_activeref(sb, mk);
 }
 
-- 
2.51.0


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

* [PATCH v6 05/43] blk-crypto: add a process_bio callback
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (3 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 04/43] fscrypt: conditionally don't wipe mk secret until the last active user is done Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-06 18:22 ` [PATCH v6 06/43] fscrypt: add a process_bio hook to fscrypt_operations Daniel Vacek
                   ` (39 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

Btrfs does checksumming, and the checksums need to match the bytes on
disk.  In order to facilitate this add a process bio callback for the
blk-crypto layer.  This allows the file system to specify a callback and
then can process the encrypted bio as necessary.

For btrfs, writes will have the checksums calculated and saved into our
relevant data structures for storage once the write completes.  For
reads we will validate the checksums match what is on disk and error out
if there is a mismatch.

This is incompatible with native encryption obviously, so make sure we
don't use native encryption if this callback is set.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/66c781f3b2afdf2c558efdf33a7bba8bcfe47ce7.1706116485.git.josef@toxicpanda.com/
 * No changes since.
---
 block/blk-crypto-fallback.c | 43 +++++++++++++++++++++++++++++++++++++
 block/blk-crypto-internal.h |  8 +++++++
 block/blk-crypto-profile.c  |  2 ++
 block/blk-crypto.c          |  6 +++++-
 fs/crypto/inline_crypt.c    |  3 ++-
 include/linux/blk-crypto.h  | 15 +++++++++++--
 6 files changed, 73 insertions(+), 4 deletions(-)

diff --git a/block/blk-crypto-fallback.c b/block/blk-crypto-fallback.c
index 86b27f96051a..7af6424a19ec 100644
--- a/block/blk-crypto-fallback.c
+++ b/block/blk-crypto-fallback.c
@@ -210,12 +210,15 @@ blk_crypto_fallback_alloc_cipher_req(struct blk_crypto_keyslot *slot,
 
 static bool blk_crypto_fallback_split_bio_if_needed(struct bio **bio_ptr)
 {
+	struct bio_crypt_ctx *bc;
 	struct bio *bio = *bio_ptr;
 	unsigned int i = 0;
 	unsigned int num_sectors = 0;
 	struct bio_vec bv;
 	struct bvec_iter iter;
 
+	bc = bio->bi_crypt_context;
+
 	bio_for_each_segment(bv, bio, iter) {
 		num_sectors += bv.bv_len >> SECTOR_SHIFT;
 		if (++i == BIO_MAX_VECS)
@@ -223,6 +226,16 @@ static bool blk_crypto_fallback_split_bio_if_needed(struct bio **bio_ptr)
 	}
 
 	if (num_sectors < bio_sectors(bio)) {
+		/*
+		 * We cannot split bio's that have process_bio, as they require
+		 * the original bio.  The upper layer must make sure to limit
+		 * the submitted bio's appropriately.
+		 */
+		if (bc->bc_key->crypto_cfg.process_bio) {
+			bio->bi_status = BLK_STS_RESOURCE;
+			return false;
+		}
+
 		bio = bio_submit_split_bioset(bio, num_sectors,
 					      &crypto_bio_split);
 		if (!bio)
@@ -343,6 +356,15 @@ static bool blk_crypto_fallback_encrypt_bio(struct bio **bio_ptr)
 		}
 	}
 
+	/* Process the encrypted bio before we submit it. */
+	if (bc->bc_key->crypto_cfg.process_bio) {
+		blk_st = bc->bc_key->crypto_cfg.process_bio(src_bio, enc_bio);
+		if (blk_st != BLK_STS_OK) {
+			src_bio->bi_status = blk_st;
+			goto out_free_bounce_pages;
+		}
+	}
+
 	enc_bio->bi_private = src_bio;
 	enc_bio->bi_end_io = blk_crypto_fallback_encrypt_endio;
 	*bio_ptr = enc_bio;
@@ -388,6 +410,15 @@ static void blk_crypto_fallback_decrypt_bio(struct work_struct *work)
 	unsigned int i;
 	blk_status_t blk_st;
 
+	/* Process the bio first before trying to decrypt. */
+	if (bc->bc_key->crypto_cfg.process_bio) {
+		blk_st = bc->bc_key->crypto_cfg.process_bio(bio, bio);
+		if (blk_st != BLK_STS_OK) {
+			bio->bi_status = blk_st;
+			goto out_no_keyslot;
+		}
+	}
+
 	/*
 	 * Get a blk-crypto-fallback keyslot that contains a crypto_skcipher for
 	 * this bio's algorithm and key.
@@ -437,6 +468,18 @@ static void blk_crypto_fallback_decrypt_bio(struct work_struct *work)
 	bio_endio(bio);
 }
 
+/**
+ * blk_crypto_profile_is_fallback - check if this profile is the fallback
+ *				    profile
+ * @profile: the profile we're checking
+ *
+ * This is just a quick check to make sure @profile is the fallback profile.
+ */
+bool blk_crypto_profile_is_fallback(struct blk_crypto_profile *profile)
+{
+	return profile == blk_crypto_fallback_profile;
+}
+
 /**
  * blk_crypto_fallback_decrypt_endio - queue bio for fallback decryption
  *
diff --git a/block/blk-crypto-internal.h b/block/blk-crypto-internal.h
index ccf6dff6ff6b..82b512ca35f0 100644
--- a/block/blk-crypto-internal.h
+++ b/block/blk-crypto-internal.h
@@ -223,6 +223,8 @@ bool blk_crypto_fallback_bio_prep(struct bio **bio_ptr);
 
 int blk_crypto_fallback_evict_key(const struct blk_crypto_key *key);
 
+bool blk_crypto_profile_is_fallback(struct blk_crypto_profile *profile);
+
 #else /* CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK */
 
 static inline int
@@ -245,6 +247,12 @@ blk_crypto_fallback_evict_key(const struct blk_crypto_key *key)
 	return 0;
 }
 
+static inline bool
+blk_crypto_profile_is_fallback(struct blk_crypto_profile *profile)
+{
+	return false;
+}
+
 #endif /* CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK */
 
 #endif /* __LINUX_BLK_CRYPTO_INTERNAL_H */
diff --git a/block/blk-crypto-profile.c b/block/blk-crypto-profile.c
index 81918f6e0cae..e57af3bb17dc 100644
--- a/block/blk-crypto-profile.c
+++ b/block/blk-crypto-profile.c
@@ -354,6 +354,8 @@ bool __blk_crypto_cfg_supported(struct blk_crypto_profile *profile,
 		return false;
 	if (!(profile->key_types_supported & cfg->key_type))
 		return false;
+	if (cfg->process_bio && !blk_crypto_profile_is_fallback(profile))
+		return false;
 	return true;
 }
 
diff --git a/block/blk-crypto.c b/block/blk-crypto.c
index 3e7bf1974cbd..b0576e502d4d 100644
--- a/block/blk-crypto.c
+++ b/block/blk-crypto.c
@@ -332,6 +332,8 @@ int __blk_crypto_rq_bio_prep(struct request *rq, struct bio *bio,
  * @dun_bytes: number of bytes that will be used to specify the DUN when this
  *	       key is used
  * @data_unit_size: the data unit size to use for en/decryption
+ * @process_bio: the call back if the upper layer needs to process the encrypted
+ *		 bio
  *
  * Return: 0 on success, -errno on failure.  The caller is responsible for
  *	   zeroizing both blk_key and key_bytes when done with them.
@@ -341,7 +343,8 @@ int blk_crypto_init_key(struct blk_crypto_key *blk_key,
 			enum blk_crypto_key_type key_type,
 			enum blk_crypto_mode_num crypto_mode,
 			unsigned int dun_bytes,
-			unsigned int data_unit_size)
+			unsigned int data_unit_size,
+			blk_crypto_process_bio_t process_bio)
 {
 	const struct blk_crypto_mode *mode;
 
@@ -375,6 +378,7 @@ int blk_crypto_init_key(struct blk_crypto_key *blk_key,
 	blk_key->crypto_cfg.dun_bytes = dun_bytes;
 	blk_key->crypto_cfg.data_unit_size = data_unit_size;
 	blk_key->crypto_cfg.key_type = key_type;
+	blk_key->crypto_cfg.process_bio = process_bio;
 	blk_key->data_unit_size_bits = ilog2(data_unit_size);
 	blk_key->size = key_size;
 	memcpy(blk_key->bytes, key_bytes, key_size);
diff --git a/fs/crypto/inline_crypt.c b/fs/crypto/inline_crypt.c
index 38a729700552..d737fb6ff011 100644
--- a/fs/crypto/inline_crypt.c
+++ b/fs/crypto/inline_crypt.c
@@ -178,7 +178,8 @@ int fscrypt_prepare_inline_crypt_key(struct fscrypt_prepared_key *prep_key,
 
 	err = blk_crypto_init_key(blk_key, key_bytes, key_size, key_type,
 				  crypto_mode, fscrypt_get_dun_bytes(ci),
-				  1U << ci->ci_data_unit_bits);
+				  1U << ci->ci_data_unit_bits,
+				  NULL);
 	if (err) {
 		fscrypt_err(inode, "error %d initializing blk-crypto key", err);
 		goto fail;
diff --git a/include/linux/blk-crypto.h b/include/linux/blk-crypto.h
index 58b0c5254a67..022b00004517 100644
--- a/include/linux/blk-crypto.h
+++ b/include/linux/blk-crypto.h
@@ -7,7 +7,7 @@
 #define __LINUX_BLK_CRYPTO_H
 
 #include <linux/minmax.h>
-#include <linux/types.h>
+#include <linux/blk_types.h>
 #include <uapi/linux/blk-crypto.h>
 
 enum blk_crypto_mode_num {
@@ -19,6 +19,14 @@ enum blk_crypto_mode_num {
 	BLK_ENCRYPTION_MODE_MAX,
 };
 
+/*
+ * orig_bio must be the bio that was submitted from the upper layer as the upper
+ * layer could have used a specific bioset and expect the orig_bio to be from
+ * its bioset.
+ */
+typedef blk_status_t (*blk_crypto_process_bio_t)(struct bio *orig_bio,
+						 struct bio *enc_bio);
+
 /*
  * Supported types of keys.  Must be bitflags due to their use in
  * blk_crypto_profile::key_types_supported.
@@ -77,12 +85,14 @@ enum blk_crypto_key_type {
  *	filesystem block size or the disk sector size.
  * @dun_bytes: the maximum number of bytes of DUN used when using this key
  * @key_type: the type of this key -- either raw or hardware-wrapped
+ * @proces_bio: optional callback to process encrypted bios.
  */
 struct blk_crypto_config {
 	enum blk_crypto_mode_num crypto_mode;
 	unsigned int data_unit_size;
 	unsigned int dun_bytes;
 	enum blk_crypto_key_type key_type;
+	blk_crypto_process_bio_t process_bio;
 };
 
 /**
@@ -145,7 +155,8 @@ int blk_crypto_init_key(struct blk_crypto_key *blk_key,
 			enum blk_crypto_key_type key_type,
 			enum blk_crypto_mode_num crypto_mode,
 			unsigned int dun_bytes,
-			unsigned int data_unit_size);
+			unsigned int data_unit_size,
+			blk_crypto_process_bio_t process_bio);
 
 int blk_crypto_start_using_key(struct block_device *bdev,
 			       const struct blk_crypto_key *key);
-- 
2.51.0


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

* [PATCH v6 06/43] fscrypt: add a process_bio hook to fscrypt_operations
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (4 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 05/43] blk-crypto: add a process_bio callback Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-06 18:22 ` [PATCH v6 07/43] fscrypt: expose fscrypt_nokey_name Daniel Vacek
                   ` (38 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

This will allow file systems to set a process_bio hook for inline
encryption.  This will be utilized by btrfs in order to make sure the
checksumming work is done on the encrypted bio's.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/2c638e5fa1b7868dbf79d932b15364c3c30ca9de.1706116485.git.josef@toxicpanda.com/
 * No changes since.
---
 fs/crypto/inline_crypt.c |  2 +-
 include/linux/fscrypt.h  | 14 ++++++++++++++
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/fs/crypto/inline_crypt.c b/fs/crypto/inline_crypt.c
index d737fb6ff011..fc1aa5b00af1 100644
--- a/fs/crypto/inline_crypt.c
+++ b/fs/crypto/inline_crypt.c
@@ -179,7 +179,7 @@ int fscrypt_prepare_inline_crypt_key(struct fscrypt_prepared_key *prep_key,
 	err = blk_crypto_init_key(blk_key, key_bytes, key_size, key_type,
 				  crypto_mode, fscrypt_get_dun_bytes(ci),
 				  1U << ci->ci_data_unit_bits,
-				  NULL);
+				  sb->s_cop->process_bio);
 	if (err) {
 		fscrypt_err(inode, "error %d initializing blk-crypto key", err);
 		goto fail;
diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h
index dba5ca122775..e3bb9e3756e1 100644
--- a/include/linux/fscrypt.h
+++ b/include/linux/fscrypt.h
@@ -16,6 +16,7 @@
 #include <linux/fs.h>
 #include <linux/mm.h>
 #include <linux/slab.h>
+#include <linux/blk-crypto.h>
 #include <uapi/linux/fscrypt.h>
 
 /*
@@ -205,6 +206,19 @@ struct fscrypt_operations {
 	 */
 	struct block_device **(*get_devices)(struct super_block *sb,
 					     unsigned int *num_devs);
+
+	/*
+	 * A callback if the file system requires the ability to process the
+	 * encrypted bio, used only with inline encryption.
+	 *
+	 * @orig_bio: the original bio submitted.
+	 * @enc_bio: the encrypted bio.
+	 *
+	 * For writes the enc_bio will be different from the orig_bio, for reads
+	 * they will be the same.  For reads we get the bio before it is
+	 * decrypted, for writes we get the bio before it is submitted.
+	 */
+	blk_crypto_process_bio_t process_bio;
 };
 
 int fscrypt_d_revalidate(struct inode *dir, const struct qstr *name,
-- 
2.51.0


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

* [PATCH v6 07/43] fscrypt: expose fscrypt_nokey_name
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (5 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 06/43] fscrypt: add a process_bio hook to fscrypt_operations Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-06 18:22 ` [PATCH v6 08/43] fscrypt: add documentation about extent encryption Daniel Vacek
                   ` (37 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel, Omar Sandoval, Sweet Tea Dorminy

From: Omar Sandoval <osandov@osandov.com>

btrfs stores its data structures, including filenames in directories, in
its own buffer implementation, struct extent_buffer, composed of
several non-contiguous pages. We could copy filenames into a
temporary buffer and use fscrypt_match_name() against that buffer, such
extensive memcpying would be expensive. Instead, exposing
fscrypt_nokey_name as in this change allows btrfs to recapitulate
fscrypt_match_name() using methods on struct extent_buffer instead of
dealing with a raw byte array.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/132b64edf1e6b705995fb1a6dc2f194527f6be75.1706116485.git.josef@toxicpanda.com/
 * No changes since.
---
 fs/crypto/fname.c       | 36 ------------------------------------
 include/linux/fscrypt.h | 36 ++++++++++++++++++++++++++++++++++++
 2 files changed, 36 insertions(+), 36 deletions(-)

diff --git a/fs/crypto/fname.c b/fs/crypto/fname.c
index a9a4432d12ba..1e5e503686dd 100644
--- a/fs/crypto/fname.c
+++ b/fs/crypto/fname.c
@@ -27,42 +27,6 @@
  */
 #define FSCRYPT_FNAME_MIN_MSG_LEN 16
 
-/*
- * struct fscrypt_nokey_name - identifier for directory entry when key is absent
- *
- * When userspace lists an encrypted directory without access to the key, the
- * filesystem must present a unique "no-key name" for each filename that allows
- * it to find the directory entry again if requested.  Naively, that would just
- * mean using the ciphertext filenames.  However, since the ciphertext filenames
- * can contain illegal characters ('\0' and '/'), they must be encoded in some
- * way.  We use base64url.  But that can cause names to exceed NAME_MAX (255
- * bytes), so we also need to use a strong hash to abbreviate long names.
- *
- * The filesystem may also need another kind of hash, the "dirhash", to quickly
- * find the directory entry.  Since filesystems normally compute the dirhash
- * over the on-disk filename (i.e. the ciphertext), it's not computable from
- * no-key names that abbreviate the ciphertext using the strong hash to fit in
- * NAME_MAX.  It's also not computable if it's a keyed hash taken over the
- * plaintext (but it may still be available in the on-disk directory entry);
- * casefolded directories use this type of dirhash.  At least in these cases,
- * each no-key name must include the name's dirhash too.
- *
- * To meet all these requirements, we base64url-encode the following
- * variable-length structure.  It contains the dirhash, or 0's if the filesystem
- * didn't provide one; up to 149 bytes of the ciphertext name; and for
- * ciphertexts longer than 149 bytes, also the SHA-256 of the remaining bytes.
- *
- * This ensures that each no-key name contains everything needed to find the
- * directory entry again, contains only legal characters, doesn't exceed
- * NAME_MAX, is unambiguous unless there's a SHA-256 collision, and that we only
- * take the performance hit of SHA-256 on very long filenames (which are rare).
- */
-struct fscrypt_nokey_name {
-	u32 dirhash[2];
-	u8 bytes[149];
-	u8 sha256[SHA256_DIGEST_SIZE];
-}; /* 189 bytes => 252 bytes base64url-encoded, which is <= NAME_MAX (255) */
-
 /*
  * Decoded size of max-size no-key name, i.e. a name that was abbreviated using
  * the strong hash and thus includes the 'sha256' field.  This isn't simply
diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h
index e3bb9e3756e1..5b4fc75c257c 100644
--- a/include/linux/fscrypt.h
+++ b/include/linux/fscrypt.h
@@ -56,6 +56,42 @@ struct fscrypt_name {
 #define fname_name(p)		((p)->disk_name.name)
 #define fname_len(p)		((p)->disk_name.len)
 
+/*
+ * struct fscrypt_nokey_name - identifier for directory entry when key is absent
+ *
+ * When userspace lists an encrypted directory without access to the key, the
+ * filesystem must present a unique "no-key name" for each filename that allows
+ * it to find the directory entry again if requested.  Naively, that would just
+ * mean using the ciphertext filenames.  However, since the ciphertext filenames
+ * can contain illegal characters ('\0' and '/'), they must be encoded in some
+ * way.  We use base64url.  But that can cause names to exceed NAME_MAX (255
+ * bytes), so we also need to use a strong hash to abbreviate long names.
+ *
+ * The filesystem may also need another kind of hash, the "dirhash", to quickly
+ * find the directory entry.  Since filesystems normally compute the dirhash
+ * over the on-disk filename (i.e. the ciphertext), it's not computable from
+ * no-key names that abbreviate the ciphertext using the strong hash to fit in
+ * NAME_MAX.  It's also not computable if it's a keyed hash taken over the
+ * plaintext (but it may still be available in the on-disk directory entry);
+ * casefolded directories use this type of dirhash.  At least in these cases,
+ * each no-key name must include the name's dirhash too.
+ *
+ * To meet all these requirements, we base64url-encode the following
+ * variable-length structure.  It contains the dirhash, or 0's if the filesystem
+ * didn't provide one; up to 149 bytes of the ciphertext name; and for
+ * ciphertexts longer than 149 bytes, also the SHA-256 of the remaining bytes.
+ *
+ * This ensures that each no-key name contains everything needed to find the
+ * directory entry again, contains only legal characters, doesn't exceed
+ * NAME_MAX, is unambiguous unless there's a SHA-256 collision, and that we only
+ * take the performance hit of SHA-256 on very long filenames (which are rare).
+ */
+struct fscrypt_nokey_name {
+	u32 dirhash[2];
+	u8 bytes[149];
+	u8 sha256[32];
+}; /* 189 bytes => 252 bytes base64url-encoded, which is <= NAME_MAX (255) */
+
 /* Maximum value for the third parameter of fscrypt_operations.set_context(). */
 #define FSCRYPT_SET_CONTEXT_MAX_SIZE	40
 
-- 
2.51.0


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

* [PATCH v6 08/43] fscrypt: add documentation about extent encryption
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (6 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 07/43] fscrypt: expose fscrypt_nokey_name Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-06 18:43   ` Randy Dunlap
  2026-02-06 18:22 ` [PATCH v6 09/43] btrfs: add infrastructure for safe em freeing Daniel Vacek
                   ` (36 subsequent siblings)
  44 siblings, 1 reply; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, Jonathan Corbet
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel, linux-doc

From: Josef Bacik <josef@toxicpanda.com>

Add a couple of sections to the fscrypt documentation about per-extent
encryption.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/7b2cc4dd423c3930e51b1ef5dd209164ff11c05a.1706116485.git.josef@toxicpanda.com/
 * No changes since.
---
 Documentation/filesystems/fscrypt.rst | 41 +++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/Documentation/filesystems/fscrypt.rst b/Documentation/filesystems/fscrypt.rst
index 70af896822e1..8afec55dd913 100644
--- a/Documentation/filesystems/fscrypt.rst
+++ b/Documentation/filesystems/fscrypt.rst
@@ -283,6 +283,21 @@ alternative master keys or to support rotating master keys.  Instead,
 the master keys may be wrapped in userspace, e.g. as is done by the
 `fscrypt <https://github.com/google/fscrypt>`_ tool.
 
+Per-extent encryption keys
+--------------------------
+
+For certain file systems, such as btrfs, it's desired to derive a
+per-extent encryption key.  This is to enable features such as snapshots
+and reflink, where you could have different inodes pointing at the same
+extent.  When a new extent is created fscrypt randomly generates a
+16-byte nonce and the file system stores it along side the extent.
+Then, it uses a KDF (as described in `Key derivation function`_) to
+derive the extent's key from the master key and nonce.
+
+Currently the inode's master key and encryption policy must match the
+extent, so you cannot share extents between inodes that were encrypted
+differently.
+
 DIRECT_KEY policies
 -------------------
 
@@ -1488,6 +1503,27 @@ by the kernel and is used as KDF input or as a tweak to cause
 different files to be encrypted differently; see `Per-file encryption
 keys`_ and `DIRECT_KEY policies`_.
 
+Extent encryption context
+-------------------------
+
+The extent encryption context mirrors the important parts of the above
+`Encryption context`_, with a few ommisions.  The struct is defined as
+follows::
+
+        struct fscrypt_extent_context {
+                u8 version;
+                u8 encryption_mode;
+                u8 master_key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
+                u8 nonce[FSCRYPT_FILE_NONCE_SIZE];
+        };
+
+Currently all fields much match the containing inode's encryption
+context, with the exception of the nonce.
+
+Additionally extent encryption is only supported with
+FSCRYPT_EXTENT_CONTEXT_V2 using the standard policy, all other policies
+are disallowed.
+
 Data path changes
 -----------------
 
@@ -1511,6 +1547,11 @@ buffer.  Some filesystems, such as UBIFS, already use temporary
 buffers regardless of encryption.  Other filesystems, such as ext4 and
 F2FS, have to allocate bounce pages specially for encryption.
 
+Inline encryption is not optional for extent encryption based file
+systems, the amount of objects required to be kept around is too much.
+Inline encryption handles the object lifetime details which results in a
+cleaner implementation.
+
 Filename hashing and encoding
 -----------------------------
 
-- 
2.51.0


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

* [PATCH v6 09/43] btrfs: add infrastructure for safe em freeing
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (7 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 08/43] fscrypt: add documentation about extent encryption Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-06 18:22 ` [PATCH v6 10/43] btrfs: start using fscrypt hooks Daniel Vacek
                   ` (35 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel, Boris Burkov

From: Josef Bacik <josef@toxicpanda.com>

When we add fscrypt support we're going to have fscrypt objects hanging
off of extent_maps.  This includes a block key, which if we're the last
one freeing the key we may have to unregister it from the block layer.
This requires taking a semaphore in the block layer, which means we
can't free em's under the extent map tree lock.

Thankfully we only do this in two places, one where we're dropping a
range of extent maps, and when we're freeing logged extents.  Add a
free_extent_map_safe() which will add the em to a list in the em_tree if
we free'd the object.  Currently this is unconditional but will be
changed to conditional on the fscrypt object we will add in a later
patch.

To process these delayed objects add a free_pending_extent_maps() that
is called after the lock has been dropped on the em_tree.  This will
process the extent maps on the freed list and do the appropriate freeing
work in a safe manner.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/6cf44f7860e94de68df242e69f4c5250bd061cff.1706116485.git.josef@toxicpanda.com/
 * No changes since (other than simple function renames).
---
 fs/btrfs/extent_map.c | 76 +++++++++++++++++++++++++++++++++++++++++--
 fs/btrfs/extent_map.h | 10 ++++++
 fs/btrfs/tree-log.c   |  6 ++--
 3 files changed, 87 insertions(+), 5 deletions(-)

diff --git a/fs/btrfs/extent_map.c b/fs/btrfs/extent_map.c
index 095a561d733f..58589fc11802 100644
--- a/fs/btrfs/extent_map.c
+++ b/fs/btrfs/extent_map.c
@@ -34,7 +34,9 @@ void __cold btrfs_extent_map_exit(void)
 void btrfs_extent_map_tree_init(struct extent_map_tree *tree)
 {
 	tree->root = RB_ROOT;
+	tree->flags = 0;
 	INIT_LIST_HEAD(&tree->modified_extents);
+	INIT_LIST_HEAD(&tree->freed_extents);
 	rwlock_init(&tree->lock);
 }
 
@@ -51,9 +53,15 @@ struct extent_map *btrfs_alloc_extent_map(void)
 	RB_CLEAR_NODE(&em->rb_node);
 	refcount_set(&em->refs, 1);
 	INIT_LIST_HEAD(&em->list);
+	INIT_LIST_HEAD(&em->free_list);
 	return em;
 }
 
+static void free_extent_map(struct extent_map *em)
+{
+	kmem_cache_free(extent_map_cache, em);
+}
+
 /*
  * Drop the reference out on @em by one and free the structure if the reference
  * count hits zero.
@@ -65,10 +73,69 @@ void btrfs_free_extent_map(struct extent_map *em)
 	if (refcount_dec_and_test(&em->refs)) {
 		WARN_ON(btrfs_extent_map_in_tree(em));
 		WARN_ON(!list_empty(&em->list));
-		kmem_cache_free(extent_map_cache, em);
+		free_extent_map(em);
+	}
+}
+
+/*
+ * Drop a ref for the extent map in the given tree.
+ *
+ * @tree:	tree that the em is a part of.
+ * @em:		the em to drop the reference to.
+ *
+ * Drop the reference count on @em by one, if the reference count hits 0 and
+ * there is an object on the em that can't be safely freed in the current
+ * context (if we are holding the extent_map_tree->lock for example), then add
+ * it to the freed_extents list on the extent_map_tree for later processing.
+ *
+ * This must be followed by a btrfs_free_pending_extent_maps() to clear
+ * the pending frees.
+ */
+void btrfs_free_extent_map_safe(struct extent_map_tree *tree,
+				struct extent_map *em)
+{
+	lockdep_assert_held_write(&tree->lock);
+
+	if (!em)
+		return;
+
+	if (refcount_dec_and_test(&em->refs)) {
+		WARN_ON(btrfs_extent_map_in_tree(em));
+		WARN_ON(!list_empty(&em->list));
+		list_add_tail(&em->free_list, &tree->freed_extents);
+		set_bit(EXTENT_MAP_TREE_PENDING_FREES, &tree->flags);
 	}
 }
 
+/*
+ * Free the em objects that exist on the em tree
+ *
+ * @tree:	the tree to free the objects from.
+ *
+ * If there are any objects on the em->freed_extents list go ahead and
+ * free them here in a safe way.  This is to be coupled with any uses of
+ * btrfs_free_extent_map_safe().
+ */
+void btrfs_free_pending_extent_maps(struct extent_map_tree *tree)
+{
+	struct extent_map *em;
+
+	/* Avoid taking the write lock if we don't have any pending frees. */
+	if (!test_and_clear_bit(EXTENT_MAP_TREE_PENDING_FREES, &tree->flags))
+		return;
+
+	write_lock(&tree->lock);
+	while ((em = list_first_entry_or_null(&tree->freed_extents,
+					      struct extent_map, free_list))) {
+		list_del_init(&em->free_list);
+		write_unlock(&tree->lock);
+		free_extent_map(em);
+		cond_resched();
+		write_lock(&tree->lock);
+	}
+	write_unlock(&tree->lock);
+}
+
 /* Do the math around the end of an extent, handling wrapping. */
 static u64 range_end(u64 start, u64 len)
 {
@@ -784,7 +851,7 @@ static void drop_all_extent_maps_fast(struct btrfs_inode *inode)
 		em = rb_entry(node, struct extent_map, rb_node);
 		em->flags &= ~(EXTENT_FLAG_PINNED | EXTENT_FLAG_LOGGING);
 		btrfs_remove_extent_mapping(inode, em);
-		btrfs_free_extent_map(em);
+		btrfs_free_extent_map_safe(tree, em);
 
 		if (cond_resched_rwlock_write(&tree->lock))
 			node = rb_first(&tree->root);
@@ -792,6 +859,8 @@ static void drop_all_extent_maps_fast(struct btrfs_inode *inode)
 			node = next;
 	}
 	write_unlock(&tree->lock);
+
+	btrfs_free_pending_extent_maps(tree);
 }
 
 /*
@@ -986,13 +1055,14 @@ void btrfs_drop_extent_map_range(struct btrfs_inode *inode, u64 start, u64 end,
 		btrfs_free_extent_map(em);
 next:
 		/* Once for us (for our lookup reference). */
-		btrfs_free_extent_map(em);
+		btrfs_free_extent_map_safe(em_tree, em);
 
 		em = next_em;
 	}
 
 	write_unlock(&em_tree->lock);
 
+	btrfs_free_pending_extent_maps(em_tree);
 	btrfs_free_extent_map(split);
 	btrfs_free_extent_map(split2);
 }
diff --git a/fs/btrfs/extent_map.h b/fs/btrfs/extent_map.h
index 6f685f3c9327..a962012be1c3 100644
--- a/fs/btrfs/extent_map.h
+++ b/fs/btrfs/extent_map.h
@@ -97,11 +97,18 @@ struct extent_map {
 	u32 flags;
 	refcount_t refs;
 	struct list_head list;
+	struct list_head free_list;
+};
+
+enum extent_map_flags {
+	EXTENT_MAP_TREE_PENDING_FREES,
 };
 
 struct extent_map_tree {
 	struct rb_root root;
+	unsigned long flags;
 	struct list_head modified_extents;
+	struct list_head freed_extents;
 	rwlock_t lock;
 };
 
@@ -175,6 +182,9 @@ int btrfs_split_extent_map(struct btrfs_inode *inode, u64 start, u64 len, u64 pr
 
 struct extent_map *btrfs_alloc_extent_map(void);
 void btrfs_free_extent_map(struct extent_map *em);
+void btrfs_free_extent_map_safe(struct extent_map_tree *tree,
+				struct extent_map *em);
+void btrfs_free_pending_extent_maps(struct extent_map_tree *tree);
 int __init btrfs_extent_map_init(void);
 void __cold btrfs_extent_map_exit(void);
 int btrfs_unpin_extent_cache(struct btrfs_inode *inode, u64 start, u64 len, u64 gen);
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
index e1bd03ebfd98..4034c04d4d63 100644
--- a/fs/btrfs/tree-log.c
+++ b/fs/btrfs/tree-log.c
@@ -5383,7 +5383,7 @@ static int btrfs_log_changed_extents(struct btrfs_trans_handle *trans,
 		 */
 		if (ret) {
 			btrfs_clear_em_logging(inode, em);
-			btrfs_free_extent_map(em);
+			btrfs_free_extent_map_safe(tree, em);
 			continue;
 		}
 
@@ -5392,11 +5392,13 @@ static int btrfs_log_changed_extents(struct btrfs_trans_handle *trans,
 		ret = log_one_extent(trans, inode, em, path, ctx);
 		write_lock(&tree->lock);
 		btrfs_clear_em_logging(inode, em);
-		btrfs_free_extent_map(em);
+		btrfs_free_extent_map_safe(tree, em);
 	}
 	WARN_ON(!list_empty(&extents));
 	write_unlock(&tree->lock);
 
+	btrfs_free_pending_extent_maps(tree);
+
 	if (!ret)
 		ret = btrfs_log_prealloc_extents(trans, inode, path, ctx);
 	if (ret)
-- 
2.51.0


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

* [PATCH v6 10/43] btrfs: start using fscrypt hooks
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (8 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 09/43] btrfs: add infrastructure for safe em freeing Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-08 15:44   ` Chris Mason
  2026-02-06 18:22 ` [PATCH v6 11/43] btrfs: add inode encryption contexts Daniel Vacek
                   ` (34 subsequent siblings)
  44 siblings, 1 reply; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel, Omar Sandoval, Sweet Tea Dorminy

From: Omar Sandoval <osandov@osandov.com>

In order to appropriately encrypt, create, open, rename, and various
symlink operations must call fscrypt hooks. These determine whether the
inode should be encrypted and do other preparatory actions. The
superblock must have fscrypt operations registered, so implement the
minimal set also, and introduce the new fscrypt.[ch] files to hold the
fscrypt-specific functionality.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/333de15efb2d7ec220293c4b5e3782fe85634c06.1706116485.git.josef@toxicpanda.com/
 * FSCrypt info moved from VFS inode to FS specific inode structure.
 * Trivial renames.
---
 fs/btrfs/Makefile      |   1 +
 fs/btrfs/btrfs_inode.h |   4 ++
 fs/btrfs/file.c        |   3 ++
 fs/btrfs/fscrypt.c     |  10 ++++
 fs/btrfs/fscrypt.h     |  10 ++++
 fs/btrfs/inode.c       | 109 +++++++++++++++++++++++++++++++++--------
 fs/btrfs/super.c       |   2 +
 7 files changed, 118 insertions(+), 21 deletions(-)
 create mode 100644 fs/btrfs/fscrypt.c
 create mode 100644 fs/btrfs/fscrypt.h

diff --git a/fs/btrfs/Makefile b/fs/btrfs/Makefile
index 743d7677b175..715ba42f48dc 100644
--- a/fs/btrfs/Makefile
+++ b/fs/btrfs/Makefile
@@ -39,6 +39,7 @@ btrfs-$(CONFIG_BTRFS_FS_POSIX_ACL) += acl.o
 btrfs-$(CONFIG_BTRFS_DEBUG) += ref-verify.o
 btrfs-$(CONFIG_BLK_DEV_ZONED) += zoned.o
 btrfs-$(CONFIG_FS_VERITY) += verity.o
+btrfs-$(CONFIG_FS_ENCRYPTION) += fscrypt.o
 
 btrfs-$(CONFIG_BTRFS_FS_RUN_SANITY_TESTS) += tests/free-space-tests.o \
 	tests/extent-buffer-tests.o tests/btrfs-tests.o \
diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h
index 73602ee8de3f..bf638a6a0973 100644
--- a/fs/btrfs/btrfs_inode.h
+++ b/fs/btrfs/btrfs_inode.h
@@ -342,6 +342,9 @@ struct btrfs_inode {
 #ifdef CONFIG_FS_VERITY
 	struct fsverity_info *i_verity_info;
 #endif
+#ifdef CONFIG_FS_ENCRYPTION
+	struct fscrypt_inode_info *i_crypt_info;
+#endif
 
 	struct inode vfs_inode;
 };
@@ -586,6 +589,7 @@ struct btrfs_new_inode_args {
 	struct posix_acl *default_acl;
 	struct posix_acl *acl;
 	struct fscrypt_name fname;
+	bool encrypt;
 };
 
 int btrfs_new_inode_prepare(struct btrfs_new_inode_args *args,
diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index 56ece1109832..3c0db279f592 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -3812,6 +3812,9 @@ static int btrfs_file_open(struct inode *inode, struct file *filp)
 		return -EIO;
 
 	filp->f_mode |= FMODE_NOWAIT | FMODE_CAN_ODIRECT;
+	ret = fscrypt_file_open(inode, filp);
+	if (ret)
+		return ret;
 
 	ret = fsverity_file_open(inode, filp);
 	if (ret)
diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
new file mode 100644
index 000000000000..6cfba7d94e72
--- /dev/null
+++ b/fs/btrfs/fscrypt.c
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "ctree.h"
+#include "btrfs_inode.h"
+#include "fscrypt.h"
+
+const struct fscrypt_operations btrfs_fscrypt_ops = {
+	.inode_info_offs = (int)offsetof(struct btrfs_inode, i_crypt_info) -
+			   (int)offsetof(struct btrfs_inode, vfs_inode),
+};
diff --git a/fs/btrfs/fscrypt.h b/fs/btrfs/fscrypt.h
new file mode 100644
index 000000000000..7f4e6888bd43
--- /dev/null
+++ b/fs/btrfs/fscrypt.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef BTRFS_FSCRYPT_H
+#define BTRFS_FSCRYPT_H
+
+#include <linux/fscrypt.h>
+
+extern const struct fscrypt_operations btrfs_fscrypt_ops;
+
+#endif /* BTRFS_FSCRYPT_H */
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 10609b8199a0..fdc07612af6e 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -5446,6 +5446,10 @@ static int btrfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
 	if (ret)
 		return ret;
 
+	ret = fscrypt_prepare_setattr(dentry, attr);
+	if (ret)
+		return ret;
+
 	if (S_ISREG(inode->i_mode) && (attr->ia_valid & ATTR_SIZE)) {
 		ret = btrfs_setsize(inode, attr);
 		if (ret)
@@ -5600,11 +5604,8 @@ void btrfs_evict_inode(struct inode *inode)
 
 	trace_btrfs_inode_evict(inode);
 
-	if (!root) {
-		fsverity_cleanup_inode(inode);
-		clear_inode(inode);
-		return;
-	}
+	if (!root)
+		goto cleanup;
 
 	fs_info = inode_to_fs_info(inode);
 	evict_inode_truncate_pages(inode);
@@ -5704,6 +5705,9 @@ void btrfs_evict_inode(struct inode *inode)
 	 * to retry these periodically in the future.
 	 */
 	btrfs_remove_delayed_node(BTRFS_I(inode));
+
+cleanup:
+	fscrypt_put_encryption_info(inode);
 	fsverity_cleanup_inode(inode);
 	clear_inode(inode);
 }
@@ -6482,6 +6486,12 @@ int btrfs_new_inode_prepare(struct btrfs_new_inode_args *args,
 		return ret;
 	}
 
+	ret = fscrypt_prepare_new_inode(dir, inode, &args->encrypt);
+	if (ret) {
+		fscrypt_free_filename(&args->fname);
+		return ret;
+	}
+
 	/* 1 to add inode item */
 	*trans_num_items = 1;
 	/* 1 to add compression property */
@@ -6974,9 +6984,13 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir,
 	if (inode->i_nlink >= BTRFS_LINK_MAX)
 		return -EMLINK;
 
+	ret = fscrypt_prepare_link(old_dentry, dir, dentry);
+	if (ret)
+		return ret;
+
 	ret = fscrypt_setup_filename(dir, &dentry->d_name, 0, &fname);
 	if (ret)
-		goto fail;
+		return ret;
 
 	ret = btrfs_set_inode_index(BTRFS_I(dir), &index);
 	if (ret)
@@ -8035,6 +8049,9 @@ struct inode *btrfs_alloc_inode(struct super_block *sb)
 	INIT_LIST_HEAD(&ei->delayed_iput);
 	init_rwsem(&ei->i_mmap_lock);
 
+#ifdef CONFIG_FS_ENCRYPTION
+	ei->i_crypt_info = NULL;
+#endif
 	return inode;
 }
 
@@ -8050,6 +8067,7 @@ void btrfs_test_destroy_inode(struct inode *inode)
 void btrfs_free_inode(struct inode *inode)
 {
 	kfree(BTRFS_I(inode)->file_extent_tree);
+	fscrypt_free_inode(inode);
 	kmem_cache_free(btrfs_inode_cachep, BTRFS_I(inode));
 }
 
@@ -8121,8 +8139,7 @@ int btrfs_drop_inode(struct inode *inode)
 	/* the snap/subvol tree is on deleting */
 	if (btrfs_root_refs(&root->root_item) == 0)
 		return 1;
-	else
-		return inode_generic_drop(inode);
+	return inode_generic_drop(inode) || fscrypt_drop_inode(inode);
 }
 
 static void init_once(void *foo)
@@ -8776,6 +8793,10 @@ static int btrfs_rename2(struct mnt_idmap *idmap, struct inode *old_dir,
 	if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE | RENAME_WHITEOUT))
 		return -EINVAL;
 
+	ret = fscrypt_prepare_rename(old_dir, old_dentry, new_dir, new_dentry, flags);
+	if (ret)
+		return ret;
+
 	if (flags & RENAME_EXCHANGE)
 		ret = btrfs_rename_exchange(old_dir, old_dentry, new_dir,
 					    new_dentry);
@@ -8970,20 +8991,28 @@ static int btrfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
 	};
 	unsigned int trans_num_items;
 	int ret;
-	int name_len;
 	int datasize;
 	unsigned long ptr;
 	struct btrfs_file_extent_item *ei;
 	struct extent_buffer *leaf;
+	struct fscrypt_str disk_link;
+	size_t max_len;
+	u32 name_len = strlen(symname);
+
+	/*
+	 * BTRFS_MAX_INLINE_DATA_SIZE() isn't actually telling the truth, we actually
+	 * limit inline data extents to min(BTRFS_MAX_INLINE_DATA_SIZE(), sectorsize),
+	 * so adjust max_len given this wonderful bit of inconsistency.
+	 */
+	max_len = min_t(size_t, BTRFS_MAX_INLINE_DATA_SIZE(fs_info), fs_info->sectorsize);
 
-	name_len = strlen(symname);
 	/*
-	 * Symlinks utilize uncompressed inline extent data, which should not
-	 * reach block size.
+	 * fscrypt sets disk_link.len to be len + 1, including a NUL terminator,
+	 * but we don't store that '\0' character.
 	 */
-	if (name_len > BTRFS_MAX_INLINE_DATA_SIZE(fs_info) ||
-	    name_len >= fs_info->sectorsize)
-		return -ENAMETOOLONG;
+	ret = fscrypt_prepare_symlink(dir, symname, name_len, max_len + 1, &disk_link);
+	if (ret)
+		return ret;
 
 	inode = new_inode(dir->i_sb);
 	if (!inode)
@@ -8992,8 +9021,8 @@ static int btrfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
 	inode->i_op = &btrfs_symlink_inode_operations;
 	inode_nohighmem(inode);
 	inode->i_mapping->a_ops = &btrfs_aops;
-	btrfs_i_size_write(BTRFS_I(inode), name_len);
-	inode_set_bytes(inode, name_len);
+	btrfs_i_size_write(BTRFS_I(inode), disk_link.len - 1);
+	inode_set_bytes(inode, disk_link.len - 1);
 
 	new_inode_args.inode = inode;
 	ret = btrfs_new_inode_prepare(&new_inode_args, &trans_num_items);
@@ -9020,10 +9049,22 @@ static int btrfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
 		inode = NULL;
 		goto out;
 	}
+
+	if (IS_ENCRYPTED(inode)) {
+		ret = fscrypt_encrypt_symlink(inode, symname, name_len, &disk_link);
+		if (ret) {
+			btrfs_abort_transaction(trans, ret);
+			btrfs_free_path(path);
+			discard_new_inode(inode);
+			inode = NULL;
+			goto out;
+		}
+	}
+
 	key.objectid = btrfs_ino(BTRFS_I(inode));
 	key.type = BTRFS_EXTENT_DATA_KEY;
 	key.offset = 0;
-	datasize = btrfs_file_extent_calc_inline_size(name_len);
+	datasize = btrfs_file_extent_calc_inline_size(disk_link.len - 1);
 	ret = btrfs_insert_empty_item(trans, root, path, &key, datasize);
 	if (unlikely(ret)) {
 		btrfs_abort_transaction(trans, ret);
@@ -9041,10 +9082,10 @@ static int btrfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
 	btrfs_set_file_extent_encryption(leaf, ei, 0);
 	btrfs_set_file_extent_compression(leaf, ei, 0);
 	btrfs_set_file_extent_other_encoding(leaf, ei, 0);
-	btrfs_set_file_extent_ram_bytes(leaf, ei, name_len);
+	btrfs_set_file_extent_ram_bytes(leaf, ei, disk_link.len - 1);
 
 	ptr = btrfs_file_extent_inline_start(ei);
-	write_extent_buffer(leaf, symname, ptr, name_len);
+	write_extent_buffer(leaf, disk_link.name, ptr, disk_link.len - 1);
 	btrfs_free_path(path);
 
 	d_instantiate_new(dentry, inode);
@@ -9060,6 +9101,29 @@ static int btrfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
 	return ret;
 }
 
+static const char *btrfs_get_link(struct dentry *dentry, struct inode *inode,
+				  struct delayed_call *done)
+{
+	struct page *cpage;
+	const char *paddr;
+	struct btrfs_fs_info *fs_info = btrfs_sb(inode->i_sb);
+
+	if (!IS_ENCRYPTED(inode))
+		return page_get_link(dentry, inode, done);
+
+	if (!dentry)
+		return ERR_PTR(-ECHILD);
+
+	cpage = read_mapping_page(inode->i_mapping, 0, NULL);
+	if (IS_ERR(cpage))
+		return ERR_CAST(cpage);
+
+	paddr = fscrypt_get_symlink(inode, page_address(cpage),
+				    BTRFS_MAX_INLINE_DATA_SIZE(fs_info), done);
+	put_page(cpage);
+	return paddr;
+}
+
 static struct btrfs_trans_handle *insert_prealloc_file_extent(
 				       struct btrfs_trans_handle *trans_in,
 				       struct btrfs_inode *inode,
@@ -10684,7 +10748,7 @@ static const struct inode_operations btrfs_special_inode_operations = {
 	.update_time	= btrfs_update_time,
 };
 static const struct inode_operations btrfs_symlink_inode_operations = {
-	.get_link	= page_get_link,
+	.get_link	= btrfs_get_link,
 	.getattr	= btrfs_getattr,
 	.setattr	= btrfs_setattr,
 	.permission	= btrfs_permission,
@@ -10694,4 +10758,7 @@ static const struct inode_operations btrfs_symlink_inode_operations = {
 
 const struct dentry_operations btrfs_dentry_operations = {
 	.d_delete	= btrfs_dentry_delete,
+#ifdef CONFIG_FS_ENCRYPTION
+	.d_revalidate	= fscrypt_d_revalidate,
+#endif
 };
diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
index d64d303b6edc..7eb367b1f6f6 100644
--- a/fs/btrfs/super.c
+++ b/fs/btrfs/super.c
@@ -49,6 +49,7 @@
 #include "tests/btrfs-tests.h"
 #include "block-group.h"
 #include "discard.h"
+#include "fscrypt.h"
 #include "qgroup.h"
 #include "raid56.h"
 #include "fs.h"
@@ -969,6 +970,7 @@ static int btrfs_fill_super(struct super_block *sb,
 	sb->s_vop = &btrfs_verityops;
 #endif
 	sb->s_xattr = btrfs_xattr_handlers;
+	fscrypt_set_ops(sb, &btrfs_fscrypt_ops);
 	sb->s_time_gran = 1;
 	sb->s_iflags |= SB_I_CGROUPWB | SB_I_ALLOW_HSM;
 
-- 
2.51.0


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

* [PATCH v6 11/43] btrfs: add inode encryption contexts
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (9 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 10/43] btrfs: start using fscrypt hooks Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-08 15:36   ` Chris Mason
  2026-02-06 18:22 ` [PATCH v6 12/43] btrfs: add new FEATURE_INCOMPAT_ENCRYPT flag Daniel Vacek
                   ` (33 subsequent siblings)
  44 siblings, 1 reply; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel, Omar Sandoval, Sweet Tea Dorminy

From: Omar Sandoval <osandov@osandov.com>

fscrypt stores a context item with encrypted inodes that contains the
related encryption information.  fscrypt provides an arbitrary blob for
the filesystem to store, and it does not clearly fit into an existing
structure, so this goes in a new item type.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/5a88efb484b0874a7430b83bc6e5f6b9aa5858d5.1706116485.git.josef@toxicpanda.com/
 * Shorten the inode context key macro name to BTRFS_FSCRYPT_INODE_CTX_KEY.
---
 fs/btrfs/fscrypt.c              | 116 ++++++++++++++++++++++++++++++++
 fs/btrfs/fscrypt.h              |   2 +
 fs/btrfs/inode.c                |  19 ++++++
 fs/btrfs/ioctl.c                |   8 ++-
 include/uapi/linux/btrfs_tree.h |  10 +++
 5 files changed, 153 insertions(+), 2 deletions(-)

diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
index 6cfba7d94e72..e9b024d671a2 100644
--- a/fs/btrfs/fscrypt.c
+++ b/fs/btrfs/fscrypt.c
@@ -1,10 +1,126 @@
 // SPDX-License-Identifier: GPL-2.0
 
+#include <linux/iversion.h>
 #include "ctree.h"
+#include "accessors.h"
 #include "btrfs_inode.h"
+#include "disk-io.h"
+#include "fs.h"
 #include "fscrypt.h"
+#include "ioctl.h"
+#include "messages.h"
+#include "transaction.h"
+#include "xattr.h"
+
+static int btrfs_fscrypt_get_context(struct inode *inode, void *ctx, size_t len)
+{
+	struct btrfs_key key = {
+		.objectid = btrfs_ino(BTRFS_I(inode)),
+		.type = BTRFS_FSCRYPT_INODE_CTX_KEY,
+		.offset = 0,
+	};
+	struct btrfs_path *path;
+	struct extent_buffer *leaf;
+	unsigned long ptr;
+	int ret;
+
+
+	path = btrfs_alloc_path();
+	if (!path)
+		return -ENOMEM;
+
+	ret = btrfs_search_slot(NULL, BTRFS_I(inode)->root, &key, path, 0, 0);
+	if (ret) {
+		len = -ENOENT;
+		goto out;
+	}
+
+	leaf = path->nodes[0];
+	ptr = btrfs_item_ptr_offset(leaf, path->slots[0]);
+	/* fscrypt provides max context length, but it could be less */
+	len = min_t(size_t, len, btrfs_item_size(leaf, path->slots[0]));
+	read_extent_buffer(leaf, ctx, ptr, len);
+
+out:
+	btrfs_free_path(path);
+	return len;
+}
+
+static int btrfs_fscrypt_set_context(struct inode *inode, const void *ctx,
+				     size_t len, void *fs_data)
+{
+	struct btrfs_trans_handle *trans = fs_data;
+	struct btrfs_key key = {
+		.objectid = btrfs_ino(BTRFS_I(inode)),
+		.type = BTRFS_FSCRYPT_INODE_CTX_KEY,
+		.offset = 0,
+	};
+	struct btrfs_path *path = NULL;
+	struct extent_buffer *leaf;
+	unsigned long ptr;
+	int ret;
+
+	if (!trans)
+		trans = btrfs_start_transaction(BTRFS_I(inode)->root, 2);
+	if (IS_ERR(trans))
+		return PTR_ERR(trans);
+
+	path = btrfs_alloc_path();
+	if (!path) {
+		ret = -ENOMEM;
+		goto out_err;
+	}
+
+	ret = btrfs_search_slot(trans, BTRFS_I(inode)->root, &key, path, 0, 1);
+	if (ret < 0)
+		goto out_err;
+
+	if (ret > 0) {
+		btrfs_release_path(path);
+		ret = btrfs_insert_empty_item(trans, BTRFS_I(inode)->root, path, &key, len);
+		if (ret)
+			goto out_err;
+	}
+
+	leaf = path->nodes[0];
+	ptr = btrfs_item_ptr_offset(leaf, path->slots[0]);
+
+	len = min_t(size_t, len, btrfs_item_size(leaf, path->slots[0]));
+	write_extent_buffer(leaf, ctx, ptr, len);
+	btrfs_mark_buffer_dirty(trans, leaf);
+	btrfs_release_path(path);
+
+	if (fs_data)
+		return ret;
+
+	BTRFS_I(inode)->flags |= BTRFS_INODE_ENCRYPT;
+	btrfs_sync_inode_flags_to_i_flags(BTRFS_I(inode));
+	inode_inc_iversion(inode);
+	inode_set_ctime_current(inode);
+	ret = btrfs_update_inode(trans, BTRFS_I(inode));
+	if (ret)
+		goto out_abort;
+	btrfs_free_path(path);
+	btrfs_end_transaction(trans);
+	return 0;
+out_abort:
+	btrfs_abort_transaction(trans, ret);
+out_err:
+	if (!fs_data)
+		btrfs_end_transaction(trans);
+	btrfs_free_path(path);
+	return ret;
+}
+
+static bool btrfs_fscrypt_empty_dir(struct inode *inode)
+{
+	return inode->i_size == BTRFS_EMPTY_DIR_SIZE;
+}
 
 const struct fscrypt_operations btrfs_fscrypt_ops = {
 	.inode_info_offs = (int)offsetof(struct btrfs_inode, i_crypt_info) -
 			   (int)offsetof(struct btrfs_inode, vfs_inode),
+	.get_context = btrfs_fscrypt_get_context,
+	.set_context = btrfs_fscrypt_set_context,
+	.empty_dir = btrfs_fscrypt_empty_dir,
 };
diff --git a/fs/btrfs/fscrypt.h b/fs/btrfs/fscrypt.h
index 7f4e6888bd43..80adb7e56826 100644
--- a/fs/btrfs/fscrypt.h
+++ b/fs/btrfs/fscrypt.h
@@ -5,6 +5,8 @@
 
 #include <linux/fscrypt.h>
 
+#include "fs.h"
+
 extern const struct fscrypt_operations btrfs_fscrypt_ops;
 
 #endif /* BTRFS_FSCRYPT_H */
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index fdc07612af6e..c06d7cd45be5 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -60,6 +60,7 @@
 #include "defrag.h"
 #include "dir-item.h"
 #include "file-item.h"
+#include "fscrypt.h"
 #include "uuid-tree.h"
 #include "ioctl.h"
 #include "file.h"
@@ -6473,6 +6474,9 @@ int btrfs_new_inode_prepare(struct btrfs_new_inode_args *args,
 	struct inode *inode = args->inode;
 	int ret;
 
+	if (fscrypt_is_nokey_name(args->dentry))
+		return -ENOKEY;
+
 	if (!args->orphan) {
 		ret = fscrypt_setup_filename(dir, &args->dentry->d_name, 0,
 					     &args->fname);
@@ -6508,6 +6512,9 @@ int btrfs_new_inode_prepare(struct btrfs_new_inode_args *args,
 	if (dir->i_security)
 		(*trans_num_items)++;
 #endif
+	/* 1 to add fscrypt item */
+	if (args->encrypt)
+		(*trans_num_items)++;
 	if (args->orphan) {
 		/* 1 to add orphan item */
 		(*trans_num_items)++;
@@ -6701,6 +6708,11 @@ int btrfs_create_new_inode(struct btrfs_trans_handle *trans,
 	BTRFS_I(inode)->i_otime_sec = ts.tv_sec;
 	BTRFS_I(inode)->i_otime_nsec = ts.tv_nsec;
 
+	if (args->encrypt) {
+		BTRFS_I(inode)->flags |= BTRFS_INODE_ENCRYPT;
+		btrfs_sync_inode_flags_to_i_flags(BTRFS_I(inode));
+	}
+
 	/*
 	 * We're going to fill the inode item now, so at this point the inode
 	 * must be fully initialized.
@@ -6774,6 +6786,13 @@ int btrfs_create_new_inode(struct btrfs_trans_handle *trans,
 			goto discard;
 		}
 	}
+	if (args->encrypt) {
+		ret = fscrypt_set_context(inode, trans);
+		if (ret) {
+			btrfs_abort_transaction(trans, ret);
+			goto discard;
+		}
+	}
 
 	ret = btrfs_add_inode_to_root(BTRFS_I(inode), false);
 	if (WARN_ON(ret)) {
diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
index f1b56be6f8f4..9d0716498286 100644
--- a/fs/btrfs/ioctl.c
+++ b/fs/btrfs/ioctl.c
@@ -153,6 +153,8 @@ static unsigned int btrfs_inode_flags_to_fsflags(const struct btrfs_inode *inode
 		iflags |= FS_DIRSYNC_FL;
 	if (flags & BTRFS_INODE_NODATACOW)
 		iflags |= FS_NOCOW_FL;
+	if (flags & BTRFS_INODE_ENCRYPT)
+		iflags |= FS_ENCRYPT_FL;
 	if (ro_flags & BTRFS_INODE_RO_VERITY)
 		iflags |= FS_VERITY_FL;
 
@@ -181,12 +183,14 @@ void btrfs_sync_inode_flags_to_i_flags(struct btrfs_inode *inode)
 		new_fl |= S_NOATIME;
 	if (inode->flags & BTRFS_INODE_DIRSYNC)
 		new_fl |= S_DIRSYNC;
+	if (inode->flags & BTRFS_INODE_ENCRYPT)
+		new_fl |= S_ENCRYPTED;
 	if (inode->ro_flags & BTRFS_INODE_RO_VERITY)
 		new_fl |= S_VERITY;
 
 	set_mask_bits(&inode->vfs_inode.i_flags,
 		      S_SYNC | S_APPEND | S_IMMUTABLE | S_NOATIME | S_DIRSYNC |
-		      S_VERITY, new_fl);
+		      S_VERITY | S_ENCRYPTED, new_fl);
 }
 
 /*
@@ -199,7 +203,7 @@ static int check_fsflags(unsigned int old_flags, unsigned int flags)
 		      FS_NOATIME_FL | FS_NODUMP_FL | \
 		      FS_SYNC_FL | FS_DIRSYNC_FL | \
 		      FS_NOCOMP_FL | FS_COMPR_FL |
-		      FS_NOCOW_FL))
+		      FS_NOCOW_FL | FS_ENCRYPT_FL))
 		return -EOPNOTSUPP;
 
 	/* COMPR and NOCOMP on new/old are valid */
diff --git a/include/uapi/linux/btrfs_tree.h b/include/uapi/linux/btrfs_tree.h
index f7843e6bb978..02b1ab1f7858 100644
--- a/include/uapi/linux/btrfs_tree.h
+++ b/include/uapi/linux/btrfs_tree.h
@@ -167,6 +167,8 @@
 #define BTRFS_VERITY_DESC_ITEM_KEY	36
 #define BTRFS_VERITY_MERKLE_ITEM_KEY	37
 
+#define BTRFS_FSCRYPT_INODE_CTX_KEY	41
+
 #define BTRFS_ORPHAN_ITEM_KEY		48
 /* reserve 2-15 close to the inode for later flexibility */
 
@@ -431,6 +433,7 @@ static inline __u8 btrfs_dir_flags_to_ftype(__u8 flags)
 #define BTRFS_INODE_NOATIME		(1U << 9)
 #define BTRFS_INODE_DIRSYNC		(1U << 10)
 #define BTRFS_INODE_COMPRESS		(1U << 11)
+#define BTRFS_INODE_ENCRYPT		(1U << 12)
 
 #define BTRFS_INODE_ROOT_ITEM_INIT	(1U << 31)
 
@@ -447,6 +450,7 @@ static inline __u8 btrfs_dir_flags_to_ftype(__u8 flags)
 	 BTRFS_INODE_NOATIME |						\
 	 BTRFS_INODE_DIRSYNC |						\
 	 BTRFS_INODE_COMPRESS |						\
+	 BTRFS_INODE_ENCRYPT |						\
 	 BTRFS_INODE_ROOT_ITEM_INIT)
 
 #define BTRFS_INODE_RO_VERITY		(1U << 0)
@@ -1075,6 +1079,12 @@ enum {
 	BTRFS_NR_FILE_EXTENT_TYPES = 3,
 };
 
+enum btrfs_encryption_type {
+	BTRFS_ENCRYPTION_NONE,
+	BTRFS_ENCRYPTION_FSCRYPT,
+	BTRFS_NR_ENCRYPTION_TYPES,
+};
+
 struct btrfs_file_extent_item {
 	/*
 	 * transaction id that created this extent
-- 
2.51.0


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

* [PATCH v6 12/43] btrfs: add new FEATURE_INCOMPAT_ENCRYPT flag
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (10 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 11/43] btrfs: add inode encryption contexts Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-06 18:22 ` [PATCH v6 13/43] btrfs: adapt readdir for encrypted and nokey names Daniel Vacek
                   ` (32 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel, Omar Sandoval, Sweet Tea Dorminy

From: Omar Sandoval <osandov@osandov.com>

As encrypted files will be incompatible with older filesystem versions,
new filesystems should be created with an incompat flag for fscrypt,
which will gate access to the encryption ioctls.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/ccbea52046c1dadbbef926bfc878cc23af952729.1706116485.git.josef@toxicpanda.com/
 * No changes since.
---
 fs/btrfs/fs.h              | 3 ++-
 fs/btrfs/super.c           | 5 +++++
 fs/btrfs/sysfs.c           | 6 ++++++
 include/uapi/linux/btrfs.h | 1 +
 4 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h
index d3762fbe7267..5077b7eed4b8 100644
--- a/fs/btrfs/fs.h
+++ b/fs/btrfs/fs.h
@@ -315,7 +315,8 @@ enum {
 	(BTRFS_FEATURE_INCOMPAT_SUPP_STABLE |	\
 	 BTRFS_FEATURE_INCOMPAT_RAID_STRIPE_TREE | \
 	 BTRFS_FEATURE_INCOMPAT_EXTENT_TREE_V2 | \
-	 BTRFS_FEATURE_INCOMPAT_REMAP_TREE)
+	 BTRFS_FEATURE_INCOMPAT_REMAP_TREE |	\
+	 BTRFS_FEATURE_INCOMPAT_ENCRYPT)
 
 #else
 
diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
index 7eb367b1f6f6..3914abec5b12 100644
--- a/fs/btrfs/super.c
+++ b/fs/btrfs/super.c
@@ -2567,6 +2567,11 @@ static int __init btrfs_print_mod_info(void)
 			", fsverity=yes"
 #else
 			", fsverity=no"
+#endif
+#ifdef CONFIG_FS_ENCRYPTION
+			", fscrypt=yes"
+#else
+			", fscrypt=no"
 #endif
 			;
 
diff --git a/fs/btrfs/sysfs.c b/fs/btrfs/sysfs.c
index 27bfb7b55ec4..98d6b1594d43 100644
--- a/fs/btrfs/sysfs.c
+++ b/fs/btrfs/sysfs.c
@@ -305,6 +305,9 @@ BTRFS_FEAT_ATTR_INCOMPAT(remap_tree, REMAP_TREE);
 #ifdef CONFIG_FS_VERITY
 BTRFS_FEAT_ATTR_COMPAT_RO(verity, VERITY);
 #endif
+#ifdef CONFIG_FS_ENCRYPTION
+BTRFS_FEAT_ATTR_INCOMPAT(encryption, ENCRYPT);
+#endif /* CONFIG_FS_ENCRYPTION */
 
 /*
  * Features which depend on feature bits and may differ between each fs.
@@ -338,6 +341,9 @@ static struct attribute *btrfs_supported_feature_attrs[] = {
 #ifdef CONFIG_FS_VERITY
 	BTRFS_FEAT_ATTR_PTR(verity),
 #endif
+#ifdef CONFIG_FS_ENCRYPTION
+	BTRFS_FEAT_ATTR_PTR(encryption),
+#endif /* CONFIG_FS_ENCRYPTION */
 	NULL
 };
 
diff --git a/include/uapi/linux/btrfs.h b/include/uapi/linux/btrfs.h
index 9165154a274d..2f6a46e5f4ce 100644
--- a/include/uapi/linux/btrfs.h
+++ b/include/uapi/linux/btrfs.h
@@ -335,6 +335,7 @@ struct btrfs_ioctl_fs_info_args {
 #define BTRFS_FEATURE_INCOMPAT_ZONED		(1ULL << 12)
 #define BTRFS_FEATURE_INCOMPAT_EXTENT_TREE_V2	(1ULL << 13)
 #define BTRFS_FEATURE_INCOMPAT_RAID_STRIPE_TREE	(1ULL << 14)
+#define BTRFS_FEATURE_INCOMPAT_ENCRYPT		(1ULL << 15)
 #define BTRFS_FEATURE_INCOMPAT_SIMPLE_QUOTA	(1ULL << 16)
 #define BTRFS_FEATURE_INCOMPAT_REMAP_TREE	(1ULL << 17)
 
-- 
2.51.0


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

* [PATCH v6 13/43] btrfs: adapt readdir for encrypted and nokey names
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (11 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 12/43] btrfs: add new FEATURE_INCOMPAT_ENCRYPT flag Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-08 15:35   ` Chris Mason
  2026-02-06 18:22 ` [PATCH v6 14/43] btrfs: handle " Daniel Vacek
                   ` (31 subsequent siblings)
  44 siblings, 1 reply; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel, Omar Sandoval, Sweet Tea Dorminy

From: Omar Sandoval <osandov@osandov.com>

Deleting an encrypted file must always be permitted, even if the user
does not have the appropriate key. Therefore, for listing an encrypted
directory, so-called 'nokey' names are provided, and these nokey names
must be sufficient to look up and delete the appropriate encrypted
files. See 'struct fscrypt_nokey_name' for more information on the
format of these names.

The first part of supporting nokey names is allowing lookups by nokey
name. Only a few entry points need to support these: deleting a
directory, file, or subvolume -- each of these call
fscrypt_setup_filename() with a '1' argument, indicating that the key is
not required and therefore a nokey name may be provided. If a nokey name
is provided, the fscrypt_name returned by fscrypt_setup_filename() will
not have its disk_name field populated, but will have various other
fields set.

This change alters the relevant codepaths to pass a complete
fscrypt_name anywhere that it might contain a nokey name. When it does
contain a nokey name, the first time the name is successfully matched
to a stored name populates the disk name field of the fscrypt_name,
allowing the caller to use the normal disk name codepaths afterward.
Otherwise, the matching functionality is in close analogue to the
function fscrypt_match_name().

Functions where most callers are providing a fscrypt_str are duplicated
and adapted for a fscrypt_name, and functions where most callers are
providing a fscrypt_name are changed to so require at all callsites.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/52b31cd6e5c86f8fe3d08ce65bca5f40c3ce5c5a.1706116485.git.josef@toxicpanda.com/
 * Adapt to changed prototypes.
 * Return error directly instead of goto out and remove the label in
   btrfs_inode_by_name().
---
 fs/btrfs/btrfs_inode.h   |   2 +-
 fs/btrfs/delayed-inode.c |  25 ++++++-
 fs/btrfs/delayed-inode.h |   5 +-
 fs/btrfs/dir-item.c      |  73 +++++++++++++++---
 fs/btrfs/dir-item.h      |  10 ++-
 fs/btrfs/extent_io.c     |  41 +++++++++++
 fs/btrfs/extent_io.h     |   3 +
 fs/btrfs/fscrypt.c       |  33 +++++++++
 fs/btrfs/fscrypt.h       |  18 +++++
 fs/btrfs/inode.c         | 155 ++++++++++++++++++++++++---------------
 fs/btrfs/root-tree.c     |   9 ++-
 fs/btrfs/root-tree.h     |   2 +-
 fs/btrfs/tree-log.c      |   3 +-
 13 files changed, 301 insertions(+), 78 deletions(-)

diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h
index bf638a6a0973..6a170eb159bd 100644
--- a/fs/btrfs/btrfs_inode.h
+++ b/fs/btrfs/btrfs_inode.h
@@ -563,7 +563,7 @@ struct inode *btrfs_lookup_dentry(struct inode *dir, struct dentry *dentry);
 int btrfs_set_inode_index(struct btrfs_inode *dir, u64 *index);
 int btrfs_unlink_inode(struct btrfs_trans_handle *trans,
 		       struct btrfs_inode *dir, struct btrfs_inode *inode,
-		       const struct fscrypt_str *name);
+		       struct fscrypt_name *name);
 int btrfs_add_link(struct btrfs_trans_handle *trans,
 		   struct btrfs_inode *parent_inode, struct btrfs_inode *inode,
 		   const struct fscrypt_str *name, bool add_backref, u64 index);
diff --git a/fs/btrfs/delayed-inode.c b/fs/btrfs/delayed-inode.c
index 1739a0b29c49..c19213fab3dd 100644
--- a/fs/btrfs/delayed-inode.c
+++ b/fs/btrfs/delayed-inode.c
@@ -1785,7 +1785,9 @@ bool btrfs_should_delete_dir_index(const struct list_head *del_list, u64 index)
 /*
  * Read dir info stored in the delayed tree.
  */
-bool btrfs_readdir_delayed_dir_index(struct dir_context *ctx,
+bool btrfs_readdir_delayed_dir_index(const struct inode *inode,
+				     struct fscrypt_str *fstr,
+				     struct dir_context *ctx,
 				     const struct list_head *ins_list)
 {
 	struct btrfs_dir_item *di;
@@ -1794,6 +1796,7 @@ bool btrfs_readdir_delayed_dir_index(struct dir_context *ctx,
 	char *name;
 	int name_len;
 	unsigned char d_type;
+	size_t fstr_len = fstr->len;
 
 	/*
 	 * Changing the data of the delayed item is impossible. So
@@ -1820,7 +1823,25 @@ bool btrfs_readdir_delayed_dir_index(struct dir_context *ctx,
 		d_type = fs_ftype_to_dtype(btrfs_dir_flags_to_ftype(di->type));
 		btrfs_disk_key_to_cpu(&location, &di->location);
 
-		over = !dir_emit(ctx, name, name_len, location.objectid, d_type);
+		if (di->type & BTRFS_FT_ENCRYPTED) {
+			int ret;
+			struct fscrypt_str iname = FSTR_INIT(name, name_len);
+
+			fstr->len = fstr_len;
+			/*
+			 * The hash is only used when the encryption key is not
+			 * available. But if we have delayed insertions, then we
+			 * must have the encryption key available or we wouldn't
+			 * have been able to create entries in the directory.
+			 * So, we don't calculate the hash.
+			 */
+			ret = fscrypt_fname_disk_to_usr(inode, 0, 0, &iname, fstr);
+			if (ret)
+				return ret;
+			over = !dir_emit(ctx, fstr->name, fstr->len, location.objectid, d_type);
+		} else {
+			over = !dir_emit(ctx, name, name_len, location.objectid, d_type);
+		}
 
 		if (refcount_dec_and_test(&curr->refs))
 			kfree(curr);
diff --git a/fs/btrfs/delayed-inode.h b/fs/btrfs/delayed-inode.h
index fc752863f89b..9bff11478632 100644
--- a/fs/btrfs/delayed-inode.h
+++ b/fs/btrfs/delayed-inode.h
@@ -19,6 +19,7 @@
 #include <linux/ref_tracker.h>
 #include "ctree.h"
 
+struct fscrypt_str;
 struct btrfs_disk_key;
 struct btrfs_fs_info;
 struct btrfs_inode;
@@ -159,7 +160,9 @@ void btrfs_readdir_put_delayed_items(struct btrfs_inode *inode,
 				     struct list_head *ins_list,
 				     struct list_head *del_list);
 bool btrfs_should_delete_dir_index(const struct list_head *del_list, u64 index);
-bool btrfs_readdir_delayed_dir_index(struct dir_context *ctx,
+bool btrfs_readdir_delayed_dir_index(const struct inode *inode,
+				     struct fscrypt_str *fstr,
+				     struct dir_context *ctx,
 				     const struct list_head *ins_list);
 
 /* Used during directory logging. */
diff --git a/fs/btrfs/dir-item.c b/fs/btrfs/dir-item.c
index 085a83ae9e62..6e10dd4a4e9e 100644
--- a/fs/btrfs/dir-item.c
+++ b/fs/btrfs/dir-item.c
@@ -6,6 +6,7 @@
 #include "messages.h"
 #include "ctree.h"
 #include "disk-io.h"
+#include "fscrypt.h"
 #include "transaction.h"
 #include "accessors.h"
 #include "dir-item.h"
@@ -227,6 +228,47 @@ struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans,
 	return di;
 }
 
+/*
+ * Lookup for a directory item by fscrypt_name.
+ *
+ * @trans:	The transaction handle to use.
+ * @root:	The root of the target tree.
+ * @path:	Path to use for the search.
+ * @dir:	The inode number (objectid) of the directory.
+ * @name:	The fscrypt_name associated to the directory entry
+ * @mod:	Used to indicate if the tree search is meant for a read only
+ *		lookup or for a deletion lookup, so its value should be 0 or
+ *		-1, respectively.
+ *
+ * Returns: NULL if the dir item does not exists, an error pointer if an error
+ * happened, or a pointer to a dir item if a dir item exists for the given name.
+ */
+struct btrfs_dir_item *btrfs_lookup_dir_item_fname(struct btrfs_trans_handle *trans,
+						   struct btrfs_root *root,
+						   struct btrfs_path *path, u64 dir,
+						   struct fscrypt_name *name, int mod)
+{
+	struct btrfs_key key;
+	struct btrfs_dir_item *di = NULL;
+	int ret = 0;
+
+	key.objectid = dir;
+	key.type = BTRFS_DIR_ITEM_KEY;
+	key.offset = btrfs_name_hash(name->disk_name.name, name->disk_name.len);
+	/* XXX get the right hash for no-key names */
+
+	ret = btrfs_search_slot(trans, root, &key, path, mod, -mod);
+	if (ret == 0)
+		di = btrfs_match_dir_item_fname(path, name);
+
+	if (ret == -ENOENT || (di && IS_ERR(di) && PTR_ERR(di) == -ENOENT))
+		return NULL;
+	if (ret < 0)
+		di = ERR_PTR(ret);
+
+	return di;
+}
+
 int btrfs_check_dir_item_collision(struct btrfs_root *root, u64 dir_ino,
 				   const struct fscrypt_str *name)
 {
@@ -278,9 +320,9 @@ int btrfs_check_dir_item_collision(struct btrfs_root *root, u64 dir_ino,
 }
 
 /*
- * Lookup for a directory index item by name and index number.
+ * Lookup for a directory index item by fscrypt_name and index number.
  *
- * @trans:	The transaction handle to use. Can be NULL if @mod is 0.
+ * @trans:	The transaction handle to use.
  * @root:	The root of the target tree.
  * @path:	Path to use for the search.
  * @dir:	The inode number (objectid) of the directory.
@@ -318,7 +360,7 @@ btrfs_lookup_dir_index_item(struct btrfs_trans_handle *trans,
 
 struct btrfs_dir_item *
 btrfs_search_dir_index_item(struct btrfs_root *root, struct btrfs_path *path,
-			    u64 dirid, const struct fscrypt_str *name)
+			    u64 dirid, struct fscrypt_name *name)
 {
 	struct btrfs_dir_item *di;
 	struct btrfs_key key;
@@ -331,8 +373,7 @@ btrfs_search_dir_index_item(struct btrfs_root *root, struct btrfs_path *path,
 	btrfs_for_each_slot(root, &key, &key, path, ret) {
 		if (key.objectid != dirid || key.type != BTRFS_DIR_INDEX_KEY)
 			break;
-
-		di = btrfs_match_dir_item_name(path, name->name, name->len);
+		di = btrfs_match_dir_item_fname(path, name);
 		if (di)
 			return di;
 	}
@@ -368,8 +409,8 @@ struct btrfs_dir_item *btrfs_lookup_xattr(struct btrfs_trans_handle *trans,
  * this walks through all the entries in a dir item and finds one
  * for a specific name.
  */
-struct btrfs_dir_item *btrfs_match_dir_item_name(const struct btrfs_path *path,
-						 const char *name, int name_len)
+struct btrfs_dir_item *btrfs_match_dir_item_fname(const struct btrfs_path *path,
+						  struct fscrypt_name *name)
 {
 	struct btrfs_dir_item *dir_item;
 	unsigned long name_ptr;
@@ -388,8 +429,8 @@ struct btrfs_dir_item *btrfs_match_dir_item_name(const struct btrfs_path *path,
 			btrfs_dir_data_len(leaf, dir_item);
 		name_ptr = (unsigned long)(dir_item + 1);
 
-		if (btrfs_dir_name_len(leaf, dir_item) == name_len &&
-		    memcmp_extent_buffer(leaf, name, name_ptr, name_len) == 0)
+		if (btrfs_fscrypt_match_name(name, leaf, name_ptr,
+					     btrfs_dir_name_len(leaf, dir_item)))
 			return dir_item;
 
 		cur += this_len;
@@ -399,6 +440,20 @@ struct btrfs_dir_item *btrfs_match_dir_item_name(const struct btrfs_path *path,
 	return NULL;
 }
 
+/*
+ * helper function to look at the directory item pointed to by 'path'
+ * this walks through all the entries in a dir item and finds one
+ * for a specific name.
+ */
+struct btrfs_dir_item *btrfs_match_dir_item_name(const struct btrfs_path *path,
+						 const char *name, int name_len)
+{
+	struct fscrypt_name fname = {
+		.disk_name = FSTR_INIT((char *) name, name_len)
+	};
+	return btrfs_match_dir_item_fname(path, &fname);
+}
+
 /*
  * given a pointer into a directory item, delete it.  This
  * handles items that have more than one entry in them.
diff --git a/fs/btrfs/dir-item.h b/fs/btrfs/dir-item.h
index e52174a8baf9..f89d12ee28a7 100644
--- a/fs/btrfs/dir-item.h
+++ b/fs/btrfs/dir-item.h
@@ -7,6 +7,7 @@
 #include <linux/crc32c.h>
 
 struct fscrypt_str;
+struct fscrypt_name;
 struct btrfs_fs_info;
 struct btrfs_key;
 struct btrfs_path;
@@ -23,6 +24,11 @@ struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans,
 					     struct btrfs_root *root,
 					     struct btrfs_path *path, u64 dir,
 					     const struct fscrypt_str *name, int mod);
+struct btrfs_dir_item *btrfs_lookup_dir_item_fname(
+			struct btrfs_trans_handle *trans,
+			struct btrfs_root *root,
+			struct btrfs_path *path, u64 dir,
+			struct fscrypt_name *name, int mod);
 struct btrfs_dir_item *btrfs_lookup_dir_index_item(
 			struct btrfs_trans_handle *trans,
 			struct btrfs_root *root,
@@ -30,7 +36,7 @@ struct btrfs_dir_item *btrfs_lookup_dir_index_item(
 			u64 index, const struct fscrypt_str *name, int mod);
 struct btrfs_dir_item *btrfs_search_dir_index_item(struct btrfs_root *root,
 			    struct btrfs_path *path, u64 dirid,
-			    const struct fscrypt_str *name);
+			    struct fscrypt_name *name);
 int btrfs_delete_one_dir_name(struct btrfs_trans_handle *trans,
 			      struct btrfs_root *root,
 			      struct btrfs_path *path,
@@ -48,6 +54,8 @@ struct btrfs_dir_item *btrfs_lookup_xattr(struct btrfs_trans_handle *trans,
 struct btrfs_dir_item *btrfs_match_dir_item_name(const struct btrfs_path *path,
 						 const char *name,
 						 int name_len);
+struct btrfs_dir_item *btrfs_match_dir_item_fname(const struct btrfs_path *path,
+						  struct fscrypt_name *name);
 
 static inline u64 btrfs_name_hash(const char *name, int len)
 {
diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c
index 3df399dc8856..a440cc550ece 100644
--- a/fs/btrfs/extent_io.c
+++ b/fs/btrfs/extent_io.c
@@ -4100,6 +4100,47 @@ static void assert_eb_folio_uptodate(const struct extent_buffer *eb, int i)
 	}
 }
 
+/* Take a sha256 of a portion of an extent buffer. */
+void extent_buffer_sha256(const struct extent_buffer *eb,
+			  unsigned long start,
+			  unsigned long len, u8 *out)
+{
+	size_t cur;
+	size_t offset;
+	char *kaddr;
+	unsigned long i = get_eb_folio_index(eb, start);
+	struct sha256_ctx sctx;
+
+	if (check_eb_range(eb, start, len))
+		return;
+
+	if (eb->addr) {
+		sha256(eb->addr + start, len, out);
+		return;
+	}
+
+	offset = get_eb_offset_in_folio(eb, start);
+
+	/*
+	 * TODO: This should maybe be using the crypto API, not the fallback,
+	 * but fscrypt uses the fallback and this is only used in emulation of
+	 * fscrypt's buffer sha256 method.
+	 */
+	sha256_init(&sctx);
+	while (len > 0) {
+		assert_eb_folio_uptodate(eb, i);
+
+		cur = min(len, PAGE_SIZE - offset);
+		kaddr = folio_address(eb->folios[i]);
+		sha256_update(&sctx, (u8 *)(kaddr + offset), cur);
+
+		len -= cur;
+		offset = 0;
+		i++;
+	}
+	sha256_final(&sctx, out);
+}
+
 static void __write_extent_buffer(const struct extent_buffer *eb,
 				  const void *srcv, unsigned long start,
 				  unsigned long len, bool use_memmove)
diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h
index 73571d5d3d5a..21ec92f0a868 100644
--- a/fs/btrfs/extent_io.h
+++ b/fs/btrfs/extent_io.h
@@ -305,6 +305,9 @@ static inline int extent_buffer_uptodate(const struct extent_buffer *eb)
 
 int memcmp_extent_buffer(const struct extent_buffer *eb, const void *ptrv,
 			 unsigned long start, unsigned long len);
+void extent_buffer_sha256(const struct extent_buffer *eb,
+			  unsigned long start,
+			  unsigned long len, u8 *out);
 void read_extent_buffer(const struct extent_buffer *eb, void *dst,
 			unsigned long start,
 			unsigned long len);
diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
index e9b024d671a2..99b87776ce51 100644
--- a/fs/btrfs/fscrypt.c
+++ b/fs/btrfs/fscrypt.c
@@ -1,17 +1,50 @@
 // SPDX-License-Identifier: GPL-2.0
 
 #include <linux/iversion.h>
+#include <crypto/sha2.h>
 #include "ctree.h"
 #include "accessors.h"
 #include "btrfs_inode.h"
 #include "disk-io.h"
+#include "ioctl.h"
 #include "fs.h"
 #include "fscrypt.h"
 #include "ioctl.h"
 #include "messages.h"
+#include "root-tree.h"
 #include "transaction.h"
 #include "xattr.h"
 
+/*
+ * This function is extremely similar to fscrypt_match_name() but uses an
+ * extent_buffer.
+ */
+bool btrfs_fscrypt_match_name(struct fscrypt_name *fname,
+			      struct extent_buffer *leaf, unsigned long de_name,
+			      u32 de_name_len)
+{
+	const struct fscrypt_nokey_name *nokey_name =
+		(const struct fscrypt_nokey_name *)fname->crypto_buf.name;
+	u8 digest[SHA256_DIGEST_SIZE];
+
+	if (likely(fname->disk_name.name)) {
+		if (de_name_len != fname->disk_name.len)
+			return false;
+		return !memcmp_extent_buffer(leaf, fname->disk_name.name, de_name, de_name_len);
+	}
+
+	if (de_name_len <= sizeof(nokey_name->bytes))
+		return false;
+
+	if (memcmp_extent_buffer(leaf, nokey_name->bytes, de_name, sizeof(nokey_name->bytes)))
+		return false;
+
+	extent_buffer_sha256(leaf, de_name + sizeof(nokey_name->bytes),
+			     de_name_len - sizeof(nokey_name->bytes), digest);
+
+	return !memcmp(digest, nokey_name->sha256, sizeof(digest));
+}
+
 static int btrfs_fscrypt_get_context(struct inode *inode, void *ctx, size_t len)
 {
 	struct btrfs_key key = {
diff --git a/fs/btrfs/fscrypt.h b/fs/btrfs/fscrypt.h
index 80adb7e56826..4e9c87290158 100644
--- a/fs/btrfs/fscrypt.h
+++ b/fs/btrfs/fscrypt.h
@@ -4,9 +4,27 @@
 #define BTRFS_FSCRYPT_H
 
 #include <linux/fscrypt.h>
+#include "extent_map.h"
 
 #include "fs.h"
 
+#ifdef CONFIG_FS_ENCRYPTION
+bool btrfs_fscrypt_match_name(struct fscrypt_name *fname,
+			      struct extent_buffer *leaf,
+			      unsigned long de_name, u32 de_name_len);
+
+#else
+static inline bool btrfs_fscrypt_match_name(struct fscrypt_name *fname,
+					    struct extent_buffer *leaf,
+					    unsigned long de_name,
+					    u32 de_name_len)
+{
+	if (de_name_len != fname_len(fname))
+		return false;
+	return !memcmp_extent_buffer(leaf, fname->disk_name.name, de_name, de_name_len);
+}
+#endif /* CONFIG_FS_ENCRYPTION */
+
 extern const struct fscrypt_operations btrfs_fscrypt_ops;
 
 #endif /* BTRFS_FSCRYPT_H */
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index c06d7cd45be5..79f0b67249de 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -4362,7 +4362,7 @@ static void update_time_after_link_or_unlink(struct btrfs_inode *dir)
 static int __btrfs_unlink_inode(struct btrfs_trans_handle *trans,
 				struct btrfs_inode *dir,
 				struct btrfs_inode *inode,
-				const struct fscrypt_str *name,
+				struct fscrypt_name *name,
 				struct btrfs_rename_ctx *rename_ctx)
 {
 	struct btrfs_root *root = dir->root;
@@ -4378,7 +4378,7 @@ static int __btrfs_unlink_inode(struct btrfs_trans_handle *trans,
 	if (!path)
 		return -ENOMEM;
 
-	di = btrfs_lookup_dir_item(trans, root, path, dir_ino, name, -1);
+	di = btrfs_lookup_dir_item_fname(trans, root, path, dir_ino, name, -1);
 	if (IS_ERR_OR_NULL(di)) {
 		btrfs_free_path(path);
 		return di ? PTR_ERR(di) : -ENOENT;
@@ -4410,11 +4410,13 @@ static int __btrfs_unlink_inode(struct btrfs_trans_handle *trans,
 		}
 	}
 
-	ret = btrfs_del_inode_ref(trans, root, name, ino, dir_ino, &index);
+	ret = btrfs_del_inode_ref(trans, root, &name->disk_name, ino, dir_ino, &index);
 	if (unlikely(ret)) {
+		/* This should print a base-64 encoded name if relevant? */
 		btrfs_crit(fs_info,
 	   "failed to delete reference to %.*s, root %llu inode %llu parent %llu",
-			   name->len, name->name, btrfs_root_id(root), ino, dir_ino);
+			   name->disk_name.len, name->disk_name.name,
+			   btrfs_root_id(root), ino, dir_ino);
 		btrfs_abort_transaction(trans, ret);
 		return ret;
 	}
@@ -4435,8 +4437,8 @@ static int __btrfs_unlink_inode(struct btrfs_trans_handle *trans,
 	 * operations on the log tree, increasing latency for applications.
 	 */
 	if (!rename_ctx) {
-		btrfs_del_inode_ref_in_log(trans, name, inode, dir);
-		btrfs_del_dir_entries_in_log(trans, name, dir, index);
+		btrfs_del_inode_ref_in_log(trans, &name->disk_name, inode, dir);
+		btrfs_del_dir_entries_in_log(trans, &name->disk_name, dir, index);
 	}
 
 	/*
@@ -4450,7 +4452,7 @@ static int __btrfs_unlink_inode(struct btrfs_trans_handle *trans,
 	 */
 	btrfs_run_delayed_iput(fs_info, inode);
 
-	btrfs_i_size_write(dir, dir->vfs_inode.i_size - name->len * 2);
+	btrfs_i_size_write(dir, dir->vfs_inode.i_size - name->disk_name.len * 2);
 	inode_inc_iversion(&inode->vfs_inode);
 	inode_set_ctime_current(&inode->vfs_inode);
 	inode_inc_iversion(&dir->vfs_inode);
@@ -4461,7 +4463,7 @@ static int __btrfs_unlink_inode(struct btrfs_trans_handle *trans,
 
 int btrfs_unlink_inode(struct btrfs_trans_handle *trans,
 		       struct btrfs_inode *dir, struct btrfs_inode *inode,
-		       const struct fscrypt_str *name)
+		       struct fscrypt_name *name)
 {
 	int ret;
 
@@ -4508,11 +4510,9 @@ static int btrfs_unlink(struct inode *dir, struct dentry *dentry)
 		goto fscrypt_free;
 	}
 
-	btrfs_record_unlink_dir(trans, BTRFS_I(dir), BTRFS_I(d_inode(dentry)),
-				false);
+	btrfs_record_unlink_dir(trans, BTRFS_I(dir), BTRFS_I(d_inode(dentry)), false);
 
-	ret = btrfs_unlink_inode(trans, BTRFS_I(dir), BTRFS_I(d_inode(dentry)),
-				 &fname.disk_name);
+	ret = btrfs_unlink_inode(trans, BTRFS_I(dir), BTRFS_I(d_inode(dentry)), &fname);
 	if (ret)
 		goto end_trans;
 
@@ -4549,8 +4549,6 @@ static int btrfs_unlink_subvol(struct btrfs_trans_handle *trans,
 	if (ret)
 		return ret;
 
-	/* This needs to handle no-key deletions later on */
-
 	if (btrfs_ino(inode) == BTRFS_FIRST_FREE_OBJECTID) {
 		objectid = btrfs_root_id(inode->root);
 	} else if (btrfs_ino(inode) == BTRFS_EMPTY_SUBVOL_DIR_OBJECTID) {
@@ -4567,8 +4565,7 @@ static int btrfs_unlink_subvol(struct btrfs_trans_handle *trans,
 		goto out;
 	}
 
-	di = btrfs_lookup_dir_item(trans, root, path, dir_ino,
-				   &fname.disk_name, -1);
+	di = btrfs_lookup_dir_item_fname(trans, root, path, dir_ino, &fname, -1);
 	if (IS_ERR_OR_NULL(di)) {
 		ret = di ? PTR_ERR(di) : -ENOENT;
 		goto out;
@@ -4594,7 +4591,7 @@ static int btrfs_unlink_subvol(struct btrfs_trans_handle *trans,
 	 * call btrfs_del_root_ref, and it _shouldn't_ fail.
 	 */
 	if (btrfs_ino(inode) == BTRFS_EMPTY_SUBVOL_DIR_OBJECTID) {
-		di = btrfs_search_dir_index_item(root, path, dir_ino, &fname.disk_name);
+		di = btrfs_search_dir_index_item(root, path, dir_ino, &fname);
 		if (IS_ERR(di)) {
 			ret = PTR_ERR(di);
 			btrfs_abort_transaction(trans, ret);
@@ -4608,7 +4605,7 @@ static int btrfs_unlink_subvol(struct btrfs_trans_handle *trans,
 	} else {
 		ret = btrfs_del_root_ref(trans, objectid,
 					 btrfs_root_id(root), dir_ino,
-					 &index, &fname.disk_name);
+					 &index, &fname);
 		if (unlikely(ret)) {
 			btrfs_abort_transaction(trans, ret);
 			goto out;
@@ -4919,7 +4916,7 @@ static int btrfs_rmdir(struct inode *vfs_dir, struct dentry *dentry)
 		goto out;
 
 	/* now the directory is empty */
-	ret = btrfs_unlink_inode(trans, dir, inode, &fname.disk_name);
+	ret = btrfs_unlink_inode(trans, dir, inode, &fname);
 	if (!ret)
 		btrfs_i_size_write(inode, 0);
 out:
@@ -5720,36 +5717,23 @@ void btrfs_evict_inode(struct inode *inode)
  * If no dir entries were found, returns -ENOENT.
  * If found a corrupted location in dir entry, returns -EUCLEAN.
  */
-static int btrfs_inode_by_name(struct btrfs_inode *dir, struct dentry *dentry,
+static int btrfs_inode_by_name(struct btrfs_inode *dir, struct fscrypt_name *fname,
 			       struct btrfs_key *location, u8 *type)
 {
 	struct btrfs_dir_item *di;
 	BTRFS_PATH_AUTO_FREE(path);
 	struct btrfs_root *root = dir->root;
 	int ret = 0;
-	struct fscrypt_name fname;
 
 	path = btrfs_alloc_path();
 	if (!path)
 		return -ENOMEM;
 
-	ret = fscrypt_setup_filename(&dir->vfs_inode, &dentry->d_name, 1, &fname);
-	if (ret < 0)
-		return ret;
-	/*
-	 * fscrypt_setup_filename() should never return a positive value, but
-	 * gcc on sparc/parisc thinks it can, so assert that doesn't happen.
-	 */
-	ASSERT(ret == 0);
-
 	/* This needs to handle no-key deletions later on */
 
-	di = btrfs_lookup_dir_item(NULL, root, path, btrfs_ino(dir),
-				   &fname.disk_name, 0);
-	if (IS_ERR_OR_NULL(di)) {
-		ret = di ? PTR_ERR(di) : -ENOENT;
-		goto out;
-	}
+	di = btrfs_lookup_dir_item_fname(NULL, root, path, btrfs_ino(dir), fname, 0);
+	if (IS_ERR_OR_NULL(di))
+		return di ? PTR_ERR(di) : -ENOENT;
 
 	btrfs_dir_item_key_to_cpu(path->nodes[0], di, location);
 	if (unlikely(location->type != BTRFS_INODE_ITEM_KEY &&
@@ -5757,13 +5741,11 @@ static int btrfs_inode_by_name(struct btrfs_inode *dir, struct dentry *dentry,
 		ret = -EUCLEAN;
 		btrfs_warn(root->fs_info,
 "%s gets something invalid in DIR_ITEM (name %s, directory ino %llu, location " BTRFS_KEY_FMT ")",
-			   __func__, fname.disk_name.name, btrfs_ino(dir),
+			   __func__, fname->usr_fname->name, btrfs_ino(dir),
 			   BTRFS_KEY_FMT_VALUE(location));
 	}
 	if (!ret)
 		*type = btrfs_dir_ftype(path->nodes[0], di);
-out:
-	fscrypt_free_filename(&fname);
 	return ret;
 }
 
@@ -6030,20 +6012,27 @@ struct inode *btrfs_lookup_dentry(struct inode *dir, struct dentry *dentry)
 	struct btrfs_root *root = BTRFS_I(dir)->root;
 	struct btrfs_root *sub_root = root;
 	struct btrfs_key location = { 0 };
+	struct fscrypt_name fname;
 	u8 di_type = 0;
 	int ret = 0;
 
 	if (dentry->d_name.len > BTRFS_NAME_LEN)
 		return ERR_PTR(-ENAMETOOLONG);
 
-	ret = btrfs_inode_by_name(BTRFS_I(dir), dentry, &location, &di_type);
-	if (ret < 0)
+	ret = fscrypt_prepare_lookup(dir, dentry, &fname);
+	if (ret)
 		return ERR_PTR(ret);
 
+	ret = btrfs_inode_by_name(BTRFS_I(dir), &fname, &location, &di_type);
+	if (ret < 0) {
+		inode = ERR_PTR(ret);
+		goto cleanup;
+	}
+
 	if (location.type == BTRFS_INODE_ITEM_KEY) {
 		inode = btrfs_iget(location.objectid, root);
 		if (IS_ERR(inode))
-			return ERR_CAST(inode);
+			goto cleanup;
 
 		/* Do extra check against inode mode with di_type */
 		if (unlikely(btrfs_inode_type(inode) != di_type)) {
@@ -6052,9 +6041,10 @@ struct inode *btrfs_lookup_dentry(struct inode *dir, struct dentry *dentry)
 				  inode->vfs_inode.i_mode, btrfs_inode_type(inode),
 				  di_type);
 			iput(&inode->vfs_inode);
-			return ERR_PTR(-EUCLEAN);
+			inode = ERR_PTR(-EUCLEAN);
+			goto cleanup;
 		}
-		return &inode->vfs_inode;
+		goto cleanup;
 	}
 
 	ret = fixup_tree_root_location(fs_info, BTRFS_I(dir), dentry,
@@ -6069,7 +6059,7 @@ struct inode *btrfs_lookup_dentry(struct inode *dir, struct dentry *dentry)
 		btrfs_put_root(sub_root);
 
 		if (IS_ERR(inode))
-			return ERR_CAST(inode);
+			goto cleanup;
 
 		down_read(&fs_info->cleanup_work_sem);
 		if (!sb_rdonly(inode->vfs_inode.i_sb))
@@ -6081,6 +6071,9 @@ struct inode *btrfs_lookup_dentry(struct inode *dir, struct dentry *dentry)
 		}
 	}
 
+cleanup:
+	fscrypt_free_filename(&fname);
+
 	if (IS_ERR(inode))
 		return ERR_CAST(inode);
 
@@ -6270,18 +6263,32 @@ static int btrfs_real_readdir(struct file *file, struct dir_context *ctx)
 	LIST_HEAD(del_list);
 	int ret;
 	char *name_ptr;
-	int name_len;
+	u32 name_len;
 	int entries = 0;
 	int total_len = 0;
 	bool put = false;
 	struct btrfs_key location;
+	struct fscrypt_str fstr = FSTR_INIT(NULL, 0);
+	u32 fstr_len = 0;
 
 	if (!dir_emit_dots(file, ctx))
 		return 0;
 
+	if (BTRFS_I(inode)->flags & BTRFS_INODE_ENCRYPT) {
+		ret = fscrypt_prepare_readdir(inode);
+		if (ret)
+			return ret;
+		ret = fscrypt_fname_alloc_buffer(BTRFS_NAME_LEN, &fstr);
+		if (ret)
+			return ret;
+		fstr_len = fstr.len;
+	}
+
 	path = btrfs_alloc_path();
-	if (!path)
-		return -ENOMEM;
+	if (!path) {
+		ret = -ENOMEM;
+		goto err_fstr;
+	}
 
 	addr = private->filldir_buf;
 	path->reada = READA_FORWARD;
@@ -6298,6 +6305,7 @@ static int btrfs_real_readdir(struct file *file, struct dir_context *ctx)
 		struct dir_entry *entry;
 		struct extent_buffer *leaf = path->nodes[0];
 		u8 ftype;
+		u32 nokey_len;
 
 		if (found_key.objectid != key.objectid)
 			break;
@@ -6311,8 +6319,12 @@ static int btrfs_real_readdir(struct file *file, struct dir_context *ctx)
 			continue;
 		di = btrfs_item_ptr(leaf, path->slots[0], struct btrfs_dir_item);
 		name_len = btrfs_dir_name_len(leaf, di);
-		if ((total_len + sizeof(struct dir_entry) + name_len) >=
-		    PAGE_SIZE) {
+		nokey_len = DIV_ROUND_UP(name_len * 4, 3);
+		/*
+		 * If name is encrypted, and we don't have the key, we could
+		 * need up to 4/3rds the bytes to print it.
+		 */
+		if ((total_len + sizeof(struct dir_entry) + nokey_len) >= PAGE_SIZE) {
 			btrfs_release_path(path);
 			ret = btrfs_filldir(private->filldir_buf, entries, ctx);
 			if (ret)
@@ -6326,8 +6338,31 @@ static int btrfs_real_readdir(struct file *file, struct dir_context *ctx)
 		ftype = btrfs_dir_flags_to_ftype(btrfs_dir_flags(leaf, di));
 		entry = addr;
 		name_ptr = (char *)(entry + 1);
-		read_extent_buffer(leaf, name_ptr,
-				   (unsigned long)(di + 1), name_len);
+		if (btrfs_dir_flags(leaf, di) & BTRFS_FT_ENCRYPTED) {
+			struct fscrypt_str oname = FSTR_INIT(name_ptr, nokey_len);
+			u32 hash = 0, minor_hash = 0;
+
+			read_extent_buffer(leaf, fstr.name, (unsigned long)(di + 1), name_len);
+			fstr.len = name_len;
+			/*
+			 * We're iterating through DIR_INDEX items, so we don't
+			 * have the DIR_ITEM hash handy. Only compute it if
+			 * we'll need it -- the nokey name stores it, so that
+			 * we can look up the appropriate item by nokey name
+			 * later on.
+			 */
+			if (!fscrypt_has_encryption_key(inode)) {
+				u64 name_hash = btrfs_name_hash(fstr.name, fstr.len);
+				hash = name_hash;
+				minor_hash = name_hash >> 32;
+			}
+			ret = fscrypt_fname_disk_to_usr(inode, hash, minor_hash, &fstr, &oname);
+			if (ret)
+				goto err;
+			name_len = oname.len;
+		} else {
+			read_extent_buffer(leaf, name_ptr, (unsigned long)(di + 1), name_len);
+		}
 		put_unaligned(name_len, &entry->name_len);
 		put_unaligned(fs_ftype_to_dtype(ftype), &entry->type);
 		btrfs_dir_item_key_to_cpu(leaf, di, &location);
@@ -6347,7 +6382,8 @@ static int btrfs_real_readdir(struct file *file, struct dir_context *ctx)
 	if (ret)
 		goto nopos;
 
-	if (btrfs_readdir_delayed_dir_index(ctx, &ins_list))
+	fstr.len = fstr_len;
+	if (btrfs_readdir_delayed_dir_index(inode, &fstr, ctx, &ins_list))
 		goto nopos;
 
 	/*
@@ -6376,6 +6412,8 @@ static int btrfs_real_readdir(struct file *file, struct dir_context *ctx)
 err:
 	if (put)
 		btrfs_readdir_put_delayed_items(BTRFS_I(inode), &ins_list, &del_list);
+err_fstr:
+	fscrypt_fname_free_buffer(&fstr);
 	return ret;
 }
 
@@ -6853,6 +6891,7 @@ int btrfs_add_link(struct btrfs_trans_handle *trans,
 	struct btrfs_root *root = parent_inode->root;
 	u64 ino = btrfs_ino(inode);
 	u64 parent_ino = btrfs_ino(parent_inode);
+	struct fscrypt_name fname = { .disk_name = *name };
 
 	if (unlikely(ino == BTRFS_FIRST_FREE_OBJECTID)) {
 		memcpy(&key, &inode->root->root_key, sizeof(key));
@@ -6900,7 +6939,7 @@ int btrfs_add_link(struct btrfs_trans_handle *trans,
 		int ret2;
 
 		ret2 = btrfs_del_root_ref(trans, key.objectid, btrfs_root_id(root),
-					  parent_ino, &local_index, name);
+					  parent_ino, &local_index, &fname);
 		if (ret2)
 			btrfs_abort_transaction(trans, ret2);
 	} else if (add_backref) {
@@ -8425,7 +8464,7 @@ static int btrfs_rename_exchange(struct inode *old_dir,
 	} else { /* src is an inode */
 		ret = __btrfs_unlink_inode(trans, BTRFS_I(old_dir),
 					   BTRFS_I(old_dentry->d_inode),
-					   old_name, &old_rename_ctx);
+					   &old_fname, &old_rename_ctx);
 		if (unlikely(ret)) {
 			btrfs_abort_transaction(trans, ret);
 			goto out_fail;
@@ -8447,7 +8486,7 @@ static int btrfs_rename_exchange(struct inode *old_dir,
 	} else { /* dest is an inode */
 		ret = __btrfs_unlink_inode(trans, BTRFS_I(new_dir),
 					   BTRFS_I(new_dentry->d_inode),
-					   new_name, &new_rename_ctx);
+					   &new_fname, &new_rename_ctx);
 		if (unlikely(ret)) {
 			btrfs_abort_transaction(trans, ret);
 			goto out_fail;
@@ -8716,7 +8755,7 @@ static int btrfs_rename(struct mnt_idmap *idmap,
 	} else {
 		ret = __btrfs_unlink_inode(trans, BTRFS_I(old_dir),
 					   BTRFS_I(d_inode(old_dentry)),
-					   &old_fname.disk_name, &rename_ctx);
+					   &old_fname, &rename_ctx);
 		if (unlikely(ret)) {
 			btrfs_abort_transaction(trans, ret);
 			goto out_fail;
@@ -8741,7 +8780,7 @@ static int btrfs_rename(struct mnt_idmap *idmap,
 		} else {
 			ret = btrfs_unlink_inode(trans, BTRFS_I(new_dir),
 						 BTRFS_I(d_inode(new_dentry)),
-						 &new_fname.disk_name);
+						 &new_fname);
 			if (unlikely(ret)) {
 				btrfs_abort_transaction(trans, ret);
 				goto out_fail;
diff --git a/fs/btrfs/root-tree.c b/fs/btrfs/root-tree.c
index 37a4173c0a0b..f56245b09951 100644
--- a/fs/btrfs/root-tree.c
+++ b/fs/btrfs/root-tree.c
@@ -10,6 +10,7 @@
 #include "messages.h"
 #include "transaction.h"
 #include "disk-io.h"
+#include "fscrypt.h"
 #include "qgroup.h"
 #include "space-info.h"
 #include "accessors.h"
@@ -328,7 +329,7 @@ int btrfs_del_root(struct btrfs_trans_handle *trans,
 
 int btrfs_del_root_ref(struct btrfs_trans_handle *trans, u64 root_id,
 		       u64 ref_id, u64 dirid, u64 *sequence,
-		       const struct fscrypt_str *name)
+		       struct fscrypt_name *name)
 {
 	struct btrfs_root *tree_root = trans->fs_info->tree_root;
 	BTRFS_PATH_AUTO_FREE(path);
@@ -350,15 +351,15 @@ int btrfs_del_root_ref(struct btrfs_trans_handle *trans, u64 root_id,
 	if (ret < 0) {
 		return ret;
 	} else if (ret == 0) {
+		u32 name_len;
 		leaf = path->nodes[0];
 		ref = btrfs_item_ptr(leaf, path->slots[0],
 				     struct btrfs_root_ref);
 		ptr = (unsigned long)(ref + 1);
+		name_len = btrfs_root_ref_name_len(leaf, ref);
 		if ((btrfs_root_ref_dirid(leaf, ref) != dirid) ||
-		    (btrfs_root_ref_name_len(leaf, ref) != name->len) ||
-		    memcmp_extent_buffer(leaf, name->name, ptr, name->len))
+		    !btrfs_fscrypt_match_name(name, leaf, ptr, name_len))
 			return -ENOENT;
-
 		*sequence = btrfs_root_ref_sequence(leaf, ref);
 
 		ret = btrfs_del_item(trans, tree_root, path);
diff --git a/fs/btrfs/root-tree.h b/fs/btrfs/root-tree.h
index 8f5739e732b9..e85623dba952 100644
--- a/fs/btrfs/root-tree.h
+++ b/fs/btrfs/root-tree.h
@@ -23,7 +23,7 @@ int btrfs_add_root_ref(struct btrfs_trans_handle *trans, u64 root_id,
 		       const struct fscrypt_str *name);
 int btrfs_del_root_ref(struct btrfs_trans_handle *trans, u64 root_id,
 		       u64 ref_id, u64 dirid, u64 *sequence,
-		       const struct fscrypt_str *name);
+		       struct fscrypt_name *name);
 int btrfs_del_root(struct btrfs_trans_handle *trans, const struct btrfs_key *key);
 int btrfs_insert_root(struct btrfs_trans_handle *trans, struct btrfs_root *root,
 		      const struct btrfs_key *key,
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
index 4034c04d4d63..d6d3ce41fc3e 100644
--- a/fs/btrfs/tree-log.c
+++ b/fs/btrfs/tree-log.c
@@ -1038,9 +1038,10 @@ static int unlink_inode_for_log_replay(struct walk_control *wc,
 				       const struct fscrypt_str *name)
 {
 	struct btrfs_trans_handle *trans = wc->trans;
+	struct fscrypt_name fname = { .disk_name = *name, };
 	int ret;
 
-	ret = btrfs_unlink_inode(trans, dir, inode, name);
+	ret = btrfs_unlink_inode(trans, dir, inode, &fname);
 	if (ret) {
 		btrfs_abort_log_replay(wc, ret,
 	       "failed to unlink inode %llu parent dir %llu name %.*s root %llu",
-- 
2.51.0


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

* [PATCH v6 14/43] btrfs: handle nokey names
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (12 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 13/43] btrfs: adapt readdir for encrypted and nokey names Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-08 15:28   ` Chris Mason
  2026-02-06 18:22 ` [PATCH v6 15/43] btrfs: implement fscrypt ioctls Daniel Vacek
                   ` (30 subsequent siblings)
  44 siblings, 1 reply; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel, Sweet Tea Dorminy

From: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>

For encrypted or unencrypted names, we calculate the offset for the dir
item by hashing the name for the dir item. However, this doesn't work
for a long nokey name, where we do not have the complete ciphertext.
Instead, fscrypt stores the filesystem-provided hash in the nokey name,
and we can extract it from the fscrypt_name structure in such a case.

Additionally, for nokey names, if we find the nokey name on disk we can
update the fscrypt_name with the disk name, so add that to searching for
diritems.

Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/a7e3aec8ec03814b4baa1d929ae8fb77f13c17d2.1706116485.git.josef@toxicpanda.com/
 * No changes since.
---
 fs/btrfs/dir-item.c | 35 +++++++++++++++++++++++++++++++++--
 fs/btrfs/fscrypt.c  | 27 +++++++++++++++++++++++++++
 fs/btrfs/fscrypt.h  | 11 +++++++++++
 3 files changed, 71 insertions(+), 2 deletions(-)

diff --git a/fs/btrfs/dir-item.c b/fs/btrfs/dir-item.c
index 6e10dd4a4e9e..75c213bd826e 100644
--- a/fs/btrfs/dir-item.c
+++ b/fs/btrfs/dir-item.c
@@ -228,6 +228,27 @@ struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans,
 	return di;
 }
 
+/*
+ * If appropriate, populate the disk name for a fscrypt_name looked up without
+ * a key.
+ *
+ * @path:	The path to the extent buffer in which the name was found.
+ * @di:		The dir item corresponding.
+ * @fname:	The fscrypt_name to perhaps populate.
+ *
+ * Returns: 0 if the name is already populated or the dir item doesn't exist
+ * or the name was successfully populated, else an error code.
+ */
+static int ensure_disk_name_from_dir_item(struct btrfs_path *path,
+					  struct btrfs_dir_item *di,
+					  struct fscrypt_name *name)
+{
+	if (name->disk_name.name || !di)
+		return 0;
+
+	return btrfs_fscrypt_get_disk_name(path->nodes[0], di, &name->disk_name);
+}
+
 /*
  * Lookup for a directory item by fscrypt_name.
  *
@@ -254,8 +275,11 @@ struct btrfs_dir_item *btrfs_lookup_dir_item_fname(struct btrfs_trans_handle *tr
 
 	key.objectid = dir;
 	key.type = BTRFS_DIR_ITEM_KEY;
-	key.offset = btrfs_name_hash(name->disk_name.name, name->disk_name.len);
-	/* XXX get the right hash for no-key names */
+
+	if (!name->disk_name.name)
+		key.offset = name->hash | ((u64)name->minor_hash << 32);
+	else
+		key.offset = btrfs_name_hash(name->disk_name.name, name->disk_name.len);
 
 	ret = btrfs_search_slot(trans, root, &key, path, mod, -mod);
 	if (ret == 0)
@@ -263,6 +287,8 @@ struct btrfs_dir_item *btrfs_lookup_dir_item_fname(struct btrfs_trans_handle *tr
 
 	if (ret == -ENOENT || (di && IS_ERR(di) && PTR_ERR(di) == -ENOENT))
 		return NULL;
+	if (ret == 0)
+		ret = ensure_disk_name_from_dir_item(path, di, name);
 	if (ret < 0)
 		di = ERR_PTR(ret);
 
@@ -373,7 +399,12 @@ btrfs_search_dir_index_item(struct btrfs_root *root, struct btrfs_path *path,
 	btrfs_for_each_slot(root, &key, &key, path, ret) {
 		if (key.objectid != dirid || key.type != BTRFS_DIR_INDEX_KEY)
 			break;
+
 		di = btrfs_match_dir_item_fname(path, name);
+		if (di)
+			ret = ensure_disk_name_from_dir_item(path, di, name);
+		if (ret)
+			break;
 		if (di)
 			return di;
 	}
diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
index 99b87776ce51..078b8c9a0334 100644
--- a/fs/btrfs/fscrypt.c
+++ b/fs/btrfs/fscrypt.c
@@ -15,6 +15,33 @@
 #include "transaction.h"
 #include "xattr.h"
 
+/*
+ * From a given location in a leaf, read a name into a qstr (usually a
+ * fscrypt_name's disk_name), allocating the required buffer. Used for
+ * nokey names.
+ */
+int btrfs_fscrypt_get_disk_name(struct extent_buffer *leaf,
+				struct btrfs_dir_item *dir_item,
+				struct fscrypt_str *name)
+{
+	unsigned long de_name_len = btrfs_dir_name_len(leaf, dir_item);
+	unsigned long de_name = (unsigned long)(dir_item + 1);
+	/*
+	 * For no-key names, we use this opportunity to find the disk
+	 * name, so future searches don't need to deal with nokey names
+	 * and we know what the encrypted size is.
+	 */
+	name->name = kmalloc(de_name_len, GFP_NOFS);
+
+	if (!name->name)
+		return -ENOMEM;
+
+	read_extent_buffer(leaf, name->name, de_name, de_name_len);
+
+	name->len = de_name_len;
+	return 0;
+}
+
 /*
  * This function is extremely similar to fscrypt_match_name() but uses an
  * extent_buffer.
diff --git a/fs/btrfs/fscrypt.h b/fs/btrfs/fscrypt.h
index 4e9c87290158..c5cdc27f943c 100644
--- a/fs/btrfs/fscrypt.h
+++ b/fs/btrfs/fscrypt.h
@@ -9,11 +9,22 @@
 #include "fs.h"
 
 #ifdef CONFIG_FS_ENCRYPTION
+int btrfs_fscrypt_get_disk_name(struct extent_buffer *leaf,
+				struct btrfs_dir_item *di,
+				struct fscrypt_str *qstr);
+
 bool btrfs_fscrypt_match_name(struct fscrypt_name *fname,
 			      struct extent_buffer *leaf,
 			      unsigned long de_name, u32 de_name_len);
 
 #else
+static inline int btrfs_fscrypt_get_disk_name(struct extent_buffer *leaf,
+					      struct btrfs_dir_item *di,
+					      struct fscrypt_str *qstr)
+{
+	return 0;
+}
+
 static inline bool btrfs_fscrypt_match_name(struct fscrypt_name *fname,
 					    struct extent_buffer *leaf,
 					    unsigned long de_name,
-- 
2.51.0


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

* [PATCH v6 15/43] btrfs: implement fscrypt ioctls
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (13 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 14/43] btrfs: handle " Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-06 18:22 ` [PATCH v6 16/43] btrfs: select encryption dependencies if FS_ENCRYPTION Daniel Vacek
                   ` (29 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel, Omar Sandoval, Sweet Tea Dorminy

From: Omar Sandoval <osandov@osandov.com>

These ioctls allow encryption to actually be used.

The set_encryption_policy ioctl is the thing which actually turns on
encryption, and therefore sets the ENCRYPT flag in the superblock.
This prevents the filesystem from being loaded on older kernels.

fscrypt provides CONFIG_FS_ENCRYPTION-disabled versions of all these
functions which just return -EOPNOTSUPP, so the ioctls don't need
to be compiled out if CONFIG_FS_ENCRYPTION isn't enabled.

We could instead gate this ioctl on the superblock having the flag set,
if we wanted to require mkfs with the encrypt flag in order to have a
filesystem with any encryption.

Since this is a disk format change and is currently in development,
gate the fscrypt ioctls for btrfs behind BTRFS_EXPERIMENTAL.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/6182b77868d0c9f9447641f869658ef017c1f467.1706116485.git.josef@toxicpanda.com/
 * No changes since.
 * But squash in https://lore.kernel.org/linux-btrfs/5134ab713b080f78b90eae46671596f72bbb1815.1706116485.git.josef@toxicpanda.com/
   ("btrfs: gate encryption behind BTRFS_DEBUG") and change it to
   BTRFS_EXPERIMENTAL as suggested by Dave.
---
 fs/btrfs/ioctl.c | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
index 9d0716498286..a8adf99ad0a8 100644
--- a/fs/btrfs/ioctl.c
+++ b/fs/btrfs/ioctl.c
@@ -5192,6 +5192,35 @@ long btrfs_ioctl(struct file *file, unsigned int
 		return btrfs_ioctl_get_fslabel(fs_info, argp);
 	case FS_IOC_SETFSLABEL:
 		return btrfs_ioctl_set_fslabel(file, argp);
+#ifdef CONFIG_BTRFS_EXPERIMENTAL
+	case FS_IOC_SET_ENCRYPTION_POLICY: {
+		if (!IS_ENABLED(CONFIG_FS_ENCRYPTION))
+			return -EOPNOTSUPP;
+		if (sb_rdonly(fs_info->sb))
+			return -EROFS;
+		/*
+		 *  If we crash before we commit, nothing encrypted could have
+		 * been written so it doesn't matter whether the encrypted
+		 * state persists.
+		 */
+		btrfs_set_fs_incompat(fs_info, ENCRYPT);
+		return fscrypt_ioctl_set_policy(file, (const void __user *)arg);
+	}
+	case FS_IOC_GET_ENCRYPTION_POLICY:
+		return fscrypt_ioctl_get_policy(file, (void __user *)arg);
+	case FS_IOC_GET_ENCRYPTION_POLICY_EX:
+		return fscrypt_ioctl_get_policy_ex(file, (void __user *)arg);
+	case FS_IOC_ADD_ENCRYPTION_KEY:
+		return fscrypt_ioctl_add_key(file, (void __user *)arg);
+	case FS_IOC_REMOVE_ENCRYPTION_KEY:
+		return fscrypt_ioctl_remove_key(file, (void __user *)arg);
+	case FS_IOC_REMOVE_ENCRYPTION_KEY_ALL_USERS:
+		return fscrypt_ioctl_remove_key_all_users(file, (void __user *)arg);
+	case FS_IOC_GET_ENCRYPTION_KEY_STATUS:
+		return fscrypt_ioctl_get_key_status(file, (void __user *)arg);
+	case FS_IOC_GET_ENCRYPTION_NONCE:
+		return fscrypt_ioctl_get_nonce(file, (void __user *)arg);
+#endif /* CONFIG_BTRFS_EXPERIMENTAL */
 	case FITRIM:
 		return btrfs_ioctl_fitrim(fs_info, argp);
 	case BTRFS_IOC_SNAP_CREATE:
-- 
2.51.0


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

* [PATCH v6 16/43] btrfs: select encryption dependencies if FS_ENCRYPTION
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (14 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 15/43] btrfs: implement fscrypt ioctls Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-08 15:22   ` Chris Mason
  2026-02-06 18:22 ` [PATCH v6 17/43] btrfs: add get_devices hook for fscrypt Daniel Vacek
                   ` (28 subsequent siblings)
  44 siblings, 1 reply; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

We need this to make sure the appropriate encryption algorithms are
turned on in our config if we have FS_ENCRYPTION enabled, and
additionally we only support inline encryption with the fallback block
crypto, so we need to make sure we pull in those dependencies.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/396f5067b2551a9f2de4b439509e1a285985d358.1706116485.git.josef@toxicpanda.com/
 * No changes since.
---
 fs/btrfs/Kconfig | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/fs/btrfs/Kconfig b/fs/btrfs/Kconfig
index ede184b6eda1..216a5707b099 100644
--- a/fs/btrfs/Kconfig
+++ b/fs/btrfs/Kconfig
@@ -16,6 +16,9 @@ config BTRFS_FS
 	select RAID6_PQ
 	select XOR_BLOCKS
 	select XXHASH
+	select FS_ENCRYPTION_ALGS if FS_ENCRYPTION
+	select FS_ENCRYPTION_INLINE_CRYPT if FS_ENCRYPTION
+	select BLK_INLINE_ENCRYPTION_FALLBACK if FS_ENCRYPTION
 	depends on PAGE_SIZE_LESS_THAN_256KB
 
 	help
-- 
2.51.0


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

* [PATCH v6 17/43] btrfs: add get_devices hook for fscrypt
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (15 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 16/43] btrfs: select encryption dependencies if FS_ENCRYPTION Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-06 18:22 ` [PATCH v6 18/43] btrfs: set file extent encryption excplicitly Daniel Vacek
                   ` (27 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel, Sweet Tea Dorminy

From: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>

Since extent encryption requires inline encryption, even though we
expect to use the inlinecrypt software fallback most of the time, we
need to enumerate all the devices in use by btrfs.

Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/47ff044fae65cb2b06a6bb6ffc91027511c4d1f4.1706116485.git.josef@toxicpanda.com/
 * No changes since.
---
 fs/btrfs/fscrypt.c | 36 ++++++++++++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)

diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
index 078b8c9a0334..608f8797ea6f 100644
--- a/fs/btrfs/fscrypt.c
+++ b/fs/btrfs/fscrypt.c
@@ -12,7 +12,9 @@
 #include "ioctl.h"
 #include "messages.h"
 #include "root-tree.h"
+#include "super.h"
 #include "transaction.h"
+#include "volumes.h"
 #include "xattr.h"
 
 /*
@@ -177,10 +179,44 @@ static bool btrfs_fscrypt_empty_dir(struct inode *inode)
 	return inode->i_size == BTRFS_EMPTY_DIR_SIZE;
 }
 
+static struct block_device **btrfs_fscrypt_get_devices(struct super_block *sb,
+						       unsigned int *num_devs)
+{
+	struct btrfs_fs_info *fs_info = btrfs_sb(sb);
+	struct btrfs_fs_devices *fs_devices = fs_info->fs_devices;
+	int nr_devices = fs_devices->open_devices;
+	struct block_device **devs;
+	struct btrfs_device *device;
+	int i = 0;
+
+	devs = kmalloc_array(nr_devices, sizeof(*devs), GFP_NOFS | GFP_NOWAIT);
+	if (!devs)
+		return ERR_PTR(-ENOMEM);
+
+	rcu_read_lock();
+	list_for_each_entry_rcu(device, &fs_devices->devices, dev_list) {
+		if (!test_bit(BTRFS_DEV_STATE_IN_FS_METADATA, &device->dev_state) ||
+		    !device->bdev ||
+		    test_bit(BTRFS_DEV_STATE_REPLACE_TGT, &device->dev_state))
+			continue;
+
+		devs[i++] = device->bdev;
+
+		if (i >= nr_devices)
+			break;
+
+	}
+	rcu_read_unlock();
+
+	*num_devs = i;
+	return devs;
+}
+
 const struct fscrypt_operations btrfs_fscrypt_ops = {
 	.inode_info_offs = (int)offsetof(struct btrfs_inode, i_crypt_info) -
 			   (int)offsetof(struct btrfs_inode, vfs_inode),
 	.get_context = btrfs_fscrypt_get_context,
 	.set_context = btrfs_fscrypt_set_context,
 	.empty_dir = btrfs_fscrypt_empty_dir,
+	.get_devices = btrfs_fscrypt_get_devices,
 };
-- 
2.51.0


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

* [PATCH v6 18/43] btrfs: set file extent encryption excplicitly
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (16 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 17/43] btrfs: add get_devices hook for fscrypt Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-06 18:22 ` [PATCH v6 19/43] btrfs: add fscrypt_info and encryption_type to extent_map Daniel Vacek
                   ` (26 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel, Sweet Tea Dorminy

From: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>

This puts the long-preserved 1-byte encryption field to work, storing
whether the extent is encrypted.  Update the tree-checker to allow for
the encryption bit to be set to our valid types.

Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/1bf1cb768bc8bc54b3855e271412077c60f23c50.1706116485.git.josef@toxicpanda.com/
 * No changes since.
---
 fs/btrfs/accessors.h            | 2 ++
 fs/btrfs/inode.c                | 6 ++++--
 fs/btrfs/tree-checker.c         | 8 +++++---
 fs/btrfs/tree-log.c             | 2 ++
 include/uapi/linux/btrfs_tree.h | 8 +++++++-
 5 files changed, 20 insertions(+), 6 deletions(-)

diff --git a/fs/btrfs/accessors.h b/fs/btrfs/accessors.h
index 8938357fcb40..09ed68fa2041 100644
--- a/fs/btrfs/accessors.h
+++ b/fs/btrfs/accessors.h
@@ -907,6 +907,8 @@ BTRFS_SETGET_STACK_FUNCS(stack_file_extent_disk_num_bytes,
 			 struct btrfs_file_extent_item, disk_num_bytes, 64);
 BTRFS_SETGET_STACK_FUNCS(stack_file_extent_compression,
 			 struct btrfs_file_extent_item, compression, 8);
+BTRFS_SETGET_STACK_FUNCS(stack_file_extent_encryption,
+			 struct btrfs_file_extent_item, encryption, 8);
 
 
 BTRFS_SETGET_FUNCS(file_extent_type, struct btrfs_file_extent_item, type, 8);
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 79f0b67249de..758dde148be6 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -3123,7 +3123,8 @@ static int insert_ordered_extent_file_extent(struct btrfs_trans_handle *trans,
 	btrfs_set_stack_file_extent_num_bytes(&stack_fi, num_bytes);
 	btrfs_set_stack_file_extent_ram_bytes(&stack_fi, ram_bytes);
 	btrfs_set_stack_file_extent_compression(&stack_fi, oe->compress_type);
-	/* Encryption and other encoding is reserved and all 0 */
+	btrfs_set_stack_file_extent_encryption(&stack_fi, BTRFS_ENCRYPTION_NONE);
+	/* Other encoding is reserved and always 0 */
 
 	/*
 	 * For delalloc, when completing an ordered extent we update the inode's
@@ -9205,7 +9206,8 @@ static struct btrfs_trans_handle *insert_prealloc_file_extent(
 	btrfs_set_stack_file_extent_num_bytes(&stack_fi, len);
 	btrfs_set_stack_file_extent_ram_bytes(&stack_fi, len);
 	btrfs_set_stack_file_extent_compression(&stack_fi, BTRFS_COMPRESS_NONE);
-	/* Encryption and other encoding is reserved and all 0 */
+	btrfs_set_stack_file_extent_encryption(&stack_fi, BTRFS_ENCRYPTION_NONE);
+	/* Other encoding is reserved and always 0 */
 
 	ret = btrfs_qgroup_release_data(inode, file_offset, len, &qgroup_released);
 	if (ret < 0)
diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
index 452394b34d01..9675dbcd78a3 100644
--- a/fs/btrfs/tree-checker.c
+++ b/fs/btrfs/tree-checker.c
@@ -213,6 +213,7 @@ static int check_extent_data_item(struct extent_buffer *leaf,
 	u32 sectorsize = fs_info->sectorsize;
 	u32 item_size = btrfs_item_size(leaf, slot);
 	u64 extent_end;
+	u8 policy;
 
 	if (unlikely(!IS_ALIGNED(key->offset, sectorsize))) {
 		file_extent_err(leaf, slot,
@@ -264,10 +265,11 @@ static int check_extent_data_item(struct extent_buffer *leaf,
 			BTRFS_NR_COMPRESS_TYPES - 1);
 		return -EUCLEAN;
 	}
-	if (unlikely(btrfs_file_extent_encryption(leaf, fi))) {
+	policy = btrfs_file_extent_encryption(leaf, fi);
+	if (unlikely(policy >= BTRFS_NR_ENCRYPTION_TYPES)) {
 		file_extent_err(leaf, slot,
-			"invalid encryption for file extent, have %u expect 0",
-			btrfs_file_extent_encryption(leaf, fi));
+			"invalid encryption for file extent, have %u expect range [0, %u]",
+			policy, BTRFS_NR_ENCRYPTION_TYPES - 1);
 		return -EUCLEAN;
 	}
 	if (btrfs_file_extent_type(leaf, fi) == BTRFS_FILE_EXTENT_INLINE) {
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
index d6d3ce41fc3e..118dd1888996 100644
--- a/fs/btrfs/tree-log.c
+++ b/fs/btrfs/tree-log.c
@@ -5123,6 +5123,7 @@ static int log_one_extent(struct btrfs_trans_handle *trans,
 	u64 block_start = btrfs_extent_map_block_start(em);
 	u64 block_len;
 	int ret;
+	u8 encryption = BTRFS_ENCRYPTION_NONE;
 
 	btrfs_set_stack_file_extent_generation(&fi, trans->transid);
 	if (em->flags & EXTENT_FLAG_PREALLOC)
@@ -5144,6 +5145,7 @@ static int log_one_extent(struct btrfs_trans_handle *trans,
 	btrfs_set_stack_file_extent_num_bytes(&fi, em->len);
 	btrfs_set_stack_file_extent_ram_bytes(&fi, em->ram_bytes);
 	btrfs_set_stack_file_extent_compression(&fi, compress_type);
+	btrfs_set_stack_file_extent_encryption(&fi, encryption);
 
 	ret = log_extent_csums(trans, inode, log, em, ctx);
 	if (ret)
diff --git a/include/uapi/linux/btrfs_tree.h b/include/uapi/linux/btrfs_tree.h
index 02b1ab1f7858..cb8dbcc612e9 100644
--- a/include/uapi/linux/btrfs_tree.h
+++ b/include/uapi/linux/btrfs_tree.h
@@ -1107,8 +1107,14 @@ struct btrfs_file_extent_item {
 	 * but not for stat.
 	 */
 	__u8 compression;
+
+	/*
+	 * Type of encryption in use. Unencrypted value is 0.
+	 */
 	__u8 encryption;
-	__le16 other_encoding; /* spare for later use */
+
+	/* spare for later use */
+	__le16 other_encoding;
 
 	/* are we inline data or a real extent? */
 	__u8 type;
-- 
2.51.0


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

* [PATCH v6 19/43] btrfs: add fscrypt_info and encryption_type to extent_map
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (17 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 18/43] btrfs: set file extent encryption excplicitly Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-06 18:22 ` [PATCH v6 20/43] btrfs: add fscrypt_info and encryption_type to ordered_extent Daniel Vacek
                   ` (25 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel, Sweet Tea Dorminy

From: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>

Each extent_map will end up with a pointer to its associated
fscrypt_info if any, which should have the same lifetime as the
extent_map.  We are also going to need to track the encryption_type
for the file extent items.  Add the fscrypt_info to the extent_map and
the subsequent code for transferring it in the split and merge cases
as well as the code necessary to free them.  A future patch will add
the code to load them as appropriate.

Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/9818cd4134d048d2d641b9b8ae10be6c6af51956.1706116485.git.josef@toxicpanda.com/
 * No significant changes since. Just add some btrfs_ prefixes even
   to static functions in extent map header file to match the style.
---
 fs/btrfs/extent_map.c | 32 +++++++++++++++++++++++++++++---
 fs/btrfs/extent_map.h | 16 ++++++++++++++++
 fs/btrfs/file-item.c  |  1 +
 fs/btrfs/inode.c      |  1 +
 4 files changed, 47 insertions(+), 3 deletions(-)

diff --git a/fs/btrfs/extent_map.c b/fs/btrfs/extent_map.c
index 58589fc11802..f6b58a5c1151 100644
--- a/fs/btrfs/extent_map.c
+++ b/fs/btrfs/extent_map.c
@@ -59,6 +59,7 @@ struct extent_map *btrfs_alloc_extent_map(void)
 
 static void free_extent_map(struct extent_map *em)
 {
+	fscrypt_put_extent_info(em->fscrypt_info);
 	kmem_cache_free(extent_map_cache, em);
 }
 
@@ -99,12 +100,24 @@ void btrfs_free_extent_map_safe(struct extent_map_tree *tree,
 	if (!em)
 		return;
 
-	if (refcount_dec_and_test(&em->refs)) {
-		WARN_ON(btrfs_extent_map_in_tree(em));
-		WARN_ON(!list_empty(&em->list));
+	if (!refcount_dec_and_test(&em->refs))
+		return;
+
+	WARN_ON(btrfs_extent_map_in_tree(em));
+	WARN_ON(!list_empty(&em->list));
+
+	/*
+	 * We could take a lock freeing the fscrypt_info, so add this to the
+	 * list of freed_extents to be freed later.
+	 */
+	if (em->fscrypt_info) {
 		list_add_tail(&em->free_list, &tree->freed_extents);
 		set_bit(EXTENT_MAP_TREE_PENDING_FREES, &tree->flags);
+		return;
 	}
+
+	/* Nothing scary here, just free the object. */
+	free_extent_map(em);
 }
 
 /*
@@ -291,6 +304,10 @@ static bool can_merge_extent_map(const struct extent_map *em)
 	if (!list_empty(&em->list))
 		return false;
 
+	/* We can't merge encrypted extents. */
+	if (em->fscrypt_info)
+		return false;
+
 	return true;
 }
 
@@ -311,6 +328,10 @@ static bool mergeable_maps(const struct extent_map *prev, const struct extent_ma
 	if (next->disk_bytenr < EXTENT_MAP_LAST_BYTE - 1)
 		return btrfs_extent_map_block_start(next) == extent_map_block_end(prev);
 
+	/* Don't merge adjacent encrypted maps. */
+	if (prev->fscrypt_info || next->fscrypt_info)
+		return false;
+
 	/* HOLES and INLINE extents. */
 	return next->disk_bytenr == prev->disk_bytenr;
 }
@@ -977,6 +998,7 @@ void btrfs_drop_extent_map_range(struct btrfs_inode *inode, u64 start, u64 end,
 
 			split->generation = gen;
 			split->flags = flags;
+			split->fscrypt_info = fscrypt_get_extent_info(em->fscrypt_info);
 			replace_extent_mapping(inode, em, split, modified);
 			btrfs_free_extent_map(split);
 			split = split2;
@@ -1005,6 +1027,7 @@ void btrfs_drop_extent_map_range(struct btrfs_inode *inode, u64 start, u64 end,
 				split->ram_bytes = split->len;
 			}
 
+			split->fscrypt_info = fscrypt_get_extent_info(em->fscrypt_info);
 			if (btrfs_extent_map_in_tree(em)) {
 				replace_extent_mapping(inode, em, split, modified);
 			} else {
@@ -1163,6 +1186,7 @@ int btrfs_split_extent_map(struct btrfs_inode *inode, u64 start, u64 len, u64 pr
 	split_pre->ram_bytes = split_pre->len;
 	split_pre->flags = flags;
 	split_pre->generation = em->generation;
+	split_pre->fscrypt_info = fscrypt_get_extent_info(em->fscrypt_info);
 
 	replace_extent_mapping(inode, em, split_pre, true);
 
@@ -1180,6 +1204,8 @@ int btrfs_split_extent_map(struct btrfs_inode *inode, u64 start, u64 len, u64 pr
 	split_mid->ram_bytes = split_mid->len;
 	split_mid->flags = flags;
 	split_mid->generation = em->generation;
+	split_mid->fscrypt_info = fscrypt_get_extent_info(em->fscrypt_info);
+
 	add_extent_mapping(inode, split_mid, true);
 
 	/* Once for us */
diff --git a/fs/btrfs/extent_map.h b/fs/btrfs/extent_map.h
index a962012be1c3..a0d5be758e7e 100644
--- a/fs/btrfs/extent_map.h
+++ b/fs/btrfs/extent_map.h
@@ -24,6 +24,7 @@ enum {
 	ENUM_BIT(EXTENT_FLAG_COMPRESS_ZLIB),
 	ENUM_BIT(EXTENT_FLAG_COMPRESS_LZO),
 	ENUM_BIT(EXTENT_FLAG_COMPRESS_ZSTD),
+	ENUM_BIT(EXTENT_FLAG_ENCRYPT_FSCRYPT),
 	/* pre-allocated extent */
 	ENUM_BIT(EXTENT_FLAG_PREALLOC),
 	/* Logging this extent */
@@ -96,6 +97,7 @@ struct extent_map {
 	u64 generation;
 	u32 flags;
 	refcount_t refs;
+	struct fscrypt_extent_info *fscrypt_info;
 	struct list_head list;
 	struct list_head free_list;
 };
@@ -114,6 +116,20 @@ struct extent_map_tree {
 
 struct btrfs_inode;
 
+static inline void btrfs_extent_map_set_encryption(struct extent_map *em,
+					     enum btrfs_encryption_type type)
+{
+	if (type == BTRFS_ENCRYPTION_FSCRYPT)
+		em->flags |= EXTENT_FLAG_ENCRYPT_FSCRYPT;
+}
+
+static inline enum btrfs_encryption_type btrfs_extent_map_encryption(const struct extent_map *em)
+{
+	if (em->flags & EXTENT_FLAG_ENCRYPT_FSCRYPT)
+		return BTRFS_ENCRYPTION_FSCRYPT;
+	return BTRFS_ENCRYPTION_NONE;
+}
+
 static inline void btrfs_extent_map_set_compression(struct extent_map *em,
 						    enum btrfs_compression_type type)
 {
diff --git a/fs/btrfs/file-item.c b/fs/btrfs/file-item.c
index 7bd715442f3e..08dc78295707 100644
--- a/fs/btrfs/file-item.c
+++ b/fs/btrfs/file-item.c
@@ -1357,6 +1357,7 @@ void btrfs_extent_item_to_extent_map(struct btrfs_inode *inode,
 			if (type == BTRFS_FILE_EXTENT_PREALLOC)
 				em->flags |= EXTENT_FLAG_PREALLOC;
 		}
+		btrfs_extent_map_set_encryption(em, btrfs_file_extent_encryption(leaf, fi));
 	} else if (type == BTRFS_FILE_EXTENT_INLINE) {
 		/* Tree-checker has ensured this. */
 		ASSERT(extent_start == 0);
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 758dde148be6..b425047f77c7 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -7580,6 +7580,7 @@ struct extent_map *btrfs_create_io_em(struct btrfs_inode *inode, u64 start,
 	em->flags |= EXTENT_FLAG_PINNED;
 	if (type == BTRFS_ORDERED_COMPRESSED)
 		btrfs_extent_map_set_compression(em, file_extent->compression);
+	btrfs_extent_map_set_encryption(em, BTRFS_ENCRYPTION_NONE);
 
 	ret = btrfs_replace_extent_map_range(inode, em, true);
 	if (ret) {
-- 
2.51.0


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

* [PATCH v6 20/43] btrfs: add fscrypt_info and encryption_type to ordered_extent
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (18 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 19/43] btrfs: add fscrypt_info and encryption_type to extent_map Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-08 15:18   ` Chris Mason
  2026-02-06 18:22 ` [PATCH v6 21/43] btrfs: plumb through setting the fscrypt_info for ordered extents Daniel Vacek
                   ` (24 subsequent siblings)
  44 siblings, 1 reply; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

We're going to need these to update the file extent items once the
writes are complete.  Add them and add the pieces necessary to assign
them and free everything.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/de9ff13d1dc042b764c224d039fbb2a08946e004.1706116485.git.josef@toxicpanda.com/
 * Changed encryption_type member to u8.
---
 fs/btrfs/ordered-data.c | 2 ++
 fs/btrfs/ordered-data.h | 6 ++++++
 2 files changed, 8 insertions(+)

diff --git a/fs/btrfs/ordered-data.c b/fs/btrfs/ordered-data.c
index 5df02c707aee..7a5701937184 100644
--- a/fs/btrfs/ordered-data.c
+++ b/fs/btrfs/ordered-data.c
@@ -192,6 +192,7 @@ static struct btrfs_ordered_extent *alloc_ordered_extent(
 	}
 	entry->inode = inode;
 	entry->compress_type = compress_type;
+	entry->encryption_type = BTRFS_ENCRYPTION_NONE;
 	entry->truncated_len = (u64)-1;
 	entry->qgroup_rsv = qgroup_rsv;
 	entry->flags = flags;
@@ -630,6 +631,7 @@ void btrfs_put_ordered_extent(struct btrfs_ordered_extent *entry)
 		btrfs_add_delayed_iput(entry->inode);
 		list_for_each_entry_safe(sum, tmp, &entry->list, list)
 			kvfree(sum);
+		fscrypt_put_extent_info(entry->fscrypt_info);
 		kmem_cache_free(btrfs_ordered_extent_cache, entry);
 	}
 }
diff --git a/fs/btrfs/ordered-data.h b/fs/btrfs/ordered-data.h
index 1e6b0b182b29..ce19198e7f84 100644
--- a/fs/btrfs/ordered-data.h
+++ b/fs/btrfs/ordered-data.h
@@ -124,6 +124,9 @@ struct btrfs_ordered_extent {
 	/* compression algorithm */
 	int compress_type;
 
+	/* encryption mode */
+	u8 encryption_type;
+
 	/* Qgroup reserved space */
 	int qgroup_rsv;
 
@@ -133,6 +136,9 @@ struct btrfs_ordered_extent {
 	/* the inode we belong to */
 	struct btrfs_inode *inode;
 
+	/* the fscrypt_info for this extent, if necessary */
+	struct fscrypt_extent_info *fscrypt_info;
+
 	/* list of checksums for insertion when the extent io is done */
 	struct list_head list;
 
-- 
2.51.0


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

* [PATCH v6 21/43] btrfs: plumb through setting the fscrypt_info for ordered extents
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (19 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 20/43] btrfs: add fscrypt_info and encryption_type to ordered_extent Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-06 18:22 ` [PATCH v6 22/43] btrfs: populate the ordered_extent with the fscrypt context Daniel Vacek
                   ` (23 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

We're going to be getting fscrypt_info from the extent maps.  Pass the
fscrypt_info to helpers using the file_extent structure and use that
to set the encryption type on the ordered extent.

For prealloc extents we create an em, since we already have the context
loaded from the original prealloc extent creation we need to pre-
populate the extent map fscrypt info so it can be read properly later
if the pages are evicted.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/80c5dabfe190b84e31a95160021da64ebcf7ecf7.1706116485.git.josef@toxicpanda.com/
 * Added fscrypt_info to file_extent structure to clean up and simplify
   the code following upstream changes since.
 * Squashed in https://lore.kernel.org/linux-btrfs/f2b402eac1963296b6b8db3cb59cdf24a8121b97.1706116485.git.josef@toxicpanda.com/
   ("btrfs: plumb the fscrypt extent context through create_io_em").
 * Moved fscrypt_info to be the last argument of alloc_ordered_extent().
---
 fs/btrfs/direct-io.c    |  1 +
 fs/btrfs/inode.c        |  3 +++
 fs/btrfs/ordered-data.c | 18 ++++++++++++------
 fs/btrfs/ordered-data.h |  1 +
 4 files changed, 17 insertions(+), 6 deletions(-)

diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
index 9a63200d7a53..f3efc451d9a5 100644
--- a/fs/btrfs/direct-io.c
+++ b/fs/btrfs/direct-io.c
@@ -202,6 +202,7 @@ static struct extent_map *btrfs_new_extent_direct(struct btrfs_inode *inode,
 	file_extent.ram_bytes = ins.offset;
 	file_extent.offset = 0;
 	file_extent.compression = BTRFS_COMPRESS_NONE;
+	file_extent.fscrypt_info = NULL;
 	em = btrfs_create_dio_extent(inode, dio_data, start, &file_extent,
 				     BTRFS_ORDERED_REGULAR);
 	btrfs_dec_block_group_reservations(fs_info, ins.objectid);
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index b425047f77c7..aef95d6e02bf 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -1183,6 +1183,7 @@ static void submit_one_async_extent(struct async_chunk *async_chunk,
 	file_extent.num_bytes = async_extent->ram_size;
 	file_extent.offset = 0;
 	file_extent.compression = async_extent->compress_type;
+	file_extent.fscrypt_info = NULL;
 
 	em = btrfs_create_io_em(inode, start, &file_extent, BTRFS_ORDERED_COMPRESSED);
 	if (IS_ERR(em)) {
@@ -1319,6 +1320,7 @@ static int cow_one_range(struct btrfs_inode *inode, struct folio *locked_folio,
 	file_extent.ram_bytes = ins->offset;
 	file_extent.offset = 0;
 	file_extent.compression = BTRFS_COMPRESS_NONE;
+	file_extent.fscrypt_info = NULL;
 
 	/*
 	 * Locked range will be released either during error clean up (inside
@@ -10117,6 +10119,7 @@ ssize_t btrfs_do_encoded_write(struct kiocb *iocb, struct iov_iter *from,
 	file_extent.ram_bytes = ram_bytes;
 	file_extent.offset = encoded->unencoded_offset;
 	file_extent.compression = compression;
+	file_extent.fscrypt_info = NULL;
 	em = btrfs_create_io_em(inode, start, &file_extent, BTRFS_ORDERED_COMPRESSED);
 	if (IS_ERR(em)) {
 		ret = PTR_ERR(em);
diff --git a/fs/btrfs/ordered-data.c b/fs/btrfs/ordered-data.c
index 7a5701937184..1b4d20a2f983 100644
--- a/fs/btrfs/ordered-data.c
+++ b/fs/btrfs/ordered-data.c
@@ -148,7 +148,8 @@ static inline struct rb_node *ordered_tree_search(struct btrfs_inode *inode,
 static struct btrfs_ordered_extent *alloc_ordered_extent(
 			struct btrfs_inode *inode, u64 file_offset, u64 num_bytes,
 			u64 ram_bytes, u64 disk_bytenr, u64 disk_num_bytes,
-			u64 offset, unsigned long flags, int compress_type)
+			u64 offset, unsigned long flags, int compress_type,
+			struct fscrypt_extent_info *fscrypt_info)
 {
 	struct btrfs_ordered_extent *entry;
 	int ret;
@@ -192,10 +193,12 @@ static struct btrfs_ordered_extent *alloc_ordered_extent(
 	}
 	entry->inode = inode;
 	entry->compress_type = compress_type;
-	entry->encryption_type = BTRFS_ENCRYPTION_NONE;
 	entry->truncated_len = (u64)-1;
 	entry->qgroup_rsv = qgroup_rsv;
 	entry->flags = flags;
+	entry->fscrypt_info = fscrypt_get_extent_info(fscrypt_info);
+	entry->encryption_type = entry->fscrypt_info ?
+		BTRFS_ENCRYPTION_FSCRYPT : BTRFS_ENCRYPTION_NONE;
 	refcount_set(&entry->refs, 1);
 	init_waitqueue_head(&entry->wait);
 	INIT_LIST_HEAD(&entry->list);
@@ -272,6 +275,7 @@ static void insert_ordered_extent(struct btrfs_ordered_extent *entry)
  * @offset:          Offset into unencoded data where file data starts.
  * @flags:           Flags specifying type of extent (1U << BTRFS_ORDERED_*).
  * @compress_type:   Compression algorithm used for data.
+ * @fscrypt_info:    The fscrypt_extent_info for this extent, if necessary.
  *
  * Most of these parameters correspond to &struct btrfs_file_extent_item. The
  * tree is given a single reference on the ordered extent that was inserted, and
@@ -305,7 +309,8 @@ struct btrfs_ordered_extent *btrfs_alloc_ordered_extent(
 					     file_extent->num_bytes,
 					     file_extent->disk_bytenr + file_extent->offset,
 					     file_extent->num_bytes, 0, flags,
-					     file_extent->compression);
+					     file_extent->compression,
+					     file_extent->fscrypt_info);
 	else
 		entry = alloc_ordered_extent(inode, file_offset,
 					     file_extent->num_bytes,
@@ -313,7 +318,8 @@ struct btrfs_ordered_extent *btrfs_alloc_ordered_extent(
 					     file_extent->disk_bytenr,
 					     file_extent->disk_num_bytes,
 					     file_extent->offset, flags,
-					     file_extent->compression);
+					     file_extent->compression,
+					     file_extent->fscrypt_info);
 	if (!IS_ERR(entry))
 		insert_ordered_extent(entry);
 	return entry;
@@ -1271,8 +1277,8 @@ struct btrfs_ordered_extent *btrfs_split_ordered_extent(
 	if (WARN_ON_ONCE(ordered->disk_num_bytes != ordered->num_bytes))
 		return ERR_PTR(-EINVAL);
 
-	new = alloc_ordered_extent(inode, file_offset, len, len, disk_bytenr,
-				   len, 0, flags, ordered->compress_type);
+	new = alloc_ordered_extent(inode, file_offset, len, len, disk_bytenr, len, 0,
+				   flags, ordered->compress_type, ordered->fscrypt_info);
 	if (IS_ERR(new))
 		return new;
 
diff --git a/fs/btrfs/ordered-data.h b/fs/btrfs/ordered-data.h
index ce19198e7f84..51c795865fe6 100644
--- a/fs/btrfs/ordered-data.h
+++ b/fs/btrfs/ordered-data.h
@@ -188,6 +188,7 @@ struct btrfs_file_extent {
 	u64 num_bytes;
 	u64 ram_bytes;
 	u64 offset;
+	struct fscrypt_extent_info *fscrypt_info;
 	u8 compression;
 };
 
-- 
2.51.0


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

* [PATCH v6 22/43] btrfs: populate the ordered_extent with the fscrypt context
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (20 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 21/43] btrfs: plumb through setting the fscrypt_info for ordered extents Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-06 18:22 ` [PATCH v6 23/43] btrfs: keep track of fscrypt info and orig_start for dio reads Daniel Vacek
                   ` (22 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

The fscrypt_extent_info will be tied to the extent_map lifetime, so it
will be created when we create the IO em, or it'll already exist in the
NOCOW case.  Use this fscrypt_info when creating the ordered extent to
make sure everything is passed through properly.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/33e650f3e91ed0318211301beb27fa613382f28e.1706116485.git.josef@toxicpanda.com/
 * Splitted the dio-related hunks from inode.c to direct-io.c as upstream
   refactored in the meantime.
 * Pass fscrypt_info using the file_extent structure to follow the
   upstream cleanup.
---
 fs/btrfs/direct-io.c |  4 +++-
 fs/btrfs/inode.c     | 36 ++++++++++++++++++++++++++++++------
 2 files changed, 33 insertions(+), 7 deletions(-)

diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
index f3efc451d9a5..c95b4e768043 100644
--- a/fs/btrfs/direct-io.c
+++ b/fs/btrfs/direct-io.c
@@ -140,7 +140,7 @@ static int lock_extent_direct(struct inode *inode, u64 lockstart, u64 lockend,
 static struct extent_map *btrfs_create_dio_extent(struct btrfs_inode *inode,
 						  struct btrfs_dio_data *dio_data,
 						  const u64 start,
-						  const struct btrfs_file_extent *file_extent,
+						  struct btrfs_file_extent *file_extent,
 						  const int type)
 {
 	struct extent_map *em = NULL;
@@ -150,6 +150,7 @@ static struct extent_map *btrfs_create_dio_extent(struct btrfs_inode *inode,
 		em = btrfs_create_io_em(inode, start, file_extent, type);
 		if (IS_ERR(em))
 			goto out;
+		file_extent->fscrypt_info = em->fscrypt_info;
 	}
 
 	ordered = btrfs_alloc_ordered_extent(inode, start, file_extent,
@@ -276,6 +277,7 @@ static int btrfs_get_blocks_direct_write(struct extent_map **map,
 		}
 		space_reserved = true;
 
+		file_extent.fscrypt_info = em->fscrypt_info;
 		em2 = btrfs_create_dio_extent(BTRFS_I(inode), dio_data, start,
 					      &file_extent, type);
 		btrfs_dec_nocow_writers(bg);
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index aef95d6e02bf..f81bdb97b212 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -1190,10 +1190,11 @@ static void submit_one_async_extent(struct async_chunk *async_chunk,
 		ret = PTR_ERR(em);
 		goto out_free_reserve;
 	}
-	btrfs_free_extent_map(em);
 
+	file_extent.fscrypt_info = em->fscrypt_info;
 	ordered = btrfs_alloc_ordered_extent(inode, start, &file_extent,
 					     1U << BTRFS_ORDERED_COMPRESSED);
+	btrfs_free_extent_map(em);
 	if (IS_ERR(ordered)) {
 		btrfs_drop_extent_map_range(inode, start, end, false);
 		ret = PTR_ERR(ordered);
@@ -1333,10 +1334,11 @@ static int cow_one_range(struct btrfs_inode *inode, struct folio *locked_folio,
 		ret = PTR_ERR(em);
 		goto free_reserved;
 	}
-	btrfs_free_extent_map(em);
 
+	file_extent.fscrypt_info = em->fscrypt_info;
 	ordered = btrfs_alloc_ordered_extent(inode, file_offset, &file_extent,
 					     1U << BTRFS_ORDERED_REGULAR);
+	btrfs_free_extent_map(em);
 	if (IS_ERR(ordered)) {
 		btrfs_drop_extent_map_range(inode, file_offset, cur_end, false);
 		ret = PTR_ERR(ordered);
@@ -2006,18 +2008,32 @@ static int nocow_one_range(struct btrfs_inode *inode, struct folio *locked_folio
 			   u64 file_pos, bool is_prealloc)
 {
 	struct btrfs_ordered_extent *ordered;
+	struct extent_map *em;
 	const u64 len = nocow_args->file_extent.num_bytes;
 	const u64 end = file_pos + len - 1;
 	int ret = 0;
 
 	btrfs_lock_extent(&inode->io_tree, file_pos, end, cached);
 
-	if (is_prealloc) {
-		struct extent_map *em;
+	/*
+	 * We only want to do this lookup if we're encrypted, otherwise
+	 * fsrypt_info will be null and we can avoid this lookup.
+	 */
+	if (IS_ENCRYPTED(&inode->vfs_inode)) {
+		em = btrfs_get_extent(inode, NULL, file_pos, len);
+		if (IS_ERR(em)) {
+			btrfs_unlock_extent(&inode->io_tree, file_pos, end, cached);
+			return PTR_ERR(em);
+		}
+		nocow_args->file_extent.fscrypt_info = fscrypt_get_extent_info(em->fscrypt_info);
+		btrfs_free_extent_map(em);
+	}
 
+	if (is_prealloc) {
 		em = btrfs_create_io_em(inode, file_pos, &nocow_args->file_extent,
 					BTRFS_ORDERED_PREALLOC);
 		if (IS_ERR(em)) {
+			fscrypt_put_extent_info(nocow_args->file_extent.fscrypt_info);
 			ret = PTR_ERR(em);
 			goto error;
 		}
@@ -2028,6 +2044,7 @@ static int nocow_one_range(struct btrfs_inode *inode, struct folio *locked_folio
 					     is_prealloc
 					     ? (1U << BTRFS_ORDERED_PREALLOC)
 					     : (1U << BTRFS_ORDERED_NOCOW));
+	fscrypt_put_extent_info(nocow_args->file_extent.fscrypt_info);
 	if (IS_ERR(ordered)) {
 		if (is_prealloc)
 			btrfs_drop_extent_map_range(inode, file_pos, end, false);
@@ -7582,7 +7599,13 @@ struct extent_map *btrfs_create_io_em(struct btrfs_inode *inode, u64 start,
 	em->flags |= EXTENT_FLAG_PINNED;
 	if (type == BTRFS_ORDERED_COMPRESSED)
 		btrfs_extent_map_set_compression(em, file_extent->compression);
-	btrfs_extent_map_set_encryption(em, BTRFS_ENCRYPTION_NONE);
+
+	if (file_extent->fscrypt_info) {
+		btrfs_extent_map_set_encryption(em, BTRFS_ENCRYPTION_FSCRYPT);
+		em->fscrypt_info = fscrypt_get_extent_info(file_extent->fscrypt_info);
+	} else {
+		btrfs_extent_map_set_encryption(em, BTRFS_ENCRYPTION_NONE);
+	}
 
 	ret = btrfs_replace_extent_map_range(inode, em, true);
 	if (ret) {
@@ -10125,11 +10148,12 @@ ssize_t btrfs_do_encoded_write(struct kiocb *iocb, struct iov_iter *from,
 		ret = PTR_ERR(em);
 		goto out_free_reserved;
 	}
-	btrfs_free_extent_map(em);
 
+	file_extent.fscrypt_info = em->fscrypt_info;
 	ordered = btrfs_alloc_ordered_extent(inode, start, &file_extent,
 				       (1U << BTRFS_ORDERED_ENCODED) |
 				       (1U << BTRFS_ORDERED_COMPRESSED));
+	btrfs_free_extent_map(em);
 	if (IS_ERR(ordered)) {
 		btrfs_drop_extent_map_range(inode, start, end, false);
 		ret = PTR_ERR(ordered);
-- 
2.51.0


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

* [PATCH v6 23/43] btrfs: keep track of fscrypt info and orig_start for dio reads
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (21 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 22/43] btrfs: populate the ordered_extent with the fscrypt context Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-06 18:22 ` [PATCH v6 24/43] btrfs: add extent encryption context tree item type Daniel Vacek
                   ` (21 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

We keep track of this information in the ordered extent for writes, but
we need it for reads as well.  Add fscrypt_extent_info and orig_start to
the dio_data so we can populate this on reads.  This will be used later
when we attach the fscrypt context to the bios.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/acf3d79bcd72a4447b2993e78d9742fa5a05397f.1706116485.git.josef@toxicpanda.com/
 * Open-code orig_start to start - offset.
---
 fs/btrfs/direct-io.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
index c95b4e768043..d3789109ca85 100644
--- a/fs/btrfs/direct-io.c
+++ b/fs/btrfs/direct-io.c
@@ -17,6 +17,8 @@ struct btrfs_dio_data {
 	ssize_t submitted;
 	struct extent_changeset *data_reserved;
 	struct btrfs_ordered_extent *ordered;
+	struct fscrypt_extent_info *fscrypt_info;
+	u64 orig_start;
 	bool data_space_reserved;
 	bool nocow_done;
 };
@@ -550,6 +552,9 @@ static int btrfs_dio_iomap_begin(struct inode *inode, loff_t start,
 							       release_offset,
 							       release_len);
 		}
+	} else {
+		dio_data->fscrypt_info = fscrypt_get_extent_info(em->fscrypt_info);
+		dio_data->orig_start = em->start - em->offset;
 	}
 
 	/*
@@ -640,6 +645,11 @@ static int btrfs_dio_iomap_end(struct inode *inode, loff_t pos, loff_t length,
 		dio_data->ordered = NULL;
 	}
 
+	if (dio_data->fscrypt_info) {
+		fscrypt_put_extent_info(dio_data->fscrypt_info);
+		dio_data->fscrypt_info = NULL;
+	}
+
 	if (write)
 		extent_changeset_free(dio_data->data_reserved);
 	return ret;
-- 
2.51.0


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

* [PATCH v6 24/43] btrfs: add extent encryption context tree item type
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (22 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 23/43] btrfs: keep track of fscrypt info and orig_start for dio reads Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-08 15:16   ` Chris Mason
  2026-02-06 18:22 ` [PATCH v6 25/43] btrfs: pass through fscrypt_extent_info to the file extent helpers Daniel Vacek
                   ` (20 subsequent siblings)
  44 siblings, 1 reply; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

The fscrypt encryption context will be stored as a new tree item type.
This gives us flexibility to include different things in the future.

Also update the tree-checker to validate the new item type.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/7ee9171262857336011bf0e121846617c5181fa4.1706116485.git.josef@toxicpanda.com/
    ("btrfs: add an optional encryption context to the end of file extents")
 * Not much left from the original commit.
   - This was reworked so that the encryption context is now a separate
     tree item with it's unique key.
   - It is tightly related to the file extent item but still optional and
     only used for encrypted extents.
   - The content (and hence the size as well) comes from the fscrrypt
     subsystem and it is not touched by btrfs at all.
   - It's handled as a raw binary data (u8 *).
   - This patch makes sure it is correctly removed when the related extent
     is dropped.
 * As a result, the following patch https://lore.kernel.org/linux-btrfs/f0d9b2d3a40b7a963a977d3dfb62793ff7b065d1.1706116485.git.josef@toxicpanda.com/
   ("btrfs: explicitly track file extent length for replace and drop")
   was dropped as not applicable.  There's no need to track the size
   anymore as it just matches the size of the stored item.

   [RFC]: Should I have kept the structure with __u8 type? Like:

   |  struct btrfs_encryption_info {
   |        __u8 context[0];
   |  };

   I did remove it as it was only used to extend the file extent item
   structure and hence no longer needed.
---
 fs/btrfs/file.c                 | 61 +++++++++++++++++++++++++++++++++
 fs/btrfs/tree-checker.c         | 59 ++++++++++++++++++++++++++++---
 include/uapi/linux/btrfs_tree.h |  8 +++++
 3 files changed, 124 insertions(+), 4 deletions(-)

diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index 3c0db279f592..639462164d08 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -150,6 +150,7 @@ int btrfs_drop_extents(struct btrfs_trans_handle *trans,
 	u64 extent_offset = 0;
 	u64 extent_end = 0;
 	u64 last_end = args->start;
+	u64 first_ctx = 1, last_ctx = 0;
 	int del_nr = 0;
 	int del_slot = 0;
 	int extent_type;
@@ -407,6 +408,12 @@ int btrfs_drop_extents(struct btrfs_trans_handle *trans,
 				del_nr++;
 			}
 
+			if (btrfs_file_extent_encryption(leaf, fi) == BTRFS_ENCRYPTION_FSCRYPT) {
+				if (first_ctx > last_ctx)
+					first_ctx = key.offset;
+				last_ctx = key.offset;
+			}
+
 			if (update_refs &&
 			    extent_type == BTRFS_FILE_EXTENT_INLINE) {
 				args->bytes_found += extent_end - key.offset;
@@ -496,6 +503,60 @@ int btrfs_drop_extents(struct btrfs_trans_handle *trans,
 		args->extent_inserted = true;
 	}
 
+	if (first_ctx <= last_ctx) {
+		int slot, nritems;
+
+		btrfs_release_path(path);
+
+		key.objectid = ino;
+		key.type = BTRFS_FSCRYPT_CTX_KEY;
+		key.offset = first_ctx;
+
+		ret = btrfs_search_slot(trans, root, &key, path, modify_tree, !!modify_tree);
+		if (ret < 0)
+			goto out_ctx;
+next_leaf:
+		leaf = path->nodes[0];
+		slot = path->slots[0];
+
+		del_slot = slot;
+		del_nr = 0;
+		nritems = btrfs_header_nritems(leaf);
+		while (slot < nritems) {
+			btrfs_item_key_to_cpu(leaf, &key, slot);
+			if (key.objectid > ino ||
+			    key.type > BTRFS_FSCRYPT_CTX_KEY ||
+			    key.offset > last_ctx)
+				break;
+			del_nr++;
+			slot++;
+		}
+		if (del_nr) {
+			ret = btrfs_del_items(trans, root, path, del_slot, del_nr);
+			if (unlikely(ret)) {
+				btrfs_abort_transaction(trans, ret);
+				goto out_ctx;
+			}
+
+			if (slot == nritems) {
+				ret = btrfs_next_leaf(root, path);
+				if (!ret)
+					goto next_leaf;
+				if (ret > 0)
+					ret = 0;
+			}
+		}
+out_ctx:
+		if (args->path && args->extent_inserted) {
+			btrfs_release_path(path);
+
+			key.objectid = ino;
+			key.type = BTRFS_EXTENT_DATA_KEY;
+			key.offset = args->start;
+			ret = btrfs_search_slot(trans, root, &key, path, 0, 0);
+		}
+	}
+
 	if (!args->path)
 		btrfs_free_path(path);
 	else if (!args->extent_inserted)
diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
index 9675dbcd78a3..776901f297fe 100644
--- a/fs/btrfs/tree-checker.c
+++ b/fs/btrfs/tree-checker.c
@@ -186,6 +186,7 @@ static bool check_prev_ino(struct extent_buffer *leaf,
 	       key->type == BTRFS_INODE_EXTREF_KEY ||
 	       key->type == BTRFS_DIR_INDEX_KEY ||
 	       key->type == BTRFS_DIR_ITEM_KEY ||
+	       key->type == BTRFS_FSCRYPT_CTX_KEY ||
 	       key->type == BTRFS_EXTENT_DATA_KEY, "key->type=%u", key->type);
 
 	/*
@@ -204,6 +205,39 @@ static bool check_prev_ino(struct extent_buffer *leaf,
 		prev_key->objectid, key->objectid);
 	return false;
 }
+static int check_fscrypt_context(struct extent_buffer *leaf,
+				 struct btrfs_key *key, int slot,
+				 struct btrfs_key *prev_key)
+{
+	u32 sectorsize = leaf->fs_info->sectorsize;
+	u32 item_size = btrfs_item_size(leaf, slot);
+
+	if (unlikely(!IS_ALIGNED(key->offset, sectorsize))) {
+		file_extent_err(leaf, slot,
+"unaligned file_offset for encryption context, have %llu should be aligned to %u",
+			key->offset, sectorsize);
+		return -EUCLEAN;
+	}
+
+	/*
+	 * Previous key must have the same key->objectid (ino).
+	 * It can be XATTR_ITEM, INODE_ITEM or just another EXTENT_DATA.
+	 * But if objectids mismatch, it means we have a missing
+	 * INODE_ITEM.
+	 */
+	if (unlikely(!check_prev_ino(leaf, key, slot, prev_key)))
+		return -EUCLEAN;
+
+	if (unlikely(item_size > BTRFS_MAX_EXTENT_CTX_SIZE)) {
+		file_extent_err(leaf, slot,
+	"invalid encryption context size, have %u expect a maximum of %u",
+				item_size, BTRFS_MAX_EXTENT_CTX_SIZE);
+		return -EUCLEAN;
+	}
+
+	return 0;
+}
+
 static int check_extent_data_item(struct extent_buffer *leaf,
 				  struct btrfs_key *key, int slot,
 				  struct btrfs_key *prev_key)
@@ -214,6 +248,7 @@ static int check_extent_data_item(struct extent_buffer *leaf,
 	u32 item_size = btrfs_item_size(leaf, slot);
 	u64 extent_end;
 	u8 policy;
+	u8 fe_type;
 
 	if (unlikely(!IS_ALIGNED(key->offset, sectorsize))) {
 		file_extent_err(leaf, slot,
@@ -244,12 +279,12 @@ static int check_extent_data_item(struct extent_buffer *leaf,
 				SZ_4K);
 		return -EUCLEAN;
 	}
-	if (unlikely(btrfs_file_extent_type(leaf, fi) >=
-		     BTRFS_NR_FILE_EXTENT_TYPES)) {
+
+	fe_type = btrfs_file_extent_type(leaf, fi);
+	if (unlikely(fe_type >= BTRFS_NR_FILE_EXTENT_TYPES)) {
 		file_extent_err(leaf, slot,
 		"invalid type for file extent, have %u expect range [0, %u]",
-			btrfs_file_extent_type(leaf, fi),
-			BTRFS_NR_FILE_EXTENT_TYPES - 1);
+			fe_type, BTRFS_NR_FILE_EXTENT_TYPES - 1);
 		return -EUCLEAN;
 	}
 
@@ -298,6 +333,19 @@ static int check_extent_data_item(struct extent_buffer *leaf,
 		return 0;
 	}
 
+	if (policy == BTRFS_ENCRYPTION_FSCRYPT) {
+		/*
+		 * Only regular and prealloc extents should have an encryption
+		 * context.
+		 */
+		if (unlikely(fe_type != BTRFS_FILE_EXTENT_REG &&
+			     fe_type != BTRFS_FILE_EXTENT_PREALLOC)) {
+			file_extent_err(leaf, slot,
+		"invalid type for encrypted file extent, have %u", fe_type);
+			return -EUCLEAN;
+		}
+	}
+
 	/* Regular or preallocated extent has fixed item size */
 	if (unlikely(item_size != sizeof(*fi))) {
 		file_extent_err(leaf, slot,
@@ -1948,6 +1996,9 @@ static enum btrfs_tree_block_status check_leaf_item(struct extent_buffer *leaf,
 	case BTRFS_EXTENT_CSUM_KEY:
 		ret = check_csum_item(leaf, key, slot, prev_key);
 		break;
+	case BTRFS_FSCRYPT_CTX_KEY:
+		ret = check_fscrypt_context(leaf, key, slot, prev_key);
+		break;
 	case BTRFS_DIR_ITEM_KEY:
 	case BTRFS_DIR_INDEX_KEY:
 	case BTRFS_XATTR_ITEM_KEY:
diff --git a/include/uapi/linux/btrfs_tree.h b/include/uapi/linux/btrfs_tree.h
index cb8dbcc612e9..76937d66b5dd 100644
--- a/include/uapi/linux/btrfs_tree.h
+++ b/include/uapi/linux/btrfs_tree.h
@@ -168,6 +168,7 @@
 #define BTRFS_VERITY_MERKLE_ITEM_KEY	37
 
 #define BTRFS_FSCRYPT_INODE_CTX_KEY	41
+#define BTRFS_FSCRYPT_CTX_KEY		42
 
 #define BTRFS_ORPHAN_ITEM_KEY		48
 /* reserve 2-15 close to the inode for later flexibility */
@@ -1079,6 +1080,13 @@ enum {
 	BTRFS_NR_FILE_EXTENT_TYPES = 3,
 };
 
+/*
+ * Currently just the FSCRYPT_SET_CONTEXT_MAX_SIZE, which is larger than the
+ * current extent context size from fscrypt, so this should give us plenty of
+ * breathing room for expansion later.
+ */
+#define BTRFS_MAX_EXTENT_CTX_SIZE 40
+
 enum btrfs_encryption_type {
 	BTRFS_ENCRYPTION_NONE,
 	BTRFS_ENCRYPTION_FSCRYPT,
-- 
2.51.0


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

* [PATCH v6 25/43] btrfs: pass through fscrypt_extent_info to the file extent helpers
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (23 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 24/43] btrfs: add extent encryption context tree item type Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-06 18:22 ` [PATCH v6 26/43] btrfs: implement the fscrypt extent encryption hooks Daniel Vacek
                   ` (19 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

Now that we have the fscrypt_extnet_info in all of the supporting
structures, pass this through and set the file extent encryption bit
accordingly from the supporting structures.  In subsequent patches code
will be added to populate these appropriately.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/a8b9dd312a4504f209e861cca5289a528b30ff95.1706116485.git.josef@toxicpanda.com/
 * No changes since.
---
 fs/btrfs/inode.c    | 16 ++++++++++------
 fs/btrfs/tree-log.c |  2 +-
 2 files changed, 11 insertions(+), 7 deletions(-)

diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index f81bdb97b212..f449839d6d84 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -3018,7 +3018,9 @@ int btrfs_writepage_cow_fixup(struct folio *folio)
 }
 
 static int insert_reserved_file_extent(struct btrfs_trans_handle *trans,
-				       struct btrfs_inode *inode, u64 file_pos,
+				       struct btrfs_inode *inode,
+				       struct fscrypt_extent_info *fscrypt_info,
+				       u64 file_pos,
 				       struct btrfs_file_extent_item *stack_fi,
 				       const bool update_inode_bytes,
 				       u64 qgroup_reserved)
@@ -3142,7 +3144,7 @@ static int insert_ordered_extent_file_extent(struct btrfs_trans_handle *trans,
 	btrfs_set_stack_file_extent_num_bytes(&stack_fi, num_bytes);
 	btrfs_set_stack_file_extent_ram_bytes(&stack_fi, ram_bytes);
 	btrfs_set_stack_file_extent_compression(&stack_fi, oe->compress_type);
-	btrfs_set_stack_file_extent_encryption(&stack_fi, BTRFS_ENCRYPTION_NONE);
+	btrfs_set_stack_file_extent_encryption(&stack_fi, oe->encryption_type);
 	/* Other encoding is reserved and always 0 */
 
 	/*
@@ -3155,7 +3157,7 @@ static int insert_ordered_extent_file_extent(struct btrfs_trans_handle *trans,
 			     test_bit(BTRFS_ORDERED_ENCODED, &oe->flags) ||
 			     test_bit(BTRFS_ORDERED_TRUNCATED, &oe->flags);
 
-	return insert_reserved_file_extent(trans, oe->inode,
+	return insert_reserved_file_extent(trans, oe->inode, oe->fscrypt_info,
 					   oe->file_offset, &stack_fi,
 					   update_inode_bytes, oe->qgroup_rsv);
 }
@@ -9213,6 +9215,7 @@ static struct btrfs_trans_handle *insert_prealloc_file_extent(
 				       struct btrfs_trans_handle *trans_in,
 				       struct btrfs_inode *inode,
 				       struct btrfs_key *ins,
+				       struct fscrypt_extent_info *fscrypt_info,
 				       u64 file_offset)
 {
 	struct btrfs_file_extent_item stack_fi;
@@ -9232,7 +9235,8 @@ static struct btrfs_trans_handle *insert_prealloc_file_extent(
 	btrfs_set_stack_file_extent_num_bytes(&stack_fi, len);
 	btrfs_set_stack_file_extent_ram_bytes(&stack_fi, len);
 	btrfs_set_stack_file_extent_compression(&stack_fi, BTRFS_COMPRESS_NONE);
-	btrfs_set_stack_file_extent_encryption(&stack_fi, BTRFS_ENCRYPTION_NONE);
+	btrfs_set_stack_file_extent_encryption(&stack_fi, fscrypt_info? BTRFS_ENCRYPTION_FSCRYPT:
+									BTRFS_ENCRYPTION_NONE);
 	/* Other encoding is reserved and always 0 */
 
 	ret = btrfs_qgroup_release_data(inode, file_offset, len, &qgroup_released);
@@ -9240,7 +9244,7 @@ static struct btrfs_trans_handle *insert_prealloc_file_extent(
 		return ERR_PTR(ret);
 
 	if (trans) {
-		ret = insert_reserved_file_extent(trans, inode,
+		ret = insert_reserved_file_extent(trans, inode, fscrypt_info,
 						  file_offset, &stack_fi,
 						  true, qgroup_released);
 		if (ret)
@@ -9333,7 +9337,7 @@ static int __btrfs_prealloc_file_range(struct inode *inode, int mode,
 
 		last_alloc = ins.offset;
 		trans = insert_prealloc_file_extent(trans, BTRFS_I(inode),
-						    &ins, cur_offset);
+						    &ins, NULL, cur_offset);
 		/*
 		 * Now that we inserted the prealloc extent we can finally
 		 * decrement the number of reservations in the block group.
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
index 118dd1888996..43b1470ebf71 100644
--- a/fs/btrfs/tree-log.c
+++ b/fs/btrfs/tree-log.c
@@ -5123,7 +5123,7 @@ static int log_one_extent(struct btrfs_trans_handle *trans,
 	u64 block_start = btrfs_extent_map_block_start(em);
 	u64 block_len;
 	int ret;
-	u8 encryption = BTRFS_ENCRYPTION_NONE;
+	u8 encryption = btrfs_extent_map_encryption(em);
 
 	btrfs_set_stack_file_extent_generation(&fi, trans->transid);
 	if (em->flags & EXTENT_FLAG_PREALLOC)
-- 
2.51.0


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

* [PATCH v6 26/43] btrfs: implement the fscrypt extent encryption hooks
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (24 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 25/43] btrfs: pass through fscrypt_extent_info to the file extent helpers Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-06 18:22 ` [PATCH v6 27/43] btrfs: setup fscrypt_extent_info for new extents Daniel Vacek
                   ` (18 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

This patch implements the necessary hooks from fscrypt to support
per-extent encryption.  There's two main entry points

btrfs_fscrypt_load_extent_info
btrfs_fscrypt_save_extent_info

btrfs_fscrypt_load_extent_info gets called when we create the extent
maps from the file extent item at btrfs_get_extent() time.  We read the
extent context, and pass it into fscrypt to create the appropriate
fscrypt_extent_info structure.  This is then used on the bio's to make
sure the encryption is done properly.

btrfs_fscrypt_save_extent_info is used to generate the fscrypt context
from fscrypt and save it into the tree item when we create a new
file extent item.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/30eaad31964c88c3497a0c5bc8f2c727c1dc763a.1706116485.git.josef@toxicpanda.com/
 * Also significantly reworked due to the changes in previous commit [24/43].
---
 fs/btrfs/ctree.h    |  3 ++
 fs/btrfs/defrag.c   |  6 ++++
 fs/btrfs/file.c     | 11 ++++++
 fs/btrfs/fscrypt.c  | 84 +++++++++++++++++++++++++++++++++++++++++++++
 fs/btrfs/fscrypt.h  | 29 ++++++++++++++++
 fs/btrfs/inode.c    | 36 +++++++++++++++++++
 fs/btrfs/reflink.c  | 43 ++++++++++++++++++++++-
 fs/btrfs/tree-log.c | 19 ++++++++++
 8 files changed, 230 insertions(+), 1 deletion(-)

diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h
index 6de7ad191e04..89d3c3137786 100644
--- a/fs/btrfs/ctree.h
+++ b/fs/btrfs/ctree.h
@@ -400,6 +400,9 @@ struct btrfs_replace_extent_info {
 	u64 file_offset;
 	/* Pointer to a file extent item of type regular or prealloc. */
 	char *extent_buf;
+	/* The fscrypt_extent_info for a new extent. */
+	u8 *fscrypt_ctx;
+	u32 fscrypt_context_size;
 	/*
 	 * Set to true when attempting to replace a file range with a new extent
 	 * described by this structure, set to false when attempting to clone an
diff --git a/fs/btrfs/defrag.c b/fs/btrfs/defrag.c
index ecf05cd64696..f64c0502cef9 100644
--- a/fs/btrfs/defrag.c
+++ b/fs/btrfs/defrag.c
@@ -16,6 +16,7 @@
 #include "file-item.h"
 #include "super.h"
 #include "compression.h"
+#include "fscrypt.h"
 
 static struct kmem_cache *btrfs_inode_defrag_cachep;
 
@@ -720,6 +721,11 @@ static struct extent_map *defrag_get_extent(struct btrfs_inode *inode,
 		if (ret > 0)
 			goto not_found;
 	}
+	btrfs_release_path(&path);
+
+	ret = btrfs_fscrypt_load_extent_info(inode, &path, &key, em);
+	if (ret)
+		goto err;
 	return em;
 
 not_found:
diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index 639462164d08..89cd4f49e84b 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -37,6 +37,7 @@
 #include "file.h"
 #include "super.h"
 #include "print-tree.h"
+#include "fscrypt.h"
 
 /*
  * Unlock folio after btrfs_file_write() is done with it.
@@ -2401,6 +2402,16 @@ static int btrfs_insert_replace_extent(struct btrfs_trans_handle *trans,
 	if (extent_info->is_new_extent)
 		btrfs_set_file_extent_generation(leaf, extent, trans->transid);
 	btrfs_release_path(path);
+	if (extent_info->fscrypt_context_size) {
+		key.type = BTRFS_FSCRYPT_CTX_KEY;
+		ret = btrfs_insert_empty_item(trans, root, path, &key,
+					      extent_info->fscrypt_context_size);
+		if (ret)
+			return ret;
+		btrfs_fscrypt_save_extent_info(path,
+					       extent_info->fscrypt_ctx,
+					       extent_info->fscrypt_context_size);
+	}
 
 	ret = btrfs_inode_set_file_extent_range(inode, extent_info->file_offset,
 						replace_len);
diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
index 608f8797ea6f..26060f3e50de 100644
--- a/fs/btrfs/fscrypt.c
+++ b/fs/btrfs/fscrypt.c
@@ -212,9 +212,93 @@ static struct block_device **btrfs_fscrypt_get_devices(struct super_block *sb,
 	return devs;
 }
 
+int btrfs_fscrypt_load_extent_info(struct btrfs_inode *inode,
+				   struct btrfs_path *path,
+				   struct btrfs_key *key,
+				   struct extent_map *em)
+{
+	struct extent_buffer *leaf;
+	int slot;
+	unsigned long offset;
+	u8 ctx[BTRFS_MAX_EXTENT_CTX_SIZE];
+	unsigned long size;
+	struct fscrypt_extent_info *info;
+	unsigned long nofs_flag;
+	int ret;
+
+	if (em->disk_bytenr == EXTENT_MAP_HOLE)
+		return 0;
+	if (btrfs_extent_map_encryption(em) != BTRFS_ENCRYPTION_FSCRYPT)
+		return 0;
+
+	key->type = BTRFS_FSCRYPT_CTX_KEY;
+	ret = btrfs_search_slot(NULL, inode->root, key, path, 0, 0);
+	leaf = path->nodes[0];
+	slot = path->slots[0];
+	if (ret) {
+		btrfs_err(leaf->fs_info,
+	"missing or error searching encryption context item in leaf: root=%llu block=%llu slot=%d ino=%llu file_offset=%llu, err %i",
+			  btrfs_header_owner(leaf), btrfs_header_bytenr(leaf), slot,
+			  key->objectid, key->offset, ret);
+		btrfs_release_path(path);
+		return ret;
+	}
+
+	size = btrfs_item_size(leaf, slot);
+	if (size > FSCRYPT_SET_CONTEXT_MAX_SIZE) {
+		btrfs_err(leaf->fs_info,
+	"unexpected or corrupted encryption context size in leaf: root=%llu block=%llu slot=%d ino=%llu file_offset=%llu, size %lu (too big)",
+			  btrfs_header_owner(leaf), btrfs_header_bytenr(leaf), slot,
+			  key->objectid, key->offset, size);
+		btrfs_release_path(path);
+		return -EIO;
+	}
+
+	offset = btrfs_item_ptr_offset(leaf, slot),
+	read_extent_buffer(leaf, ctx, offset, size);
+	btrfs_release_path(path);
+
+	nofs_flag = memalloc_nofs_save();
+	info = fscrypt_load_extent_info(&inode->vfs_inode, ctx, size);
+	memalloc_nofs_restore(nofs_flag);
+	if (IS_ERR(info))
+		return PTR_ERR(info);
+	em->fscrypt_info = info;
+	return 0;
+}
+
+void btrfs_fscrypt_save_extent_info(struct btrfs_path *path, u8 *ctx, unsigned long size)
+{
+	struct extent_buffer *leaf = path->nodes[0];
+	unsigned long offset = btrfs_item_ptr_offset(leaf, path->slots[0]);
+
+	ASSERT(size <= FSCRYPT_SET_CONTEXT_MAX_SIZE);
+
+	write_extent_buffer(leaf, ctx, offset, size);
+	btrfs_release_path(path);
+}
+
+ssize_t btrfs_fscrypt_context_for_new_extent(struct btrfs_inode *inode,
+					     struct fscrypt_extent_info *info,
+					     u8 *ctx)
+{
+	ssize_t ret;
+
+	if (!info)
+		return 0;
+
+	ret = fscrypt_context_for_new_extent(&inode->vfs_inode, info, ctx);
+	if (ret < 0) {
+		btrfs_err_rl(inode->root->fs_info, "invalid encrypt context");
+		return ret;
+	}
+	return ret;
+}
+
 const struct fscrypt_operations btrfs_fscrypt_ops = {
 	.inode_info_offs = (int)offsetof(struct btrfs_inode, i_crypt_info) -
 			   (int)offsetof(struct btrfs_inode, vfs_inode),
+	.has_per_extent_encryption = 1,
 	.get_context = btrfs_fscrypt_get_context,
 	.set_context = btrfs_fscrypt_set_context,
 	.empty_dir = btrfs_fscrypt_empty_dir,
diff --git a/fs/btrfs/fscrypt.h b/fs/btrfs/fscrypt.h
index c5cdc27f943c..68eab4606935 100644
--- a/fs/btrfs/fscrypt.h
+++ b/fs/btrfs/fscrypt.h
@@ -16,8 +16,27 @@ int btrfs_fscrypt_get_disk_name(struct extent_buffer *leaf,
 bool btrfs_fscrypt_match_name(struct fscrypt_name *fname,
 			      struct extent_buffer *leaf,
 			      unsigned long de_name, u32 de_name_len);
+int btrfs_fscrypt_load_extent_info(struct btrfs_inode *inode,
+				   struct btrfs_path *path,
+				   struct btrfs_key *key,
+				   struct extent_map *em);
+void btrfs_fscrypt_save_extent_info(struct btrfs_path *path, u8 *ctx, unsigned long size);
+ssize_t btrfs_fscrypt_context_for_new_extent(struct btrfs_inode *inode,
+					     struct fscrypt_extent_info *info,
+					     u8 *ctx);
 
 #else
+static inline void btrfs_fscrypt_save_extent_info(struct btrfs_path *path,
+						  u8 *ctx, unsigned long size) { }
+
+static inline int btrfs_fscrypt_load_extent_info(struct btrfs_inode *inode,
+						 struct btrfs_path *path,
+						 struct btrfs_key *key,
+						 struct extent_map *em)
+{
+	return 0;
+}
+
 static inline int btrfs_fscrypt_get_disk_name(struct extent_buffer *leaf,
 					      struct btrfs_dir_item *di,
 					      struct fscrypt_str *qstr)
@@ -34,6 +53,16 @@ static inline bool btrfs_fscrypt_match_name(struct fscrypt_name *fname,
 		return false;
 	return !memcmp_extent_buffer(leaf, fname->disk_name.name, de_name, de_name_len);
 }
+
+static inline ssize_t btrfs_fscrypt_context_for_new_extent(struct btrfs_inode *inode,
+							   struct fscrypt_extent_info *info,
+							   u8 *ctx)
+{
+	if (!info)
+		return 0;
+	return -EINVAL;
+}
+
 #endif /* CONFIG_FS_ENCRYPTION */
 
 extern const struct fscrypt_operations btrfs_fscrypt_ops;
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index f449839d6d84..15191dffa354 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -3036,8 +3036,16 @@ static int insert_reserved_file_extent(struct btrfs_trans_handle *trans,
 	u64 num_bytes = btrfs_stack_file_extent_num_bytes(stack_fi);
 	u64 ram_bytes = btrfs_stack_file_extent_ram_bytes(stack_fi);
 	struct btrfs_drop_extents_args drop_args = { 0 };
+	u8 fscrypt_ctx[FSCRYPT_SET_CONTEXT_MAX_SIZE];
+	ssize_t fscrypt_context_size;
 	int ret;
 
+	fscrypt_context_size = btrfs_fscrypt_context_for_new_extent(inode,
+								    fscrypt_info,
+								    fscrypt_ctx);
+	if (fscrypt_context_size < 0)
+		return (int)fscrypt_context_size;
+
 	path = btrfs_alloc_path();
 	if (!path)
 		return -ENOMEM;
@@ -3078,6 +3086,16 @@ static int insert_reserved_file_extent(struct btrfs_trans_handle *trans,
 
 	btrfs_release_path(path);
 
+	if (fscrypt_context_size) {
+		ins.objectid = btrfs_ino(inode);
+		ins.type = BTRFS_FSCRYPT_CTX_KEY;
+		ins.offset = file_pos;
+		ret = btrfs_insert_empty_item(trans, root, path, &ins, fscrypt_context_size);
+		if (ret)
+			return ret;
+		btrfs_fscrypt_save_extent_info(path, fscrypt_ctx, fscrypt_context_size);
+	}
+
 	/*
 	 * If we dropped an inline extent here, we know the range where it is
 	 * was not marked with the EXTENT_DELALLOC_NEW bit, so we update the
@@ -7406,6 +7424,12 @@ struct extent_map *btrfs_get_extent(struct btrfs_inode *inode,
 		goto out;
 	}
 
+	ret = btrfs_fscrypt_load_extent_info(inode, path, &found_key, em);
+	if (ret > 0)
+		ret = -EIO;
+	if (ret < 0)
+		goto out;
+
 	write_lock(&em_tree->lock);
 	ret = btrfs_add_extent_mapping(inode, &em, start, len);
 	write_unlock(&em_tree->lock);
@@ -9219,6 +9243,8 @@ static struct btrfs_trans_handle *insert_prealloc_file_extent(
 				       u64 file_offset)
 {
 	struct btrfs_file_extent_item stack_fi;
+	u8 fscrypt_ctx[FSCRYPT_SET_CONTEXT_MAX_SIZE];
+	ssize_t fscrypt_context_size;
 	struct btrfs_replace_extent_info extent_info;
 	struct btrfs_trans_handle *trans = trans_in;
 	struct btrfs_path *path;
@@ -9252,6 +9278,14 @@ static struct btrfs_trans_handle *insert_prealloc_file_extent(
 		return trans;
 	}
 
+	fscrypt_context_size = btrfs_fscrypt_context_for_new_extent(inode,
+								    fscrypt_info,
+								    fscrypt_ctx);
+	if (fscrypt_context_size < 0) {
+		ret = (int)fscrypt_context_size;
+		goto free_qgroup;
+	}
+
 	extent_info.disk_offset = start;
 	extent_info.disk_len = len;
 	extent_info.data_offset = 0;
@@ -9262,6 +9296,8 @@ static struct btrfs_trans_handle *insert_prealloc_file_extent(
 	extent_info.update_times = true;
 	extent_info.qgroup_reserved = qgroup_released;
 	extent_info.insertions = 0;
+	extent_info.fscrypt_ctx = fscrypt_ctx;
+	extent_info.fscrypt_context_size = fscrypt_context_size;
 
 	path = btrfs_alloc_path();
 	if (!path) {
diff --git a/fs/btrfs/reflink.c b/fs/btrfs/reflink.c
index 314cb95ba846..8e8d2f3c4c0a 100644
--- a/fs/btrfs/reflink.c
+++ b/fs/btrfs/reflink.c
@@ -376,7 +376,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode,
 		struct btrfs_key new_key;
 		u64 disko = 0, diskl = 0;
 		u64 datao = 0, datal = 0;
-		u8 comp;
+		u8 comp, encryption;
 		u64 drop_start;
 
 		/* Note the key will change type as we walk through the tree */
@@ -419,6 +419,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode,
 		extent = btrfs_item_ptr(leaf, slot,
 					struct btrfs_file_extent_item);
 		extent_gen = btrfs_file_extent_generation(leaf, extent);
+		encryption = btrfs_file_extent_encryption(leaf, extent);
 		comp = btrfs_file_extent_compression(leaf, extent);
 		type = btrfs_file_extent_type(leaf, extent);
 		if (type == BTRFS_FILE_EXTENT_REG ||
@@ -478,6 +479,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode,
 		if (type == BTRFS_FILE_EXTENT_REG ||
 		    type == BTRFS_FILE_EXTENT_PREALLOC) {
 			struct btrfs_replace_extent_info clone_info;
+			u8 fscrypt_ctx[FSCRYPT_SET_CONTEXT_MAX_SIZE];
 
 			/*
 			 *    a  | --- range to clone ---|  b
@@ -494,6 +496,43 @@ static int btrfs_clone(struct inode *src, struct inode *inode,
 				datal -= off - key.offset;
 			}
 
+			if (encryption == BTRFS_ENCRYPTION_FSCRYPT) {
+				unsigned long offset;
+
+				key.type = BTRFS_FSCRYPT_CTX_KEY;
+				ret = btrfs_search_slot(NULL, BTRFS_I(src)->root, &key, path, 0, 0);
+				if (ret < 0)
+					goto out;
+				leaf = path->nodes[0];
+				slot = path->slots[0];
+				if (ret) {
+					btrfs_err(leaf->fs_info,
+	"missing or error searching encryption context item in leaf: root=%llu block=%llu slot=%d ino=%llu file_offset=%llu, err %i",
+						btrfs_header_owner(leaf),
+						btrfs_header_bytenr(leaf), slot,
+						key.objectid, key.offset, ret);
+					goto out;
+				}
+
+				size = btrfs_item_size(leaf, slot);
+				if (size > FSCRYPT_SET_CONTEXT_MAX_SIZE) {
+					btrfs_err(leaf->fs_info,
+	"unexpected or corrupted encryption context size in leaf: root=%llu block=%llu slot=%d ino=%llu file_offset=%llu, size %u (too big)",
+						btrfs_header_owner(leaf),
+						btrfs_header_bytenr(leaf), slot,
+						key.objectid, key.offset, size);
+					ret = -EIO;
+					goto out;
+				}
+
+				offset = btrfs_item_ptr_offset(leaf, slot),
+				read_extent_buffer(leaf, fscrypt_ctx, offset, size);
+				btrfs_release_path(path);
+				key.type = BTRFS_EXTENT_DATA_KEY;
+			} else {
+				size = 0;
+			}
+
 			clone_info.disk_offset = disko;
 			clone_info.disk_len = diskl;
 			clone_info.data_offset = datao;
@@ -502,6 +541,8 @@ static int btrfs_clone(struct inode *src, struct inode *inode,
 			clone_info.extent_buf = buf;
 			clone_info.is_new_extent = false;
 			clone_info.update_times = !no_time_update;
+			clone_info.fscrypt_ctx = fscrypt_ctx;
+			clone_info.fscrypt_context_size = size;
 			ret = btrfs_replace_file_extents(BTRFS_I(inode), path,
 					drop_start, new_key.offset + datal - 1,
 					&clone_info, &trans);
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
index 43b1470ebf71..0d01e31a4592 100644
--- a/fs/btrfs/tree-log.c
+++ b/fs/btrfs/tree-log.c
@@ -30,6 +30,7 @@
 #include "print-tree.h"
 #include "tree-checker.h"
 #include "delayed-inode.h"
+#include "fscrypt.h"
 
 #define MAX_CONFLICT_INODES 10
 
@@ -5118,6 +5119,8 @@ static int log_one_extent(struct btrfs_trans_handle *trans,
 	struct btrfs_file_extent_item fi = { 0 };
 	struct extent_buffer *leaf;
 	struct btrfs_key key;
+	u8 fscrypt_ctx[FSCRYPT_SET_CONTEXT_MAX_SIZE];
+	ssize_t fscrypt_context_size;
 	enum btrfs_compression_type compress_type;
 	u64 extent_offset = em->offset;
 	u64 block_start = btrfs_extent_map_block_start(em);
@@ -5125,6 +5128,12 @@ static int log_one_extent(struct btrfs_trans_handle *trans,
 	int ret;
 	u8 encryption = btrfs_extent_map_encryption(em);
 
+	fscrypt_context_size = btrfs_fscrypt_context_for_new_extent(inode,
+								    em->fscrypt_info,
+								    fscrypt_ctx);
+	if (fscrypt_context_size < 0)
+		return (int)fscrypt_context_size;
+
 	btrfs_set_stack_file_extent_generation(&fi, trans->transid);
 	if (em->flags & EXTENT_FLAG_PREALLOC)
 		btrfs_set_stack_file_extent_type(&fi, BTRFS_FILE_EXTENT_PREALLOC);
@@ -5188,6 +5197,16 @@ static int log_one_extent(struct btrfs_trans_handle *trans,
 
 	btrfs_release_path(path);
 
+	if (fscrypt_context_size) {
+		key.objectid = btrfs_ino(inode);
+		key.type = BTRFS_FSCRYPT_CTX_KEY;
+		key.offset = em->start;
+		ret = btrfs_insert_empty_item(trans, log, path, &key, fscrypt_context_size);
+		if (ret)
+			return ret;
+		btrfs_fscrypt_save_extent_info(path, fscrypt_ctx, fscrypt_context_size);
+	}
+
 	return ret;
 }
 
-- 
2.51.0


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

* [PATCH v6 27/43] btrfs: setup fscrypt_extent_info for new extents
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (25 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 26/43] btrfs: implement the fscrypt extent encryption hooks Daniel Vacek
@ 2026-02-06 18:22 ` Daniel Vacek
  2026-02-06 18:23 ` [PATCH v6 28/43] btrfs: populate ordered_extent with the orig offset Daniel Vacek
                   ` (17 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:22 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

New extents for encrypted inodes must have a fscrypt_extent_info, which
has the necessary keys and does all the registration at the block layer
for them.  This is passed through all of the infrastructure we've
previously added to make sure the context gets saved properly with the
file extents.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/d8ab016d25f70c9365f508af1d8e0b9ab7c09d76.1706116485.git.josef@toxicpanda.com/
 * No changes since.
---
 fs/btrfs/inode.c | 30 +++++++++++++++++++++++++++++-
 1 file changed, 29 insertions(+), 1 deletion(-)

diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 15191dffa354..de1989edffc1 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -7629,6 +7629,16 @@ struct extent_map *btrfs_create_io_em(struct btrfs_inode *inode, u64 start,
 	if (file_extent->fscrypt_info) {
 		btrfs_extent_map_set_encryption(em, BTRFS_ENCRYPTION_FSCRYPT);
 		em->fscrypt_info = fscrypt_get_extent_info(file_extent->fscrypt_info);
+	} else if (IS_ENCRYPTED(&inode->vfs_inode)) {
+		struct fscrypt_extent_info *fscrypt_info;
+
+		btrfs_extent_map_set_encryption(em, BTRFS_ENCRYPTION_FSCRYPT);
+		fscrypt_info = fscrypt_prepare_new_extent(&inode->vfs_inode);
+		if (IS_ERR(fscrypt_info)) {
+			btrfs_free_extent_map(em);
+			return ERR_CAST(fscrypt_info);
+		}
+		em->fscrypt_info = fscrypt_info;
 	} else {
 		btrfs_extent_map_set_encryption(em, BTRFS_ENCRYPTION_NONE);
 	}
@@ -9348,6 +9358,9 @@ static int __btrfs_prealloc_file_range(struct inode *inode, int mode,
 	if (trans)
 		own_trans = false;
 	while (num_bytes > 0) {
+		struct fscrypt_extent_info *fscrypt_info = NULL;
+		int encryption_type = BTRFS_ENCRYPTION_NONE;
+
 		cur_bytes = min_t(u64, num_bytes, SZ_256M);
 		cur_bytes = max(cur_bytes, min_size);
 		/*
@@ -9362,6 +9375,17 @@ static int __btrfs_prealloc_file_range(struct inode *inode, int mode,
 		if (ret)
 			break;
 
+		if (IS_ENCRYPTED(inode)) {
+			fscrypt_info = fscrypt_prepare_new_extent(inode);
+			if (IS_ERR(fscrypt_info)) {
+				btrfs_dec_block_group_reservations(fs_info, ins.objectid);
+				btrfs_free_reserved_extent(fs_info, ins.objectid, ins.offset, 0);
+				ret = PTR_ERR(fscrypt_info);
+				break;
+			}
+			encryption_type = BTRFS_ENCRYPTION_FSCRYPT;
+		}
+
 		/*
 		 * We've reserved this space, and thus converted it from
 		 * ->bytes_may_use to ->bytes_reserved.  Any error that happens
@@ -9373,7 +9397,7 @@ static int __btrfs_prealloc_file_range(struct inode *inode, int mode,
 
 		last_alloc = ins.offset;
 		trans = insert_prealloc_file_extent(trans, BTRFS_I(inode),
-						    &ins, NULL, cur_offset);
+						    &ins, fscrypt_info, cur_offset);
 		/*
 		 * Now that we inserted the prealloc extent we can finally
 		 * decrement the number of reservations in the block group.
@@ -9383,6 +9407,7 @@ static int __btrfs_prealloc_file_range(struct inode *inode, int mode,
 		btrfs_dec_block_group_reservations(fs_info, ins.objectid);
 		if (IS_ERR(trans)) {
 			ret = PTR_ERR(trans);
+			fscrypt_put_extent_info(fscrypt_info);
 			btrfs_free_reserved_extent(fs_info, ins.objectid,
 						   ins.offset, false);
 			break;
@@ -9390,6 +9415,7 @@ static int __btrfs_prealloc_file_range(struct inode *inode, int mode,
 
 		em = btrfs_alloc_extent_map();
 		if (!em) {
+			fscrypt_put_extent_info(fscrypt_info);
 			btrfs_drop_extent_map_range(BTRFS_I(inode), cur_offset,
 					    cur_offset + ins.offset - 1, false);
 			btrfs_set_inode_full_sync(BTRFS_I(inode));
@@ -9404,6 +9430,8 @@ static int __btrfs_prealloc_file_range(struct inode *inode, int mode,
 		em->ram_bytes = ins.offset;
 		em->flags |= EXTENT_FLAG_PREALLOC;
 		em->generation = trans->transid;
+		em->fscrypt_info = fscrypt_info;
+		btrfs_extent_map_set_encryption(em, encryption_type);
 
 		ret = btrfs_replace_extent_map_range(BTRFS_I(inode), em, true);
 		btrfs_free_extent_map(em);
-- 
2.51.0


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

* [PATCH v6 28/43] btrfs: populate ordered_extent with the orig offset
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (26 preceding siblings ...)
  2026-02-06 18:22 ` [PATCH v6 27/43] btrfs: setup fscrypt_extent_info for new extents Daniel Vacek
@ 2026-02-06 18:23 ` Daniel Vacek
  2026-02-08 15:12   ` Chris Mason
  2026-02-06 18:23 ` [PATCH v6 29/43] btrfs: set the bio fscrypt context when applicable Daniel Vacek
                   ` (16 subsequent siblings)
  44 siblings, 1 reply; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:23 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

For extent encryption we have to use a logical block nr as input for the
IV.  For btrfs we're using the offset into the extent we're operating
on.  For most ordered extents this is the same as the file_offset,
however for prealloc and NOCOW we have to use the original offset.

Add this as an argument and plumb it through everywhere, this will be
used when setting up the bio.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/fe06053fe2973c424dd539fecfee8cc171bdd22d.1706116485.git.josef@toxicpanda.com/
 * Splitted the dio-related hunks from inode.c to direct-io.c as upstream
   refactored in the meantime.
 * Open-code orig_start to start - offset.
---
 fs/btrfs/direct-io.c    |  1 +
 fs/btrfs/inode.c        |  5 +++++
 fs/btrfs/ordered-data.c | 21 ++++++++++++++++++---
 fs/btrfs/ordered-data.h |  7 +++++++
 4 files changed, 31 insertions(+), 3 deletions(-)

diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
index d3789109ca85..2d89ac05b1b3 100644
--- a/fs/btrfs/direct-io.c
+++ b/fs/btrfs/direct-io.c
@@ -155,6 +155,7 @@ static struct extent_map *btrfs_create_dio_extent(struct btrfs_inode *inode,
 		file_extent->fscrypt_info = em->fscrypt_info;
 	}
 
+	file_extent->orig_offset = start - file_extent->offset;
 	ordered = btrfs_alloc_ordered_extent(inode, start, file_extent,
 					     (1U << type) |
 					     (1U << BTRFS_ORDERED_DIRECT));
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index de1989edffc1..b28e1b7497b8 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -1192,6 +1192,7 @@ static void submit_one_async_extent(struct async_chunk *async_chunk,
 	}
 
 	file_extent.fscrypt_info = em->fscrypt_info;
+	file_extent.orig_offset = start;
 	ordered = btrfs_alloc_ordered_extent(inode, start, &file_extent,
 					     1U << BTRFS_ORDERED_COMPRESSED);
 	btrfs_free_extent_map(em);
@@ -1336,6 +1337,7 @@ static int cow_one_range(struct btrfs_inode *inode, struct folio *locked_folio,
 	}
 
 	file_extent.fscrypt_info = em->fscrypt_info;
+	file_extent.orig_offset = file_offset;
 	ordered = btrfs_alloc_ordered_extent(inode, file_offset, &file_extent,
 					     1U << BTRFS_ORDERED_REGULAR);
 	btrfs_free_extent_map(em);
@@ -2281,6 +2283,8 @@ static noinline int run_delalloc_nocow(struct btrfs_inode *inode,
 			cow_start = (u64)-1;
 		}
 
+		nocow_args.file_extent.orig_offset =
+			found_key.offset - nocow_args.file_extent.offset;
 		ret = nocow_one_range(inode, locked_folio, &cached_state,
 				      &nocow_args, cur_offset,
 				      extent_type == BTRFS_FILE_EXTENT_PREALLOC);
@@ -10218,6 +10222,7 @@ ssize_t btrfs_do_encoded_write(struct kiocb *iocb, struct iov_iter *from,
 	}
 
 	file_extent.fscrypt_info = em->fscrypt_info;
+	file_extent.orig_offset = start - encoded->unencoded_offset;
 	ordered = btrfs_alloc_ordered_extent(inode, start, &file_extent,
 				       (1U << BTRFS_ORDERED_ENCODED) |
 				       (1U << BTRFS_ORDERED_COMPRESSED));
diff --git a/fs/btrfs/ordered-data.c b/fs/btrfs/ordered-data.c
index 1b4d20a2f983..bb5477ce58db 100644
--- a/fs/btrfs/ordered-data.c
+++ b/fs/btrfs/ordered-data.c
@@ -146,7 +146,8 @@ static inline struct rb_node *ordered_tree_search(struct btrfs_inode *inode,
 }
 
 static struct btrfs_ordered_extent *alloc_ordered_extent(
-			struct btrfs_inode *inode, u64 file_offset, u64 num_bytes,
+			struct btrfs_inode *inode,
+			u64 file_offset, u64 orig_offset, u64 num_bytes,
 			u64 ram_bytes, u64 disk_bytenr, u64 disk_num_bytes,
 			u64 offset, unsigned long flags, int compress_type,
 			struct fscrypt_extent_info *fscrypt_info)
@@ -180,6 +181,7 @@ static struct btrfs_ordered_extent *alloc_ordered_extent(
 	}
 
 	entry->file_offset = file_offset;
+	entry->orig_offset = orig_offset;
 	entry->num_bytes = num_bytes;
 	entry->ram_bytes = ram_bytes;
 	entry->disk_bytenr = disk_bytenr;
@@ -268,6 +270,7 @@ static void insert_ordered_extent(struct btrfs_ordered_extent *entry)
  *
  * @inode:           Inode that this extent is for.
  * @file_offset:     Logical offset in file where the extent starts.
+ * @orig_offset:     Logical offset of the original extent (PREALLOC or NOCOW)
  * @num_bytes:       Logical length of extent in file.
  * @ram_bytes:       Full length of unencoded data.
  * @disk_bytenr:     Offset of extent on disk.
@@ -305,6 +308,7 @@ struct btrfs_ordered_extent *btrfs_alloc_ordered_extent(
 	 */
 	if (flags & ((1U << BTRFS_ORDERED_NOCOW) | (1U << BTRFS_ORDERED_PREALLOC)))
 		entry = alloc_ordered_extent(inode, file_offset,
+					     file_extent->orig_offset,
 					     file_extent->num_bytes,
 					     file_extent->num_bytes,
 					     file_extent->disk_bytenr + file_extent->offset,
@@ -313,6 +317,7 @@ struct btrfs_ordered_extent *btrfs_alloc_ordered_extent(
 					     file_extent->fscrypt_info);
 	else
 		entry = alloc_ordered_extent(inode, file_offset,
+					     file_extent->orig_offset,
 					     file_extent->num_bytes,
 					     file_extent->ram_bytes,
 					     file_extent->disk_bytenr,
@@ -1277,8 +1282,8 @@ struct btrfs_ordered_extent *btrfs_split_ordered_extent(
 	if (WARN_ON_ONCE(ordered->disk_num_bytes != ordered->num_bytes))
 		return ERR_PTR(-EINVAL);
 
-	new = alloc_ordered_extent(inode, file_offset, len, len, disk_bytenr, len, 0,
-				   flags, ordered->compress_type, ordered->fscrypt_info);
+	new = alloc_ordered_extent(inode, file_offset, ordered->orig_offset, len, len, disk_bytenr,
+				   len, 0, flags, ordered->compress_type, ordered->fscrypt_info);
 	if (IS_ERR(new))
 		return new;
 
@@ -1315,6 +1320,16 @@ struct btrfs_ordered_extent *btrfs_split_ordered_extent(
 	ordered->disk_num_bytes -= len;
 	ordered->ram_bytes -= len;
 
+	/*
+	 * ->orig_offset is the original offset of the original extent, which
+	 * for PREALLOC or NOCOW stays the same, but if we're a regular extent
+	 * that means this is a new extent and thus ->orig_offset must equal
+	 * ->file_offset.  This is only important for encryption as we only use
+	 * it for setting the offset for the bio encryption context.
+	 */
+	if (test_bit(BTRFS_ORDERED_REGULAR, &ordered->flags))
+		ordered->orig_offset = ordered->file_offset;
+
 	if (test_bit(BTRFS_ORDERED_IO_DONE, &ordered->flags)) {
 		ASSERT(ordered->bytes_left == 0);
 		new->bytes_left = 0;
diff --git a/fs/btrfs/ordered-data.h b/fs/btrfs/ordered-data.h
index 51c795865fe6..58cc1713eb4d 100644
--- a/fs/btrfs/ordered-data.h
+++ b/fs/btrfs/ordered-data.h
@@ -99,6 +99,12 @@ struct btrfs_ordered_extent {
 	/* logical offset in the file */
 	u64 file_offset;
 
+	/*
+	 * The original logical offset of the extent, this is for NOCOW and
+	 * PREALLOC extents, otherwise it'll be the same as file_offset.
+	 */
+	u64 orig_offset;
+
 	/*
 	 * These fields directly correspond to the same fields in
 	 * btrfs_file_extent_item.
@@ -188,6 +194,7 @@ struct btrfs_file_extent {
 	u64 num_bytes;
 	u64 ram_bytes;
 	u64 offset;
+	u64 orig_offset;
 	struct fscrypt_extent_info *fscrypt_info;
 	u8 compression;
 };
-- 
2.51.0


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

* [PATCH v6 29/43] btrfs: set the bio fscrypt context when applicable
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (27 preceding siblings ...)
  2026-02-06 18:23 ` [PATCH v6 28/43] btrfs: populate ordered_extent with the orig offset Daniel Vacek
@ 2026-02-06 18:23 ` Daniel Vacek
  2026-02-06 18:23 ` [PATCH v6 30/43] btrfs: add a bio argument to btrfs_csum_one_bio Daniel Vacek
                   ` (15 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:23 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

Now that we have the fscrypt_info plumbed through everywhere, add the
code to setup the bio encryption context from the extent context.

We use the per-extent fscrypt_extent_info for encryption/decryption.
We use the offset into the extent as the lblk for fscrypt.  So the start
of the extent has the lblk of 0, 4k into the extent has the lblk of 4k,
etc.  This is done to allow things like relocation to continue to work
properly.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/9ca883acc746b5aba980d1d19317168a7491aba6.1706116485.git.josef@toxicpanda.com/
 * The dio-related changes in inode.c were moved to direct-io.c
   as upstream refactored in the meantime.
 * Open-code orig_start to start - offset.
 * Get the inode from bbio instead of from page mapping
   as we no longer have the page in btrfs_bio_is_contig().
 * Directly use the file_offset passed to btrfs_bio_is_contig(), no need
   to compute it locally.  We even no longer have the page and offset.
---
 fs/btrfs/compression.c |  4 +++
 fs/btrfs/direct-io.c   | 10 ++++++
 fs/btrfs/extent_io.c   | 72 +++++++++++++++++++++++++++++++++++++++++-
 fs/btrfs/fscrypt.c     | 33 +++++++++++++++++++
 fs/btrfs/fscrypt.h     | 22 +++++++++++++
 5 files changed, 140 insertions(+), 1 deletion(-)

diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c
index 4c6298cf01b2..cd7b245bedb8 100644
--- a/fs/btrfs/compression.c
+++ b/fs/btrfs/compression.c
@@ -33,6 +33,7 @@
 #include "subpage.h"
 #include "messages.h"
 #include "super.h"
+#include "fscrypt.h"
 
 static struct bio_set btrfs_compressed_bioset;
 
@@ -391,6 +392,8 @@ void btrfs_submit_compressed_write(struct btrfs_ordered_extent *ordered,
 	cb->bbio.ordered = ordered;
 	btrfs_add_compressed_bio_folios(cb);
 
+	btrfs_set_bio_crypt_ctx_from_extent(&cb->bbio.bio, inode, ordered->fscrypt_info, 0);
+
 	btrfs_submit_bbio(&cb->bbio, 0);
 }
 
@@ -604,6 +607,7 @@ void btrfs_submit_compressed_read(struct btrfs_bio *bbio)
 	cb->orig_bbio = bbio;
 	cb->bbio.csum_search_commit_root = bbio->csum_search_commit_root;
 
+	btrfs_set_bio_crypt_ctx_from_extent(&cb->bbio.bio, inode, em->fscrypt_info, 0);
 	btrfs_free_extent_map(em);
 
 	cb->nr_folios = DIV_ROUND_UP(compressed_len, btrfs_min_folio_size(fs_info));
diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
index 2d89ac05b1b3..d20a5b99bdde 100644
--- a/fs/btrfs/direct-io.c
+++ b/fs/btrfs/direct-io.c
@@ -12,6 +12,7 @@
 #include "volumes.h"
 #include "bio.h"
 #include "ordered-data.h"
+#include "fscrypt.h"
 
 struct btrfs_dio_data {
 	ssize_t submitted;
@@ -728,6 +729,8 @@ static void btrfs_dio_submit_io(const struct iomap_iter *iter, struct bio *bio,
 	struct btrfs_dio_private *dip =
 		container_of(bbio, struct btrfs_dio_private, bbio);
 	struct btrfs_dio_data *dio_data = iter->private;
+	struct fscrypt_extent_info *fscrypt_info = NULL;
+	u64 offset = 0;
 
 	btrfs_bio_init(bbio, BTRFS_I(iter->inode), file_offset,
 		       btrfs_dio_end_io, bio->bi_private);
@@ -747,6 +750,9 @@ static void btrfs_dio_submit_io(const struct iomap_iter *iter, struct bio *bio,
 	if (iter->flags & IOMAP_WRITE) {
 		int ret;
 
+		offset = file_offset - dio_data->ordered->orig_offset;
+		fscrypt_info = dio_data->ordered->fscrypt_info;
+
 		ret = btrfs_extract_ordered_extent(bbio, dio_data->ordered);
 		if (ret) {
 			btrfs_finish_ordered_extent(dio_data->ordered, NULL,
@@ -756,8 +762,12 @@ static void btrfs_dio_submit_io(const struct iomap_iter *iter, struct bio *bio,
 			iomap_dio_bio_end_io(bio);
 			return;
 		}
+	} else {
+		fscrypt_info = dio_data->fscrypt_info;
+		offset = file_offset - dio_data->orig_start;
 	}
 
+	btrfs_set_bio_crypt_ctx_from_extent(&bbio->bio, bbio->inode, fscrypt_info, offset);
 	btrfs_submit_bbio(bbio, 0);
 }
 
diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c
index a440cc550ece..3273b7e3b4b0 100644
--- a/fs/btrfs/extent_io.c
+++ b/fs/btrfs/extent_io.c
@@ -34,6 +34,7 @@
 #include "dev-replace.h"
 #include "super.h"
 #include "transaction.h"
+#include "fscrypt.h"
 
 static struct kmem_cache *extent_buffer_cache;
 
@@ -149,6 +150,10 @@ struct btrfs_bio_ctrl {
 	 * inside the same inode.
 	 */
 	u64 last_em_start;
+
+	/* This is set for reads and we have encryption. */
+	struct fscrypt_extent_info *fscrypt_info;
+	u64 orig_start;
 };
 
 /*
@@ -711,9 +716,28 @@ static int alloc_eb_folio_array(struct extent_buffer *eb, bool nofail)
 static bool btrfs_bio_is_contig(struct btrfs_bio_ctrl *bio_ctrl,
 				u64 disk_bytenr, loff_t file_offset)
 {
-	struct bio *bio = &bio_ctrl->bbio->bio;
+	struct btrfs_bio *bbio = bio_ctrl->bbio;
+	struct bio *bio = &bbio->bio;
+	struct inode *inode = &bbio->inode->vfs_inode;
 	const sector_t sector = disk_bytenr >> SECTOR_SHIFT;
 
+	if (IS_ENCRYPTED(inode)) {
+		u64 offset = 0;
+		struct fscrypt_extent_info *fscrypt_info = NULL;
+
+		/* bio_ctrl->fscrypt_info is only set in the READ case. */
+		if (bio_ctrl->fscrypt_info) {
+			offset = file_offset - bio_ctrl->orig_start;
+			fscrypt_info = bio_ctrl->fscrypt_info;
+		} else if (bbio->ordered) {
+			fscrypt_info = bbio->ordered->fscrypt_info;
+			offset = file_offset - bbio->ordered->orig_offset;
+		}
+
+		if (!btrfs_mergeable_encrypted_bio(bio, inode, fscrypt_info, offset))
+			return false;
+	}
+
 	if (bio_ctrl->compress_type != BTRFS_COMPRESS_NONE) {
 		/*
 		 * For compression, all IO should have its logical bytenr set
@@ -736,6 +760,8 @@ static void alloc_new_bio(struct btrfs_inode *inode,
 {
 	struct btrfs_fs_info *fs_info = inode->root->fs_info;
 	struct btrfs_bio *bbio;
+	struct fscrypt_extent_info *fscrypt_info = NULL;
+	u64 offset = 0;
 
 	bbio = btrfs_bio_alloc(BIO_MAX_VECS, bio_ctrl->opf, inode,
 			       file_offset, bio_ctrl->end_io_func, NULL);
@@ -755,6 +781,8 @@ static void alloc_new_bio(struct btrfs_inode *inode,
 					ordered->file_offset +
 					ordered->disk_num_bytes - file_offset);
 			bbio->ordered = ordered;
+			fscrypt_info = ordered->fscrypt_info;
+			offset = file_offset - ordered->orig_offset;
 		}
 
 		/*
@@ -765,7 +793,12 @@ static void alloc_new_bio(struct btrfs_inode *inode,
 		 */
 		bio_set_dev(&bbio->bio, fs_info->fs_devices->latest_dev->bdev);
 		wbc_init_bio(bio_ctrl->wbc, &bbio->bio);
+	} else {
+		fscrypt_info = bio_ctrl->fscrypt_info;
+		offset = file_offset - bio_ctrl->orig_start;
 	}
+
+	btrfs_set_bio_crypt_ctx_from_extent(&bbio->bio, inode, fscrypt_info, offset);
 }
 
 /*
@@ -811,6 +844,19 @@ static void submit_extent_folio(struct btrfs_bio_ctrl *bio_ctrl,
 			len = bio_ctrl->len_to_oe_boundary;
 		}
 
+		/*
+		 * Encryption has to allocate bounce buffers to encrypt the bio,
+		 * and we need to make sure that it doesn't split the bio so we
+		 * retain all of our special info in the btrfs_bio, so submit
+		 * any bio that gets up to BIO_MAX_VECS worth of segments.
+		 */
+		if (IS_ENCRYPTED(&inode->vfs_inode) &&
+		    bio_data_dir(&bio_ctrl->bbio->bio) == WRITE &&
+		    bio_segments(&bio_ctrl->bbio->bio) == BIO_MAX_VECS) {
+			submit_one_bio(bio_ctrl);
+			continue;
+		}
+
 		if (!bio_add_folio(&bio_ctrl->bbio->bio, folio, len, pg_offset)) {
 			/* bio full: move on to a new one */
 			submit_one_bio(bio_ctrl);
@@ -1031,6 +1077,8 @@ static int btrfs_do_readpage(struct folio *folio, struct extent_map **em_cached,
 		u64 block_start;
 		u64 em_gen;
 
+		bio_ctrl->fscrypt_info = NULL;
+
 		ASSERT(IS_ALIGNED(cur, fs_info->sectorsize));
 		if (cur >= last_byte) {
 			folio_zero_range(folio, pg_offset, end - cur + 1);
@@ -1120,6 +1168,21 @@ static int btrfs_do_readpage(struct folio *folio, struct extent_map **em_cached,
 
 		bio_ctrl->last_em_start = em->start;
 
+		/*
+		 * We use the extent offset for the IV when decrypting the page,
+		 * so we have to set the extent_offset based on the orig_start
+		 * for this extent.  Also save the fscrypt_info so the bio ctx
+		 * can be set properly.  If this inode isn't encrypted this
+		 * won't do anything.
+		 *
+		 * If we're compressed we'll handle all of this in
+		 * btrfs_submit_compressed_read.
+		 */
+		if (compress_type == BTRFS_COMPRESS_NONE) {
+			bio_ctrl->orig_start = em->start - em->offset;
+			bio_ctrl->fscrypt_info = fscrypt_get_extent_info(em->fscrypt_info);
+		}
+
 		em_gen = em->generation;
 		btrfs_free_extent_map(em);
 		em = NULL;
@@ -1128,11 +1191,17 @@ static int btrfs_do_readpage(struct folio *folio, struct extent_map **em_cached,
 		if (block_start == EXTENT_MAP_HOLE) {
 			folio_zero_range(folio, pg_offset, blocksize);
 			end_folio_read(folio, true, cur, blocksize);
+
+			/* This shouldn't be set, but clear it just in case. */
+			fscrypt_put_extent_info(bio_ctrl->fscrypt_info);
 			continue;
 		}
 		/* the get_extent function already copied into the folio */
 		if (block_start == EXTENT_MAP_INLINE) {
 			end_folio_read(folio, true, cur, blocksize);
+
+			/* This shouldn't be set, but clear it just in case. */
+			fscrypt_put_extent_info(bio_ctrl->fscrypt_info);
 			continue;
 		}
 
@@ -1145,6 +1214,7 @@ static int btrfs_do_readpage(struct folio *folio, struct extent_map **em_cached,
 			submit_one_bio(bio_ctrl);
 		submit_extent_folio(bio_ctrl, disk_bytenr, folio, blocksize,
 				    pg_offset, em_gen);
+		fscrypt_put_extent_info(bio_ctrl->fscrypt_info);
 	}
 	return 0;
 }
diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
index 26060f3e50de..f4d6979a581b 100644
--- a/fs/btrfs/fscrypt.c
+++ b/fs/btrfs/fscrypt.c
@@ -295,6 +295,39 @@ ssize_t btrfs_fscrypt_context_for_new_extent(struct btrfs_inode *inode,
 	return ret;
 }
 
+void btrfs_set_bio_crypt_ctx_from_extent(struct bio *bio,
+					 struct btrfs_inode *inode,
+					 struct fscrypt_extent_info *fi,
+					 u64 logical_offset)
+{
+	if (!fi)
+		return;
+
+	/*
+	 * fscrypt uses bytes >> s_blocksize_bits for the block numbers, so we
+	 * have to adjust everything based on our sectorsize so that the DUN
+	 * calculations are correct.
+	 */
+	logical_offset = div64_u64(logical_offset, inode->root->fs_info->sectorsize);
+	fscrypt_set_bio_crypt_ctx_from_extent(bio, fi, logical_offset, GFP_NOFS);
+}
+
+bool btrfs_mergeable_encrypted_bio(struct bio *bio, struct inode *inode,
+				   struct fscrypt_extent_info *fi,
+				   u64 logical_offset)
+{
+	if (!fi)
+		return true;
+
+	/*
+	 * fscrypt uses bytes >> s_blocksize_bits for the block numbers, so we
+	 * have to adjust everything based on our sectorsize so that the DUN
+	 * calculations are correct.
+	 */
+	logical_offset = div64_u64(logical_offset, BTRFS_I(inode)->root->fs_info->sectorsize);
+	return fscrypt_mergeable_extent_bio(bio, fi, logical_offset);
+}
+
 const struct fscrypt_operations btrfs_fscrypt_ops = {
 	.inode_info_offs = (int)offsetof(struct btrfs_inode, i_crypt_info) -
 			   (int)offsetof(struct btrfs_inode, vfs_inode),
diff --git a/fs/btrfs/fscrypt.h b/fs/btrfs/fscrypt.h
index 68eab4606935..1a2a4ffee383 100644
--- a/fs/btrfs/fscrypt.h
+++ b/fs/btrfs/fscrypt.h
@@ -24,6 +24,13 @@ void btrfs_fscrypt_save_extent_info(struct btrfs_path *path, u8 *ctx, unsigned l
 ssize_t btrfs_fscrypt_context_for_new_extent(struct btrfs_inode *inode,
 					     struct fscrypt_extent_info *info,
 					     u8 *ctx);
+void btrfs_set_bio_crypt_ctx_from_extent(struct bio *bio,
+					 struct btrfs_inode *inode,
+					 struct fscrypt_extent_info *fi,
+					 u64 logical_offset);
+bool btrfs_mergeable_encrypted_bio(struct bio *bio, struct inode *inode,
+				   struct fscrypt_extent_info *fi,
+				   u64 logical_offset);
 
 #else
 static inline void btrfs_fscrypt_save_extent_info(struct btrfs_path *path,
@@ -63,6 +70,21 @@ static inline ssize_t btrfs_fscrypt_context_for_new_extent(struct btrfs_inode *i
 	return -EINVAL;
 }
 
+static inline void btrfs_set_bio_crypt_ctx_from_extent(struct bio *bio,
+						       struct btrfs_inode *inode,
+						       struct fscrypt_extent_info *fi,
+						       u64 logical_offset)
+{
+}
+
+static inline bool btrfs_mergeable_encrypted_bio(struct bio *bio,
+						 struct inode *inode,
+						 struct fscrypt_extent_info *fi,
+						 u64 logical_offset)
+{
+	return true;
+}
+
 #endif /* CONFIG_FS_ENCRYPTION */
 
 extern const struct fscrypt_operations btrfs_fscrypt_ops;
-- 
2.51.0


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

* [PATCH v6 30/43] btrfs: add a bio argument to btrfs_csum_one_bio
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (28 preceding siblings ...)
  2026-02-06 18:23 ` [PATCH v6 29/43] btrfs: set the bio fscrypt context when applicable Daniel Vacek
@ 2026-02-06 18:23 ` Daniel Vacek
  2026-02-06 18:23 ` [PATCH v6 31/43] btrfs: limit encrypted writes to 256 segments Daniel Vacek
                   ` (14 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:23 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

We only ever needed the bbio in btrfs_csum_one_bio, since that has the
bio embedded in it.  However with encryption we'll have a different bio
with the encrypted data in it, and the original bbio.  Update
btrfs_csum_one_bio to take the bio we're going to csum as an argument,
which will allow us to csum the encrypted bio and stuff the csums into
the corresponding bbio to be used later when the IO completes.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/595a2e46dae3d18bceb5dfa60ac3ae23760fc800.1706116485.git.josef@toxicpanda.com/
 * Adapted for async csums.  For writes we have to stick with sync
   csums.  Otherwise fscrypt would have to hand us the bounced bio
   instead of releasing it.  Though that is not implemented yet.
---
 fs/btrfs/bio.c       |  4 ++--
 fs/btrfs/bio.h       |  1 +
 fs/btrfs/file-item.c | 13 ++++++-------
 fs/btrfs/file-item.h |  2 +-
 4 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c
index 0a69e09bfe28..487dd9267fd7 100644
--- a/fs/btrfs/bio.c
+++ b/fs/btrfs/bio.c
@@ -592,9 +592,9 @@ static int btrfs_bio_csum(struct btrfs_bio *bbio)
 	if (bbio->bio.bi_opf & REQ_META)
 		return btree_csum_one_bio(bbio);
 #ifdef CONFIG_BTRFS_EXPERIMENTAL
-	return btrfs_csum_one_bio(bbio, true);
+	return btrfs_csum_one_bio(bbio, &bbio->bio, true);
 #else
-	return btrfs_csum_one_bio(bbio, false);
+	return btrfs_csum_one_bio(bbio, &bbio->bio, false);
 #endif
 }
 
diff --git a/fs/btrfs/bio.h b/fs/btrfs/bio.h
index 303ed6c7103d..43f7544029ac 100644
--- a/fs/btrfs/bio.h
+++ b/fs/btrfs/bio.h
@@ -59,6 +59,7 @@ struct btrfs_bio {
 			struct btrfs_ordered_sum *sums;
 			struct work_struct csum_work;
 			struct completion csum_done;
+			struct bio *csum_bio;
 			struct bvec_iter csum_saved_iter;
 			u64 orig_physical;
 			u64 orig_logical;
diff --git a/fs/btrfs/file-item.c b/fs/btrfs/file-item.c
index 08dc78295707..ef0b6faf3de0 100644
--- a/fs/btrfs/file-item.c
+++ b/fs/btrfs/file-item.c
@@ -764,11 +764,10 @@ int btrfs_lookup_csums_bitmap(struct btrfs_root *root, struct btrfs_path *path,
 	return ret;
 }
 
-static void csum_one_bio(struct btrfs_bio *bbio, struct bvec_iter *src)
+static void csum_one_bio(struct btrfs_bio *bbio, struct bio *bio, struct bvec_iter *src)
 {
 	struct btrfs_inode *inode = bbio->inode;
 	struct btrfs_fs_info *fs_info = inode->root->fs_info;
-	struct bio *bio = &bbio->bio;
 	struct btrfs_ordered_sum *sums = bbio->sums;
 	struct bvec_iter iter = *src;
 	phys_addr_t paddr;
@@ -796,19 +795,18 @@ static void csum_one_bio_work(struct work_struct *work)
 
 	ASSERT(btrfs_op(&bbio->bio) == BTRFS_MAP_WRITE);
 	ASSERT(bbio->async_csum == true);
-	csum_one_bio(bbio, &bbio->csum_saved_iter);
+	csum_one_bio(bbio, bbio->csum_bio, &bbio->csum_saved_iter);
 	complete(&bbio->csum_done);
 }
 
 /*
  * Calculate checksums of the data contained inside a bio.
  */
-int btrfs_csum_one_bio(struct btrfs_bio *bbio, bool async)
+int btrfs_csum_one_bio(struct btrfs_bio *bbio, struct bio *bio, bool async)
 {
 	struct btrfs_ordered_extent *ordered = bbio->ordered;
 	struct btrfs_inode *inode = bbio->inode;
 	struct btrfs_fs_info *fs_info = inode->root->fs_info;
-	struct bio *bio = &bbio->bio;
 	struct btrfs_ordered_sum *sums;
 	unsigned nofs_flag;
 
@@ -827,12 +825,13 @@ int btrfs_csum_one_bio(struct btrfs_bio *bbio, bool async)
 	btrfs_add_ordered_sum(ordered, sums);
 
 	if (!async) {
-		csum_one_bio(bbio, &bbio->bio.bi_iter);
+		csum_one_bio(bbio, bio, &bio->bi_iter);
 		return 0;
 	}
 	init_completion(&bbio->csum_done);
 	bbio->async_csum = true;
-	bbio->csum_saved_iter = bbio->bio.bi_iter;
+	bbio->csum_bio = bio;
+	bbio->csum_saved_iter = bio->bi_iter;
 	INIT_WORK(&bbio->csum_work, csum_one_bio_work);
 	schedule_work(&bbio->csum_work);
 	return 0;
diff --git a/fs/btrfs/file-item.h b/fs/btrfs/file-item.h
index 5645c5e3abdb..d16fd2144552 100644
--- a/fs/btrfs/file-item.h
+++ b/fs/btrfs/file-item.h
@@ -64,7 +64,7 @@ int btrfs_lookup_file_extent(struct btrfs_trans_handle *trans,
 int btrfs_csum_file_blocks(struct btrfs_trans_handle *trans,
 			   struct btrfs_root *root,
 			   struct btrfs_ordered_sum *sums);
-int btrfs_csum_one_bio(struct btrfs_bio *bbio, bool async);
+int btrfs_csum_one_bio(struct btrfs_bio *bbio, struct bio *bio, bool async);
 int btrfs_alloc_dummy_sum(struct btrfs_bio *bbio);
 int btrfs_lookup_csums_range(struct btrfs_root *root, u64 start, u64 end,
 			     struct list_head *list, int search_commit,
-- 
2.51.0


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

* [PATCH v6 31/43] btrfs: limit encrypted writes to 256 segments
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (29 preceding siblings ...)
  2026-02-06 18:23 ` [PATCH v6 30/43] btrfs: add a bio argument to btrfs_csum_one_bio Daniel Vacek
@ 2026-02-06 18:23 ` Daniel Vacek
  2026-02-06 18:23 ` [PATCH v6 32/43] btrfs: implement process_bio cb for fscrypt Daniel Vacek
                   ` (13 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:23 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

For the fallback encrypted writes it allocates a bounce buffer to
encrypt, and if the bio is larger than 256 segments it splits the bio we
send down.  This wreaks havoc on us because we need our actual original
bio to be passed into the process_cb callback in order to get at our
ordered extent.  With the split we'll get some cloned bio that has none
of our information.  Handle this by returning the length we need to be
truncated to and then splitting ourselves, which will handle giving us
the correct btrfs_bio that we need.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/1fc07b885453495bccea5a37e13d7d26333bd2af.1706116485.git.josef@toxicpanda.com/
 * No changes since.
---
 fs/btrfs/bio.c     | 29 ++++++++++++++++++++++++++++-
 fs/btrfs/fscrypt.c | 24 ++++++++++++++++++++++++
 fs/btrfs/fscrypt.h |  6 ++++++
 3 files changed, 58 insertions(+), 1 deletion(-)

diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c
index 487dd9267fd7..1ae81997fb2d 100644
--- a/fs/btrfs/bio.c
+++ b/fs/btrfs/bio.c
@@ -14,6 +14,7 @@
 #include "zoned.h"
 #include "file-item.h"
 #include "raid-stripe-tree.h"
+#include "fscrypt.h"
 
 static struct bio_set btrfs_bioset;
 static struct bio_set btrfs_clone_bioset;
@@ -751,6 +752,7 @@ static bool btrfs_submit_chunk(struct btrfs_bio *bbio, int mirror_num)
 	u64 logical = bio->bi_iter.bi_sector << SECTOR_SHIFT;
 	u64 length = bio->bi_iter.bi_size;
 	u64 map_length = length;
+	u64 max_bio_len = length;
 	struct btrfs_io_context *bioc = NULL;
 	struct btrfs_io_stripe smap;
 	blk_status_t status;
@@ -762,6 +764,31 @@ static bool btrfs_submit_chunk(struct btrfs_bio *bbio, int mirror_num)
 		smap.rst_search_commit_root = false;
 
 	btrfs_bio_counter_inc_blocked(fs_info);
+
+	/*
+	 * The blk-crypto-fallback limits bio sizes to 256 segments, because it
+	 * has no way of controlling the pages it gets for the bounce bio it
+	 * submits.
+	 *
+	 * If we don't pre-split our bio blk-crypto-fallback will do it for us,
+	 * and then call into our checksum callback with a random cloned bio
+	 * that isn't a btrfs_bio.
+	 *
+	 * To account for this we must truncate the bio ourselves, so we need to
+	 * get our length at 256 segments and return that.  We must do this
+	 * before btrfs_map_block because the RAID5/6 code relies on having
+	 * properly stripe aligned things, so we return the truncated length
+	 * here so that btrfs_map_block() does the correct thing.  Further down
+	 * we actually truncate map_length to map_bio_len because map_length
+	 * will be set to whatever the mapping length is for the underlying
+	 * geometry.  This will work properly with RAID5/6 as it will have
+	 * already setup everything for the expected length, and everything else
+	 * can handle with a truncated map_length.
+	 */
+	if (bio_has_crypt_ctx(bio))
+		max_bio_len = btrfs_fscrypt_bio_length(bio, map_length);
+
+	map_length = max_bio_len;
 	ret = btrfs_map_block(fs_info, btrfs_op(bio), logical, &map_length,
 			      &bioc, &smap, &mirror_num);
 	if (ret) {
@@ -780,7 +807,7 @@ static bool btrfs_submit_chunk(struct btrfs_bio *bbio, int mirror_num)
 
 	bbio->can_use_append = btrfs_use_zone_append(bbio);
 
-	map_length = min(map_length, length);
+	map_length = min(map_length, max_bio_len);
 	if (bbio->can_use_append)
 		map_length = btrfs_append_map_length(bbio, map_length);
 
diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
index f4d6979a581b..b6350b043994 100644
--- a/fs/btrfs/fscrypt.c
+++ b/fs/btrfs/fscrypt.c
@@ -328,6 +328,30 @@ bool btrfs_mergeable_encrypted_bio(struct bio *bio, struct inode *inode,
 	return fscrypt_mergeable_extent_bio(bio, fi, logical_offset);
 }
 
+/*
+ * The block crypto stuff allocates bounce buffers for encryption, so splits at
+ * BIO_MAX_VECS worth of segments.  If we are larger than that number of
+ * segments then we need to limit the size to the size that BIO_MAX_VECS covers.
+ */
+int btrfs_fscrypt_bio_length(struct bio *bio, u64 map_length)
+{
+	unsigned int i = 0;
+	struct bio_vec bv;
+	struct bvec_iter iter;
+	u64 segments_length = 0;
+
+	if (bio_op(bio) != REQ_OP_WRITE)
+		return map_length;
+
+	bio_for_each_segment(bv, bio, iter) {
+		segments_length += bv.bv_len;
+		if (++i == BIO_MAX_VECS)
+			return segments_length;
+	}
+
+	return map_length;
+}
+
 const struct fscrypt_operations btrfs_fscrypt_ops = {
 	.inode_info_offs = (int)offsetof(struct btrfs_inode, i_crypt_info) -
 			   (int)offsetof(struct btrfs_inode, vfs_inode),
diff --git a/fs/btrfs/fscrypt.h b/fs/btrfs/fscrypt.h
index 1a2a4ffee383..347b34f45715 100644
--- a/fs/btrfs/fscrypt.h
+++ b/fs/btrfs/fscrypt.h
@@ -31,6 +31,7 @@ void btrfs_set_bio_crypt_ctx_from_extent(struct bio *bio,
 bool btrfs_mergeable_encrypted_bio(struct bio *bio, struct inode *inode,
 				   struct fscrypt_extent_info *fi,
 				   u64 logical_offset);
+int btrfs_fscrypt_bio_length(struct bio *bio, u64 map_length);
 
 #else
 static inline void btrfs_fscrypt_save_extent_info(struct btrfs_path *path,
@@ -85,6 +86,11 @@ static inline bool btrfs_mergeable_encrypted_bio(struct bio *bio,
 	return true;
 }
 
+static inline u64 btrfs_fscrypt_bio_length(struct bio *bio, u64 map_length)
+{
+	return map_length;
+}
+
 #endif /* CONFIG_FS_ENCRYPTION */
 
 extern const struct fscrypt_operations btrfs_fscrypt_ops;
-- 
2.51.0


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

* [PATCH v6 32/43] btrfs: implement process_bio cb for fscrypt
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (30 preceding siblings ...)
  2026-02-06 18:23 ` [PATCH v6 31/43] btrfs: limit encrypted writes to 256 segments Daniel Vacek
@ 2026-02-06 18:23 ` Daniel Vacek
  2026-02-08 15:10   ` Chris Mason
  2026-02-06 18:23 ` [PATCH v6 33/43] btrfs: implement read repair for encryption Daniel Vacek
                   ` (12 subsequent siblings)
  44 siblings, 1 reply; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:23 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

We are going to be checksumming the encrypted data, so we have to
implement the ->process_bio fscrypt callback.  This will provide us with
the original bio and the encrypted bio to do work on.  For WRITE's this
will happen after the encrypted bio has been encrypted.  For READ's this
will happen after the read has completed and before the decryption step
is done.

For write's this is straightforward, we can just pass in the encrypted
bio to btrfs_csum_one_bio and then the csums will be added to the bbio
as normal.

For read's this is relatively straightforward, but requires some care.
We assume (because that's how it works currently) that the encrypted bio
match the original bio, this is important because we save the iter of
the bio before we submit.  If this changes in the future we'll need a
hook to give us the bi_iter of the decryption bio before it's submitted.
We check the csums before decryption.  If it doesn't match we simply
error out and we let the normal path handle the repair work.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/ca32684b01ff8c252be515509137e0a4a0e5db7a.1706116485.git.josef@toxicpanda.com/
 * Adapt to btrfs_data_csum_ok() changes for bs > ps.  Mostly follow
   what was done in 052fd7a5cace ("btrfs: make read verification
   handle bs > ps cases without large folios").
 * Rename bbio::csum_done to csum_ok due to name collision.
   With upstream, member name csum_done was used for async csums.
---
 fs/btrfs/bio.c       | 38 +++++++++++++++++++++++++++++++++++++-
 fs/btrfs/bio.h       |  3 +++
 fs/btrfs/file-item.c | 14 ++++++++++++--
 fs/btrfs/fscrypt.c   | 29 +++++++++++++++++++++++++++++
 4 files changed, 81 insertions(+), 3 deletions(-)

diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c
index 1ae81997fb2d..69dc32cb4ed6 100644
--- a/fs/btrfs/bio.c
+++ b/fs/btrfs/bio.c
@@ -300,6 +300,34 @@ static struct btrfs_failed_bio *repair_one_sector(struct btrfs_bio *failed_bbio,
 	return fbio;
 }
 
+blk_status_t btrfs_check_encrypted_read_bio(struct btrfs_bio *bbio, struct bio *enc_bio)
+{
+	struct btrfs_inode *inode = bbio->inode;
+	struct btrfs_fs_info *fs_info = inode->root->fs_info;
+	struct bvec_iter iter = bbio->saved_iter;
+	struct btrfs_device *dev = bbio->bio.bi_private;
+	const u32 step = min(fs_info->sectorsize, PAGE_SIZE);
+	const u32 nr_steps = iter.bi_size / step;
+	phys_addr_t paddrs[BTRFS_MAX_BLOCKSIZE / PAGE_SIZE];
+	phys_addr_t paddr;
+	unsigned int slot = 0;
+
+	/*
+	 * We have to use a copy of iter in case there's an error,
+	 * btrfs_check_read_bio will handle submitting the repair bios.
+	 */
+	btrfs_bio_for_each_block(paddr, enc_bio, &iter, step) {
+		ASSERT(slot < nr_steps);
+		paddrs[slot] = paddr;
+		slot++;
+	}
+	if (!btrfs_data_csum_ok(bbio, dev, 0, paddrs))
+			return BLK_STS_IOERR;
+
+	bbio->csum_ok = true;
+	return BLK_STS_OK;
+}
+
 static void btrfs_check_read_bio(struct btrfs_bio *bbio, struct btrfs_device *dev)
 {
 	struct btrfs_inode *inode = bbio->inode;
@@ -329,6 +357,10 @@ static void btrfs_check_read_bio(struct btrfs_bio *bbio, struct btrfs_device *de
 	/* Clear the I/O error. A failed repair will reset it. */
 	bbio->bio.bi_status = BLK_STS_OK;
 
+	/* This was an encrypted bio and we've already done the csum check. */
+	if (status == BLK_STS_OK && bbio->csum_ok)
+		goto out;
+
 	btrfs_bio_for_each_block(paddr, &bbio->bio, iter, step) {
 		paddrs[(offset / step) % nr_steps] = paddr;
 		offset += step;
@@ -340,6 +372,7 @@ static void btrfs_check_read_bio(struct btrfs_bio *bbio, struct btrfs_device *de
 							 paddrs, fbio);
 		}
 	}
+out:
 	if (bbio->csum != bbio->csum_inline)
 		kvfree(bbio->csum);
 
@@ -851,10 +884,13 @@ static bool btrfs_submit_chunk(struct btrfs_bio *bbio, int mirror_num)
 		/*
 		 * Csum items for reloc roots have already been cloned at this
 		 * point, so they are handled as part of the no-checksum case.
+		 *
+		 * Encrypted inodes are csum'ed via the ->process_bio callback.
 		 */
 		if (!(inode->flags & BTRFS_INODE_NODATASUM) &&
 		    !test_bit(BTRFS_FS_STATE_NO_DATA_CSUMS, &fs_info->fs_state) &&
-		    !btrfs_is_data_reloc_root(inode->root) && !bbio->is_remap) {
+		    !btrfs_is_data_reloc_root(inode->root) && !bbio->is_remap &&
+		    !IS_ENCRYPTED(&inode->vfs_inode)) {
 			if (should_async_write(bbio) &&
 			    btrfs_wq_submit_bio(bbio, bioc, &smap, mirror_num))
 				goto done;
diff --git a/fs/btrfs/bio.h b/fs/btrfs/bio.h
index 43f7544029ac..456d32db9e9e 100644
--- a/fs/btrfs/bio.h
+++ b/fs/btrfs/bio.h
@@ -43,6 +43,7 @@ struct btrfs_bio {
 		struct {
 			u8 *csum;
 			u8 csum_inline[BTRFS_BIO_INLINE_CSUM_SIZE];
+			bool csum_ok;
 			struct bvec_iter saved_iter;
 		};
 
@@ -130,5 +131,7 @@ void btrfs_submit_repair_write(struct btrfs_bio *bbio, int mirror_num, bool dev_
 int btrfs_repair_io_failure(struct btrfs_fs_info *fs_info, u64 ino, u64 fileoff,
 			    u32 length, u64 logical, const phys_addr_t paddrs[],
 			    unsigned int step, int mirror_num);
+blk_status_t btrfs_check_encrypted_read_bio(struct btrfs_bio *bbio,
+					    struct bio *enc_bio);
 
 #endif
diff --git a/fs/btrfs/file-item.c b/fs/btrfs/file-item.c
index ef0b6faf3de0..cee57a2f241b 100644
--- a/fs/btrfs/file-item.c
+++ b/fs/btrfs/file-item.c
@@ -331,6 +331,14 @@ static int search_csum_tree(struct btrfs_fs_info *fs_info,
 	return ret;
 }
 
+static inline bool inode_skip_csum(struct btrfs_inode *inode)
+{
+	struct btrfs_fs_info *fs_info = inode->root->fs_info;
+
+	return (inode->flags & BTRFS_INODE_NODATASUM) ||
+		test_bit(BTRFS_FS_STATE_NO_DATA_CSUMS, &fs_info->fs_state);
+}
+
 /*
  * Lookup the checksum for the read bio in csum tree.
  *
@@ -350,8 +358,7 @@ int btrfs_lookup_bio_sums(struct btrfs_bio *bbio)
 	int ret = 0;
 	u32 bio_offset = 0;
 
-	if ((inode->flags & BTRFS_INODE_NODATASUM) ||
-	    test_bit(BTRFS_FS_STATE_NO_DATA_CSUMS, &fs_info->fs_state))
+	if (inode_skip_csum(inode))
 		return 0;
 
 	/*
@@ -810,6 +817,9 @@ int btrfs_csum_one_bio(struct btrfs_bio *bbio, struct bio *bio, bool async)
 	struct btrfs_ordered_sum *sums;
 	unsigned nofs_flag;
 
+	if (inode_skip_csum(inode))
+		return 0;
+
 	nofs_flag = memalloc_nofs_save();
 	sums = kvzalloc(btrfs_ordered_sum_size(fs_info, bio->bi_iter.bi_size),
 		       GFP_KERNEL);
diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
index b6350b043994..f74404bdd89e 100644
--- a/fs/btrfs/fscrypt.c
+++ b/fs/btrfs/fscrypt.c
@@ -16,6 +16,7 @@
 #include "transaction.h"
 #include "volumes.h"
 #include "xattr.h"
+#include "file-item.h"
 
 /*
  * From a given location in a leaf, read a name into a qstr (usually a
@@ -212,6 +213,33 @@ static struct block_device **btrfs_fscrypt_get_devices(struct super_block *sb,
 	return devs;
 }
 
+static blk_status_t btrfs_process_encrypted_bio(struct bio *orig_bio,
+						struct bio *enc_bio)
+{
+	struct btrfs_bio *bbio;
+
+	/*
+	 * If our bio is from the normal fs_bio_set then we know this is a
+	 * mirror split and we can skip it, we'll get the real bio on the last
+	 * mirror and we can process that one.
+	 */
+	if (orig_bio->bi_pool == &fs_bio_set)
+		return BLK_STS_OK;
+
+	bbio = btrfs_bio(orig_bio);
+
+	if (bio_op(orig_bio) == REQ_OP_READ) {
+		/*
+		 * We have ->saved_iter based on the orig_bio, so if the block
+		 * layer changes we need to notice this asap so we can update
+		 * our code to handle the new world order.
+		 */
+		ASSERT(orig_bio == enc_bio);
+		return btrfs_check_encrypted_read_bio(bbio, enc_bio);
+	}
+	return btrfs_csum_one_bio(bbio, enc_bio, false);
+}
+
 int btrfs_fscrypt_load_extent_info(struct btrfs_inode *inode,
 				   struct btrfs_path *path,
 				   struct btrfs_key *key,
@@ -360,4 +388,5 @@ const struct fscrypt_operations btrfs_fscrypt_ops = {
 	.set_context = btrfs_fscrypt_set_context,
 	.empty_dir = btrfs_fscrypt_empty_dir,
 	.get_devices = btrfs_fscrypt_get_devices,
+	.process_bio = btrfs_process_encrypted_bio,
 };
-- 
2.51.0


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

* [PATCH v6 33/43] btrfs: implement read repair for encryption
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (31 preceding siblings ...)
  2026-02-06 18:23 ` [PATCH v6 32/43] btrfs: implement process_bio cb for fscrypt Daniel Vacek
@ 2026-02-06 18:23 ` Daniel Vacek
  2026-02-08 15:08   ` Chris Mason
  2026-02-06 18:23 ` [PATCH v6 34/43] btrfs: add test_dummy_encryption support Daniel Vacek
                   ` (11 subsequent siblings)
  44 siblings, 1 reply; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:23 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

In order to do read repair we will allocate sectorsize bio's and read
them one at a time, repairing any sectors that don't match their csum.
In order to do this we re-submit the IO's after it's failed, and at this
point we still need the fscrypt_extent_info for these new bio's.

Add the fscrypt_extent_info to the read part of the union in the
btrfs_bio, and then pass this through all the places where we do reads.
Additionally add the orig_start, because we need to be able to put the
correct extent offset for the encryption context.

With these in place we can utilize the normal read repair path.  The
only exception is that the actual repair of the bad copies has to be
triggered from the ->process_bio callback, because this is the encrypted
data.  If we waited until the end_io we would have the decrypted data
and we don't want to write that to the disk.  This is the only change to
the normal read repair path, we trigger the fixup of the broken sectors
in ->process_bio, and then we skip that part if we successfully repair
the sector in ->process_bio once we get to the endio.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/310c0ebdc78613b6f379595e160206013f75b6dc.1706116485.git.josef@toxicpanda.com/
 * Fixed UAF bug with !ordered case doing bbio->end_io(bbio) >>>
   fscrypt_put_extent_info(bbio->fscrypt_info).
   - We can simply put the fscrypt_info first and then end the bio.
   - Also no need to clear the bbio->fscrypt_info pointer as bbio is
     just going to be freed.  That cleans up the code a bit.
 * Adapted to bs > ps changes.
 * Updated and re-wrap the comments.
 * Moved the dio-related changes from inode.c to direct-io.c
   as upstream refactored in the meantime.
---
 fs/btrfs/bio.c         | 75 +++++++++++++++++++++++++++++++++++++-----
 fs/btrfs/bio.h         | 10 +++++-
 fs/btrfs/compression.c |  2 ++
 fs/btrfs/direct-io.c   |  2 ++
 fs/btrfs/extent_io.c   |  2 ++
 5 files changed, 82 insertions(+), 9 deletions(-)

diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c
index 69dc32cb4ed6..a89f49dac0f2 100644
--- a/fs/btrfs/bio.c
+++ b/fs/btrfs/bio.c
@@ -97,6 +97,9 @@ static struct btrfs_bio *btrfs_split_bio(struct btrfs_fs_info *fs_info,
 		bbio->ordered = orig_bbio->ordered;
 		bbio->orig_logical = orig_bbio->orig_logical;
 		orig_bbio->orig_logical += map_length;
+	} else if (is_data_bbio(bbio)) {
+		bbio->fscrypt_info = fscrypt_get_extent_info(orig_bbio->fscrypt_info);
+		bbio->orig_start = orig_bbio->orig_start;
 	}
 
 	bbio->csum_search_commit_root = orig_bbio->csum_search_commit_root;
@@ -124,6 +127,8 @@ void btrfs_bio_end_io(struct btrfs_bio *bbio, blk_status_t status)
 		/* Free bio that was never submitted to the underlying device. */
 		if (bbio_has_ordered_extent(bbio))
 			btrfs_put_ordered_extent(bbio->ordered);
+		else if (is_data_bbio(bbio))
+			fscrypt_put_extent_info(bbio->fscrypt_info);
 		bio_put(&bbio->bio);
 
 		bbio = orig_bbio;
@@ -147,6 +152,8 @@ void btrfs_bio_end_io(struct btrfs_bio *bbio, blk_status_t status)
 			bbio->end_io(bbio);
 			btrfs_put_ordered_extent(ordered);
 		} else {
+			if (is_data_bbio(bbio))
+				fscrypt_put_extent_info(bbio->fscrypt_info);
 			bbio->end_io(bbio);
 		}
 	}
@@ -174,6 +181,23 @@ static void btrfs_repair_done(struct btrfs_failed_bio *fbio)
 	}
 }
 
+static void handle_repair(struct btrfs_bio *repair_bbio, phys_addr_t *paddrs)
+{
+	struct btrfs_failed_bio *fbio = repair_bbio->private;
+	struct btrfs_inode *inode = repair_bbio->inode;
+	struct btrfs_fs_info *fs_info = inode->root->fs_info;
+	const u32 step = min(fs_info->sectorsize, PAGE_SIZE);
+	const u64 logical = repair_bbio->saved_iter.bi_sector << SECTOR_SHIFT;
+	int mirror = repair_bbio->mirror_num;
+
+	do {
+		mirror = prev_repair_mirror(fbio, mirror);
+		btrfs_repair_io_failure(fs_info, btrfs_ino(inode),
+				  repair_bbio->file_offset, fs_info->sectorsize,
+				  logical, paddrs, step, mirror);
+	} while (mirror != fbio->bbio->mirror_num);
+}
+
 static void btrfs_end_repair_bio(struct btrfs_bio *repair_bbio,
 				 struct btrfs_device *dev)
 {
@@ -186,7 +210,6 @@ static void btrfs_end_repair_bio(struct btrfs_bio *repair_bbio,
 	 */
 	struct bvec_iter saved_iter = repair_bbio->saved_iter;
 	const u32 step = min(fs_info->sectorsize, PAGE_SIZE);
-	const u64 logical = repair_bbio->saved_iter.bi_sector << SECTOR_SHIFT;
 	const u32 nr_steps = repair_bbio->saved_iter.bi_size / step;
 	int mirror = repair_bbio->mirror_num;
 	phys_addr_t paddrs[BTRFS_MAX_BLOCKSIZE / PAGE_SIZE];
@@ -202,6 +225,13 @@ static void btrfs_end_repair_bio(struct btrfs_bio *repair_bbio,
 		slot++;
 	}
 
+	/*
+	 * If we got here from the encrypted path with ->csum_ok set then
+	 * we've already csumed and repaired this sector, we're all done.
+	 */
+	if (repair_bbio->csum_ok)
+		goto done;
+
 	if (repair_bbio->bio.bi_status ||
 	    !btrfs_data_csum_ok(repair_bbio, dev, 0, paddrs)) {
 		bio_reset(&repair_bbio->bio, NULL, REQ_OP_READ);
@@ -214,17 +244,17 @@ static void btrfs_end_repair_bio(struct btrfs_bio *repair_bbio,
 			goto done;
 		}
 
+		btrfs_set_bio_crypt_ctx_from_extent(&repair_bbio->bio,
+						    repair_bbio->inode,
+						    repair_bbio->fscrypt_info,
+						    repair_bbio->file_offset -
+						    repair_bbio->orig_start);
+
 		btrfs_submit_bbio(repair_bbio, mirror);
 		return;
 	}
 
-	do {
-		mirror = prev_repair_mirror(fbio, mirror);
-		btrfs_repair_io_failure(fs_info, btrfs_ino(inode),
-				  repair_bbio->file_offset, fs_info->sectorsize,
-				  logical, paddrs, step, mirror);
-	} while (mirror != fbio->bbio->mirror_num);
-
+	handle_repair(repair_bbio, paddrs);
 done:
 	btrfs_repair_done(fbio);
 	bio_put(&repair_bbio->bio);
@@ -293,6 +323,13 @@ static struct btrfs_failed_bio *repair_one_sector(struct btrfs_bio *failed_bbio,
 	repair_bbio = btrfs_bio(repair_bio);
 	btrfs_bio_init(repair_bbio, failed_bbio->inode, failed_bbio->file_offset + bio_offset,
 		       NULL, fbio);
+	repair_bbio->fscrypt_info = fscrypt_get_extent_info(failed_bbio->fscrypt_info);
+	repair_bbio->orig_start = failed_bbio->orig_start;
+
+	btrfs_set_bio_crypt_ctx_from_extent(repair_bio, repair_bbio->inode,
+					    failed_bbio->fscrypt_info,
+					    repair_bbio->file_offset -
+					    failed_bbio->orig_start);
 
 	mirror = next_repair_mirror(fbio, failed_bbio->mirror_num);
 	btrfs_debug(fs_info, "submitting repair read to mirror %d", mirror);
@@ -324,7 +361,29 @@ blk_status_t btrfs_check_encrypted_read_bio(struct btrfs_bio *bbio, struct bio *
 	if (!btrfs_data_csum_ok(bbio, dev, 0, paddrs))
 			return BLK_STS_IOERR;
 
+	/*
+	 * Read repair is slightly different for encrypted bio's.  This
+	 * callback is before we decrypt the bio in the block crypto layer,
+	 * we're not actually in the endio handler.
+	 *
+	 * We don't trigger the repair process here either, that is handled
+	 * in the actual endio path because we don't want to create another
+	 * pseudo endio path through this callback.  This is because when we
+	 * call btrfs_repair_done() we want to call the endio for the original
+	 * bbio. Short circuiting that for the encrypted case would be ugly.
+	 * We really want to the repair case to be handled generically.
+	 *
+	 * However for the actual repair part we need to use this page
+	 * pre-decrypted, which is why we call the btrfs_repair_io_failure()
+	 * code from this path.  The repair path is synchronous so we are
+	 * safe there.  Then we simply mark the repair bbio as completed so
+	 * the actual btrfs_end_repair_bio() code can skip the repair part.
+	 */
+	if (bbio->bio.bi_pool == &btrfs_repair_bioset)
+		handle_repair(bbio, paddrs);
 	bbio->csum_ok = true;
+	fscrypt_put_extent_info(bbio->fscrypt_info);
+	bbio->fscrypt_info = NULL;
 	return BLK_STS_OK;
 }
 
diff --git a/fs/btrfs/bio.h b/fs/btrfs/bio.h
index 456d32db9e9e..7a8ff4378cba 100644
--- a/fs/btrfs/bio.h
+++ b/fs/btrfs/bio.h
@@ -15,6 +15,7 @@
 struct btrfs_bio;
 struct btrfs_fs_info;
 struct btrfs_inode;
+struct fscrypt_extent_info;
 
 #define BTRFS_BIO_INLINE_CSUM_SIZE	64
 
@@ -38,13 +39,20 @@ struct btrfs_bio {
 	union {
 		/*
 		 * For data reads: checksumming and original I/O information.
-		 * (for internal use in the btrfs_submit_bbio() machinery only)
+		 * (for internal use in the btrfs_submit_bbio() machinery only).
+		 *
+		 * The fscrypt context is used for read repair, this is the
+		 * only thing not internal to btrfs_submit_bbio() machinery.
 		 */
 		struct {
 			u8 *csum;
 			u8 csum_inline[BTRFS_BIO_INLINE_CSUM_SIZE];
 			bool csum_ok;
 			struct bvec_iter saved_iter;
+
+			/* Used for read repair. */
+			struct fscrypt_extent_info *fscrypt_info;
+			u64 orig_start;
 		};
 
 		/*
diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c
index cd7b245bedb8..15bf03be3fd5 100644
--- a/fs/btrfs/compression.c
+++ b/fs/btrfs/compression.c
@@ -606,6 +606,8 @@ void btrfs_submit_compressed_read(struct btrfs_bio *bbio)
 	cb->compress_type = btrfs_extent_map_compression(em);
 	cb->orig_bbio = bbio;
 	cb->bbio.csum_search_commit_root = bbio->csum_search_commit_root;
+	cb->bbio.fscrypt_info = fscrypt_get_extent_info(em->fscrypt_info);
+	cb->bbio.orig_start = 0;
 
 	btrfs_set_bio_crypt_ctx_from_extent(&cb->bbio.bio, inode, em->fscrypt_info, 0);
 	btrfs_free_extent_map(em);
diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
index d20a5b99bdde..c59e13fce764 100644
--- a/fs/btrfs/direct-io.c
+++ b/fs/btrfs/direct-io.c
@@ -765,6 +765,8 @@ static void btrfs_dio_submit_io(const struct iomap_iter *iter, struct bio *bio,
 	} else {
 		fscrypt_info = dio_data->fscrypt_info;
 		offset = file_offset - dio_data->orig_start;
+		bbio->fscrypt_info = fscrypt_get_extent_info(fscrypt_info);
+		bbio->orig_start = dio_data->orig_start;
 	}
 
 	btrfs_set_bio_crypt_ctx_from_extent(&bbio->bio, bbio->inode, fscrypt_info, offset);
diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c
index 3273b7e3b4b0..094855b77768 100644
--- a/fs/btrfs/extent_io.c
+++ b/fs/btrfs/extent_io.c
@@ -796,6 +796,8 @@ static void alloc_new_bio(struct btrfs_inode *inode,
 	} else {
 		fscrypt_info = bio_ctrl->fscrypt_info;
 		offset = file_offset - bio_ctrl->orig_start;
+		bbio->fscrypt_info = fscrypt_get_extent_info(fscrypt_info);
+		bbio->orig_start = bio_ctrl->orig_start;
 	}
 
 	btrfs_set_bio_crypt_ctx_from_extent(&bbio->bio, inode, fscrypt_info, offset);
-- 
2.51.0


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

* [PATCH v6 34/43] btrfs: add test_dummy_encryption support
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (32 preceding siblings ...)
  2026-02-06 18:23 ` [PATCH v6 33/43] btrfs: implement read repair for encryption Daniel Vacek
@ 2026-02-06 18:23 ` Daniel Vacek
  2026-02-06 18:23 ` [PATCH v6 35/43] btrfs: make btrfs_ref_to_path handle encrypted filenames Daniel Vacek
                   ` (10 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:23 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

In order to enable more thorough testing of fscrypt enable the
test_dummy_encryption mount option.  This is used by fscrypt users to
easily enable fscrypt on the file system for testing without needing to
do the key setup and everything.

The only deviation from other file systems we make is we only support
the fsparam_flag version of this mount option, as it defaults to v2.  We
don't want to have to bother with rejecting v1 related mount options.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/77449ee5a882db2945429946c74ea7e796122328.1706116485.git.josef@toxicpanda.com/
 * No changes since.  Just re-wrapping.  We now accept longer lines.
---
 fs/btrfs/disk-io.c |  1 +
 fs/btrfs/fs.h      |  3 +++
 fs/btrfs/fscrypt.c |  6 +++++
 fs/btrfs/super.c   | 58 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 68 insertions(+)

diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
index 20c405a4789d..f47a20976b53 100644
--- a/fs/btrfs/disk-io.c
+++ b/fs/btrfs/disk-io.c
@@ -1224,6 +1224,7 @@ void btrfs_free_fs_info(struct btrfs_fs_info *fs_info)
 	btrfs_extent_buffer_leak_debug_check(fs_info);
 	kfree(fs_info->super_copy);
 	kfree(fs_info->super_for_commit);
+	fscrypt_free_dummy_policy(&fs_info->dummy_enc_policy);
 	kvfree(fs_info);
 }
 
diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h
index 5077b7eed4b8..13cfb6887d89 100644
--- a/fs/btrfs/fs.h
+++ b/fs/btrfs/fs.h
@@ -27,6 +27,7 @@
 #include <linux/sched.h>
 #include <linux/rbtree.h>
 #include <linux/xxhash.h>
+#include <linux/fscrypt.h>
 #include <uapi/linux/btrfs.h>
 #include <uapi/linux/btrfs_tree.h>
 #include "extent-io-tree.h"
@@ -263,6 +264,7 @@ enum {
 	BTRFS_MOUNT_IGNOREMETACSUMS		= (1ULL << 31),
 	BTRFS_MOUNT_IGNORESUPERFLAGS		= (1ULL << 32),
 	BTRFS_MOUNT_REF_TRACKER			= (1ULL << 33),
+	BTRFS_MOUNT_TEST_DUMMY_ENCRYPTION	= (1ULL << 34),
 };
 
 /* These mount options require a full read-only fs, no new transaction is allowed. */
@@ -951,6 +953,7 @@ struct btrfs_fs_info {
 	spinlock_t eb_leak_lock;
 	struct list_head allocated_ebs;
 #endif
+	struct fscrypt_dummy_policy dummy_enc_policy;
 };
 
 #define folio_to_inode(_folio)	(BTRFS_I(_Generic((_folio),			\
diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
index f74404bdd89e..d1a4cbb990d4 100644
--- a/fs/btrfs/fscrypt.c
+++ b/fs/btrfs/fscrypt.c
@@ -240,6 +240,11 @@ static blk_status_t btrfs_process_encrypted_bio(struct bio *orig_bio,
 	return btrfs_csum_one_bio(bbio, enc_bio, false);
 }
 
+static const union fscrypt_policy *btrfs_get_dummy_policy(struct super_block *sb)
+{
+	return btrfs_sb(sb)->dummy_enc_policy.policy;
+}
+
 int btrfs_fscrypt_load_extent_info(struct btrfs_inode *inode,
 				   struct btrfs_path *path,
 				   struct btrfs_key *key,
@@ -389,4 +394,5 @@ const struct fscrypt_operations btrfs_fscrypt_ops = {
 	.empty_dir = btrfs_fscrypt_empty_dir,
 	.get_devices = btrfs_fscrypt_get_devices,
 	.process_bio = btrfs_process_encrypted_bio,
+	.get_dummy_policy = btrfs_get_dummy_policy,
 };
diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
index 3914abec5b12..06788b27a870 100644
--- a/fs/btrfs/super.c
+++ b/fs/btrfs/super.c
@@ -87,6 +87,7 @@ struct btrfs_fs_context {
 	unsigned long compress_type:4;
 	int compress_level;
 	refcount_t refs;
+	struct fscrypt_dummy_policy dummy_enc_policy;
 };
 
 static void btrfs_emit_options(struct btrfs_fs_info *info,
@@ -125,6 +126,7 @@ enum {
 	Opt_treelog,
 	Opt_user_subvol_rm_allowed,
 	Opt_norecovery,
+	Opt_test_dummy_encryption,
 
 	/* Rescue options */
 	Opt_rescue,
@@ -259,6 +261,8 @@ static const struct fs_parameter_spec btrfs_fs_parameters[] = {
 	fsparam_enum("fragment", Opt_fragment, btrfs_parameter_fragment),
 	fsparam_flag("ref_tracker", Opt_ref_tracker),
 	fsparam_flag("ref_verify", Opt_ref_verify),
+	fsparam_flag("test_dummy_encryption", Opt_test_dummy_encryption),
+	fsparam_string("test_dummy_encryption", Opt_test_dummy_encryption),
 #endif
 	{}
 };
@@ -651,6 +655,23 @@ static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param)
 	case Opt_ref_tracker:
 		btrfs_set_opt(ctx->mount_opt, REF_TRACKER);
 		break;
+	case Opt_test_dummy_encryption:
+		int ret;
+
+		/*
+		 * We only support v2, so reject any v1 policies.
+		 */
+		if (param->type == fs_value_is_string && *param->string &&
+		    !strcmp(param->string, "v1")) {
+			btrfs_info(NULL, "v1 encryption isn't supported");
+			return -EINVAL;
+		}
+
+		btrfs_set_opt(ctx->mount_opt, TEST_DUMMY_ENCRYPTION);
+		ret = fscrypt_parse_test_dummy_encryption(param, &ctx->dummy_enc_policy);
+		if (ret)
+			return ret;
+		break;
 #endif
 	default:
 		btrfs_err(NULL, "unrecognized mount option '%s'", param->key);
@@ -986,6 +1007,9 @@ static int btrfs_fill_super(struct super_block *sb,
 		return ret;
 	}
 
+	if (fscrypt_is_dummy_policy_set(&fs_info->dummy_enc_policy))
+		btrfs_set_fs_incompat(fs_info, ENCRYPT);
+
 	btrfs_emit_options(fs_info, NULL);
 
 	inode = btrfs_iget(BTRFS_FIRST_FREE_OBJECTID, fs_info->fs_root);
@@ -1150,6 +1174,8 @@ static int btrfs_show_options(struct seq_file *seq, struct dentry *dentry)
 		seq_puts(seq, ",ref_verify");
 	if (btrfs_test_opt(info, REF_TRACKER))
 		seq_puts(seq, ",ref_tracker");
+	if (btrfs_test_opt(info, TEST_DUMMY_ENCRYPTION))
+		fscrypt_show_test_dummy_encryption(seq, ',', dentry->d_sb);
 	seq_printf(seq, ",subvolid=%llu", btrfs_root_id(BTRFS_I(d_inode(dentry))->root));
 	subvol_name = btrfs_get_subvol_name_from_objectid(info,
 			btrfs_root_id(BTRFS_I(d_inode(dentry))->root));
@@ -1415,6 +1441,18 @@ static void btrfs_ctx_to_info(struct btrfs_fs_info *fs_info, struct btrfs_fs_con
 	fs_info->mount_opt = ctx->mount_opt;
 	fs_info->compress_type = ctx->compress_type;
 	fs_info->compress_level = ctx->compress_level;
+
+	/*
+	 * If there's nothing set, or if the fs_info already has one set, don't
+	 * do anything.  If the fs_info is set we'll free the dummy one when we
+	 * free the ctx.
+	 */
+	if (!fscrypt_is_dummy_policy_set(&ctx->dummy_enc_policy) ||
+	    fscrypt_is_dummy_policy_set(&fs_info->dummy_enc_policy))
+		return;
+
+	fs_info->dummy_enc_policy = ctx->dummy_enc_policy;
+	memset(&ctx->dummy_enc_policy, 0, sizeof(ctx->dummy_enc_policy));
 }
 
 static void btrfs_info_to_ctx(struct btrfs_fs_info *fs_info, struct btrfs_fs_context *ctx)
@@ -1468,6 +1506,7 @@ static void btrfs_emit_options(struct btrfs_fs_info *info,
 	btrfs_info_if_set(info, old, IGNOREDATACSUMS, "ignoring data csums");
 	btrfs_info_if_set(info, old, IGNOREMETACSUMS, "ignoring meta csums");
 	btrfs_info_if_set(info, old, IGNORESUPERFLAGS, "ignoring unknown super block flags");
+	btrfs_info_if_set(info, old, TEST_DUMMY_ENCRYPTION, "test dummy encryption mode enabled");
 
 	btrfs_info_if_unset(info, old, NODATASUM, "setting datasum");
 	btrfs_info_if_unset(info, old, NODATACOW, "setting datacow");
@@ -1498,6 +1537,21 @@ static void btrfs_emit_options(struct btrfs_fs_info *info,
 		btrfs_info(info, "max_inline set to %llu", info->max_inline);
 }
 
+static bool btrfs_check_test_dummy_encryption(struct fs_context *fc)
+{
+	struct btrfs_fs_context *ctx = fc->fs_private;
+	struct btrfs_fs_info *fs_info = btrfs_sb(fc->root->d_sb);
+
+	if (!fscrypt_is_dummy_policy_set(&ctx->dummy_enc_policy))
+		return true;
+
+	if (fscrypt_dummy_policies_equal(&fs_info->dummy_enc_policy, &ctx->dummy_enc_policy))
+		return true;
+
+	btrfs_warn(fs_info, "Can't set or change test_dummy_encryption on remount");
+	return false;
+}
+
 static int btrfs_reconfigure(struct fs_context *fc)
 {
 	struct super_block *sb = fc->root->d_sb;
@@ -1523,6 +1577,9 @@ static int btrfs_reconfigure(struct fs_context *fc)
 	if (!btrfs_check_options(fs_info, &ctx->mount_opt, fc->sb_flags))
 		return -EINVAL;
 
+	if (!mount_reconfigure && !btrfs_check_test_dummy_encryption(fc))
+		return -EINVAL;
+
 	ret = btrfs_check_features(fs_info, !(fc->sb_flags & SB_RDONLY));
 	if (ret < 0)
 		return ret;
@@ -2139,6 +2196,7 @@ static void btrfs_free_fs_context(struct fs_context *fc)
 		btrfs_free_fs_info(fs_info);
 
 	if (ctx && refcount_dec_and_test(&ctx->refs)) {
+		fscrypt_free_dummy_policy(&ctx->dummy_enc_policy);
 		kfree(ctx->subvol_name);
 		kfree(ctx);
 	}
-- 
2.51.0


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

* [PATCH v6 35/43] btrfs: make btrfs_ref_to_path handle encrypted filenames
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (33 preceding siblings ...)
  2026-02-06 18:23 ` [PATCH v6 34/43] btrfs: add test_dummy_encryption support Daniel Vacek
@ 2026-02-06 18:23 ` Daniel Vacek
  2026-02-08 15:02   ` Chris Mason
  2026-02-06 18:23 ` [PATCH v6 36/43] btrfs: deal with encrypted symlinks in send Daniel Vacek
                   ` (9 subsequent siblings)
  44 siblings, 1 reply; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:23 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

We use this helper for inode-resolve and path resolution in send, so
update this helper to properly decrypt any encrypted names it finds.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/365d4f820f70b7cf69b1b9cae9b949a15c3350b0.1706116485.git.josef@toxicpanda.com/
 * Adapted to btrfs_iget() now returning binode instead of vfs inode
   as before.
 * Adapted to crypt info being moved from vfs inode to FS specific inode.
---
 fs/btrfs/backref.c | 42 +++++++++++++++++++++++++++++++++++++----
 fs/btrfs/fscrypt.c | 47 ++++++++++++++++++++++++++++++++++++++++++++++
 fs/btrfs/fscrypt.h | 10 ++++++++++
 3 files changed, 95 insertions(+), 4 deletions(-)

diff --git a/fs/btrfs/backref.c b/fs/btrfs/backref.c
index 9bb406f7dd30..577c3ef87791 100644
--- a/fs/btrfs/backref.c
+++ b/fs/btrfs/backref.c
@@ -20,6 +20,7 @@
 #include "extent-tree.h"
 #include "relocation.h"
 #include "tree-checker.h"
+#include "fscrypt.h"
 
 /* Just arbitrary numbers so we can be sure one of these happened. */
 #define BACKREF_FOUND_SHARED     6
@@ -2107,6 +2108,39 @@ int btrfs_find_one_extref(struct btrfs_root *root, u64 inode_objectid,
 	return ret;
 }
 
+static int copy_resolved_iref_to_buf(struct btrfs_root *fs_root,
+				     struct extent_buffer *eb, char *dest,
+				     u64 parent, unsigned long name_off,
+				     u32 name_len, s64 *bytes_left)
+{
+	struct btrfs_fs_info *fs_info = fs_root->fs_info;
+	struct fscrypt_str fname = FSTR_INIT(NULL, 0);
+	int ret;
+
+	/* No encryption, just copy the name in. */
+	if (!btrfs_fs_incompat(fs_info, ENCRYPT)) {
+		*bytes_left -= name_len;
+		if (*bytes_left >= 0)
+			read_extent_buffer(eb, dest + *bytes_left, name_off, name_len);
+		return 0;
+	}
+
+	ret = fscrypt_fname_alloc_buffer(BTRFS_NAME_LEN, &fname);
+	if (ret)
+		return ret;
+
+	ret = btrfs_decrypt_name(fs_root, eb, name_off, name_len, parent, &fname);
+	if (ret)
+		goto out;
+
+	*bytes_left -= fname.len;
+	if (*bytes_left >= 0)
+		memcpy(dest + *bytes_left, fname.name, fname.len);
+out:
+	fscrypt_fname_free_buffer(&fname);
+	return ret;
+}
+
 /*
  * this iterates to turn a name (from iref/extref) into a full filesystem path.
  * Elements of the path are separated by '/' and the path is guaranteed to be
@@ -2138,10 +2172,10 @@ char *btrfs_ref_to_path(struct btrfs_root *fs_root, struct btrfs_path *path,
 		dest[bytes_left] = '\0';
 
 	while (1) {
-		bytes_left -= name_len;
-		if (bytes_left >= 0)
-			read_extent_buffer(eb, dest + bytes_left,
-					   name_off, name_len);
+		ret = copy_resolved_iref_to_buf(fs_root, eb, dest, parent,
+						name_off, name_len, &bytes_left);
+		if (ret)
+			break;
 		if (eb != eb_in) {
 			if (!path->skip_locking)
 				btrfs_tree_read_unlock(eb);
diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
index d1a4cbb990d4..bcb86cbaa171 100644
--- a/fs/btrfs/fscrypt.c
+++ b/fs/btrfs/fscrypt.c
@@ -385,6 +385,53 @@ int btrfs_fscrypt_bio_length(struct bio *bio, u64 map_length)
 	return map_length;
 }
 
+int btrfs_decrypt_name(struct btrfs_root *root, struct extent_buffer *eb,
+		       unsigned long name_off, u32 name_len,
+		       u64 parent_ino, struct fscrypt_str *name)
+{
+	struct btrfs_inode *inode;
+	struct inode *dir;
+	struct fscrypt_str iname = FSTR_INIT(NULL, 0);
+	int ret;
+
+	ASSERT(name_len <= BTRFS_NAME_LEN);
+
+	ret = fscrypt_fname_alloc_buffer(name_len, &iname);
+	if (ret)
+		return ret;
+
+	inode = btrfs_iget(parent_ino, root);
+	if (IS_ERR(inode)) {
+		ret = PTR_ERR(inode);
+		goto out;
+	}
+	dir = &inode->vfs_inode;
+
+	/*
+	 * Directory isn't encrypted, the name isn't encrypted, we can just copy
+	 * it into the buffer.
+	 */
+	if (!IS_ENCRYPTED(dir)) {
+		read_extent_buffer(eb, name->name, name_off, name_len);
+		name->len = name_len;
+		goto out_inode;
+	}
+
+	read_extent_buffer(eb, iname.name, name_off, name_len);
+
+	ret = fscrypt_prepare_readdir(dir);
+	if (ret)
+		goto out_inode;
+
+	ASSERT(inode->i_crypt_info);
+	ret = fscrypt_fname_disk_to_usr(dir, 0, 0, &iname, name);
+out_inode:
+	iput(dir);
+out:
+	fscrypt_fname_free_buffer(&iname);
+	return ret;
+}
+
 const struct fscrypt_operations btrfs_fscrypt_ops = {
 	.inode_info_offs = (int)offsetof(struct btrfs_inode, i_crypt_info) -
 			   (int)offsetof(struct btrfs_inode, vfs_inode),
diff --git a/fs/btrfs/fscrypt.h b/fs/btrfs/fscrypt.h
index 347b34f45715..4f49ed6176d4 100644
--- a/fs/btrfs/fscrypt.h
+++ b/fs/btrfs/fscrypt.h
@@ -32,6 +32,9 @@ bool btrfs_mergeable_encrypted_bio(struct bio *bio, struct inode *inode,
 				   struct fscrypt_extent_info *fi,
 				   u64 logical_offset);
 int btrfs_fscrypt_bio_length(struct bio *bio, u64 map_length);
+int btrfs_decrypt_name(struct btrfs_root *root, struct extent_buffer *eb,
+		       unsigned long name_off, u32 name_len,
+		       u64 parent_ino, struct fscrypt_str *name);
 
 #else
 static inline void btrfs_fscrypt_save_extent_info(struct btrfs_path *path,
@@ -91,6 +94,13 @@ static inline u64 btrfs_fscrypt_bio_length(struct bio *bio, u64 map_length)
 	return map_length;
 }
 
+static inline int btrfs_decrypt_name(struct btrfs_root *root, struct extent_buffer *eb,
+				     unsigned long name_off, u32 name_len,
+				     u64 parent_ino, struct fscrypt_str *name)
+{
+	return -EINVAL;
+}
+
 #endif /* CONFIG_FS_ENCRYPTION */
 
 extern const struct fscrypt_operations btrfs_fscrypt_ops;
-- 
2.51.0


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

* [PATCH v6 36/43] btrfs: deal with encrypted symlinks in send
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (34 preceding siblings ...)
  2026-02-06 18:23 ` [PATCH v6 35/43] btrfs: make btrfs_ref_to_path handle encrypted filenames Daniel Vacek
@ 2026-02-06 18:23 ` Daniel Vacek
  2026-02-06 18:23 ` [PATCH v6 37/43] btrfs: decrypt file names for send Daniel Vacek
                   ` (8 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:23 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

Send needs to send the decrypted value of the symlinks, handle the case
where the inode is encrypted and decrypt the symlink name into a buffer
and copy this buffer into our fs_path struct.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/4d97f35d6f85ff041b09bed33b63446a92b7a20c.1706116485.git.josef@toxicpanda.com/
 * read_symlink_encrypted() reworked from using pages to using folios.
---
 fs/btrfs/send.c | 45 ++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 42 insertions(+), 3 deletions(-)

diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
index 3dcfdba018b5..b77f96ae2fea 100644
--- a/fs/btrfs/send.c
+++ b/fs/btrfs/send.c
@@ -1701,9 +1701,7 @@ static int find_extent_clone(struct send_ctx *sctx,
 	return ret;
 }
 
-static int read_symlink(struct btrfs_root *root,
-			u64 ino,
-			struct fs_path *dest)
+static int read_symlink_unencrypted(struct btrfs_root *root, u64 ino, struct fs_path *dest)
 {
 	int ret;
 	BTRFS_PATH_AUTO_FREE(path);
@@ -1764,6 +1762,47 @@ static int read_symlink(struct btrfs_root *root,
 	return fs_path_add_from_extent_buffer(dest, path->nodes[0], off, len);
 }
 
+static int read_symlink_encrypted(struct btrfs_root *root, u64 ino, struct fs_path *dest)
+{
+	DEFINE_DELAYED_CALL(done);
+	const char *buf;
+	struct folio *folio;
+	struct btrfs_inode *inode;
+	int ret = 0;
+
+	inode = btrfs_iget(ino, root);
+	if (IS_ERR(inode))
+		return PTR_ERR(inode);
+
+	folio = read_mapping_folio(inode->vfs_inode.i_mapping, 0, NULL);
+	if (IS_ERR(folio)) {
+		iput(&inode->vfs_inode);
+		return PTR_ERR(folio);
+	}
+
+	buf = fscrypt_get_symlink(&inode->vfs_inode, folio_address(folio),
+				  BTRFS_MAX_INLINE_DATA_SIZE(root->fs_info),
+				  &done);
+	folio_put(folio);
+	iput(&inode->vfs_inode);
+
+	if (IS_ERR(buf))
+		return PTR_ERR(buf);
+
+	ret = fs_path_add(dest, buf, strlen(buf));
+	do_delayed_call(&done);
+	return ret;
+}
+
+
+static int read_symlink(struct btrfs_root *root, u64 ino,
+			struct fs_path *dest)
+{
+	if (btrfs_fs_incompat(root->fs_info, ENCRYPT))
+		return read_symlink_encrypted(root, ino, dest);
+	return read_symlink_unencrypted(root, ino, dest);
+}
+
 /*
  * Helper function to generate a file name that is unique in the root of
  * send_root and parent_root. This is used to generate names for orphan inodes.
-- 
2.51.0


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

* [PATCH v6 37/43] btrfs: decrypt file names for send
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (35 preceding siblings ...)
  2026-02-06 18:23 ` [PATCH v6 36/43] btrfs: deal with encrypted symlinks in send Daniel Vacek
@ 2026-02-06 18:23 ` Daniel Vacek
  2026-02-06 18:23 ` [PATCH v6 38/43] btrfs: load the inode context before sending writes Daniel Vacek
                   ` (7 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:23 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

In send we're going to be looking up file names from back references and
putting them into the send stream.  If we are encrypted use the helper
for decrypting names and copy the decrypted name into the buffer.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/fd8b1d5f395a890dbdf8281a52fbaaa920c7b726.1706116485.git.josef@toxicpanda.com/
 * Simplified the error return logic in fs_path_add_from_encrypted().
---
 fs/btrfs/send.c | 47 +++++++++++++++++++++++++++++++++++++----------
 1 file changed, 37 insertions(+), 10 deletions(-)

diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
index b77f96ae2fea..faaade4c35dc 100644
--- a/fs/btrfs/send.c
+++ b/fs/btrfs/send.c
@@ -32,6 +32,7 @@
 #include "ioctl.h"
 #include "verity.h"
 #include "lru_cache.h"
+#include "fscrypt.h"
 
 /*
  * Maximum number of references an extent can have in order for us to attempt to
@@ -579,13 +580,39 @@ static inline int fs_path_add_path(struct fs_path *p, const struct fs_path *p2)
 	return fs_path_add(p, p2->start, fs_path_len(p2));
 }
 
-static int fs_path_add_from_extent_buffer(struct fs_path *p,
+static int fs_path_add_from_encrypted(struct btrfs_root *root,
+				      struct fs_path *p,
+				      struct extent_buffer *eb,
+				      unsigned long off, int len,
+				      u64 parent_ino)
+{
+	struct fscrypt_str fname = FSTR_INIT(NULL, 0);
+	int ret;
+
+	ret = fscrypt_fname_alloc_buffer(BTRFS_NAME_LEN, &fname);
+	if (ret)
+		return ret;
+
+	ret = btrfs_decrypt_name(root, eb, off, len, parent_ino, &fname);
+	if (!ret)
+		ret = fs_path_add(p, fname.name, fname.len);
+
+	fscrypt_fname_free_buffer(&fname);
+	return ret;
+}
+
+static int fs_path_add_from_extent_buffer(struct btrfs_root *root,
+					  struct fs_path *p,
 					  struct extent_buffer *eb,
-					  unsigned long off, int len)
+					  unsigned long off, int len,
+					  u64 parent_ino)
 {
 	int ret;
 	char *prepared;
 
+	if (root && btrfs_fs_incompat(root->fs_info, ENCRYPT))
+		return fs_path_add_from_encrypted(root, p, eb, off, len, parent_ino);
+
 	ret = fs_path_prepare_for_add(p, len, &prepared);
 	if (ret < 0)
 		return ret;
@@ -1062,8 +1089,7 @@ static int iterate_inode_ref(struct btrfs_root *root, struct btrfs_path *path,
 			}
 			p->start = start;
 		} else {
-			ret = fs_path_add_from_extent_buffer(p, eb, name_off,
-							     name_len);
+			ret = fs_path_add_from_extent_buffer(root, p, eb, name_off, name_len, dir);
 			if (ret < 0)
 				goto out;
 		}
@@ -1759,7 +1785,7 @@ static int read_symlink_unencrypted(struct btrfs_root *root, u64 ino, struct fs_
 	off = btrfs_file_extent_inline_start(ei);
 	len = btrfs_file_extent_ram_bytes(path->nodes[0], ei);
 
-	return fs_path_add_from_extent_buffer(dest, path->nodes[0], off, len);
+	return fs_path_add_from_extent_buffer(NULL, dest, path->nodes[0], off, len, 0);
 }
 
 static int read_symlink_encrypted(struct btrfs_root *root, u64 ino, struct fs_path *dest)
@@ -2034,18 +2060,19 @@ static int get_first_ref(struct btrfs_root *root, u64 ino,
 		iref = btrfs_item_ptr(path->nodes[0], path->slots[0],
 				      struct btrfs_inode_ref);
 		len = btrfs_inode_ref_name_len(path->nodes[0], iref);
-		ret = fs_path_add_from_extent_buffer(name, path->nodes[0],
-						     (unsigned long)(iref + 1),
-						     len);
 		parent_dir = found_key.offset;
+		ret = fs_path_add_from_extent_buffer(root, name, path->nodes[0],
+						     (unsigned long)(iref + 1),
+						     len, parent_dir);
 	} else {
 		struct btrfs_inode_extref *extref;
 		extref = btrfs_item_ptr(path->nodes[0], path->slots[0],
 					struct btrfs_inode_extref);
 		len = btrfs_inode_extref_name_len(path->nodes[0], extref);
-		ret = fs_path_add_from_extent_buffer(name, path->nodes[0],
-					(unsigned long)&extref->name, len);
 		parent_dir = btrfs_inode_extref_parent(path->nodes[0], extref);
+		ret = fs_path_add_from_extent_buffer(root, name, path->nodes[0],
+					(unsigned long)&extref->name, len,
+					parent_dir);
 	}
 	if (ret < 0)
 		return ret;
-- 
2.51.0


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

* [PATCH v6 38/43] btrfs: load the inode context before sending writes
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (36 preceding siblings ...)
  2026-02-06 18:23 ` [PATCH v6 37/43] btrfs: decrypt file names for send Daniel Vacek
@ 2026-02-06 18:23 ` Daniel Vacek
  2026-02-06 18:23 ` [PATCH v6 39/43] btrfs: set the appropriate free space settings in reconfigure Daniel Vacek
                   ` (6 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:23 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

For send we will read the pages and copy them into our buffer.  Use the
fscrypt_inode_open helper to make sure the key is loaded properly before
trying to read from the inode so the contents are properly decrypted.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/a307def01bf28c3ed99398868a142180c6088527.1706116485.git.josef@toxicpanda.com/
 * btrfs_iget() returns binode now.
---
 fs/btrfs/send.c | 36 ++++++++++++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)

diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
index faaade4c35dc..bf9a99a9d24d 100644
--- a/fs/btrfs/send.c
+++ b/fs/btrfs/send.c
@@ -5331,6 +5331,38 @@ static int put_file_data(struct send_ctx *sctx, u64 offset, u32 len)
 	return ret;
 }
 
+static int load_fscrypt_context(struct send_ctx *sctx)
+{
+	struct btrfs_root *root = sctx->send_root;
+	struct name_cache_entry *nce;
+	struct btrfs_inode *dir;
+	int ret;
+
+	if (!btrfs_fs_incompat(root->fs_info, ENCRYPT))
+		return 0;
+
+	/*
+	 * If we're encrypted we need to load the parent inode in order to make
+	 * sure the encryption context is loaded.  We have to do this even if
+	 * we're not encrypted, as we need to make sure that we don't violate
+	 * the rule about encrypted children with non-encrypted parents, which
+	 * is enforced by __fscrypt_file_open.
+	 */
+	nce = name_cache_search(sctx, sctx->cur_ino, sctx->cur_inode_gen);
+	if (!nce) {
+		ASSERT(nce);
+		return -EINVAL;
+	}
+
+	dir = btrfs_iget(nce->parent_ino, root);
+	if (IS_ERR(dir))
+		return PTR_ERR(dir);
+
+	ret = __fscrypt_file_open(&dir->vfs_inode, sctx->cur_inode);
+	iput(&dir->vfs_inode);
+	return ret;
+}
+
 /*
  * Read some bytes from the current inode/file and send a write command to
  * user space.
@@ -5348,6 +5380,10 @@ static int send_write(struct send_ctx *sctx, u64 offset, u32 len)
 	if (ret < 0)
 		return ret;
 
+	ret = load_fscrypt_context(sctx);
+	if (ret < 0)
+		return ret;
+
 	TLV_PUT_PATH(sctx, BTRFS_SEND_A_PATH, p);
 	TLV_PUT_U64(sctx, BTRFS_SEND_A_FILE_OFFSET, offset);
 	ret = put_file_data(sctx, offset, len);
-- 
2.51.0


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

* [PATCH v6 39/43] btrfs: set the appropriate free space settings in reconfigure
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (37 preceding siblings ...)
  2026-02-06 18:23 ` [PATCH v6 38/43] btrfs: load the inode context before sending writes Daniel Vacek
@ 2026-02-06 18:23 ` Daniel Vacek
  2026-02-06 18:23 ` [PATCH v6 40/43] btrfs: support encryption with log replay Daniel Vacek
                   ` (5 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:23 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

btrfs/330 uncovered a problem where we were accidentally turning off the
free space tree when we do the transition from ro->rw.  This happens
because we don't update

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/325598eeb1d23f9ae04f675b4ee218f7e98e3ff0.1706116485.git.josef@toxicpanda.com/
 * Re-wrapped comments.
 * Fixed argument type.  mount_opt is now ull.
---
 fs/btrfs/disk-io.c |  2 +-
 fs/btrfs/super.c   | 28 +++++++++++++++-------------
 fs/btrfs/super.h   |  3 ++-
 3 files changed, 18 insertions(+), 15 deletions(-)

diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
index f47a20976b53..993d2080fcbf 100644
--- a/fs/btrfs/disk-io.c
+++ b/fs/btrfs/disk-io.c
@@ -3416,7 +3416,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device
 	 * Handle the space caching options appropriately now that we have the
 	 * super block loaded and validated.
 	 */
-	btrfs_set_free_space_cache_settings(fs_info);
+	btrfs_set_free_space_cache_settings(fs_info, &fs_info->mount_opt);
 
 	if (!btrfs_check_options(fs_info, &fs_info->mount_opt, sb->s_flags)) {
 		ret = -EINVAL;
diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
index 06788b27a870..4a2887147ead 100644
--- a/fs/btrfs/super.c
+++ b/fs/btrfs/super.c
@@ -745,10 +745,10 @@ bool btrfs_check_options(const struct btrfs_fs_info *info,
 }
 
 /*
- * This is subtle, we only call this during open_ctree().  We need to pre-load
- * the mount options with the on-disk settings.  Before the new mount API took
- * effect we would do this on mount and remount.  With the new mount API we'll
- * only do this on the initial mount.
+ * Because we have an odd set of behavior with turning on and off the space cache
+ * and free space tree we have to call this before we start the mount operation
+ * after we load the super, or before we start remount.  This is to make sure we
+ * have the proper free space settings in place if the user didn't specify any.
  *
  * This isn't a change in behavior, because we're using the current state of the
  * file system to set the current mount options.  If you mounted with special
@@ -756,21 +756,22 @@ bool btrfs_check_options(const struct btrfs_fs_info *info,
  * settings, because mounting without these features cleared the on-disk
  * settings, so this being called on re-mount is not needed.
  */
-void btrfs_set_free_space_cache_settings(struct btrfs_fs_info *fs_info)
+void btrfs_set_free_space_cache_settings(struct btrfs_fs_info *fs_info,
+					 unsigned long long *mount_opt)
 {
-	if (fs_info->sectorsize != PAGE_SIZE && btrfs_test_opt(fs_info, SPACE_CACHE)) {
+	if (fs_info->sectorsize != PAGE_SIZE && btrfs_raw_test_opt(*mount_opt, SPACE_CACHE)) {
 		btrfs_info(fs_info,
 			   "forcing free space tree for sector size %u with page size %lu",
 			   fs_info->sectorsize, PAGE_SIZE);
-		btrfs_clear_opt(fs_info->mount_opt, SPACE_CACHE);
-		btrfs_set_opt(fs_info->mount_opt, FREE_SPACE_TREE);
+		btrfs_clear_opt(*mount_opt, SPACE_CACHE);
+		btrfs_set_opt(*mount_opt, FREE_SPACE_TREE);
 	}
 
 	/*
 	 * At this point our mount options are populated, so we only mess with
 	 * these settings if we don't have any settings already.
 	 */
-	if (btrfs_test_opt(fs_info, FREE_SPACE_TREE))
+	if (btrfs_raw_test_opt(*mount_opt, FREE_SPACE_TREE))
 		return;
 
 	if (btrfs_is_zoned(fs_info) &&
@@ -780,10 +781,10 @@ void btrfs_set_free_space_cache_settings(struct btrfs_fs_info *fs_info)
 		return;
 	}
 
-	if (btrfs_test_opt(fs_info, SPACE_CACHE))
+	if (btrfs_raw_test_opt(*mount_opt, SPACE_CACHE))
 		return;
 
-	if (btrfs_test_opt(fs_info, NOSPACECACHE))
+	if (btrfs_raw_test_opt(*mount_opt, NOSPACECACHE))
 		return;
 
 	/*
@@ -791,9 +792,9 @@ void btrfs_set_free_space_cache_settings(struct btrfs_fs_info *fs_info)
 	 * them ourselves based on the state of the file system.
 	 */
 	if (btrfs_fs_compat_ro(fs_info, FREE_SPACE_TREE))
-		btrfs_set_opt(fs_info->mount_opt, FREE_SPACE_TREE);
+		btrfs_set_opt(*mount_opt, FREE_SPACE_TREE);
 	else if (btrfs_free_space_cache_v1_active(fs_info))
-		btrfs_set_opt(fs_info->mount_opt, SPACE_CACHE);
+		btrfs_set_opt(*mount_opt, SPACE_CACHE);
 }
 
 static void set_device_specific_options(struct btrfs_fs_info *fs_info)
@@ -1573,6 +1574,7 @@ static int btrfs_reconfigure(struct fs_context *fc)
 
 	sync_filesystem(sb);
 	set_bit(BTRFS_FS_STATE_REMOUNTING, &fs_info->fs_state);
+	btrfs_set_free_space_cache_settings(fs_info, &ctx->mount_opt);
 
 	if (!btrfs_check_options(fs_info, &ctx->mount_opt, fc->sb_flags))
 		return -EINVAL;
diff --git a/fs/btrfs/super.h b/fs/btrfs/super.h
index d80a86acfbbe..584f428d36e2 100644
--- a/fs/btrfs/super.h
+++ b/fs/btrfs/super.h
@@ -16,7 +16,8 @@ bool btrfs_check_options(const struct btrfs_fs_info *info,
 int btrfs_sync_fs(struct super_block *sb, int wait);
 char *btrfs_get_subvol_name_from_objectid(struct btrfs_fs_info *fs_info,
 					  u64 subvol_objectid);
-void btrfs_set_free_space_cache_settings(struct btrfs_fs_info *fs_info);
+void btrfs_set_free_space_cache_settings(struct btrfs_fs_info *fs_info,
+					 unsigned long long *mount_opt);
 
 static inline struct btrfs_fs_info *btrfs_sb(struct super_block *sb)
 {
-- 
2.51.0


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

* [PATCH v6 40/43] btrfs: support encryption with log replay
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (38 preceding siblings ...)
  2026-02-06 18:23 ` [PATCH v6 39/43] btrfs: set the appropriate free space settings in reconfigure Daniel Vacek
@ 2026-02-06 18:23 ` Daniel Vacek
  2026-02-06 18:23 ` [PATCH v6 41/43] btrfs: disable auto defrag on encrypted files Daniel Vacek
                   ` (4 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:23 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

Log replay needs a few tweaks in order to make sure everything works
with encryption.

1. Copy in the fscrypt context if we find one in the log.
2. Set NEEDS_FULL_SYNC when we update the inode context so all of the
   items are copied into the log at fsync time.

This makes replay of encrypted files work properly.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/d1ada7ac632c2ab554a840c7ba29b53a93b9855f.1706116485.git.josef@toxicpanda.com/
 * Adapted to the redesigned encryption context storing.
   - No need to specially handle the extent item now.
     It is not any different to before and the
     new context items are simply copied.
---
 fs/btrfs/fscrypt.c  | 1 +
 fs/btrfs/tree-log.c | 4 +++-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
index bcb86cbaa171..cbdeaa75a868 100644
--- a/fs/btrfs/fscrypt.c
+++ b/fs/btrfs/fscrypt.c
@@ -145,6 +145,7 @@ static int btrfs_fscrypt_set_context(struct inode *inode, const void *ctx,
 			goto out_err;
 	}
 
+	set_bit(BTRFS_INODE_NEEDS_FULL_SYNC, &BTRFS_I(inode)->runtime_flags);
 	leaf = path->nodes[0];
 	ptr = btrfs_item_ptr_offset(leaf, path->slots[0]);
 
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
index 0d01e31a4592..fa75531f9633 100644
--- a/fs/btrfs/tree-log.c
+++ b/fs/btrfs/tree-log.c
@@ -2922,7 +2922,9 @@ static int replay_one_buffer(struct extent_buffer *eb,
 			continue;
 
 		/* these keys are simply copied */
-		if (wc->log_key.type == BTRFS_XATTR_ITEM_KEY) {
+		if (wc->log_key.type == BTRFS_XATTR_ITEM_KEY ||
+		    wc->log_key.type == BTRFS_FSCRYPT_INODE_CTX_KEY ||
+		    wc->log_key.type == BTRFS_FSCRYPT_CTX_KEY) {
 			ret = overwrite_item(wc);
 			if (ret)
 				break;
-- 
2.51.0


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

* [PATCH v6 41/43] btrfs: disable auto defrag on encrypted files
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (39 preceding siblings ...)
  2026-02-06 18:23 ` [PATCH v6 40/43] btrfs: support encryption with log replay Daniel Vacek
@ 2026-02-06 18:23 ` Daniel Vacek
  2026-02-06 18:23 ` [PATCH v6 42/43] btrfs: disable encryption on RAID5/6 Daniel Vacek
                   ` (3 subsequent siblings)
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:23 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

We will drop the inode and re-look it up to do defrag with auto defrag,
which means we could lose the encryption policy.

Auto defrag needs to be reworked to just hold onto the inode for
scheduling later so we don't lose the context.  For now just disable it
if the file is encrypted.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/b717912bf88797b3044a3c2724b59b1ecc17ea78.1706116485.git.josef@toxicpanda.com/
 * No changes since.
---
 fs/btrfs/defrag.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/fs/btrfs/defrag.c b/fs/btrfs/defrag.c
index f64c0502cef9..cd6ea6e5d3ea 100644
--- a/fs/btrfs/defrag.c
+++ b/fs/btrfs/defrag.c
@@ -126,6 +126,14 @@ void btrfs_add_inode_defrag(struct btrfs_inode *inode, u32 extent_thresh)
 	if (!need_auto_defrag(fs_info))
 		return;
 
+	/*
+	 * Since we have to read the inode at defrag time disable auto defrag
+	 * for encrypted inodes until we have code to read the parent and load
+	 * the encryption context.
+	 */
+	if (IS_ENCRYPTED(&inode->vfs_inode))
+		return;
+
 	if (test_bit(BTRFS_INODE_IN_DEFRAG, &inode->runtime_flags))
 		return;
 
-- 
2.51.0


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

* [PATCH v6 42/43] btrfs: disable encryption on RAID5/6
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (40 preceding siblings ...)
  2026-02-06 18:23 ` [PATCH v6 41/43] btrfs: disable auto defrag on encrypted files Daniel Vacek
@ 2026-02-06 18:23 ` Daniel Vacek
  2026-02-08 13:14   ` Chris Mason
  2026-02-06 18:23 ` [PATCH v6 43/43] btrfs: disable send if we have encryption enabled Daniel Vacek
                   ` (2 subsequent siblings)
  44 siblings, 1 reply; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:23 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel

From: Josef Bacik <josef@toxicpanda.com>

The RAID5/6 code will re-arrange bios and submit them through a
different mechanism.  This is problematic with inline encryption as we
have to get the bio and csum it after it's been encrypted, and the
radi5/6 bio's don't have the btrfs_bio embedded, so we have no way to
get the csums put on disk.

This isn't an unsolvable problem, but would require a bit of reworking.
Since we discourage users from using this code currently simply don't
allow encryption on RAID5/6 setups.  If there's sufficient demand in the
future we can add the support for this.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/941f02bb923edadae1aea4ae3e5aa6ba05d1215a.1706116485.git.josef@toxicpanda.com/
 * No changes since.
---
 fs/btrfs/ioctl.c | 4 ++++
 fs/btrfs/super.c | 6 ++++++
 2 files changed, 10 insertions(+)

diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
index a8adf99ad0a8..1bade8fea16e 100644
--- a/fs/btrfs/ioctl.c
+++ b/fs/btrfs/ioctl.c
@@ -5198,6 +5198,10 @@ long btrfs_ioctl(struct file *file, unsigned int
 			return -EOPNOTSUPP;
 		if (sb_rdonly(fs_info->sb))
 			return -EROFS;
+		if (btrfs_fs_incompat(fs_info, RAID56)) {
+			btrfs_warn(fs_info, "can't enable encryption with RAID5/6");
+			return -EINVAL;
+		}
 		/*
 		 *  If we crash before we commit, nothing encrypted could have
 		 * been written so it doesn't matter whether the encrypted
diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
index 4a2887147ead..aefcbe56e85a 100644
--- a/fs/btrfs/super.c
+++ b/fs/btrfs/super.c
@@ -734,6 +734,12 @@ bool btrfs_check_options(const struct btrfs_fs_info *info,
 	if (btrfs_check_mountopts_zoned(info, mount_opt))
 		ret = false;
 
+	if (btrfs_fs_incompat(info, RAID56) &&
+	    btrfs_raw_test_opt(*mount_opt, TEST_DUMMY_ENCRYPTION)) {
+		btrfs_err(info, "cannot use test_dummy_encryption with RAID5/6");
+		ret = false;
+	}
+
 	if (!test_bit(BTRFS_FS_STATE_REMOUNTING, &info->fs_state)) {
 		if (btrfs_raw_test_opt(*mount_opt, SPACE_CACHE)) {
 			btrfs_warn(info,
-- 
2.51.0


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

* [PATCH v6 43/43] btrfs: disable send if we have encryption enabled
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (41 preceding siblings ...)
  2026-02-06 18:23 ` [PATCH v6 42/43] btrfs: disable encryption on RAID5/6 Daniel Vacek
@ 2026-02-06 18:23 ` Daniel Vacek
  2026-02-06 18:42 ` [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
  2026-02-21 20:56 ` Eric Biggers
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:23 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
	linux-kernel, Boris Burkov

From: Josef Bacik <josef@toxicpanda.com>

send needs to track the dir item values to see if files were renamed
when doing an incremental send.  There is code to decrypt the names, but
this breaks the code that checks to see if something was overwritten.
Until this gap is closed we need to disable send on encrypted file
systems.  Fixing this is straightforward, but a medium sized project.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---

v5: https://lore.kernel.org/linux-btrfs/62ce86b38e2575c542eed7fbe8d986e68496b1d7.1706116485.git.josef@toxicpanda.com/
 * No changes since.
---
 fs/btrfs/send.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
index bf9a99a9d24d..a3aa95ce4f87 100644
--- a/fs/btrfs/send.c
+++ b/fs/btrfs/send.c
@@ -8095,6 +8095,12 @@ long btrfs_ioctl_send(struct btrfs_root *send_root, const struct btrfs_ioctl_sen
 	if (!capable(CAP_SYS_ADMIN))
 		return -EPERM;
 
+	if (btrfs_fs_incompat(fs_info, ENCRYPT)) {
+		btrfs_err(fs_info,
+		  "send with encryption enabled isn't currently suported");
+		return -EINVAL;
+	}
+
 	/*
 	 * The subvolume must remain read-only during send, protect against
 	 * making it RW. This also protects against deletion.
-- 
2.51.0


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

* Re: [PATCH v6 00/43] btrfs: add fscrypt support
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (42 preceding siblings ...)
  2026-02-06 18:23 ` [PATCH v6 43/43] btrfs: disable send if we have encryption enabled Daniel Vacek
@ 2026-02-06 18:42 ` Daniel Vacek
  2026-02-21 20:56 ` Eric Biggers
  44 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-06 18:42 UTC (permalink / raw)
  To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba
  Cc: linux-block, linux-fscrypt, linux-btrfs, linux-kernel

Heads up,

I also have related btrfs-progs and xfs-tests changes matching this
series. But I won't be able to send them before my vacation next week.
That also means I will (most likely) not be able to get back to review
feedback before Feb 16. But for sure, any feedback is appreciated.

Thank you,
Daniel

On Fri, 6 Feb 2026 at 19:23, Daniel Vacek <neelx@suse.com> wrote:
>
> Hello,
>
> These are the remaining parts from former series [1] from Omar, Sweet Tea
> and Josef.  Some bits of it were split into the separate set [2] before.
>
> Notably, at this stage encryption is not supported with RAID5/6 setup
> and send is also isabled for now.
>
> There are a few changes since v5:
>  * Rebased to btrfs/for-next branch.  Couple things changed in the last
>    years.  A few patches were dropped as the code cleaned up or refactored.
>    More details in the patches themselves.
>  * As suggested by Qu and Dave, the on-disk format of storing the extent
>    encryption context was re-designed.  Now, a new tree item with dedicated
>    key is inserted instead of extending the file extent item.  As a result
>    a special care needs to be taken when removing the encrypted extents
>    to also remove the related encryption context item.
>  * Fixed bugs found during testing.
>
> Have a nice day,
> Daniel
>
> [1] v5 https://lore.kernel.org/linux-btrfs/cover.1706116485.git.josef@toxicpanda.com/
> [2] https://lore.kernel.org/linux-btrfs/20251112193611.2536093-1-neelx@suse.com/
>
> Josef Bacik (33):
>   fscrypt: add per-extent encryption support
>   fscrypt: allow inline encryption for extent based encryption
>   fscrypt: add a __fscrypt_file_open helper
>   fscrypt: conditionally don't wipe mk secret until the last active user
>     is done
>   blk-crypto: add a process bio callback
>   fscrypt: add a process_bio hook to fscrypt_operations
>   fscrypt: add documentation about extent encryption
>   btrfs: add infrastructure for safe em freeing
>   btrfs: select encryption dependencies if FS_ENCRYPTION
>   btrfs: add fscrypt_info and encryption_type to ordered_extent
>   btrfs: plumb through setting the fscrypt_info for ordered extents
>   btrfs: populate the ordered_extent with the fscrypt context
>   btrfs: keep track of fscrypt info and orig_start for dio reads
>   btrfs: add extent encryption context tree item type
>   btrfs: pass through fscrypt_extent_info to the file extent helpers
>   btrfs: implement the fscrypt extent encryption hooks
>   btrfs: setup fscrypt_extent_info for new extents
>   btrfs: populate ordered_extent with the orig offset
>   btrfs: set the bio fscrypt context when applicable
>   btrfs: add a bio argument to btrfs_csum_one_bio
>   btrfs: limit encrypted writes to 256 segments
>   btrfs: implement process_bio cb for fscrypt
>   btrfs: implement read repair for encryption
>   btrfs: add test_dummy_encryption support
>   btrfs: make btrfs_ref_to_path handle encrypted filenames
>   btrfs: deal with encrypted symlinks in send
>   btrfs: decrypt file names for send
>   btrfs: load the inode context before sending writes
>   btrfs: set the appropriate free space settings in reconfigure
>   btrfs: support encryption with log replay
>   btrfs: disable auto defrag on encrypted files
>   btrfs: disable encryption on RAID5/6
>   btrfs: disable send if we have encryption enabled
>
> Omar Sandoval (6):
>   fscrypt: expose fscrypt_nokey_name
>   btrfs: start using fscrypt hooks
>   btrfs: add inode encryption contexts
>   btrfs: add new FEATURE_INCOMPAT_ENCRYPT flag
>   btrfs: adapt readdir for encrypted and nokey names
>   btrfs: implement fscrypt ioctls
>
> Sweet Tea Dorminy (4):
>   btrfs: handle nokey names.
>   btrfs: add get_devices hook for fscrypt
>   btrfs: set file extent encryption excplicitly
>   btrfs: add fscrypt_info and encryption_type to extent_map
>
>  Documentation/filesystems/fscrypt.rst |  41 +++
>  block/blk-crypto-fallback.c           |  43 +++
>  block/blk-crypto-internal.h           |   8 +
>  block/blk-crypto-profile.c            |   2 +
>  block/blk-crypto.c                    |   6 +-
>  fs/btrfs/Kconfig                      |   3 +
>  fs/btrfs/Makefile                     |   1 +
>  fs/btrfs/accessors.h                  |   2 +
>  fs/btrfs/backref.c                    |  42 ++-
>  fs/btrfs/bio.c                        | 146 ++++++++-
>  fs/btrfs/bio.h                        |  14 +-
>  fs/btrfs/btrfs_inode.h                |   6 +-
>  fs/btrfs/compression.c                |   6 +
>  fs/btrfs/ctree.h                      |   3 +
>  fs/btrfs/defrag.c                     |  14 +
>  fs/btrfs/delayed-inode.c              |  25 +-
>  fs/btrfs/delayed-inode.h              |   5 +-
>  fs/btrfs/dir-item.c                   | 102 +++++-
>  fs/btrfs/dir-item.h                   |  10 +-
>  fs/btrfs/direct-io.c                  |  28 +-
>  fs/btrfs/disk-io.c                    |   3 +-
>  fs/btrfs/extent_io.c                  | 115 ++++++-
>  fs/btrfs/extent_io.h                  |   3 +
>  fs/btrfs/extent_map.c                 | 102 +++++-
>  fs/btrfs/extent_map.h                 |  26 ++
>  fs/btrfs/file-item.c                  |  28 +-
>  fs/btrfs/file-item.h                  |   2 +-
>  fs/btrfs/file.c                       |  75 +++++
>  fs/btrfs/fs.h                         |   6 +-
>  fs/btrfs/fscrypt.c                    | 446 ++++++++++++++++++++++++++
>  fs/btrfs/fscrypt.h                    | 108 +++++++
>  fs/btrfs/inode.c                      | 408 +++++++++++++++++------
>  fs/btrfs/ioctl.c                      |  41 ++-
>  fs/btrfs/ordered-data.c               |  35 +-
>  fs/btrfs/ordered-data.h               |  14 +
>  fs/btrfs/reflink.c                    |  43 ++-
>  fs/btrfs/root-tree.c                  |   9 +-
>  fs/btrfs/root-tree.h                  |   2 +-
>  fs/btrfs/send.c                       | 134 +++++++-
>  fs/btrfs/super.c                      |  99 +++++-
>  fs/btrfs/super.h                      |   3 +-
>  fs/btrfs/sysfs.c                      |   6 +
>  fs/btrfs/tree-checker.c               |  67 +++-
>  fs/btrfs/tree-log.c                   |  34 +-
>  fs/crypto/crypto.c                    |  10 +-
>  fs/crypto/fname.c                     |  36 ---
>  fs/crypto/fscrypt_private.h           |  42 +++
>  fs/crypto/hooks.c                     |  38 ++-
>  fs/crypto/inline_crypt.c              |  84 ++++-
>  fs/crypto/keyring.c                   |  18 +-
>  fs/crypto/keysetup.c                  | 165 ++++++++++
>  fs/crypto/policy.c                    |  47 +++
>  include/linux/blk-crypto.h            |  15 +-
>  include/linux/fscrypt.h               | 125 ++++++++
>  include/uapi/linux/btrfs.h            |   1 +
>  include/uapi/linux/btrfs_tree.h       |  26 +-
>  56 files changed, 2683 insertions(+), 240 deletions(-)
>  create mode 100644 fs/btrfs/fscrypt.c
>  create mode 100644 fs/btrfs/fscrypt.h
>
> --
> 2.51.0
>

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

* Re: [PATCH v6 08/43] fscrypt: add documentation about extent encryption
  2026-02-06 18:22 ` [PATCH v6 08/43] fscrypt: add documentation about extent encryption Daniel Vacek
@ 2026-02-06 18:43   ` Randy Dunlap
  2026-02-17 14:48     ` Daniel Vacek
  0 siblings, 1 reply; 77+ messages in thread
From: Randy Dunlap @ 2026-02-06 18:43 UTC (permalink / raw)
  To: Daniel Vacek, Chris Mason, Josef Bacik, Eric Biggers,
	Theodore Y. Ts'o, Jaegeuk Kim, Jens Axboe, David Sterba,
	Jonathan Corbet
  Cc: linux-block, linux-fscrypt, linux-btrfs, linux-kernel, linux-doc



On 2/6/26 10:22 AM, Daniel Vacek wrote:
> From: Josef Bacik <josef@toxicpanda.com>
> 
> Add a couple of sections to the fscrypt documentation about per-extent
> encryption.
> 
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
> Signed-off-by: Daniel Vacek <neelx@suse.com>
> ---
> 
> v5: https://lore.kernel.org/linux-btrfs/7b2cc4dd423c3930e51b1ef5dd209164ff11c05a.1706116485.git.josef@toxicpanda.com/
>  * No changes since.
> ---
>  Documentation/filesystems/fscrypt.rst | 41 +++++++++++++++++++++++++++
>  1 file changed, 41 insertions(+)
> 
> diff --git a/Documentation/filesystems/fscrypt.rst b/Documentation/filesystems/fscrypt.rst
> index 70af896822e1..8afec55dd913 100644
> --- a/Documentation/filesystems/fscrypt.rst
> +++ b/Documentation/filesystems/fscrypt.rst
> @@ -283,6 +283,21 @@ alternative master keys or to support rotating master keys.  Instead,
>  the master keys may be wrapped in userspace, e.g. as is done by the
>  `fscrypt <https://github.com/google/fscrypt>`_ tool.
>  
> +Per-extent encryption keys
> +--------------------------
> +
> +For certain file systems, such as btrfs, it's desired to derive a
> +per-extent encryption key.  This is to enable features such as snapshots
> +and reflink, where you could have different inodes pointing at the same
> +extent.  When a new extent is created fscrypt randomly generates a
> +16-byte nonce and the file system stores it along side the extent.

                                               alongside

> +Then, it uses a KDF (as described in `Key derivation function`_) to
> +derive the extent's key from the master key and nonce.
> +
> +Currently the inode's master key and encryption policy must match the
> +extent, so you cannot share extents between inodes that were encrypted
> +differently.
> +
>  DIRECT_KEY policies
>  -------------------
>  
> @@ -1488,6 +1503,27 @@ by the kernel and is used as KDF input or as a tweak to cause
>  different files to be encrypted differently; see `Per-file encryption
>  keys`_ and `DIRECT_KEY policies`_.
>  
> +Extent encryption context
> +-------------------------
> +
> +The extent encryption context mirrors the important parts of the above
> +`Encryption context`_, with a few ommisions.  The struct is defined as

                                     omissions

> +follows::
> +
> +        struct fscrypt_extent_context {
> +                u8 version;
> +                u8 encryption_mode;
> +                u8 master_key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
> +                u8 nonce[FSCRYPT_FILE_NONCE_SIZE];
> +        };
> +
> +Currently all fields much match the containing inode's encryption
> +context, with the exception of the nonce.
> +
> +Additionally extent encryption is only supported with
> +FSCRYPT_EXTENT_CONTEXT_V2 using the standard policy, all other policies

                                                policy; all other policies

> +are disallowed.
> +
>  Data path changes
>  -----------------
>  
> @@ -1511,6 +1547,11 @@ buffer.  Some filesystems, such as UBIFS, already use temporary
>  buffers regardless of encryption.  Other filesystems, such as ext4 and
>  F2FS, have to allocate bounce pages specially for encryption.
>  
> +Inline encryption is not optional for extent encryption based file
> +systems, the amount of objects required to be kept around is too much.

   systems; the amount of

> +Inline encryption handles the object lifetime details which results in a
> +cleaner implementation.
> +
>  Filename hashing and encoding
>  -----------------------------
>  

-- 
~Randy


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

* Re: [PATCH v6 42/43] btrfs: disable encryption on RAID5/6
  2026-02-06 18:23 ` [PATCH v6 42/43] btrfs: disable encryption on RAID5/6 Daniel Vacek
@ 2026-02-08 13:14   ` Chris Mason
  0 siblings, 0 replies; 77+ messages in thread
From: Chris Mason @ 2026-02-08 13:14 UTC (permalink / raw)
  To: Daniel Vacek
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel

Daniel Vacek <neelx@suse.com> wrote:
> From: Josef Bacik <josef@toxicpanda.com>
> 
> The RAID5/6 code will re-arrange bios and submit them through a
> different mechanism.  This is problematic with inline encryption as we
> have to get the bio and csum it after it's been encrypted, and the
> radi5/6 bio's don't have the btrfs_bio embedded, so we have no way to
> get the csums put on disk.
> 
> This isn't an unsolvable problem, but would require a bit of reworking.
> Since we discourage users from using this code currently simply don't
> allow encryption on RAID5/6 setups.  If there's sufficient demand in the
> future we can add the support for this.
> 

Hi everyone,

I'm running the btrfs for-next branch, along with the encryption patch set
through my AI review prompts:

https://github.com/masoncl/review-prompts

I'm working on some btrfs specific instructions, but hopefully these reviews
are already useful.  I've kept the headers to make it easier to double check
that I'm actually replying to the right email, but please let me know if any
parts of this are incorrect:


commit 6a39a21e95e116ad02c6ce9f832838acc111692f
Author: Josef Bacik <josef@toxicpanda.com>

btrfs: disable encryption on RAID5/6

The RAID5/6 code will re-arrange bios and submit them through a
different mechanism to handle parity. This is problematic with inline
encryption as we have to get the bio and csum it after it's been
encrypted, and the RAID5/6 bios don't have the btrfs_bio embedded, so
there's no way to get the csums put on disk.

> commit 6a39a21e95e116ad02c6ce9f832838acc111692f
> Author: Josef Bacik <josef@toxicpanda.com>
> Date:   Fri Feb 6 19:23:14 2026 +0100
>
>     btrfs: disable encryption on RAID5/6
>
>     The RAID5/6 code will re-arrange bios and submit them through a
>     different mechanism.  This is problematic with inline encryption as we
>     have to get the bio and csum it after it's been encrypted, and the
>     radi5/6 bio's don't have the btrfs_bio embedded, so we have no way to
                                                                       ^^^^

This isn't a bug, but there's a typo: 'radi5/6' should be 'raid5/6'.

[ ... ]

>
> diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
> index a8adf99ad0a81..1bade8fea16e8 100644
> --- a/fs/btrfs/ioctl.c
> +++ b/fs/btrfs/ioctl.c
> @@ -5198,6 +5198,10 @@ long btrfs_ioctl(struct file *file, unsigned int
>  			return -EOPNOTSUPP;
>  		if (sb_rdonly(fs_info->sb))
>  			return -EROFS;
> +		if (btrfs_fs_incompat(fs_info, RAID56)) {
> +			btrfs_warn(fs_info, "can't enable encryption with RAID5/6");
> +			return -EINVAL;
> +		}

This check prevents enabling encryption when RAID56 is already active,
but does NOT prevent converting to RAID5/6 after encryption is enabled.
The check_raid56_incompat_flag() function in fs/btrfs/volumes.c:5558
sets the RAID56 flag during balance operations without checking if
ENCRYPT is already set. This allows users to:

1. Enable encryption via FS_IOC_SET_ENCRYPTION_POLICY (sets ENCRYPT at
   line 5210 in this function)
2. Later run btrfs balance to convert to RAID5/6 (sets RAID56 flag at
   volumes.c:5558)
3. End up with both ENCRYPT and RAID56 flags set

The commit message states "simply don't allow encryption on RAID5/6
setups" which implies preventing the incompatible state entirely, not
just one entry path. The volumes.c code path needs a corresponding
check:

static void check_raid56_incompat_flag(struct btrfs_fs_info *info, u64 type)
{
	if (!(type & BTRFS_BLOCK_GROUP_RAID56_MASK))
		return;
	// MISSING: check for ENCRYPT flag here
	btrfs_set_fs_incompat(info, RAID56);
}

Without this check, data corruption can occur via the RAID5/6 bio path
that cannot handle encrypted data properly, as explained in the commit
message.

> diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
> index 4a2887147ead4..aefcbe56e85ad 100644
> --- a/fs/btrfs/super.c
> +++ b/fs/btrfs/super.c
> @@ -734,6 +734,12 @@ bool btrfs_check_options(const struct btrfs_fs_info *info,
>  	if (btrfs_check_mountopts_zoned(info, mount_opt))
>  		ret = false;
>
> +	if (btrfs_fs_incompat(info, RAID56) &&
> +	    btrfs_raw_test_opt(*mount_opt, TEST_DUMMY_ENCRYPTION)) {
> +		btrfs_err(info, "cannot use test_dummy_encryption with RAID5/6");
> +		ret = false;
> +	}
> +

Similar to the ioctl.c issue, this prevents mounting with
test_dummy_encryption when RAID56 is already active, but does NOT
prevent converting to RAID5/6 after mounting with test_dummy_encryption.

The check_raid56_incompat_flag() function in fs/btrfs/volumes.c:5558
sets the RAID56 flag during balance operations without checking if
test_dummy_encryption is in use. Since test_dummy_encryption cannot be
changed after mount (enforced by btrfs_check_test_dummy_encryption at
line 1547-1560), a user can:

1. Mount filesystem with -o test_dummy_encryption
2. Later run btrfs balance to convert to RAID5/6 (sets RAID56 flag)
3. End up with both test_dummy_encryption active and RAID56 flag set

While test_dummy_encryption is primarily a testing/development feature,
the patch explicitly protects against this combination, indicating it's
an important incompatibility. The volumes.c code path needs a check for
the TEST_DUMMY_ENCRYPTION mount option:

static void check_raid56_incompat_flag(struct btrfs_fs_info *info, u64 type)
{
	if (!(type & BTRFS_BLOCK_GROUP_RAID56_MASK))
		return;
	// MISSING: check for TEST_DUMMY_ENCRYPTION mount option
	btrfs_set_fs_incompat(info, RAID56);
}

Does this allow the same encrypted bio / RAID5/6 bio incompatibility
described in the commit message?


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

* Re: [PATCH v6 35/43] btrfs: make btrfs_ref_to_path handle encrypted filenames
  2026-02-06 18:23 ` [PATCH v6 35/43] btrfs: make btrfs_ref_to_path handle encrypted filenames Daniel Vacek
@ 2026-02-08 15:02   ` Chris Mason
  2026-03-25 15:27     ` Daniel Vacek
  0 siblings, 1 reply; 77+ messages in thread
From: Chris Mason @ 2026-02-08 15:02 UTC (permalink / raw)
  To: Daniel Vacek
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel

Daniel Vacek <neelx@suse.com> wrote:
> From: Josef Bacik <josef@toxicpanda.com>
> 
> We use this helper for inode-resolve and path resolution in send, so
> update this helper to properly decrypt any encrypted names it finds.
> 

Hi everyone,

I'm running the btrfs for-next branch, along with the encryption patch set
through my AI review prompts:

https://github.com/masoncl/review-prompts

I'm working on some btrfs specific instructions, but hopefully these reviews
are already useful.  I've kept the headers to make it easier to double check
that I'm actually replying to the right email, but please let me know if any
parts of this are incorrect:


commit 473bbb12052f731a4e2ce3c48e7d7e9f2510528b
Author: Josef Bacik <josef@toxicpanda.com>

btrfs: make btrfs_ref_to_path handle encrypted filenames

This commit updates btrfs_ref_to_path() to handle encrypted filenames by
introducing a new copy_resolved_iref_to_buf() helper that can decrypt
names when the filesystem has encryption enabled.

> diff --git a/fs/btrfs/backref.c b/fs/btrfs/backref.c
> index 9bb406f7dd302..577c3ef87791a 100644
> --- a/fs/btrfs/backref.c
> +++ b/fs/btrfs/backref.c

[ ... ]

> @@ -2107,6 +2108,39 @@ int btrfs_find_one_extref(struct btrfs_root *root, u64 inode_objectid,
>  	return ret;
>  }
>
> +static int copy_resolved_iref_to_buf(struct btrfs_root *fs_root,
> +				     struct extent_buffer *eb, char *dest,
> +				     u64 parent, unsigned long name_off,
> +				     u32 name_len, s64 *bytes_left)
> +{
> +	struct btrfs_fs_info *fs_info = fs_root->fs_info;
> +	struct fscrypt_str fname = FSTR_INIT(NULL, 0);
> +	int ret;
> +
> +	/* No encryption, just copy the name in. */
> +	if (!btrfs_fs_incompat(fs_info, ENCRYPT)) {
> +		*bytes_left -= name_len;
> +		if (*bytes_left >= 0)
> +			read_extent_buffer(eb, dest + *bytes_left, name_off, name_len);
> +		return 0;
> +	}
> +
> +	ret = fscrypt_fname_alloc_buffer(BTRFS_NAME_LEN, &fname);
> +	if (ret)
> +		return ret;
> +
> +	ret = btrfs_decrypt_name(fs_root, eb, name_off, name_len, parent, &fname);
> +	if (ret)
> +		goto out;
> +
> +	*bytes_left -= fname.len;
> +	if (*bytes_left >= 0)
> +		memcpy(dest + *bytes_left, fname.name, fname.len);
> +out:
> +	fscrypt_fname_free_buffer(&fname);
> +	return ret;
> +}
> +
>  /*
>   * this iterates to turn a name (from iref/extref) into a full filesystem path.
>   * Elements of the path are separated by '/' and the path is guaranteed to be
> @@ -2138,10 +2172,10 @@ char *btrfs_ref_to_path(struct btrfs_root *fs_root, struct btrfs_path *path,
>  		dest[bytes_left] = '\0';
>
>  	while (1) {
> -		bytes_left -= name_len;
> -		if (bytes_left >= 0)
> -			read_extent_buffer(eb, dest + bytes_left,
> -					   name_off, name_len);
> +		ret = copy_resolved_iref_to_buf(fs_root, eb, dest, parent,
> +						name_off, name_len, &bytes_left);
> +		if (ret)
> +			break;
>  		if (eb != eb_in) {
>  			if (!path->skip_locking)
>  				btrfs_tree_read_unlock(eb);

Does this code leak the extent buffer when copy_resolved_iref_to_buf()
fails? Looking at the loop structure in btrfs_ref_to_path(), when
copy_resolved_iref_to_buf() returns an error and we break at line 2178,
the cleanup code that follows never executes:

    if (eb != eb_in) {
        if (!path->skip_locking)
            btrfs_tree_read_unlock(eb);
        free_extent_buffer(eb);
    }

This can happen in subsequent iterations where eb != eb_in (the extent
buffer was detached from path->nodes[0] and set to NULL in the previous
iteration). The cleanup at the end of the function (btrfs_release_path()
at line 2216) cannot free eb since it's no longer in the path.

This could be triggered when decryption fails in copy_resolved_iref_to_buf()
due to btrfs_iget() error, fscrypt_fname_alloc_buffer() ENOMEM, or
fscrypt_prepare_readdir() failure on an encrypted filesystem.

Before this patch, the name copy operation (read_extent_buffer) could not
fail, so the cleanup always happened before any error check.


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

* Re: [PATCH v6 33/43] btrfs: implement read repair for encryption
  2026-02-06 18:23 ` [PATCH v6 33/43] btrfs: implement read repair for encryption Daniel Vacek
@ 2026-02-08 15:08   ` Chris Mason
  2026-03-25 14:17     ` Daniel Vacek
  0 siblings, 1 reply; 77+ messages in thread
From: Chris Mason @ 2026-02-08 15:08 UTC (permalink / raw)
  To: Daniel Vacek
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel

Daniel Vacek <neelx@suse.com> wrote:
> From: Josef Bacik <josef@toxicpanda.com>
> 
> In order to do read repair we will allocate sectorsize bio's and read
> them one at a time, repairing any sectors that don't match their csum.
> In order to do this we re-submit the IO's after it's failed, and at this
> point we still need the fscrypt_extent_info for these new bio's.
> 
> Add the fscrypt_extent_info to the read part of the union in the
> btrfs_bio, and then pass this through all the places where we do reads.
> Additionally add the orig_start, because we need to be able to put the
> correct extent offset for the encryption context.
> 
> With these in place we can utilize the normal read repair path.  The
> only exception is that the actual repair of the bad copies has to be
> triggered from the ->process_bio callback, because this is the encrypted
> data.  If we waited until the end_io we would have the decrypted data
> and we don't want to write that to the disk.  This is the only change to
> the normal read repair path, we trigger the fixup of the broken sectors
> in ->process_bio, and then we skip that part if we successfully repair
> the sector in ->process_bio once we get to the endio.

Hi everyone,

I'm running the btrfs for-next branch, along with the encryption patch set
through my AI review prompts:

https://github.com/masoncl/review-prompts

I'm working on some btrfs specific instructions, but hopefully these reviews
are already useful.  I've kept the headers to make it easier to double check
that I'm actually replying to the right email, but please let me know if any
parts of this are incorrect:

commit 096c17a6a951ed0c1100e863c4ee3a95bab0ad51
Author: Josef Bacik <josef@toxicpanda.com>

btrfs: implement read repair for encryption

This commit implements read repair functionality for encrypted btrfs
files by adding fscrypt_extent_info tracking to the btrfs_bio structure
and handling repair operations before decryption occurs.

> diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c
> index c28f3f53bad28..e82de96b533cb 100644
> --- a/fs/btrfs/bio.c
> +++ b/fs/btrfs/bio.c

[ ... ]

> @@ -293,6 +323,13 @@ static struct btrfs_failed_bio *repair_one_sector(struct btrfs_bio *failed_bbio,
>  	repair_bbio = btrfs_bio(repair_bio);
>  	btrfs_bio_init(repair_bbio, failed_bbio->inode, failed_bbio->file_offset + bio_offset,
>  		       NULL, fbio);
> +	repair_bbio->fscrypt_info = fscrypt_get_extent_info(failed_bbio->fscrypt_info);
> +	repair_bbio->orig_start = failed_bbio->orig_start;
> +
> +	btrfs_set_bio_crypt_ctx_from_extent(repair_bio, repair_bbio->inode,
> +					    failed_bbio->fscrypt_info,
> +					    repair_bbio->file_offset -
> +					    failed_bbio->orig_start);
>
>  	mirror = next_repair_mirror(fbio, failed_bbio->mirror_num);
>  	btrfs_debug(fs_info, "submitting repair read to mirror %d", mirror);

[ ... ]

> @@ -214,17 +244,17 @@ static void btrfs_end_repair_bio(struct btrfs_bio *repair_bbio,
>  			goto done;
>  		}
>
> +		btrfs_set_bio_crypt_ctx_from_extent(&repair_bbio->bio,
> +						    repair_bbio->inode,
> +						    repair_bbio->fscrypt_info,
> +						    repair_bbio->file_offset -
> +						    repair_bbio->orig_start);
> +
>  		btrfs_submit_bbio(repair_bbio, mirror);
>  		return;
>  	}
>
> -	do {
> -		mirror = prev_repair_mirror(fbio, mirror);
> -		btrfs_repair_io_failure(fs_info, btrfs_ino(inode),
> -				  repair_bbio->file_offset, fs_info->sectorsize,
> -				  logical, paddrs, step, mirror);
> -	} while (mirror != fbio->bbio->mirror_num);
> -
> +	handle_repair(repair_bbio, paddrs);
>  done:
>  	btrfs_repair_done(fbio);
>  	bio_put(&repair_bbio->bio);

Does this leak the fscrypt_extent_info reference acquired at line 326?
In repair_one_sector(), repair_bbio->fscrypt_info is set via
fscrypt_get_extent_info(failed_bbio->fscrypt_info), but when
btrfs_end_repair_bio() completes at the done label, it calls
bio_put(&repair_bbio->bio) without calling
fscrypt_put_extent_info(repair_bbio->fscrypt_info) first.

> diff --git a/fs/btrfs/bio.h b/fs/btrfs/bio.h
> index 456d32db9e9e9..7a8ff4378cba0 100644
> --- a/fs/btrfs/bio.h
> +++ b/fs/btrfs/bio.h

[ ... ]

> diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c
> index 5a39f707c2933..afc461d7642e4 100644
> --- a/fs/btrfs/compression.c
> +++ b/fs/btrfs/compression.c
> @@ -562,6 +562,8 @@ void btrfs_submit_compressed_read(struct btrfs_bio *bbio)
>  	cb->compress_type = btrfs_extent_map_compression(em);
>  	cb->orig_bbio = bbio;
>  	cb->bbio.csum_search_commit_root = bbio->csum_search_commit_root;
> +	cb->bbio.fscrypt_info = fscrypt_get_extent_info(em->fscrypt_info);
> +	cb->bbio.orig_start = 0;
>
>  	btrfs_set_bio_crypt_ctx_from_extent(&cb->bbio.bio, inode, em->fscrypt_info, 0);
>  	btrfs_free_extent_map(em);

Does this leak the fscrypt_extent_info reference in the compressed read
path? At line 565, fscrypt_get_extent_info() acquires a reference and
stores it in cb->bbio.fscrypt_info. Looking at end_bbio_compressed_read()
in compression.c, it calls btrfs_bio_end_io(cb->orig_bbio, status) which
releases the ORIGINAL bbio's fscrypt_info, but then calls bio_put(cb_bio)
without calling fscrypt_put_extent_info(cb->bbio.fscrypt_info). The error
path in cleanup_compressed_bio() also calls bio_put() without releasing
the cb->bbio.fscrypt_info reference.

[ ... ]



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

* Re: [PATCH v6 32/43] btrfs: implement process_bio cb for fscrypt
  2026-02-06 18:23 ` [PATCH v6 32/43] btrfs: implement process_bio cb for fscrypt Daniel Vacek
@ 2026-02-08 15:10   ` Chris Mason
  2026-03-24  9:36     ` Daniel Vacek
  0 siblings, 1 reply; 77+ messages in thread
From: Chris Mason @ 2026-02-08 15:10 UTC (permalink / raw)
  To: Daniel Vacek
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel

Daniel Vacek <neelx@suse.com> wrote:
> From: Josef Bacik <josef@toxicpanda.com>
> 
> We are going to be checksumming the encrypted data, so we have to
> implement the ->process_bio fscrypt callback.  This will provide us with
> the original bio and the encrypted bio to do work on.  For WRITE's this
> will happen after the encrypted bio has been encrypted.  For READ's this
> will happen after the read has completed and before the decryption step
> is done.
> 
> For write's this is straightforward, we can just pass in the encrypted
> bio to btrfs_csum_one_bio and then the csums will be added to the bbio
> as normal.
> 
> For read's this is relatively straightforward, but requires some care.
> We assume (because that's how it works currently) that the encrypted bio
> match the original bio, this is important because we save the iter of
> the bio before we submit.  If this changes in the future we'll need a
> hook to give us the bi_iter of the decryption bio before it's submitted.
> We check the csums before decryption.  If it doesn't match we simply
> error out and we let the normal path handle the repair work.

Hi everyone,

I'm running the btrfs for-next branch, along with the encryption patch set
through my AI review prompts:

https://github.com/masoncl/review-prompts

I'm working on some btrfs specific instructions, but hopefully these reviews
are already useful.  I've kept the headers to make it easier to double check
that I'm actually replying to the right email, but please let me know if any
parts of this are incorrect:

commit 2b25418669e46c939ff864b1144e2c27d618920c
Author: Josef Bacik <josef@toxicpanda.com>

btrfs: implement process_bio cb for fscrypt

This commit implements the ->process_bio fscrypt callback for btrfs,
enabling checksum verification of encrypted data before decryption and
checksum generation after encryption.

> diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c
> index 3b420866f3b82..c28f3f53bad28 100644
> --- a/fs/btrfs/bio.c
> +++ b/fs/btrfs/bio.c
> @@ -300,6 +300,34 @@ static struct btrfs_failed_bio *repair_one_sector(struct btrfs_bio *failed_bbio,
>  	return fbio;
>  }
>
> +blk_status_t btrfs_check_encrypted_read_bio(struct btrfs_bio *bbio, struct bio *enc_bio)
> +{
> +	struct btrfs_inode *inode = bbio->inode;
> +	struct btrfs_fs_info *fs_info = inode->root->fs_info;
> +	struct bvec_iter iter = bbio->saved_iter;
> +	struct btrfs_device *dev = bbio->bio.bi_private;
> +	const u32 step = min(fs_info->sectorsize, PAGE_SIZE);
> +	const u32 nr_steps = iter.bi_size / step;
> +	phys_addr_t paddrs[BTRFS_MAX_BLOCKSIZE / PAGE_SIZE];

Can paddrs[] overflow here? BTRFS_MAX_BLOCKSIZE is 64KB, so the array
has 16 elements (64KB / 4KB). However, nr_steps is calculated from
iter.bi_size which can be much larger than 64KB for merged bios.

If a bio has bi_size > 64KB, the loop below will write beyond the array
bounds when filling paddrs[slot].

> +	phys_addr_t paddr;
> +	unsigned int slot = 0;
> +
> +	/*
> +	 * We have to use a copy of iter in case there's an error,
> +	 * btrfs_check_read_bio will handle submitting the repair bios.
> +	 */
> +	btrfs_bio_for_each_block(paddr, enc_bio, &iter, step) {
> +		ASSERT(slot < nr_steps);
> +		paddrs[slot] = paddr;
> +		slot++;
> +	}
> +	if (!btrfs_data_csum_ok(bbio, dev, 0, paddrs))

Does this verify checksums for all sectors in the bio? Looking at
btrfs_data_csum_ok() in inode.c, it verifies one sector's worth of data
(one blocksize). The function is called with bio_offset=0, meaning it
only checksums the first sector.

However, the bio can contain multiple sectors (iter.bi_size could be
much larger than one sectorsize). If the bio has multiple sectors,
shouldn't this call btrfs_data_csum_ok() for each sector, or pass the
actual bio size so all sectors are verified?

The existing btrfs_check_read_bio() function loops through all sectors
and verifies each one individually.

> +			return BLK_STS_IOERR;
> +
> +	bbio->csum_ok = true;
> +	return BLK_STS_OK;
> +}

[ ... ]



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

* Re: [PATCH v6 28/43] btrfs: populate ordered_extent with the orig offset
  2026-02-06 18:23 ` [PATCH v6 28/43] btrfs: populate ordered_extent with the orig offset Daniel Vacek
@ 2026-02-08 15:12   ` Chris Mason
  2026-03-03 13:42     ` Daniel Vacek
  0 siblings, 1 reply; 77+ messages in thread
From: Chris Mason @ 2026-02-08 15:12 UTC (permalink / raw)
  To: Daniel Vacek
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel

Daniel Vacek <neelx@suse.com> wrote:
> From: Josef Bacik <josef@toxicpanda.com>
> 
> For extent encryption we have to use a logical block nr as input for the
> IV.  For btrfs we're using the offset into the extent we're operating
> on.  For most ordered extents this is the same as the file_offset,
> however for prealloc and NOCOW we have to use the original offset.
> 
> Add this as an argument and plumb it through everywhere, this will be
> used when setting up the bio.
> 

Hi everyone,

I'm running the btrfs for-next branch, along with the encryption patch set
through my AI review prompts:

https://github.com/masoncl/review-prompts

I'm working on some btrfs specific instructions, but hopefully these reviews
are already useful.  I've kept the headers to make it easier to double check
that I'm actually replying to the right email, but please let me know if any
parts of this are incorrect:

commit 0e7f6a617a2642bbdf97bcd639fb887fa88189ad
Author: Josef Bacik <josef@toxicpanda.com>

btrfs: populate ordered_extent with the orig offset

This commit adds orig_offset tracking to btrfs_ordered_extent and
btrfs_file_extent structures for extent encryption support, where the
offset into the extent is needed for IV generation.

> diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
> index d3789109ca851..2d89ac05b1b38 100644
> --- a/fs/btrfs/direct-io.c
> +++ b/fs/btrfs/direct-io.c
> @@ -155,6 +155,7 @@ static struct extent_map *btrfs_create_dio_extent(struct btrfs_inode *inode,
>  		file_extent->fscrypt_info = em->fscrypt_info;
>  	}
>
> +	file_extent->orig_offset = start - file_extent->offset;
>  	ordered = btrfs_alloc_ordered_extent(inode, start, file_extent,
>  					     (1U << type) |
>  					     (1U << BTRFS_ORDERED_DIRECT));

[ ... ]

> diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
> index fbb968d4010b7..8078049f690e1 100644
> --- a/fs/btrfs/inode.c
> +++ b/fs/btrfs/inode.c

[ ... ]

> @@ -2296,6 +2298,8 @@ static noinline int run_delalloc_nocow(struct btrfs_inode *inode,
>  			cow_start = (u64)-1;
>  		}
>
> +		nocow_args.file_extent.orig_offset =
> +			found_key.offset - nocow_args.file_extent.offset;
>  		ret = nocow_one_range(inode, locked_folio, &cached_state,
>  				      &nocow_args, cur_offset,
>  				      extent_type == BTRFS_FILE_EXTENT_PREALLOC);

Should this calculation use cur_offset instead of found_key.offset?

Looking at the DIO path in btrfs_create_dio_extent() (direct-io.c line
158), the orig_offset is calculated as:

    file_extent->orig_offset = start - file_extent->offset;

where 'start' is the current write position. In run_delalloc_nocow(),
found_key.offset represents where the extent item is stored, while
cur_offset represents the current write position (analogous to 'start'
in the DIO path).

When writing to a position within an existing extent (not at its start),
using found_key.offset - file_extent.offset could produce incorrect
values. For consistency with the DIO path, would the formula be:

    nocow_args.file_extent.orig_offset =
        cur_offset - nocow_args.file_extent.offset;

This would ensure that NOCOW writes calculate orig_offset the same way
as DIO writes, which matters for encryption IV generation where an
incorrect offset would cause data corruption on reads.


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

* Re: [PATCH v6 24/43] btrfs: add extent encryption context tree item type
  2026-02-06 18:22 ` [PATCH v6 24/43] btrfs: add extent encryption context tree item type Daniel Vacek
@ 2026-02-08 15:16   ` Chris Mason
  2026-02-18 17:25     ` Daniel Vacek
  0 siblings, 1 reply; 77+ messages in thread
From: Chris Mason @ 2026-02-08 15:16 UTC (permalink / raw)
  To: Daniel Vacek
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel

Daniel Vacek <neelx@suse.com> wrote:
> From: Josef Bacik <josef@toxicpanda.com>
> 
> The fscrypt encryption context will be stored as a new tree item type.
> This gives us flexibility to include different things in the future.
> 
> Also update the tree-checker to validate the new item type.

Hi everyone,

I'm running the btrfs for-next branch, along with the encryption patch set
through my AI review prompts:

https://github.com/masoncl/review-prompts

I'm working on some btrfs specific instructions, but hopefully these reviews
are already useful.  I've kept the headers to make it easier to double check
that I'm actually replying to the right email, but please let me know if any
parts of this are incorrect:

commit 203bfb91a4db9c826fa7f72034c13a1810a1b605
Author: Josef Bacik <josef@toxicpanda.com>

btrfs: add extent encryption context tree item type

The fscrypt encryption context will be stored as a new tree item type.
This gives us flexibility to include different things in the future.
Also update the tree-checker to validate the new item type.

> diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
> index 3c0db279f592e..639462164d089 100644
> --- a/fs/btrfs/file.c
> +++ b/fs/btrfs/file.c

[ ... ]

> @@ -496,6 +503,60 @@ int btrfs_drop_extents(struct btrfs_trans_handle *trans,
>  		args->extent_inserted = true;
>  	}
>
> +	if (first_ctx <= last_ctx) {
> +		int slot, nritems;
> +
> +		btrfs_release_path(path);
> +
> +		key.objectid = ino;
> +		key.type = BTRFS_FSCRYPT_CTX_KEY;
> +		key.offset = first_ctx;
> +
> +		ret = btrfs_search_slot(trans, root, &key, path, modify_tree, !!modify_tree);
> +		if (ret < 0)
> +			goto out_ctx;

[ ... ]

> +out_ctx:
> +		if (args->path && args->extent_inserted) {
> +			btrfs_release_path(path);
> +
> +			key.objectid = ino;
> +			key.type = BTRFS_EXTENT_DATA_KEY;
> +			key.offset = args->start;
> +			ret = btrfs_search_slot(trans, root, &key, path, 0, 0);
> +		}
> +	}
> +

Can btrfs_search_slot() at line 556 overwrite an error that occurred
earlier in the out_ctx path?

If btrfs_search_slot() at line 515 fails and execution jumps to out_ctx,
but args->path and args->extent_inserted are both true, the second
btrfs_search_slot() call will overwrite the original error stored in ret.

If this second search succeeds (returns 0 or >0), the function returns
success to the caller despite the fscrypt context cleanup having failed.
This could lead to inconsistent filesystem state where extent data was
inserted but the corresponding fscrypt context items were not properly
cleaned up.

> diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
> index 9675dbcd78a3b..776901f297fea 100644
> --- a/fs/btrfs/tree-checker.c
> +++ b/fs/btrfs/tree-checker.c

[ ... ]

> +static int check_fscrypt_context(struct extent_buffer *leaf,
> +				 struct btrfs_key *key, int slot,
> +				 struct btrfs_key *prev_key)
> +{
> +	u32 sectorsize = leaf->fs_info->sectorsize;
> +	u32 item_size = btrfs_item_size(leaf, slot);
> +
> +	if (unlikely(!IS_ALIGNED(key->offset, sectorsize))) {
> +		file_extent_err(leaf, slot,
> +"unaligned file_offset for encryption context, have %llu should be aligned to %u",
> +			key->offset, sectorsize);
> +		return -EUCLEAN;
> +	}
> +
> +	/*
> +	 * Previous key must have the same key->objectid (ino).
> +	 * It can be XATTR_ITEM, INODE_ITEM or just another EXTENT_DATA.
> +	 * But if objectids mismatch, it means we have a missing
> +	 * INODE_ITEM.
> +	 */

This isn't a bug, but the comment says "It can be XATTR_ITEM, INODE_ITEM
or just another EXTENT_DATA" - can EXTENT_DATA actually be the previous
key type?

For BTRFS_FSCRYPT_CTX_KEY (type=42), EXTENT_DATA (type=108) cannot be a
previous key because keys are sorted by (objectid, type, offset) and
108 > 42.

The correct previous key types would be: XATTR_ITEM, INODE_ITEM,
FSCRYPT_INODE_CTX_KEY, or another FSCRYPT_CTX_KEY.

> @@ -214,6 +248,7 @@ static int check_extent_data_item(struct extent_buffer *leaf,
>  	u32 item_size = btrfs_item_size(leaf, slot);
>  	u64 extent_end;
>  	u8 policy;
> +	u8 fe_type;
>
>  	if (unlikely(!IS_ALIGNED(key->offset, sectorsize))) {
>  		file_extent_err(leaf, slot,

[ ... ]

> @@ -298,6 +333,19 @@ static int check_extent_data_item(struct extent_buffer *leaf,
>  		return 0;
>  	}
>
> +	if (policy == BTRFS_ENCRYPTION_FSCRYPT) {
> +		/*
> +		 * Only regular and prealloc extents should have an encryption
> +		 * context.
> +		 */
> +		if (unlikely(fe_type != BTRFS_FILE_EXTENT_REG &&
> +			     fe_type != BTRFS_FILE_EXTENT_PREALLOC)) {
> +			file_extent_err(leaf, slot,
> +		"invalid type for encrypted file extent, have %u", fe_type);
> +			return -EUCLEAN;
> +		}
> +	}

Can this validation check detect encrypted inline extents? Looking at
the function flow, inline extents (BTRFS_FILE_EXTENT_INLINE) are handled
at lines 310-333 and return before reaching this check.

If a corrupted filesystem has an encrypted inline extent, it would pass
tree-checker validation. Should the encryption check be moved to also
cover the inline extent path, or should inline extents explicitly reject
BTRFS_ENCRYPTION_FSCRYPT?


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

* Re: [PATCH v6 20/43] btrfs: add fscrypt_info and encryption_type to ordered_extent
  2026-02-06 18:22 ` [PATCH v6 20/43] btrfs: add fscrypt_info and encryption_type to ordered_extent Daniel Vacek
@ 2026-02-08 15:18   ` Chris Mason
  2026-02-18 15:29     ` Daniel Vacek
  0 siblings, 1 reply; 77+ messages in thread
From: Chris Mason @ 2026-02-08 15:18 UTC (permalink / raw)
  To: Daniel Vacek
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel

Daniel Vacek <neelx@suse.com> wrote:
> From: Josef Bacik <josef@toxicpanda.com>
> 
> We're going to need these to update the file extent items once the
> writes are complete.  Add them and add the pieces necessary to assign
> them and free everything.
> 

Hi everyone,

I'm running the btrfs for-next branch, along with the encryption patch set
through my AI review prompts:

https://github.com/masoncl/review-prompts

I'm working on some btrfs specific instructions, but hopefully these reviews
are already useful.  I've kept the headers to make it easier to double check
that I'm actually replying to the right email, but please let me know if any
parts of this are incorrect:

commit 61cd57301840de69a3f6573374d41ad4b4bcf348
Author: Josef Bacik <josef@toxicpanda.com>

btrfs: add fscrypt_info and encryption_type to ordered_extent

This commit adds encryption-related fields (encryption_type and
fscrypt_info) to struct btrfs_ordered_extent to support fscrypt
integration in btrfs.

> diff --git a/fs/btrfs/ordered-data.c b/fs/btrfs/ordered-data.c
> index 5df02c707aee6..7a5701937184e 100644
> --- a/fs/btrfs/ordered-data.c
> +++ b/fs/btrfs/ordered-data.c
> @@ -192,6 +192,7 @@ static struct btrfs_ordered_extent *alloc_ordered_extent(
>  	}
>  	entry->inode = inode;
>  	entry->compress_type = compress_type;
> +	entry->encryption_type = BTRFS_ENCRYPTION_NONE;
>  	entry->truncated_len = (u64)-1;
>  	entry->qgroup_rsv = qgroup_rsv;

This isn't a bug, but the fscrypt_info field isn't explicitly initialized
to NULL here. While kmem_cache_zalloc() at line 175 does zero the memory,
all the other important fields in this function are explicitly initialized
(compress_type, encryption_type, truncated_len, qgroup_rsv, refs). Would
adding an explicit 'entry->fscrypt_info = NULL;' improve consistency?

>  	entry->flags = flags;
> @@ -630,6 +631,7 @@ void btrfs_put_ordered_extent(struct btrfs_ordered_extent *entry)
>  		btrfs_add_delayed_iput(entry->inode);
>  		list_for_each_entry_safe(sum, tmp, &entry->list, list)
>  			kvfree(sum);
> +		fscrypt_put_extent_info(entry->fscrypt_info);
>  		kmem_cache_free(btrfs_ordered_extent_cache, entry);
>  	}
>  }
> diff --git a/fs/btrfs/ordered-data.h b/fs/btrfs/ordered-data.h
> index 1e6b0b182b295..ce19198e7f844 100644
> --- a/fs/btrfs/ordered-data.h
> +++ b/fs/btrfs/ordered-data.h
> @@ -124,6 +124,9 @@ struct btrfs_ordered_extent {
>  	/* compression algorithm */
>  	int compress_type;
>
> +	/* encryption mode */
> +	u8 encryption_type;
> +

This isn't a bug, but placing the u8 encryption_type field between two int
fields creates 3 bytes of padding. This is minor, but in a filesystem with
many ordered extents, it could add up. The current placement does have the
advantage of grouping encryption-related fields together logically, though.

>  	/* Qgroup reserved space */
>  	int qgroup_rsv;
>
> @@ -133,6 +136,9 @@ struct btrfs_ordered_extent {
>  	/* the inode we belong to */
>  	struct btrfs_inode *inode;
>
> +	/* the fscrypt_info for this extent, if necessary */
> +	struct fscrypt_extent_info *fscrypt_info;
> +
>  	/* list of checksums for insertion when the extent io is done */
>  	struct list_head list;

How does btrfs_split_ordered_extent() handle the new fscrypt_info field?
Looking at that function in ordered-data.c, it calls alloc_ordered_extent()
which initializes encryption_type to BTRFS_ENCRYPTION_NONE and fscrypt_info
to NULL. If the original ordered extent has encryption_type set to
BTRFS_ENCRYPTION_FSCRYPT with a non-NULL fscrypt_info pointer, the split
creates an inconsistency where the two extents representing parts of the
same encrypted data have different encryption metadata.

The function already has an assertion preventing splits of compressed
extents at line 1244:

    ASSERT(!(flags & (1U << BTRFS_ORDERED_COMPRESSED)))

Should there be similar protection for encrypted extents, or if splits must
be supported, should the function call fscrypt_get_extent_info() to
properly handle the reference count and copy the encryption_type to the new
extent?


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

* Re: [PATCH v6 16/43] btrfs: select encryption dependencies if FS_ENCRYPTION
  2026-02-06 18:22 ` [PATCH v6 16/43] btrfs: select encryption dependencies if FS_ENCRYPTION Daniel Vacek
@ 2026-02-08 15:22   ` Chris Mason
  2026-02-18 15:02     ` Daniel Vacek
  0 siblings, 1 reply; 77+ messages in thread
From: Chris Mason @ 2026-02-08 15:22 UTC (permalink / raw)
  To: Daniel Vacek
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel

Daniel Vacek <neelx@suse.com> wrote:
> From: Josef Bacik <josef@toxicpanda.com>
> 
> We need this to make sure the appropriate encryption algorithms are
> turned on in our config if we have FS_ENCRYPTION enabled, and
> additionally we only support inline encryption with the fallback block
> crypto, so we need to make sure we pull in those dependencies.

Hi everyone,

I'm running the btrfs for-next branch, along with the encryption patch set
through my AI review prompts:

https://github.com/masoncl/review-prompts

I'm working on some btrfs specific instructions, but hopefully these reviews
are already useful.  I've kept the headers to make it easier to double check
that I'm actually replying to the right email, but please let me know if any
parts of this are incorrect:

commit 0ab45d92f6f53371d87e1a302f09b56218d02546
Author: Josef Bacik <josef@toxicpanda.com>

btrfs: select encryption dependencies if FS_ENCRYPTION

This commit adds select statements for encryption-related dependencies when
FS_ENCRYPTION is enabled, including encryption algorithms, inline crypto, and
the block layer inline encryption fallback.

> diff --git a/fs/btrfs/Kconfig b/fs/btrfs/Kconfig
> index ede184b6eda16..216a5707b099c 100644
> --- a/fs/btrfs/Kconfig
> +++ b/fs/btrfs/Kconfig
> @@ -16,6 +16,9 @@ config BTRFS_FS
>  	select RAID6_PQ
>  	select XOR_BLOCKS
>  	select XXHASH
> +	select FS_ENCRYPTION_ALGS if FS_ENCRYPTION
> +	select FS_ENCRYPTION_INLINE_CRYPT if FS_ENCRYPTION
> +	select BLK_INLINE_ENCRYPTION_FALLBACK if FS_ENCRYPTION

Are the dependencies for FS_ENCRYPTION_INLINE_CRYPT and
BLK_INLINE_ENCRYPTION_FALLBACK satisfied?

Looking at fs/crypto/Kconfig:38, FS_ENCRYPTION_INLINE_CRYPT depends on
'FS_ENCRYPTION && BLK_INLINE_ENCRYPTION'. Similarly, in block/Kconfig:204,
BLK_INLINE_ENCRYPTION_FALLBACK depends on 'BLK_INLINE_ENCRYPTION'.

When BTRFS_FS=y with FS_ENCRYPTION=y but BLK_INLINE_ENCRYPTION=n (which is
valid), Kconfig will generate warnings:

  WARNING: unmet direct dependencies detected for BLK_INLINE_ENCRYPTION_FALLBACK
  WARNING: unmet direct dependencies detected for FS_ENCRYPTION_INLINE_CRYPT

The build will fail when compiling fs/crypto/inline_crypt.c because
BLK_INLINE_ENCRYPTION guards the required function definitions and struct
members:

  fs/crypto/inline_crypt.c:259:15: error: implicit declaration of function
      'blk_crypto_derive_sw_secret'
  fs/crypto/inline_crypt.c:317:9: error: implicit declaration of function
      'bio_crypt_set_ctx'
  fs/crypto/inline_crypt.c:418:45: error: 'struct bio' has no member named
      'bi_crypt_context'

Should BLK_INLINE_ENCRYPTION also be selected when FS_ENCRYPTION is enabled?

  select BLK_INLINE_ENCRYPTION if FS_ENCRYPTION
  select FS_ENCRYPTION_ALGS if FS_ENCRYPTION
  select FS_ENCRYPTION_INLINE_CRYPT if FS_ENCRYPTION
  select BLK_INLINE_ENCRYPTION_FALLBACK if FS_ENCRYPTION

>  	depends on PAGE_SIZE_LESS_THAN_256KB
>
>  	help


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

* Re: [PATCH v6 14/43] btrfs: handle nokey names
  2026-02-06 18:22 ` [PATCH v6 14/43] btrfs: handle " Daniel Vacek
@ 2026-02-08 15:28   ` Chris Mason
  2026-02-18 14:50     ` Daniel Vacek
  0 siblings, 1 reply; 77+ messages in thread
From: Chris Mason @ 2026-02-08 15:28 UTC (permalink / raw)
  To: Daniel Vacek
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel, Sweet Tea Dorminy

Daniel Vacek <neelx@suse.com> wrote:
> From: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
> 
> For encrypted or unencrypted names, we calculate the offset for the dir
> item by hashing the name for the dir item. However, this doesn't work
> for a long nokey name, where we do not have the complete ciphertext.
> Instead, fscrypt stores the filesystem-provided hash in the nokey name,
> and we can extract it from the fscrypt_name structure in such a case.
> 
> Additionally, for nokey names, if we find the nokey name on disk we can
> update the fscrypt_name with the disk name, so add that to searching for
> diritems.

Hi everyone,

I'm running the btrfs for-next branch, along with the encryption patch set
through my AI review prompts:

https://github.com/masoncl/review-prompts

I'm working on some btrfs specific instructions, but hopefully these reviews
are already useful.  I've kept the headers to make it easier to double check
that I'm actually replying to the right email, but please let me know if any
parts of this are incorrect:

commit fabad1c3ea82184fba8e78ce1da1b2fbe8c34fae
Author: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>

btrfs: handle nokey names

This commit adds support for handling nokey names in btrfs, enabling
proper hash calculation and disk name extraction for long encrypted
filenames when the encryption key is unavailable.

> diff --git a/fs/btrfs/dir-item.c b/fs/btrfs/dir-item.c
> index 6e10dd4a4e9e1..75c213bd826e9 100644
> --- a/fs/btrfs/dir-item.c
> +++ b/fs/btrfs/dir-item.c
> @@ -228,6 +228,27 @@ struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans,
>  	return di;
>  }
>
> +/*
> + * If appropriate, populate the disk name for a fscrypt_name looked up without
> + * a key.
> + *
> + * @path:	The path to the extent buffer in which the name was found.
> + * @di:		The dir item corresponding.
> + * @fname:	The fscrypt_name to perhaps populate.
> + *
> + * Returns: 0 if the name is already populated or the dir item doesn't exist
> + * or the name was successfully populated, else an error code.
> + */
> +static int ensure_disk_name_from_dir_item(struct btrfs_path *path,
> +					  struct btrfs_dir_item *di,
> +					  struct fscrypt_name *name)
> +{
> +	if (name->disk_name.name || !di)
> +		return 0;
> +
> +	return btrfs_fscrypt_get_disk_name(path->nodes[0], di, &name->disk_name);
> +}
> +

[ ... ]

> @@ -263,6 +287,8 @@ struct btrfs_dir_item *btrfs_lookup_dir_item_fname(struct btrfs_trans_handle *tr
>
>  	if (ret == -ENOENT || (di && IS_ERR(di) && PTR_ERR(di) == -ENOENT))
>  		return NULL;
> +	if (ret == 0)
> +		ret = ensure_disk_name_from_dir_item(path, di, name);
>  	if (ret < 0)
>  		di = ERR_PTR(ret);
>

Does ensure_disk_name_from_dir_item() leak memory when it allocates a
disk name? Looking at the call chain:

btrfs_unlink_subvol() at inode.c:4583 calls btrfs_lookup_dir_item_fname(),
which calls ensure_disk_name_from_dir_item(), which calls
btrfs_fscrypt_get_disk_name() at fscrypt.c:34, allocating memory via
kmalloc() and storing it in name->disk_name.name.

However, btrfs_unlink_subvol() cleans up with fscrypt_free_filename() at
include/linux/fscrypt.h:503, which only frees name->crypto_buf.name, not
name->disk_name.name.

The same issue appears in btrfs_search_dir_index_item() at line 405, which
also calls ensure_disk_name_from_dir_item().

For nokey encrypted filenames, wouldn't this leak memory on every
successful lookup?


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

* Re: [PATCH v6 13/43] btrfs: adapt readdir for encrypted and nokey names
  2026-02-06 18:22 ` [PATCH v6 13/43] btrfs: adapt readdir for encrypted and nokey names Daniel Vacek
@ 2026-02-08 15:35   ` Chris Mason
  2026-02-18 14:05     ` Daniel Vacek
  0 siblings, 1 reply; 77+ messages in thread
From: Chris Mason @ 2026-02-08 15:35 UTC (permalink / raw)
  To: Daniel Vacek
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel, Omar Sandoval, Sweet Tea Dorminy

Daniel Vacek <neelx@suse.com> wrote:
> From: Omar Sandoval <osandov@osandov.com>
> 
> Deleting an encrypted file must always be permitted, even if the user
> does not have the appropriate key. Therefore, for listing an encrypted
> directory, so-called 'nokey' names are provided, and these nokey names
> must be sufficient to look up and delete the appropriate encrypted
> files. See 'struct fscrypt_nokey_name' for more information on the
> format of these names.

Hi everyone,

I'm running the btrfs for-next branch, along with the encryption patch set
through my AI review prompts:

https://github.com/masoncl/review-prompts

I'm working on some btrfs specific instructions, but hopefully these reviews
are already useful.  I've kept the headers to make it easier to double check
that I'm actually replying to the right email, but please let me know if any
parts of this are incorrect:

commit e264482bd61ca8096981d81cc5e7c839284ac969
Author: Omar Sandoval <osandov@osandov.com>

btrfs: adapt readdir for encrypted and nokey names

This commit adapts the btrfs readdir implementation to handle encrypted
filenames and nokey names, enabling directory listing and deletion of
encrypted files even without the encryption key.

> diff --git a/fs/btrfs/delayed-inode.c b/fs/btrfs/delayed-inode.c
> index 1739a0b29c49d..c19213fab3dda 100644
> --- a/fs/btrfs/delayed-inode.c
> +++ b/fs/btrfs/delayed-inode.c

[ ... ]

> @@ -1820,7 +1823,25 @@ bool btrfs_readdir_delayed_dir_index(struct dir_context *ctx,
>  		d_type = fs_ftype_to_dtype(btrfs_dir_flags_to_ftype(di->type));
>  		btrfs_disk_key_to_cpu(&location, &di->location);
>
> -		over = !dir_emit(ctx, name, name_len, location.objectid, d_type);
> +		if (di->type & BTRFS_FT_ENCRYPTED) {
> +			int ret;
> +			struct fscrypt_str iname = FSTR_INIT(name, name_len);
> +
> +			fstr->len = fstr_len;
> +			/*
> +			 * The hash is only used when the encryption key is not
> +			 * available. But if we have delayed insertions, then we
> +			 * must have the encryption key available or we wouldn't
> +			 * have been able to create entries in the directory.
> +			 * So, we don't calculate the hash.
> +			 */
> +			ret = fscrypt_fname_disk_to_usr(inode, 0, 0, &iname, fstr);
> +			if (ret)
> +				return ret;
> +			over = !dir_emit(ctx, fstr->name, fstr->len, location.objectid, d_type);
> +		} else {
> +			over = !dir_emit(ctx, name, name_len, location.objectid, d_type);
> +		}

Does the return statement at line 1840 cause silent error loss?

The function btrfs_readdir_delayed_dir_index returns bool, but
'return ret;' returns an int error code from fscrypt_fname_disk_to_usr.
Since ret is negative on error (e.g., -EUCLEAN from fname_decrypt
failures), this converts to 'true' in bool context.

The caller btrfs_real_readdir at inode.c:6410 interprets true as "stop
iteration normally":

    if (btrfs_readdir_delayed_dir_index(inode, &fstr, ctx, &ins_list))
        goto nopos;

The nopos label at line 6417 sets ret = 0, which means readdir()
completes successfully with incomplete results when crypto operations
fail on encrypted directory entries.

[ ... ]

> diff --git a/fs/btrfs/dir-item.c b/fs/btrfs/dir-item.c
> index 085a83ae9e62f..6e10dd4a4e9e1 100644
> --- a/fs/btrfs/dir-item.c
> +++ b/fs/btrfs/dir-item.c

[ ... ]

> @@ -227,6 +228,47 @@ struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans,
>  	return di;
>  }
>
> +/*
> + * Lookup for a directory item by fscrypt_name.
> + *
> + * @trans:	The transaction handle to use.
> + * @root:	The root of the target tree.
> + * @path:	Path to use for the search.
> + * @dir:	The inode number (objectid) of the directory.
> + * @name:	The fscrypt_name associated to the directory entry
> + * @mod:	Used to indicate if the tree search is meant for a read only
> + *		lookup or for a deletion lookup, so its value should be 0 or
> + *		-1, respectively.
> + *
> + * Returns: NULL if the dir item does not exists, an error pointer if an error
> + * happened, or a pointer to a dir item if a dir item exists for the given name.
> + */
> +struct btrfs_dir_item *btrfs_lookup_dir_item_fname(struct btrfs_trans_handle *trans,
> +						   struct btrfs_root *root,
> +						   struct btrfs_path *path, u64 dir,
> +						   struct fscrypt_name *name, int mod)
> +{
> +	struct btrfs_key key;
> +	struct btrfs_dir_item *di = NULL;
> +	int ret = 0;
> +
> +	key.objectid = dir;
> +	key.type = BTRFS_DIR_ITEM_KEY;
> +	key.offset = btrfs_name_hash(name->disk_name.name, name->disk_name.len);
> +	/* XXX get the right hash for no-key names */

Can this dereference a NULL pointer for long nokey names?

When btrfs_unlink() calls fscrypt_setup_filename() with lookup=1 for a
nokey encrypted filename, fname->disk_name.name is NULL for long nokey
names (see fscrypt_setup_filename lines 370-375 in fs/crypto/fname.c).

The call chain is: btrfs_unlink -> btrfs_unlink_inode ->
__btrfs_unlink_inode (line 4396) -> btrfs_lookup_dir_item_fname.

This passes NULL to btrfs_name_hash() at line 257, which calls
crc32c(NULL, len), causing a kernel crash. The XXX comment at line 258
acknowledges this is unhandled.

Note: This issue is addressed in a later commit in the patch series
(fabad1c3ea821 'btrfs: handle nokey names').


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

* Re: [PATCH v6 11/43] btrfs: add inode encryption contexts
  2026-02-06 18:22 ` [PATCH v6 11/43] btrfs: add inode encryption contexts Daniel Vacek
@ 2026-02-08 15:36   ` Chris Mason
  2026-02-18 13:18     ` Daniel Vacek
  0 siblings, 1 reply; 77+ messages in thread
From: Chris Mason @ 2026-02-08 15:36 UTC (permalink / raw)
  To: Daniel Vacek
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel, Omar Sandoval, Sweet Tea Dorminy

Daniel Vacek <neelx@suse.com> wrote:
> From: Omar Sandoval <osandov@osandov.com>
> 
> fscrypt stores a context item with encrypted inodes that contains the
> related encryption information.  fscrypt provides an arbitrary blob for
> the filesystem to store, and it does not clearly fit into an existing
> structure, so this goes in a new item type.

Hi everyone,

I'm running the btrfs for-next branch, along with the encryption patch set
through my AI review prompts:

https://github.com/masoncl/review-prompts

I'm working on some btrfs specific instructions, but hopefully these reviews
are already useful.  I've kept the headers to make it easier to double check
that I'm actually replying to the right email, but please let me know if any
parts of this are incorrect:

commit 2d6114c2937b897d844735aa16d5369e3ec6d135
Author: Omar Sandoval <osandov@osandov.com>

btrfs: add inode encryption contexts

fscrypt stores a context item with encrypted inodes that contains the
related encryption information.  fscrypt provides an arbitrary blob for
the filesystem to store, and it does not clearly fit into an existing
structure, so this goes in a new item type.

> diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
> index 6cfba7d94e726..e9b024d671a22 100644
> --- a/fs/btrfs/fscrypt.c
> +++ b/fs/btrfs/fscrypt.c

[ ... ]

> @@ -1,10 +1,126 @@
>  // SPDX-License-Identifier: GPL-2.0
>
> +#include <linux/iversion.h>
>  #include "ctree.h"
> +#include "accessors.h"
>  #include "btrfs_inode.h"
> +#include "disk-io.h"
> +#include "fs.h"
>  #include "fscrypt.h"
> +#include "ioctl.h"
> +#include "messages.h"
> +#include "transaction.h"
> +#include "xattr.h"
> +
> +static int btrfs_fscrypt_get_context(struct inode *inode, void *ctx, size_t len)
> +{
> +	struct btrfs_key key = {
> +		.objectid = btrfs_ino(BTRFS_I(inode)),
> +		.type = BTRFS_FSCRYPT_INODE_CTX_KEY,
> +		.offset = 0,
> +	};
> +	struct btrfs_path *path;
> +	struct extent_buffer *leaf;
> +	unsigned long ptr;
> +	int ret;
> +
> +
> +	path = btrfs_alloc_path();
> +	if (!path)
> +		return -ENOMEM;
> +
> +	ret = btrfs_search_slot(NULL, BTRFS_I(inode)->root, &key, path, 0, 0);
> +	if (ret) {
> +		len = -ENOENT;
> +		goto out;
> +	}
> +
> +	leaf = path->nodes[0];
> +	ptr = btrfs_item_ptr_offset(leaf, path->slots[0]);
> +	/* fscrypt provides max context length, but it could be less */
> +	len = min_t(size_t, len, btrfs_item_size(leaf, path->slots[0]));
> +	read_extent_buffer(leaf, ctx, ptr, len);
> +
> +out:
> +	btrfs_free_path(path);
> +	return len;
> +}
> +
> +static int btrfs_fscrypt_set_context(struct inode *inode, const void *ctx,
> +				     size_t len, void *fs_data)
> +{
> +	struct btrfs_trans_handle *trans = fs_data;
> +	struct btrfs_key key = {
> +		.objectid = btrfs_ino(BTRFS_I(inode)),
> +		.type = BTRFS_FSCRYPT_INODE_CTX_KEY,
> +		.offset = 0,
> +	};
> +	struct btrfs_path *path = NULL;
> +	struct extent_buffer *leaf;
> +	unsigned long ptr;
> +	int ret;
> +
> +	if (!trans)
> +		trans = btrfs_start_transaction(BTRFS_I(inode)->root, 2);
> +	if (IS_ERR(trans))
> +		return PTR_ERR(trans);
> +
> +	path = btrfs_alloc_path();
> +	if (!path) {
> +		ret = -ENOMEM;
> +		goto out_err;
> +	}
> +
> +	ret = btrfs_search_slot(trans, BTRFS_I(inode)->root, &key, path, 0, 1);
> +	if (ret < 0)
> +		goto out_err;
> +
> +	if (ret > 0) {
> +		btrfs_release_path(path);
> +		ret = btrfs_insert_empty_item(trans, BTRFS_I(inode)->root, path, &key, len);
> +		if (ret)
> +			goto out_err;
> +	}
> +
> +	leaf = path->nodes[0];
> +	ptr = btrfs_item_ptr_offset(leaf, path->slots[0]);
> +
> +	len = min_t(size_t, len, btrfs_item_size(leaf, path->slots[0]));
> +	write_extent_buffer(leaf, ctx, ptr, len);
> +	btrfs_mark_buffer_dirty(trans, leaf);
> +	btrfs_release_path(path);
> +
> +	if (fs_data)
> +		return ret;

Does this code leak the path when fs_data is non-NULL?

In btrfs_fscrypt_set_context(), a path is allocated via btrfs_alloc_path()
but when fs_data is provided (during inode creation with a transaction
handle), the function returns here without calling btrfs_free_path(path).

The btrfs_release_path() call above only releases internal references to
extent buffers and locks but does not free the path structure itself.

This leak occurs on every encrypted inode creation. The fix would be to add
btrfs_free_path(path) before the return.

> +
> +	BTRFS_I(inode)->flags |= BTRFS_INODE_ENCRYPT;
> +	btrfs_sync_inode_flags_to_i_flags(BTRFS_I(inode));
> +	inode_inc_iversion(inode);
> +	inode_set_ctime_current(inode);
> +	ret = btrfs_update_inode(trans, BTRFS_I(inode));
> +	if (ret)
> +		goto out_abort;
> +	btrfs_free_path(path);
> +	btrfs_end_transaction(trans);
> +	return 0;
> +out_abort:
> +	btrfs_abort_transaction(trans, ret);
> +out_err:
> +	if (!fs_data)
> +		btrfs_end_transaction(trans);
> +	btrfs_free_path(path);
> +	return ret;
> +}

[ ... ]


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

* Re: [PATCH v6 10/43] btrfs: start using fscrypt hooks
  2026-02-06 18:22 ` [PATCH v6 10/43] btrfs: start using fscrypt hooks Daniel Vacek
@ 2026-02-08 15:44   ` Chris Mason
  2026-02-17 15:26     ` Daniel Vacek
  0 siblings, 1 reply; 77+ messages in thread
From: Chris Mason @ 2026-02-08 15:44 UTC (permalink / raw)
  To: Daniel Vacek
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel, Omar Sandoval, Sweet Tea Dorminy

Daniel Vacek <neelx@suse.com> wrote:
> From: Omar Sandoval <osandov@osandov.com>
> 
> In order to appropriately encrypt, create, open, rename, and various
> symlink operations must call fscrypt hooks. These determine whether the
> inode should be encrypted and do other preparatory actions. The
> superblock must have fscrypt operations registered, so implement the
> minimal set also, and introduce the new fscrypt.[ch] files to hold the
> fscrypt-specific functionality.

Hi everyone,

I'm running the btrfs for-next branch, along with the encryption patch set
through my AI review prompts:

https://github.com/masoncl/review-prompts

I'm working on some btrfs specific instructions, but hopefully these reviews
are already useful.  I've kept the headers to make it easier to double check
that I'm actually replying to the right email, but please let me know if any
parts of this are incorrect:

commit 4d7d769810b1d47ec75a6efa0f691c43d9969b54
Author: Omar Sandoval <osandov@osandov.com>

btrfs: start using fscrypt hooks

This commit adds fscrypt support to btrfs by implementing fscrypt hooks
in various inode operations including create, open, rename, and symlink
operations.

> diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
> index b6c763a17406b..9d90e2e946fc4 100644
> --- a/fs/btrfs/inode.c
> +++ b/fs/btrfs/inode.c

[ ... ]

> @@ -6506,6 +6510,12 @@ int btrfs_new_inode_prepare(struct btrfs_new_inode_args *args,
>  		return ret;
>  	}
>
> +	ret = fscrypt_prepare_new_inode(dir, inode, &args->encrypt);
> +	if (ret) {
> +		fscrypt_free_filename(&args->fname);
> +		return ret;
> +	}
> +

When fscrypt_prepare_new_inode() fails, are the ACLs leaked?

Looking earlier in the function, posix_acl_create() allocates args->acl
and args->default_acl. When fscrypt_prepare_new_inode() fails, the
error path frees the filename but doesn't release these ACLs.

Callers like btrfs_symlink() skip btrfs_new_inode_args_destroy() on
error, so the ACLs would never be released. Should this add
posix_acl_release() calls before returning?

[ ... ]

> @@ -8994,20 +9015,28 @@ static int btrfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
>  	};
>  	unsigned int trans_num_items;
>  	int ret;
> -	int name_len;
>  	int datasize;
>  	unsigned long ptr;
>  	struct btrfs_file_extent_item *ei;
>  	struct extent_buffer *leaf;
> +	struct fscrypt_str disk_link;
> +	size_t max_len;
> +	u32 name_len = strlen(symname);
> +
> +	/*
> +	 * BTRFS_MAX_INLINE_DATA_SIZE() isn't actually telling the truth, we actually
> +	 * limit inline data extents to min(BTRFS_MAX_INLINE_DATA_SIZE(), sectorsize),
> +	 * so adjust max_len given this wonderful bit of inconsistency.
> +	 */
> +	max_len = min_t(size_t, BTRFS_MAX_INLINE_DATA_SIZE(fs_info), fs_info->sectorsize);
>
> -	name_len = strlen(symname);
>  	/*
> -	 * Symlinks utilize uncompressed inline extent data, which should not
> -	 * reach block size.
> +	 * fscrypt sets disk_link.len to be len + 1, including a NUL terminator,
> +	 * but we don't store that '\0' character.
>  	 */
> -	if (name_len > BTRFS_MAX_INLINE_DATA_SIZE(fs_info) ||
> -	    name_len >= fs_info->sectorsize)
> -		return -ENAMETOOLONG;
> +	ret = fscrypt_prepare_symlink(dir, symname, name_len, max_len + 1, &disk_link);
> +	if (ret)
> +		return ret;
>
>  	inode = new_inode(dir->i_sb);
>  	if (!inode)

[ ... ]

> @@ -9044,10 +9073,22 @@ static int btrfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
>  		inode = NULL;
>  		goto out;
>  	}
> +
> +	if (IS_ENCRYPTED(inode)) {
> +		ret = fscrypt_encrypt_symlink(inode, symname, name_len, &disk_link);
> +		if (ret) {
> +			btrfs_abort_transaction(trans, ret);
> +			btrfs_free_path(path);
> +			discard_new_inode(inode);
> +			inode = NULL;
> +			goto out;
> +		}
> +	}
> +
>  	key.objectid = btrfs_ino(BTRFS_I(inode));
>  	key.type = BTRFS_EXTENT_DATA_KEY;
>  	key.offset = 0;
> -	datasize = btrfs_file_extent_calc_inline_size(name_len);
> +	datasize = btrfs_file_extent_calc_inline_size(disk_link.len - 1);
>  	ret = btrfs_insert_empty_item(trans, root, path, &key, datasize);
>  	if (unlikely(ret)) {
>  		btrfs_abort_transaction(trans, ret);

[ ... ]

> @@ -9084,6 +9125,29 @@ static int btrfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
>  	return ret;
>  }

Does this code leak disk_link.name when creating encrypted symlinks?

According to fscrypt.h, when fscrypt_encrypt_symlink() is called,
'one will be kmalloc()'ed and the filesystem will be responsible
for freeing it'. However, btrfs_symlink() never frees disk_link.name
in any exit path, whether success or failure.

Looking at ext4/namei.c:3443-3444, it correctly handles this:

    if (disk_link.name != (unsigned char *)symname)
        kfree(disk_link.name);

Should similar cleanup be added here?


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

* Re: [PATCH v6 08/43] fscrypt: add documentation about extent encryption
  2026-02-06 18:43   ` Randy Dunlap
@ 2026-02-17 14:48     ` Daniel Vacek
  0 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-17 14:48 UTC (permalink / raw)
  To: Randy Dunlap
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, Jonathan Corbet,
	linux-block, linux-fscrypt, linux-btrfs, linux-kernel, linux-doc

On Fri, 6 Feb 2026 at 19:43, Randy Dunlap <rdunlap@infradead.org> wrote:
> On 2/6/26 10:22 AM, Daniel Vacek wrote:
> > From: Josef Bacik <josef@toxicpanda.com>
> >
> > Add a couple of sections to the fscrypt documentation about per-extent
> > encryption.
> >
> > Signed-off-by: Josef Bacik <josef@toxicpanda.com>
> > Signed-off-by: Daniel Vacek <neelx@suse.com>
> > ---
> >
> > v5: https://lore.kernel.org/linux-btrfs/7b2cc4dd423c3930e51b1ef5dd209164ff11c05a.1706116485.git.josef@toxicpanda.com/
> >  * No changes since.
> > ---
> >  Documentation/filesystems/fscrypt.rst | 41 +++++++++++++++++++++++++++
> >  1 file changed, 41 insertions(+)
> >
> > diff --git a/Documentation/filesystems/fscrypt.rst b/Documentation/filesystems/fscrypt.rst
> > index 70af896822e1..8afec55dd913 100644
> > --- a/Documentation/filesystems/fscrypt.rst
> > +++ b/Documentation/filesystems/fscrypt.rst
> > @@ -283,6 +283,21 @@ alternative master keys or to support rotating master keys.  Instead,
> >  the master keys may be wrapped in userspace, e.g. as is done by the
> >  `fscrypt <https://github.com/google/fscrypt>`_ tool.
> >
> > +Per-extent encryption keys
> > +--------------------------
> > +
> > +For certain file systems, such as btrfs, it's desired to derive a
> > +per-extent encryption key.  This is to enable features such as snapshots
> > +and reflink, where you could have different inodes pointing at the same
> > +extent.  When a new extent is created fscrypt randomly generates a
> > +16-byte nonce and the file system stores it along side the extent.
>
>                                                alongside
>
> > +Then, it uses a KDF (as described in `Key derivation function`_) to
> > +derive the extent's key from the master key and nonce.
> > +
> > +Currently the inode's master key and encryption policy must match the
> > +extent, so you cannot share extents between inodes that were encrypted
> > +differently.
> > +
> >  DIRECT_KEY policies
> >  -------------------
> >
> > @@ -1488,6 +1503,27 @@ by the kernel and is used as KDF input or as a tweak to cause
> >  different files to be encrypted differently; see `Per-file encryption
> >  keys`_ and `DIRECT_KEY policies`_.
> >
> > +Extent encryption context
> > +-------------------------
> > +
> > +The extent encryption context mirrors the important parts of the above
> > +`Encryption context`_, with a few ommisions.  The struct is defined as
>
>                                      omissions
>
> > +follows::
> > +
> > +        struct fscrypt_extent_context {
> > +                u8 version;
> > +                u8 encryption_mode;
> > +                u8 master_key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
> > +                u8 nonce[FSCRYPT_FILE_NONCE_SIZE];
> > +        };
> > +
> > +Currently all fields much match the containing inode's encryption
> > +context, with the exception of the nonce.
> > +
> > +Additionally extent encryption is only supported with
> > +FSCRYPT_EXTENT_CONTEXT_V2 using the standard policy, all other policies
>
>                                                 policy; all other policies
>
> > +are disallowed.
> > +
> >  Data path changes
> >  -----------------
> >
> > @@ -1511,6 +1547,11 @@ buffer.  Some filesystems, such as UBIFS, already use temporary
> >  buffers regardless of encryption.  Other filesystems, such as ext4 and
> >  F2FS, have to allocate bounce pages specially for encryption.
> >
> > +Inline encryption is not optional for extent encryption based file
> > +systems, the amount of objects required to be kept around is too much.
>
>    systems; the amount of

Thanks Randy. I'll amend all these in the next iteration.

--nX

> > +Inline encryption handles the object lifetime details which results in a
> > +cleaner implementation.
> > +
> >  Filename hashing and encoding
> >  -----------------------------
> >
>
> --
> ~Randy
>

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

* Re: [PATCH v6 10/43] btrfs: start using fscrypt hooks
  2026-02-08 15:44   ` Chris Mason
@ 2026-02-17 15:26     ` Daniel Vacek
  0 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-17 15:26 UTC (permalink / raw)
  To: Chris Mason
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel, Omar Sandoval, Sweet Tea Dorminy

On Sun, 8 Feb 2026 at 16:46, Chris Mason <clm@meta.com> wrote:
> Daniel Vacek <neelx@suse.com> wrote:
> > From: Omar Sandoval <osandov@osandov.com>
> >
> > In order to appropriately encrypt, create, open, rename, and various
> > symlink operations must call fscrypt hooks. These determine whether the
> > inode should be encrypted and do other preparatory actions. The
> > superblock must have fscrypt operations registered, so implement the
> > minimal set also, and introduce the new fscrypt.[ch] files to hold the
> > fscrypt-specific functionality.
>
> Hi everyone,
>
> I'm running the btrfs for-next branch, along with the encryption patch set
> through my AI review prompts:
>
> https://github.com/masoncl/review-prompts
>
> I'm working on some btrfs specific instructions, but hopefully these reviews
> are already useful.  I've kept the headers to make it easier to double check
> that I'm actually replying to the right email, but please let me know if any
> parts of this are incorrect:
>
> commit 4d7d769810b1d47ec75a6efa0f691c43d9969b54
> Author: Omar Sandoval <osandov@osandov.com>
>
> btrfs: start using fscrypt hooks
>
> This commit adds fscrypt support to btrfs by implementing fscrypt hooks
> in various inode operations including create, open, rename, and symlink
> operations.
>
> > diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
> > index b6c763a17406b..9d90e2e946fc4 100644
> > --- a/fs/btrfs/inode.c
> > +++ b/fs/btrfs/inode.c
>
> [ ... ]
>
> > @@ -6506,6 +6510,12 @@ int btrfs_new_inode_prepare(struct btrfs_new_inode_args *args,
> >               return ret;
> >       }
> >
> > +     ret = fscrypt_prepare_new_inode(dir, inode, &args->encrypt);
> > +     if (ret) {
> > +             fscrypt_free_filename(&args->fname);
> > +             return ret;
> > +     }
> > +
>
> When fscrypt_prepare_new_inode() fails, are the ACLs leaked?
>
> Looking earlier in the function, posix_acl_create() allocates args->acl
> and args->default_acl. When fscrypt_prepare_new_inode() fails, the
> error path frees the filename but doesn't release these ACLs.
>
> Callers like btrfs_symlink() skip btrfs_new_inode_args_destroy() on
> error, so the ACLs would never be released. Should this add
> posix_acl_release() calls before returning?

True. Fixed with the below hunk. Thanks!

--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -6574,7 +6574,7 @@ int btrfs_new_inode_prepare(struct
btrfs_new_inode_args *args,

     ret = fscrypt_prepare_new_inode(dir, inode, &args->encrypt);
     if (ret) {
-        fscrypt_free_filename(&args->fname);
+        btrfs_new_inode_args_destroy(args);
         return ret;
     }


> [ ... ]
>
> > @@ -8994,20 +9015,28 @@ static int btrfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
> >       };
> >       unsigned int trans_num_items;
> >       int ret;
> > -     int name_len;
> >       int datasize;
> >       unsigned long ptr;
> >       struct btrfs_file_extent_item *ei;
> >       struct extent_buffer *leaf;
> > +     struct fscrypt_str disk_link;
> > +     size_t max_len;
> > +     u32 name_len = strlen(symname);
> > +
> > +     /*
> > +      * BTRFS_MAX_INLINE_DATA_SIZE() isn't actually telling the truth, we actually
> > +      * limit inline data extents to min(BTRFS_MAX_INLINE_DATA_SIZE(), sectorsize),
> > +      * so adjust max_len given this wonderful bit of inconsistency.
> > +      */
> > +     max_len = min_t(size_t, BTRFS_MAX_INLINE_DATA_SIZE(fs_info), fs_info->sectorsize);
> >
> > -     name_len = strlen(symname);
> >       /*
> > -      * Symlinks utilize uncompressed inline extent data, which should not
> > -      * reach block size.
> > +      * fscrypt sets disk_link.len to be len + 1, including a NUL terminator,
> > +      * but we don't store that '\0' character.
> >        */
> > -     if (name_len > BTRFS_MAX_INLINE_DATA_SIZE(fs_info) ||
> > -         name_len >= fs_info->sectorsize)
> > -             return -ENAMETOOLONG;
> > +     ret = fscrypt_prepare_symlink(dir, symname, name_len, max_len + 1, &disk_link);
> > +     if (ret)
> > +             return ret;
> >
> >       inode = new_inode(dir->i_sb);
> >       if (!inode)
>
> [ ... ]
>
> > @@ -9044,10 +9073,22 @@ static int btrfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
> >               inode = NULL;
> >               goto out;
> >       }
> > +
> > +     if (IS_ENCRYPTED(inode)) {
> > +             ret = fscrypt_encrypt_symlink(inode, symname, name_len, &disk_link);
> > +             if (ret) {
> > +                     btrfs_abort_transaction(trans, ret);
> > +                     btrfs_free_path(path);
> > +                     discard_new_inode(inode);
> > +                     inode = NULL;
> > +                     goto out;
> > +             }
> > +     }
> > +
> >       key.objectid = btrfs_ino(BTRFS_I(inode));
> >       key.type = BTRFS_EXTENT_DATA_KEY;
> >       key.offset = 0;
> > -     datasize = btrfs_file_extent_calc_inline_size(name_len);
> > +     datasize = btrfs_file_extent_calc_inline_size(disk_link.len - 1);
> >       ret = btrfs_insert_empty_item(trans, root, path, &key, datasize);
> >       if (unlikely(ret)) {
> >               btrfs_abort_transaction(trans, ret);
>
> [ ... ]
>
> > @@ -9084,6 +9125,29 @@ static int btrfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
> >       return ret;
> >  }
>
> Does this code leak disk_link.name when creating encrypted symlinks?
>
> According to fscrypt.h, when fscrypt_encrypt_symlink() is called,
> 'one will be kmalloc()'ed and the filesystem will be responsible
> for freeing it'. However, btrfs_symlink() never frees disk_link.name
> in any exit path, whether success or failure.
>
> Looking at ext4/namei.c:3443-3444, it correctly handles this:
>
>     if (disk_link.name != (unsigned char *)symname)
>         kfree(disk_link.name);
>
> Should similar cleanup be added here?

Agreed. I think this fixes that leak (untested):

--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -9196,7 +9196,7 @@ static int btrfs_symlink(struct mnt_idmap
*idmap, struct inode *dir,
         btrfs_free_path(path);
         discard_new_inode(inode);
         inode = NULL;
-        goto out;
+        goto free_name;
     }
     leaf = path->nodes[0];
     ei = btrfs_item_ptr(leaf, path->slots[0],
@@ -9215,6 +9215,9 @@ static int btrfs_symlink(struct mnt_idmap
*idmap, struct inode *dir,

     d_instantiate_new(dentry, inode);
     ret = 0;
+free_name:
+    if (disk_link.name != (unsigned char *)symname)
+        kfree(disk_link.name);
 out:
     btrfs_end_transaction(trans);
     btrfs_btree_balance_dirty(fs_info);

Thanks again!

--nX

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

* Re: [PATCH v6 11/43] btrfs: add inode encryption contexts
  2026-02-08 15:36   ` Chris Mason
@ 2026-02-18 13:18     ` Daniel Vacek
  0 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-18 13:18 UTC (permalink / raw)
  To: Chris Mason
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel, Omar Sandoval, Sweet Tea Dorminy

On Sun, 8 Feb 2026 at 16:38, Chris Mason <clm@meta.com> wrote:
> Daniel Vacek <neelx@suse.com> wrote:
> > From: Omar Sandoval <osandov@osandov.com>
> >
> > fscrypt stores a context item with encrypted inodes that contains the
> > related encryption information.  fscrypt provides an arbitrary blob for
> > the filesystem to store, and it does not clearly fit into an existing
> > structure, so this goes in a new item type.
>
> Hi everyone,
>
> I'm running the btrfs for-next branch, along with the encryption patch set
> through my AI review prompts:
>
> https://github.com/masoncl/review-prompts
>
> I'm working on some btrfs specific instructions, but hopefully these reviews
> are already useful.  I've kept the headers to make it easier to double check
> that I'm actually replying to the right email, but please let me know if any
> parts of this are incorrect:
>
> commit 2d6114c2937b897d844735aa16d5369e3ec6d135
> Author: Omar Sandoval <osandov@osandov.com>
>
> btrfs: add inode encryption contexts
>
> fscrypt stores a context item with encrypted inodes that contains the
> related encryption information.  fscrypt provides an arbitrary blob for
> the filesystem to store, and it does not clearly fit into an existing
> structure, so this goes in a new item type.
>
> > diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
> > index 6cfba7d94e726..e9b024d671a22 100644
> > --- a/fs/btrfs/fscrypt.c
> > +++ b/fs/btrfs/fscrypt.c
>
> [ ... ]
>
> > @@ -1,10 +1,126 @@
> >  // SPDX-License-Identifier: GPL-2.0
> >
> > +#include <linux/iversion.h>
> >  #include "ctree.h"
> > +#include "accessors.h"
> >  #include "btrfs_inode.h"
> > +#include "disk-io.h"
> > +#include "fs.h"
> >  #include "fscrypt.h"
> > +#include "ioctl.h"
> > +#include "messages.h"
> > +#include "transaction.h"
> > +#include "xattr.h"
> > +
> > +static int btrfs_fscrypt_get_context(struct inode *inode, void *ctx, size_t len)
> > +{
> > +     struct btrfs_key key = {
> > +             .objectid = btrfs_ino(BTRFS_I(inode)),
> > +             .type = BTRFS_FSCRYPT_INODE_CTX_KEY,
> > +             .offset = 0,
> > +     };
> > +     struct btrfs_path *path;
> > +     struct extent_buffer *leaf;
> > +     unsigned long ptr;
> > +     int ret;
> > +
> > +
> > +     path = btrfs_alloc_path();
> > +     if (!path)
> > +             return -ENOMEM;
> > +
> > +     ret = btrfs_search_slot(NULL, BTRFS_I(inode)->root, &key, path, 0, 0);
> > +     if (ret) {
> > +             len = -ENOENT;
> > +             goto out;
> > +     }
> > +
> > +     leaf = path->nodes[0];
> > +     ptr = btrfs_item_ptr_offset(leaf, path->slots[0]);
> > +     /* fscrypt provides max context length, but it could be less */
> > +     len = min_t(size_t, len, btrfs_item_size(leaf, path->slots[0]));
> > +     read_extent_buffer(leaf, ctx, ptr, len);
> > +
> > +out:
> > +     btrfs_free_path(path);
> > +     return len;
> > +}
> > +
> > +static int btrfs_fscrypt_set_context(struct inode *inode, const void *ctx,
> > +                                  size_t len, void *fs_data)
> > +{
> > +     struct btrfs_trans_handle *trans = fs_data;
> > +     struct btrfs_key key = {
> > +             .objectid = btrfs_ino(BTRFS_I(inode)),
> > +             .type = BTRFS_FSCRYPT_INODE_CTX_KEY,
> > +             .offset = 0,
> > +     };
> > +     struct btrfs_path *path = NULL;
> > +     struct extent_buffer *leaf;
> > +     unsigned long ptr;
> > +     int ret;
> > +
> > +     if (!trans)
> > +             trans = btrfs_start_transaction(BTRFS_I(inode)->root, 2);
> > +     if (IS_ERR(trans))
> > +             return PTR_ERR(trans);
> > +
> > +     path = btrfs_alloc_path();
> > +     if (!path) {
> > +             ret = -ENOMEM;
> > +             goto out_err;
> > +     }
> > +
> > +     ret = btrfs_search_slot(trans, BTRFS_I(inode)->root, &key, path, 0, 1);
> > +     if (ret < 0)
> > +             goto out_err;
> > +
> > +     if (ret > 0) {
> > +             btrfs_release_path(path);
> > +             ret = btrfs_insert_empty_item(trans, BTRFS_I(inode)->root, path, &key, len);
> > +             if (ret)
> > +                     goto out_err;
> > +     }
> > +
> > +     leaf = path->nodes[0];
> > +     ptr = btrfs_item_ptr_offset(leaf, path->slots[0]);
> > +
> > +     len = min_t(size_t, len, btrfs_item_size(leaf, path->slots[0]));
> > +     write_extent_buffer(leaf, ctx, ptr, len);
> > +     btrfs_mark_buffer_dirty(trans, leaf);
> > +     btrfs_release_path(path);
> > +
> > +     if (fs_data)
> > +             return ret;
>
> Does this code leak the path when fs_data is non-NULL?
>
> In btrfs_fscrypt_set_context(), a path is allocated via btrfs_alloc_path()
> but when fs_data is provided (during inode creation with a transaction
> handle), the function returns here without calling btrfs_free_path(path).
>
> The btrfs_release_path() call above only releases internal references to
> extent buffers and locks but does not free the path structure itself.
>
> This leak occurs on every encrypted inode creation. The fix would be to add
> btrfs_free_path(path) before the return.

That's correct. Good catch!

--- a/fs/btrfs/fscrypt.c
+++ b/fs/btrfs/fscrypt.c
@@ -155,7 +155,7 @@ static int btrfs_fscrypt_set_context(struct inode
*inode, const void *ctx,
     btrfs_release_path(path);

     if (fs_data)
-        return ret;
+        goto out_err;

     BTRFS_I(inode)->flags |= BTRFS_INODE_ENCRYPT;
     btrfs_sync_inode_flags_to_i_flags(BTRFS_I(inode));

Thanks.

--nX

> > +
> > +     BTRFS_I(inode)->flags |= BTRFS_INODE_ENCRYPT;
> > +     btrfs_sync_inode_flags_to_i_flags(BTRFS_I(inode));
> > +     inode_inc_iversion(inode);
> > +     inode_set_ctime_current(inode);
> > +     ret = btrfs_update_inode(trans, BTRFS_I(inode));
> > +     if (ret)
> > +             goto out_abort;
> > +     btrfs_free_path(path);
> > +     btrfs_end_transaction(trans);
> > +     return 0;
> > +out_abort:
> > +     btrfs_abort_transaction(trans, ret);
> > +out_err:
> > +     if (!fs_data)
> > +             btrfs_end_transaction(trans);
> > +     btrfs_free_path(path);
> > +     return ret;
> > +}
>
> [ ... ]
>

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

* Re: [PATCH v6 13/43] btrfs: adapt readdir for encrypted and nokey names
  2026-02-08 15:35   ` Chris Mason
@ 2026-02-18 14:05     ` Daniel Vacek
  0 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-18 14:05 UTC (permalink / raw)
  To: Chris Mason
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel, Omar Sandoval, Sweet Tea Dorminy

On Sun, 8 Feb 2026 at 16:36, Chris Mason <clm@meta.com> wrote:
> Daniel Vacek <neelx@suse.com> wrote:
> > From: Omar Sandoval <osandov@osandov.com>
> >
> > Deleting an encrypted file must always be permitted, even if the user
> > does not have the appropriate key. Therefore, for listing an encrypted
> > directory, so-called 'nokey' names are provided, and these nokey names
> > must be sufficient to look up and delete the appropriate encrypted
> > files. See 'struct fscrypt_nokey_name' for more information on the
> > format of these names.
>
> Hi everyone,
>
> I'm running the btrfs for-next branch, along with the encryption patch set
> through my AI review prompts:
>
> https://github.com/masoncl/review-prompts
>
> I'm working on some btrfs specific instructions, but hopefully these reviews
> are already useful.  I've kept the headers to make it easier to double check
> that I'm actually replying to the right email, but please let me know if any
> parts of this are incorrect:
>
> commit e264482bd61ca8096981d81cc5e7c839284ac969
> Author: Omar Sandoval <osandov@osandov.com>
>
> btrfs: adapt readdir for encrypted and nokey names
>
> This commit adapts the btrfs readdir implementation to handle encrypted
> filenames and nokey names, enabling directory listing and deletion of
> encrypted files even without the encryption key.
>
> > diff --git a/fs/btrfs/delayed-inode.c b/fs/btrfs/delayed-inode.c
> > index 1739a0b29c49d..c19213fab3dda 100644
> > --- a/fs/btrfs/delayed-inode.c
> > +++ b/fs/btrfs/delayed-inode.c
>
> [ ... ]
>
> > @@ -1820,7 +1823,25 @@ bool btrfs_readdir_delayed_dir_index(struct dir_context *ctx,
> >               d_type = fs_ftype_to_dtype(btrfs_dir_flags_to_ftype(di->type));
> >               btrfs_disk_key_to_cpu(&location, &di->location);
> >
> > -             over = !dir_emit(ctx, name, name_len, location.objectid, d_type);
> > +             if (di->type & BTRFS_FT_ENCRYPTED) {
> > +                     int ret;
> > +                     struct fscrypt_str iname = FSTR_INIT(name, name_len);
> > +
> > +                     fstr->len = fstr_len;
> > +                     /*
> > +                      * The hash is only used when the encryption key is not
> > +                      * available. But if we have delayed insertions, then we
> > +                      * must have the encryption key available or we wouldn't
> > +                      * have been able to create entries in the directory.
> > +                      * So, we don't calculate the hash.
> > +                      */
> > +                     ret = fscrypt_fname_disk_to_usr(inode, 0, 0, &iname, fstr);
> > +                     if (ret)
> > +                             return ret;
> > +                     over = !dir_emit(ctx, fstr->name, fstr->len, location.objectid, d_type);
> > +             } else {
> > +                     over = !dir_emit(ctx, name, name_len, location.objectid, d_type);
> > +             }
>
> Does the return statement at line 1840 cause silent error loss?
>
> The function btrfs_readdir_delayed_dir_index returns bool, but
> 'return ret;' returns an int error code from fscrypt_fname_disk_to_usr.
> Since ret is negative on error (e.g., -EUCLEAN from fname_decrypt
> failures), this converts to 'true' in bool context.
>
> The caller btrfs_real_readdir at inode.c:6410 interprets true as "stop
> iteration normally":
>
>     if (btrfs_readdir_delayed_dir_index(inode, &fstr, ctx, &ins_list))
>         goto nopos;
>
> The nopos label at line 6417 sets ret = 0, which means readdir()
> completes successfully with incomplete results when crypto operations
> fail on encrypted directory entries.

This still seems correct to me. While rebasing, however, I missed that
the return type changed in 41e4ea0bf555 ("btrfs: make
btrfs_readdir_delayed_dir_index() return a bool instead").
If I'm not mistaken, we can just `return true;` here:

--- a/fs/btrfs/delayed-inode.c
+++ b/fs/btrfs/delayed-inode.c
@@ -1837,7 +1837,7 @@ bool btrfs_readdir_delayed_dir_index(const
struct inode *inode,
              */
             ret = fscrypt_fname_disk_to_usr(inode, 0, 0, &iname, fstr);
             if (ret)
-                return ret;
+                return true;
             over = !dir_emit(ctx, fstr->name, fstr->len,
location.objectid, d_type);
         } else {
             over = !dir_emit(ctx, name, name_len, location.objectid, d_type);

> [ ... ]
>
> > diff --git a/fs/btrfs/dir-item.c b/fs/btrfs/dir-item.c
> > index 085a83ae9e62f..6e10dd4a4e9e1 100644
> > --- a/fs/btrfs/dir-item.c
> > +++ b/fs/btrfs/dir-item.c
>
> [ ... ]
>
> > @@ -227,6 +228,47 @@ struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans,
> >       return di;
> >  }
> >
> > +/*
> > + * Lookup for a directory item by fscrypt_name.
> > + *
> > + * @trans:   The transaction handle to use.
> > + * @root:    The root of the target tree.
> > + * @path:    Path to use for the search.
> > + * @dir:     The inode number (objectid) of the directory.
> > + * @name:    The fscrypt_name associated to the directory entry
> > + * @mod:     Used to indicate if the tree search is meant for a read only
> > + *           lookup or for a deletion lookup, so its value should be 0 or
> > + *           -1, respectively.
> > + *
> > + * Returns: NULL if the dir item does not exists, an error pointer if an error
> > + * happened, or a pointer to a dir item if a dir item exists for the given name.
> > + */
> > +struct btrfs_dir_item *btrfs_lookup_dir_item_fname(struct btrfs_trans_handle *trans,
> > +                                                struct btrfs_root *root,
> > +                                                struct btrfs_path *path, u64 dir,
> > +                                                struct fscrypt_name *name, int mod)
> > +{
> > +     struct btrfs_key key;
> > +     struct btrfs_dir_item *di = NULL;
> > +     int ret = 0;
> > +
> > +     key.objectid = dir;
> > +     key.type = BTRFS_DIR_ITEM_KEY;
> > +     key.offset = btrfs_name_hash(name->disk_name.name, name->disk_name.len);
> > +     /* XXX get the right hash for no-key names */
>
> Can this dereference a NULL pointer for long nokey names?
>
> When btrfs_unlink() calls fscrypt_setup_filename() with lookup=1 for a
> nokey encrypted filename, fname->disk_name.name is NULL for long nokey
> names (see fscrypt_setup_filename lines 370-375 in fs/crypto/fname.c).
>
> The call chain is: btrfs_unlink -> btrfs_unlink_inode ->
> __btrfs_unlink_inode (line 4396) -> btrfs_lookup_dir_item_fname.
>
> This passes NULL to btrfs_name_hash() at line 257, which calls
> crc32c(NULL, len), causing a kernel crash. The XXX comment at line 258
> acknowledges this is unhandled.
>
> Note: This issue is addressed in a later commit in the patch series
> (fabad1c3ea821 'btrfs: handle nokey names').

Yes, this is handled right in the next commit.

Thanks.

--nX

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

* Re: [PATCH v6 14/43] btrfs: handle nokey names
  2026-02-08 15:28   ` Chris Mason
@ 2026-02-18 14:50     ` Daniel Vacek
  0 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-18 14:50 UTC (permalink / raw)
  To: Chris Mason
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel, Sweet Tea Dorminy

On Sun, 8 Feb 2026 at 16:30, Chris Mason <clm@meta.com> wrote:
> Daniel Vacek <neelx@suse.com> wrote:
> > From: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
> >
> > For encrypted or unencrypted names, we calculate the offset for the dir
> > item by hashing the name for the dir item. However, this doesn't work
> > for a long nokey name, where we do not have the complete ciphertext.
> > Instead, fscrypt stores the filesystem-provided hash in the nokey name,
> > and we can extract it from the fscrypt_name structure in such a case.
> >
> > Additionally, for nokey names, if we find the nokey name on disk we can
> > update the fscrypt_name with the disk name, so add that to searching for
> > diritems.
>
> Hi everyone,
>
> I'm running the btrfs for-next branch, along with the encryption patch set
> through my AI review prompts:
>
> https://github.com/masoncl/review-prompts
>
> I'm working on some btrfs specific instructions, but hopefully these reviews
> are already useful.  I've kept the headers to make it easier to double check
> that I'm actually replying to the right email, but please let me know if any
> parts of this are incorrect:
>
> commit fabad1c3ea82184fba8e78ce1da1b2fbe8c34fae
> Author: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
>
> btrfs: handle nokey names
>
> This commit adds support for handling nokey names in btrfs, enabling
> proper hash calculation and disk name extraction for long encrypted
> filenames when the encryption key is unavailable.
>
> > diff --git a/fs/btrfs/dir-item.c b/fs/btrfs/dir-item.c
> > index 6e10dd4a4e9e1..75c213bd826e9 100644
> > --- a/fs/btrfs/dir-item.c
> > +++ b/fs/btrfs/dir-item.c
> > @@ -228,6 +228,27 @@ struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans,
> >       return di;
> >  }
> >
> > +/*
> > + * If appropriate, populate the disk name for a fscrypt_name looked up without
> > + * a key.
> > + *
> > + * @path:    The path to the extent buffer in which the name was found.
> > + * @di:              The dir item corresponding.
> > + * @fname:   The fscrypt_name to perhaps populate.
> > + *
> > + * Returns: 0 if the name is already populated or the dir item doesn't exist
> > + * or the name was successfully populated, else an error code.
> > + */
> > +static int ensure_disk_name_from_dir_item(struct btrfs_path *path,
> > +                                       struct btrfs_dir_item *di,
> > +                                       struct fscrypt_name *name)
> > +{
> > +     if (name->disk_name.name || !di)
> > +             return 0;
> > +
> > +     return btrfs_fscrypt_get_disk_name(path->nodes[0], di, &name->disk_name);
> > +}
> > +
>
> [ ... ]
>
> > @@ -263,6 +287,8 @@ struct btrfs_dir_item *btrfs_lookup_dir_item_fname(struct btrfs_trans_handle *tr
> >
> >       if (ret == -ENOENT || (di && IS_ERR(di) && PTR_ERR(di) == -ENOENT))
> >               return NULL;
> > +     if (ret == 0)
> > +             ret = ensure_disk_name_from_dir_item(path, di, name);
> >       if (ret < 0)
> >               di = ERR_PTR(ret);
> >
>
> Does ensure_disk_name_from_dir_item() leak memory when it allocates a
> disk name? Looking at the call chain:
>
> btrfs_unlink_subvol() at inode.c:4583 calls btrfs_lookup_dir_item_fname(),
> which calls ensure_disk_name_from_dir_item(), which calls
> btrfs_fscrypt_get_disk_name() at fscrypt.c:34, allocating memory via
> kmalloc() and storing it in name->disk_name.name.
>
> However, btrfs_unlink_subvol() cleans up with fscrypt_free_filename() at
> include/linux/fscrypt.h:503, which only frees name->crypto_buf.name, not
> name->disk_name.name.
>
> The same issue appears in btrfs_search_dir_index_item() at line 405, which
> also calls ensure_disk_name_from_dir_item().
>
> For nokey encrypted filenames, wouldn't this leak memory on every
> successful lookup?

Should `name->crypto_buf.name` equal to `name->disk_name.name` in this case?
How about this:

--- a/fs/btrfs/dir-item.c
+++ b/fs/btrfs/dir-item.c
@@ -243,10 +243,18 @@ static int ensure_disk_name_from_dir_item(struct
btrfs_path *path,
                       struct btrfs_dir_item *di,
                       struct fscrypt_name *name)
 {
+    int ret;
+
     if (name->disk_name.name || !di)
         return 0;

-    return btrfs_fscrypt_get_disk_name(path->nodes[0], di, &name->disk_name);
+    ret = btrfs_fscrypt_get_disk_name(path->nodes[0], di, &name->disk_name);
+    if (ret)
+        return ret;
+
+    name->crypto_buf.name = name->disk_name.name;
+    name->crypto_buf.len  = name->disk_name.len;
+    return 0;
 }

 /*

Thanks.

--nX

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

* Re: [PATCH v6 16/43] btrfs: select encryption dependencies if FS_ENCRYPTION
  2026-02-08 15:22   ` Chris Mason
@ 2026-02-18 15:02     ` Daniel Vacek
  0 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-18 15:02 UTC (permalink / raw)
  To: Chris Mason
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel

On Sun, 8 Feb 2026 at 16:25, Chris Mason <clm@meta.com> wrote:
> Daniel Vacek <neelx@suse.com> wrote:
> > From: Josef Bacik <josef@toxicpanda.com>
> >
> > We need this to make sure the appropriate encryption algorithms are
> > turned on in our config if we have FS_ENCRYPTION enabled, and
> > additionally we only support inline encryption with the fallback block
> > crypto, so we need to make sure we pull in those dependencies.
>
> Hi everyone,
>
> I'm running the btrfs for-next branch, along with the encryption patch set
> through my AI review prompts:
>
> https://github.com/masoncl/review-prompts
>
> I'm working on some btrfs specific instructions, but hopefully these reviews
> are already useful.  I've kept the headers to make it easier to double check
> that I'm actually replying to the right email, but please let me know if any
> parts of this are incorrect:
>
> commit 0ab45d92f6f53371d87e1a302f09b56218d02546
> Author: Josef Bacik <josef@toxicpanda.com>
>
> btrfs: select encryption dependencies if FS_ENCRYPTION
>
> This commit adds select statements for encryption-related dependencies when
> FS_ENCRYPTION is enabled, including encryption algorithms, inline crypto, and
> the block layer inline encryption fallback.
>
> > diff --git a/fs/btrfs/Kconfig b/fs/btrfs/Kconfig
> > index ede184b6eda16..216a5707b099c 100644
> > --- a/fs/btrfs/Kconfig
> > +++ b/fs/btrfs/Kconfig
> > @@ -16,6 +16,9 @@ config BTRFS_FS
> >       select RAID6_PQ
> >       select XOR_BLOCKS
> >       select XXHASH
> > +     select FS_ENCRYPTION_ALGS if FS_ENCRYPTION
> > +     select FS_ENCRYPTION_INLINE_CRYPT if FS_ENCRYPTION
> > +     select BLK_INLINE_ENCRYPTION_FALLBACK if FS_ENCRYPTION
>
> Are the dependencies for FS_ENCRYPTION_INLINE_CRYPT and
> BLK_INLINE_ENCRYPTION_FALLBACK satisfied?
>
> Looking at fs/crypto/Kconfig:38, FS_ENCRYPTION_INLINE_CRYPT depends on
> 'FS_ENCRYPTION && BLK_INLINE_ENCRYPTION'. Similarly, in block/Kconfig:204,
> BLK_INLINE_ENCRYPTION_FALLBACK depends on 'BLK_INLINE_ENCRYPTION'.
>
> When BTRFS_FS=y with FS_ENCRYPTION=y but BLK_INLINE_ENCRYPTION=n (which is
> valid), Kconfig will generate warnings:
>
>   WARNING: unmet direct dependencies detected for BLK_INLINE_ENCRYPTION_FALLBACK
>   WARNING: unmet direct dependencies detected for FS_ENCRYPTION_INLINE_CRYPT
>
> The build will fail when compiling fs/crypto/inline_crypt.c because
> BLK_INLINE_ENCRYPTION guards the required function definitions and struct
> members:
>
>   fs/crypto/inline_crypt.c:259:15: error: implicit declaration of function
>       'blk_crypto_derive_sw_secret'
>   fs/crypto/inline_crypt.c:317:9: error: implicit declaration of function
>       'bio_crypt_set_ctx'
>   fs/crypto/inline_crypt.c:418:45: error: 'struct bio' has no member named
>       'bi_crypt_context'
>
> Should BLK_INLINE_ENCRYPTION also be selected when FS_ENCRYPTION is enabled?

That's correct.

--- a/fs/btrfs/Kconfig
+++ b/fs/btrfs/Kconfig
@@ -18,6 +18,7 @@ config BTRFS_FS
     select XXHASH
     select FS_ENCRYPTION_ALGS if FS_ENCRYPTION
     select FS_ENCRYPTION_INLINE_CRYPT if FS_ENCRYPTION
+    select BLK_INLINE_ENCRYPTION if FS_ENCRYPTION
     select BLK_INLINE_ENCRYPTION_FALLBACK if FS_ENCRYPTION
     depends on PAGE_SIZE_LESS_THAN_256KB


Thanks.

--nX

>   select BLK_INLINE_ENCRYPTION if FS_ENCRYPTION
>   select FS_ENCRYPTION_ALGS if FS_ENCRYPTION
>   select FS_ENCRYPTION_INLINE_CRYPT if FS_ENCRYPTION
>   select BLK_INLINE_ENCRYPTION_FALLBACK if FS_ENCRYPTION
>
> >       depends on PAGE_SIZE_LESS_THAN_256KB
> >
> >       help
>

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

* Re: [PATCH v6 20/43] btrfs: add fscrypt_info and encryption_type to ordered_extent
  2026-02-08 15:18   ` Chris Mason
@ 2026-02-18 15:29     ` Daniel Vacek
  2026-02-18 15:50       ` Chris Mason
  0 siblings, 1 reply; 77+ messages in thread
From: Daniel Vacek @ 2026-02-18 15:29 UTC (permalink / raw)
  To: Chris Mason
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel

On Sun, 8 Feb 2026 at 16:20, Chris Mason <clm@meta.com> wrote:
> Daniel Vacek <neelx@suse.com> wrote:
> > From: Josef Bacik <josef@toxicpanda.com>
> >
> > We're going to need these to update the file extent items once the
> > writes are complete.  Add them and add the pieces necessary to assign
> > them and free everything.
> >
>
> Hi everyone,
>
> I'm running the btrfs for-next branch, along with the encryption patch set
> through my AI review prompts:
>
> https://github.com/masoncl/review-prompts
>
> I'm working on some btrfs specific instructions, but hopefully these reviews
> are already useful.  I've kept the headers to make it easier to double check
> that I'm actually replying to the right email, but please let me know if any
> parts of this are incorrect:
>
> commit 61cd57301840de69a3f6573374d41ad4b4bcf348
> Author: Josef Bacik <josef@toxicpanda.com>
>
> btrfs: add fscrypt_info and encryption_type to ordered_extent
>
> This commit adds encryption-related fields (encryption_type and
> fscrypt_info) to struct btrfs_ordered_extent to support fscrypt
> integration in btrfs.
>
> > diff --git a/fs/btrfs/ordered-data.c b/fs/btrfs/ordered-data.c
> > index 5df02c707aee6..7a5701937184e 100644
> > --- a/fs/btrfs/ordered-data.c
> > +++ b/fs/btrfs/ordered-data.c
> > @@ -192,6 +192,7 @@ static struct btrfs_ordered_extent *alloc_ordered_extent(
> >       }
> >       entry->inode = inode;
> >       entry->compress_type = compress_type;
> > +     entry->encryption_type = BTRFS_ENCRYPTION_NONE;
> >       entry->truncated_len = (u64)-1;
> >       entry->qgroup_rsv = qgroup_rsv;
>
> This isn't a bug, but the fscrypt_info field isn't explicitly initialized
> to NULL here. While kmem_cache_zalloc() at line 175 does zero the memory,
> all the other important fields in this function are explicitly initialized
> (compress_type, encryption_type, truncated_len, qgroup_rsv, refs). Would
> adding an explicit 'entry->fscrypt_info = NULL;' improve consistency?

`entry->fscrypt_info` is set in the very next patch "[21/43] btrfs:
plumb through setting the fscrypt_info for ordered extents". The
series is meant to be applied as a whole. It's just factored into
implementation steps.

> >       entry->flags = flags;
> > @@ -630,6 +631,7 @@ void btrfs_put_ordered_extent(struct btrfs_ordered_extent *entry)
> >               btrfs_add_delayed_iput(entry->inode);
> >               list_for_each_entry_safe(sum, tmp, &entry->list, list)
> >                       kvfree(sum);
> > +             fscrypt_put_extent_info(entry->fscrypt_info);
> >               kmem_cache_free(btrfs_ordered_extent_cache, entry);
> >       }
> >  }
> > diff --git a/fs/btrfs/ordered-data.h b/fs/btrfs/ordered-data.h
> > index 1e6b0b182b295..ce19198e7f844 100644
> > --- a/fs/btrfs/ordered-data.h
> > +++ b/fs/btrfs/ordered-data.h
> > @@ -124,6 +124,9 @@ struct btrfs_ordered_extent {
> >       /* compression algorithm */
> >       int compress_type;
> >
> > +     /* encryption mode */
> > +     u8 encryption_type;
> > +
>
> This isn't a bug, but placing the u8 encryption_type field between two int
> fields creates 3 bytes of padding. This is minor, but in a filesystem with
> many ordered extents, it could add up. The current placement does have the
> advantage of grouping encryption-related fields together logically, though.

This was actually changed from int (as Dave suggested, IIRC). But I
did not move it.
I guess we can do this:

--- a/fs/btrfs/ordered-data.h
+++ b/fs/btrfs/ordered-data.h
@@ -127,18 +127,18 @@ struct btrfs_ordered_extent {
     /* flags (described above) */
     unsigned long flags;

-    /* compression algorithm */
-    int compress_type;
-
-    /* encryption mode */
-    u8 encryption_type;
-
     /* Qgroup reserved space */
     int qgroup_rsv;

     /* reference count */
     refcount_t refs;

+    /* compression algorithm */
+    int compress_type;
+
+    /* encryption mode */
+    u8 encryption_type;
+
     /* the inode we belong to */
     struct btrfs_inode *inode;


> >       /* Qgroup reserved space */
> >       int qgroup_rsv;
> >
> > @@ -133,6 +136,9 @@ struct btrfs_ordered_extent {
> >       /* the inode we belong to */
> >       struct btrfs_inode *inode;
> >
> > +     /* the fscrypt_info for this extent, if necessary */
> > +     struct fscrypt_extent_info *fscrypt_info;
> > +
> >       /* list of checksums for insertion when the extent io is done */
> >       struct list_head list;
>
> How does btrfs_split_ordered_extent() handle the new fscrypt_info field?
> Looking at that function in ordered-data.c, it calls alloc_ordered_extent()
> which initializes encryption_type to BTRFS_ENCRYPTION_NONE and fscrypt_info
> to NULL. If the original ordered extent has encryption_type set to

Ditto. This is changed in the next patch [21/43].
alloc_ordered_extent() correctly sets these fields.

Thanks.

--nX

> BTRFS_ENCRYPTION_FSCRYPT with a non-NULL fscrypt_info pointer, the split
> creates an inconsistency where the two extents representing parts of the
> same encrypted data have different encryption metadata.
>
> The function already has an assertion preventing splits of compressed
> extents at line 1244:
>
>     ASSERT(!(flags & (1U << BTRFS_ORDERED_COMPRESSED)))
>
> Should there be similar protection for encrypted extents, or if splits must
> be supported, should the function call fscrypt_get_extent_info() to
> properly handle the reference count and copy the encryption_type to the new
> extent?
>

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

* Re: [PATCH v6 20/43] btrfs: add fscrypt_info and encryption_type to ordered_extent
  2026-02-18 15:29     ` Daniel Vacek
@ 2026-02-18 15:50       ` Chris Mason
  2026-02-18 16:11         ` Daniel Vacek
  0 siblings, 1 reply; 77+ messages in thread
From: Chris Mason @ 2026-02-18 15:50 UTC (permalink / raw)
  To: Daniel Vacek
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel



On 2/18/26 10:29 AM, Daniel Vacek wrote:
> On Sun, 8 Feb 2026 at 16:20, Chris Mason <clm@meta.com> wrote:
>> Daniel Vacek <neelx@suse.com> wrote:
>>> From: Josef Bacik <josef@toxicpanda.com>
>>>
>>> We're going to need these to update the file extent items once the
>>> writes are complete.  Add them and add the pieces necessary to assign
>>> them and free everything.
>>>
>>
>> Hi everyone,
>>
>> I'm running the btrfs for-next branch, along with the encryption patch set
>> through my AI review prompts:
>>
>> https://github.com/masoncl/review-prompts 
>>
>> I'm working on some btrfs specific instructions, but hopefully these reviews
>> are already useful.  I've kept the headers to make it easier to double check
>> that I'm actually replying to the right email, but please let me know if any
>> parts of this are incorrect:
>>
>> commit 61cd57301840de69a3f6573374d41ad4b4bcf348
>> Author: Josef Bacik <josef@toxicpanda.com>
>>
>> btrfs: add fscrypt_info and encryption_type to ordered_extent
>>
>> This commit adds encryption-related fields (encryption_type and
>> fscrypt_info) to struct btrfs_ordered_extent to support fscrypt
>> integration in btrfs.
>>
>>> diff --git a/fs/btrfs/ordered-data.c b/fs/btrfs/ordered-data.c
>>> index 5df02c707aee6..7a5701937184e 100644
>>> --- a/fs/btrfs/ordered-data.c
>>> +++ b/fs/btrfs/ordered-data.c
>>> @@ -192,6 +192,7 @@ static struct btrfs_ordered_extent *alloc_ordered_extent(
>>>       }
>>>       entry->inode = inode;
>>>       entry->compress_type = compress_type;
>>> +     entry->encryption_type = BTRFS_ENCRYPTION_NONE;
>>>       entry->truncated_len = (u64)-1;
>>>       entry->qgroup_rsv = qgroup_rsv;
>>
>> This isn't a bug, but the fscrypt_info field isn't explicitly initialized
>> to NULL here. While kmem_cache_zalloc() at line 175 does zero the memory,
>> all the other important fields in this function are explicitly initialized
>> (compress_type, encryption_type, truncated_len, qgroup_rsv, refs). Would
>> adding an explicit 'entry->fscrypt_info = NULL;' improve consistency?
> 
> `entry->fscrypt_info` is set in the very next patch "[21/43] btrfs:
> plumb through setting the fscrypt_info for ordered extents". The
> series is meant to be applied as a whole. It's just factored into
> implementation steps.

The prompts try to flag any patch series that don't follow best
practices around bisectability.  There isn't a ton of instruction about
what those best practices are beyond if commit A makes bug and then
commit B fixes the bug, flag it.

This patch set is pretty unusual because you're trying to preserve
authorship for a number of people, and so there are a lot of small
judgement calls about when to change patches and when to fix them up
later.  I sent claude's comments to help highlight places we want to
decide things, but I don't think there's really a wrong decision.

> 
>>>       entry->flags = flags;
>>> @@ -630,6 +631,7 @@ void btrfs_put_ordered_extent(struct btrfs_ordered_extent *entry)
>>>               btrfs_add_delayed_iput(entry->inode);
>>>               list_for_each_entry_safe(sum, tmp, &entry->list, list)
>>>                       kvfree(sum);
>>> +             fscrypt_put_extent_info(entry->fscrypt_info);
>>>               kmem_cache_free(btrfs_ordered_extent_cache, entry);
>>>       }
>>>  }
>>> diff --git a/fs/btrfs/ordered-data.h b/fs/btrfs/ordered-data.h
>>> index 1e6b0b182b295..ce19198e7f844 100644
>>> --- a/fs/btrfs/ordered-data.h
>>> +++ b/fs/btrfs/ordered-data.h
>>> @@ -124,6 +124,9 @@ struct btrfs_ordered_extent {
>>>       /* compression algorithm */
>>>       int compress_type;
>>>
>>> +     /* encryption mode */
>>> +     u8 encryption_type;
>>> +
>>
>> This isn't a bug, but placing the u8 encryption_type field between two int
>> fields creates 3 bytes of padding. This is minor, but in a filesystem with
>> many ordered extents, it could add up. The current placement does have the
>> advantage of grouping encryption-related fields together logically, though.
> 
> This was actually changed from int (as Dave suggested, IIRC). But I
> did not move it.
> I guess we can do this:
> 
> --- a/fs/btrfs/ordered-data.h
> +++ b/fs/btrfs/ordered-data.h
> @@ -127,18 +127,18 @@ struct btrfs_ordered_extent {
>      /* flags (described above) */
>      unsigned long flags;
> 
> -    /* compression algorithm */
> -    int compress_type;
> -
> -    /* encryption mode */
> -    u8 encryption_type;
> -
>      /* Qgroup reserved space */
>      int qgroup_rsv;
> 
>      /* reference count */
>      refcount_t refs;
> 
> +    /* compression algorithm */
> +    int compress_type;
> +
> +    /* encryption mode */
> +    u8 encryption_type;
> +

Seems mostly the same?  I'd suggest paholing things to find a good spot.

>      /* the inode we belong to */
>      struct btrfs_inode *inode;
> 
> 
>>>       /* Qgroup reserved space */
>>>       int qgroup_rsv;
>>>
>>> @@ -133,6 +136,9 @@ struct btrfs_ordered_extent {
>>>       /* the inode we belong to */
>>>       struct btrfs_inode *inode;
>>>
>>> +     /* the fscrypt_info for this extent, if necessary */
>>> +     struct fscrypt_extent_info *fscrypt_info;
>>> +
>>>       /* list of checksums for insertion when the extent io is done */
>>>       struct list_head list;
>>
>> How does btrfs_split_ordered_extent() handle the new fscrypt_info field?
>> Looking at that function in ordered-data.c, it calls alloc_ordered_extent()
>> which initializes encryption_type to BTRFS_ENCRYPTION_NONE and fscrypt_info
>> to NULL. If the original ordered extent has encryption_type set to
> 
> Ditto. This is changed in the next patch [21/43].
> alloc_ordered_extent() correctly sets these fields.

It seems unlikely that we're really going to maintain bisectability for
encryption being on in the middle of this patchset.  So this seems fine
to me as long as the bug doesn't impact encryption being off.

-chris


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

* Re: [PATCH v6 20/43] btrfs: add fscrypt_info and encryption_type to ordered_extent
  2026-02-18 15:50       ` Chris Mason
@ 2026-02-18 16:11         ` Daniel Vacek
  0 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-18 16:11 UTC (permalink / raw)
  To: Chris Mason
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel

On Wed, 18 Feb 2026 at 16:50, Chris Mason <clm@meta.com> wrote:
> On 2/18/26 10:29 AM, Daniel Vacek wrote:
> > On Sun, 8 Feb 2026 at 16:20, Chris Mason <clm@meta.com> wrote:
> >> Daniel Vacek <neelx@suse.com> wrote:
> >>> From: Josef Bacik <josef@toxicpanda.com>
> >>>
> >>> We're going to need these to update the file extent items once the
> >>> writes are complete.  Add them and add the pieces necessary to assign
> >>> them and free everything.
> >>>
> >>
> >> Hi everyone,
> >>
> >> I'm running the btrfs for-next branch, along with the encryption patch set
> >> through my AI review prompts:
> >>
> >> https://github.com/masoncl/review-prompts
> >>
> >> I'm working on some btrfs specific instructions, but hopefully these reviews
> >> are already useful.  I've kept the headers to make it easier to double check
> >> that I'm actually replying to the right email, but please let me know if any
> >> parts of this are incorrect:
> >>
> >> commit 61cd57301840de69a3f6573374d41ad4b4bcf348
> >> Author: Josef Bacik <josef@toxicpanda.com>
> >>
> >> btrfs: add fscrypt_info and encryption_type to ordered_extent
> >>
> >> This commit adds encryption-related fields (encryption_type and
> >> fscrypt_info) to struct btrfs_ordered_extent to support fscrypt
> >> integration in btrfs.
> >>
> >>> diff --git a/fs/btrfs/ordered-data.c b/fs/btrfs/ordered-data.c
> >>> index 5df02c707aee6..7a5701937184e 100644
> >>> --- a/fs/btrfs/ordered-data.c
> >>> +++ b/fs/btrfs/ordered-data.c
> >>> @@ -192,6 +192,7 @@ static struct btrfs_ordered_extent *alloc_ordered_extent(
> >>>       }
> >>>       entry->inode = inode;
> >>>       entry->compress_type = compress_type;
> >>> +     entry->encryption_type = BTRFS_ENCRYPTION_NONE;
> >>>       entry->truncated_len = (u64)-1;
> >>>       entry->qgroup_rsv = qgroup_rsv;
> >>
> >> This isn't a bug, but the fscrypt_info field isn't explicitly initialized
> >> to NULL here. While kmem_cache_zalloc() at line 175 does zero the memory,
> >> all the other important fields in this function are explicitly initialized
> >> (compress_type, encryption_type, truncated_len, qgroup_rsv, refs). Would
> >> adding an explicit 'entry->fscrypt_info = NULL;' improve consistency?
> >
> > `entry->fscrypt_info` is set in the very next patch "[21/43] btrfs:
> > plumb through setting the fscrypt_info for ordered extents". The
> > series is meant to be applied as a whole. It's just factored into
> > implementation steps.
>
> The prompts try to flag any patch series that don't follow best
> practices around bisectability.  There isn't a ton of instruction about
> what those best practices are beyond if commit A makes bug and then
> commit B fixes the bug, flag it.
>
> This patch set is pretty unusual because you're trying to preserve
> authorship for a number of people, and so there are a lot of small
> judgement calls about when to change patches and when to fix them up
> later.  I sent claude's comments to help highlight places we want to
> decide things, but I don't think there's really a wrong decision.

I understand that. I was checking every commit to ensure it builds
correctly. But not really testing for bugs or regressions.

> >
> >>>       entry->flags = flags;
> >>> @@ -630,6 +631,7 @@ void btrfs_put_ordered_extent(struct btrfs_ordered_extent *entry)
> >>>               btrfs_add_delayed_iput(entry->inode);
> >>>               list_for_each_entry_safe(sum, tmp, &entry->list, list)
> >>>                       kvfree(sum);
> >>> +             fscrypt_put_extent_info(entry->fscrypt_info);
> >>>               kmem_cache_free(btrfs_ordered_extent_cache, entry);
> >>>       }
> >>>  }
> >>> diff --git a/fs/btrfs/ordered-data.h b/fs/btrfs/ordered-data.h
> >>> index 1e6b0b182b295..ce19198e7f844 100644
> >>> --- a/fs/btrfs/ordered-data.h
> >>> +++ b/fs/btrfs/ordered-data.h
> >>> @@ -124,6 +124,9 @@ struct btrfs_ordered_extent {
> >>>       /* compression algorithm */
> >>>       int compress_type;
> >>>
> >>> +     /* encryption mode */
> >>> +     u8 encryption_type;
> >>> +
> >>
> >> This isn't a bug, but placing the u8 encryption_type field between two int
> >> fields creates 3 bytes of padding. This is minor, but in a filesystem with
> >> many ordered extents, it could add up. The current placement does have the
> >> advantage of grouping encryption-related fields together logically, though.
> >
> > This was actually changed from int (as Dave suggested, IIRC). But I
> > did not move it.
> > I guess we can do this:
> >
> > --- a/fs/btrfs/ordered-data.h
> > +++ b/fs/btrfs/ordered-data.h
> > @@ -127,18 +127,18 @@ struct btrfs_ordered_extent {
> >      /* flags (described above) */
> >      unsigned long flags;
> >
> > -    /* compression algorithm */
> > -    int compress_type;
> > -
> > -    /* encryption mode */
> > -    u8 encryption_type;
> > -
> >      /* Qgroup reserved space */
> >      int qgroup_rsv;
> >
> >      /* reference count */
> >      refcount_t refs;
> >
> > +    /* compression algorithm */
> > +    int compress_type;
> > +
> > +    /* encryption mode */
> > +    u8 encryption_type;
> > +
>
> Seems mostly the same?  I'd suggest paholing things to find a good spot.

Hmm, that's what I did. There was a 4 bytes hole.
Nah, I see. I just moved it. It's not a big deal then. The structure's
size remains unchanged in either case. Still plugging a hole. Or am I
missing something?

> >      /* the inode we belong to */
> >      struct btrfs_inode *inode;
> >
> >
> >>>       /* Qgroup reserved space */
> >>>       int qgroup_rsv;
> >>>
> >>> @@ -133,6 +136,9 @@ struct btrfs_ordered_extent {
> >>>       /* the inode we belong to */
> >>>       struct btrfs_inode *inode;
> >>>
> >>> +     /* the fscrypt_info for this extent, if necessary */
> >>> +     struct fscrypt_extent_info *fscrypt_info;
> >>> +
> >>>       /* list of checksums for insertion when the extent io is done */
> >>>       struct list_head list;
> >>
> >> How does btrfs_split_ordered_extent() handle the new fscrypt_info field?
> >> Looking at that function in ordered-data.c, it calls alloc_ordered_extent()
> >> which initializes encryption_type to BTRFS_ENCRYPTION_NONE and fscrypt_info
> >> to NULL. If the original ordered extent has encryption_type set to
> >
> > Ditto. This is changed in the next patch [21/43].
> > alloc_ordered_extent() correctly sets these fields.
>
> It seems unlikely that we're really going to maintain bisectability for
> encryption being on in the middle of this patchset.  So this seems fine
> to me as long as the bug doesn't impact encryption being off.

Yeah, I think it should not. (Famous last words...)

Thanks.

--nX

> -chris
>

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

* Re: [PATCH v6 24/43] btrfs: add extent encryption context tree item type
  2026-02-08 15:16   ` Chris Mason
@ 2026-02-18 17:25     ` Daniel Vacek
  0 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-18 17:25 UTC (permalink / raw)
  To: Chris Mason
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel

On Sun, 8 Feb 2026 at 16:17, Chris Mason <clm@meta.com> wrote:
> Daniel Vacek <neelx@suse.com> wrote:
> > From: Josef Bacik <josef@toxicpanda.com>
> >
> > The fscrypt encryption context will be stored as a new tree item type.
> > This gives us flexibility to include different things in the future.
> >
> > Also update the tree-checker to validate the new item type.
>
> Hi everyone,
>
> I'm running the btrfs for-next branch, along with the encryption patch set
> through my AI review prompts:
>
> https://github.com/masoncl/review-prompts
>
> I'm working on some btrfs specific instructions, but hopefully these reviews
> are already useful.  I've kept the headers to make it easier to double check
> that I'm actually replying to the right email, but please let me know if any
> parts of this are incorrect:
>
> commit 203bfb91a4db9c826fa7f72034c13a1810a1b605
> Author: Josef Bacik <josef@toxicpanda.com>
>
> btrfs: add extent encryption context tree item type
>
> The fscrypt encryption context will be stored as a new tree item type.
> This gives us flexibility to include different things in the future.
> Also update the tree-checker to validate the new item type.
>
> > diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
> > index 3c0db279f592e..639462164d089 100644
> > --- a/fs/btrfs/file.c
> > +++ b/fs/btrfs/file.c
>
> [ ... ]
>
> > @@ -496,6 +503,60 @@ int btrfs_drop_extents(struct btrfs_trans_handle *trans,
> >               args->extent_inserted = true;
> >       }
> >
> > +     if (first_ctx <= last_ctx) {
> > +             int slot, nritems;
> > +
> > +             btrfs_release_path(path);
> > +
> > +             key.objectid = ino;
> > +             key.type = BTRFS_FSCRYPT_CTX_KEY;
> > +             key.offset = first_ctx;
> > +
> > +             ret = btrfs_search_slot(trans, root, &key, path, modify_tree, !!modify_tree);
> > +             if (ret < 0)
> > +                     goto out_ctx;
>
> [ ... ]
>
> > +out_ctx:
> > +             if (args->path && args->extent_inserted) {
> > +                     btrfs_release_path(path);
> > +
> > +                     key.objectid = ino;
> > +                     key.type = BTRFS_EXTENT_DATA_KEY;
> > +                     key.offset = args->start;
> > +                     ret = btrfs_search_slot(trans, root, &key, path, 0, 0);
> > +             }
> > +     }
> > +
>
> Can btrfs_search_slot() at line 556 overwrite an error that occurred
> earlier in the out_ctx path?
>
> If btrfs_search_slot() at line 515 fails and execution jumps to out_ctx,
> but args->path and args->extent_inserted are both true, the second
> btrfs_search_slot() call will overwrite the original error stored in ret.
>
> If this second search succeeds (returns 0 or >0), the function returns
> success to the caller despite the fscrypt context cleanup having failed.
> This could lead to inconsistent filesystem state where extent data was
> inserted but the corresponding fscrypt context items were not properly
> cleaned up.

Right, the original error should be returned. We do not care that much
about the latter if the former failed.
This should do the trick:

--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -549,12 +549,16 @@ int btrfs_drop_extents(struct btrfs_trans_handle *trans,
         }
 out_ctx:
         if (args->path && args->extent_inserted) {
+            int err;
+
             btrfs_release_path(path);

             key.objectid = ino;
             key.type = BTRFS_EXTENT_DATA_KEY;
             key.offset = args->start;
-            ret = btrfs_search_slot(trans, root, &key, path, 0, 0);
+            err = btrfs_search_slot(trans, root, &key, path, 0, 0);
+            if (err && ret >= 0)
+                ret = err;
         }
     }


> > diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
> > index 9675dbcd78a3b..776901f297fea 100644
> > --- a/fs/btrfs/tree-checker.c
> > +++ b/fs/btrfs/tree-checker.c
>
> [ ... ]
>
> > +static int check_fscrypt_context(struct extent_buffer *leaf,
> > +                              struct btrfs_key *key, int slot,
> > +                              struct btrfs_key *prev_key)
> > +{
> > +     u32 sectorsize = leaf->fs_info->sectorsize;
> > +     u32 item_size = btrfs_item_size(leaf, slot);
> > +
> > +     if (unlikely(!IS_ALIGNED(key->offset, sectorsize))) {
> > +             file_extent_err(leaf, slot,
> > +"unaligned file_offset for encryption context, have %llu should be aligned to %u",
> > +                     key->offset, sectorsize);
> > +             return -EUCLEAN;
> > +     }
> > +
> > +     /*
> > +      * Previous key must have the same key->objectid (ino).
> > +      * It can be XATTR_ITEM, INODE_ITEM or just another EXTENT_DATA.
> > +      * But if objectids mismatch, it means we have a missing
> > +      * INODE_ITEM.
> > +      */
>
> This isn't a bug, but the comment says "It can be XATTR_ITEM, INODE_ITEM
> or just another EXTENT_DATA" - can EXTENT_DATA actually be the previous
> key type?
>
> For BTRFS_FSCRYPT_CTX_KEY (type=42), EXTENT_DATA (type=108) cannot be a
> previous key because keys are sorted by (objectid, type, offset) and
> 108 > 42.
>
> The correct previous key types would be: XATTR_ITEM, INODE_ITEM,
> FSCRYPT_INODE_CTX_KEY, or another FSCRYPT_CTX_KEY.

Spot on! The comment is wrong. I realized that later but never fixed it here.
This is because of the redesign. In v5 the crypt context was embedded
in the extent data item, so they could have been next to each other.
This should look better:

--- a/fs/btrfs/tree-checker.c
+++ b/fs/btrfs/tree-checker.c
@@ -220,10 +220,9 @@ static int check_fscrypt_context(struct
extent_buffer *leaf,
     }

     /*
-     * Previous key must have the same key->objectid (ino).
-     * It can be XATTR_ITEM, INODE_ITEM or just another EXTENT_DATA.
-     * But if objectids mismatch, it means we have a missing
-     * INODE_ITEM.
+     * Previous key must have the same key->objectid (ino).  It can be
+     * XATTR_ITEM, INODE_ITEM, FSCRYPT_INODE_CTX_KEY, or another
FSCRYPT_CTX_KEY.
+     * But if objectids mismatch, it means we have a missing INODE_ITEM.
      */
     if (unlikely(!check_prev_ino(leaf, key, slot, prev_key)))
         return -EUCLEAN;

> > @@ -214,6 +248,7 @@ static int check_extent_data_item(struct extent_buffer *leaf,
> >       u32 item_size = btrfs_item_size(leaf, slot);
> >       u64 extent_end;
> >       u8 policy;
> > +     u8 fe_type;
> >
> >       if (unlikely(!IS_ALIGNED(key->offset, sectorsize))) {
> >               file_extent_err(leaf, slot,
>
> [ ... ]
>
> > @@ -298,6 +333,19 @@ static int check_extent_data_item(struct extent_buffer *leaf,
> >               return 0;
> >       }
> >
> > +     if (policy == BTRFS_ENCRYPTION_FSCRYPT) {
> > +             /*
> > +              * Only regular and prealloc extents should have an encryption
> > +              * context.
> > +              */
> > +             if (unlikely(fe_type != BTRFS_FILE_EXTENT_REG &&
> > +                          fe_type != BTRFS_FILE_EXTENT_PREALLOC)) {
> > +                     file_extent_err(leaf, slot,
> > +             "invalid type for encrypted file extent, have %u", fe_type);
> > +                     return -EUCLEAN;
> > +             }
> > +     }
>
> Can this validation check detect encrypted inline extents? Looking at
> the function flow, inline extents (BTRFS_FILE_EXTENT_INLINE) are handled
> at lines 310-333 and return before reaching this check.
>
> If a corrupted filesystem has an encrypted inline extent, it would pass
> tree-checker validation. Should the encryption check be moved to also
> cover the inline extent path, or should inline extents explicitly reject
> BTRFS_ENCRYPTION_FSCRYPT?

Inline extents are not encrypted.

Thanks.

--nX

On Sun, 8 Feb 2026 at 16:17, Chris Mason <clm@meta.com> wrote:
>
> Daniel Vacek <neelx@suse.com> wrote:
> > From: Josef Bacik <josef@toxicpanda.com>
> >
> > The fscrypt encryption context will be stored as a new tree item type.
> > This gives us flexibility to include different things in the future.
> >
> > Also update the tree-checker to validate the new item type.
>
> Hi everyone,
>
> I'm running the btrfs for-next branch, along with the encryption patch set
> through my AI review prompts:
>
> https://github.com/masoncl/review-prompts
>
> I'm working on some btrfs specific instructions, but hopefully these reviews
> are already useful.  I've kept the headers to make it easier to double check
> that I'm actually replying to the right email, but please let me know if any
> parts of this are incorrect:
>
> commit 203bfb91a4db9c826fa7f72034c13a1810a1b605
> Author: Josef Bacik <josef@toxicpanda.com>
>
> btrfs: add extent encryption context tree item type
>
> The fscrypt encryption context will be stored as a new tree item type.
> This gives us flexibility to include different things in the future.
> Also update the tree-checker to validate the new item type.
>
> > diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
> > index 3c0db279f592e..639462164d089 100644
> > --- a/fs/btrfs/file.c
> > +++ b/fs/btrfs/file.c
>
> [ ... ]
>
> > @@ -496,6 +503,60 @@ int btrfs_drop_extents(struct btrfs_trans_handle *trans,
> >               args->extent_inserted = true;
> >       }
> >
> > +     if (first_ctx <= last_ctx) {
> > +             int slot, nritems;
> > +
> > +             btrfs_release_path(path);
> > +
> > +             key.objectid = ino;
> > +             key.type = BTRFS_FSCRYPT_CTX_KEY;
> > +             key.offset = first_ctx;
> > +
> > +             ret = btrfs_search_slot(trans, root, &key, path, modify_tree, !!modify_tree);
> > +             if (ret < 0)
> > +                     goto out_ctx;
>
> [ ... ]
>
> > +out_ctx:
> > +             if (args->path && args->extent_inserted) {
> > +                     btrfs_release_path(path);
> > +
> > +                     key.objectid = ino;
> > +                     key.type = BTRFS_EXTENT_DATA_KEY;
> > +                     key.offset = args->start;
> > +                     ret = btrfs_search_slot(trans, root, &key, path, 0, 0);
> > +             }
> > +     }
> > +
>
> Can btrfs_search_slot() at line 556 overwrite an error that occurred
> earlier in the out_ctx path?
>
> If btrfs_search_slot() at line 515 fails and execution jumps to out_ctx,
> but args->path and args->extent_inserted are both true, the second
> btrfs_search_slot() call will overwrite the original error stored in ret.
>
> If this second search succeeds (returns 0 or >0), the function returns
> success to the caller despite the fscrypt context cleanup having failed.
> This could lead to inconsistent filesystem state where extent data was
> inserted but the corresponding fscrypt context items were not properly
> cleaned up.
>
> > diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
> > index 9675dbcd78a3b..776901f297fea 100644
> > --- a/fs/btrfs/tree-checker.c
> > +++ b/fs/btrfs/tree-checker.c
>
> [ ... ]
>
> > +static int check_fscrypt_context(struct extent_buffer *leaf,
> > +                              struct btrfs_key *key, int slot,
> > +                              struct btrfs_key *prev_key)
> > +{
> > +     u32 sectorsize = leaf->fs_info->sectorsize;
> > +     u32 item_size = btrfs_item_size(leaf, slot);
> > +
> > +     if (unlikely(!IS_ALIGNED(key->offset, sectorsize))) {
> > +             file_extent_err(leaf, slot,
> > +"unaligned file_offset for encryption context, have %llu should be aligned to %u",
> > +                     key->offset, sectorsize);
> > +             return -EUCLEAN;
> > +     }
> > +
> > +     /*
> > +      * Previous key must have the same key->objectid (ino).
> > +      * It can be XATTR_ITEM, INODE_ITEM or just another EXTENT_DATA.
> > +      * But if objectids mismatch, it means we have a missing
> > +      * INODE_ITEM.
> > +      */
>
> This isn't a bug, but the comment says "It can be XATTR_ITEM, INODE_ITEM
> or just another EXTENT_DATA" - can EXTENT_DATA actually be the previous
> key type?
>
> For BTRFS_FSCRYPT_CTX_KEY (type=42), EXTENT_DATA (type=108) cannot be a
> previous key because keys are sorted by (objectid, type, offset) and
> 108 > 42.
>
> The correct previous key types would be: XATTR_ITEM, INODE_ITEM,
> FSCRYPT_INODE_CTX_KEY, or another FSCRYPT_CTX_KEY.
>
> > @@ -214,6 +248,7 @@ static int check_extent_data_item(struct extent_buffer *leaf,
> >       u32 item_size = btrfs_item_size(leaf, slot);
> >       u64 extent_end;
> >       u8 policy;
> > +     u8 fe_type;
> >
> >       if (unlikely(!IS_ALIGNED(key->offset, sectorsize))) {
> >               file_extent_err(leaf, slot,
>
> [ ... ]
>
> > @@ -298,6 +333,19 @@ static int check_extent_data_item(struct extent_buffer *leaf,
> >               return 0;
> >       }
> >
> > +     if (policy == BTRFS_ENCRYPTION_FSCRYPT) {
> > +             /*
> > +              * Only regular and prealloc extents should have an encryption
> > +              * context.
> > +              */
> > +             if (unlikely(fe_type != BTRFS_FILE_EXTENT_REG &&
> > +                          fe_type != BTRFS_FILE_EXTENT_PREALLOC)) {
> > +                     file_extent_err(leaf, slot,
> > +             "invalid type for encrypted file extent, have %u", fe_type);
> > +                     return -EUCLEAN;
> > +             }
> > +     }
>
> Can this validation check detect encrypted inline extents? Looking at
> the function flow, inline extents (BTRFS_FILE_EXTENT_INLINE) are handled
> at lines 310-333 and return before reaching this check.
>
> If a corrupted filesystem has an encrypted inline extent, it would pass
> tree-checker validation. Should the encryption check be moved to also
> cover the inline extent path, or should inline extents explicitly reject
> BTRFS_ENCRYPTION_FSCRYPT?
>

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

* Re: [PATCH v6 00/43] btrfs: add fscrypt support
  2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
                   ` (43 preceding siblings ...)
  2026-02-06 18:42 ` [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
@ 2026-02-21 20:56 ` Eric Biggers
  2026-02-27 15:50   ` Daniel Vacek
  44 siblings, 1 reply; 77+ messages in thread
From: Eric Biggers @ 2026-02-21 20:56 UTC (permalink / raw)
  To: Daniel Vacek
  Cc: Chris Mason, Josef Bacik, Theodore Y. Ts'o, Jaegeuk Kim,
	Jens Axboe, David Sterba, linux-block, linux-fscrypt, linux-btrfs,
	linux-kernel

On Fri, Feb 06, 2026 at 07:22:32PM +0100, Daniel Vacek wrote:
> Hello,
> 
> These are the remaining parts from former series [1] from Omar, Sweet Tea
> and Josef.  Some bits of it were split into the separate set [2] before.
> 
> Notably, at this stage encryption is not supported with RAID5/6 setup
> and send is also isabled for now.

Where does this series apply to?  There's no base-commit or git tree,
and it doesn't apply to mainline or btrfs/for-next.

- Eric

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

* Re: [PATCH v6 01/43] fscrypt: add per-extent encryption support
  2026-02-06 18:22 ` [PATCH v6 01/43] fscrypt: add per-extent encryption support Daniel Vacek
@ 2026-02-21 22:11   ` Eric Biggers
  0 siblings, 0 replies; 77+ messages in thread
From: Eric Biggers @ 2026-02-21 22:11 UTC (permalink / raw)
  To: Daniel Vacek
  Cc: Chris Mason, Josef Bacik, Theodore Y. Ts'o, Jaegeuk Kim,
	Jens Axboe, David Sterba, linux-block, linux-fscrypt, linux-btrfs,
	linux-kernel

I lost all my original comments on this patch due to a computer crash,
so apologies if this sounds a bit rushed.  (I should know better than to
run the latest mainline kernel.  Would be nice if kernel developers
focused on quality over new features...)

> +/*
> + * fscrypt_extent_context - the encryption context of an extent
> + *
> + * This is the on-disk information stored for an extent.  The nonce is used as a
> + * KDF input in conjuction with the inode context to derive a per-extent key for
> + * encryption.
> + *
> + * With the current implementation, master_key_identifier and encryption mode
> + * must match the inode context.  These are here for future expansion where we
> + * may want the option of mixing different keys and encryption modes for the
> + * same file.
> + */

Above comment should document that this is used only when the filesystem
uses per-extent encryption

> +/**
> + * fscrypt_set_bio_crypt_ctx_from_extent() - prepare a file contents bio for
> + *					     inline crypto with extent
> + *					     encryption
> + * @bio: a bio which will eventually be submitted to the file
> + * @ei: the extent's crypto info
> + * @first_lblk: the first file logical block number in the I/O

first_lblk probably should be 'pos' to match Christoph's pending patches
(https://lore.kernel.org/linux-fscrypt/20260218061531.3318130-1-hch@lst.de).
Either way, it also needs to be correctly documented to be an offset
into the extent, not the file.

> + * If the contents of the file should be encrypted (or decrypted) with inline
> + * encryption, then assign the appropriate encryption context to the bio.

Above comment was copy-pasted and is misleading in its new context.
This function assigns the encryption context unconditionally.

> + * Normally the bio should be newly allocated (i.e. no pages added yet), as
> + * otherwise fscrypt_mergeable_bio() won't work as intended.

Likewise, copy-pasted comment that is misleading in the new context.
It should refer to fscrypt_mergeable_extent_bio().

> +void fscrypt_set_bio_crypt_ctx_from_extent(struct bio *bio,
> +					   const struct fscrypt_extent_info *ei,
> +					   u64 first_lblk, gfp_t gfp_mask)
> +{
> +	u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE] = { first_lblk };

Above needs to calculate the DUN correctly when the data unit size is
less than the file logical block size, or else the combination of
sub-block data units and per-extent encryption needs to be explicitly
not supported.  Probably just the latter for now (it can be enforced by
fscrypt_supported_v2_policy()).

> +/**
> + * fscrypt_mergeable_extent_bio() - test whether data can be added to a bio
> + * @bio: the bio being built up
> + * @ei: the fscrypt_extent_info for this extent
> + * @next_lblk: the next file logical block number in the I/O
> + *
> + * When building a bio which may contain data which should undergo inline
> + * encryption (or decryption) via fscrypt, filesystems should call this function
> + * to ensure that the resulting bio contains only contiguous data unit numbers.
> + * This will return false if the next part of the I/O cannot be merged with the
> + * bio because either the encryption key would be different or the encryption
> + * data unit numbers would be discontiguous.
> + *
> + * fscrypt_set_bio_crypt_ctx_from_extent() must have already been called on the
> + * bio.
> + *
> + * This function isn't required in cases where crypto-mergeability is ensured in
> + * another way, such as I/O targeting only a single file (and thus a single key)
> + * combined with fscrypt_limit_io_blocks() to ensure DUN contiguity.
> + *
> + * Return: true iff the I/O is mergeable
> + */
> +bool fscrypt_mergeable_extent_bio(struct bio *bio,
> +				  const struct fscrypt_extent_info *ei,
> +				  u64 next_lblk)
> +{
> +	const struct bio_crypt_ctx *bc = bio->bi_crypt_context;
> +	u64 next_dun[BLK_CRYPTO_DUN_ARRAY_SIZE] = { next_lblk };
> +
> +	if (!ei)
> +		return true;
> +	if (!bc)
> +		return true;
> +
> +	/*
> +	 * Comparing the key pointers is good enough, as all I/O for each key
> +	 * uses the same pointer.  I.e., there's currently no need to support
> +	 * merging requests where the keys are the same but the pointers differ.
> +	 */
> +	if (bc->bc_key != ei->prep_key.blk_key)
> +		return false;
> +
> +	return bio_crypt_dun_is_contiguous(bc, bio->bi_iter.bi_size, next_dun);
> +}
> +EXPORT_SYMBOL_GPL(fscrypt_mergeable_extent_bio);

Similar to fscrypt_set_bio_crypt_ctx_from_extent().  The copy-pasted
comment needs to be updated to remove no-longer-relevant information
specific to per-file encryption and correctly reflect per-extent
encryption.  The DUN needs to be calculated correctly for sub-block data
units or else the combination of the two needs to be unsupported.

> +static struct fscrypt_extent_info *
> +setup_extent_info(struct inode *inode, const u8 nonce[FSCRYPT_FILE_NONCE_SIZE])
> +{
> +	struct fscrypt_extent_info *ei;
> +	struct fscrypt_inode_info *ci;
> +	struct fscrypt_master_key *mk;
> +	u8 derived_key[FSCRYPT_MAX_RAW_KEY_SIZE];
> +	int err;
> +
> +	ci = *fscrypt_inode_info_addr(inode);
> +	mk = ci->ci_master_key;
> +	if (WARN_ON_ONCE(!mk))
> +		return ERR_PTR(-ENOKEY);
> +
> +	ei = kmem_cache_zalloc(fscrypt_extent_info_cachep, GFP_KERNEL);
> +	if (!ei)
> +		return ERR_PTR(-ENOMEM);
> +
> +	refcount_set(&ei->refs, 1);
> +	memcpy(ei->nonce, nonce, FSCRYPT_FILE_NONCE_SIZE);
> +	ei->sb = inode->i_sb;
> +
> +	down_read(&mk->mk_sem);
> +	/*
> +	 * We specifically don't check ->mk_present here because if the inode is
> +	 * open and has a reference on the master key then it should be
> +	 * available for us to use.
> +	 */

Above comment should be reworded to clarify that it is expected for
->mk_present to be either true or false here.  As-is, it can be
interpreted as meaning that checking ->mk_present is unnecessary because
it is guaranteed to be true.

The comment above struct fscrypt_master_key (which documents the
different states the master key can be in) also needs to be updated to
document that with filesystems that use per-extent encryption,
->mk_secret isn't wiped when the key is in the incompletely-removed
state (and why that needs to be the case).

> +/**
> + * fscrypt_prepare_new_extent() - prepare to create a new extent for a file
> + * @inode: the possibly-encrypted inode
> + *
> + * If the inode is encrypted, setup the fscrypt_extent_info for a new extent.
> + * This will include the nonce and the derived key necessary for the extent to
> + * be encrypted.  This is only meant to be used with inline crypto and on inodes
> + * that need their contents encrypted.
> + *
> + * This doesn't persist the new extents encryption context, this is done later
> + * by calling fscrypt_set_extent_context().
> + *
> + * Return: The newly allocated fscrypt_extent_info on success, -EOPNOTSUPP if
> + *	   we're not encrypted, or another -errno code
> + */
> +struct fscrypt_extent_info *fscrypt_prepare_new_extent(struct inode *inode)
> +{
> +	u8 nonce[FSCRYPT_FILE_NONCE_SIZE];
> +
> +	if (WARN_ON_ONCE(!*fscrypt_inode_info_addr(inode)))
> +		return ERR_PTR(-EOPNOTSUPP);
> +	if (WARN_ON_ONCE(!fscrypt_inode_uses_inline_crypto(inode)))
> +		return ERR_PTR(-EOPNOTSUPP);
> +
> +	get_random_bytes(nonce, FSCRYPT_FILE_NONCE_SIZE);
> +	return setup_extent_info(inode, nonce);
> +}
> +EXPORT_SYMBOL_GPL(fscrypt_prepare_new_extent);

Similarly, there seems to have been a lot of incorrect copy+pasting in
the function comment.  This new function requires that the caller *must*
provide an encrypted inode, otherwise it WARNs.  It can't be
"possibly-encrypted".

> +/**
> + * fscrypt_load_extent_info() - create an fscrypt_extent_info from the context
> + * @inode: the inode
> + * @ctx: the context buffer
> + * @ctx_size: the size of the context buffer
> + *
> + * Create the fscrypt_extent_info and derive the key based on the
> + * fscrypt_extent_context buffer that is provided.
> + *
> + * Return: The newly allocated fscrypt_extent_info on success, -EOPNOTSUPP if
> + *	   we're not encrypted, or another -errno code
> + */
> +struct fscrypt_extent_info *fscrypt_load_extent_info(struct inode *inode,
> +						     u8 *ctx, size_t ctx_size)

ctx should have type 'const u8 *'

> +/**
> + * fscrypt_set_extent_context() - Set the fscrypt extent context of a new extent
> + * @inode: the inode this extent belongs to
> + * @ei: the fscrypt_extent_info for the given extent
> + * @buf: the buffer to copy the fscrypt extent context into
> + *
> + * This should be called after fscrypt_prepare_new_extent(), using the
> + * fscrypt_extent_info that was created at that point.
> + *
> + * buf must be at most FSCRYPT_SET_CONTEXT_MAX_SIZE.
> + *
> + * Return: the size of the fscrypt_extent_context, errno if the inode has the
> + *	   wrong policy version.
> + */
> +ssize_t fscrypt_context_for_new_extent(struct inode *inode,
> +				       struct fscrypt_extent_info *ei, u8 *buf)
> +{
> +	struct fscrypt_extent_context *ctx = (struct fscrypt_extent_context *)buf;
> +	const struct fscrypt_inode_info *ci = *fscrypt_inode_info_addr(inode);
> +
> +	BUILD_BUG_ON(sizeof(struct fscrypt_extent_context) >
> +		     FSCRYPT_SET_CONTEXT_MAX_SIZE);
> +
> +	if (WARN_ON_ONCE(ci->ci_policy.version != 2))
> +		return -EINVAL;
> +
> +	ctx->version = FSCRYPT_EXTENT_CONTEXT_V1;
> +	ctx->encryption_mode = ci->ci_policy.v2.contents_encryption_mode;
> +	memcpy(ctx->master_key_identifier,
> +	       ci->ci_policy.v2.master_key_identifier,
> +	       sizeof(ctx->master_key_identifier));
> +	memcpy(ctx->nonce, ei->nonce, FSCRYPT_FILE_NONCE_SIZE);
> +	return sizeof(struct fscrypt_extent_context);
> +}
> +EXPORT_SYMBOL_GPL(fscrypt_context_for_new_extent);

The documentation "buf must be at most FSCRYPT_SET_CONTEXT_MAX_SIZE" is
incorrect.  It must actually be *at least* the size of
'struct fscrypt_extent_context'.

Given that it's a fixed size, it probably would make sense to make the
ouptut parameter reflect that: 'u8 out[FSCRYPT_EXTENT_CONTEXT_SIZE]'.
Or even just use the struct itself.

> +	/*
> +	 * If set then extent based encryption will be used for this file
> +	 * system, and fs/crypto/ will enforce limits on the policies that are
> +	 * allowed to be chosen.  Currently this means only plain v2 policies
> +	 * are supported.
> +	 */
> +	unsigned int has_per_extent_encryption : 1;

Needs clarification about what is meant by "plain".  Some flags are
supported (specifically the filename padding ones), some flags are not.
All encryption modes still seem to be supported.

> +	if (count > 0 && inode->i_sb->s_cop->has_per_extent_encryption) {
> +		fscrypt_warn(inode,
> +			     "Encryption flags aren't supported on file systems that use extent encryption");
> +		return false;
> +	}

Similarly, this error message needs clarification.  Some encryption
flags are supported, some aren't.

- Eric

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

* Re: [PATCH v6 00/43] btrfs: add fscrypt support
  2026-02-21 20:56 ` Eric Biggers
@ 2026-02-27 15:50   ` Daniel Vacek
  2026-02-27 22:26     ` Neal Gompa
  0 siblings, 1 reply; 77+ messages in thread
From: Daniel Vacek @ 2026-02-27 15:50 UTC (permalink / raw)
  To: Eric Biggers
  Cc: Chris Mason, Josef Bacik, Theodore Y. Ts'o, Jaegeuk Kim,
	Jens Axboe, David Sterba, linux-block, linux-fscrypt, linux-btrfs,
	linux-kernel

On Sat, 21 Feb 2026 at 21:56, Eric Biggers <ebiggers@kernel.org> wrote:
> On Fri, Feb 06, 2026 at 07:22:32PM +0100, Daniel Vacek wrote:
> > Hello,
> >
> > These are the remaining parts from former series [1] from Omar, Sweet Tea
> > and Josef.  Some bits of it were split into the separate set [2] before.
> >
> > Notably, at this stage encryption is not supported with RAID5/6 setup
> > and send is also isabled for now.
>
> Where does this series apply to?  There's no base-commit or git tree,
> and it doesn't apply to mainline or btrfs/for-next.

Hi Eric,

My apologies, I did not explicitly mention the base. I'll do it next time.
This was based on for-next @20260127 (commit 80dbfe6512d9c).
Since then, some changes occurred that will require additional
touches. No wonder it does not apply anymore.

Daniel

> - Eric

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

* Re: [PATCH v6 00/43] btrfs: add fscrypt support
  2026-02-27 15:50   ` Daniel Vacek
@ 2026-02-27 22:26     ` Neal Gompa
  2026-02-28  7:57       ` Daniel Vacek
  0 siblings, 1 reply; 77+ messages in thread
From: Neal Gompa @ 2026-02-27 22:26 UTC (permalink / raw)
  To: Daniel Vacek
  Cc: Eric Biggers, Chris Mason, Josef Bacik, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel

On Fri, Feb 27, 2026 at 10:55 AM Daniel Vacek <neelx@suse.com> wrote:
>
> On Sat, 21 Feb 2026 at 21:56, Eric Biggers <ebiggers@kernel.org> wrote:
> > On Fri, Feb 06, 2026 at 07:22:32PM +0100, Daniel Vacek wrote:
> > > Hello,
> > >
> > > These are the remaining parts from former series [1] from Omar, Sweet Tea
> > > and Josef.  Some bits of it were split into the separate set [2] before.
> > >
> > > Notably, at this stage encryption is not supported with RAID5/6 setup
> > > and send is also isabled for now.
> >
> > Where does this series apply to?  There's no base-commit or git tree,
> > and it doesn't apply to mainline or btrfs/for-next.
>
> Hi Eric,
>
> My apologies, I did not explicitly mention the base. I'll do it next time.
> This was based on for-next @20260127 (commit 80dbfe6512d9c).
> Since then, some changes occurred that will require additional
> touches. No wonder it does not apply anymore.
>

When you make your next revision, can you also provide a tag or branch
that I can use to grab the patches for testing? It would be easier for
me than trying to yoink them down from the emails with how many of
them there are...


-- 
真実はいつも一つ!/ Always, there's only one truth!

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

* Re: [PATCH v6 00/43] btrfs: add fscrypt support
  2026-02-27 22:26     ` Neal Gompa
@ 2026-02-28  7:57       ` Daniel Vacek
  0 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-02-28  7:57 UTC (permalink / raw)
  To: Neal Gompa
  Cc: Eric Biggers, Chris Mason, Josef Bacik, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel

On Fri, 27 Feb 2026 at 23:26, Neal Gompa <ngompa13@gmail.com> wrote:
> On Fri, Feb 27, 2026 at 10:55 AM Daniel Vacek <neelx@suse.com> wrote:
> > On Sat, 21 Feb 2026 at 21:56, Eric Biggers <ebiggers@kernel.org> wrote:
> > > On Fri, Feb 06, 2026 at 07:22:32PM +0100, Daniel Vacek wrote:
> > > > Hello,
> > > >
> > > > These are the remaining parts from former series [1] from Omar, Sweet Tea
> > > > and Josef.  Some bits of it were split into the separate set [2] before.
> > > >
> > > > Notably, at this stage encryption is not supported with RAID5/6 setup
> > > > and send is also isabled for now.
> > >
> > > Where does this series apply to?  There's no base-commit or git tree,
> > > and it doesn't apply to mainline or btrfs/for-next.
> >
> > Hi Eric,
> >
> > My apologies, I did not explicitly mention the base. I'll do it next time.
> > This was based on for-next @20260127 (commit 80dbfe6512d9c).
> > Since then, some changes occurred that will require additional
> > touches. No wonder it does not apply anymore.
> >
>
> When you make your next revision, can you also provide a tag or branch
> that I can use to grab the patches for testing? It would be easier for
> me than trying to yoink them down from the emails with how many of
> them there are...

Sure

--nX

> --
> 真実はいつも一つ!/ Always, there's only one truth!

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

* Re: [PATCH v6 28/43] btrfs: populate ordered_extent with the orig offset
  2026-02-08 15:12   ` Chris Mason
@ 2026-03-03 13:42     ` Daniel Vacek
  0 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-03-03 13:42 UTC (permalink / raw)
  To: Chris Mason
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel

On Sun, 8 Feb 2026 at 16:14, Chris Mason <clm@meta.com> wrote:
> Daniel Vacek <neelx@suse.com> wrote:
> > From: Josef Bacik <josef@toxicpanda.com>
> >
> > For extent encryption we have to use a logical block nr as input for the
> > IV.  For btrfs we're using the offset into the extent we're operating
> > on.  For most ordered extents this is the same as the file_offset,
> > however for prealloc and NOCOW we have to use the original offset.
> >
> > Add this as an argument and plumb it through everywhere, this will be
> > used when setting up the bio.
> >
>
> Hi everyone,
>
> I'm running the btrfs for-next branch, along with the encryption patch set
> through my AI review prompts:
>
> https://github.com/masoncl/review-prompts
>
> I'm working on some btrfs specific instructions, but hopefully these reviews
> are already useful.  I've kept the headers to make it easier to double check
> that I'm actually replying to the right email, but please let me know if any
> parts of this are incorrect:
>
> commit 0e7f6a617a2642bbdf97bcd639fb887fa88189ad
> Author: Josef Bacik <josef@toxicpanda.com>
>
> btrfs: populate ordered_extent with the orig offset
>
> This commit adds orig_offset tracking to btrfs_ordered_extent and
> btrfs_file_extent structures for extent encryption support, where the
> offset into the extent is needed for IV generation.
>
> > diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
> > index d3789109ca851..2d89ac05b1b38 100644
> > --- a/fs/btrfs/direct-io.c
> > +++ b/fs/btrfs/direct-io.c
> > @@ -155,6 +155,7 @@ static struct extent_map *btrfs_create_dio_extent(struct btrfs_inode *inode,
> >               file_extent->fscrypt_info = em->fscrypt_info;
> >       }
> >
> > +     file_extent->orig_offset = start - file_extent->offset;
> >       ordered = btrfs_alloc_ordered_extent(inode, start, file_extent,
> >                                            (1U << type) |
> >                                            (1U << BTRFS_ORDERED_DIRECT));
>
> [ ... ]
>
> > diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
> > index fbb968d4010b7..8078049f690e1 100644
> > --- a/fs/btrfs/inode.c
> > +++ b/fs/btrfs/inode.c
>
> [ ... ]
>
> > @@ -2296,6 +2298,8 @@ static noinline int run_delalloc_nocow(struct btrfs_inode *inode,
> >                       cow_start = (u64)-1;
> >               }
> >
> > +             nocow_args.file_extent.orig_offset =
> > +                     found_key.offset - nocow_args.file_extent.offset;
> >               ret = nocow_one_range(inode, locked_folio, &cached_state,
> >                                     &nocow_args, cur_offset,
> >                                     extent_type == BTRFS_FILE_EXTENT_PREALLOC);
>
> Should this calculation use cur_offset instead of found_key.offset?

IIUC, no. We need to keep the offset from the original extent
precisely to keep the data consistent. That's the whole point.

--nX


> Looking at the DIO path in btrfs_create_dio_extent() (direct-io.c line
> 158), the orig_offset is calculated as:
>
>     file_extent->orig_offset = start - file_extent->offset;
>
> where 'start' is the current write position. In run_delalloc_nocow(),
> found_key.offset represents where the extent item is stored, while
> cur_offset represents the current write position (analogous to 'start'
> in the DIO path).
>
> When writing to a position within an existing extent (not at its start),
> using found_key.offset - file_extent.offset could produce incorrect
> values. For consistency with the DIO path, would the formula be:
>
>     nocow_args.file_extent.orig_offset =
>         cur_offset - nocow_args.file_extent.offset;
>
> This would ensure that NOCOW writes calculate orig_offset the same way
> as DIO writes, which matters for encryption IV generation where an
> incorrect offset would cause data corruption on reads.
>

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

* Re: [PATCH v6 32/43] btrfs: implement process_bio cb for fscrypt
  2026-02-08 15:10   ` Chris Mason
@ 2026-03-24  9:36     ` Daniel Vacek
  0 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-03-24  9:36 UTC (permalink / raw)
  To: Chris Mason
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel

On Sun, 8 Feb 2026 at 16:12, Chris Mason <clm@meta.com> wrote:
> Daniel Vacek <neelx@suse.com> wrote:
> > From: Josef Bacik <josef@toxicpanda.com>
> >
> > We are going to be checksumming the encrypted data, so we have to
> > implement the ->process_bio fscrypt callback.  This will provide us with
> > the original bio and the encrypted bio to do work on.  For WRITE's this
> > will happen after the encrypted bio has been encrypted.  For READ's this
> > will happen after the read has completed and before the decryption step
> > is done.
> >
> > For write's this is straightforward, we can just pass in the encrypted
> > bio to btrfs_csum_one_bio and then the csums will be added to the bbio
> > as normal.
> >
> > For read's this is relatively straightforward, but requires some care.
> > We assume (because that's how it works currently) that the encrypted bio
> > match the original bio, this is important because we save the iter of
> > the bio before we submit.  If this changes in the future we'll need a
> > hook to give us the bi_iter of the decryption bio before it's submitted.
> > We check the csums before decryption.  If it doesn't match we simply
> > error out and we let the normal path handle the repair work.
>
> Hi everyone,
>
> I'm running the btrfs for-next branch, along with the encryption patch set
> through my AI review prompts:
>
> https://github.com/masoncl/review-prompts
>
> I'm working on some btrfs specific instructions, but hopefully these reviews
> are already useful.  I've kept the headers to make it easier to double check
> that I'm actually replying to the right email, but please let me know if any
> parts of this are incorrect:
>
> commit 2b25418669e46c939ff864b1144e2c27d618920c
> Author: Josef Bacik <josef@toxicpanda.com>
>
> btrfs: implement process_bio cb for fscrypt
>
> This commit implements the ->process_bio fscrypt callback for btrfs,
> enabling checksum verification of encrypted data before decryption and
> checksum generation after encryption.
>
> > diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c
> > index 3b420866f3b82..c28f3f53bad28 100644
> > --- a/fs/btrfs/bio.c
> > +++ b/fs/btrfs/bio.c
> > @@ -300,6 +300,34 @@ static struct btrfs_failed_bio *repair_one_sector(struct btrfs_bio *failed_bbio,
> >       return fbio;
> >  }
> >
> > +blk_status_t btrfs_check_encrypted_read_bio(struct btrfs_bio *bbio, struct bio *enc_bio)
> > +{
> > +     struct btrfs_inode *inode = bbio->inode;
> > +     struct btrfs_fs_info *fs_info = inode->root->fs_info;
> > +     struct bvec_iter iter = bbio->saved_iter;
> > +     struct btrfs_device *dev = bbio->bio.bi_private;
> > +     const u32 step = min(fs_info->sectorsize, PAGE_SIZE);
> > +     const u32 nr_steps = iter.bi_size / step;
> > +     phys_addr_t paddrs[BTRFS_MAX_BLOCKSIZE / PAGE_SIZE];
>
> Can paddrs[] overflow here? BTRFS_MAX_BLOCKSIZE is 64KB, so the array
> has 16 elements (64KB / 4KB). However, nr_steps is calculated from
> iter.bi_size which can be much larger than 64KB for merged bios.
>
> If a bio has bi_size > 64KB, the loop below will write beyond the array
> bounds when filling paddrs[slot].

Indeed, that's very true and I can easily reproduce it.

> > +     phys_addr_t paddr;
> > +     unsigned int slot = 0;
> > +
> > +     /*
> > +      * We have to use a copy of iter in case there's an error,
> > +      * btrfs_check_read_bio will handle submitting the repair bios.
> > +      */
> > +     btrfs_bio_for_each_block(paddr, enc_bio, &iter, step) {
> > +             ASSERT(slot < nr_steps);
> > +             paddrs[slot] = paddr;
> > +             slot++;
> > +     }
> > +     if (!btrfs_data_csum_ok(bbio, dev, 0, paddrs))
>
> Does this verify checksums for all sectors in the bio? Looking at
> btrfs_data_csum_ok() in inode.c, it verifies one sector's worth of data
> (one blocksize). The function is called with bio_offset=0, meaning it
> only checksums the first sector.
>
> However, the bio can contain multiple sectors (iter.bi_size could be
> much larger than one sectorsize). If the bio has multiple sectors,
> shouldn't this call btrfs_data_csum_ok() for each sector, or pass the
> actual bio size so all sectors are verified?
>
> The existing btrfs_check_read_bio() function loops through all sectors
> and verifies each one individually.

This is also correct. Both points fixed this way:

--- a/fs/btrfs/bio.c
+++ b/fs/btrfs/bio.c
@@ -343,11 +343,13 @@ blk_status_t
btrfs_check_encrypted_read_bio(struct btrfs_bio *bbio, struct bio *
     struct btrfs_fs_info *fs_info = inode->root->fs_info;
     struct bvec_iter iter = bbio->saved_iter;
     struct btrfs_device *dev = bbio->bio.bi_private;
-    const u32 step = min(fs_info->sectorsize, PAGE_SIZE);
+    const u32 blocksize = fs_info->sectorsize;
+    const u32 step = min(blocksize, PAGE_SIZE);
     const u32 nr_steps = iter.bi_size / step;
     phys_addr_t paddrs[BTRFS_MAX_BLOCKSIZE / PAGE_SIZE];
     phys_addr_t paddr;
     unsigned int slot = 0;
+    u32 offset = 0;

     /*
      * We have to use a copy of iter in case there's an error,
@@ -357,9 +359,13 @@ blk_status_t
btrfs_check_encrypted_read_bio(struct btrfs_bio *bbio, struct bio *
         ASSERT(slot < nr_steps);
         paddrs[slot] = paddr;
         slot++;
+        offset += step;
+        if (IS_ALIGNED(offset, blocksize)) {
+            if (!btrfs_data_csum_ok(bbio, dev, offset - blocksize, paddrs))
+                return BLK_STS_IOERR;
+            slot = 0;
+        }
     }
-    if (!btrfs_data_csum_ok(bbio, dev, 0, paddrs))
-            return BLK_STS_IOERR;

     /*
      * Read repair is slightly different for encrypted bio's.  This

Thanks!

--nX


> > +                     return BLK_STS_IOERR;
> > +
> > +     bbio->csum_ok = true;
> > +     return BLK_STS_OK;
> > +}
>
> [ ... ]
>
>

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

* Re: [PATCH v6 33/43] btrfs: implement read repair for encryption
  2026-02-08 15:08   ` Chris Mason
@ 2026-03-25 14:17     ` Daniel Vacek
  0 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-03-25 14:17 UTC (permalink / raw)
  To: Chris Mason
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel

On Sun, 8 Feb 2026 at 16:09, Chris Mason <clm@meta.com> wrote:
> Daniel Vacek <neelx@suse.com> wrote:
> > From: Josef Bacik <josef@toxicpanda.com>
> >
> > In order to do read repair we will allocate sectorsize bio's and read
> > them one at a time, repairing any sectors that don't match their csum.
> > In order to do this we re-submit the IO's after it's failed, and at this
> > point we still need the fscrypt_extent_info for these new bio's.
> >
> > Add the fscrypt_extent_info to the read part of the union in the
> > btrfs_bio, and then pass this through all the places where we do reads.
> > Additionally add the orig_start, because we need to be able to put the
> > correct extent offset for the encryption context.
> >
> > With these in place we can utilize the normal read repair path.  The
> > only exception is that the actual repair of the bad copies has to be
> > triggered from the ->process_bio callback, because this is the encrypted
> > data.  If we waited until the end_io we would have the decrypted data
> > and we don't want to write that to the disk.  This is the only change to
> > the normal read repair path, we trigger the fixup of the broken sectors
> > in ->process_bio, and then we skip that part if we successfully repair
> > the sector in ->process_bio once we get to the endio.
>
> Hi everyone,
>
> I'm running the btrfs for-next branch, along with the encryption patch set
> through my AI review prompts:
>
> https://github.com/masoncl/review-prompts
>
> I'm working on some btrfs specific instructions, but hopefully these reviews
> are already useful.  I've kept the headers to make it easier to double check
> that I'm actually replying to the right email, but please let me know if any
> parts of this are incorrect:
>
> commit 096c17a6a951ed0c1100e863c4ee3a95bab0ad51
> Author: Josef Bacik <josef@toxicpanda.com>
>
> btrfs: implement read repair for encryption
>
> This commit implements read repair functionality for encrypted btrfs
> files by adding fscrypt_extent_info tracking to the btrfs_bio structure
> and handling repair operations before decryption occurs.
>
> > diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c
> > index c28f3f53bad28..e82de96b533cb 100644
> > --- a/fs/btrfs/bio.c
> > +++ b/fs/btrfs/bio.c
>
> [ ... ]
>
> > @@ -293,6 +323,13 @@ static struct btrfs_failed_bio *repair_one_sector(struct btrfs_bio *failed_bbio,
> >       repair_bbio = btrfs_bio(repair_bio);
> >       btrfs_bio_init(repair_bbio, failed_bbio->inode, failed_bbio->file_offset + bio_offset,
> >                      NULL, fbio);
> > +     repair_bbio->fscrypt_info = fscrypt_get_extent_info(failed_bbio->fscrypt_info);
> > +     repair_bbio->orig_start = failed_bbio->orig_start;
> > +
> > +     btrfs_set_bio_crypt_ctx_from_extent(repair_bio, repair_bbio->inode,
> > +                                         failed_bbio->fscrypt_info,
> > +                                         repair_bbio->file_offset -
> > +                                         failed_bbio->orig_start);
> >
> >       mirror = next_repair_mirror(fbio, failed_bbio->mirror_num);
> >       btrfs_debug(fs_info, "submitting repair read to mirror %d", mirror);
>
> [ ... ]
>
> > @@ -214,17 +244,17 @@ static void btrfs_end_repair_bio(struct btrfs_bio *repair_bbio,
> >                       goto done;
> >               }
> >
> > +             btrfs_set_bio_crypt_ctx_from_extent(&repair_bbio->bio,
> > +                                                 repair_bbio->inode,
> > +                                                 repair_bbio->fscrypt_info,
> > +                                                 repair_bbio->file_offset -
> > +                                                 repair_bbio->orig_start);
> > +
> >               btrfs_submit_bbio(repair_bbio, mirror);
> >               return;
> >       }
> >
> > -     do {
> > -             mirror = prev_repair_mirror(fbio, mirror);
> > -             btrfs_repair_io_failure(fs_info, btrfs_ino(inode),
> > -                               repair_bbio->file_offset, fs_info->sectorsize,
> > -                               logical, paddrs, step, mirror);
> > -     } while (mirror != fbio->bbio->mirror_num);
> > -
> > +     handle_repair(repair_bbio, paddrs);
> >  done:
> >       btrfs_repair_done(fbio);
> >       bio_put(&repair_bbio->bio);
>
> Does this leak the fscrypt_extent_info reference acquired at line 326?
> In repair_one_sector(), repair_bbio->fscrypt_info is set via
> fscrypt_get_extent_info(failed_bbio->fscrypt_info), but when
> btrfs_end_repair_bio() completes at the done label, it calls
> bio_put(&repair_bbio->bio) without calling
> fscrypt_put_extent_info(repair_bbio->fscrypt_info) first.

repair_one_sector() is called from btrfs_check_read_bio() which always
has (bbio a.k.a. failed_bbio)->fscrypt_info NULL. So there is no
object which could leak due to refcounting imbalance. Encrypted read
bbio passes through btrfs_check_encrypted_read_bio() instead which
does not yet implement repair.

Moreover btrfs_end_repair_bio() is called before repair_one_sector().
And when it is called btrfs_check_read_bio() returns and no
repair_one_sector() is caled at all.
The fscrypt_put_extent_info() is eventually called from
btrfs_simple_end_io() => simple_end_io_work() => btrfs_bio_end_io().
So this looks clean, unless I missed something.

> > diff --git a/fs/btrfs/bio.h b/fs/btrfs/bio.h
> > index 456d32db9e9e9..7a8ff4378cba0 100644
> > --- a/fs/btrfs/bio.h
> > +++ b/fs/btrfs/bio.h
>
> [ ... ]
>
> > diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c
> > index 5a39f707c2933..afc461d7642e4 100644
> > --- a/fs/btrfs/compression.c
> > +++ b/fs/btrfs/compression.c
> > @@ -562,6 +562,8 @@ void btrfs_submit_compressed_read(struct btrfs_bio *bbio)
> >       cb->compress_type = btrfs_extent_map_compression(em);
> >       cb->orig_bbio = bbio;
> >       cb->bbio.csum_search_commit_root = bbio->csum_search_commit_root;
> > +     cb->bbio.fscrypt_info = fscrypt_get_extent_info(em->fscrypt_info);
> > +     cb->bbio.orig_start = 0;
> >
> >       btrfs_set_bio_crypt_ctx_from_extent(&cb->bbio.bio, inode, em->fscrypt_info, 0);
> >       btrfs_free_extent_map(em);
>
> Does this leak the fscrypt_extent_info reference in the compressed read
> path? At line 565, fscrypt_get_extent_info() acquires a reference and
> stores it in cb->bbio.fscrypt_info. Looking at end_bbio_compressed_read()
> in compression.c, it calls btrfs_bio_end_io(cb->orig_bbio, status) which
> releases the ORIGINAL bbio's fscrypt_info, but then calls bio_put(cb_bio)
> without calling fscrypt_put_extent_info(cb->bbio.fscrypt_info). The error
> path in cleanup_compressed_bio() also calls bio_put() without releasing
> the cb->bbio.fscrypt_info reference.

IIUC, the fscrypt_info is put in btrfs_bio_end_io() before calling
end_bbio_compressed_read(). So again, this looks clean to me.

--nX

> [ ... ]
>
>

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

* Re: [PATCH v6 35/43] btrfs: make btrfs_ref_to_path handle encrypted filenames
  2026-02-08 15:02   ` Chris Mason
@ 2026-03-25 15:27     ` Daniel Vacek
  0 siblings, 0 replies; 77+ messages in thread
From: Daniel Vacek @ 2026-03-25 15:27 UTC (permalink / raw)
  To: Chris Mason
  Cc: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
	Jaegeuk Kim, Jens Axboe, David Sterba, linux-block, linux-fscrypt,
	linux-btrfs, linux-kernel

On Sun, 8 Feb 2026 at 16:04, Chris Mason <clm@meta.com> wrote:
> Daniel Vacek <neelx@suse.com> wrote:
> > From: Josef Bacik <josef@toxicpanda.com>
> >
> > We use this helper for inode-resolve and path resolution in send, so
> > update this helper to properly decrypt any encrypted names it finds.
> >
>
> Hi everyone,
>
> I'm running the btrfs for-next branch, along with the encryption patch set
> through my AI review prompts:
>
> https://github.com/masoncl/review-prompts
>
> I'm working on some btrfs specific instructions, but hopefully these reviews
> are already useful.  I've kept the headers to make it easier to double check
> that I'm actually replying to the right email, but please let me know if any
> parts of this are incorrect:
>
>
> commit 473bbb12052f731a4e2ce3c48e7d7e9f2510528b
> Author: Josef Bacik <josef@toxicpanda.com>
>
> btrfs: make btrfs_ref_to_path handle encrypted filenames
>
> This commit updates btrfs_ref_to_path() to handle encrypted filenames by
> introducing a new copy_resolved_iref_to_buf() helper that can decrypt
> names when the filesystem has encryption enabled.
>
> > diff --git a/fs/btrfs/backref.c b/fs/btrfs/backref.c
> > index 9bb406f7dd302..577c3ef87791a 100644
> > --- a/fs/btrfs/backref.c
> > +++ b/fs/btrfs/backref.c
>
> [ ... ]
>
> > @@ -2107,6 +2108,39 @@ int btrfs_find_one_extref(struct btrfs_root *root, u64 inode_objectid,
> >       return ret;
> >  }
> >
> > +static int copy_resolved_iref_to_buf(struct btrfs_root *fs_root,
> > +                                  struct extent_buffer *eb, char *dest,
> > +                                  u64 parent, unsigned long name_off,
> > +                                  u32 name_len, s64 *bytes_left)
> > +{
> > +     struct btrfs_fs_info *fs_info = fs_root->fs_info;
> > +     struct fscrypt_str fname = FSTR_INIT(NULL, 0);
> > +     int ret;
> > +
> > +     /* No encryption, just copy the name in. */
> > +     if (!btrfs_fs_incompat(fs_info, ENCRYPT)) {
> > +             *bytes_left -= name_len;
> > +             if (*bytes_left >= 0)
> > +                     read_extent_buffer(eb, dest + *bytes_left, name_off, name_len);
> > +             return 0;
> > +     }
> > +
> > +     ret = fscrypt_fname_alloc_buffer(BTRFS_NAME_LEN, &fname);
> > +     if (ret)
> > +             return ret;
> > +
> > +     ret = btrfs_decrypt_name(fs_root, eb, name_off, name_len, parent, &fname);
> > +     if (ret)
> > +             goto out;
> > +
> > +     *bytes_left -= fname.len;
> > +     if (*bytes_left >= 0)
> > +             memcpy(dest + *bytes_left, fname.name, fname.len);
> > +out:
> > +     fscrypt_fname_free_buffer(&fname);
> > +     return ret;
> > +}
> > +
> >  /*
> >   * this iterates to turn a name (from iref/extref) into a full filesystem path.
> >   * Elements of the path are separated by '/' and the path is guaranteed to be
> > @@ -2138,10 +2172,10 @@ char *btrfs_ref_to_path(struct btrfs_root *fs_root, struct btrfs_path *path,
> >               dest[bytes_left] = '\0';
> >
> >       while (1) {
> > -             bytes_left -= name_len;
> > -             if (bytes_left >= 0)
> > -                     read_extent_buffer(eb, dest + bytes_left,
> > -                                        name_off, name_len);
> > +             ret = copy_resolved_iref_to_buf(fs_root, eb, dest, parent,
> > +                                             name_off, name_len, &bytes_left);
> > +             if (ret)
> > +                     break;
> >               if (eb != eb_in) {
> >                       if (!path->skip_locking)
> >                               btrfs_tree_read_unlock(eb);
>
> Does this code leak the extent buffer when copy_resolved_iref_to_buf()
> fails? Looking at the loop structure in btrfs_ref_to_path(), when
> copy_resolved_iref_to_buf() returns an error and we break at line 2178,
> the cleanup code that follows never executes:
>
>     if (eb != eb_in) {
>         if (!path->skip_locking)
>             btrfs_tree_read_unlock(eb);
>         free_extent_buffer(eb);
>     }
>
> This can happen in subsequent iterations where eb != eb_in (the extent
> buffer was detached from path->nodes[0] and set to NULL in the previous
> iteration). The cleanup at the end of the function (btrfs_release_path()
> at line 2216) cannot free eb since it's no longer in the path.

Right, the break should be placed only after the cleanup.

--- a/fs/btrfs/backref.c
+++ b/fs/btrfs/backref.c
@@ -2174,13 +2174,14 @@ char *btrfs_ref_to_path(struct btrfs_root
*fs_root, struct btrfs_path *path,
     while (1) {
         ret = copy_resolved_iref_to_buf(fs_root, eb, dest, parent,
                         name_off, name_len, &bytes_left);
-        if (ret)
-            break;
         if (eb != eb_in) {
             if (!path->skip_locking)
                 btrfs_tree_read_unlock(eb);
             free_extent_buffer(eb);
         }
+        if (ret)
+            break;
+
         ret = btrfs_find_item(fs_root, path, parent, 0,
                 BTRFS_INODE_REF_KEY, &found_key);
         if (ret > 0)

Thanks.

--nX

> This could be triggered when decryption fails in copy_resolved_iref_to_buf()
> due to btrfs_iget() error, fscrypt_fname_alloc_buffer() ENOMEM, or
> fscrypt_prepare_readdir() failure on an encrypted filesystem.
>
> Before this patch, the name copy operation (read_extent_buffer) could not
> fail, so the cleanup always happened before any error check.
>

On Sun, 8 Feb 2026 at 16:04, Chris Mason <clm@meta.com> wrote:
>
> Daniel Vacek <neelx@suse.com> wrote:
> > From: Josef Bacik <josef@toxicpanda.com>
> >
> > We use this helper for inode-resolve and path resolution in send, so
> > update this helper to properly decrypt any encrypted names it finds.
> >
>
> Hi everyone,
>
> I'm running the btrfs for-next branch, along with the encryption patch set
> through my AI review prompts:
>
> https://github.com/masoncl/review-prompts
>
> I'm working on some btrfs specific instructions, but hopefully these reviews
> are already useful.  I've kept the headers to make it easier to double check
> that I'm actually replying to the right email, but please let me know if any
> parts of this are incorrect:
>
>
> commit 473bbb12052f731a4e2ce3c48e7d7e9f2510528b
> Author: Josef Bacik <josef@toxicpanda.com>
>
> btrfs: make btrfs_ref_to_path handle encrypted filenames
>
> This commit updates btrfs_ref_to_path() to handle encrypted filenames by
> introducing a new copy_resolved_iref_to_buf() helper that can decrypt
> names when the filesystem has encryption enabled.
>
> > diff --git a/fs/btrfs/backref.c b/fs/btrfs/backref.c
> > index 9bb406f7dd302..577c3ef87791a 100644
> > --- a/fs/btrfs/backref.c
> > +++ b/fs/btrfs/backref.c
>
> [ ... ]
>
> > @@ -2107,6 +2108,39 @@ int btrfs_find_one_extref(struct btrfs_root *root, u64 inode_objectid,
> >       return ret;
> >  }
> >
> > +static int copy_resolved_iref_to_buf(struct btrfs_root *fs_root,
> > +                                  struct extent_buffer *eb, char *dest,
> > +                                  u64 parent, unsigned long name_off,
> > +                                  u32 name_len, s64 *bytes_left)
> > +{
> > +     struct btrfs_fs_info *fs_info = fs_root->fs_info;
> > +     struct fscrypt_str fname = FSTR_INIT(NULL, 0);
> > +     int ret;
> > +
> > +     /* No encryption, just copy the name in. */
> > +     if (!btrfs_fs_incompat(fs_info, ENCRYPT)) {
> > +             *bytes_left -= name_len;
> > +             if (*bytes_left >= 0)
> > +                     read_extent_buffer(eb, dest + *bytes_left, name_off, name_len);
> > +             return 0;
> > +     }
> > +
> > +     ret = fscrypt_fname_alloc_buffer(BTRFS_NAME_LEN, &fname);
> > +     if (ret)
> > +             return ret;
> > +
> > +     ret = btrfs_decrypt_name(fs_root, eb, name_off, name_len, parent, &fname);
> > +     if (ret)
> > +             goto out;
> > +
> > +     *bytes_left -= fname.len;
> > +     if (*bytes_left >= 0)
> > +             memcpy(dest + *bytes_left, fname.name, fname.len);
> > +out:
> > +     fscrypt_fname_free_buffer(&fname);
> > +     return ret;
> > +}
> > +
> >  /*
> >   * this iterates to turn a name (from iref/extref) into a full filesystem path.
> >   * Elements of the path are separated by '/' and the path is guaranteed to be
> > @@ -2138,10 +2172,10 @@ char *btrfs_ref_to_path(struct btrfs_root *fs_root, struct btrfs_path *path,
> >               dest[bytes_left] = '\0';
> >
> >       while (1) {
> > -             bytes_left -= name_len;
> > -             if (bytes_left >= 0)
> > -                     read_extent_buffer(eb, dest + bytes_left,
> > -                                        name_off, name_len);
> > +             ret = copy_resolved_iref_to_buf(fs_root, eb, dest, parent,
> > +                                             name_off, name_len, &bytes_left);
> > +             if (ret)
> > +                     break;
> >               if (eb != eb_in) {
> >                       if (!path->skip_locking)
> >                               btrfs_tree_read_unlock(eb);
>
> Does this code leak the extent buffer when copy_resolved_iref_to_buf()
> fails? Looking at the loop structure in btrfs_ref_to_path(), when
> copy_resolved_iref_to_buf() returns an error and we break at line 2178,
> the cleanup code that follows never executes:
>
>     if (eb != eb_in) {
>         if (!path->skip_locking)
>             btrfs_tree_read_unlock(eb);
>         free_extent_buffer(eb);
>     }
>
> This can happen in subsequent iterations where eb != eb_in (the extent
> buffer was detached from path->nodes[0] and set to NULL in the previous
> iteration). The cleanup at the end of the function (btrfs_release_path()
> at line 2216) cannot free eb since it's no longer in the path.
>
> This could be triggered when decryption fails in copy_resolved_iref_to_buf()
> due to btrfs_iget() error, fscrypt_fname_alloc_buffer() ENOMEM, or
> fscrypt_prepare_readdir() failure on an encrypted filesystem.
>
> Before this patch, the name copy operation (read_extent_buffer) could not
> fail, so the cleanup always happened before any error check.
>

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

end of thread, other threads:[~2026-03-25 15:27 UTC | newest]

Thread overview: 77+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-06 18:22 [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 01/43] fscrypt: add per-extent encryption support Daniel Vacek
2026-02-21 22:11   ` Eric Biggers
2026-02-06 18:22 ` [PATCH v6 02/43] fscrypt: allow inline encryption for extent based encryption Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 03/43] fscrypt: add a __fscrypt_file_open helper Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 04/43] fscrypt: conditionally don't wipe mk secret until the last active user is done Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 05/43] blk-crypto: add a process_bio callback Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 06/43] fscrypt: add a process_bio hook to fscrypt_operations Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 07/43] fscrypt: expose fscrypt_nokey_name Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 08/43] fscrypt: add documentation about extent encryption Daniel Vacek
2026-02-06 18:43   ` Randy Dunlap
2026-02-17 14:48     ` Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 09/43] btrfs: add infrastructure for safe em freeing Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 10/43] btrfs: start using fscrypt hooks Daniel Vacek
2026-02-08 15:44   ` Chris Mason
2026-02-17 15:26     ` Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 11/43] btrfs: add inode encryption contexts Daniel Vacek
2026-02-08 15:36   ` Chris Mason
2026-02-18 13:18     ` Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 12/43] btrfs: add new FEATURE_INCOMPAT_ENCRYPT flag Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 13/43] btrfs: adapt readdir for encrypted and nokey names Daniel Vacek
2026-02-08 15:35   ` Chris Mason
2026-02-18 14:05     ` Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 14/43] btrfs: handle " Daniel Vacek
2026-02-08 15:28   ` Chris Mason
2026-02-18 14:50     ` Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 15/43] btrfs: implement fscrypt ioctls Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 16/43] btrfs: select encryption dependencies if FS_ENCRYPTION Daniel Vacek
2026-02-08 15:22   ` Chris Mason
2026-02-18 15:02     ` Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 17/43] btrfs: add get_devices hook for fscrypt Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 18/43] btrfs: set file extent encryption excplicitly Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 19/43] btrfs: add fscrypt_info and encryption_type to extent_map Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 20/43] btrfs: add fscrypt_info and encryption_type to ordered_extent Daniel Vacek
2026-02-08 15:18   ` Chris Mason
2026-02-18 15:29     ` Daniel Vacek
2026-02-18 15:50       ` Chris Mason
2026-02-18 16:11         ` Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 21/43] btrfs: plumb through setting the fscrypt_info for ordered extents Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 22/43] btrfs: populate the ordered_extent with the fscrypt context Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 23/43] btrfs: keep track of fscrypt info and orig_start for dio reads Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 24/43] btrfs: add extent encryption context tree item type Daniel Vacek
2026-02-08 15:16   ` Chris Mason
2026-02-18 17:25     ` Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 25/43] btrfs: pass through fscrypt_extent_info to the file extent helpers Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 26/43] btrfs: implement the fscrypt extent encryption hooks Daniel Vacek
2026-02-06 18:22 ` [PATCH v6 27/43] btrfs: setup fscrypt_extent_info for new extents Daniel Vacek
2026-02-06 18:23 ` [PATCH v6 28/43] btrfs: populate ordered_extent with the orig offset Daniel Vacek
2026-02-08 15:12   ` Chris Mason
2026-03-03 13:42     ` Daniel Vacek
2026-02-06 18:23 ` [PATCH v6 29/43] btrfs: set the bio fscrypt context when applicable Daniel Vacek
2026-02-06 18:23 ` [PATCH v6 30/43] btrfs: add a bio argument to btrfs_csum_one_bio Daniel Vacek
2026-02-06 18:23 ` [PATCH v6 31/43] btrfs: limit encrypted writes to 256 segments Daniel Vacek
2026-02-06 18:23 ` [PATCH v6 32/43] btrfs: implement process_bio cb for fscrypt Daniel Vacek
2026-02-08 15:10   ` Chris Mason
2026-03-24  9:36     ` Daniel Vacek
2026-02-06 18:23 ` [PATCH v6 33/43] btrfs: implement read repair for encryption Daniel Vacek
2026-02-08 15:08   ` Chris Mason
2026-03-25 14:17     ` Daniel Vacek
2026-02-06 18:23 ` [PATCH v6 34/43] btrfs: add test_dummy_encryption support Daniel Vacek
2026-02-06 18:23 ` [PATCH v6 35/43] btrfs: make btrfs_ref_to_path handle encrypted filenames Daniel Vacek
2026-02-08 15:02   ` Chris Mason
2026-03-25 15:27     ` Daniel Vacek
2026-02-06 18:23 ` [PATCH v6 36/43] btrfs: deal with encrypted symlinks in send Daniel Vacek
2026-02-06 18:23 ` [PATCH v6 37/43] btrfs: decrypt file names for send Daniel Vacek
2026-02-06 18:23 ` [PATCH v6 38/43] btrfs: load the inode context before sending writes Daniel Vacek
2026-02-06 18:23 ` [PATCH v6 39/43] btrfs: set the appropriate free space settings in reconfigure Daniel Vacek
2026-02-06 18:23 ` [PATCH v6 40/43] btrfs: support encryption with log replay Daniel Vacek
2026-02-06 18:23 ` [PATCH v6 41/43] btrfs: disable auto defrag on encrypted files Daniel Vacek
2026-02-06 18:23 ` [PATCH v6 42/43] btrfs: disable encryption on RAID5/6 Daniel Vacek
2026-02-08 13:14   ` Chris Mason
2026-02-06 18:23 ` [PATCH v6 43/43] btrfs: disable send if we have encryption enabled Daniel Vacek
2026-02-06 18:42 ` [PATCH v6 00/43] btrfs: add fscrypt support Daniel Vacek
2026-02-21 20:56 ` Eric Biggers
2026-02-27 15:50   ` Daniel Vacek
2026-02-27 22:26     ` Neal Gompa
2026-02-28  7:57       ` Daniel Vacek

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