diff mbox series

[RFC,05/13] fscrypt: add creation/usage/freeing of per-extent infos

Message ID 7045a2f0f411494e53b6ef1f995bd0e4cfc73f17.1693630890.git.sweettea-kernel@dorminy.me (mailing list archive)
State Superseded
Headers show
Series fscrypt: add extent encryption | expand

Commit Message

Sweet Tea Dorminy Sept. 2, 2023, 5:54 a.m. UTC
This change adds the superblock function pointer to get the info
corresponding to a specific block in an inode for a filesystem using
per-extent infos. It allows creating a info for a new extent and freeing
that info, and uses the extent's info if appropriate in encrypting
blocks of data.

It also makes sure that the return value of fscrypt_get_lblk_info() is
non-NULL before using it, since there's no longer a mechanical guarantee
that we'll never call fscrypt_get_lblk_info() without having the
relevant info loaded. We *oughtn't*, but we're not explicitly checking
that it's loaded before these points.

This change does not deal with saving and loading an extent's info, but
introduces the mechanics necessary therefore.

Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
---
 fs/crypto/crypto.c          | 14 +++++-
 fs/crypto/fscrypt_private.h | 53 ++++++++++++++++++--
 fs/crypto/inline_crypt.c    |  5 ++
 fs/crypto/keysetup.c        | 97 +++++++++++++++++++++++++++++++++++++
 include/linux/fscrypt.h     | 41 ++++++++++++++++
 5 files changed, 204 insertions(+), 6 deletions(-)
diff mbox series

Patch

diff --git a/fs/crypto/crypto.c b/fs/crypto/crypto.c
index d5c9326a1919..5a93c30e6b86 100644
--- a/fs/crypto/crypto.c
+++ b/fs/crypto/crypto.c
@@ -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)
 {
@@ -113,6 +114,8 @@  int fscrypt_crypt_block(const struct inode *inode, fscrypt_direction_t rw,
 	struct crypto_skcipher *tfm = cci->ci_enc_key->tfm;
 	int res = 0;
 
+	if (WARN_ON_ONCE(!cci))
+		return -EINVAL;
 	if (WARN_ON_ONCE(len <= 0))
 		return -EINVAL;
 	if (WARN_ON_ONCE(len % FSCRYPT_CONTENTS_ALIGNMENT != 0))
@@ -397,13 +400,20 @@  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_inode_info;
+
 	err = fscrypt_init_keyring();
 	if (err)
-		goto fail_free_info;
+		goto fail_free_extent_info;
 
 	return 0;
 
-fail_free_info:
+fail_free_extent_info:
+	kmem_cache_destroy(fscrypt_extent_info_cachep);
+fail_free_inode_info:
 	kmem_cache_destroy(fscrypt_inode_info_cachep);
 fail_free_queue:
 	destroy_workqueue(fscrypt_read_workqueue);
diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h
index cc1a61ade2a4..9320428f8915 100644
--- a/fs/crypto/fscrypt_private.h
+++ b/fs/crypto/fscrypt_private.h
@@ -294,13 +294,47 @@  struct fscrypt_info {
 };
 
 /*
+ * fscrypt_extent_info - the "encryption key" for an extent
  */
+struct fscrypt_extent_info {
+	struct fscrypt_common_info info;
+};
 
 typedef enum {
 	FS_DECRYPT = 0,
 	FS_ENCRYPT,
 } fscrypt_direction_t;
 
+/**
+ * fscrypt_fs_uses_extent_encryption() -- whether a filesystem uses extent
+ *					  encryption
+ *
+ * @sb: the superblock of the relevant filesystem
+ *
+ * Return: true if the fs uses extent encryption, else false
+ */
+static inline bool
+fscrypt_fs_uses_extent_encryption(const struct super_block *sb)
+{
+	return !!sb->s_cop->get_extent_info;
+}
+
+/**
+ * fscrypt_uses_extent_encryption() -- whether an inode uses extent encryption
+ *
+ * @inode: the inode in question
+ *
+ * Return: true if the inode uses extent encryption, else false
+ */
+static inline bool fscrypt_uses_extent_encryption(const struct inode *inode)
+{
+	/* Non-regular files don't have extents. */
+	if (!S_ISREG(inode->i_mode))
+		return false;
+
+	return fscrypt_fs_uses_extent_encryption(inode->i_sb);
+}
+
 /**
  * fscrypt_get_lblk_info() - get the fscrypt_common_info to crypt a particular
  *			     block
@@ -313,15 +347,26 @@  typedef enum {
  *              always be @lblk; for extent-based encryption, this will be in
  *              the range [0, lblk]. Can be NULL
  * @extent_len: a pointer to return the minimum number of lblks starting at
- *              this offset which also belong to the same fscrypt_info. Can be
- *              NULL
+ *              this offset which also belong to the same fscrypt_common_info.
+ *              Can be NULL
  *
- * Return: the appropriate fscrypt_info if there is one, else NULL.
+ * Return: the appropriate fscrypt_common_info if there is one, else NULL.
  */
 static inline struct fscrypt_common_info *
 fscrypt_get_lblk_info(const struct inode *inode, u64 lblk, u64 *offset,
 		      u64 *extent_len)
 {
+	if (fscrypt_uses_extent_encryption(inode)) {
+		struct fscrypt_extent_info *cei;
+		int res;
+
+		res = inode->i_sb->s_cop->get_extent_info(inode, lblk, &cei,
+							  offset, extent_len);
+		if (res == 0)
+			return &cei->info;
+		return NULL;
+	}
+
 	if (offset)
 		*offset = lblk;
 	if (extent_len)
@@ -330,9 +375,9 @@  fscrypt_get_lblk_info(const struct inode *inode, u64 lblk, u64 *offset,
 	return &inode->i_crypt_info->info;
 }
 
-
 /* 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,
diff --git a/fs/crypto/inline_crypt.c b/fs/crypto/inline_crypt.c
index 09ec82b8a98a..f0229234249c 100644
--- a/fs/crypto/inline_crypt.c
+++ b/fs/crypto/inline_crypt.c
@@ -272,6 +272,8 @@  void fscrypt_set_bio_crypt_ctx(struct bio *bio, const struct inode *inode,
 	if (!fscrypt_inode_uses_inline_crypto(inode))
 		return;
 	cci = fscrypt_get_lblk_info(inode, first_lblk, &ci_offset, NULL);
+	if (!cci)
+		return;
 
 	fscrypt_generate_dun(cci, ci_offset, dun);
 	bio_crypt_set_ctx(bio, cci->ci_enc_key->blk_key, dun, gfp_mask);
@@ -359,6 +361,9 @@  bool fscrypt_mergeable_bio(struct bio *bio, const struct inode *inode,
 		return true;
 
 	cci = fscrypt_get_lblk_info(inode, next_lblk, &ci_offset, NULL);
+	if (!cci)
+		return false;
+
 	/*
 	 * Comparing the key pointers is good enough, as all I/O for each key
 	 * uses the same pointer.  I.e., there's currently no need to support
diff --git a/fs/crypto/keysetup.c b/fs/crypto/keysetup.c
index ae250e432dcd..c9c16acf4c9b 100644
--- a/fs/crypto/keysetup.c
+++ b/fs/crypto/keysetup.c
@@ -957,3 +957,100 @@  int fscrypt_drop_inode(struct inode *inode)
 	return !is_master_key_secret_present(&ci->info.ci_master_key->mk_secret);
 }
 EXPORT_SYMBOL_GPL(fscrypt_drop_inode);
+
+static void put_crypt_extent_info(struct fscrypt_extent_info *ci)
+{
+	if (!ci)
+		return;
+
+	free_prepared_key(&ci->info);
+	remove_info_from_mk_decrypted_list(&ci->info);
+
+	memzero_explicit(ci, sizeof(*ci));
+	kmem_cache_free(fscrypt_extent_info_cachep, ci);
+}
+
+static int
+fscrypt_setup_extent_info(struct inode *inode,
+			  const union fscrypt_policy *policy,
+			  const u8 nonce[FSCRYPT_FILE_NONCE_SIZE],
+			  struct fscrypt_extent_info **info_ptr)
+{
+	struct fscrypt_extent_info *crypt_extent_info;
+	struct fscrypt_common_info *crypt_info;
+	struct fscrypt_master_key *mk = NULL;
+	int res;
+
+	crypt_extent_info = kmem_cache_zalloc(fscrypt_extent_info_cachep,
+					      GFP_KERNEL);
+	if (!crypt_extent_info)
+		return -ENOMEM;
+	crypt_info = &crypt_extent_info->info;
+
+	res = fscrypt_setup_common_info(crypt_info, inode, policy, nonce,
+					CI_EXTENT, &mk);
+	if (res)
+		goto out;
+
+	*info_ptr = crypt_extent_info;
+	add_info_to_mk_decrypted_list(crypt_info, mk);
+
+	crypt_extent_info = NULL;
+	res = 0;
+out:
+	if (mk) {
+		up_read(&mk->mk_sem);
+		fscrypt_put_master_key(mk);
+	}
+	put_crypt_extent_info(crypt_extent_info);
+	return res;
+}
+
+/**
+ * fscrypt_prepare_new_extent() - set up the fscrypt_extent_info for a new extent
+ * @inode: the inode to which the extent belongs
+ * @info_ptr: a pointer to return the extent's fscrypt_extent_info into
+ * *
+ * If the extent is part of an encrypted inode, set up a fscrypt_extent_info in
+ * preparation for encrypting data.
+ *
+ * This isn't %GFP_NOFS-safe.
+ *
+ * This doesn't persist the new extent's encryption context.  That still needs to
+ * be done later by calling fscrypt_set_extent_context().
+ *
+ * Return: 0 on success, -ENOKEY if the encryption key is missing, or another
+ *	   -errno code
+ */
+int fscrypt_prepare_new_extent(struct inode *inode,
+			       struct fscrypt_extent_info **info_ptr)
+{
+	const union fscrypt_policy *policy;
+	u8 nonce[FSCRYPT_FILE_NONCE_SIZE];
+
+	policy = fscrypt_policy_to_inherit(inode);
+	if (policy == NULL)
+		return 0;
+	if (IS_ERR(policy))
+		return PTR_ERR(policy);
+
+	/* Only regular files can have extents.  */
+	if (WARN_ON_ONCE(!S_ISREG(inode->i_mode)))
+		return -EINVAL;
+
+	get_random_bytes(nonce, FSCRYPT_FILE_NONCE_SIZE);
+	return fscrypt_setup_extent_info(inode, policy, nonce,
+					 info_ptr);
+}
+EXPORT_SYMBOL_GPL(fscrypt_prepare_new_extent);
+
+/**
+ * fscrypt_free_extent_info() - free an extent's fscrypt_extent_info
+ * @info_ptr: a pointer containing the extent's fscrypt_extent_info pointer.
+ */
+void fscrypt_free_extent_info(struct fscrypt_extent_info **info_ptr)
+{
+	put_crypt_extent_info(*info_ptr);
+	*info_ptr = NULL;
+}
+EXPORT_SYMBOL_GPL(fscrypt_free_extent_info);
diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h
index c895b12737a1..cc5de5ec888c 100644
--- a/include/linux/fscrypt.h
+++ b/include/linux/fscrypt.h
@@ -32,6 +32,7 @@ 
 
 union fscrypt_policy;
 struct fscrypt_info;
+struct fscrypt_extent_info;
 struct fs_parameter;
 struct seq_file;
 
@@ -113,6 +114,29 @@  struct fscrypt_operations {
 	int (*set_context)(struct inode *inode, const void *ctx, size_t len,
 			   void *fs_data);
 
+	/*
+	 * Get the fscrypt_info for the given inode at the given block, for
+	 * extent-based encryption only.
+	 *
+	 * @inode: the inode in question
+	 * @lblk: the logical block number in question
+	 * @ci: a pointer to return the fscrypt_extent_info
+	 * @offset: a pointer to return the offset of @lblk into the extent,
+	 *          in blocks (may be NULL)
+	 * @extent_len: a pointer to return the number of blocks in this extent
+	 *              starting at this point (may be NULL)
+	 *
+	 * May cause the filesystem to allocate memory, which the filesystem
+	 * must do with %GFP_NOFS, including calls into fscrypt to create or
+	 * load an fscrypt_info.
+	 *
+	 * Return: 0 if an extent is found with an info, -ENODATA if the key is
+	 *         unavailable, or another -errno.
+	 */
+	int (*get_extent_info)(const struct inode *inode, u64 lblk,
+			       struct fscrypt_extent_info **ci, u64 *offset,
+			       u64 *extent_len);
+
 	/*
 	 * Get the dummy fscrypt policy in use on the filesystem (if any).
 	 *
@@ -330,6 +354,10 @@  void fscrypt_put_encryption_info(struct inode *inode);
 void fscrypt_free_inode(struct inode *inode);
 int fscrypt_drop_inode(struct inode *inode);
 
+int fscrypt_prepare_new_extent(struct inode *inode,
+			       struct fscrypt_extent_info **info_ptr);
+void fscrypt_free_extent_info(struct fscrypt_extent_info **info_ptr);
+
 /* fname.c */
 int fscrypt_fname_encrypt(const struct inode *inode, const struct qstr *iname,
 			  u8 *out, unsigned int olen);
@@ -591,6 +619,19 @@  static inline int fscrypt_drop_inode(struct inode *inode)
 	return 0;
 }
 
+static inline int
+fscrypt_prepare_new_extent(struct inode *inode,
+			   struct fscrypt_extent_info **info_ptr)
+{
+	if (IS_ENCRYPTED(inode))
+		return -EOPNOTSUPP;
+	return 0;
+}
+
+static inline void fscrypt_free_extent_info(struct fscrypt_extent_info **info_ptr)
+{
+}
+
  /* fname.c */
 static inline int fscrypt_setup_filename(struct inode *dir,
 					 const struct qstr *iname,