diff mbox series

[RFC,12/13] fscrypt: allow asynchronous info freeing

Message ID a7eb6e15940fa7afd800a10d80adff7e29fff926.1693630890.git.sweettea-kernel@dorminy.me (mailing list archive)
State New, archived
Headers show
Series fscrypt: add extent encryption | expand

Commit Message

Sweet Tea Dorminy Sept. 2, 2023, 5:54 a.m. UTC
btrfs sometimes frees extents while holding a mutex. This makes it hard
to free the prepared keys associated therewith, as the free process may
need to take a semaphore. Just offloading freeing to rcu doesn't work,
as rcu may call the callback in softirq context, which also doesn't
allow taking a semaphore. Thus, for extent infos, offload their freeing
to the general system workqueue.

Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
---
 fs/crypto/fscrypt_private.h | 12 +++++++++---
 fs/crypto/keyring.c         |  6 +++---
 fs/crypto/keysetup.c        | 31 +++++++++++++++++++++++++++----
 fs/crypto/keysetup_v1.c     |  3 +--
 4 files changed, 40 insertions(+), 12 deletions(-)
diff mbox series

Patch

diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h
index 30459e219fc3..67f33ad704a3 100644
--- a/fs/crypto/fscrypt_private.h
+++ b/fs/crypto/fscrypt_private.h
@@ -217,6 +217,12 @@  struct fscrypt_prepared_key {
 	 */
 	size_t device_count;
 #endif
+	/*
+	 * For destroying asynchronously.
+	 */
+	struct work_struct work;
+	/* A pointer to free after destroy. */
+	void *ptr_to_free;
 	enum fscrypt_prepared_key_type type;
 };
 
@@ -528,8 +534,7 @@  fscrypt_prepare_inline_crypt_key(struct fscrypt_prepared_key *prep_key,
 }
 
 static inline void
-fscrypt_destroy_inline_crypt_key(struct super_block *sb,
-				 struct fscrypt_prepared_key *prep_key)
+fscrypt_destroy_inline_crypt_key(struct fscrypt_prepared_key *prep_key)
 {
 }
 
@@ -751,7 +756,8 @@  int fscrypt_prepare_key(struct fscrypt_prepared_key *prep_key,
 			const struct fscrypt_common_info *ci);
 
 void fscrypt_destroy_prepared_key(struct super_block *sb,
-				  struct fscrypt_prepared_key *prep_key);
+				  struct fscrypt_prepared_key *prep_key,
+				  void *ptr_to_free);
 
 int fscrypt_set_per_info_enc_key(struct fscrypt_common_info *ci, const u8 *raw_key);
 
diff --git a/fs/crypto/keyring.c b/fs/crypto/keyring.c
index 9235a5a9bcba..d4ec4ff27266 100644
--- a/fs/crypto/keyring.c
+++ b/fs/crypto/keyring.c
@@ -107,11 +107,11 @@  void fscrypt_put_master_key_activeref(struct super_block *sb,
 
 	for (i = 0; i <= FSCRYPT_MODE_MAX; i++) {
 		fscrypt_destroy_prepared_key(
-				sb, &mk->mk_direct_keys[i]);
+				sb, &mk->mk_direct_keys[i], NULL);
 		fscrypt_destroy_prepared_key(
-				sb, &mk->mk_iv_ino_lblk_64_keys[i]);
+				sb, &mk->mk_iv_ino_lblk_64_keys[i], NULL);
 		fscrypt_destroy_prepared_key(
-				sb, &mk->mk_iv_ino_lblk_32_keys[i]);
+				sb, &mk->mk_iv_ino_lblk_32_keys[i], NULL);
 	}
 	memzero_explicit(&mk->mk_ino_hash_key,
 			 sizeof(mk->mk_ino_hash_key));
diff --git a/fs/crypto/keysetup.c b/fs/crypto/keysetup.c
index 4ea9b68363d5..293a7d765ca7 100644
--- a/fs/crypto/keysetup.c
+++ b/fs/crypto/keysetup.c
@@ -179,13 +179,36 @@  int fscrypt_prepare_key(struct fscrypt_prepared_key *prep_key,
 	return 0;
 }
 
-/* Destroy a crypto transform object and/or blk-crypto key. */
-void fscrypt_destroy_prepared_key(struct super_block *sb,
-				  struct fscrypt_prepared_key *prep_key)
+static void __destroy_key(struct fscrypt_prepared_key *prep_key)
 {
+	void *ptr_to_free = prep_key->ptr_to_free;
+
 	crypto_free_skcipher(prep_key->tfm);
 	fscrypt_destroy_inline_crypt_key(prep_key);
 	memzero_explicit(prep_key, sizeof(*prep_key));
+	if (ptr_to_free)
+		kfree_sensitive(ptr_to_free);
+}
+
+static void __destroy_key_work(struct work_struct *work)
+{
+	struct fscrypt_prepared_key *prep_key =
+		container_of(work, struct fscrypt_prepared_key, work);
+
+	__destroy_key(prep_key);
+}
+
+/* Destroy a crypto transform object and/or blk-crypto key. */
+void fscrypt_destroy_prepared_key(struct super_block *sb,
+				  struct fscrypt_prepared_key *prep_key,
+				  void *ptr_to_free)
+{
+	prep_key->ptr_to_free = ptr_to_free;
+	if (fscrypt_fs_uses_extent_encryption(sb)) {
+		INIT_WORK(&prep_key->work, __destroy_key_work);
+		queue_work(system_unbound_wq, &prep_key->work);
+	} else
+		__destroy_key(prep_key);
 }
 
 /* Given a per-info encryption key, set up the info's crypto transform object */
@@ -594,8 +617,8 @@  static void free_prepared_key(struct fscrypt_common_info *cci)
 			fscrypt_put_direct_key(cci->ci_enc_key);
 		if (type == FSCRYPT_KEY_PER_INFO) {
 			fscrypt_destroy_prepared_key(cci->ci_inode->i_sb,
+						     cci->ci_enc_key,
 						     cci->ci_enc_key);
-			kfree_sensitive(cci->ci_enc_key);
 		}
 	}
 }
diff --git a/fs/crypto/keysetup_v1.c b/fs/crypto/keysetup_v1.c
index b57ed49ac201..8ccd8d4154d0 100644
--- a/fs/crypto/keysetup_v1.c
+++ b/fs/crypto/keysetup_v1.c
@@ -155,8 +155,7 @@  struct fscrypt_direct_key {
 static void free_direct_key(struct fscrypt_direct_key *dk)
 {
 	if (dk) {
-		fscrypt_destroy_prepared_key(dk->dk_sb, &dk->dk_key);
-		kfree_sensitive(dk);
+		fscrypt_destroy_prepared_key(dk->dk_sb, &dk->dk_key, dk);
 	}
 }