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/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