@@ -32,6 +32,7 @@
#include "extent-io-tree.h"
#include "extent_io.h"
#include "extent_map.h"
+#include "fscrypt.h"
#include "async-thread.h"
#include "block-rsv.h"
#include "locking.h"
@@ -1,7 +1,177 @@
// SPDX-License-Identifier: GPL-2.0
+#include <linux/iversion.h>
#include "ctree.h"
+#include "btrfs_inode.h"
+#include "disk-io.h"
#include "fscrypt.h"
+#include "transaction.h"
+#include "xattr.h"
+
+static int btrfs_fscrypt_get_context(struct inode *inode, void *ctx, size_t len)
+{
+ struct btrfs_root *root = BTRFS_I(inode)->root;
+ struct btrfs_key key = {
+ .objectid = btrfs_ino(BTRFS_I(inode)),
+ .type = BTRFS_FSCRYPT_CTXT_ITEM_KEY,
+ .offset = 0,
+ };
+ struct inode *put_inode = NULL;
+ struct btrfs_path *path;
+ struct extent_buffer *leaf;
+ unsigned long ptr;
+ int ret;
+
+
+ if (btrfs_root_flags(&root->root_item) & BTRFS_ROOT_SUBVOL_FSCRYPT) {
+ inode = btrfs_iget(inode->i_sb, BTRFS_FIRST_FREE_OBJECTID,
+ root);
+ if (IS_ERR(inode))
+ return PTR_ERR(inode);
+ put_inode = inode;
+ }
+
+ path = btrfs_alloc_path();
+ if (!path)
+ return -ENOMEM;
+
+ ret = btrfs_search_slot(NULL, BTRFS_I(inode)->root, &key, path, 0, 0);
+ if (ret) {
+ len = -EINVAL;
+ 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);
+ iput(put_inode);
+ return len;
+}
+
+static int btrfs_fscrypt_set_context(struct inode *inode, const void *ctx,
+ size_t len, void *fs_data)
+{
+ struct btrfs_root *root = BTRFS_I(inode)->root;
+ struct btrfs_trans_handle *trans;
+ int is_subvolume = inode->i_ino == BTRFS_FIRST_FREE_OBJECTID;
+ int ret;
+ struct btrfs_path *path;
+ struct btrfs_key key = {
+ .objectid = btrfs_ino(BTRFS_I(inode)),
+ .type = BTRFS_FSCRYPT_CTXT_ITEM_KEY,
+ .offset = 0,
+ };
+
+ /*
+ * If the whole subvolume is encrypted, we expect that all children
+ * have the same policy.
+ */
+ if (btrfs_root_flags(&root->root_item) & BTRFS_ROOT_SUBVOL_FSCRYPT) {
+ bool same_policy;
+ struct inode *root_inode = NULL;
+
+ root_inode = btrfs_iget(inode->i_sb, BTRFS_FIRST_FREE_OBJECTID,
+ root);
+ if (IS_ERR(inode))
+ return PTR_ERR(inode);
+ same_policy = fscrypt_have_same_policy(inode, root_inode);
+ iput(root_inode);
+ if (same_policy)
+ return 0;
+ }
+
+ if (fs_data) {
+ /*
+ * We are setting the context as part of an existing
+ * transaction. This happens when we are inheriting the context
+ * for a new inode.
+ */
+ trans = fs_data;
+ } else {
+ /*
+ * 1 for the inode item
+ * 1 for the fscrypt item
+ * 1 for the root item if the inode is a subvolume
+ */
+ trans = btrfs_start_transaction(root, 2 + is_subvolume);
+ if (IS_ERR(trans))
+ return PTR_ERR(trans);
+ }
+
+ path = btrfs_alloc_path();
+ if (!path)
+ return -ENOMEM;
+ ret = btrfs_search_slot(trans, BTRFS_I(inode)->root, &key, path, 0, 1);
+ if (ret == 0) {
+ struct extent_buffer *leaf = path->nodes[0];
+ unsigned long 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(leaf);
+ btrfs_free_path(path);
+ goto out;
+ } else if (ret < 0) {
+ goto out;
+ }
+ btrfs_free_path(path);
+
+ ret = btrfs_insert_item(trans, BTRFS_I(inode)->root, &key, (void *) ctx, len);
+ if (ret)
+ goto out;
+
+ BTRFS_I(inode)->flags |= BTRFS_INODE_FSCRYPT_CONTEXT;
+ btrfs_sync_inode_flags_to_i_flags(inode);
+ inode_inc_iversion(inode);
+ inode->i_ctime = current_time(inode);
+ ret = btrfs_update_inode(trans, root, BTRFS_I(inode));
+ if (ret)
+ goto out_fatal;
+
+ /*
+ * For new subvolumes, the root item is already initialized with
+ * the BTRFS_ROOT_SUBVOL_FSCRYPT flag.
+ */
+ if (!fs_data && is_subvolume) {
+ u64 root_flags = btrfs_root_flags(&root->root_item);
+
+ btrfs_set_root_flags(&root->root_item,
+ root_flags |
+ BTRFS_ROOT_SUBVOL_FSCRYPT);
+ ret = btrfs_update_root(trans, root->fs_info->tree_root,
+ &root->root_key,
+ &root->root_item);
+ }
+ if (!ret)
+ goto out;
+
+out_fatal:
+ if (fs_data)
+ return ret;
+
+ btrfs_abort_transaction(trans, ret);
+
+out:
+ if (fs_data)
+ return ret;
+
+ btrfs_end_transaction(trans);
+ 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 = {
+ .key_prefix = "btrfs:",
+ .get_context = btrfs_fscrypt_get_context,
+ .set_context = btrfs_fscrypt_set_context,
+ .empty_dir = btrfs_fscrypt_empty_dir,
};
@@ -6283,6 +6283,34 @@ 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 (IS_ENCRYPTED(dir) &&
+ !(BTRFS_I(dir)->flags & BTRFS_INODE_FSCRYPT_CONTEXT)) {
+ struct inode *root_inode;
+ bool encrypt;
+
+ root_inode = btrfs_iget(inode->i_sb, BTRFS_FIRST_FREE_OBJECTID,
+ BTRFS_I(dir)->root);
+ if (IS_ERR(root_inode))
+ return PTR_ERR(root_inode);
+ /*
+ * TODO: perhaps instead of faking making a new dir to get a
+ * new context, it would be better to expose
+ * fscrypt_setup_encryption_info() for our use.
+ */
+ ret = fscrypt_prepare_new_inode(root_inode, dir, &encrypt);
+ if (!ret) {
+ ret = fscrypt_set_context(dir, NULL);
+ if (ret)
+ fscrypt_put_encryption_info(dir);
+ }
+ iput(root_inode);
+ if (ret)
+ return ret;
+ }
+
if (!args->orphan) {
ret = fscrypt_setup_filename(dir, &args->dentry->d_name, 0,
&args->fname);
@@ -6316,6 +6344,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)++;
@@ -6569,6 +6600,14 @@ int btrfs_create_new_inode(struct btrfs_trans_handle *trans,
}
}
+ if (args->encrypt) {
+ ret = fscrypt_set_context(inode, trans);
+ if (ret) {
+ btrfs_abort_transaction(trans, ret);
+ goto discard;
+ }
+ }
+
inode_tree_add(inode);
trace_btrfs_inode_new(inode);
@@ -658,7 +658,8 @@ static noinline int create_subvol(struct user_namespace *mnt_userns,
fs_info->nodesize);
btrfs_set_stack_inode_mode(inode_item, S_IFDIR | 0755);
- btrfs_set_root_flags(root_item, 0);
+ btrfs_set_root_flags(root_item, new_inode_args.encrypt ?
+ BTRFS_ROOT_SUBVOL_FSCRYPT : 0);
btrfs_set_root_limit(root_item, 0);
btrfs_set_stack_inode_flags(inode_item, BTRFS_INODE_ROOT_ITEM_INIT);
@@ -787,6 +788,10 @@ static int create_snapshot(struct btrfs_root *root, struct inode *dir,
return -ETXTBSY;
}
+ if ((btrfs_root_flags(&root->root_item) & BTRFS_ROOT_SUBVOL_FSCRYPT) &&
+ !IS_ENCRYPTED(dir))
+ return -EXDEV;
+
pending_snapshot = kzalloc(sizeof(*pending_snapshot), GFP_KERNEL);
if (!pending_snapshot)
return -ENOMEM;
@@ -1120,6 +1120,7 @@ static int check_root_item(struct extent_buffer *leaf, struct btrfs_key *key,
struct btrfs_fs_info *fs_info = leaf->fs_info;
struct btrfs_root_item ri = { 0 };
const u64 valid_root_flags = BTRFS_ROOT_SUBVOL_RDONLY |
+ BTRFS_ROOT_SUBVOL_FSCRYPT |
BTRFS_ROOT_SUBVOL_DEAD;
int ret;
@@ -160,6 +160,8 @@
#define BTRFS_VERITY_DESC_ITEM_KEY 36
#define BTRFS_VERITY_MERKLE_ITEM_KEY 37
+#define BTRFS_FSCRYPT_CTXT_ITEM_KEY 41
+
#define BTRFS_ORPHAN_ITEM_KEY 48
/* reserve 2-15 close to the inode for later flexibility */
@@ -398,6 +400,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_FSCRYPT_CONTEXT (1U << 12)
#define BTRFS_INODE_ROOT_ITEM_INIT (1U << 31)
@@ -414,6 +417,7 @@ static inline __u8 btrfs_dir_flags_to_ftype(__u8 flags)
BTRFS_INODE_NOATIME | \
BTRFS_INODE_DIRSYNC | \
BTRFS_INODE_COMPRESS | \
+ BTRFS_INODE_FSCRYPT_CONTEXT | \
BTRFS_INODE_ROOT_ITEM_INIT)
#define BTRFS_INODE_RO_VERITY (1U << 0)
@@ -858,6 +862,8 @@ struct btrfs_dir_item {
} __attribute__ ((__packed__));
#define BTRFS_ROOT_SUBVOL_RDONLY (1ULL << 0)
+/* Top-level subvolume directory is encrypted with fscrypt. */
+#define BTRFS_ROOT_SUBVOL_FSCRYPT (1ULL << 1)
/*
* Internal in-memory flag that a subvolume has been marked for deletion but
@@ -1013,6 +1019,12 @@ enum {
BTRFS_NR_FILE_EXTENT_TYPES = 3,
};
+enum {
+ BTRFS_ENCRYPTION_NONE,
+ BTRFS_ENCRYPTION_FSCRYPT,
+ BTRFS_NR_ENCRYPTION_TYPES,
+};
+
struct btrfs_file_extent_item {
/*
* transaction id that created this extent