@@ -40,6 +40,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)
{
@@ -396,12 +397,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_info;
+
err = fscrypt_init_keyring();
if (err)
- goto fail_free_info;
+ goto fail_free_extent_info;
return 0;
+fail_free_extent_info:
+ kmem_cache_destroy(fscrypt_extent_info_cachep);
fail_free_info:
kmem_cache_destroy(fscrypt_inode_info_cachep);
fail_free_queue:
@@ -30,6 +30,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
@@ -52,6 +54,19 @@ 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 policy and
+ * relevante information is stored in the inode, the per-extent information is
+ * simply the nonce that's used in as KDF input in conjunction with the inode
+ * context to derive a per-extent key for encryption.
+ */
+struct fscrypt_extent_context {
+ u8 version; /* FSCRYPT_EXTENT_CONTEXT_V2 */
+ u8 nonce[FSCRYPT_FILE_NONCE_SIZE];
+};
+
/*
* fscrypt_context - the encryption context of an inode
*
@@ -260,6 +275,25 @@ struct fscrypt_inode_info {
u32 ci_hashed_ino;
};
+/*
+ * fscrypt_extent_info - the "encryption key" for an extent.
+ *
+ * This contains the dervied 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 extents nonce, loaded from the fscrypt_extent_context */
+ u8 nonce[FSCRYPT_FILE_NONCE_SIZE];
+};
+
typedef enum {
FS_DECRYPT = 0,
FS_ENCRYPT,
@@ -267,6 +301,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_block(const struct inode *inode, fscrypt_direction_t rw,
u64 lblk_num, struct page *src_page,
@@ -326,6 +361,7 @@ int fscrypt_init_hkdf(struct fscrypt_hkdf *hkdf, const u8 *master_key,
#define HKDF_CONTEXT_DIRHASH_KEY 5 /* info=file_nonce */
#define HKDF_CONTEXT_IV_INO_LBLK_32_KEY 6 /* info=mode_num||fs_uuid */
#define HKDF_CONTEXT_INODE_HASH_KEY 7 /* info=<empty> */
+#define HKDF_CONTEXT_PER_EXTENT_ENC_KEY 8 /* info=extent_nonce */
int fscrypt_hkdf_expand(const struct fscrypt_hkdf *hkdf, u8 context,
const u8 *info, unsigned int infolen,
@@ -278,6 +278,40 @@ 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() - prepare a file contents bio for inline crypto
+ * @bio: a bio which will eventually be submitted to the file
+ * @inode: the file's inode
+ * @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 inode *inode,
+ const struct fscrypt_extent_info *ei,
+ u64 first_lblk, gfp_t gfp_mask)
+{
+ const struct fscrypt_inode_info *ci;
+ u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE];
+
+ if (!fscrypt_inode_uses_inline_crypto(inode))
+ return;
+ ci = inode->i_crypt_info;
+
+ fscrypt_generate_dun(ci, first_lblk, dun);
+ 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,
@@ -806,3 +806,139 @@ int fscrypt_drop_inode(struct inode *inode)
return !is_master_key_secret_present(&ci->ci_master_key->mk_secret);
}
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_KEY_SIZE];
+ int err;
+
+ ci = inode->i_crypt_info;
+
+ 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;
+
+ mk = ci->ci_master_key;
+ down_read(&mk->mk_sem);
+ if (!is_master_key_secret_present(&mk->mk_secret)) {
+ err = -ENOKEY;
+ goto out_free;
+ }
+
+ err = 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);
+ if (err)
+ goto out_free;
+
+ err = fscrypt_prepare_inline_crypt_key(&ei->prep_key, derived_key, 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.
+ *
+ * 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 (!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 file_extent_info and derive the key based on the
+ * fscrypt_extent_context buffer that is probided.
+ *
+ * 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;
+
+ if (!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(ctx));
+ return setup_extent_info(inode, extent_ctx.nonce);
+}
+EXPORT_SYMBOL(fscrypt_load_extent_info);
+
+/**
+ * fscrypt_put_extent_info() - free the extent_info fscrypt data
+ * @ei: the extent_info being evicted
+ *
+ * Drop a reference and possibly free the fscrypt_extent_info.
+ */
+void fscrypt_put_extent_info(struct fscrypt_extent_info *ei)
+{
+ if (!ei)
+ return;
+
+ if (!refcount_dec_and_test(&ei->refs))
+ return;
+
+ 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 ref on the fscrypt extent info
+ * @ei: the extent_info to get.
+ *
+ * Get a reference on the fscrypt_extent_info.
+ *
+ * Return: the ei with an extra ref, NULL if there was no ei passed in.
+ */
+struct fscrypt_extent_info *fscrypt_get_extent_info(struct fscrypt_extent_info *ei)
+{
+ if (!ei)
+ return NULL;
+ refcount_inc(&ei->refs);
+ return ei;
+}
+EXPORT_SYMBOL_GPL(fscrypt_get_extent_info);
@@ -763,6 +763,28 @@ 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.
+ *
+ * Return: the size of the fscrypt_extent_context.
+ */
+size_t fscrypt_set_extent_context(struct inode *inode,
+ struct fscrypt_extent_info *ei, u8 *buf)
+{
+ struct fscrypt_extent_context *ctx = (struct fscrypt_extent_context *)buf;
+
+ ctx->version = 1;
+ memcpy(ctx->nonce, ei->nonce, FSCRYPT_FILE_NONCE_SIZE);
+ return sizeof(struct fscrypt_extent_context);
+}
+EXPORT_SYMBOL_GPL(fscrypt_set_extent_context);
+
/**
* fscrypt_parse_test_dummy_encryption() - parse the test_dummy_encryption mount option
* @param: the mount option
@@ -32,6 +32,7 @@
union fscrypt_policy;
struct fscrypt_inode_info;
+struct fscrypt_extent_info;
struct fs_parameter;
struct seq_file;
@@ -66,6 +67,9 @@ struct fscrypt_name {
*/
#define FS_CFLG_OWN_PAGES (1U << 1)
+/* If set, the file system is using extent based encryption. */
+#define FS_CFLG_EXTENT_ENCRYPTION (1U << 2)
+
/* Crypto operations for filesystems */
struct fscrypt_operations {
@@ -293,6 +297,10 @@ 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);
+size_t fscrypt_set_extent_context(struct inode *inode,
+ struct fscrypt_extent_info *ei, u8 *buf);
+struct fscrypt_extent_info *fscrypt_load_extent_info(struct inode *inode,
+ u8 *ctx, size_t ctx_size);
struct fscrypt_dummy_policy {
const union fscrypt_policy *policy;
@@ -329,6 +337,9 @@ 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);
/* fname.c */
int fscrypt_fname_encrypt(const struct inode *inode, const struct qstr *iname,
@@ -772,6 +783,11 @@ 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 inode *inode,
+ 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);
@@ -797,6 +813,11 @@ 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 inode *inode,
+ 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,
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->set_extent_context. 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. Signed-off-by: Josef Bacik <josef@toxicpanda.com> --- fs/crypto/crypto.c | 10 ++- fs/crypto/fscrypt_private.h | 36 ++++++++++ fs/crypto/inline_crypt.c | 34 +++++++++ fs/crypto/keysetup.c | 136 ++++++++++++++++++++++++++++++++++++ fs/crypto/policy.c | 22 ++++++ include/linux/fscrypt.h | 21 ++++++ 6 files changed, 258 insertions(+), 1 deletion(-)