[PATCH v2 11/13] fscrypt: wipe master keys before kdump

From: Jan Sebastian Götte

Date: Tue Aug 11 2026 - 14:21:22 EST


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 <linux@xxxxxxxx>
---
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