From nobody Mon Feb 9 10:12:44 2026 Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4660E42EEB8 for ; Fri, 6 Feb 2026 18:24:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.135.223.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770402298; cv=none; b=RvTtuqT3Qms/O/0XQ8ew5nG18j0AbAQt8a6BcvGzYNPtEtyuIin08wqmgWfH3vs1BQvGXKHOdddlrsZiNLVFdRK3U0oXrRo0wiP0wd8tWzXu+e7c8ZTTuBrvoCbryZB2bC3X2INrk5sU6abbudjq5I07qqm+UFq/wPNwlI7xs4w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770402298; c=relaxed/simple; bh=FfvPlXAyWoUxGB8f+0UO9dIt1mwkYNum1bSIDzve2Ic=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Czr1CmHIYM3JoZECTEtblp2Mdk61Hl4+QGYeCar6BKfvS6lx6dJYNuUNxbu27hOsFwU8tcLpdidRo52r6v0crAuEn0Utqd+ZlWnY+DH/ksnhyvpOT1WJjGGDmCwhHHs8PYoT2iLJCy4qXslOzWHD4uWFg302vrOzM3eD5fLPH2g= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com; spf=pass smtp.mailfrom=suse.com; arc=none smtp.client-ip=195.135.223.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=suse.com Received: from imap1.dmz-prg2.suse.org (imap1.dmz-prg2.suse.org [IPv6:2a07:de40:b281:104:10:150:64:97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by smtp-out2.suse.de (Postfix) with ESMTPS id 043805BD2E; Fri, 6 Feb 2026 18:24:11 +0000 (UTC) Authentication-Results: smtp-out2.suse.de; none Received: from imap1.dmz-prg2.suse.org (localhost [127.0.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by imap1.dmz-prg2.suse.org (Postfix) with ESMTPS id CBA723EA63; Fri, 6 Feb 2026 18:24:10 +0000 (UTC) Received: from dovecot-director2.suse.de ([2a07:de40:b281:106:10:150:64:167]) by imap1.dmz-prg2.suse.org with ESMTPSA id wGH9MMoxhmkTCQAAD6G6ig (envelope-from ); Fri, 06 Feb 2026 18:24:10 +0000 From: Daniel Vacek To: Chris Mason , Josef Bacik , Eric Biggers , "Theodore Y. Ts'o" , Jaegeuk Kim , Jens Axboe , David Sterba Cc: linux-block@vger.kernel.org, Daniel Vacek , linux-fscrypt@vger.kernel.org, linux-btrfs@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v6 26/43] btrfs: implement the fscrypt extent encryption hooks Date: Fri, 6 Feb 2026 19:22:58 +0100 Message-ID: <20260206182336.1397715-27-neelx@suse.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260206182336.1397715-1-neelx@suse.com> References: <20260206182336.1397715-1-neelx@suse.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Rspamd-Pre-Result: action=no action; module=replies; Message is reply to one we originated X-Spamd-Result: default: False [-4.00 / 50.00]; REPLY(-4.00)[] X-Spam-Flag: NO X-Spam-Score: -4.00 X-Rspamd-Queue-Id: 043805BD2E X-Rspamd-Pre-Result: action=no action; module=replies; Message is reply to one we originated X-Rspamd-Action: no action X-Rspamd-Server: rspamd2.dmz-prg2.suse.org X-Spam-Level: Content-Type: text/plain; charset="utf-8" From: Josef Bacik 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 Signed-off-by: Daniel Vacek --- v5: https://lore.kernel.org/linux-btrfs/30eaad31964c88c3497a0c5bc8f2c727c1d= c763a.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" =20 static struct kmem_cache *btrfs_inode_defrag_cachep; =20 @@ -720,6 +721,11 @@ static struct extent_map *defrag_get_extent(struct btr= fs_inode *inode, if (ret > 0) goto not_found; } + btrfs_release_path(&path); + + ret =3D btrfs_fscrypt_load_extent_info(inode, &path, &key, em); + if (ret) + goto err; return em; =20 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" =20 /* * 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 =3D BTRFS_FSCRYPT_CTX_KEY; + ret =3D 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); + } =20 ret =3D 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; } =20 +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 =3D=3D EXTENT_MAP_HOLE) + return 0; + if (btrfs_extent_map_encryption(em) !=3D BTRFS_ENCRYPTION_FSCRYPT) + return 0; + + key->type =3D BTRFS_FSCRYPT_CTX_KEY; + ret =3D btrfs_search_slot(NULL, inode->root, key, path, 0, 0); + leaf =3D path->nodes[0]; + slot =3D path->slots[0]; + if (ret) { + btrfs_err(leaf->fs_info, + "missing or error searching encryption context item in leaf: root=3D%llu = block=3D%llu slot=3D%d ino=3D%llu file_offset=3D%llu, err %i", + btrfs_header_owner(leaf), btrfs_header_bytenr(leaf), slot, + key->objectid, key->offset, ret); + btrfs_release_path(path); + return ret; + } + + size =3D 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=3D%llu blo= ck=3D%llu slot=3D%d ino=3D%llu file_offset=3D%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 =3D btrfs_item_ptr_offset(leaf, slot), + read_extent_buffer(leaf, ctx, offset, size); + btrfs_release_path(path); + + nofs_flag =3D memalloc_nofs_save(); + info =3D 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 =3D info; + return 0; +} + +void btrfs_fscrypt_save_extent_info(struct btrfs_path *path, u8 *ctx, unsi= gned long size) +{ + struct extent_buffer *leaf =3D path->nodes[0]; + unsigned long offset =3D btrfs_item_ptr_offset(leaf, path->slots[0]); + + ASSERT(size <=3D 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 =3D 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 =3D { .inode_info_offs =3D (int)offsetof(struct btrfs_inode, i_crypt_info) - (int)offsetof(struct btrfs_inode, vfs_inode), + .has_per_extent_encryption =3D 1, .get_context =3D btrfs_fscrypt_get_context, .set_context =3D btrfs_fscrypt_set_context, .empty_dir =3D 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 *le= af, 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, unsi= gned long size); +ssize_t btrfs_fscrypt_context_for_new_extent(struct btrfs_inode *inode, + struct fscrypt_extent_info *info, + u8 *ctx); =20 #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 fscry= pt_name *fname, return false; return !memcmp_extent_buffer(leaf, fname->disk_name.name, de_name, de_nam= e_len); } + +static inline ssize_t btrfs_fscrypt_context_for_new_extent(struct btrfs_in= ode *inode, + struct fscrypt_extent_info *info, + u8 *ctx) +{ + if (!info) + return 0; + return -EINVAL; +} + #endif /* CONFIG_FS_ENCRYPTION */ =20 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 =3D btrfs_stack_file_extent_num_bytes(stack_fi); u64 ram_bytes =3D btrfs_stack_file_extent_ram_bytes(stack_fi); struct btrfs_drop_extents_args drop_args =3D { 0 }; + u8 fscrypt_ctx[FSCRYPT_SET_CONTEXT_MAX_SIZE]; + ssize_t fscrypt_context_size; int ret; =20 + fscrypt_context_size =3D btrfs_fscrypt_context_for_new_extent(inode, + fscrypt_info, + fscrypt_ctx); + if (fscrypt_context_size < 0) + return (int)fscrypt_context_size; + path =3D btrfs_alloc_path(); if (!path) return -ENOMEM; @@ -3078,6 +3086,16 @@ static int insert_reserved_file_extent(struct btrfs_= trans_handle *trans, =20 btrfs_release_path(path); =20 + if (fscrypt_context_size) { + ins.objectid =3D btrfs_ino(inode); + ins.type =3D BTRFS_FSCRYPT_CTX_KEY; + ins.offset =3D file_pos; + ret =3D 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_ino= de *inode, goto out; } =20 + ret =3D btrfs_fscrypt_load_extent_info(inode, path, &found_key, em); + if (ret > 0) + ret =3D -EIO; + if (ret < 0) + goto out; + write_lock(&em_tree->lock); ret =3D btrfs_add_extent_mapping(inode, &em, start, len); write_unlock(&em_tree->lock); @@ -9219,6 +9243,8 @@ static struct btrfs_trans_handle *insert_prealloc_fil= e_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 =3D trans_in; struct btrfs_path *path; @@ -9252,6 +9278,14 @@ static struct btrfs_trans_handle *insert_prealloc_fi= le_extent( return trans; } =20 + fscrypt_context_size =3D btrfs_fscrypt_context_for_new_extent(inode, + fscrypt_info, + fscrypt_ctx); + if (fscrypt_context_size < 0) { + ret =3D (int)fscrypt_context_size; + goto free_qgroup; + } + extent_info.disk_offset =3D start; extent_info.disk_len =3D len; extent_info.data_offset =3D 0; @@ -9262,6 +9296,8 @@ static struct btrfs_trans_handle *insert_prealloc_fil= e_extent( extent_info.update_times =3D true; extent_info.qgroup_reserved =3D qgroup_released; extent_info.insertions =3D 0; + extent_info.fscrypt_ctx =3D fscrypt_ctx; + extent_info.fscrypt_context_size =3D fscrypt_context_size; =20 path =3D 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 =3D 0, diskl =3D 0; u64 datao =3D 0, datal =3D 0; - u8 comp; + u8 comp, encryption; u64 drop_start; =20 /* 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 =3D btrfs_item_ptr(leaf, slot, struct btrfs_file_extent_item); extent_gen =3D btrfs_file_extent_generation(leaf, extent); + encryption =3D btrfs_file_extent_encryption(leaf, extent); comp =3D btrfs_file_extent_compression(leaf, extent); type =3D btrfs_file_extent_type(leaf, extent); if (type =3D=3D BTRFS_FILE_EXTENT_REG || @@ -478,6 +479,7 @@ static int btrfs_clone(struct inode *src, struct inode = *inode, if (type =3D=3D BTRFS_FILE_EXTENT_REG || type =3D=3D BTRFS_FILE_EXTENT_PREALLOC) { struct btrfs_replace_extent_info clone_info; + u8 fscrypt_ctx[FSCRYPT_SET_CONTEXT_MAX_SIZE]; =20 /* * a | --- range to clone ---| b @@ -494,6 +496,43 @@ static int btrfs_clone(struct inode *src, struct inode= *inode, datal -=3D off - key.offset; } =20 + if (encryption =3D=3D BTRFS_ENCRYPTION_FSCRYPT) { + unsigned long offset; + + key.type =3D BTRFS_FSCRYPT_CTX_KEY; + ret =3D btrfs_search_slot(NULL, BTRFS_I(src)->root, &key, path, 0, 0); + if (ret < 0) + goto out; + leaf =3D path->nodes[0]; + slot =3D path->slots[0]; + if (ret) { + btrfs_err(leaf->fs_info, + "missing or error searching encryption context item in leaf: root=3D%llu = block=3D%llu slot=3D%d ino=3D%llu file_offset=3D%llu, err %i", + btrfs_header_owner(leaf), + btrfs_header_bytenr(leaf), slot, + key.objectid, key.offset, ret); + goto out; + } + + size =3D 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=3D%llu blo= ck=3D%llu slot=3D%d ino=3D%llu file_offset=3D%llu, size %u (too big)", + btrfs_header_owner(leaf), + btrfs_header_bytenr(leaf), slot, + key.objectid, key.offset, size); + ret =3D -EIO; + goto out; + } + + offset =3D btrfs_item_ptr_offset(leaf, slot), + read_extent_buffer(leaf, fscrypt_ctx, offset, size); + btrfs_release_path(path); + key.type =3D BTRFS_EXTENT_DATA_KEY; + } else { + size =3D 0; + } + clone_info.disk_offset =3D disko; clone_info.disk_len =3D diskl; clone_info.data_offset =3D datao; @@ -502,6 +541,8 @@ static int btrfs_clone(struct inode *src, struct inode = *inode, clone_info.extent_buf =3D buf; clone_info.is_new_extent =3D false; clone_info.update_times =3D !no_time_update; + clone_info.fscrypt_ctx =3D fscrypt_ctx; + clone_info.fscrypt_context_size =3D size; ret =3D 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" =20 #define MAX_CONFLICT_INODES 10 =20 @@ -5118,6 +5119,8 @@ static int log_one_extent(struct btrfs_trans_handle *= trans, struct btrfs_file_extent_item fi =3D { 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 =3D em->offset; u64 block_start =3D btrfs_extent_map_block_start(em); @@ -5125,6 +5128,12 @@ static int log_one_extent(struct btrfs_trans_handle = *trans, int ret; u8 encryption =3D btrfs_extent_map_encryption(em); =20 + fscrypt_context_size =3D 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, =20 btrfs_release_path(path); =20 + if (fscrypt_context_size) { + key.objectid =3D btrfs_ino(inode); + key.type =3D BTRFS_FSCRYPT_CTX_KEY; + key.offset =3D em->start; + ret =3D 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; } =20 --=20 2.51.0