diff mbox series

[v2,01/36] fscrypt: use a flag to indicate that the master key is being evicted

Message ID e86c16dddc049ff065f877d793ad773e4c6bfad9.1696970227.git.josef@toxicpanda.com (mailing list archive)
State Superseded
Headers show
Series btrfs: add fscrypt support | expand

Commit Message

Josef Bacik Oct. 10, 2023, 8:40 p.m. UTC
Currently we wipe the mk->mk_secret when we remove the master key, and
we use this status to tell everybody whether or not the master key is
available for use.

With extent based encryption we're going to need to keep the secret
around until the inode is evicted, so we need a different mechanism to
tell everybody that the key is currently unusable.

Accomplish this with a mk_flags member in the master key, and update the
is_master_key_secret_present() helper to return the status of this bit.
Update the removal and adding helpers to manipulate this bit and use it
as the source of truth about whether or not the key is available for
use.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
 fs/crypto/fscrypt_private.h | 17 ++++++++---------
 fs/crypto/hooks.c           |  2 +-
 fs/crypto/keyring.c         | 20 ++++++++++++++------
 fs/crypto/keysetup.c        |  4 ++--
 4 files changed, 25 insertions(+), 18 deletions(-)

Comments

Eric Biggers Oct. 15, 2023, 6:22 a.m. UTC | #1
On Tue, Oct 10, 2023 at 04:40:16PM -0400, Josef Bacik wrote:
> Currently we wipe the mk->mk_secret when we remove the master key, and
> we use this status to tell everybody whether or not the master key is
> available for use.
> 
> With extent based encryption we're going to need to keep the secret
> around until the inode is evicted, so we need a different mechanism to
> tell everybody that the key is currently unusable.
> 
> Accomplish this with a mk_flags member in the master key, and update the
> is_master_key_secret_present() helper to return the status of this bit.
> Update the removal and adding helpers to manipulate this bit and use it
> as the source of truth about whether or not the key is available for
> use.
> 
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
> ---
>  fs/crypto/fscrypt_private.h | 17 ++++++++---------
>  fs/crypto/hooks.c           |  2 +-
>  fs/crypto/keyring.c         | 20 ++++++++++++++------
>  fs/crypto/keysetup.c        |  4 ++--
>  4 files changed, 25 insertions(+), 18 deletions(-)

Thanks, this patch seems like it's on the right track.  There are a lot of
little things that need to be updated to be consistent, though.  I'm also
thinking we should do it the other way around, where we explicitly mark the key
as "present", matching the terminology used in the UAPI for
FS_IOC_GET_ENCRYPTION_KEY_STATUS.  I also noticed two bugs: BIT(0) should be 0,
and the code in add_existing_master_key() is racy.

Can you take a look at the patch
"fscrypt: track master key presence separately from secret"
(https://lore.kernel.org/r/20231015061055.62673-1-ebiggers@kernel.org)
I just sent out?  It's a replacement for this one.

- Eric
diff mbox series

Patch

diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h
index 2fb4ba435d27..f44342f17269 100644
--- a/fs/crypto/fscrypt_private.h
+++ b/fs/crypto/fscrypt_private.h
@@ -471,6 +471,10 @@  struct fscrypt_master_key_secret {
 
 } __randomize_layout;
 
+enum fscrypt_mk_flags {
+	FSCRYPT_MK_FLAG_EVICTED = BIT(0),
+};
+
 /*
  * fscrypt_master_key - an in-use master key
  *
@@ -565,19 +569,14 @@  struct fscrypt_master_key {
 	siphash_key_t		mk_ino_hash_key;
 	bool			mk_ino_hash_key_initialized;
 
+	/* Flags for the master key. */
+	unsigned long		mk_flags;
 } __randomize_layout;
 
 static inline bool
-is_master_key_secret_present(const struct fscrypt_master_key_secret *secret)
+is_master_key_secret_present(const struct fscrypt_master_key *mk)
 {
-	/*
-	 * The READ_ONCE() is only necessary for fscrypt_drop_inode().
-	 * fscrypt_drop_inode() runs in atomic context, so it can't take the key
-	 * semaphore and thus 'secret' can change concurrently which would be a
-	 * data race.  But fscrypt_drop_inode() only need to know whether the
-	 * secret *was* present at the time of check, so READ_ONCE() suffices.
-	 */
-	return READ_ONCE(secret->size) != 0;
+	return !test_bit(FSCRYPT_MK_FLAG_EVICTED, &mk->mk_flags);
 }
 
 static inline const char *master_key_spec_type(
diff --git a/fs/crypto/hooks.c b/fs/crypto/hooks.c
index 85d2975b69b7..f7cf724cf256 100644
--- a/fs/crypto/hooks.c
+++ b/fs/crypto/hooks.c
@@ -187,7 +187,7 @@  int fscrypt_prepare_setflags(struct inode *inode,
 			return -EINVAL;
 		mk = ci->ci_master_key;
 		down_read(&mk->mk_sem);
-		if (is_master_key_secret_present(&mk->mk_secret))
+		if (is_master_key_secret_present(mk))
 			err = fscrypt_derive_dirhash_key(ci, mk);
 		else
 			err = -ENOKEY;
diff --git a/fs/crypto/keyring.c b/fs/crypto/keyring.c
index a51fa6a33de1..e0e311ed6b88 100644
--- a/fs/crypto/keyring.c
+++ b/fs/crypto/keyring.c
@@ -102,7 +102,7 @@  void fscrypt_put_master_key_activeref(struct super_block *sb,
 	 * ->mk_active_refs == 0 implies that ->mk_secret is not present and
 	 * that ->mk_decrypted_inodes is empty.
 	 */
-	WARN_ON_ONCE(is_master_key_secret_present(&mk->mk_secret));
+	WARN_ON_ONCE(is_master_key_secret_present(mk));
 	WARN_ON_ONCE(!list_empty(&mk->mk_decrypted_inodes));
 
 	for (i = 0; i <= FSCRYPT_MODE_MAX; i++) {
@@ -236,11 +236,17 @@  void fscrypt_destroy_keyring(struct super_block *sb)
 			 * the keyring due to the single active ref associated
 			 * with ->mk_secret.  There should be no structural refs
 			 * beyond the one associated with the active ref.
+			 *
+			 * We set the EVICTED flag in order to avoid the
+			 * WARN_ON_ONCE(is_master_key_secret_present(mk)) in
+			 * fscrypt_put_master_key_activeref(), as we want to
+			 * maintain that warning for improper cleanup elsewhere.
 			 */
 			WARN_ON_ONCE(refcount_read(&mk->mk_active_refs) != 1);
 			WARN_ON_ONCE(refcount_read(&mk->mk_struct_refs) != 1);
-			WARN_ON_ONCE(!is_master_key_secret_present(&mk->mk_secret));
+			WARN_ON_ONCE(!is_master_key_secret_present(mk));
 			wipe_master_key_secret(&mk->mk_secret);
+			set_bit(FSCRYPT_MK_FLAG_EVICTED, &mk->mk_flags);
 			fscrypt_put_master_key_activeref(sb, mk);
 		}
 	}
@@ -479,9 +485,11 @@  static int add_existing_master_key(struct fscrypt_master_key *mk,
 	}
 
 	/* Re-add the secret if needed. */
-	if (!is_master_key_secret_present(&mk->mk_secret)) {
-		if (!refcount_inc_not_zero(&mk->mk_active_refs))
+	if (test_and_clear_bit(FSCRYPT_MK_FLAG_EVICTED, &mk->mk_flags)) {
+		if (!refcount_inc_not_zero(&mk->mk_active_refs)) {
+			set_bit(FSCRYPT_MK_FLAG_EVICTED, &mk->mk_flags);
 			return KEY_DEAD;
+		}
 		move_master_key_secret(&mk->mk_secret, secret);
 	}
 
@@ -1055,7 +1063,7 @@  static int do_remove_key(struct file *filp, void __user *_uarg, bool all_users)
 
 	/* No user claims remaining.  Go ahead and wipe the secret. */
 	err = -ENOKEY;
-	if (is_master_key_secret_present(&mk->mk_secret)) {
+	if (!test_and_set_bit(FSCRYPT_MK_FLAG_EVICTED, &mk->mk_flags)) {
 		wipe_master_key_secret(&mk->mk_secret);
 		fscrypt_put_master_key_activeref(sb, mk);
 		err = 0;
@@ -1150,7 +1158,7 @@  int fscrypt_ioctl_get_key_status(struct file *filp, void __user *uarg)
 	}
 	down_read(&mk->mk_sem);
 
-	if (!is_master_key_secret_present(&mk->mk_secret)) {
+	if (!is_master_key_secret_present(mk)) {
 		arg.status = refcount_read(&mk->mk_active_refs) > 0 ?
 			FSCRYPT_KEY_STATUS_INCOMPLETELY_REMOVED :
 			FSCRYPT_KEY_STATUS_ABSENT /* raced with full removal */;
diff --git a/fs/crypto/keysetup.c b/fs/crypto/keysetup.c
index 094d1b7a1ae6..92eca1400b2d 100644
--- a/fs/crypto/keysetup.c
+++ b/fs/crypto/keysetup.c
@@ -487,7 +487,7 @@  static int setup_file_encryption_key(struct fscrypt_inode_info *ci,
 	down_read(&mk->mk_sem);
 
 	/* Has the secret been removed (via FS_IOC_REMOVE_ENCRYPTION_KEY)? */
-	if (!is_master_key_secret_present(&mk->mk_secret)) {
+	if (!is_master_key_secret_present(mk)) {
 		err = -ENOKEY;
 		goto out_release_key;
 	}
@@ -808,6 +808,6 @@  int fscrypt_drop_inode(struct inode *inode)
 	 * then the thread removing the key will either evict the inode itself
 	 * or will correctly detect that it wasn't evicted due to the race.
 	 */
-	return !is_master_key_secret_present(&ci->ci_master_key->mk_secret);
+	return !is_master_key_secret_present(ci->ci_master_key);
 }
 EXPORT_SYMBOL_GPL(fscrypt_drop_inode);