fscrypt master keys hang off the superblock rather than a keyring of their
own key type, so register a separate notifier that walks the live
superblocks via the new super_blocks_crash_wipe() helper.

Signed-off-by: Jan Sebastian Götte <[email protected]>
---
 fs/crypto/block.c           | 10 +++++++
 fs/crypto/fscrypt_private.h | 15 ++++++++++
 fs/crypto/keyring.c         | 68 ++++++++++++++++++++++++++++++++++++++++++++-
 fs/crypto/keysetup_v1.c     | 15 ++++++++++
 fs/super.c                  | 29 +++++++++++++++++++
 include/linux/fs.h          |  4 +++
 6 files changed, 140 insertions(+), 1 deletion(-)

diff --git a/fs/crypto/block.c b/fs/crypto/block.c
index 5193f8ba3ee0..a2fa1469b3c4 100644
--- a/fs/crypto/block.c
+++ b/fs/crypto/block.c
@@ -15,6 +15,7 @@
 
 #include <linux/blk-crypto.h>
 #include <linux/blkdev.h>
+#include <linux/crash_core.h>
 #include <linux/export.h>
 #include <linux/sched/mm.h>
 #include <linux/slab.h>
@@ -144,6 +145,15 @@ int fscrypt_prepare_inline_crypt_key(struct 
fscrypt_prepared_key *prep_key,
        return err;
 }
 
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+void fscrypt_crash_wipe_inline_crypt_key(struct fscrypt_prepared_key *prep_key)
+{
+       if (prep_key->blk_key)
+               crash_wipe_memzero(prep_key->blk_key->bytes,
+                                  sizeof(prep_key->blk_key->bytes));
+}
+#endif /* CONFIG_CRASH_WIPE_SECRETS */
+
 void fscrypt_destroy_inline_crypt_key(struct super_block *sb,
                                      struct fscrypt_prepared_key *prep_key)
 {
diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h
index 74329e0953d1..95cb50e5cbcd 100644
--- a/fs/crypto/fscrypt_private.h
+++ b/fs/crypto/fscrypt_private.h
@@ -413,6 +413,10 @@ int fscrypt_prepare_inline_crypt_key(struct 
fscrypt_prepared_key *prep_key,
 void fscrypt_destroy_inline_crypt_key(struct super_block *sb,
                                      struct fscrypt_prepared_key *prep_key);
 
+#ifdef CONFIG_CRASH_WIPE_SECRETS /* && CONFIG_FS_ENCRYPTION_INLINE_CRYPT */
+void fscrypt_crash_wipe_inline_crypt_key(struct fscrypt_prepared_key 
*prep_key);
+#endif
+
 int fscrypt_derive_sw_secret(struct super_block *sb,
                             const u8 *wrapped_key, size_t wrapped_key_size,
                             u8 sw_secret[BLK_CRYPTO_SW_SECRET_SIZE]);
@@ -454,6 +458,13 @@ fscrypt_destroy_inline_crypt_key(struct super_block *sb,
 {
 }
 
+#ifdef CONFIG_CRASH_WIPE_SECRETS /* && !CONFIG_FS_ENCRYPTION_INLINE_CRYPT */
+static inline void
+fscrypt_crash_wipe_inline_crypt_key(struct fscrypt_prepared_key *prep_key)
+{
+}
+#endif
+
 static inline int
 fscrypt_derive_sw_secret(struct super_block *sb,
                         const u8 *wrapped_key, size_t wrapped_key_size,
@@ -760,6 +771,10 @@ static inline int fscrypt_require_key(struct inode *inode)
 
 void fscrypt_put_direct_key(struct fscrypt_direct_key *dk);
 
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+void fscrypt_crash_wipe_direct_keys(void);
+#endif
+
 int fscrypt_setup_v1_file_key(struct fscrypt_inode_info *ci,
                              const u8 *raw_master_key);
 
diff --git a/fs/crypto/keyring.c b/fs/crypto/keyring.c
index 76e28d1e0064..5ac302146eb2 100644
--- a/fs/crypto/keyring.c
+++ b/fs/crypto/keyring.c
@@ -19,6 +19,7 @@
  */
 
 #include <crypto/skcipher.h>
+#include <linux/crash_core.h>
 #include <linux/export.h>
 #include <linux/key-type.h>
 #include <linux/once.h>
@@ -239,8 +240,9 @@ void fscrypt_destroy_keyring(struct super_block *sb)
                        fscrypt_initiate_key_removal(sb, mk);
                }
        }
+       /* Stop panic-time walkers from finding @keyring before it is freed. */
+       smp_store_release(&sb->s_master_keys, NULL);
        kfree_sensitive(keyring);
-       sb->s_master_keys = NULL;
 }
 
 static struct hlist_head *
@@ -640,6 +642,13 @@ static void fscrypt_provisioning_key_destroy(struct key 
*key)
        kfree_sensitive(key->payload.data[0]);
 }
 
+/* wipe the key without freeing. used by CONFIG_CRASH_WIPE_SECRETS. */
+static void fscrypt_provisioning_key_wipe(struct key *key)
+{
+       if (key->payload.data[0])
+               crash_wipe_memzero(key->payload.data[0], key->datalen);
+}
+
 static struct key_type key_type_fscrypt_provisioning = {
        .name                   = "fscrypt-provisioning",
        .preparse               = fscrypt_provisioning_key_preparse,
@@ -647,6 +656,7 @@ static struct key_type key_type_fscrypt_provisioning = {
        .instantiate            = generic_key_instantiate,
        .describe               = fscrypt_provisioning_key_describe,
        .destroy                = fscrypt_provisioning_key_destroy,
+       .wipe                   = fscrypt_provisioning_key_wipe,
 };
 
 /*
@@ -1220,6 +1230,58 @@ int fscrypt_ioctl_get_key_status(struct file *filp, void 
__user *uarg)
 }
 EXPORT_SYMBOL_GPL(fscrypt_ioctl_get_key_status);
 
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+/*
+ * Wipe the master keys of one superblock.  The master keys don't live on a
+ * keyring of their own key_type, so the keyrings core can't reach them.
+ */
+static void fscrypt_crash_wipe_sb(struct super_block *sb)
+{
+       struct fscrypt_keyring *keyring = sb->s_master_keys;
+       size_t i;
+
+       if (!keyring)
+               return;
+
+       for (i = 0; i < ARRAY_SIZE(keyring->key_hashtable); i++) {
+               struct fscrypt_master_key *mk;
+
+               hlist_for_each_entry(mk, &keyring->key_hashtable[i], mk_node) {
+                       struct fscrypt_inode_info *ci;
+                       struct fscrypt_mode_key *node;
+
+                       crash_wipe_memzero(&mk->mk_secret,
+                                          sizeof(mk->mk_secret));
+
+                       list_for_each_entry(node, &mk->mk_mode_keys, link)
+                               fscrypt_crash_wipe_inline_crypt_key(&node->key);
+
+                       list_for_each_entry(ci, &mk->mk_decrypted_inodes,
+                                           ci_master_key_link)
+                               
fscrypt_crash_wipe_inline_crypt_key(&ci->ci_enc_key);
+               }
+       }
+}
+
+/* Called far into vpanic from crash_core.c with other CPUs stopped and
+ * preemption disabled
+ */
+static int fscrypt_crash_wipe(struct notifier_block *nb, unsigned long action,
+                             void *data)
+{
+       if (!super_blocks_crash_wipe(fscrypt_crash_wipe_sb))
+               pr_crit("crash_wipe_secrets: can't acquire sb_lock. skipping 
fscrypt keys.\n");
+
+       fscrypt_crash_wipe_direct_keys();
+
+       return NOTIFY_DONE;
+}
+
+static struct notifier_block fscrypt_crash_wipe_nb = {
+       .notifier_call = fscrypt_crash_wipe
+};
+#endif /* CONFIG_CRASH_WIPE_SECRETS */
+
 void __init fscrypt_init_keyring(void)
 {
        int err;
@@ -1235,4 +1297,8 @@ void __init fscrypt_init_keyring(void)
        if (err)
                panic("failed to register fscrypt-provisioning key type (%d)",
                      err);
+
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+       crash_wipe_secrets_register(&fscrypt_crash_wipe_nb);
+#endif
 }
diff --git a/fs/crypto/keysetup_v1.c b/fs/crypto/keysetup_v1.c
index 87fe13ccb253..08dd0a682a8f 100644
--- a/fs/crypto/keysetup_v1.c
+++ b/fs/crypto/keysetup_v1.c
@@ -23,6 +23,7 @@
 #include <crypto/aes.h>
 #include <crypto/utils.h>
 #include <keys/user-type.h>
+#include <linux/crash_core.h>
 #include <linux/hashtable.h>
 
 #include "fscrypt_private.h"
@@ -118,6 +119,20 @@ void fscrypt_put_direct_key(struct fscrypt_direct_key *dk)
        free_direct_key(dk);
 }
 
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+void fscrypt_crash_wipe_direct_keys(void)
+{
+       struct fscrypt_direct_key *dk;
+       unsigned int i;
+
+       /* No locking: all other CPUs are stopped, so nothing can race with us. 
*/
+       hash_for_each(fscrypt_direct_keys, i, dk, dk_node) {
+               crash_wipe_memzero(dk->dk_raw, sizeof(dk->dk_raw));
+               fscrypt_crash_wipe_inline_crypt_key(&dk->dk_key);
+       }
+}
+#endif /* CONFIG_CRASH_WIPE_SECRETS */
+
 /*
  * Find/insert the given key into the fscrypt_direct_keys table.  If found, it
  * is returned with elevated refcount, and 'to_insert' is freed if non-NULL.  
If
diff --git a/fs/super.c b/fs/super.c
index 5feecf5d9038..cd51e9cfe93b 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -2463,3 +2463,32 @@ int sb_init_dio_done_wq(struct super_block *sb)
        return 0;
 }
 EXPORT_SYMBOL_GPL(sb_init_dio_done_wq);
+
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+/**
+ * super_blocks_crash_wipe - run @wipe against every live superblock
+ * @wipe: callback to invoke for each superblock
+ *
+ * Called from the panic path with other CPUs stopped and preemption disabled.
+ * The callback must not sleep, allocate, free or take locks.
+ *
+ * Returns false without doing anything if @sb_lock could not be acquired, in
+ * which case the list may be inconsistent and walking it is unsafe.
+ */
+bool super_blocks_crash_wipe(void (*wipe)(struct super_block *sb))
+{
+       struct super_block *sb;
+
+       /* There is no point in waiting for a lock that will never be released
+        * at this stage.
+        */
+       if (!spin_trylock(&sb_lock))
+               return false;
+
+       list_for_each_entry(sb, &super_blocks, s_list)
+               wipe(sb);
+
+       spin_unlock(&sb_lock);
+       return true;
+}
+#endif
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 072d8cd09a0b..7832a77908d2 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2378,6 +2378,10 @@ extern __printf(2, 3)
 int super_setup_bdi_name(struct super_block *sb, char *fmt, ...);
 extern int super_setup_bdi(struct super_block *sb);
 
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+bool super_blocks_crash_wipe(void (*wipe)(struct super_block *sb));
+#endif
+
 static inline void super_set_uuid(struct super_block *sb, const u8 *uuid, 
unsigned len)
 {
        if (WARN_ON(len > sizeof(sb->s_uuid)))

-- 
2.53.0


Reply via email to