@@ -33,6 +33,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"
@@ -1674,6 +1675,7 @@ do { \
#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)
@@ -1690,6 +1692,7 @@ do { \
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)
@@ -3,8 +3,13 @@
* Copyright (C) 2020 Facebook
*/
+#include <linux/iversion.h>
#include "ctree.h"
+#include "btrfs_inode.h"
+#include "disk-io.h"
#include "fscrypt.h"
+#include "transaction.h"
+#include "xattr.h"
/* fscrypt_match_name() but for an extent_buffer. */
bool btrfs_fscrypt_match_name(const struct fscrypt_name *fname,
@@ -31,5 +36,160 @@ bool btrfs_fscrypt_match_name(const struct fscrypt_name *fname,
return !memcmp(digest, nokey_name->sha256, sizeof(digest));
}
+static int btrfs_fscrypt_get_context(struct inode *inode, void *ctx, size_t len)
+{
+ struct btrfs_root *root = BTRFS_I(inode)->root;
+ struct inode *put_inode = NULL;
+ struct btrfs_key key;
+ struct btrfs_path *path;
+ struct extent_buffer *leaf;
+ unsigned long ptr;
+ int ret;
+
+ if (S_ISREG(inode->i_mode) &&
+ (btrfs_root_flags(&root->root_item) & BTRFS_ROOT_SUBVOL_FSCRYPT)) {
+ /* TODO: maybe cache the item */
+ 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;
+
+ key = (struct btrfs_key) {
+ .objectid = btrfs_ino(BTRFS_I(inode)),
+ .type = BTRFS_FSCRYPT_CTXT_ITEM_KEY,
+ .offset = 0,
+ };
+
+ 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 can get the policy for
+ * regular files from the root inode.
+ */
+ if (S_ISREG(inode->i_mode) &&
+ (btrfs_root_flags(&root->root_item) & BTRFS_ROOT_SUBVOL_FSCRYPT))
+ 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;
+
+ /*
+ * 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);
+ }
+out:
+ if (fs_data)
+ return ret;
+
+ if (ret)
+ btrfs_abort_transaction(trans, ret);
+ else
+ btrfs_end_transaction(trans);
+ return ret;
+}
+
+static bool btrfs_fscrypt_empty_dir(struct inode *inode)
+{
+ /*
+ * We don't care about turning on encryption on a non-empty directory
+ * so we always return true.
+ */
+ return true;
+}
+
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,
};
@@ -6282,6 +6282,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);
@@ -6315,6 +6343,8 @@ int btrfs_new_inode_prepare(struct btrfs_new_inode_args *args,
if (dir->i_security)
(*trans_num_items)++;
#endif
+ if (args->encrypt)
+ (*trans_num_items)++; /* 1 to add fscrypt item */
if (args->orphan) {
/* 1 to add orphan item */
(*trans_num_items)++;
@@ -6568,6 +6598,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,13 @@ 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)) {
+ btrfs_warn(fs_info,
+ "cannot snapshot encrypted volume to unencrypted destination");
+ return -EXDEV;
+ }
+
pending_snapshot = kzalloc(sizeof(*pending_snapshot), GFP_KERNEL);
if (!pending_snapshot)
return -ENOMEM;
@@ -1124,6 +1124,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;
@@ -144,6 +144,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 */
@@ -633,6 +635,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
@@ -788,6 +792,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