AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/08/11 18:21 flow patch-triage
2m Args:
null

Results:
{
  "EnableConfigs": [
    "CRASH_DUMP",
    "CRASH_WIPE_SECRETS",
    "DM_CRYPT",
    "FS_ENCRYPTION",
    "SECRETMEM"
  ],
  "FocusSymbols": [
    "__crypto_alloc_tfm",
    "crypto_create_tfm_node",
    "crypto_destroy_tfm",
    "crypt_ctr",
    "crypt_dtr",
    "fscrypt_destroy_keyring"
  ],
  "KMSANReasoning": "The patch series introduces a new feature `CONFIG_CRASH_WIPE_SECRETS` which zeroes out sensitive secrets (such as cryptographic keys, dm-crypt keys, fscrypt keys, and secretmem pages) during a kernel panic before a kdump crash kernel is started. The changes primarily involve adding `.wipe` callbacks to various key types, registering panic notifiers, and tracking allocated cryptographic contexts in lists so they can be iterated and wiped during a crash.\n\nThese changes only affect the panic/crash path and do not introduce any new structures sent to user space, nor do they modify normal execution paths in a way that would leave memory uninitialized. The modifications to normal execution paths are limited to adding/removing objects from tracking lists and using `kfree_sensitive` or `xchg` to safely clear pointers. There is no risk of uninitialized memory being exposed to user space or used in control flow. Standard KASAN and other bug detectors are sufficient to catch any potential memory corruption or use-after-free issues related to the new tracking lists.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch introduces CONFIG_CRASH_WIPE_SECRETS to wipe sensitive secrets before kdump. While the actual wiping occurs in the panic path, the patch also introduces tracking mechanisms that are executed during normal operation. This includes adding global locks and list operations for tracking crypto tfms (crypto_tfm_list_lock), dm-crypt configs (dm_crypt_wipe_list_lock), and adding secretmem inodes to the superblock list. These changes can potentially introduce race conditions, deadlocks, or list corruptions during normal allocation and destruction of these objects. Therefore, the changes have a functional impact worth fuzzing.",
  "WorthFuzzing": true
}

1/1 2026/08/11 18:21 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 3f0999c4f5e6c648c580d868cc5f6dc90351b881\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Tue Aug 11 18:21:02 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst\nindex 326b8a9738289..35cb00a87c461 100644\n--- a/Documentation/security/keys/core.rst\n+++ b/Documentation/security/keys/core.rst\n@@ -1596,6 +1596,19 @@ The structure has a number of fields, some of which are mandatory:\n      It is not safe to sleep in this method; the caller may hold spinlocks.\n \n \n+  *  ``void (*wipe)(struct key *key);``\n+\n+     This method is optional. It is called from the panic path when\n+     CONFIG_CRASH_WIPE_SECRETS is enabled, to erase the key material from\n+     memory before the kdump kernel is started, so that it does not end up in\n+     the crash dump. Unlike destroy(), it must only clear the payload, not\n+     free it.\n+\n+     This method is called with all other CPUs stopped and preemption\n+     disabled, and only for positively instantiated keys. It must not sleep,\n+     allocate, free or take locks, as they will never be released.\n+\n+\n   *  ``void (*describe)(const struct key *key, struct seq_file *p);``\n \n      This method is optional. It is called during /proc/keys reading to\ndiff --git a/arch/arm64/include/asm/set_memory.h b/arch/arm64/include/asm/set_memory.h\nindex 90f61b17275e1..cb5f44bb5a949 100644\n--- a/arch/arm64/include/asm/set_memory.h\n+++ b/arch/arm64/include/asm/set_memory.h\n@@ -13,6 +13,8 @@ int set_memory_valid(unsigned long addr, int numpages, int enable);\n \n int set_direct_map_invalid_noflush(struct page *page);\n int set_direct_map_default_noflush(struct page *page);\n+int set_direct_map_default_nosplit(struct page *page);\n+#define set_direct_map_default_nosplit set_direct_map_default_nosplit\n int set_direct_map_valid_noflush(struct page *page, unsigned nr, bool valid);\n bool kernel_page_present(struct page *page);\n \ndiff --git a/arch/arm64/kernel/machine_kexec.c b/arch/arm64/kernel/machine_kexec.c\nindex c5693a32e49b0..12bf3c90f5193 100644\n--- a/arch/arm64/kernel/machine_kexec.c\n+++ b/arch/arm64/kernel/machine_kexec.c\n@@ -6,6 +6,7 @@\n  * Copyright (C) Huawei Futurewei Technologies.\n  */\n \n+#include \u003clinux/crash_core.h\u003e\n #include \u003clinux/interrupt.h\u003e\n #include \u003clinux/irq.h\u003e\n #include \u003clinux/kernel.h\u003e\n@@ -15,6 +16,7 @@\n #include \u003clinux/set_memory.h\u003e\n #include \u003clinux/smp.h\u003e\n \n+#include \u003casm/barrier.h\u003e\n #include \u003casm/cacheflush.h\u003e\n #include \u003casm/cpu_ops.h\u003e\n #include \u003casm/daifflags.h\u003e\n@@ -221,6 +223,24 @@ void machine_crash_shutdown(struct pt_regs *regs)\n \tpr_info(\"Starting crashdump kernel...\\n\");\n }\n \n+#ifdef CONFIG_CRASH_WIPE_SECRETS\n+/*\n+ * Queue each wiped range for cleaning to the point of coherency.  Deferring\n+ * the barrier until the notifier chain is complete avoids one DSB per range.\n+ */\n+void arch_crash_wipe_range(void *addr, size_t size)\n+{\n+\tunsigned long start = (unsigned long)addr;\n+\n+\tdcache_clean_poc_nosync(start, start + size);\n+}\n+\n+void arch_crash_wipe_flush(void)\n+{\n+\tdsb(sy);\n+}\n+#endif\n+\n #if defined(CONFIG_CRASH_DUMP) \u0026\u0026 defined(CONFIG_HIBERNATION)\n /*\n  * To preserve the crash dump kernel image, the relevant memory segments\ndiff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c\nindex bbe98ac9ad8c6..a7bb6bfa12ed3 100644\n--- a/arch/arm64/mm/pageattr.c\n+++ b/arch/arm64/mm/pageattr.c\n@@ -275,6 +275,27 @@ int set_direct_map_default_noflush(struct page *page)\n \t\t\t\t PAGE_SIZE, set_mask, clear_mask);\n }\n \n+/* Caller must guarantee @page is already mapped at page granularity. */\n+int set_direct_map_default_nosplit(struct page *page)\n+{\n+\tstruct page_change_data data = {\n+\t\t.set_mask = __pgprot(PTE_PRESENT_VALID_KERNEL | PTE_WRITE),\n+\t\t.clear_mask = __pgprot(PTE_PRESENT_INVALID | PTE_RDONLY),\n+\t};\n+\tunsigned long addr = (unsigned long)page_address(page);\n+\tint ret;\n+\n+\tif (!can_set_direct_map())\n+\t\treturn 0;\n+\n+\tlazy_mmu_mode_enable();\n+\tret = walk_kernel_page_table_range_lockless(addr, addr + PAGE_SIZE,\n+\t\t\t\t\t\t    \u0026pageattr_ops, NULL, \u0026data);\n+\tlazy_mmu_mode_disable();\n+\n+\treturn ret;\n+}\n+\n static int __set_memory_enc_dec(unsigned long addr,\n \t\t\t\tint numpages,\n \t\t\t\tbool encrypt)\ndiff --git a/crypto/api.c b/crypto/api.c\nindex 24227582cfcf3..f919f52e8b5fc 100644\n--- a/crypto/api.c\n+++ b/crypto/api.c\n@@ -10,13 +10,16 @@\n  * and Nettle, by Niels Möller.\n  */\n \n+#include \u003clinux/crash_core.h\u003e\n #include \u003clinux/err.h\u003e\n #include \u003clinux/errno.h\u003e\n #include \u003clinux/jump_label.h\u003e\n #include \u003clinux/kernel.h\u003e\n #include \u003clinux/kmod.h\u003e\n #include \u003clinux/module.h\u003e\n+#include \u003clinux/notifier.h\u003e\n #include \u003clinux/param.h\u003e\n+#include \u003clinux/rculist.h\u003e\n #include \u003clinux/sched/signal.h\u003e\n #include \u003clinux/slab.h\u003e\n #include \u003clinux/string.h\u003e\n@@ -397,6 +400,60 @@ static unsigned int crypto_ctxsize(struct crypto_alg *alg, u32 type, u32 mask)\n \treturn len;\n }\n \n+#ifdef CONFIG_CRASH_WIPE_SECRETS\n+/*\n+ * tfm allocations are tracked on crypto_tfm_list so that they can be wiped\n+ * before kdump.\n+ */\n+static HLIST_HEAD(crypto_tfm_list);\n+static DEFINE_SPINLOCK(crypto_tfm_list_lock);\n+\n+static int crypto_crash_wipe(struct notifier_block *nb, unsigned long action,\n+\t\tvoid *data)\n+{\n+\tstruct crypto_tfm *tfm;\n+\n+\trcu_read_lock();\n+\thlist_for_each_entry_rcu(tfm, \u0026crypto_tfm_list, wipe_list)\n+\t\tcrash_wipe_memzero(tfm-\u003e__crt_ctx, tfm-\u003ewipe_size);\n+\trcu_read_unlock();\n+\n+\t/* off to kexec()! */\n+\treturn NOTIFY_DONE;\n+}\n+\n+static struct notifier_block crypto_wipe_nb = {\n+\t.notifier_call = crypto_crash_wipe\n+};\n+\n+static int __init crypto_tfm_track_init(void)\n+{\n+\tcrash_wipe_secrets_register(\u0026crypto_wipe_nb);\n+\treturn 0;\n+}\n+core_initcall(crypto_tfm_track_init);\n+\n+/* @size is the size of __crt_ctx */\n+static void crypto_track_tfm(struct crypto_tfm *tfm, size_t size)\n+{\n+\ttfm-\u003ewipe_size = size;\n+\n+\tspin_lock(\u0026crypto_tfm_list_lock);\n+\thlist_add_head_rcu(\u0026tfm-\u003ewipe_list, \u0026crypto_tfm_list);\n+\tspin_unlock(\u0026crypto_tfm_list_lock);\n+}\n+\n+static void crypto_untrack_tfm(struct crypto_tfm *tfm)\n+{\n+\tspin_lock(\u0026crypto_tfm_list_lock);\n+\thlist_del_rcu(\u0026tfm-\u003ewipe_list);\n+\tspin_unlock(\u0026crypto_tfm_list_lock);\n+}\n+#else\n+static void crypto_track_tfm(struct crypto_tfm *tfm, size_t size) { }\n+static void crypto_untrack_tfm(struct crypto_tfm *tfm) { }\n+#endif /* CONFIG_CRASH_WIPE_SECRETS */\n+\n void crypto_shoot_alg(struct crypto_alg *alg)\n {\n \tdown_write(\u0026crypto_alg_sem);\n@@ -409,15 +466,16 @@ struct crypto_tfm *__crypto_alloc_tfm(struct crypto_alg *alg, u32 type,\n \t\t\t\t      u32 mask)\n {\n \tstruct crypto_tfm *tfm;\n-\tunsigned int tfm_size;\n+\tunsigned int ctx_size;\n \tint err = -ENOMEM;\n \n-\ttfm_size = sizeof(*tfm) + crypto_ctxsize(alg, type, mask);\n-\ttfm = kzalloc(tfm_size, GFP_KERNEL);\n+\tctx_size = crypto_ctxsize(alg, type, mask);\n+\ttfm = kzalloc(sizeof(*tfm) + ctx_size, GFP_KERNEL);\n \tif (tfm == NULL)\n \t\tgoto out_err;\n \n \ttfm-\u003e__crt_alg = alg;\n+\tcrypto_track_tfm(tfm, ctx_size);\n \n \tif (!tfm-\u003eexit \u0026\u0026 alg-\u003ecra_init \u0026\u0026 (err = alg-\u003ecra_init(tfm)))\n \t\tgoto cra_init_failed;\n@@ -428,7 +486,8 @@ struct crypto_tfm *__crypto_alloc_tfm(struct crypto_alg *alg, u32 type,\n \tcrypto_exit_ops(tfm);\n \tif (err == -EAGAIN)\n \t\tcrypto_shoot_alg(alg);\n-\tkfree(tfm);\n+\tcrypto_untrack_tfm(tfm);\n+\tkfree_sensitive(tfm);\n out_err:\n \ttfm = ERR_PTR(err);\n out:\n@@ -497,12 +556,12 @@ void *crypto_create_tfm_node(struct crypto_alg *alg,\n \t\t\t     int node)\n {\n \tstruct crypto_tfm *tfm;\n-\tsize_t size;\n \tchar *mem;\n \tint err;\n+\tsize_t ctx_size = frontend-\u003eextsize(alg);\n \n-\tsize = frontend-\u003etfmsize + sizeof(*tfm) + frontend-\u003eextsize(alg);\n-\tmem = kzalloc_node(size, GFP_KERNEL, node);\n+\tmem = kzalloc_node(frontend-\u003etfmsize + sizeof(*tfm) + ctx_size,\n+\t\t\tGFP_KERNEL, node);\n \tif (!mem)\n \t\treturn ERR_PTR(-ENOMEM);\n \n@@ -510,6 +569,7 @@ void *crypto_create_tfm_node(struct crypto_alg *alg,\n \ttfm-\u003e__crt_alg = alg;\n \ttfm-\u003enode = node;\n \ttfm-\u003efb = tfm;\n+\tcrypto_track_tfm(tfm, ctx_size);\n \n \terr = frontend-\u003einit_tfm(tfm);\n \tif (err)\n@@ -525,7 +585,8 @@ void *crypto_create_tfm_node(struct crypto_alg *alg,\n out_free_tfm:\n \tif (err == -EAGAIN)\n \t\tcrypto_shoot_alg(alg);\n-\tkfree(mem);\n+\tcrypto_untrack_tfm(tfm);\n+\tkfree_sensitive(mem);\n \tmem = ERR_PTR(err);\n out:\n \treturn mem;\n@@ -627,6 +688,7 @@ void crypto_destroy_tfm(void *mem, struct crypto_tfm *tfm)\n \t\talg-\u003ecra_exit(tfm);\n \tcrypto_exit_ops(tfm);\n \tcrypto_mod_put(alg);\n+\tcrypto_untrack_tfm(tfm);\n \tkfree_sensitive(mem);\n }\n EXPORT_SYMBOL_GPL(crypto_destroy_tfm);\ndiff --git a/crypto/asymmetric_keys/asymmetric_type.c b/crypto/asymmetric_keys/asymmetric_type.c\nindex 16a7ae16593c9..a0a68ebd07463 100644\n--- a/crypto/asymmetric_keys/asymmetric_type.c\n+++ b/crypto/asymmetric_keys/asymmetric_type.c\n@@ -481,6 +481,16 @@ static void asymmetric_key_destroy(struct key *key)\n \tasymmetric_key_free_kids(kids);\n }\n \n+/* wipe the key without freeing. used by CONFIG_CRASH_WIPE_SECRETS. */\n+static void asymmetric_key_wipe(struct key *key)\n+{\n+\tstruct asymmetric_key_subtype *subtype = asymmetric_key_subtype(key);\n+\n+\tif (subtype \u0026\u0026 subtype-\u003ewipe)\n+\t\tsubtype-\u003ewipe(key-\u003epayload.data[asym_crypto],\n+\t\t\t\tkey-\u003epayload.data[asym_auth]);\n+}\n+\n static struct key_restriction *asymmetric_restriction_alloc(\n \tkey_restrict_link_func_t check,\n \tstruct key *key)\n@@ -612,6 +622,7 @@ struct key_type key_type_asymmetric = {\n \t.match_preparse\t\t= asymmetric_key_match_preparse,\n \t.match_free\t\t= asymmetric_key_match_free,\n \t.destroy\t\t= asymmetric_key_destroy,\n+\t.wipe\t\t\t= asymmetric_key_wipe,\n \t.describe\t\t= asymmetric_key_describe,\n \t.lookup_restriction\t= asymmetric_lookup_restriction,\n \t.asym_query\t\t= query_asymmetric_key,\ndiff --git a/crypto/asymmetric_keys/pkcs7_key_type.c b/crypto/asymmetric_keys/pkcs7_key_type.c\nindex b930d3bbf1af5..663a246742f71 100644\n--- a/crypto/asymmetric_keys/pkcs7_key_type.c\n+++ b/crypto/asymmetric_keys/pkcs7_key_type.c\n@@ -74,6 +74,7 @@ static struct key_type key_type_pkcs7 = {\n \t.instantiate\t\t= generic_key_instantiate,\n \t.revoke\t\t\t= user_revoke,\n \t.destroy\t\t= user_destroy,\n+\t.wipe\t\t\t= user_wipe,\n \t.describe\t\t= user_describe,\n \t.read\t\t\t= user_read,\n };\ndiff --git a/crypto/asymmetric_keys/public_key.c b/crypto/asymmetric_keys/public_key.c\nindex 09a0b83d5d77f..9931d56337e55 100644\n--- a/crypto/asymmetric_keys/public_key.c\n+++ b/crypto/asymmetric_keys/public_key.c\n@@ -13,6 +13,7 @@\n #include \u003ccrypto/sig.h\u003e\n #include \u003ckeys/asymmetric-subtype.h\u003e\n #include \u003clinux/asn1.h\u003e\n+#include \u003clinux/crash_core.h\u003e\n #include \u003clinux/err.h\u003e\n #include \u003clinux/kernel.h\u003e\n #include \u003clinux/module.h\u003e\n@@ -58,6 +59,19 @@ static void public_key_destroy(void *payload0, void *payload3)\n \tpublic_key_signature_free(payload3);\n }\n \n+/* wipe the key without freeing. used by CONFIG_CRASH_WIPE_SECRETS. */\n+static void public_key_wipe(void *payload0, void *payload3)\n+{\n+\tstruct public_key *key = payload0;\n+\n+\tif (key) {\n+\t\tif (key-\u003ekey)\n+\t\t\tcrash_wipe_memzero(key-\u003ekey, key-\u003ekeylen);\n+\t\tif (key-\u003eparams)\n+\t\t\tcrash_wipe_memzero(key-\u003eparams, key-\u003eparamlen);\n+\t}\n+}\n+\n /*\n  * Given a public_key, and an encoding and hash_algo to be used for signing\n  * and/or verification with that key, determine the name of the corresponding\n@@ -464,6 +478,7 @@ struct asymmetric_key_subtype public_key_subtype = {\n \t.name_len\t\t= sizeof(\"public_key\") - 1,\n \t.describe\t\t= public_key_describe,\n \t.destroy\t\t= public_key_destroy,\n+\t.wipe\t\t\t= public_key_wipe,\n \t.query\t\t\t= software_key_query,\n \t.eds_op\t\t\t= software_key_eds_op,\n \t.verify_signature\t= public_key_verify_signature_2,\ndiff --git a/drivers/md/dm-crypt.c b/drivers/md/dm-crypt.c\nindex 608b617fb817f..86adb2f9b94c0 100644\n--- a/drivers/md/dm-crypt.c\n+++ b/drivers/md/dm-crypt.c\n@@ -9,12 +9,14 @@\n  */\n \n #include \u003clinux/completion.h\u003e\n+#include \u003clinux/crash_core.h\u003e\n #include \u003clinux/err.h\u003e\n #include \u003clinux/module.h\u003e\n #include \u003clinux/hex.h\u003e\n #include \u003clinux/init.h\u003e\n #include \u003clinux/kernel.h\u003e\n #include \u003clinux/key.h\u003e\n+#include \u003clinux/list.h\u003e\n #include \u003clinux/bio.h\u003e\n #include \u003clinux/blkdev.h\u003e\n #include \u003clinux/blk-integrity.h\u003e\n@@ -234,6 +236,11 @@ struct crypt_config {\n \tstruct mutex bio_alloc_lock;\n \n \tu8 *authenc_key; /* space for keys in authenc() format (if used) */\n+\n+#ifdef CONFIG_CRASH_WIPE_SECRETS\n+\tstruct list_head wipe_list;\n+#endif\n+\n \tu8 key[] __counted_by(key_size);\n };\n \n@@ -243,6 +250,12 @@ struct crypt_config {\n \n static DEFINE_SPINLOCK(dm_crypt_clients_lock);\n static unsigned int dm_crypt_clients_n;\n+\n+#ifdef CONFIG_CRASH_WIPE_SECRETS\n+static LIST_HEAD(dm_crypt_wipe_list);\n+static DEFINE_SPINLOCK(dm_crypt_wipe_list_lock);\n+#endif\n+\n static volatile unsigned long dm_crypt_pages_per_client;\n #define DM_CRYPT_MEMORY_PERCENT\t\t\t2\n #define DM_CRYPT_MIN_PAGES_PER_CLIENT\t\t(BIO_MAX_VECS * 16)\n@@ -460,8 +473,7 @@ static void crypt_iv_lmk_dtr(struct crypt_config *cc)\n {\n \tstruct iv_lmk_private *lmk = \u0026cc-\u003eiv_gen_private.lmk;\n \n-\tkfree_sensitive(lmk-\u003eseed);\n-\tlmk-\u003eseed = NULL;\n+\tkfree_sensitive(xchg(\u0026lmk-\u003eseed, NULL));\n }\n \n static int crypt_iv_lmk_ctr(struct crypt_config *cc, struct dm_target *ti,\n@@ -582,10 +594,8 @@ static void crypt_iv_tcw_dtr(struct crypt_config *cc)\n {\n \tstruct iv_tcw_private *tcw = \u0026cc-\u003eiv_gen_private.tcw;\n \n-\tkfree_sensitive(tcw-\u003eiv_seed);\n-\ttcw-\u003eiv_seed = NULL;\n-\tkfree_sensitive(tcw-\u003ewhitening);\n-\ttcw-\u003ewhitening = NULL;\n+\tkfree_sensitive(xchg(\u0026tcw-\u003eiv_seed, NULL));\n+\tkfree_sensitive(xchg(\u0026tcw-\u003ewhitening, NULL));\n }\n \n static int crypt_iv_tcw_ctr(struct crypt_config *cc, struct dm_target *ti,\n@@ -761,8 +771,7 @@ static void crypt_iv_elephant_dtr(struct crypt_config *cc)\n {\n \tstruct iv_elephant_private *elephant = \u0026cc-\u003eiv_gen_private.elephant;\n \n-\tkfree_sensitive(elephant-\u003ekey);\n-\telephant-\u003ekey = NULL;\n+\tkfree_sensitive(xchg(\u0026elephant-\u003ekey, NULL));\n }\n \n static int crypt_iv_elephant_ctr(struct crypt_config *cc, struct dm_target *ti,\n@@ -2547,8 +2556,7 @@ static int crypt_set_keyring_key(struct crypt_config *cc, const char *key_string\n \t\tgoto free_new_key_string;\n \n \tset_bit(DM_CRYPT_KEY_VALID, \u0026cc-\u003eflags);\n-\tkfree_sensitive(cc-\u003ekey_string);\n-\tcc-\u003ekey_string = new_key_string;\n+\tkfree_sensitive(xchg(\u0026cc-\u003ekey_string, new_key_string));\n \treturn 0;\n \n free_new_key_string:\n@@ -2612,8 +2620,7 @@ static int crypt_set_key(struct crypt_config *cc, char *key)\n \tclear_bit(DM_CRYPT_KEY_VALID, \u0026cc-\u003eflags);\n \n \t/* wipe references to any kernel keyring key */\n-\tkfree_sensitive(cc-\u003ekey_string);\n-\tcc-\u003ekey_string = NULL;\n+\tkfree_sensitive(xchg(\u0026cc-\u003ekey_string, NULL));\n \n \t/* Decode key from its hex representation. */\n \tif (cc-\u003ekey_size \u0026\u0026 hex2bin(cc-\u003ekey, key, cc-\u003ekey_size) \u003c 0)\n@@ -2641,14 +2648,97 @@ static int crypt_wipe_key(struct crypt_config *cc)\n \tif (cc-\u003eiv_gen_ops \u0026\u0026 cc-\u003eiv_gen_ops-\u003ewipe)\n \t\tcc-\u003eiv_gen_ops-\u003ewipe(cc);\n \n-\tkfree_sensitive(cc-\u003ekey_string);\n-\tcc-\u003ekey_string = NULL;\n+\tkfree_sensitive(xchg(\u0026cc-\u003ekey_string, NULL));\n \tr = crypt_setkey(cc);\n \tmemset(\u0026cc-\u003ekey, 0, cc-\u003ekey_size * sizeof(u8));\n \n \treturn r;\n }\n \n+#ifdef CONFIG_CRASH_WIPE_SECRETS\n+static void crypt_crash_wipe_iv(struct crypt_config *cc)\n+{\n+\tif (cc-\u003eiv_gen_ops == \u0026crypt_iv_lmk_ops) {\n+\t\tstruct iv_lmk_private *lmk = \u0026cc-\u003eiv_gen_private.lmk;\n+\n+\t\tif (lmk-\u003eseed)\n+\t\t\tcrash_wipe_memzero(lmk-\u003eseed, LMK_SEED_SIZE);\n+\t} else if (cc-\u003eiv_gen_ops == \u0026crypt_iv_tcw_ops) {\n+\t\tstruct iv_tcw_private *tcw = \u0026cc-\u003eiv_gen_private.tcw;\n+\n+\t\tif (tcw-\u003eiv_seed)\n+\t\t\tcrash_wipe_memzero(tcw-\u003eiv_seed, cc-\u003eiv_size);\n+\t\tif (tcw-\u003ewhitening)\n+\t\t\tcrash_wipe_memzero(tcw-\u003ewhitening, TCW_WHITENING_SIZE);\n+\t} else if (cc-\u003eiv_gen_ops == \u0026crypt_iv_elephant_ops) {\n+\t\tstruct iv_elephant_private *elephant =\n+\t\t\t\u0026cc-\u003eiv_gen_private.elephant;\n+\n+\t\tif (elephant-\u003ekey)\n+\t\t\tcrash_wipe_memzero(elephant-\u003ekey,\n+\t\t\t\t\t   sizeof(*elephant-\u003ekey));\n+\t}\n+}\n+\n+static int crypt_crash_wipe(struct notifier_block *nb, unsigned long action,\n+\t\t\t    void *data)\n+{\n+\tstruct crypt_config *cc;\n+\n+\t/* No locking: all other CPUs are stopped, and a cc is unlinked before\n+\t * it is freed, so the forward walk can't reach freed memory.\n+\t */\n+\tlist_for_each_entry(cc, \u0026dm_crypt_wipe_list, wipe_list) {\n+\t\tcrash_wipe_memzero(cc-\u003ekey, cc-\u003ekey_size);\n+\n+\t\tif (cc-\u003eauthenc_key)\n+\t\t\tcrash_wipe_memzero(cc-\u003eauthenc_key,\n+\t\t\t\t\t   crypt_authenckey_size(cc));\n+\n+\t\tif (cc-\u003ekey_string)\n+\t\t\tcrash_wipe_memzero(cc-\u003ekey_string,\n+\t\t\t\t\t   strlen(cc-\u003ekey_string));\n+\n+\t\tcrypt_crash_wipe_iv(cc);\n+\t}\n+\n+\treturn NOTIFY_DONE;\n+}\n+\n+static struct notifier_block crypt_crash_wipe_nb = {\n+\t.notifier_call = crypt_crash_wipe\n+};\n+\n+static void crypt_track_cc(struct crypt_config *cc)\n+{\n+\tspin_lock(\u0026dm_crypt_wipe_list_lock);\n+\tlist_add(\u0026cc-\u003ewipe_list, \u0026dm_crypt_wipe_list);\n+\tspin_unlock(\u0026dm_crypt_wipe_list_lock);\n+}\n+\n+static void crypt_untrack_cc(struct crypt_config *cc)\n+{\n+\tspin_lock(\u0026dm_crypt_wipe_list_lock);\n+\tlist_del(\u0026cc-\u003ewipe_list);\n+\tspin_unlock(\u0026dm_crypt_wipe_list_lock);\n+}\n+\n+static void crypt_crash_wipe_init(void)\n+{\n+\tcrash_wipe_secrets_register(\u0026crypt_crash_wipe_nb);\n+}\n+\n+static void crypt_crash_wipe_exit(void)\n+{\n+\tcrash_wipe_secrets_unregister(\u0026crypt_crash_wipe_nb);\n+}\n+#else\n+static void crypt_track_cc(struct crypt_config *cc) { }\n+static void crypt_untrack_cc(struct crypt_config *cc) { }\n+static void crypt_crash_wipe_init(void) { }\n+static void crypt_crash_wipe_exit(void) { }\n+#endif /* CONFIG_CRASH_WIPE_SECRETS */\n+\n static void crypt_calculate_pages_per_client(void)\n {\n \tunsigned long pages = (totalram_pages() - totalhigh_pages()) * DM_CRYPT_MEMORY_PERCENT / 100;\n@@ -2729,12 +2819,15 @@ static void crypt_dtr(struct dm_target *ti)\n \t\tdm_put_device(ti, cc-\u003edev);\n \n \tkfree_sensitive(cc-\u003ecipher_string);\n-\tkfree_sensitive(cc-\u003ekey_string);\n+\tkfree_sensitive(xchg(\u0026cc-\u003ekey_string, NULL));\n \tkfree_sensitive(cc-\u003ecipher_auth);\n-\tkfree_sensitive(cc-\u003eauthenc_key);\n+\tkfree_sensitive(xchg(\u0026cc-\u003eauthenc_key, NULL));\n \n \tmutex_destroy(\u0026cc-\u003ebio_alloc_lock);\n \n+\tmemzero_explicit(cc-\u003ekey, cc-\u003ekey_size);\n+\tcrypt_untrack_cc(cc);\n+\n \t/* Must zero key material before freeing */\n \tkfree_sensitive(cc);\n \n@@ -3210,6 +3303,8 @@ static int crypt_ctr(struct dm_target *ti, unsigned int argc, char **argv)\n \n \tti-\u003eprivate = cc;\n \n+\tcrypt_track_cc(cc);\n+\n \tspin_lock(\u0026dm_crypt_clients_lock);\n \tdm_crypt_clients_n++;\n \tcrypt_calculate_pages_per_client();\n@@ -3716,7 +3811,29 @@ static struct target_type crypt_target = {\n \t.iterate_devices = crypt_iterate_devices,\n \t.io_hints = crypt_io_hints,\n };\n-module_dm(crypt);\n+\n+static int __init dm_crypt_init(void)\n+{\n+\tint r;\n+\n+\tcrypt_crash_wipe_init();\n+\n+\tr = dm_register_target(\u0026crypt_target);\n+\tif (r) {\n+\t\tcrypt_crash_wipe_exit();\n+\t\treturn r;\n+\t}\n+\n+\treturn 0;\n+}\n+module_init(dm_crypt_init);\n+\n+static void __exit dm_crypt_exit(void)\n+{\n+\tcrypt_crash_wipe_exit();\n+\tdm_unregister_target(\u0026crypt_target);\n+}\n+module_exit(dm_crypt_exit);\n \n MODULE_AUTHOR(\"Jana Saout \u003cjana@saout.de\u003e\");\n MODULE_DESCRIPTION(DM_NAME \" target for transparent encryption / decryption\");\ndiff --git a/drivers/nvme/common/keyring.c b/drivers/nvme/common/keyring.c\nindex 32d16c53133be..33303f035fb1b 100644\n--- a/drivers/nvme/common/keyring.c\n+++ b/drivers/nvme/common/keyring.c\n@@ -84,6 +84,7 @@ static struct key_type nvme_tls_psk_key_type = {\n \t.instantiate    = generic_key_instantiate,\n \t.revoke         = user_revoke,\n \t.destroy        = user_destroy,\n+\t.wipe\t\t= user_wipe,\n \t.describe       = nvme_tls_psk_describe,\n \t.read           = user_read,\n };\ndiff --git a/fs/crypto/block.c b/fs/crypto/block.c\nindex 5193f8ba3ee09..a2fa1469b3c40 100644\n--- a/fs/crypto/block.c\n+++ b/fs/crypto/block.c\n@@ -15,6 +15,7 @@\n \n #include \u003clinux/blk-crypto.h\u003e\n #include \u003clinux/blkdev.h\u003e\n+#include \u003clinux/crash_core.h\u003e\n #include \u003clinux/export.h\u003e\n #include \u003clinux/sched/mm.h\u003e\n #include \u003clinux/slab.h\u003e\n@@ -144,6 +145,15 @@ int fscrypt_prepare_inline_crypt_key(struct fscrypt_prepared_key *prep_key,\n \treturn err;\n }\n \n+#ifdef CONFIG_CRASH_WIPE_SECRETS\n+void fscrypt_crash_wipe_inline_crypt_key(struct fscrypt_prepared_key *prep_key)\n+{\n+\tif (prep_key-\u003eblk_key)\n+\t\tcrash_wipe_memzero(prep_key-\u003eblk_key-\u003ebytes,\n+\t\t\t\t   sizeof(prep_key-\u003eblk_key-\u003ebytes));\n+}\n+#endif /* CONFIG_CRASH_WIPE_SECRETS */\n+\n void fscrypt_destroy_inline_crypt_key(struct super_block *sb,\n \t\t\t\t      struct fscrypt_prepared_key *prep_key)\n {\ndiff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h\nindex 74329e0953d14..95cb50e5cbcd4 100644\n--- a/fs/crypto/fscrypt_private.h\n+++ b/fs/crypto/fscrypt_private.h\n@@ -413,6 +413,10 @@ int fscrypt_prepare_inline_crypt_key(struct fscrypt_prepared_key *prep_key,\n void fscrypt_destroy_inline_crypt_key(struct super_block *sb,\n \t\t\t\t      struct fscrypt_prepared_key *prep_key);\n \n+#ifdef CONFIG_CRASH_WIPE_SECRETS /* \u0026\u0026 CONFIG_FS_ENCRYPTION_INLINE_CRYPT */\n+void fscrypt_crash_wipe_inline_crypt_key(struct fscrypt_prepared_key *prep_key);\n+#endif\n+\n int fscrypt_derive_sw_secret(struct super_block *sb,\n \t\t\t     const u8 *wrapped_key, size_t wrapped_key_size,\n \t\t\t     u8 sw_secret[BLK_CRYPTO_SW_SECRET_SIZE]);\n@@ -454,6 +458,13 @@ fscrypt_destroy_inline_crypt_key(struct super_block *sb,\n {\n }\n \n+#ifdef CONFIG_CRASH_WIPE_SECRETS /* \u0026\u0026 !CONFIG_FS_ENCRYPTION_INLINE_CRYPT */\n+static inline void\n+fscrypt_crash_wipe_inline_crypt_key(struct fscrypt_prepared_key *prep_key)\n+{\n+}\n+#endif\n+\n static inline int\n fscrypt_derive_sw_secret(struct super_block *sb,\n \t\t\t const u8 *wrapped_key, size_t wrapped_key_size,\n@@ -760,6 +771,10 @@ static inline int fscrypt_require_key(struct inode *inode)\n \n void fscrypt_put_direct_key(struct fscrypt_direct_key *dk);\n \n+#ifdef CONFIG_CRASH_WIPE_SECRETS\n+void fscrypt_crash_wipe_direct_keys(void);\n+#endif\n+\n int fscrypt_setup_v1_file_key(struct fscrypt_inode_info *ci,\n \t\t\t      const u8 *raw_master_key);\n \ndiff --git a/fs/crypto/keyring.c b/fs/crypto/keyring.c\nindex 76e28d1e00640..5ac302146eb2c 100644\n--- a/fs/crypto/keyring.c\n+++ b/fs/crypto/keyring.c\n@@ -19,6 +19,7 @@\n  */\n \n #include \u003ccrypto/skcipher.h\u003e\n+#include \u003clinux/crash_core.h\u003e\n #include \u003clinux/export.h\u003e\n #include \u003clinux/key-type.h\u003e\n #include \u003clinux/once.h\u003e\n@@ -239,8 +240,9 @@ void fscrypt_destroy_keyring(struct super_block *sb)\n \t\t\tfscrypt_initiate_key_removal(sb, mk);\n \t\t}\n \t}\n+\t/* Stop panic-time walkers from finding @keyring before it is freed. */\n+\tsmp_store_release(\u0026sb-\u003es_master_keys, NULL);\n \tkfree_sensitive(keyring);\n-\tsb-\u003es_master_keys = NULL;\n }\n \n static struct hlist_head *\n@@ -640,6 +642,13 @@ static void fscrypt_provisioning_key_destroy(struct key *key)\n \tkfree_sensitive(key-\u003epayload.data[0]);\n }\n \n+/* wipe the key without freeing. used by CONFIG_CRASH_WIPE_SECRETS. */\n+static void fscrypt_provisioning_key_wipe(struct key *key)\n+{\n+\tif (key-\u003epayload.data[0])\n+\t\tcrash_wipe_memzero(key-\u003epayload.data[0], key-\u003edatalen);\n+}\n+\n static struct key_type key_type_fscrypt_provisioning = {\n \t.name\t\t\t= \"fscrypt-provisioning\",\n \t.preparse\t\t= fscrypt_provisioning_key_preparse,\n@@ -647,6 +656,7 @@ static struct key_type key_type_fscrypt_provisioning = {\n \t.instantiate\t\t= generic_key_instantiate,\n \t.describe\t\t= fscrypt_provisioning_key_describe,\n \t.destroy\t\t= fscrypt_provisioning_key_destroy,\n+\t.wipe\t\t\t= fscrypt_provisioning_key_wipe,\n };\n \n /*\n@@ -1220,6 +1230,58 @@ int fscrypt_ioctl_get_key_status(struct file *filp, void __user *uarg)\n }\n EXPORT_SYMBOL_GPL(fscrypt_ioctl_get_key_status);\n \n+#ifdef CONFIG_CRASH_WIPE_SECRETS\n+/*\n+ * Wipe the master keys of one superblock.  The master keys don't live on a\n+ * keyring of their own key_type, so the keyrings core can't reach them.\n+ */\n+static void fscrypt_crash_wipe_sb(struct super_block *sb)\n+{\n+\tstruct fscrypt_keyring *keyring = sb-\u003es_master_keys;\n+\tsize_t i;\n+\n+\tif (!keyring)\n+\t\treturn;\n+\n+\tfor (i = 0; i \u003c ARRAY_SIZE(keyring-\u003ekey_hashtable); i++) {\n+\t\tstruct fscrypt_master_key *mk;\n+\n+\t\thlist_for_each_entry(mk, \u0026keyring-\u003ekey_hashtable[i], mk_node) {\n+\t\t\tstruct fscrypt_inode_info *ci;\n+\t\t\tstruct fscrypt_mode_key *node;\n+\n+\t\t\tcrash_wipe_memzero(\u0026mk-\u003emk_secret,\n+\t\t\t\t\t   sizeof(mk-\u003emk_secret));\n+\n+\t\t\tlist_for_each_entry(node, \u0026mk-\u003emk_mode_keys, link)\n+\t\t\t\tfscrypt_crash_wipe_inline_crypt_key(\u0026node-\u003ekey);\n+\n+\t\t\tlist_for_each_entry(ci, \u0026mk-\u003emk_decrypted_inodes,\n+\t\t\t\t\t    ci_master_key_link)\n+\t\t\t\tfscrypt_crash_wipe_inline_crypt_key(\u0026ci-\u003eci_enc_key);\n+\t\t}\n+\t}\n+}\n+\n+/* Called far into vpanic from crash_core.c with other CPUs stopped and\n+ * preemption disabled\n+ */\n+static int fscrypt_crash_wipe(struct notifier_block *nb, unsigned long action,\n+\t\t\t      void *data)\n+{\n+\tif (!super_blocks_crash_wipe(fscrypt_crash_wipe_sb))\n+\t\tpr_crit(\"crash_wipe_secrets: can't acquire sb_lock. skipping fscrypt keys.\\n\");\n+\n+\tfscrypt_crash_wipe_direct_keys();\n+\n+\treturn NOTIFY_DONE;\n+}\n+\n+static struct notifier_block fscrypt_crash_wipe_nb = {\n+\t.notifier_call = fscrypt_crash_wipe\n+};\n+#endif /* CONFIG_CRASH_WIPE_SECRETS */\n+\n void __init fscrypt_init_keyring(void)\n {\n \tint err;\n@@ -1235,4 +1297,8 @@ void __init fscrypt_init_keyring(void)\n \tif (err)\n \t\tpanic(\"failed to register fscrypt-provisioning key type (%d)\",\n \t\t      err);\n+\n+#ifdef CONFIG_CRASH_WIPE_SECRETS\n+\tcrash_wipe_secrets_register(\u0026fscrypt_crash_wipe_nb);\n+#endif\n }\ndiff --git a/fs/crypto/keysetup_v1.c b/fs/crypto/keysetup_v1.c\nindex 87fe13ccb2535..08dd0a682a8f8 100644\n--- a/fs/crypto/keysetup_v1.c\n+++ b/fs/crypto/keysetup_v1.c\n@@ -23,6 +23,7 @@\n #include \u003ccrypto/aes.h\u003e\n #include \u003ccrypto/utils.h\u003e\n #include \u003ckeys/user-type.h\u003e\n+#include \u003clinux/crash_core.h\u003e\n #include \u003clinux/hashtable.h\u003e\n \n #include \"fscrypt_private.h\"\n@@ -118,6 +119,20 @@ void fscrypt_put_direct_key(struct fscrypt_direct_key *dk)\n \tfree_direct_key(dk);\n }\n \n+#ifdef CONFIG_CRASH_WIPE_SECRETS\n+void fscrypt_crash_wipe_direct_keys(void)\n+{\n+\tstruct fscrypt_direct_key *dk;\n+\tunsigned int i;\n+\n+\t/* No locking: all other CPUs are stopped, so nothing can race with us. */\n+\thash_for_each(fscrypt_direct_keys, i, dk, dk_node) {\n+\t\tcrash_wipe_memzero(dk-\u003edk_raw, sizeof(dk-\u003edk_raw));\n+\t\tfscrypt_crash_wipe_inline_crypt_key(\u0026dk-\u003edk_key);\n+\t}\n+}\n+#endif /* CONFIG_CRASH_WIPE_SECRETS */\n+\n /*\n  * Find/insert the given key into the fscrypt_direct_keys table.  If found, it\n  * is returned with elevated refcount, and 'to_insert' is freed if non-NULL.  If\ndiff --git a/fs/nfs/nfs4idmap.c b/fs/nfs/nfs4idmap.c\nindex bc397110d977f..26f579ae2c732 100644\n--- a/fs/nfs/nfs4idmap.c\n+++ b/fs/nfs/nfs4idmap.c\n@@ -190,6 +190,7 @@ static struct key_type key_type_id_resolver = {\n \t.instantiate\t= generic_key_instantiate,\n \t.revoke\t\t= user_revoke,\n \t.destroy\t= user_destroy,\n+\t.wipe\t\t= user_wipe,\n \t.describe\t= user_describe,\n \t.read\t\t= user_read,\n };\n@@ -412,6 +413,7 @@ static struct key_type key_type_id_resolver_legacy = {\n \t.instantiate\t= generic_key_instantiate,\n \t.revoke\t\t= user_revoke,\n \t.destroy\t= user_destroy,\n+\t.wipe\t\t= user_wipe,\n \t.describe\t= user_describe,\n \t.read\t\t= user_read,\n \t.request_key\t= nfs_idmap_legacy_upcall,\ndiff --git a/fs/super.c b/fs/super.c\nindex 5feecf5d9038d..cd51e9cfe93ba 100644\n--- a/fs/super.c\n+++ b/fs/super.c\n@@ -2463,3 +2463,32 @@ int sb_init_dio_done_wq(struct super_block *sb)\n \treturn 0;\n }\n EXPORT_SYMBOL_GPL(sb_init_dio_done_wq);\n+\n+#ifdef CONFIG_CRASH_WIPE_SECRETS\n+/**\n+ * super_blocks_crash_wipe - run @wipe against every live superblock\n+ * @wipe: callback to invoke for each superblock\n+ *\n+ * Called from the panic path with other CPUs stopped and preemption disabled.\n+ * The callback must not sleep, allocate, free or take locks.\n+ *\n+ * Returns false without doing anything if @sb_lock could not be acquired, in\n+ * which case the list may be inconsistent and walking it is unsafe.\n+ */\n+bool super_blocks_crash_wipe(void (*wipe)(struct super_block *sb))\n+{\n+\tstruct super_block *sb;\n+\n+\t/* There is no point in waiting for a lock that will never be released\n+\t * at this stage.\n+\t */\n+\tif (!spin_trylock(\u0026sb_lock))\n+\t\treturn false;\n+\n+\tlist_for_each_entry(sb, \u0026super_blocks, s_list)\n+\t\twipe(sb);\n+\n+\tspin_unlock(\u0026sb_lock);\n+\treturn true;\n+}\n+#endif\ndiff --git a/include/keys/asymmetric-subtype.h b/include/keys/asymmetric-subtype.h\nindex d55171f640a04..17c74070c1014 100644\n--- a/include/keys/asymmetric-subtype.h\n+++ b/include/keys/asymmetric-subtype.h\n@@ -32,6 +32,11 @@ struct asymmetric_key_subtype {\n \t/* Destroy a key of this subtype */\n \tvoid (*destroy)(void *payload_crypto, void *payload_auth);\n \n+\t/* Wipe a key of this subtype without freeing it (optional).  Used by\n+\t * CONFIG_CRASH_WIPE_SECRETS from the panic path.\n+\t */\n+\tvoid (*wipe)(void *payload_crypto, void *payload_auth);\n+\n \tint (*query)(const struct kernel_pkey_params *params,\n \t\t     struct kernel_pkey_query *info);\n \ndiff --git a/include/keys/user-type.h b/include/keys/user-type.h\nindex 386c314327899..685fa054b0ac5 100644\n--- a/include/keys/user-type.h\n+++ b/include/keys/user-type.h\n@@ -40,6 +40,7 @@ extern void user_free_preparse(struct key_preparsed_payload *prep);\n extern int user_update(struct key *key, struct key_preparsed_payload *prep);\n extern void user_revoke(struct key *key);\n extern void user_destroy(struct key *key);\n+extern void user_wipe(struct key *key);\n extern void user_describe(const struct key *user, struct seq_file *m);\n extern long user_read(const struct key *key, char *buffer, size_t buflen);\n \ndiff --git a/include/linux/crash_core.h b/include/linux/crash_core.h\nindex bc087124cd78e..d3e4192b7e6d6 100644\n--- a/include/linux/crash_core.h\n+++ b/include/linux/crash_core.h\n@@ -5,6 +5,8 @@\n #include \u003clinux/linkage.h\u003e\n #include \u003clinux/elfcore.h\u003e\n #include \u003clinux/elf.h\u003e\n+#include \u003clinux/notifier.h\u003e\n+#include \u003clinux/string.h\u003e\n \n struct kimage;\n \n@@ -14,6 +16,26 @@ struct crash_mem {\n \tstruct range ranges[] __counted_by(max_nr_ranges);\n };\n \n+#ifdef CONFIG_CRASH_WIPE_SECRETS\n+/*\n+ * Record a range that has already been wiped, or wipe and record it in one\n+ * operation.  Architectures may use the ranges to push the wipes out to\n+ * memory before kexec disables the caches.\n+ */\n+void crash_wipe_cache_range(void *addr, size_t size);\n+void crash_wipe_memzero(void *addr, size_t size);\n+\n+void arch_crash_wipe_range(void *addr, size_t size);\n+void arch_crash_wipe_flush(void);\n+#else\n+static inline void crash_wipe_cache_range(void *addr, size_t size) { }\n+\n+static inline void crash_wipe_memzero(void *addr, size_t size)\n+{\n+\tmemzero_explicit(addr, size);\n+}\n+#endif\n+\n #ifdef CONFIG_CRASH_DUMP\n \n int crash_shrink_memory(unsigned long new_size);\n@@ -34,6 +56,21 @@ static inline void arch_kexec_protect_crashkres(void) { }\n static inline void arch_kexec_unprotect_crashkres(void) { }\n #endif\n \n+#ifdef CONFIG_CRASH_WIPE_SECRETS\n+int crash_wipe_secrets_register(struct notifier_block *nb);\n+int crash_wipe_secrets_unregister(struct notifier_block *nb);\n+#else\n+static inline int crash_wipe_secrets_register(struct notifier_block *nb)\n+{\n+\treturn 0;\n+}\n+\n+static inline int crash_wipe_secrets_unregister(struct notifier_block *nb)\n+{\n+\treturn 0;\n+}\n+#endif\n+\n #ifndef arch_crash_handle_hotplug_event\n static inline void arch_crash_handle_hotplug_event(struct kimage *image, void *arg) { }\n #endif\ndiff --git a/include/linux/crypto.h b/include/linux/crypto.h\nindex b7c97f1c47c92..59d6d62180fdd 100644\n--- a/include/linux/crypto.h\n+++ b/include/linux/crypto.h\n@@ -14,6 +14,7 @@\n \n #include \u003clinux/completion.h\u003e\n #include \u003clinux/errno.h\u003e\n+#include \u003clinux/list.h\u003e\n #include \u003clinux/refcount_types.h\u003e\n #include \u003clinux/slab.h\u003e\n #include \u003clinux/types.h\u003e\n@@ -420,6 +421,16 @@ struct crypto_tfm {\n \n \tstruct crypto_alg *__crt_alg;\n \n+#ifdef CONFIG_CRASH_WIPE_SECRETS\n+\t/*\n+\t * Link on crypto_tfm_list, used to wipe the expanded key schedule in\n+\t * __crt_ctx before kdump. wipe_size covers __crt_ctx only, excluding\n+\t * this struct and any frontend preceding it.\n+\t */\n+\tstruct hlist_node wipe_list;\n+\tsize_t wipe_size;\n+#endif\n+\n \tvoid *__crt_ctx[] CRYPTO_MINALIGN_ATTR;\n };\n \ndiff --git a/include/linux/fs.h b/include/linux/fs.h\nindex 072d8cd09a0b2..7832a77908d2b 100644\n--- a/include/linux/fs.h\n+++ b/include/linux/fs.h\n@@ -2378,6 +2378,10 @@ extern __printf(2, 3)\n int super_setup_bdi_name(struct super_block *sb, char *fmt, ...);\n extern int super_setup_bdi(struct super_block *sb);\n \n+#ifdef CONFIG_CRASH_WIPE_SECRETS\n+bool super_blocks_crash_wipe(void (*wipe)(struct super_block *sb));\n+#endif\n+\n static inline void super_set_uuid(struct super_block *sb, const u8 *uuid, unsigned len)\n {\n \tif (WARN_ON(len \u003e sizeof(sb-\u003es_uuid)))\ndiff --git a/include/linux/key-type.h b/include/linux/key-type.h\nindex bb97bd3e5af4f..21e07db0c5f2d 100644\n--- a/include/linux/key-type.h\n+++ b/include/linux/key-type.h\n@@ -122,6 +122,15 @@ struct key_type {\n \t/* clear the data from a key (optional) */\n \tvoid (*destroy)(struct key *key);\n \n+\t/* wipe the key material without free'ing (optional)\n+\t * - used from CONFIG_CRASH_WIPE_SECRETS during panic to keep keys out\n+\t *   of crash dumps\n+\t * - called from the panic path with other CPUs stopped and preemption\n+\t *   disabled\n+\t * - must not sleep, allocate, free or take locks\n+\t */\n+\tvoid (*wipe)(struct key *key);\n+\n \t/* describe a key */\n \tvoid (*describe)(const struct key *key, struct seq_file *p);\n \ndiff --git a/include/linux/set_memory.h b/include/linux/set_memory.h\nindex 3030d9245f5ac..65d0fa010368d 100644\n--- a/include/linux/set_memory.h\n+++ b/include/linux/set_memory.h\n@@ -44,7 +44,22 @@ static inline bool kernel_page_present(struct page *page)\n {\n \treturn true;\n }\n-#else /* CONFIG_ARCH_HAS_SET_DIRECT_MAP */\n+#endif /* !CONFIG_ARCH_HAS_SET_DIRECT_MAP */\n+\n+/*\n+ * Variant of set_direct_map_default_noflush() that is safe to call from atomic\n+ * context, for pages the caller knows are already mapped at page granularity.\n+ * Architectures that need to split the linear map, and can therefore sleep,\n+ * override this.\n+ */\n+#ifndef set_direct_map_default_nosplit\n+static inline int set_direct_map_default_nosplit(struct page *page)\n+{\n+\treturn set_direct_map_default_noflush(page);\n+}\n+#endif\n+\n+#ifdef CONFIG_ARCH_HAS_SET_DIRECT_MAP\n /*\n  * Some architectures, e.g. ARM64 can disable direct map modifications at\n  * boot time. Let them overrive this query.\ndiff --git a/kernel/Kconfig.kexec b/kernel/Kconfig.kexec\nindex 15632358bcf71..1d2d273145df0 100644\n--- a/kernel/Kconfig.kexec\n+++ b/kernel/Kconfig.kexec\n@@ -179,4 +179,20 @@ config CRASH_MAX_MEMORY_RANGES\n \t  the computation behind the value provided through the\n \t  /sys/kernel/crash_elfcorehdr_size attribute.\n \n+config CRASH_WIPE_SECRETS\n+\tbool \"Wipe secrets before kdump\"\n+\tdepends on CRASH_DUMP\n+\thelp\n+\t  Wipe secrets (e.g. kernel keyring and memfd_secret pages) on crash or\n+\t  panic. This is a best effort, defense-in-depth feature: If the panic\n+\t  happens at a really bad time, or if copies of the secrets are present\n+\t  in places like on the stack, in I/O buffers, or in userspace memory\n+\t  not allocated through memfd_secret, they may still be leaked.\n+\n+\t  Note that enabling this feature carries some risk of crashing the\n+\t  system during the wipe process if the kernel was already unstable\n+\t  when the panic happened.\n+\n+\t  If unsure, say N.\n+\n endmenu\ndiff --git a/kernel/crash_core.c b/kernel/crash_core.c\nindex 2b36aa9fade06..42faf8d0a4c48 100644\n--- a/kernel/crash_core.c\n+++ b/kernel/crash_core.c\n@@ -23,6 +23,7 @@\n #include \u003clinux/objtool.h\u003e\n #include \u003clinux/delay.h\u003e\n #include \u003clinux/panic.h\u003e\n+#include \u003clinux/timekeeping.h\u003e\n \n #include \u003casm/page.h\u003e\n #include \u003casm/sections.h\u003e\n@@ -33,6 +34,56 @@\n /* Per cpu memory for storing cpu states in case of system crash. */\n note_buf_t __percpu *crash_notes;\n \n+#ifdef CONFIG_CRASH_WIPE_SECRETS\n+ATOMIC_NOTIFIER_HEAD(crash_wipe_secrets_notifier_list);\n+\n+int crash_wipe_secrets_register(struct notifier_block *nb)\n+{\n+\treturn atomic_notifier_chain_register(\n+\t\t\t\u0026crash_wipe_secrets_notifier_list, nb);\n+}\n+EXPORT_SYMBOL_GPL(crash_wipe_secrets_register);\n+\n+int crash_wipe_secrets_unregister(struct notifier_block *nb)\n+{\n+\treturn atomic_notifier_chain_unregister(\n+\t\t\t\u0026crash_wipe_secrets_notifier_list, nb);\n+}\n+EXPORT_SYMBOL_GPL(crash_wipe_secrets_unregister);\n+\n+/*\n+ * Some kexec paths disable the data cache without first cleaning it.  Give\n+ * architectures valid virtual ranges for the wiped data, then let them defer\n+ * any completion barrier until all crash-wipe callbacks have run.\n+ */\n+void __weak arch_crash_wipe_range(void *addr, size_t size) { }\n+void __weak arch_crash_wipe_flush(void) { }\n+\n+void crash_wipe_cache_range(void *addr, size_t size)\n+{\n+\tif (size)\n+\t\tarch_crash_wipe_range(addr, size);\n+}\n+EXPORT_SYMBOL_GPL(crash_wipe_cache_range);\n+\n+void crash_wipe_memzero(void *addr, size_t size)\n+{\n+\tmemzero_explicit(addr, size);\n+\tcrash_wipe_cache_range(addr, size);\n+}\n+EXPORT_SYMBOL_GPL(crash_wipe_memzero);\n+\n+static void crash_wipe_secrets(void)\n+{\n+\tpr_info(\"Wiping sensitive secrets...\\n\");\n+\tatomic_notifier_call_chain(\u0026crash_wipe_secrets_notifier_list, 0, NULL);\n+\tarch_crash_wipe_flush();\n+\tpr_info(\"Done wiping secrets.\\n\");\n+}\n+#else\n+static inline void crash_wipe_secrets(void) { }\n+#endif /* CONFIG_CRASH_WIPE_SECRETS */\n+\n /* time to wait for possible DMA to finish before starting the kdump kernel\n  * when a CMA reservation is used\n  */\n@@ -142,6 +193,7 @@ void __noclone __crash_kexec(struct pt_regs *regs)\n \t\t\tcrash_save_vmcoreinfo();\n \t\t\tmachine_crash_shutdown(\u0026fixed_regs);\n \t\t\tcrash_cma_clear_pending_dma();\n+\t\t\tcrash_wipe_secrets();\n \t\t\tmachine_kexec(kexec_crash_image);\n \t\t}\n \t\tkexec_unlock();\ndiff --git a/mm/secretmem.c b/mm/secretmem.c\nindex d29865075b6ea..db00b7d060807 100644\n--- a/mm/secretmem.c\n+++ b/mm/secretmem.c\n@@ -13,9 +13,11 @@\n #include \u003clinux/bitops.h\u003e\n #include \u003clinux/printk.h\u003e\n #include \u003clinux/pagemap.h\u003e\n+#include \u003clinux/notifier.h\u003e\n #include \u003clinux/syscalls.h\u003e\n #include \u003clinux/pseudo_fs.h\u003e\n #include \u003clinux/secretmem.h\u003e\n+#include \u003clinux/crash_core.h\u003e\n #include \u003clinux/set_memory.h\u003e\n #include \u003clinux/sched/signal.h\u003e\n \n@@ -187,6 +189,54 @@ static const struct inode_operations secretmem_iops = {\n \n static struct vfsmount *secretmem_mnt;\n \n+/* Called far into vpanic from crash_core.c with other CPUs stopped and\n+ * preemption disabled\n+ */\n+static int secretmem_crash_wipe(struct notifier_block *nb, unsigned long action,\n+\t\tvoid *data)\n+{\n+\tstruct super_block *sb;\n+\tstruct inode *inode;\n+\n+\tif (!secretmem_mnt)\n+\t\treturn NOTIFY_DONE;\n+\tsb = secretmem_mnt-\u003emnt_sb;\n+\n+\t/* If the list was modified in the exact moment we panic'ed, it might be\n+\t * in an inconsistent state that would be unsafe to iterate. If we can't\n+\t * get the lock, too bad, that's all we can do here.\n+\t */\n+\tif (!spin_trylock(\u0026sb-\u003es_inode_list_lock)) {\n+\t\tpr_crit(\"crash_wipe_secrets: can't acquire secretmem superblock lock.\\n\"\n+\t\t\t \"crash_wipe_secrets: skipping zeroizing secretmem.\\n\");\n+\t\treturn NOTIFY_DONE;\n+\t}\n+\n+\tlist_for_each_entry(inode, \u0026sb-\u003es_inodes, i_sb_list) {\n+\t\tXA_STATE(xas, \u0026inode-\u003ei_mapping-\u003ei_pages, 0);\n+\t\tstruct folio *folio;\n+\n+\t\t/* no need for locks if we're burning down the house :) */\n+\t\txas_for_each(\u0026xas, folio, ULONG_MAX) {\n+\t\t\tif (xas_retry(\u0026xas, folio) || xa_is_value(folio))\n+\t\t\t\tcontinue;\n+\n+\t\t\t/* secretmem_fault() already split the linear map */\n+\t\t\tset_direct_map_default_nosplit(folio_page(folio, 0));\n+\t\t\tfolio_zero_segment(folio, 0, folio_size(folio));\n+\t\t\tcrash_wipe_cache_range(folio_address(folio),\n+\t\t\t\t\t       folio_size(folio));\n+\t\t}\n+\t}\n+\tspin_unlock(\u0026sb-\u003es_inode_list_lock);\n+\t/* off to kexec()! */\n+\treturn NOTIFY_DONE;\n+}\n+\n+static struct notifier_block secretmem_wipe_nb = {\n+\t.notifier_call = secretmem_crash_wipe\n+};\n+\n static struct file *secretmem_file_create(unsigned long flags)\n {\n \tstruct file *file;\n@@ -212,6 +262,8 @@ static struct file *secretmem_file_create(unsigned long flags)\n \tinode-\u003ei_mode |= S_IFREG;\n \tinode-\u003ei_size = 0;\n \n+\tinode_sb_list_add(inode);\n+\n \tatomic_inc(\u0026secretmem_users);\n \n \treturn file;\n@@ -263,6 +315,7 @@ static int __init secretmem_init(void)\n \tif (IS_ERR(secretmem_mnt))\n \t\treturn PTR_ERR(secretmem_mnt);\n \n+\tcrash_wipe_secrets_register(\u0026secretmem_wipe_nb);\n \treturn 0;\n }\n fs_initcall(secretmem_init);\ndiff --git a/net/rxrpc/ar-internal.h b/net/rxrpc/ar-internal.h\nindex 865f05fe37ab9..6466eb5a9929e 100644\n--- a/net/rxrpc/ar-internal.h\n+++ b/net/rxrpc/ar-internal.h\n@@ -282,6 +282,11 @@ struct rxrpc_security {\n \t/* Destroy the payload of a server key */\n \tvoid (*destroy_server_key)(struct key *);\n \n+\t/* Wipe the payload of a server key without freeing it.  Used by\n+\t * CONFIG_CRASH_WIPE_SECRETS from the panic path.\n+\t */\n+\tvoid (*wipe_server_key)(struct key *);\n+\n \t/* Describe a server key */\n \tvoid (*describe_server_key)(const struct key *, struct seq_file *);\n \ndiff --git a/net/rxrpc/key.c b/net/rxrpc/key.c\nindex a0aa78d892897..e612b7a8df13f 100644\n--- a/net/rxrpc/key.c\n+++ b/net/rxrpc/key.c\n@@ -11,6 +11,7 @@\n #define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n \n #include \u003clinux/module.h\u003e\n+#include \u003clinux/crash_core.h\u003e\n #include \u003clinux/net.h\u003e\n #include \u003clinux/overflow.h\u003e\n #include \u003clinux/skbuff.h\u003e\n@@ -26,6 +27,7 @@\n static int rxrpc_preparse(struct key_preparsed_payload *);\n static void rxrpc_free_preparse(struct key_preparsed_payload *);\n static void rxrpc_destroy(struct key *);\n+static void rxrpc_wipe(struct key *);\n static void rxrpc_describe(const struct key *, struct seq_file *);\n static long rxrpc_read(const struct key *, char *, size_t);\n \n@@ -40,6 +42,7 @@ struct key_type key_type_rxrpc = {\n \t.free_preparse\t= rxrpc_free_preparse,\n \t.instantiate\t= generic_key_instantiate,\n \t.destroy\t= rxrpc_destroy,\n+\t.wipe\t\t= rxrpc_wipe,\n \t.describe\t= rxrpc_describe,\n \t.read\t\t= rxrpc_read,\n };\n@@ -570,6 +573,31 @@ static void rxrpc_free_token_list(struct rxrpc_key_token *token)\n \t}\n }\n \n+static void rxrpc_wipe_token_list(struct rxrpc_key_token *token)\n+{\n+\tstruct rxrpc_key_token *next;\n+\n+\tfor (; token; token = next) {\n+\t\tnext = token-\u003enext;\n+\t\tswitch (token-\u003esecurity_index) {\n+\t\tcase RXRPC_SECURITY_RXKAD:\n+\t\t\tcrash_wipe_memzero(token-\u003ekad-\u003esession_key,\n+\t\t\t\t\t   sizeof(token-\u003ekad-\u003esession_key));\n+\t\t\tcrash_wipe_memzero(token-\u003ekad-\u003eticket,\n+\t\t\t\t\t   token-\u003ekad-\u003eticket_len);\n+\t\t\tbreak;\n+\t\tcase RXRPC_SECURITY_YFS_RXGK:\n+\t\t\tcrash_wipe_memzero(token-\u003erxgk-\u003ekey.data,\n+\t\t\t\t\t   token-\u003erxgk-\u003ekey.len);\n+\t\t\tcrash_wipe_memzero(token-\u003erxgk-\u003eticket.data,\n+\t\t\t\t\t   token-\u003erxgk-\u003eticket.len);\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+}\n+\n /*\n  * Clean up preparse data.\n  */\n@@ -586,6 +614,12 @@ static void rxrpc_destroy(struct key *key)\n \trxrpc_free_token_list(key-\u003epayload.data[0]);\n }\n \n+/* wipe the key without freeing. used by CONFIG_CRASH_WIPE_SECRETS. */\n+static void rxrpc_wipe(struct key *key)\n+{\n+\trxrpc_wipe_token_list(key-\u003epayload.data[0]);\n+}\n+\n /*\n  * describe the rxrpc key\n  */\ndiff --git a/net/rxrpc/rxgk.c b/net/rxrpc/rxgk.c\nindex 77a67ace1d248..51c428fb3a60a 100644\n--- a/net/rxrpc/rxgk.c\n+++ b/net/rxrpc/rxgk.c\n@@ -8,6 +8,7 @@\n #define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n \n #include \u003clinux/net.h\u003e\n+#include \u003clinux/crash_core.h\u003e\n #include \u003clinux/skbuff.h\u003e\n #include \u003clinux/slab.h\u003e\n #include \u003clinux/key-type.h\u003e\n@@ -68,6 +69,15 @@ static void rxgk_destroy_server_key(struct key *key)\n \trxgk_free_server_key(\u0026key-\u003epayload);\n }\n \n+/* wipe the key without freeing. used by CONFIG_CRASH_WIPE_SECRETS. */\n+static void rxgk_wipe_server_key(struct key *key)\n+{\n+\tstruct krb5_buffer *server_key = (void *)\u0026key-\u003epayload.data[2];\n+\n+\tif (server_key-\u003edata)\n+\t\tcrash_wipe_memzero(server_key-\u003edata, server_key-\u003elen);\n+}\n+\n static void rxgk_describe_server_key(const struct key *key, struct seq_file *m)\n {\n \tconst struct krb5_enctype *krb5 = key-\u003epayload.data[0];\n@@ -1338,6 +1348,7 @@ const struct rxrpc_security rxgk_yfs = {\n \t.preparse_server_key\t\t= rxgk_preparse_server_key,\n \t.free_preparse_server_key\t= rxgk_free_preparse_server_key,\n \t.destroy_server_key\t\t= rxgk_destroy_server_key,\n+\t.wipe_server_key\t\t= rxgk_wipe_server_key,\n \t.describe_server_key\t\t= rxgk_describe_server_key,\n \t.init_connection_security\t= rxgk_init_connection_security,\n \t.alloc_txbuf\t\t\t= rxgk_alloc_txbuf,\ndiff --git a/net/rxrpc/rxkad.c b/net/rxrpc/rxkad.c\nindex ca9f0e82cb9ac..054ab67aca324 100644\n--- a/net/rxrpc/rxkad.c\n+++ b/net/rxrpc/rxkad.c\n@@ -10,6 +10,7 @@\n #include \u003ccrypto/des.h\u003e\n #include \u003ckunit/visibility.h\u003e\n #include \u003clinux/export.h\u003e\n+#include \u003clinux/crash_core.h\u003e\n #include \u003clinux/fips.h\u003e\n #include \u003clinux/module.h\u003e\n #include \u003clinux/net.h\u003e\n@@ -91,6 +92,18 @@ static void rxkad_destroy_server_key(struct key *key)\n \tkey-\u003epayload.data[0] = NULL;\n }\n \n+/* wipe the key without freeing. used by CONFIG_CRASH_WIPE_SECRETS. */\n+static void rxkad_wipe_server_key(struct key *key)\n+{\n+\tstruct des_ctx *des_key = key-\u003epayload.data[0];\n+\n+\tif (des_key)\n+\t\tcrash_wipe_memzero(des_key, sizeof(*des_key));\n+\n+\t/* the raw 8-byte key is kept inline in the payload union */\n+\tcrash_wipe_memzero(\u0026key-\u003epayload.data[2], 8);\n+}\n+\n /*\n  * initialise connection security\n  */\n@@ -1127,6 +1140,7 @@ const struct rxrpc_security rxkad = {\n \t.preparse_server_key\t\t= rxkad_preparse_server_key,\n \t.free_preparse_server_key\t= rxkad_free_preparse_server_key,\n \t.destroy_server_key\t\t= rxkad_destroy_server_key,\n+\t.wipe_server_key\t\t= rxkad_wipe_server_key,\n \t.init_connection_security\t= rxkad_init_connection_security,\n \t.alloc_txbuf\t\t\t= rxkad_alloc_txbuf,\n \t.secure_packet\t\t\t= rxkad_secure_packet,\ndiff --git a/net/rxrpc/server_key.c b/net/rxrpc/server_key.c\nindex 3efe104b1930c..a8e736d587935 100644\n--- a/net/rxrpc/server_key.c\n+++ b/net/rxrpc/server_key.c\n@@ -26,6 +26,7 @@ static int rxrpc_vet_description_s(const char *);\n static int rxrpc_preparse_s(struct key_preparsed_payload *);\n static void rxrpc_free_preparse_s(struct key_preparsed_payload *);\n static void rxrpc_destroy_s(struct key *);\n+static void rxrpc_wipe_s(struct key *);\n static void rxrpc_describe_s(const struct key *, struct seq_file *);\n \n /*\n@@ -40,6 +41,7 @@ struct key_type key_type_rxrpc_s = {\n \t.free_preparse\t= rxrpc_free_preparse_s,\n \t.instantiate\t= generic_key_instantiate,\n \t.destroy\t= rxrpc_destroy_s,\n+\t.wipe\t\t= rxrpc_wipe_s,\n \t.describe\t= rxrpc_describe_s,\n };\n \n@@ -105,6 +107,15 @@ static void rxrpc_destroy_s(struct key *key)\n \t\tsec-\u003edestroy_server_key(key);\n }\n \n+/* wipe the key without freeing. used by CONFIG_CRASH_WIPE_SECRETS. */\n+static void rxrpc_wipe_s(struct key *key)\n+{\n+\tconst struct rxrpc_security *sec = key-\u003epayload.data[1];\n+\n+\tif (sec \u0026\u0026 sec-\u003ewipe_server_key)\n+\t\tsec-\u003ewipe_server_key(key);\n+}\n+\n static void rxrpc_describe_s(const struct key *key, struct seq_file *m)\n {\n \tconst struct rxrpc_security *sec = key-\u003epayload.data[1];\ndiff --git a/security/keys/big_key.c b/security/keys/big_key.c\nindex 268f702df380e..c01a212bec903 100644\n--- a/security/keys/big_key.c\n+++ b/security/keys/big_key.c\n@@ -8,6 +8,7 @@\n \n #define pr_fmt(fmt) \"big_key: \"fmt\n #include \u003clinux/init.h\u003e\n+#include \u003clinux/crash_core.h\u003e\n #include \u003clinux/seq_file.h\u003e\n #include \u003clinux/file.h\u003e\n #include \u003clinux/shmem_fs.h\u003e\n@@ -35,6 +36,8 @@ struct big_key_payload {\n  */\n #define BIG_KEY_FILE_THRESHOLD (sizeof(struct inode) + sizeof(struct dentry))\n \n+static void big_key_wipe(struct key *key);\n+\n /*\n  * big_key defined keys take an arbitrary string as the description and an\n  * arbitrary blob of data as the payload\n@@ -46,6 +49,7 @@ struct key_type key_type_big_key = {\n \t.instantiate\t\t= generic_key_instantiate,\n \t.revoke\t\t\t= big_key_revoke,\n \t.destroy\t\t= big_key_destroy,\n+\t.wipe\t\t\t= big_key_wipe,\n \t.describe\t\t= big_key_describe,\n \t.read\t\t\t= big_key_read,\n \t.update\t\t\t= big_key_update,\n@@ -279,6 +283,19 @@ long big_key_read(const struct key *key, char *buffer, size_t buflen)\n \treturn ret;\n }\n \n+static void big_key_wipe(struct key *key)\n+{\n+\tstruct big_key_payload *payload = to_big_key_payload(key-\u003epayload);\n+\n+\tif (payload-\u003edata) {\n+\t\tif (payload-\u003elength \u003e BIG_KEY_FILE_THRESHOLD)\n+\t\t\tcrash_wipe_memzero(payload-\u003edata,\n+\t\t\t\t\t   CHACHA20POLY1305_KEY_SIZE);\n+\t\telse\n+\t\t\tcrash_wipe_memzero(payload-\u003edata, payload-\u003elength);\n+\t}\n+}\n+\n /*\n  * Register key type\n  */\ndiff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c\nindex 59cb77b237b36..9d6fe08ecb7a6 100644\n--- a/security/keys/encrypted-keys/encrypted.c\n+++ b/security/keys/encrypted-keys/encrypted.c\n@@ -12,6 +12,7 @@\n  */\n \n #include \u003clinux/uaccess.h\u003e\n+#include \u003clinux/crash_core.h\u003e\n #include \u003clinux/module.h\u003e\n #include \u003clinux/hex.h\u003e\n #include \u003clinux/init.h\u003e\n@@ -970,11 +971,23 @@ static void encrypted_destroy(struct key *key)\n \tkfree_sensitive(key-\u003epayload.data[0]);\n }\n \n+static void encrypted_wipe(struct key *key)\n+{\n+\tstruct encrypted_key_payload *epayload = key-\u003epayload.data[0];\n+\n+\tif (!epayload)\n+\t\treturn;\n+\n+\tcrash_wipe_memzero(epayload-\u003epayload_data,\n+\t\t\t   epayload-\u003epayload_datalen + epayload-\u003edatablob_len);\n+}\n+\n struct key_type key_type_encrypted = {\n \t.name = \"encrypted\",\n \t.instantiate = encrypted_instantiate,\n \t.update = encrypted_update,\n \t.destroy = encrypted_destroy,\n+\t.wipe = encrypted_wipe,\n \t.describe = user_describe,\n \t.read = encrypted_read,\n };\ndiff --git a/security/keys/key.c b/security/keys/key.c\nindex b34a64d81d47a..213e6f1d5d838 100644\n--- a/security/keys/key.c\n+++ b/security/keys/key.c\n@@ -12,6 +12,7 @@\n #include \u003clinux/slab.h\u003e\n #include \u003clinux/security.h\u003e\n #include \u003clinux/workqueue.h\u003e\n+#include \u003clinux/crash_core.h\u003e\n #include \u003clinux/random.h\u003e\n #include \u003clinux/err.h\u003e\n #include \"internal.h\"\n@@ -1268,6 +1269,47 @@ void unregister_key_type(struct key_type *ktype)\n }\n EXPORT_SYMBOL(unregister_key_type);\n \n+/* Called far into vpanic from crash_core.c with other CPUs stopped and\n+ * preemption disabled\n+ */\n+static int key_crash_wipe(struct notifier_block *nb, unsigned long action,\n+\t\tvoid *data)\n+{\n+\tstruct rb_node *node;\n+\n+\t/* If we can't acquire the lock, the rbtree might be in an inconsistent\n+\t * state. That's all we can do then, as there's no point to waiting\n+\t * at this stage.\n+\t */\n+\tif (!spin_trylock(\u0026key_serial_lock)) {\n+\t\tpr_crit(\"crash_wipe_secrets: can't acquire key_serial_lock. skipping keyrings.\\n\");\n+\t\treturn NOTIFY_DONE;\n+\t}\n+\n+\tfor (node = rb_first(\u0026key_serial_tree); node; node = rb_next(node)) {\n+\t\tstruct key *key = rb_entry(node, struct key, serial_node);\n+\n+\t\t/* Negatively instantiated keys have key-\u003estate \u003c 0 and never\n+\t\t * had a payload attached, so only wipe positive ones.\n+\t\t */\n+\t\tif (key-\u003etype == \u0026key_type_keyring || !key_is_positive(key))\n+\t\t\tcontinue;\n+\n+\t\t/* We have a dedicated wipe callback for this since free'ing\n+\t\t * isn't safe at this point\n+\t\t */\n+\t\tif (key-\u003etype-\u003ewipe)\n+\t\t\tkey-\u003etype-\u003ewipe(key);\n+\t}\n+\tspin_unlock(\u0026key_serial_lock);\n+\t/* off to kexec()! */\n+\treturn NOTIFY_DONE;\n+}\n+\n+static struct notifier_block key_crash_wipe_nb = {\n+\t.notifier_call = key_crash_wipe\n+};\n+\n /*\n  * Initialise the key management state.\n  */\n@@ -1290,4 +1332,5 @@ void __init key_init(void)\n \n \trb_insert_color(\u0026root_key_user.node,\n \t\t\t\u0026key_user_tree);\n+\tcrash_wipe_secrets_register(\u0026key_crash_wipe_nb);\n }\ndiff --git a/security/keys/trusted-keys/trusted_core.c b/security/keys/trusted-keys/trusted_core.c\nindex 0509d9955f2a6..7b220c6381b5f 100644\n--- a/security/keys/trusted-keys/trusted_core.c\n+++ b/security/keys/trusted-keys/trusted_core.c\n@@ -14,6 +14,7 @@\n #include \u003ckeys/trusted_tpm.h\u003e\n #include \u003ckeys/trusted_pkwm.h\u003e\n #include \u003clinux/capability.h\u003e\n+#include \u003clinux/crash_core.h\u003e\n #include \u003clinux/err.h\u003e\n #include \u003clinux/hex.h\u003e\n #include \u003clinux/init.h\u003e\n@@ -325,11 +326,25 @@ static void trusted_destroy(struct key *key)\n \tkfree_sensitive(key-\u003epayload.data[0]);\n }\n \n+static void trusted_wipe(struct key *key)\n+{\n+\tstruct trusted_key_payload *p = key-\u003epayload.data[0];\n+\n+\tif (!p)\n+\t\treturn;\n+\n+\tcrash_wipe_memzero(p-\u003ekey, sizeof(p-\u003ekey));\n+\tcrash_wipe_memzero(p-\u003eblob, sizeof(p-\u003eblob));\n+\tp-\u003ekey_len = 0;\n+\tp-\u003eblob_len = 0;\n+}\n+\n struct key_type key_type_trusted = {\n \t.name = \"trusted\",\n \t.instantiate = trusted_instantiate,\n \t.update = trusted_update,\n \t.destroy = trusted_destroy,\n+\t.wipe = trusted_wipe,\n \t.describe = user_describe,\n \t.read = trusted_read,\n };\ndiff --git a/security/keys/user_defined.c b/security/keys/user_defined.c\nindex 6f88b507f9279..f4897c19c2ddf 100644\n--- a/security/keys/user_defined.c\n+++ b/security/keys/user_defined.c\n@@ -6,6 +6,7 @@\n  */\n \n #include \u003clinux/export.h\u003e\n+#include \u003clinux/crash_core.h\u003e\n #include \u003clinux/init.h\u003e\n #include \u003clinux/slab.h\u003e\n #include \u003clinux/seq_file.h\u003e\n@@ -28,6 +29,7 @@ struct key_type key_type_user = {\n \t.update\t\t\t= user_update,\n \t.revoke\t\t\t= user_revoke,\n \t.destroy\t\t= user_destroy,\n+\t.wipe\t\t\t= user_wipe,\n \t.describe\t\t= user_describe,\n \t.read\t\t\t= user_read,\n };\n@@ -48,6 +50,7 @@ struct key_type key_type_logon = {\n \t.update\t\t\t= user_update,\n \t.revoke\t\t\t= user_revoke,\n \t.destroy\t\t= user_destroy,\n+\t.wipe\t\t\t= user_wipe,\n \t.describe\t\t= user_describe,\n \t.vet_description\t= logon_vet_description,\n };\n@@ -152,6 +155,18 @@ void user_destroy(struct key *key)\n \n EXPORT_SYMBOL_GPL(user_destroy);\n \n+void user_wipe(struct key *key)\n+{\n+\tstruct user_key_payload *upayload = key-\u003epayload.data[0];\n+\n+\tif (upayload)\n+\t\tcrash_wipe_memzero(upayload-\u003edata, upayload-\u003edatalen);\n+\tkey-\u003epayload.data[1] = NULL;\n+\tkey-\u003epayload.data[2] = NULL;\n+\tkey-\u003epayload.data[3] = NULL;\n+}\n+EXPORT_SYMBOL_GPL(user_wipe);\n+\n /*\n  * describe the user key\n  */\n"
}

2/1 2026/08/11 18:21 agent patch-evaluator
2m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": [
    "CRASH_DUMP",
    "CRASH_WIPE_SECRETS",
    "DM_CRYPT",
    "FS_ENCRYPTION",
    "SECRETMEM"
  ],
  "FocusSymbols": [
    "__crypto_alloc_tfm",
    "crypto_create_tfm_node",
    "crypto_destroy_tfm",
    "crypt_ctr",
    "crypt_dtr",
    "fscrypt_destroy_keyring"
  ],
  "Reasoning": "The patch introduces CONFIG_CRASH_WIPE_SECRETS to wipe sensitive secrets before kdump. While the actual wiping occurs in the panic path, the patch also introduces tracking mechanisms that are executed during normal operation. This includes adding global locks and list operations for tracking crypto tfms (crypto_tfm_list_lock), dm-crypt configs (dm_crypt_wipe_list_lock), and adding secretmem inodes to the superblock list. These changes can potentially introduce race conditions, deadlocks, or list corruptions during normal allocation and destruction of these objects. Therefore, the changes have a functional impact worth fuzzing.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
  even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).
- Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed,
  ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU.
- Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove)
  that are executed only during PCI hot-unplug or sysfs driver unbind operations.

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
   Prefer non-static, non-inlined API entrypoint functions over internal static helper functions
   (which are inlined by the compiler and do not have distinct symbol addresses).
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs
   needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs
   output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit 3f0999c4f5e6c648c580d868cc5f6dc90351b881
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Aug 11 18:21:02 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst
index 326b8a9738289..35cb00a87c461 100644
--- a/Documentation/security/keys/core.rst
+++ b/Documentation/security/keys/core.rst
@@ -1596,6 +1596,19 @@ The structure has a number of fields, some of which are mandatory:
      It is not safe to sleep in this method; the caller may hold spinlocks.
 
 
+  *  ``void (*wipe)(struct key *key);``
+
+     This method is optional. It is called from the panic path when
+     CONFIG_CRASH_WIPE_SECRETS is enabled, to erase the key material from
+     memory before the kdump kernel is started, so that it does not end up in
+     the crash dump. Unlike destroy(), it must only clear the payload, not
+     free it.
+
+     This method is called with all other CPUs stopped and preemption
+     disabled, and only for positively instantiated keys. It must not sleep,
+     allocate, free or take locks, as they will never be released.
+
+
   *  ``void (*describe)(const struct key *key, struct seq_file *p);``
 
      This method is optional. It is called during /proc/keys reading to
diff --git a/arch/arm64/include/asm/set_memory.h b/arch/arm64/include/asm/set_memory.h
index 90f61b17275e1..cb5f44bb5a949 100644
--- a/arch/arm64/include/asm/set_memory.h
+++ b/arch/arm64/include/asm/set_memory.h
@@ -13,6 +13,8 @@ int set_memory_valid(unsigned long addr, int numpages, int enable);
 
 int set_direct_map_invalid_noflush(struct page *page);
 int set_direct_map_default_noflush(struct page *page);
+int set_direct_map_default_nosplit(struct page *page);
+#define set_direct_map_default_nosplit set_direct_map_default_nosplit
 int set_direct_map_valid_noflush(struct page *page, unsigned nr, bool valid);
 bool kernel_page_present(struct page *page);
 
diff --git a/arch/arm64/kernel/machine_kexec.c b/arch/arm64/kernel/machine_kexec.c
index c5693a32e49b0..12bf3c90f5193 100644
--- a/arch/arm64/kernel/machine_kexec.c
+++ b/arch/arm64/kernel/machine_kexec.c
@@ -6,6 +6,7 @@
  * Copyright (C) Huawei Futurewei Technologies.
  */
 
+#include <linux/crash_core.h>
 #include <linux/interrupt.h>
 #include <linux/irq.h>
 #include <linux/kernel.h>
@@ -15,6 +16,7 @@
 #include <linux/set_memory.h>
 #include <linux/smp.h>
 
+#include <asm/barrier.h>
 #include <asm/cacheflush.h>
 #include <asm/cpu_ops.h>
 #include <asm/daifflags.h>
@@ -221,6 +223,24 @@ void machine_crash_shutdown(struct pt_regs *regs)
 	pr_info("Starting crashdump kernel...\n");
 }
 
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+/*
+ * Queue each wiped range for cleaning to the point of coherency.  Deferring
+ * the barrier until the notifier chain is complete avoids one DSB per range.
+ */
+void arch_crash_wipe_range(void *addr, size_t size)
+{
+	unsigned long start = (unsigned long)addr;
+
+	dcache_clean_poc_nosync(start, start + size);
+}
+
+void arch_crash_wipe_flush(void)
+{
+	dsb(sy);
+}
+#endif
+
 #if defined(CONFIG_CRASH_DUMP) && defined(CONFIG_HIBERNATION)
 /*
  * To preserve the crash dump kernel image, the relevant memory segments
diff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c
index bbe98ac9ad8c6..a7bb6bfa12ed3 100644
--- a/arch/arm64/mm/pageattr.c
+++ b/arch/arm64/mm/pageattr.c
@@ -275,6 +275,27 @@ int set_direct_map_default_noflush(struct page *page)
 				 PAGE_SIZE, set_mask, clear_mask);
 }
 
+/* Caller must guarantee @page is already mapped at page granularity. */
+int set_direct_map_default_nosplit(struct page *page)
+{
+	struct page_change_data data = {
+		.set_mask = __pgprot(PTE_PRESENT_VALID_KERNEL | PTE_WRITE),
+		.clear_mask = __pgprot(PTE_PRESENT_INVALID | PTE_RDONLY),
+	};
+	unsigned long addr = (unsigned long)page_address(page);
+	int ret;
+
+	if (!can_set_direct_map())
+		return 0;
+
+	lazy_mmu_mode_enable();
+	ret = walk_kernel_page_table_range_lockless(addr, addr + PAGE_SIZE,
+						    &pageattr_ops, NULL, &data);
+	lazy_mmu_mode_disable();
+
+	return ret;
+}
+
 static int __set_memory_enc_dec(unsigned long addr,
 				int numpages,
 				bool encrypt)
diff --git a/crypto/api.c b/crypto/api.c
index 24227582cfcf3..f919f52e8b5fc 100644
--- a/crypto/api.c
+++ b/crypto/api.c
@@ -10,13 +10,16 @@
  * and Nettle, by Niels Möller.
  */
 
+#include <linux/crash_core.h>
 #include <linux/err.h>
 #include <linux/errno.h>
 #include <linux/jump_label.h>
 #include <linux/kernel.h>
 #include <linux/kmod.h>
 #include <linux/module.h>
+#include <linux/notifier.h>
 #include <linux/param.h>
+#include <linux/rculist.h>
 #include <linux/sched/signal.h>
 #include <linux/slab.h>
 #include <linux/string.h>
@@ -397,6 +400,60 @@ static unsigned int crypto_ctxsize(struct crypto_alg *alg, u32 type, u32 mask)
 	return len;
 }
 
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+/*
+ * tfm allocations are tracked on crypto_tfm_list so that they can be wiped
+ * before kdump.
+ */
+static HLIST_HEAD(crypto_tfm_list);
+static DEFINE_SPINLOCK(crypto_tfm_list_lock);
+
+static int crypto_crash_wipe(struct notifier_block *nb, unsigned long action,
+		void *data)
+{
+	struct crypto_tfm *tfm;
+
+	rcu_read_lock();
+	hlist_for_each_entry_rcu(tfm, &crypto_tfm_list, wipe_list)
+		crash_wipe_memzero(tfm->__crt_ctx, tfm->wipe_size);
+	rcu_read_unlock();
+
+	/* off to kexec()! */
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block crypto_wipe_nb = {
+	.notifier_call = crypto_crash_wipe
+};
+
+static int __init crypto_tfm_track_init(void)
+{
+	crash_wipe_secrets_register(&crypto_wipe_nb);
+	return 0;
+}
+core_initcall(crypto_tfm_track_init);
+
+/* @size is the size of __crt_ctx */
+static void crypto_track_tfm(struct crypto_tfm *tfm, size_t size)
+{
+	tfm->wipe_size = size;
+
+	spin_lock(&crypto_tfm_list_lock);
+	hlist_add_head_rcu(&tfm->wipe_list, &crypto_tfm_list);
+	spin_unlock(&crypto_tfm_list_lock);
+}
+
+static void crypto_untrack_tfm(struct crypto_tfm *tfm)
+{
+	spin_lock(&crypto_tfm_list_lock);
+	hlist_del_rcu(&tfm->wipe_list);
+	spin_unlock(&crypto_tfm_list_lock);
+}
+#else
+static void crypto_track_tfm(struct crypto_tfm *tfm, size_t size) { }
+static void crypto_untrack_tfm(struct crypto_tfm *tfm) { }
+#endif /* CONFIG_CRASH_WIPE_SECRETS */
+
 void crypto_shoot_alg(struct crypto_alg *alg)
 {
 	down_write(&crypto_alg_sem);
@@ -409,15 +466,16 @@ struct crypto_tfm *__crypto_alloc_tfm(struct crypto_alg *alg, u32 type,
 				      u32 mask)
 {
 	struct crypto_tfm *tfm;
-	unsigned int tfm_size;
+	unsigned int ctx_size;
 	int err = -ENOMEM;
 
-	tfm_size = sizeof(*tfm) + crypto_ctxsize(alg, type, mask);
-	tfm = kzalloc(tfm_size, GFP_KERNEL);
+	ctx_size = crypto_ctxsize(alg, type, mask);
+	tfm = kzalloc(sizeof(*tfm) + ctx_size, GFP_KERNEL);
 	if (tfm == NULL)
 		goto out_err;
 
 	tfm->__crt_alg = alg;
+	crypto_track_tfm(tfm, ctx_size);
 
 	if (!tfm->exit && alg->cra_init && (err = alg->cra_init(tfm)))
 		goto cra_init_failed;
@@ -428,7 +486,8 @@ struct crypto_tfm *__crypto_alloc_tfm(struct crypto_alg *alg, u32 type,
 	crypto_exit_ops(tfm);
 	if (err == -EAGAIN)
 		crypto_shoot_alg(alg);
-	kfree(tfm);
+	crypto_untrack_tfm(tfm);
+	kfree_sensitive(tfm);
 out_err:
 	tfm = ERR_PTR(err);
 out:
@@ -497,12 +556,12 @@ void *crypto_create_tfm_node(struct crypto_alg *alg,
 			     int node)
 {
 	struct crypto_tfm *tfm;
-	size_t size;
 	char *mem;
 	int err;
+	size_t ctx_size = frontend->extsize(alg);
 
-	size = frontend->tfmsize + sizeof(*tfm) + frontend->extsize(alg);
-	mem = kzalloc_node(size, GFP_KERNEL, node);
+	mem = kzalloc_node(frontend->tfmsize + sizeof(*tfm) + ctx_size,
+			GFP_KERNEL, node);
 	if (!mem)
 		return ERR_PTR(-ENOMEM);
 
@@ -510,6 +569,7 @@ void *crypto_create_tfm_node(struct crypto_alg *alg,
 	tfm->__crt_alg = alg;
 	tfm->node = node;
 	tfm->fb = tfm;
+	crypto_track_tfm(tfm, ctx_size);
 
 	err = frontend->init_tfm(tfm);
 	if (err)
@@ -525,7 +585,8 @@ void *crypto_create_tfm_node(struct crypto_alg *alg,
 out_free_tfm:
 	if (err == -EAGAIN)
 		crypto_shoot_alg(alg);
-	kfree(mem);
+	crypto_untrack_tfm(tfm);
+	kfree_sensitive(mem);
 	mem = ERR_PTR(err);
 out:
 	return mem;
@@ -627,6 +688,7 @@ void crypto_destroy_tfm(void *mem, struct crypto_tfm *tfm)
 		alg->cra_exit(tfm);
 	crypto_exit_ops(tfm);
 	crypto_mod_put(alg);
+	crypto_untrack_tfm(tfm);
 	kfree_sensitive(mem);
 }
 EXPORT_SYMBOL_GPL(crypto_destroy_tfm);
diff --git a/crypto/asymmetric_keys/asymmetric_type.c b/crypto/asymmetric_keys/asymmetric_type.c
index 16a7ae16593c9..a0a68ebd07463 100644
--- a/crypto/asymmetric_keys/asymmetric_type.c
+++ b/crypto/asymmetric_keys/asymmetric_type.c
@@ -481,6 +481,16 @@ static void asymmetric_key_destroy(struct key *key)
 	asymmetric_key_free_kids(kids);
 }
 
+/* wipe the key without freeing. used by CONFIG_CRASH_WIPE_SECRETS. */
+static void asymmetric_key_wipe(struct key *key)
+{
+	struct asymmetric_key_subtype *subtype = asymmetric_key_subtype(key);
+
+	if (subtype && subtype->wipe)
+		subtype->wipe(key->payload.data[asym_crypto],
+				key->payload.data[asym_auth]);
+}
+
 static struct key_restriction *asymmetric_restriction_alloc(
 	key_restrict_link_func_t check,
 	struct key *key)
@@ -612,6 +622,7 @@ struct key_type key_type_asymmetric = {
 	.match_preparse		= asymmetric_key_match_preparse,
 	.match_free		= asymmetric_key_match_free,
 	.destroy		= asymmetric_key_destroy,
+	.wipe			= asymmetric_key_wipe,
 	.describe		= asymmetric_key_describe,
 	.lookup_restriction	= asymmetric_lookup_restriction,
 	.asym_query		= query_asymmetric_key,
diff --git a/crypto/asymmetric_keys/pkcs7_key_type.c b/crypto/asymmetric_keys/pkcs7_key_type.c
index b930d3bbf1af5..663a246742f71 100644
--- a/crypto/asymmetric_keys/pkcs7_key_type.c
+++ b/crypto/asymmetric_keys/pkcs7_key_type.c
@@ -74,6 +74,7 @@ static struct key_type key_type_pkcs7 = {
 	.instantiate		= generic_key_instantiate,
 	.revoke			= user_revoke,
 	.destroy		= user_destroy,
+	.wipe			= user_wipe,
 	.describe		= user_describe,
 	.read			= user_read,
 };
diff --git a/crypto/asymmetric_keys/public_key.c b/crypto/asymmetric_keys/public_key.c
index 09a0b83d5d77f..9931d56337e55 100644
--- a/crypto/asymmetric_keys/public_key.c
+++ b/crypto/asymmetric_keys/public_key.c
@@ -13,6 +13,7 @@
 #include <crypto/sig.h>
 #include <keys/asymmetric-subtype.h>
 #include <linux/asn1.h>
+#include <linux/crash_core.h>
 #include <linux/err.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
@@ -58,6 +59,19 @@ static void public_key_destroy(void *payload0, void *payload3)
 	public_key_signature_free(payload3);
 }
 
+/* wipe the key without freeing. used by CONFIG_CRASH_WIPE_SECRETS. */
+static void public_key_wipe(void *payload0, void *payload3)
+{
+	struct public_key *key = payload0;
+
+	if (key) {
+		if (key->key)
+			crash_wipe_memzero(key->key, key->keylen);
+		if (key->params)
+			crash_wipe_memzero(key->params, key->paramlen);
+	}
+}
+
 /*
  * Given a public_key, and an encoding and hash_algo to be used for signing
  * and/or verification with that key, determine the name of the corresponding
@@ -464,6 +478,7 @@ struct asymmetric_key_subtype public_key_subtype = {
 	.name_len		= sizeof("public_key") - 1,
 	.describe		= public_key_describe,
 	.destroy		= public_key_destroy,
+	.wipe			= public_key_wipe,
 	.query			= software_key_query,
 	.eds_op			= software_key_eds_op,
 	.verify_signature	= public_key_verify_signature_2,
diff --git a/drivers/md/dm-crypt.c b/drivers/md/dm-crypt.c
index 608b617fb817f..86adb2f9b94c0 100644
--- a/drivers/md/dm-crypt.c
+++ b/drivers/md/dm-crypt.c
@@ -9,12 +9,14 @@
  */
 
 #include <linux/completion.h>
+#include <linux/crash_core.h>
 #include <linux/err.h>
 #include <linux/module.h>
 #include <linux/hex.h>
 #include <linux/init.h>
 #include <linux/kernel.h>
 #include <linux/key.h>
+#include <linux/list.h>
 #include <linux/bio.h>
 #include <linux/blkdev.h>
 #include <linux/blk-integrity.h>
@@ -234,6 +236,11 @@ struct crypt_config {
 	struct mutex bio_alloc_lock;
 
 	u8 *authenc_key; /* space for keys in authenc() format (if used) */
+
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+	struct list_head wipe_list;
+#endif
+
 	u8 key[] __counted_by(key_size);
 };
 
@@ -243,6 +250,12 @@ struct crypt_config {
 
 static DEFINE_SPINLOCK(dm_crypt_clients_lock);
 static unsigned int dm_crypt_clients_n;
+
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+static LIST_HEAD(dm_crypt_wipe_list);
+static DEFINE_SPINLOCK(dm_crypt_wipe_list_lock);
+#endif
+
 static volatile unsigned long dm_crypt_pages_per_client;
 #define DM_CRYPT_MEMORY_PERCENT			2
 #define DM_CRYPT_MIN_PAGES_PER_CLIENT		(BIO_MAX_VECS * 16)
@@ -460,8 +473,7 @@ static void crypt_iv_lmk_dtr(struct crypt_config *cc)
 {
 	struct iv_lmk_private *lmk = &cc->iv_gen_private.lmk;
 
-	kfree_sensitive(lmk->seed);
-	lmk->seed = NULL;
+	kfree_sensitive(xchg(&lmk->seed, NULL));
 }
 
 static int crypt_iv_lmk_ctr(struct crypt_config *cc, struct dm_target *ti,
@@ -582,10 +594,8 @@ static void crypt_iv_tcw_dtr(struct crypt_config *cc)
 {
 	struct iv_tcw_private *tcw = &cc->iv_gen_private.tcw;
 
-	kfree_sensitive(tcw->iv_seed);
-	tcw->iv_seed = NULL;
-	kfree_sensitive(tcw->whitening);
-	tcw->whitening = NULL;
+	kfree_sensitive(xchg(&tcw->iv_seed, NULL));
+	kfree_sensitive(xchg(&tcw->whitening, NULL));
 }
 
 static int crypt_iv_tcw_ctr(struct crypt_config *cc, struct dm_target *ti,
@@ -761,8 +771,7 @@ static void crypt_iv_elephant_dtr(struct crypt_config *cc)
 {
 	struct iv_elephant_private *elephant = &cc->iv_gen_private.elephant;
 
-	kfree_sensitive(elephant->key);
-	elephant->key = NULL;
+	kfree_sensitive(xchg(&elephant->key, NULL));
 }
 
 static int crypt_iv_elephant_ctr(struct crypt_config *cc, struct dm_target *ti,
@@ -2547,8 +2556,7 @@ static int crypt_set_keyring_key(struct crypt_config *cc, const char *key_string
 		goto free_new_key_string;
 
 	set_bit(DM_CRYPT_KEY_VALID, &cc->flags);
-	kfree_sensitive(cc->key_string);
-	cc->key_string = new_key_string;
+	kfree_sensitive(xchg(&cc->key_string, new_key_string));
 	return 0;
 
 free_new_key_string:
@@ -2612,8 +2620,7 @@ static int crypt_set_key(struct crypt_config *cc, char *key)
 	clear_bit(DM_CRYPT_KEY_VALID, &cc->flags);
 
 	/* wipe references to any kernel keyring key */
-	kfree_sensitive(cc->key_string);
-	cc->key_string = NULL;
+	kfree_sensitive(xchg(&cc->key_string, NULL));
 
 	/* Decode key from its hex representation. */
 	if (cc->key_size && hex2bin(cc->key, key, cc->key_size) < 0)
@@ -2641,14 +2648,97 @@ static int crypt_wipe_key(struct crypt_config *cc)
 	if (cc->iv_gen_ops && cc->iv_gen_ops->wipe)
 		cc->iv_gen_ops->wipe(cc);
 
-	kfree_sensitive(cc->key_string);
-	cc->key_string = NULL;
+	kfree_sensitive(xchg(&cc->key_string, NULL));
 	r = crypt_setkey(cc);
 	memset(&cc->key, 0, cc->key_size * sizeof(u8));
 
 	return r;
 }
 
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+static void crypt_crash_wipe_iv(struct crypt_config *cc)
+{
+	if (cc->iv_gen_ops == &crypt_iv_lmk_ops) {
+		struct iv_lmk_private *lmk = &cc->iv_gen_private.lmk;
+
+		if (lmk->seed)
+			crash_wipe_memzero(lmk->seed, LMK_SEED_SIZE);
+	} else if (cc->iv_gen_ops == &crypt_iv_tcw_ops) {
+		struct iv_tcw_private *tcw = &cc->iv_gen_private.tcw;
+
+		if (tcw->iv_seed)
+			crash_wipe_memzero(tcw->iv_seed, cc->iv_size);
+		if (tcw->whitening)
+			crash_wipe_memzero(tcw->whitening, TCW_WHITENING_SIZE);
+	} else if (cc->iv_gen_ops == &crypt_iv_elephant_ops) {
+		struct iv_elephant_private *elephant =
+			&cc->iv_gen_private.elephant;
+
+		if (elephant->key)
+			crash_wipe_memzero(elephant->key,
+					   sizeof(*elephant->key));
+	}
+}
+
+static int crypt_crash_wipe(struct notifier_block *nb, unsigned long action,
+			    void *data)
+{
+	struct crypt_config *cc;
+
+	/* No locking: all other CPUs are stopped, and a cc is unlinked before
+	 * it is freed, so the forward walk can't reach freed memory.
+	 */
+	list_for_each_entry(cc, &dm_crypt_wipe_list, wipe_list) {
+		crash_wipe_memzero(cc->key, cc->key_size);
+
+		if (cc->authenc_key)
+			crash_wipe_memzero(cc->authenc_key,
+					   crypt_authenckey_size(cc));
+
+		if (cc->key_string)
+			crash_wipe_memzero(cc->key_string,
+					   strlen(cc->key_string));
+
+		crypt_crash_wipe_iv(cc);
+	}
+
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block crypt_crash_wipe_nb = {
+	.notifier_call = crypt_crash_wipe
+};
+
+static void crypt_track_cc(struct crypt_config *cc)
+{
+	spin_lock(&dm_crypt_wipe_list_lock);
+	list_add(&cc->wipe_list, &dm_crypt_wipe_list);
+	spin_unlock(&dm_crypt_wipe_list_lock);
+}
+
+static void crypt_untrack_cc(struct crypt_config *cc)
+{
+	spin_lock(&dm_crypt_wipe_list_lock);
+	list_del(&cc->wipe_list);
+	spin_unlock(&dm_crypt_wipe_list_lock);
+}
+
+static void crypt_crash_wipe_init(void)
+{
+	crash_wipe_secrets_register(&crypt_crash_wipe_nb);
+}
+
+static void crypt_crash_wipe_exit(void)
+{
+	crash_wipe_secrets_unregister(&crypt_crash_wipe_nb);
+}
+#else
+static void crypt_track_cc(struct crypt_config *cc) { }
+static void crypt_untrack_cc(struct crypt_config *cc) { }
+static void crypt_crash_wipe_init(void) { }
+static void crypt_crash_wipe_exit(void) { }
+#endif /* CONFIG_CRASH_WIPE_SECRETS */
+
 static void crypt_calculate_pages_per_client(void)
 {
 	unsigned long pages = (totalram_pages() - totalhigh_pages()) * DM_CRYPT_MEMORY_PERCENT / 100;
@@ -2729,12 +2819,15 @@ static void crypt_dtr(struct dm_target *ti)
 		dm_put_device(ti, cc->dev);
 
 	kfree_sensitive(cc->cipher_string);
-	kfree_sensitive(cc->key_string);
+	kfree_sensitive(xchg(&cc->key_string, NULL));
 	kfree_sensitive(cc->cipher_auth);
-	kfree_sensitive(cc->authenc_key);
+	kfree_sensitive(xchg(&cc->authenc_key, NULL));
 
 	mutex_destroy(&cc->bio_alloc_lock);
 
+	memzero_explicit(cc->key, cc->key_size);
+	crypt_untrack_cc(cc);
+
 	/* Must zero key material before freeing */
 	kfree_sensitive(cc);
 
@@ -3210,6 +3303,8 @@ static int crypt_ctr(struct dm_target *ti, unsigned int argc, char **argv)
 
 	ti->private = cc;
 
+	crypt_track_cc(cc);
+
 	spin_lock(&dm_crypt_clients_lock);
 	dm_crypt_clients_n++;
 	crypt_calculate_pages_per_client();
@@ -3716,7 +3811,29 @@ static struct target_type crypt_target = {
 	.iterate_devices = crypt_iterate_devices,
 	.io_hints = crypt_io_hints,
 };
-module_dm(crypt);
+
+static int __init dm_crypt_init(void)
+{
+	int r;
+
+	crypt_crash_wipe_init();
+
+	r = dm_register_target(&crypt_target);
+	if (r) {
+		crypt_crash_wipe_exit();
+		return r;
+	}
+
+	return 0;
+}
+module_init(dm_crypt_init);
+
+static void __exit dm_crypt_exit(void)
+{
+	crypt_crash_wipe_exit();
+	dm_unregister_target(&crypt_target);
+}
+module_exit(dm_crypt_exit);
 
 MODULE_AUTHOR("Jana Saout <jana@saout.de>");
 MODULE_DESCRIPTION(DM_NAME " target for transparent encryption / decryption");
diff --git a/drivers/nvme/common/keyring.c b/drivers/nvme/common/keyring.c
index 32d16c53133be..33303f035fb1b 100644
--- a/drivers/nvme/common/keyring.c
+++ b/drivers/nvme/common/keyring.c
@@ -84,6 +84,7 @@ static struct key_type nvme_tls_psk_key_type = {
 	.instantiate    = generic_key_instantiate,
 	.revoke         = user_revoke,
 	.destroy        = user_destroy,
+	.wipe		= user_wipe,
 	.describe       = nvme_tls_psk_describe,
 	.read           = user_read,
 };
diff --git a/fs/crypto/block.c b/fs/crypto/block.c
index 5193f8ba3ee09..a2fa1469b3c40 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 74329e0953d14..95cb50e5cbcd4 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 76e28d1e00640..5ac302146eb2c 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 87fe13ccb2535..08dd0a682a8f8 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/nfs/nfs4idmap.c b/fs/nfs/nfs4idmap.c
index bc397110d977f..26f579ae2c732 100644
--- a/fs/nfs/nfs4idmap.c
+++ b/fs/nfs/nfs4idmap.c
@@ -190,6 +190,7 @@ static struct key_type key_type_id_resolver = {
 	.instantiate	= generic_key_instantiate,
 	.revoke		= user_revoke,
 	.destroy	= user_destroy,
+	.wipe		= user_wipe,
 	.describe	= user_describe,
 	.read		= user_read,
 };
@@ -412,6 +413,7 @@ static struct key_type key_type_id_resolver_legacy = {
 	.instantiate	= generic_key_instantiate,
 	.revoke		= user_revoke,
 	.destroy	= user_destroy,
+	.wipe		= user_wipe,
 	.describe	= user_describe,
 	.read		= user_read,
 	.request_key	= nfs_idmap_legacy_upcall,
diff --git a/fs/super.c b/fs/super.c
index 5feecf5d9038d..cd51e9cfe93ba 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/keys/asymmetric-subtype.h b/include/keys/asymmetric-subtype.h
index d55171f640a04..17c74070c1014 100644
--- a/include/keys/asymmetric-subtype.h
+++ b/include/keys/asymmetric-subtype.h
@@ -32,6 +32,11 @@ struct asymmetric_key_subtype {
 	/* Destroy a key of this subtype */
 	void (*destroy)(void *payload_crypto, void *payload_auth);
 
+	/* Wipe a key of this subtype without freeing it (optional).  Used by
+	 * CONFIG_CRASH_WIPE_SECRETS from the panic path.
+	 */
+	void (*wipe)(void *payload_crypto, void *payload_auth);
+
 	int (*query)(const struct kernel_pkey_params *params,
 		     struct kernel_pkey_query *info);
 
diff --git a/include/keys/user-type.h b/include/keys/user-type.h
index 386c314327899..685fa054b0ac5 100644
--- a/include/keys/user-type.h
+++ b/include/keys/user-type.h
@@ -40,6 +40,7 @@ extern void user_free_preparse(struct key_preparsed_payload *prep);
 extern int user_update(struct key *key, struct key_preparsed_payload *prep);
 extern void user_revoke(struct key *key);
 extern void user_destroy(struct key *key);
+extern void user_wipe(struct key *key);
 extern void user_describe(const struct key *user, struct seq_file *m);
 extern long user_read(const struct key *key, char *buffer, size_t buflen);
 
diff --git a/include/linux/crash_core.h b/include/linux/crash_core.h
index bc087124cd78e..d3e4192b7e6d6 100644
--- a/include/linux/crash_core.h
+++ b/include/linux/crash_core.h
@@ -5,6 +5,8 @@
 #include <linux/linkage.h>
 #include <linux/elfcore.h>
 #include <linux/elf.h>
+#include <linux/notifier.h>
+#include <linux/string.h>
 
 struct kimage;
 
@@ -14,6 +16,26 @@ struct crash_mem {
 	struct range ranges[] __counted_by(max_nr_ranges);
 };
 
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+/*
+ * Record a range that has already been wiped, or wipe and record it in one
+ * operation.  Architectures may use the ranges to push the wipes out to
+ * memory before kexec disables the caches.
+ */
+void crash_wipe_cache_range(void *addr, size_t size);
+void crash_wipe_memzero(void *addr, size_t size);
+
+void arch_crash_wipe_range(void *addr, size_t size);
+void arch_crash_wipe_flush(void);
+#else
+static inline void crash_wipe_cache_range(void *addr, size_t size) { }
+
+static inline void crash_wipe_memzero(void *addr, size_t size)
+{
+	memzero_explicit(addr, size);
+}
+#endif
+
 #ifdef CONFIG_CRASH_DUMP
 
 int crash_shrink_memory(unsigned long new_size);
@@ -34,6 +56,21 @@ static inline void arch_kexec_protect_crashkres(void) { }
 static inline void arch_kexec_unprotect_crashkres(void) { }
 #endif
 
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+int crash_wipe_secrets_register(struct notifier_block *nb);
+int crash_wipe_secrets_unregister(struct notifier_block *nb);
+#else
+static inline int crash_wipe_secrets_register(struct notifier_block *nb)
+{
+	return 0;
+}
+
+static inline int crash_wipe_secrets_unregister(struct notifier_block *nb)
+{
+	return 0;
+}
+#endif
+
 #ifndef arch_crash_handle_hotplug_event
 static inline void arch_crash_handle_hotplug_event(struct kimage *image, void *arg) { }
 #endif
diff --git a/include/linux/crypto.h b/include/linux/crypto.h
index b7c97f1c47c92..59d6d62180fdd 100644
--- a/include/linux/crypto.h
+++ b/include/linux/crypto.h
@@ -14,6 +14,7 @@
 
 #include <linux/completion.h>
 #include <linux/errno.h>
+#include <linux/list.h>
 #include <linux/refcount_types.h>
 #include <linux/slab.h>
 #include <linux/types.h>
@@ -420,6 +421,16 @@ struct crypto_tfm {
 
 	struct crypto_alg *__crt_alg;
 
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+	/*
+	 * Link on crypto_tfm_list, used to wipe the expanded key schedule in
+	 * __crt_ctx before kdump. wipe_size covers __crt_ctx only, excluding
+	 * this struct and any frontend preceding it.
+	 */
+	struct hlist_node wipe_list;
+	size_t wipe_size;
+#endif
+
 	void *__crt_ctx[] CRYPTO_MINALIGN_ATTR;
 };
 
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 072d8cd09a0b2..7832a77908d2b 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)))
diff --git a/include/linux/key-type.h b/include/linux/key-type.h
index bb97bd3e5af4f..21e07db0c5f2d 100644
--- a/include/linux/key-type.h
+++ b/include/linux/key-type.h
@@ -122,6 +122,15 @@ struct key_type {
 	/* clear the data from a key (optional) */
 	void (*destroy)(struct key *key);
 
+	/* wipe the key material without free'ing (optional)
+	 * - used from CONFIG_CRASH_WIPE_SECRETS during panic to keep keys out
+	 *   of crash dumps
+	 * - called from the panic path with other CPUs stopped and preemption
+	 *   disabled
+	 * - must not sleep, allocate, free or take locks
+	 */
+	void (*wipe)(struct key *key);
+
 	/* describe a key */
 	void (*describe)(const struct key *key, struct seq_file *p);
 
diff --git a/include/linux/set_memory.h b/include/linux/set_memory.h
index 3030d9245f5ac..65d0fa010368d 100644
--- a/include/linux/set_memory.h
+++ b/include/linux/set_memory.h
@@ -44,7 +44,22 @@ static inline bool kernel_page_present(struct page *page)
 {
 	return true;
 }
-#else /* CONFIG_ARCH_HAS_SET_DIRECT_MAP */
+#endif /* !CONFIG_ARCH_HAS_SET_DIRECT_MAP */
+
+/*
+ * Variant of set_direct_map_default_noflush() that is safe to call from atomic
+ * context, for pages the caller knows are already mapped at page granularity.
+ * Architectures that need to split the linear map, and can therefore sleep,
+ * override this.
+ */
+#ifndef set_direct_map_default_nosplit
+static inline int set_direct_map_default_nosplit(struct page *page)
+{
+	return set_direct_map_default_noflush(page);
+}
+#endif
+
+#ifdef CONFIG_ARCH_HAS_SET_DIRECT_MAP
 /*
  * Some architectures, e.g. ARM64 can disable direct map modifications at
  * boot time. Let them overrive this query.
diff --git a/kernel/Kconfig.kexec b/kernel/Kconfig.kexec
index 15632358bcf71..1d2d273145df0 100644
--- a/kernel/Kconfig.kexec
+++ b/kernel/Kconfig.kexec
@@ -179,4 +179,20 @@ config CRASH_MAX_MEMORY_RANGES
 	  the computation behind the value provided through the
 	  /sys/kernel/crash_elfcorehdr_size attribute.
 
+config CRASH_WIPE_SECRETS
+	bool "Wipe secrets before kdump"
+	depends on CRASH_DUMP
+	help
+	  Wipe secrets (e.g. kernel keyring and memfd_secret pages) on crash or
+	  panic. This is a best effort, defense-in-depth feature: If the panic
+	  happens at a really bad time, or if copies of the secrets are present
+	  in places like on the stack, in I/O buffers, or in userspace memory
+	  not allocated through memfd_secret, they may still be leaked.
+
+	  Note that enabling this feature carries some risk of crashing the
+	  system during the wipe process if the kernel was already unstable
+	  when the panic happened.
+
+	  If unsure, say N.
+
 endmenu
diff --git a/kernel/crash_core.c b/kernel/crash_core.c
index 2b36aa9fade06..42faf8d0a4c48 100644
--- a/kernel/crash_core.c
+++ b/kernel/crash_core.c
@@ -23,6 +23,7 @@
 #include <linux/objtool.h>
 #include <linux/delay.h>
 #include <linux/panic.h>
+#include <linux/timekeeping.h>
 
 #include <asm/page.h>
 #include <asm/sections.h>
@@ -33,6 +34,56 @@
 /* Per cpu memory for storing cpu states in case of system crash. */
 note_buf_t __percpu *crash_notes;
 
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+ATOMIC_NOTIFIER_HEAD(crash_wipe_secrets_notifier_list);
+
+int crash_wipe_secrets_register(struct notifier_block *nb)
+{
+	return atomic_notifier_chain_register(
+			&crash_wipe_secrets_notifier_list, nb);
+}
+EXPORT_SYMBOL_GPL(crash_wipe_secrets_register);
+
+int crash_wipe_secrets_unregister(struct notifier_block *nb)
+{
+	return atomic_notifier_chain_unregister(
+			&crash_wipe_secrets_notifier_list, nb);
+}
+EXPORT_SYMBOL_GPL(crash_wipe_secrets_unregister);
+
+/*
+ * Some kexec paths disable the data cache without first cleaning it.  Give
+ * architectures valid virtual ranges for the wiped data, then let them defer
+ * any completion barrier until all crash-wipe callbacks have run.
+ */
+void __weak arch_crash_wipe_range(void *addr, size_t size) { }
+void __weak arch_crash_wipe_flush(void) { }
+
+void crash_wipe_cache_range(void *addr, size_t size)
+{
+	if (size)
+		arch_crash_wipe_range(addr, size);
+}
+EXPORT_SYMBOL_GPL(crash_wipe_cache_range);
+
+void crash_wipe_memzero(void *addr, size_t size)
+{
+	memzero_explicit(addr, size);
+	crash_wipe_cache_range(addr, size);
+}
+EXPORT_SYMBOL_GPL(crash_wipe_memzero);
+
+static void crash_wipe_secrets(void)
+{
+	pr_info("Wiping sensitive secrets...\n");
+	atomic_notifier_call_chain(&crash_wipe_secrets_notifier_list, 0, NULL);
+	arch_crash_wipe_flush();
+	pr_info("Done wiping secrets.\n");
+}
+#else
+static inline void crash_wipe_secrets(void) { }
+#endif /* CONFIG_CRASH_WIPE_SECRETS */
+
 /* time to wait for possible DMA to finish before starting the kdump kernel
  * when a CMA reservation is used
  */
@@ -142,6 +193,7 @@ void __noclone __crash_kexec(struct pt_regs *regs)
 			crash_save_vmcoreinfo();
 			machine_crash_shutdown(&fixed_regs);
 			crash_cma_clear_pending_dma();
+			crash_wipe_secrets();
 			machine_kexec(kexec_crash_image);
 		}
 		kexec_unlock();
diff --git a/mm/secretmem.c b/mm/secretmem.c
index d29865075b6ea..db00b7d060807 100644
--- a/mm/secretmem.c
+++ b/mm/secretmem.c
@@ -13,9 +13,11 @@
 #include <linux/bitops.h>
 #include <linux/printk.h>
 #include <linux/pagemap.h>
+#include <linux/notifier.h>
 #include <linux/syscalls.h>
 #include <linux/pseudo_fs.h>
 #include <linux/secretmem.h>
+#include <linux/crash_core.h>
 #include <linux/set_memory.h>
 #include <linux/sched/signal.h>
 
@@ -187,6 +189,54 @@ static const struct inode_operations secretmem_iops = {
 
 static struct vfsmount *secretmem_mnt;
 
+/* Called far into vpanic from crash_core.c with other CPUs stopped and
+ * preemption disabled
+ */
+static int secretmem_crash_wipe(struct notifier_block *nb, unsigned long action,
+		void *data)
+{
+	struct super_block *sb;
+	struct inode *inode;
+
+	if (!secretmem_mnt)
+		return NOTIFY_DONE;
+	sb = secretmem_mnt->mnt_sb;
+
+	/* If the list was modified in the exact moment we panic'ed, it might be
+	 * in an inconsistent state that would be unsafe to iterate. If we can't
+	 * get the lock, too bad, that's all we can do here.
+	 */
+	if (!spin_trylock(&sb->s_inode_list_lock)) {
+		pr_crit("crash_wipe_secrets: can't acquire secretmem superblock lock.\n"
+			 "crash_wipe_secrets: skipping zeroizing secretmem.\n");
+		return NOTIFY_DONE;
+	}
+
+	list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
+		XA_STATE(xas, &inode->i_mapping->i_pages, 0);
+		struct folio *folio;
+
+		/* no need for locks if we're burning down the house :) */
+		xas_for_each(&xas, folio, ULONG_MAX) {
+			if (xas_retry(&xas, folio) || xa_is_value(folio))
+				continue;
+
+			/* secretmem_fault() already split the linear map */
+			set_direct_map_default_nosplit(folio_page(folio, 0));
+			folio_zero_segment(folio, 0, folio_size(folio));
+			crash_wipe_cache_range(folio_address(folio),
+					       folio_size(folio));
+		}
+	}
+	spin_unlock(&sb->s_inode_list_lock);
+	/* off to kexec()! */
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block secretmem_wipe_nb = {
+	.notifier_call = secretmem_crash_wipe
+};
+
 static struct file *secretmem_file_create(unsigned long flags)
 {
 	struct file *file;
@@ -212,6 +262,8 @@ static struct file *secretmem_file_create(unsigned long flags)
 	inode->i_mode |= S_IFREG;
 	inode->i_size = 0;
 
+	inode_sb_list_add(inode);
+
 	atomic_inc(&secretmem_users);
 
 	return file;
@@ -263,6 +315,7 @@ static int __init secretmem_init(void)
 	if (IS_ERR(secretmem_mnt))
 		return PTR_ERR(secretmem_mnt);
 
+	crash_wipe_secrets_register(&secretmem_wipe_nb);
 	return 0;
 }
 fs_initcall(secretmem_init);
diff --git a/net/rxrpc/ar-internal.h b/net/rxrpc/ar-internal.h
index 865f05fe37ab9..6466eb5a9929e 100644
--- a/net/rxrpc/ar-internal.h
+++ b/net/rxrpc/ar-internal.h
@@ -282,6 +282,11 @@ struct rxrpc_security {
 	/* Destroy the payload of a server key */
 	void (*destroy_server_key)(struct key *);
 
+	/* Wipe the payload of a server key without freeing it.  Used by
+	 * CONFIG_CRASH_WIPE_SECRETS from the panic path.
+	 */
+	void (*wipe_server_key)(struct key *);
+
 	/* Describe a server key */
 	void (*describe_server_key)(const struct key *, struct seq_file *);
 
diff --git a/net/rxrpc/key.c b/net/rxrpc/key.c
index a0aa78d892897..e612b7a8df13f 100644
--- a/net/rxrpc/key.c
+++ b/net/rxrpc/key.c
@@ -11,6 +11,7 @@
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
 #include <linux/module.h>
+#include <linux/crash_core.h>
 #include <linux/net.h>
 #include <linux/overflow.h>
 #include <linux/skbuff.h>
@@ -26,6 +27,7 @@
 static int rxrpc_preparse(struct key_preparsed_payload *);
 static void rxrpc_free_preparse(struct key_preparsed_payload *);
 static void rxrpc_destroy(struct key *);
+static void rxrpc_wipe(struct key *);
 static void rxrpc_describe(const struct key *, struct seq_file *);
 static long rxrpc_read(const struct key *, char *, size_t);
 
@@ -40,6 +42,7 @@ struct key_type key_type_rxrpc = {
 	.free_preparse	= rxrpc_free_preparse,
 	.instantiate	= generic_key_instantiate,
 	.destroy	= rxrpc_destroy,
+	.wipe		= rxrpc_wipe,
 	.describe	= rxrpc_describe,
 	.read		= rxrpc_read,
 };
@@ -570,6 +573,31 @@ static void rxrpc_free_token_list(struct rxrpc_key_token *token)
 	}
 }
 
+static void rxrpc_wipe_token_list(struct rxrpc_key_token *token)
+{
+	struct rxrpc_key_token *next;
+
+	for (; token; token = next) {
+		next = token->next;
+		switch (token->security_index) {
+		case RXRPC_SECURITY_RXKAD:
+			crash_wipe_memzero(token->kad->session_key,
+					   sizeof(token->kad->session_key));
+			crash_wipe_memzero(token->kad->ticket,
+					   token->kad->ticket_len);
+			break;
+		case RXRPC_SECURITY_YFS_RXGK:
+			crash_wipe_memzero(token->rxgk->key.data,
+					   token->rxgk->key.len);
+			crash_wipe_memzero(token->rxgk->ticket.data,
+					   token->rxgk->ticket.len);
+			break;
+		default:
+			break;
+		}
+	}
+}
+
 /*
  * Clean up preparse data.
  */
@@ -586,6 +614,12 @@ static void rxrpc_destroy(struct key *key)
 	rxrpc_free_token_list(key->payload.data[0]);
 }
 
+/* wipe the key without freeing. used by CONFIG_CRASH_WIPE_SECRETS. */
+static void rxrpc_wipe(struct key *key)
+{
+	rxrpc_wipe_token_list(key->payload.data[0]);
+}
+
 /*
  * describe the rxrpc key
  */
diff --git a/net/rxrpc/rxgk.c b/net/rxrpc/rxgk.c
index 77a67ace1d248..51c428fb3a60a 100644
--- a/net/rxrpc/rxgk.c
+++ b/net/rxrpc/rxgk.c
@@ -8,6 +8,7 @@
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
 #include <linux/net.h>
+#include <linux/crash_core.h>
 #include <linux/skbuff.h>
 #include <linux/slab.h>
 #include <linux/key-type.h>
@@ -68,6 +69,15 @@ static void rxgk_destroy_server_key(struct key *key)
 	rxgk_free_server_key(&key->payload);
 }
 
+/* wipe the key without freeing. used by CONFIG_CRASH_WIPE_SECRETS. */
+static void rxgk_wipe_server_key(struct key *key)
+{
+	struct krb5_buffer *server_key = (void *)&key->payload.data[2];
+
+	if (server_key->data)
+		crash_wipe_memzero(server_key->data, server_key->len);
+}
+
 static void rxgk_describe_server_key(const struct key *key, struct seq_file *m)
 {
 	const struct krb5_enctype *krb5 = key->payload.data[0];
@@ -1338,6 +1348,7 @@ const struct rxrpc_security rxgk_yfs = {
 	.preparse_server_key		= rxgk_preparse_server_key,
 	.free_preparse_server_key	= rxgk_free_preparse_server_key,
 	.destroy_server_key		= rxgk_destroy_server_key,
+	.wipe_server_key		= rxgk_wipe_server_key,
 	.describe_server_key		= rxgk_describe_server_key,
 	.init_connection_security	= rxgk_init_connection_security,
 	.alloc_txbuf			= rxgk_alloc_txbuf,
diff --git a/net/rxrpc/rxkad.c b/net/rxrpc/rxkad.c
index ca9f0e82cb9ac..054ab67aca324 100644
--- a/net/rxrpc/rxkad.c
+++ b/net/rxrpc/rxkad.c
@@ -10,6 +10,7 @@
 #include <crypto/des.h>
 #include <kunit/visibility.h>
 #include <linux/export.h>
+#include <linux/crash_core.h>
 #include <linux/fips.h>
 #include <linux/module.h>
 #include <linux/net.h>
@@ -91,6 +92,18 @@ static void rxkad_destroy_server_key(struct key *key)
 	key->payload.data[0] = NULL;
 }
 
+/* wipe the key without freeing. used by CONFIG_CRASH_WIPE_SECRETS. */
+static void rxkad_wipe_server_key(struct key *key)
+{
+	struct des_ctx *des_key = key->payload.data[0];
+
+	if (des_key)
+		crash_wipe_memzero(des_key, sizeof(*des_key));
+
+	/* the raw 8-byte key is kept inline in the payload union */
+	crash_wipe_memzero(&key->payload.data[2], 8);
+}
+
 /*
  * initialise connection security
  */
@@ -1127,6 +1140,7 @@ const struct rxrpc_security rxkad = {
 	.preparse_server_key		= rxkad_preparse_server_key,
 	.free_preparse_server_key	= rxkad_free_preparse_server_key,
 	.destroy_server_key		= rxkad_destroy_server_key,
+	.wipe_server_key		= rxkad_wipe_server_key,
 	.init_connection_security	= rxkad_init_connection_security,
 	.alloc_txbuf			= rxkad_alloc_txbuf,
 	.secure_packet			= rxkad_secure_packet,
diff --git a/net/rxrpc/server_key.c b/net/rxrpc/server_key.c
index 3efe104b1930c..a8e736d587935 100644
--- a/net/rxrpc/server_key.c
+++ b/net/rxrpc/server_key.c
@@ -26,6 +26,7 @@ static int rxrpc_vet_description_s(const char *);
 static int rxrpc_preparse_s(struct key_preparsed_payload *);
 static void rxrpc_free_preparse_s(struct key_preparsed_payload *);
 static void rxrpc_destroy_s(struct key *);
+static void rxrpc_wipe_s(struct key *);
 static void rxrpc_describe_s(const struct key *, struct seq_file *);
 
 /*
@@ -40,6 +41,7 @@ struct key_type key_type_rxrpc_s = {
 	.free_preparse	= rxrpc_free_preparse_s,
 	.instantiate	= generic_key_instantiate,
 	.destroy	= rxrpc_destroy_s,
+	.wipe		= rxrpc_wipe_s,
 	.describe	= rxrpc_describe_s,
 };
 
@@ -105,6 +107,15 @@ static void rxrpc_destroy_s(struct key *key)
 		sec->destroy_server_key(key);
 }
 
+/* wipe the key without freeing. used by CONFIG_CRASH_WIPE_SECRETS. */
+static void rxrpc_wipe_s(struct key *key)
+{
+	const struct rxrpc_security *sec = key->payload.data[1];
+
+	if (sec && sec->wipe_server_key)
+		sec->wipe_server_key(key);
+}
+
 static void rxrpc_describe_s(const struct key *key, struct seq_file *m)
 {
 	const struct rxrpc_security *sec = key->payload.data[1];
diff --git a/security/keys/big_key.c b/security/keys/big_key.c
index 268f702df380e..c01a212bec903 100644
--- a/security/keys/big_key.c
+++ b/security/keys/big_key.c
@@ -8,6 +8,7 @@
 
 #define pr_fmt(fmt) "big_key: "fmt
 #include <linux/init.h>
+#include <linux/crash_core.h>
 #include <linux/seq_file.h>
 #include <linux/file.h>
 #include <linux/shmem_fs.h>
@@ -35,6 +36,8 @@ struct big_key_payload {
  */
 #define BIG_KEY_FILE_THRESHOLD (sizeof(struct inode) + sizeof(struct dentry))
 
+static void big_key_wipe(struct key *key);
+
 /*
  * big_key defined keys take an arbitrary string as the description and an
  * arbitrary blob of data as the payload
@@ -46,6 +49,7 @@ struct key_type key_type_big_key = {
 	.instantiate		= generic_key_instantiate,
 	.revoke			= big_key_revoke,
 	.destroy		= big_key_destroy,
+	.wipe			= big_key_wipe,
 	.describe		= big_key_describe,
 	.read			= big_key_read,
 	.update			= big_key_update,
@@ -279,6 +283,19 @@ long big_key_read(const struct key *key, char *buffer, size_t buflen)
 	return ret;
 }
 
+static void big_key_wipe(struct key *key)
+{
+	struct big_key_payload *payload = to_big_key_payload(key->payload);
+
+	if (payload->data) {
+		if (payload->length > BIG_KEY_FILE_THRESHOLD)
+			crash_wipe_memzero(payload->data,
+					   CHACHA20POLY1305_KEY_SIZE);
+		else
+			crash_wipe_memzero(payload->data, payload->length);
+	}
+}
+
 /*
  * Register key type
  */
diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c
index 59cb77b237b36..9d6fe08ecb7a6 100644
--- a/security/keys/encrypted-keys/encrypted.c
+++ b/security/keys/encrypted-keys/encrypted.c
@@ -12,6 +12,7 @@
  */
 
 #include <linux/uaccess.h>
+#include <linux/crash_core.h>
 #include <linux/module.h>
 #include <linux/hex.h>
 #include <linux/init.h>
@@ -970,11 +971,23 @@ static void encrypted_destroy(struct key *key)
 	kfree_sensitive(key->payload.data[0]);
 }
 
+static void encrypted_wipe(struct key *key)
+{
+	struct encrypted_key_payload *epayload = key->payload.data[0];
+
+	if (!epayload)
+		return;
+
+	crash_wipe_memzero(epayload->payload_data,
+			   epayload->payload_datalen + epayload->datablob_len);
+}
+
 struct key_type key_type_encrypted = {
 	.name = "encrypted",
 	.instantiate = encrypted_instantiate,
 	.update = encrypted_update,
 	.destroy = encrypted_destroy,
+	.wipe = encrypted_wipe,
 	.describe = user_describe,
 	.read = encrypted_read,
 };
diff --git a/security/keys/key.c b/security/keys/key.c
index b34a64d81d47a..213e6f1d5d838 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -12,6 +12,7 @@
 #include <linux/slab.h>
 #include <linux/security.h>
 #include <linux/workqueue.h>
+#include <linux/crash_core.h>
 #include <linux/random.h>
 #include <linux/err.h>
 #include "internal.h"
@@ -1268,6 +1269,47 @@ void unregister_key_type(struct key_type *ktype)
 }
 EXPORT_SYMBOL(unregister_key_type);
 
+/* Called far into vpanic from crash_core.c with other CPUs stopped and
+ * preemption disabled
+ */
+static int key_crash_wipe(struct notifier_block *nb, unsigned long action,
+		void *data)
+{
+	struct rb_node *node;
+
+	/* If we can't acquire the lock, the rbtree might be in an inconsistent
+	 * state. That's all we can do then, as there's no point to waiting
+	 * at this stage.
+	 */
+	if (!spin_trylock(&key_serial_lock)) {
+		pr_crit("crash_wipe_secrets: can't acquire key_serial_lock. skipping keyrings.\n");
+		return NOTIFY_DONE;
+	}
+
+	for (node = rb_first(&key_serial_tree); node; node = rb_next(node)) {
+		struct key *key = rb_entry(node, struct key, serial_node);
+
+		/* Negatively instantiated keys have key->state < 0 and never
+		 * had a payload attached, so only wipe positive ones.
+		 */
+		if (key->type == &key_type_keyring || !key_is_positive(key))
+			continue;
+
+		/* We have a dedicated wipe callback for this since free'ing
+		 * isn't safe at this point
+		 */
+		if (key->type->wipe)
+			key->type->wipe(key);
+	}
+	spin_unlock(&key_serial_lock);
+	/* off to kexec()! */
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block key_crash_wipe_nb = {
+	.notifier_call = key_crash_wipe
+};
+
 /*
  * Initialise the key management state.
  */
@@ -1290,4 +1332,5 @@ void __init key_init(void)
 
 	rb_insert_color(&root_key_user.node,
 			&key_user_tree);
+	crash_wipe_secrets_register(&key_crash_wipe_nb);
 }
diff --git a/security/keys/trusted-keys/trusted_core.c b/security/keys/trusted-keys/trusted_core.c
index 0509d9955f2a6..7b220c6381b5f 100644
--- a/security/keys/trusted-keys/trusted_core.c
+++ b/security/keys/trusted-keys/trusted_core.c
@@ -14,6 +14,7 @@
 #include <keys/trusted_tpm.h>
 #include <keys/trusted_pkwm.h>
 #include <linux/capability.h>
+#include <linux/crash_core.h>
 #include <linux/err.h>
 #include <linux/hex.h>
 #include <linux/init.h>
@@ -325,11 +326,25 @@ static void trusted_destroy(struct key *key)
 	kfree_sensitive(key->payload.data[0]);
 }
 
+static void trusted_wipe(struct key *key)
+{
+	struct trusted_key_payload *p = key->payload.data[0];
+
+	if (!p)
+		return;
+
+	crash_wipe_memzero(p->key, sizeof(p->key));
+	crash_wipe_memzero(p->blob, sizeof(p->blob));
+	p->key_len = 0;
+	p->blob_len = 0;
+}
+
 struct key_type key_type_trusted = {
 	.name = "trusted",
 	.instantiate = trusted_instantiate,
 	.update = trusted_update,
 	.destroy = trusted_destroy,
+	.wipe = trusted_wipe,
 	.describe = user_describe,
 	.read = trusted_read,
 };
diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c
index 6f88b507f9279..f4897c19c2ddf 100644
--- a/security/keys/user_defined.c
+++ b/security/keys/user_defined.c
@@ -6,6 +6,7 @@
  */
 
 #include <linux/export.h>
+#include <linux/crash_core.h>
 #include <linux/init.h>
 #include <linux/slab.h>
 #include <linux/seq_file.h>
@@ -28,6 +29,7 @@ struct key_type key_type_user = {
 	.update			= user_update,
 	.revoke			= user_revoke,
 	.destroy		= user_destroy,
+	.wipe			= user_wipe,
 	.describe		= user_describe,
 	.read			= user_read,
 };
@@ -48,6 +50,7 @@ struct key_type key_type_logon = {
 	.update			= user_update,
 	.revoke			= user_revoke,
 	.destroy		= user_destroy,
+	.wipe			= user_wipe,
 	.describe		= user_describe,
 	.vet_description	= logon_vet_description,
 };
@@ -152,6 +155,18 @@ void user_destroy(struct key *key)
 
 EXPORT_SYMBOL_GPL(user_destroy);
 
+void user_wipe(struct key *key)
+{
+	struct user_key_payload *upayload = key->payload.data[0];
+
+	if (upayload)
+		crash_wipe_memzero(upayload->data, upayload->datalen);
+	key->payload.data[1] = NULL;
+	key->payload.data[2] = NULL;
+	key->payload.data[3] = NULL;
+}
+EXPORT_SYMBOL_GPL(user_wipe);
+
 /*
  * describe the user key
  */

3/2 2026/08/11 18:21 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 22902
							output: 19
							thoughts: 429
						

Thoughts:
**Analyzing the Security Implications of Secret Wiping Before Kdump**

Okay, so I'm looking at this patch series introducing `CONFIG_CRASH_WIPE_SECRETS`, which seems to be a very targeted effort to wipe sensitive data before kdump, which is a good thing. They're aiming to sanitize things like keys, crypto contexts, secretmem, dm-crypt keys, and fscrypt keys – the usual suspects. The wiping happens in the panic path, specifically within `crash_wipe_secrets()` which is called from `__crash_kexec()`. This immediately raises some questions about how reliably we can actually test this.

The patch touches a bunch of subsystems, which makes sense given the scope of what they are trying to protect: `crypto/`, `crypto/asymmetric_keys/`, `drivers/md/dm-crypt.c`, `fs/crypto/`, `fs/super.c`, `kernel/crash_core.c`, `mm/secretmem.c`, `net/rxrpc/`, and `security/keys/`.  This indicates a thorough approach, covering various key management and storage scenarios.

The critical thing is whether this wiping functionality is actually *reachable* in a virtualized environment. The obvious path is a panic, and yes, kexec is reachable. That also means `__crash_kexec()` is called on panic. Can we trigger a panic with syzkaller? Technically yes, but syzkaller typically halts on a panic. Syzkaller is not a very effective fuzzer for testing panic paths directly. The fact that `crash_wipe_secrets()` is *only* called from `__crash_kexec()` is another hurdle to overcome during testing.  So, I need to investigate other potential paths for invoking `crash_wipe_secrets()`. It seems that's a key question at this point. I need to understand if there are any other possible triggers for this, beyond the standard panic scenario. I'll need to double-check the code to be absolutely certain.



4/2 2026/08/11 18:21 tool grepper
0m Args:
{
  "Expression": "crash_wipe_secrets"
}

Results:
{
  "Output": "crypto/api.c=429=static int __init crypto_tfm_track_init(void)\ncrypto/api.c-430-{\ncrypto/api.c:431:\tcrash_wipe_secrets_register(\u0026crypto_wipe_nb);\ncrypto/api.c-432-\treturn 0;\n--\ndrivers/md/dm-crypt.c=2726=static void crypt_crash_wipe_init(void)\ndrivers/md/dm-crypt.c-2727-{\ndrivers/md/dm-crypt.c:2728:\tcrash_wipe_secrets_register(\u0026crypt_crash_wipe_nb);\ndrivers/md/dm-crypt.c-2729-}\n--\ndrivers/md/dm-crypt.c=2731=static void crypt_crash_wipe_exit(void)\ndrivers/md/dm-crypt.c-2732-{\ndrivers/md/dm-crypt.c:2733:\tcrash_wipe_secrets_unregister(\u0026crypt_crash_wipe_nb);\ndrivers/md/dm-crypt.c-2734-}\n--\nfs/crypto/keyring.c=1269=static int fscrypt_crash_wipe(struct notifier_block *nb, unsigned long action,\n--\nfs/crypto/keyring.c-1272-\tif (!super_blocks_crash_wipe(fscrypt_crash_wipe_sb))\nfs/crypto/keyring.c:1273:\t\tpr_crit(\"crash_wipe_secrets: can't acquire sb_lock. skipping fscrypt keys.\\n\");\nfs/crypto/keyring.c-1274-\n--\nfs/crypto/keyring.c=1285=void __init fscrypt_init_keyring(void)\n--\nfs/crypto/keyring.c-1301-#ifdef CONFIG_CRASH_WIPE_SECRETS\nfs/crypto/keyring.c:1302:\tcrash_wipe_secrets_register(\u0026fscrypt_crash_wipe_nb);\nfs/crypto/keyring.c-1303-#endif\n--\ninclude/linux/crash_core.h=56=static inline void arch_kexec_unprotect_crashkres(void) { }\n--\ninclude/linux/crash_core.h-59-#ifdef CONFIG_CRASH_WIPE_SECRETS\ninclude/linux/crash_core.h:60:int crash_wipe_secrets_register(struct notifier_block *nb);\ninclude/linux/crash_core.h:61:int crash_wipe_secrets_unregister(struct notifier_block *nb);\ninclude/linux/crash_core.h-62-#else\ninclude/linux/crash_core.h:63:static inline int crash_wipe_secrets_register(struct notifier_block *nb)\ninclude/linux/crash_core.h-64-{\n--\ninclude/linux/crash_core.h-67-\ninclude/linux/crash_core.h:68:static inline int crash_wipe_secrets_unregister(struct notifier_block *nb)\ninclude/linux/crash_core.h-69-{\n--\nkernel/crash_core.c=35=note_buf_t __percpu *crash_notes;\n--\nkernel/crash_core.c-37-#ifdef CONFIG_CRASH_WIPE_SECRETS\nkernel/crash_core.c:38:ATOMIC_NOTIFIER_HEAD(crash_wipe_secrets_notifier_list);\nkernel/crash_core.c-39-\nkernel/crash_core.c:40:int crash_wipe_secrets_register(struct notifier_block *nb)\nkernel/crash_core.c-41-{\nkernel/crash_core.c-42-\treturn atomic_notifier_chain_register(\nkernel/crash_core.c:43:\t\t\t\u0026crash_wipe_secrets_notifier_list, nb);\nkernel/crash_core.c-44-}\nkernel/crash_core.c:45:EXPORT_SYMBOL_GPL(crash_wipe_secrets_register);\nkernel/crash_core.c-46-\nkernel/crash_core.c:47:int crash_wipe_secrets_unregister(struct notifier_block *nb)\nkernel/crash_core.c-48-{\nkernel/crash_core.c-49-\treturn atomic_notifier_chain_unregister(\nkernel/crash_core.c:50:\t\t\t\u0026crash_wipe_secrets_notifier_list, nb);\nkernel/crash_core.c-51-}\nkernel/crash_core.c:52:EXPORT_SYMBOL_GPL(crash_wipe_secrets_unregister);\nkernel/crash_core.c-53-\n--\nkernel/crash_core.c=74=EXPORT_SYMBOL_GPL(crash_wipe_memzero);\nkernel/crash_core.c-75-\nkernel/crash_core.c:76:static void crash_wipe_secrets(void)\nkernel/crash_core.c-77-{\nkernel/crash_core.c-78-\tpr_info(\"Wiping sensitive secrets...\\n\");\nkernel/crash_core.c:79:\tatomic_notifier_call_chain(\u0026crash_wipe_secrets_notifier_list, 0, NULL);\nkernel/crash_core.c-80-\tarch_crash_wipe_flush();\n--\nkernel/crash_core.c-83-#else\nkernel/crash_core.c:84:static inline void crash_wipe_secrets(void) { }\nkernel/crash_core.c-85-#endif /* CONFIG_CRASH_WIPE_SECRETS */\n--\nkernel/crash_core.c=178=void __noclone __crash_kexec(struct pt_regs *regs)\n--\nkernel/crash_core.c-195-\t\t\tcrash_cma_clear_pending_dma();\nkernel/crash_core.c:196:\t\t\tcrash_wipe_secrets();\nkernel/crash_core.c-197-\t\t\tmachine_kexec(kexec_crash_image);\n--\nmm/secretmem.c=195=static int secretmem_crash_wipe(struct notifier_block *nb, unsigned long action,\n--\nmm/secretmem.c-209-\tif (!spin_trylock(\u0026sb-\u003es_inode_list_lock)) {\nmm/secretmem.c:210:\t\tpr_crit(\"crash_wipe_secrets: can't acquire secretmem superblock lock.\\n\"\nmm/secretmem.c:211:\t\t\t \"crash_wipe_secrets: skipping zeroizing secretmem.\\n\");\nmm/secretmem.c-212-\t\treturn NOTIFY_DONE;\n--\nmm/secretmem.c=309=static int __init secretmem_init(void)\n--\nmm/secretmem.c-317-\nmm/secretmem.c:318:\tcrash_wipe_secrets_register(\u0026secretmem_wipe_nb);\nmm/secretmem.c-319-\treturn 0;\n--\nsecurity/keys/key.c=1275=static int key_crash_wipe(struct notifier_block *nb, unsigned long action,\n--\nsecurity/keys/key.c-1284-\tif (!spin_trylock(\u0026key_serial_lock)) {\nsecurity/keys/key.c:1285:\t\tpr_crit(\"crash_wipe_secrets: can't acquire key_serial_lock. skipping keyrings.\\n\");\nsecurity/keys/key.c-1286-\t\treturn NOTIFY_DONE;\n--\nsecurity/keys/key.c=1316=void __init key_init(void)\n--\nsecurity/keys/key.c-1334-\t\t\t\u0026key_user_tree);\nsecurity/keys/key.c:1335:\tcrash_wipe_secrets_register(\u0026key_crash_wipe_nb);\nsecurity/keys/key.c-1336-}\n"
}

5/2 2026/08/11 18:21 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 25705
							output: 53
							thoughts: 1527
						

Thoughts:
**Analysis of the Patch for Fuzzing Potential**

Okay, so this patch introduces a few interesting things related to secret wiping before a crash dump (`__crash_kexec()`), and also a lot of new tracking for cryptographic and dm-crypt configurations. My initial thought was: is the crash-wipe function the only change? Let's dig deeper. The patch adds a lot more than just the crash function. The primary concern is that a lot of tracking code is added for crypto transforms (`crypto_tfm`) and dm-crypt configs.

Specifically, I see a global lock, `crypto_tfm_list_lock`, being introduced in `crypto/api.c` within `crypto_track_tfm` and `crypto_untrack_tfm`, which are called during every crypto tfm allocation and destruction. This is immediately concerning because global locks can introduce contention, deadlocks, and race conditions, which are ideal targets for fuzzing. While `crypto_alloc_tfm` might not be the hottest path, it's called frequently enough that this lock is a potential issue. The same is true for `dm_crypt_wipe_list_lock`, which tracks crypt configs.

I need to quickly assess the fuzzing-worthiness of this change given the constraints in the prompt: it's important to note the prompt's exclusion rules. Is the `__crash_kexec` path reachable? Yes, it's reachable via `echo c > /proc/sysrq-trigger` or a panic, but syzkaller generally stops on a panic. Therefore, any bugs after the panic will not be found easily by syzkaller. However, the tracking code, `crypto_track_tfm` and similar functions, are executed during normal operation and are thus fair game. There are a few functional changes in the code: `xchg` in `dm-crypt.c` in `crypt_iv_lmk_dtr` and the change in `fscrypt_destroy_keyring` in `fs/crypto/keyring.c` are good targets. Also, `secretmem_file_create` now adds the inode to the superblock list.

Therefore, the functional changes during normal operation warrant fuzzing, particularly the tracking code. I'll need to enable `CONFIG_CRASH_WIPE_SECRETS` as it enables the new tracking functionality. While I can't directly fuzz the crash path effectively (syzkaller stopping on panic), bugs in the *tracking* code can be discovered during normal operation by fuzzing those routines. I'll need to focus on functions like `crypto_track_tfm`, `crypto_untrack_tfm`, `__crypto_alloc_tfm`, `crypto_create_tfm_node`, `crypto_destroy_tfm`, `crypt_ctr`, `crypt_dtr` and `secretmem_file_create`. I should enable `CRASH_WIPE_SECRETS` and also `CRASH_DUMP` in the kernel configuration.



6/2 2026/08/11 18:21 tool grepper
0m Args:
{
  "Expression": "^(void|int|struct crypto_tfm \\*|struct file \\*) [a-zA-Z0-9_]+\\(",
  "PathPrefix": "crypto/api.c"
}

Results:
{
  "Output": "crypto/api.c=50=EXPORT_SYMBOL_GPL(crypto_mod_get);\ncrypto/api.c-51-\ncrypto/api.c:52:void crypto_mod_put(struct crypto_alg *alg)\ncrypto/api.c-53-{\n--\ncrypto/api.c=158=static void crypto_larval_kill(struct crypto_larval *larval)\n--\ncrypto/api.c-174-\ncrypto/api.c:175:void crypto_schedule_test(struct crypto_larval *larval)\ncrypto/api.c-176-{\n--\ncrypto/api.c=293=static struct crypto_alg *crypto_larval_lookup(const char *name, u32 type,\n--\ncrypto/api.c-326-\ncrypto/api.c:327:int crypto_probing_notify(unsigned long val, void *v)\ncrypto/api.c-328-{\n--\ncrypto/api.c=454=static void crypto_untrack_tfm(struct crypto_tfm *tfm) { }\n--\ncrypto/api.c-456-\ncrypto/api.c:457:void crypto_shoot_alg(struct crypto_alg *alg)\ncrypto/api.c-458-{\n--\ncrypto/api.c=668=EXPORT_SYMBOL_GPL(crypto_alloc_tfm_node);\n--\ncrypto/api.c-677- */\ncrypto/api.c:678:void crypto_destroy_tfm(void *mem, struct crypto_tfm *tfm)\ncrypto/api.c-679-{\n--\ncrypto/api.c=694=EXPORT_SYMBOL_GPL(crypto_destroy_tfm);\ncrypto/api.c-695-\ncrypto/api.c:696:int crypto_has_alg(const char *name, u32 type, u32 mask)\ncrypto/api.c-697-{\n--\ncrypto/api.c=708=EXPORT_SYMBOL_GPL(crypto_has_alg);\ncrypto/api.c-709-\ncrypto/api.c:710:void crypto_req_done(void *data, int err)\ncrypto/api.c-711-{\n--\ncrypto/api.c=720=EXPORT_SYMBOL_GPL(crypto_req_done);\ncrypto/api.c-721-\ncrypto/api.c:722:void crypto_destroy_alg(struct crypto_alg *alg)\ncrypto/api.c-723-{\n"
}

7/2 2026/08/11 18:21 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 28573
							output: 45
							thoughts: 136
						

Thoughts:
**Analyzing Crypto API Functions**

Okay, so I'm looking at these core crypto API functions.  Right, `__crypto_alloc_tfm` – that's the one in `crypto/api.c` that handles the allocation of a transform. The signature is:

```c
struct crypto_tfm *__crypto_alloc_tfm(struct crypto_alg *alg, u32 type,
				      u32 mask)
```

And then there's `crypto_create_tfm_node`, also in `crypto/api.c`, which does the job of actually creating a transform node:

```c
void *crypto_create_tfm_node(struct crypto_alg *alg,
			     const struct crypto_type *frontend,
			     int node)
```

Now, with this in mind, let's dive into the `dm-crypt.c` code and see how these functions are being used in the context of device mapper encryption. I'm going to look for the calls to these functions and understand the flow.



8/2 2026/08/11 18:21 tool grepper
0m Args:
{
  "Expression": "^(static )?(void|int) crypt_(ctr|dtr)\\(",
  "PathPrefix": "drivers/md/dm-crypt.c"
}

Results:
{
  "Output": "drivers/md/dm-crypt.c=2776=static void crypt_page_free(void *page, void *pool_data)\n--\ndrivers/md/dm-crypt.c-2783-\ndrivers/md/dm-crypt.c:2784:static void crypt_dtr(struct dm_target *ti)\ndrivers/md/dm-crypt.c-2785-{\n--\ndrivers/md/dm-crypt.c=3255=static int crypt_report_zones(struct dm_target *ti,\n--\ndrivers/md/dm-crypt.c-3271- */\ndrivers/md/dm-crypt.c:3272:static int crypt_ctr(struct dm_target *ti, unsigned int argc, char **argv)\ndrivers/md/dm-crypt.c-3273-{\n"
}

9/2 2026/08/11 18:21 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29201
							output: 40
							thoughts: 14
						

Thoughts:
Here's my take on the thought process, as if I were the one thinking it:

**Diving into `mm/secretmem.c`**

Alright, time to get my hands dirty. I need to understand how this secret memory management is working. My immediate focus is on the functions defined within `mm/secretmem.c`. I'll start by making sure I understand the core components of its implementation. Let's see what interesting techniques they're employing. This should be an interesting study.



10/2 2026/08/11 18:21 tool grepper
0m Args:
{
  "Expression": "^(static )?struct file \\*secretmem_file_create\\(",
  "PathPrefix": "mm/secretmem.c"
}

Results:
{
  "Output": "mm/secretmem.c=236=static struct notifier_block secretmem_wipe_nb = {\n--\nmm/secretmem.c-239-\nmm/secretmem.c:240:static struct file *secretmem_file_create(unsigned long flags)\nmm/secretmem.c-241-{\n"
}

11/2 2026/08/11 18:21 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29453
							output: 45
							thoughts: 16
						

Thoughts:
Here's my take on those thoughts, tailored for an expert audience:

**Diving into `fs/crypto/keyring.c`**

Alright, time to dig into the internals. I need to refresh my memory on the keyring interactions within the filesystem encryption code, specifically focusing on the functions defined in `fs/crypto/keyring.c`. I'm thinking I should start by mapping out the key management flow. I need to understand how keys are acquired, stored, and used within the context of encrypted file systems. I'll probably be looking for the standard keyring operations: adding keys, looking them up, iterating through them, and deleting them. I'm especially interested in how the keyrings are associated with specific filesystems or inodes, and how the encryption keys are derived from the keyrings. I'll need to pay close attention to the error handling within these functions, especially those scenarios where a keyring lookup might fail, or key retrieval fails. Let's see if the code confirms my initial assumptions about how the pieces fit together. I know there are likely complex interactions with the crypto API, so I'll keep an eye out for how this layer is orchestrated.



12/2 2026/08/11 18:21 tool grepper
0m Args:
{
  "Expression": "^(static )?(void|int) fscrypt_destroy_keyring\\(",
  "PathPrefix": "fs/crypto/keyring.c"
}

Results:
{
  "Output": "fs/crypto/keyring.c=185=static int allocate_filesystem_keyring(struct super_block *sb)\n--\nfs/crypto/keyring.c-214- */\nfs/crypto/keyring.c:215:void fscrypt_destroy_keyring(struct super_block *sb)\nfs/crypto/keyring.c-216-{\n"
}

13/2 2026/08/11 18:21 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29863
							output: 36
							thoughts: 104
						

Thoughts:
**Focusing on Key Cryptographic Functions**

Okay, let's zero in on these functions: `__crypto_alloc_tfm`, `crypto_create_tfm_node`, `crypto_destroy_tfm`, `crypt_ctr`, `crypt_dtr`, `secretmem_file_create`, and `fscrypt_destroy_keyring`. These seem to be the core of the cryptographic operations and key management we're working with here. I need to understand their interdependencies and how they are used within the broader system. 

Hmm, a quick check... wait a minute, `__crypto_alloc_tfm` and `crypto_create_tfm_node` are *exported*? That's interesting. I need to remember that these are visible outside the module/context I'm working in. That likely has important implications for security considerations and potential vulnerabilities. I'll make a mental note to keep this in mind as I continue my analysis. I should look into where they're being used outside of the obvious internal calls. This could affect the whole project.



14/2 2026/08/11 18:21 tool grepper
0m Args:
{
  "Expression": "EXPORT_SYMBOL.*__crypto_alloc_tfm",
  "PathPrefix": "crypto/api.c"
}

Results:
{
  "Output": "crypto/api.c=465=struct crypto_tfm *__crypto_alloc_tfm(struct crypto_alg *alg, u32 type,\n--\ncrypto/api.c-495-}\ncrypto/api.c:496:EXPORT_SYMBOL_GPL(__crypto_alloc_tfm);\ncrypto/api.c-497-\n"
}

15/2 2026/08/11 18:21 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 30332
							output: 37
							thoughts: 17
						

Thoughts:
Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person:

**Navigating the Cryptographic Landscape**

Yes, that's it. This feels right. I'm on the right track.

Now, let's drill down. The next logical step in this investigation is to examine the `crypto_create_tfm_node` function. I need to understand exactly how the transformation framework node is being constructed. Knowing how the framework constructs its internal node representation will provide insight into how everything fits together. I need to understand this to debug the issues with the cryptographic algorithm implementations. Time to dive into the code...



16/2 2026/08/11 18:21 tool grepper
0m Args:
{
  "Expression": "EXPORT_SYMBOL.*crypto_create_tfm_node",
  "PathPrefix": "crypto/api.c"
}

Results:
{
  "Output": "crypto/api.c=554=void *crypto_create_tfm_node(struct crypto_alg *alg,\n--\ncrypto/api.c-593-}\ncrypto/api.c:594:EXPORT_SYMBOL_GPL(crypto_create_tfm_node);\ncrypto/api.c-595-\n"
}

17/2 2026/08/11 18:21 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 30617
							output: 29
							thoughts: 70
						

Thoughts:
**Analyzing the Potential Path to Secret Memory Creation**

Okay, here we go. Yes, I've got it. My initial thought is to zero in on `secretmem_file_create`. It's defined as `static`, which is interesting, but it's called from the `memfd_secret` system call. The question now is whether the `memfd_secret` system call is reachable. Yes, it looks like it is, so long as `CONFIG_SECRETMEM` is enabled. Time to check `CONFIG_SECRETMEM`.



18/2 2026/08/11 18:21 tool grepper
0m Args:
{
  "Expression": "config SECRETMEM",
  "PathPrefix": "mm/Kconfig"
}

Results:
{
  "Output": "mm/Kconfig=1340=config MEMFD_CREATE\n--\nmm/Kconfig-1342-\nmm/Kconfig:1343:config SECRETMEM\nmm/Kconfig-1344-\tdefault y\n"
}

19/2 2026/08/11 18:21 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 30907
							output: 33
							thoughts: 1297
						

Thoughts:
**Analysis of the Secrets Wiping Patch**

Okay, so `CONFIG_SECRETMEM` is enabled by default, good. Now, let's dive into this `CONFIG_CRASH_WIPE_SECRETS` patch. I see it depends on `CRASH_DUMP`, which makes sense. I'll need to enable both `CRASH_WIPE_SECRETS` and `CRASH_DUMP` in the config.

The patch touches `net/rxrpc/`. Initially, I was looking for specific functions in `net/rxrpc/` to focus on, like `rxrpc_wipe`, but those seem limited to the panic path, along with `rxgk_wipe_server_key` and `rxkad_wipe_server_key`.  The interesting thing about `rxrpc_wipe` is that it's a callback within `key_type_rxrpc`.  So, on the whole, changes in `net/rxrpc/` are probably not an issue, since they are only used from a panic path.

However, the patch *also* modifies `crypto/api.c` and `dm-crypt.c`.  Looking at `crypto/api.c`, I see `crypto_track_tfm` and `crypto_untrack_tfm`, which add a global lock and track `tfm` allocations. This is a functional change during normal operation.  This immediately raises a red flag; any addition of a global lock in these key paths, which are `crypto_alloc_tfm` and `crypto_destroy_tfm` can introduce potential race conditions.

Similarly, in `dm-crypt.c`, I see `crypt_track_cc` and `crypt_untrack_cc`, which also adds a lock and tracks `crypt_config`.  Another functional change during normal operation.

Further exploration of `fs/crypto/keyring.c` shows this change:

```c
	/* 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;
```
This is also a functional change during normal operation.

Then I look at the `mm/secretmem.c` file and see this:
```c
	inode_sb_list_add(inode);
```
Again, a functional change during normal operation.

Okay, so despite the primary feature being panic-time secret wiping, this patch introduces significant functional changes that are *always* reachable, during normal operation. The tracking mechanisms in `crypto/api.c`, `dm-crypt.c`, `fs/crypto/keyring.c`, and `mm/secretmem.c` are all executed outside of the panic path. This adds potential for race conditions, list corruption, and other bugs that would have a significant impact during *normal* operation. Therefore, it's absolutely worth fuzzing these new code paths to expose any hidden issues.

To effectively test these changes, I'll need to enable the following configurations:

*   `CRASH_WIPE_SECRETS`
*   `CRASH_DUMP`
*   `BLK_DEV_CRYPT` (for dm-crypt)
*   `FS_ENCRYPTION` (for fscrypt)
*   `SECRETMEM` (for secretmem)

Let me just quickly check `CRASH_DUMP` for any dependencies...



20/2 2026/08/11 18:22 tool grepper
0m Args:
{
  "Expression": "config CRASH_DUMP",
  "PathPrefix": "kernel/Kconfig.kexec"
}

Results:
{
  "Output": "kernel/Kconfig.kexec=89=config KEXEC_JUMP\n--\nkernel/Kconfig.kexec-96-\nkernel/Kconfig.kexec:97:config CRASH_DUMP\nkernel/Kconfig.kexec-98-\tbool \"kernel crash dumps\"\n--\nkernel/Kconfig.kexec=129=config CRASH_DM_CRYPT_CONFIGS\n--\nkernel/Kconfig.kexec-135-\nkernel/Kconfig.kexec:136:config CRASH_DUMP_KUNIT_TEST\nkernel/Kconfig.kexec-137-\ttristate \"Unit Tests for kernel crash dumps\" if !KUNIT_ALL_TESTS\n"
}

21/2 2026/08/11 18:22 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 33180
							output: 30
							thoughts: 13
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Analyzing `CRASH_DUMP` Dependencies**

Okay, time to dive into this. The `CRASH_DUMP` situation needs a good once-over. I'm immediately thinking about its dependencies. Gotta mentally map those out. We're talking about all the libraries, modules, and system components that it relies on to function correctly. Anything that could be a potential point of failure if updated, downgraded, or even just if a configuration change ripples through. I need to think about the order they are called. Could one of the dependencies itself cause a crash? Let's trace it back and confirm. Need to confirm that the documentation is clear as day. If it's not, I'll need to update it. Now, let's start with the basics...



22/2 2026/08/11 18:22 tool grepper
0m Args:
{
  "Expression": "depends on.*",
  "PathPrefix": "kernel/Kconfig.kexec"
}

Results:
{
  "Output": "kernel/Kconfig.kexec=20=config KEXEC\nkernel/Kconfig.kexec-21-\tbool \"Enable kexec system call\"\nkernel/Kconfig.kexec:22:\tdepends on ARCH_SUPPORTS_KEXEC\nkernel/Kconfig.kexec-23-\tselect KEXEC_CORE\n--\nkernel/Kconfig.kexec=38=config KEXEC_FILE\nkernel/Kconfig.kexec-39-\tbool \"Enable kexec file based system call\"\nkernel/Kconfig.kexec:40:\tdepends on ARCH_SUPPORTS_KEXEC_FILE\nkernel/Kconfig.kexec-41-\tselect CRYPTO_LIB_SHA256\n--\nkernel/Kconfig.kexec=49=config KEXEC_SIG\nkernel/Kconfig.kexec-50-\tbool \"Verify kernel signature during kexec_file_load() syscall\"\nkernel/Kconfig.kexec:51:\tdepends on ARCH_SUPPORTS_KEXEC_SIG\nkernel/Kconfig.kexec:52:\tdepends on KEXEC_FILE\nkernel/Kconfig.kexec-53-\thelp\n--\nkernel/Kconfig.kexec=63=config KEXEC_SIG_FORCE\nkernel/Kconfig.kexec-64-\tbool \"Require a valid signature in kexec_file_load() syscall\"\nkernel/Kconfig.kexec:65:\tdepends on ARCH_SUPPORTS_KEXEC_SIG_FORCE\nkernel/Kconfig.kexec:66:\tdepends on KEXEC_SIG\nkernel/Kconfig.kexec-67-\thelp\n--\nkernel/Kconfig.kexec=71=config KEXEC_IMAGE_VERIFY_SIG\n--\nkernel/Kconfig.kexec-73-\tdefault ARCH_DEFAULT_KEXEC_IMAGE_VERIFY_SIG\nkernel/Kconfig.kexec:74:\tdepends on ARCH_SUPPORTS_KEXEC_IMAGE_VERIFY_SIG\nkernel/Kconfig.kexec:75:\tdepends on KEXEC_SIG\nkernel/Kconfig.kexec:76:\tdepends on EFI \u0026\u0026 SIGNED_PE_FILE_VERIFICATION\nkernel/Kconfig.kexec-77-\thelp\n--\nkernel/Kconfig.kexec=80=config KEXEC_BZIMAGE_VERIFY_SIG\nkernel/Kconfig.kexec-81-\tbool \"Enable bzImage signature verification support\"\nkernel/Kconfig.kexec:82:\tdepends on ARCH_SUPPORTS_KEXEC_BZIMAGE_VERIFY_SIG\nkernel/Kconfig.kexec:83:\tdepends on KEXEC_SIG\nkernel/Kconfig.kexec:84:\tdepends on SIGNED_PE_FILE_VERIFICATION\nkernel/Kconfig.kexec-85-\tselect SYSTEM_TRUSTED_KEYRING\n--\nkernel/Kconfig.kexec=89=config KEXEC_JUMP\nkernel/Kconfig.kexec-90-\tbool \"kexec jump\"\nkernel/Kconfig.kexec:91:\tdepends on ARCH_SUPPORTS_KEXEC_JUMP\nkernel/Kconfig.kexec:92:\tdepends on KEXEC \u0026\u0026 HIBERNATION\nkernel/Kconfig.kexec-93-\thelp\n--\nkernel/Kconfig.kexec=97=config CRASH_DUMP\n--\nkernel/Kconfig.kexec-99-\tdefault ARCH_DEFAULT_CRASH_DUMP\nkernel/Kconfig.kexec:100:\tdepends on ARCH_SUPPORTS_CRASH_DUMP\nkernel/Kconfig.kexec:101:\tdepends on KEXEC_CORE\nkernel/Kconfig.kexec-102-\tselect VMCORE_INFO\n--\nkernel/Kconfig.kexec=118=config CRASH_DM_CRYPT\nkernel/Kconfig.kexec-119-\tbool \"Support saving crash dump to dm-crypt encrypted volume\"\nkernel/Kconfig.kexec:120:\tdepends on KEXEC_FILE\nkernel/Kconfig.kexec:121:\tdepends on CRASH_DUMP\nkernel/Kconfig.kexec:122:\tdepends on DM_CRYPT\nkernel/Kconfig.kexec:123:\tdepends on KEYS\nkernel/Kconfig.kexec-124-\thelp\n--\nkernel/Kconfig.kexec=136=config CRASH_DUMP_KUNIT_TEST\nkernel/Kconfig.kexec-137-\ttristate \"Unit Tests for kernel crash dumps\" if !KUNIT_ALL_TESTS\nkernel/Kconfig.kexec:138:\tdepends on CRASH_DUMP \u0026\u0026 KUNIT\nkernel/Kconfig.kexec-139-\tdefault KUNIT_ALL_TESTS\n--\nkernel/Kconfig.kexec=147=config CRASH_HOTPLUG\n--\nkernel/Kconfig.kexec-149-\tdefault y\nkernel/Kconfig.kexec:150:\tdepends on CRASH_DUMP \u0026\u0026 (HOTPLUG_CPU || MEMORY_HOTPLUG)\nkernel/Kconfig.kexec:151:\tdepends on ARCH_SUPPORTS_CRASH_HOTPLUG\nkernel/Kconfig.kexec-152-\thelp\n--\nkernel/Kconfig.kexec=161=config CRASH_MAX_MEMORY_RANGES\n--\nkernel/Kconfig.kexec-163-\tdefault 8192\nkernel/Kconfig.kexec:164:\tdepends on CRASH_HOTPLUG\nkernel/Kconfig.kexec-165-\thelp\n--\nkernel/Kconfig.kexec=182=config CRASH_WIPE_SECRETS\nkernel/Kconfig.kexec-183-\tbool \"Wipe secrets before kdump\"\nkernel/Kconfig.kexec:184:\tdepends on CRASH_DUMP\nkernel/Kconfig.kexec-185-\thelp\n"
}

23/2 2026/08/11 18:22 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 34821
							output: 30
							thoughts: 91
						

Thoughts:
Okay, here's my understanding, considering I know my way around kernel configurations:

**Analyzing Crash Dumps and Cryptographic Device Mapping**

Right, let me break this down. My understanding is that the `CRASH_DUMP` option hinges on both `ARCH_SUPPORTS_CRASH_DUMP` and `KEXEC_CORE` being enabled. `KEXEC_CORE` itself is automatically pulled in if either `KEXEC` or `KEXEC_FILE` is selected, which makes sense. Based on this dependency chain, a logical step here would be to enable `CRASH_DUMP` and the `CRASH_WIPE_SECRETS` option to secure the dump.

Now, let me shift gears and consider the `BLK_DEV_CRYPT` configuration. I want to make sure I am taking into account all the factors.



24/2 2026/08/11 18:22 tool grepper
0m Args:
{
  "Expression": "config DM_CRYPT",
  "PathPrefix": "drivers/md/Kconfig"
}

Results:
{
  "Output": "drivers/md/Kconfig=287=config DM_UNSTRIPED\n--\ndrivers/md/Kconfig-293-\ndrivers/md/Kconfig:294:config DM_CRYPT\ndrivers/md/Kconfig-295-\ttristate \"Crypt target support\"\n"
}

25/2 2026/08/11 18:22 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 35205
							output: 32
							thoughts: 19
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience and presented in the first person:

**Decrypting the Puzzle**

Alright, my initial hunch is leaning heavily towards `DM_CRYPT`. Based on the context, that seems the most likely avenue to explore first. Given the nature of this situation, it would be the best place to begin the process.

Now, while `DM_CRYPT` is in the forefront of my mind, I can't be completely certain. It's best to perform some due diligence. Next up, I need to check and rule out `FS_ENCRYPTION`. That needs to be on my radar and is the next logical step in this diagnostic process. Gotta cover all the bases, ya know? I will be thinking of that next.



26/2 2026/08/11 18:22 tool grepper
0m Args:
{
  "Expression": "config FS_ENCRYPTION",
  "PathPrefix": "fs/crypto/Kconfig"
}

Results:
{
  "Output": "fs/crypto/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\nfs/crypto/Kconfig:2:config FS_ENCRYPTION\nfs/crypto/Kconfig-3-\tbool \"FS Encryption (Per-file encryption)\"\n--\nfs/crypto/Kconfig-30-# strongly recommended to enable optimized implementations too.\nfs/crypto/Kconfig:31:config FS_ENCRYPTION_ALGS\nfs/crypto/Kconfig-32-\ttristate\n--\nfs/crypto/Kconfig-37-\nfs/crypto/Kconfig:38:config FS_ENCRYPTION_INLINE_CRYPT\nfs/crypto/Kconfig-39-\tbool\n"
}

27/2 2026/08/11 18:22 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 35598
							output: 29
							thoughts: 20
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Analyzing the Security Context**

Okay, first instinct – it's definitely related to `FS_ENCRYPTION`. That's the baseline assumption, the core element to consider. Now, given that's in play, my next move is to immediately think about where the secrets, the keys, are being stored. `SECRETMEM` is the obvious place to investigate next. Let's see what's happening there, what the access patterns are, and what implications those have for the overall integrity of the encryption. That's where the real vulnerabilities will be. Gotta understand the secrets!



28/2 2026/08/11 18:22 tool grepper
0m Args:
{
  "Expression": "config SECRETMEM",
  "PathPrefix": "mm/Kconfig"
}

Results:
{
  "Output": "mm/Kconfig=1340=config MEMFD_CREATE\n--\nmm/Kconfig-1342-\nmm/Kconfig:1343:config SECRETMEM\nmm/Kconfig-1344-\tdefault y\n"
}

29/2 2026/08/11 18:22 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 35861
							output: 42
							thoughts: 288
						

Thoughts:
**My Analysis of EnableConfigs**

Okay, so the situation is clear: we need to enable `SECRETMEM`. Given that, the `EnableConfigs` list should obviously include `CRASH_DUMP`, `CRASH_WIPE_SECRETS`, `DM_CRYPT`, `FS_ENCRYPTION`, and `SECRETMEM`.

Hang on, `CRASH_WIPE_SECRETS` depends on `CRASH_DUMP`, that's a key dependency to keep in mind. Now, do we need `KEXEC`? I see that `CRASH_DUMP` relies on `KEXEC_CORE`, and `KEXEC_CORE` is selected by either `KEXEC` or `KEXEC_FILE`. To simplify things, I'll just add `KEXEC` and `CRASH_DUMP` as it covers the dependency chain.

Time to revisit the `crypto_track_tfm` issue. I need to pinpoint where this function is called. Right, it's called from `__crypto_alloc_tfm` and `crypto_create_tfm_node`. Those are the critical functions to investigate. I'll also add `crypto_destroy_tfm` to the list. Thinking through the specific use cases, for dm-crypt I need to look at `crypt_ctr` and `crypt_dtr`. For `secretmem` I was focused on `secretmem_file_create`. And for fscrypt, it's `fscrypt_destroy_keyring`.

Wait a minute, `secretmem_file_create` is declared as static. Hmm, are there any non-static functions in the `secretmem` module? I should take a closer look at `mm/secretmem.c` to see if there are any other entry points.



30/2 2026/08/11 18:22 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "^(SYSCALL_DEFINE|int|void) .*memfd_secret",
  "PathPrefix": "mm/secretmem.c"
}

Results:
{
  "error": "no matches"
}

31/2 2026/08/11 18:22 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 36595
							output: 30
							thoughts: 19
						

Thoughts:
Here's a summary of my thought process, as requested:

**Diving into `memfd_secret` Implementation**

Okay, so I need to understand how `memfd_secret` is implemented. My immediate instinct is to jump into the code. The likely entry point is `mm/secretmem.c`. That file is probably where the core logic for the secret memory functionality, including the handling of `memfd_secret`, resides. Let's start there and see what we can find. I expect it will involve examining the system calls, data structures and maybe even the interactions with the memory management subsystem.



32/2 2026/08/11 18:22 tool grepper
0m Args:
{
  "Expression": "memfd_secret",
  "PathPrefix": "mm/secretmem.c"
}

Results:
{
  "Output": "mm/secretmem.c=42=MODULE_PARM_DESC(secretmem_enable,\nmm/secretmem.c:43:\t\t \"Enable secretmem and memfd_secret(2) system call\");\nmm/secretmem.c-44-\n--\nmm/secretmem.c=240=static struct file *secretmem_file_create(unsigned long flags)\n--\nmm/secretmem.c-275-\nmm/secretmem.c:276:SYSCALL_DEFINE1(memfd_secret, unsigned int, flags)\nmm/secretmem.c-277-{\n"
}

33/2 2026/08/11 18:22 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 36930
							output: 37
							thoughts: 97
						

Thoughts:
Okay, here's my breakdown of this.

**Tracing the `memfd_secret` System Call**

Right, so `SYSCALL_DEFINE1(memfd_secret, unsigned int, flags)`... that's the starting point. I need to understand what this new `memfd_secret` system call is doing. I know I saw something about it recently.

First stop, `fs/crypto/keyring.c`. I'm going to look specifically for any related functions within this filesystem encryption context. Aha! `fscrypt_destroy_keyring` is non-static, so it's a potential area of interest for interaction, let's keep it in mind.

Next, I'll switch gears and check `drivers/md/dm-crypt.c`. Looking at the device mapper crypto driver. I'll need to see how the system call interacts with block device encryption. Hmm, `crypt_ctr` and `crypt_dtr` are both static, which makes them less interesting for direct interaction. However, they might be important internal components. The question is, are there any *non*-static functions within this driver? I need to find the entrypoints if I'm going to determine what new interaction is occuring. I'll need to go back and analyze these functions in order to better understand what is going on.



34/2 2026/08/11 18:22 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "^(int|void) .*crypt_",
  "PathPrefix": "drivers/md/dm-crypt.c"
}

Results:
{
  "error": "no matches"
}

35/2 2026/08/11 18:22 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 37365
							output: 46
							thoughts: 17
						

Thoughts:
Here's my thought process, distilled:

**Digging into `dm-crypt.c` for Function Scope**

Okay, so I need to get a handle on the function landscape in `dm-crypt.c`. The immediate goal is to identify which functions aren't declared as `static`. Knowing this will tell me which functions are potentially callable from *outside* the file, which is crucial for understanding its interactions within the broader DM-Crypt subsystem. Time to dive in and start looking.



36/2 2026/08/11 18:22 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "^(int|void) [a-zA-Z0-9_]+\\(",
  "PathPrefix": "drivers/md/dm-crypt.c"
}

Results:
{
  "error": "no matches"
}

37/2 2026/08/11 18:22 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 37546
							output: 253
							thoughts: 1933
						

Thoughts:
**Analysis of Code Changes for Fuzzing Prioritization**

Okay, let's break this down. My initial thought was to identify the key entry points affected by this patch related to secret wiping. I started by looking at `dm-crypt.c`, and noticed the registration of the `crypt` target type. I need to make sure I don't miss anything that can be interesting, so I double checked that the static functions `crypt_ctr` and `crypt_dtr` *are* distinct symbols, even though static, because they are function pointers in the `struct target_type`. They're not inlined, so they're valid targets. Good, that's important for the prompt's preference for non-inlined API entrypoints.

I then briefly checked `net/rxrpc/` and `security/keys/` but concluded that the changes there are limited to the panic path, which is outside the main scope of what I'm looking for (and what Syzkaller, which is implied, will handle on its own).

The real meat is in the crypto changes: the introduction of `crypto_track_tfm` and `crypto_untrack_tfm`, called from `__crypto_alloc_tfm`, `crypto_create_tfm_node`, and `crypto_destroy_tfm`. I also saw `fscrypt_destroy_keyring`, called from `generic_shutdown_super`, which seemed interesting. And there's also `sys_memfd_secret`, which is a syscall, but might be important to note.

Initially, I compiled a list of potential focus symbols: `__crypto_alloc_tfm`, `crypto_create_tfm_node`, `crypto_destroy_tfm`, `crypt_ctr`, `crypt_dtr`, `fscrypt_destroy_keyring`, and `sys_memfd_secret`. I need to consider `fscrypt_destroy_keyring`, since it's a key cleanup function.

I realized that even though the prompt said not to focus on static functions, `crypt_ctr` and `crypt_dtr` *are* function pointers, so that's okay.

I was thinking about `dm_crypt_init` but decided that `crypt_ctr` is a better focus since it calls `crypt_track_cc`. I checked `crypto_track_tfm` again and realized that even though it is just a simple list addition, the addition introduces global locks (`crypto_tfm_list_lock`) on every crypto tfm allocation and destruction which might cause lock contention. This can lead to subtle bugs. It's not a hot path, but it introduces functional changes. Likewise, `dm_crypt_wipe_list_lock` is introduced, and `inode_sb_list_add(inode)` adds inodes to the superblock. These are all worth fuzzing because they represent global state changes with potential for races.

So, the key EnableConfigs are: `CRASH_DUMP`, `CRASH_WIPE_SECRETS`, `DM_CRYPT`, `FS_ENCRYPTION`, `SECRETMEM`.

The FocusSymbols now are: `__crypto_alloc_tfm`, `crypto_create_tfm_node`, `crypto_destroy_tfm`, `crypt_ctr`, `crypt_dtr`, and `fscrypt_destroy_keyring`.

I need to make sure to exclude `sys_memfd_secret` since it is a syscall, and syzkaller knows to fuzz that.

Then, I reviewed the reasoning: The patch's primary function is to wipe secrets before kdump, but it also introduces tracking mechanisms (global locks and list operations) during normal crypto and dm-crypt object creation and destruction. This opens the door to potential race conditions, deadlocks, or list corruptions. This means functional impact, and it's worth fuzzing.

I reviewed the changes to `fscrypt_destroy_keyring` and `crypt_iv_lmk_dtr`. They're changes that occur during normal operation, so they should be targeted.

Finally, I concluded `WorthFuzzing=true`.



38/2 2026/08/11 18:23 tool set-results
0m Args:
{
  "EnableConfigs": [
    "CRASH_DUMP",
    "CRASH_WIPE_SECRETS",
    "DM_CRYPT",
    "FS_ENCRYPTION",
    "SECRETMEM"
  ],
  "FocusSymbols": [
    "__crypto_alloc_tfm",
    "crypto_create_tfm_node",
    "crypto_destroy_tfm",
    "crypt_ctr",
    "crypt_dtr",
    "fscrypt_destroy_keyring"
  ],
  "Reasoning": "The patch introduces CONFIG_CRASH_WIPE_SECRETS to wipe sensitive secrets before kdump. While the actual wiping occurs in the panic path, the patch also introduces tracking mechanisms that are executed during normal operation. This includes adding global locks and list operations for tracking crypto tfms (crypto_tfm_list_lock), dm-crypt configs (dm_crypt_wipe_list_lock), and adding secretmem inodes to the superblock list. These changes can potentially introduce race conditions, deadlocks, or list corruptions during normal allocation and destruction of these objects. Therefore, the changes have a functional impact worth fuzzing.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "CRASH_DUMP",
    "CRASH_WIPE_SECRETS",
    "DM_CRYPT",
    "FS_ENCRYPTION",
    "SECRETMEM"
  ],
  "FocusSymbols": [
    "__crypto_alloc_tfm",
    "crypto_create_tfm_node",
    "crypto_destroy_tfm",
    "crypt_ctr",
    "crypt_dtr",
    "fscrypt_destroy_keyring"
  ],
  "Reasoning": "The patch introduces CONFIG_CRASH_WIPE_SECRETS to wipe sensitive secrets before kdump. While the actual wiping occurs in the panic path, the patch also introduces tracking mechanisms that are executed during normal operation. This includes adding global locks and list operations for tracking crypto tfms (crypto_tfm_list_lock), dm-crypt configs (dm_crypt_wipe_list_lock), and adding secretmem inodes to the superblock list. These changes can potentially introduce race conditions, deadlocks, or list corruptions during normal allocation and destruction of these objects. Therefore, the changes have a functional impact worth fuzzing.",
  "WorthFuzzing": true
}

39/1 2026/08/11 18:23 action If
0m Args:
{
  "WorthFuzzing": true
}

Results:
null

40/2 2026/08/11 18:23 agent kmsan-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "KMSANReasoning": "The patch series introduces a new feature `CONFIG_CRASH_WIPE_SECRETS` which zeroes out sensitive secrets (such as cryptographic keys, dm-crypt keys, fscrypt keys, and secretmem pages) during a kernel panic before a kdump crash kernel is started. The changes primarily involve adding `.wipe` callbacks to various key types, registering panic notifiers, and tracking allocated cryptographic contexts in lists so they can be iterated and wiped during a crash.\n\nThese changes only affect the panic/crash path and do not introduce any new structures sent to user space, nor do they modify normal execution paths in a way that would leave memory uninitialized. The modifications to normal execution paths are limited to adding/removing objects from tracking lists and using `kfree_sensitive` or `xchg` to safely clear pointers. There is no risk of uninitialized memory being exposed to user space or used in control flow. Standard KASAN and other bug detectors are sufficient to catch any potential memory corruption or use-after-free issues related to the new tracking lists.",
  "NeedsKMSAN": false
}

Instruction:
You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.

CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
  a comprehensive suite of debugging tools and sanitizers, including KASAN
  (out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
  (locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
  or page allocations) and kernel-to-user memory info-leaks.

Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.

Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.

Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
   attributes, ioctl output arguments, socket options, or BPF buffers) where fields
   or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
   or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
   could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
   uninitialized bytes of existing buffers.

Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
  dereferences, locking deadlocks, or use-after-free bugs (these are already caught
  by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
  or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.

Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit 3f0999c4f5e6c648c580d868cc5f6dc90351b881
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Aug 11 18:21:02 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst
index 326b8a9738289..35cb00a87c461 100644
--- a/Documentation/security/keys/core.rst
+++ b/Documentation/security/keys/core.rst
@@ -1596,6 +1596,19 @@ The structure has a number of fields, some of which are mandatory:
      It is not safe to sleep in this method; the caller may hold spinlocks.
 
 
+  *  ``void (*wipe)(struct key *key);``
+
+     This method is optional. It is called from the panic path when
+     CONFIG_CRASH_WIPE_SECRETS is enabled, to erase the key material from
+     memory before the kdump kernel is started, so that it does not end up in
+     the crash dump. Unlike destroy(), it must only clear the payload, not
+     free it.
+
+     This method is called with all other CPUs stopped and preemption
+     disabled, and only for positively instantiated keys. It must not sleep,
+     allocate, free or take locks, as they will never be released.
+
+
   *  ``void (*describe)(const struct key *key, struct seq_file *p);``
 
      This method is optional. It is called during /proc/keys reading to
diff --git a/arch/arm64/include/asm/set_memory.h b/arch/arm64/include/asm/set_memory.h
index 90f61b17275e1..cb5f44bb5a949 100644
--- a/arch/arm64/include/asm/set_memory.h
+++ b/arch/arm64/include/asm/set_memory.h
@@ -13,6 +13,8 @@ int set_memory_valid(unsigned long addr, int numpages, int enable);
 
 int set_direct_map_invalid_noflush(struct page *page);
 int set_direct_map_default_noflush(struct page *page);
+int set_direct_map_default_nosplit(struct page *page);
+#define set_direct_map_default_nosplit set_direct_map_default_nosplit
 int set_direct_map_valid_noflush(struct page *page, unsigned nr, bool valid);
 bool kernel_page_present(struct page *page);
 
diff --git a/arch/arm64/kernel/machine_kexec.c b/arch/arm64/kernel/machine_kexec.c
index c5693a32e49b0..12bf3c90f5193 100644
--- a/arch/arm64/kernel/machine_kexec.c
+++ b/arch/arm64/kernel/machine_kexec.c
@@ -6,6 +6,7 @@
  * Copyright (C) Huawei Futurewei Technologies.
  */
 
+#include <linux/crash_core.h>
 #include <linux/interrupt.h>
 #include <linux/irq.h>
 #include <linux/kernel.h>
@@ -15,6 +16,7 @@
 #include <linux/set_memory.h>
 #include <linux/smp.h>
 
+#include <asm/barrier.h>
 #include <asm/cacheflush.h>
 #include <asm/cpu_ops.h>
 #include <asm/daifflags.h>
@@ -221,6 +223,24 @@ void machine_crash_shutdown(struct pt_regs *regs)
 	pr_info("Starting crashdump kernel...\n");
 }
 
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+/*
+ * Queue each wiped range for cleaning to the point of coherency.  Deferring
+ * the barrier until the notifier chain is complete avoids one DSB per range.
+ */
+void arch_crash_wipe_range(void *addr, size_t size)
+{
+	unsigned long start = (unsigned long)addr;
+
+	dcache_clean_poc_nosync(start, start + size);
+}
+
+void arch_crash_wipe_flush(void)
+{
+	dsb(sy);
+}
+#endif
+
 #if defined(CONFIG_CRASH_DUMP) && defined(CONFIG_HIBERNATION)
 /*
  * To preserve the crash dump kernel image, the relevant memory segments
diff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c
index bbe98ac9ad8c6..a7bb6bfa12ed3 100644
--- a/arch/arm64/mm/pageattr.c
+++ b/arch/arm64/mm/pageattr.c
@@ -275,6 +275,27 @@ int set_direct_map_default_noflush(struct page *page)
 				 PAGE_SIZE, set_mask, clear_mask);
 }
 
+/* Caller must guarantee @page is already mapped at page granularity. */
+int set_direct_map_default_nosplit(struct page *page)
+{
+	struct page_change_data data = {
+		.set_mask = __pgprot(PTE_PRESENT_VALID_KERNEL | PTE_WRITE),
+		.clear_mask = __pgprot(PTE_PRESENT_INVALID | PTE_RDONLY),
+	};
+	unsigned long addr = (unsigned long)page_address(page);
+	int ret;
+
+	if (!can_set_direct_map())
+		return 0;
+
+	lazy_mmu_mode_enable();
+	ret = walk_kernel_page_table_range_lockless(addr, addr + PAGE_SIZE,
+						    &pageattr_ops, NULL, &data);
+	lazy_mmu_mode_disable();
+
+	return ret;
+}
+
 static int __set_memory_enc_dec(unsigned long addr,
 				int numpages,
 				bool encrypt)
diff --git a/crypto/api.c b/crypto/api.c
index 24227582cfcf3..f919f52e8b5fc 100644
--- a/crypto/api.c
+++ b/crypto/api.c
@@ -10,13 +10,16 @@
  * and Nettle, by Niels Möller.
  */
 
+#include <linux/crash_core.h>
 #include <linux/err.h>
 #include <linux/errno.h>
 #include <linux/jump_label.h>
 #include <linux/kernel.h>
 #include <linux/kmod.h>
 #include <linux/module.h>
+#include <linux/notifier.h>
 #include <linux/param.h>
+#include <linux/rculist.h>
 #include <linux/sched/signal.h>
 #include <linux/slab.h>
 #include <linux/string.h>
@@ -397,6 +400,60 @@ static unsigned int crypto_ctxsize(struct crypto_alg *alg, u32 type, u32 mask)
 	return len;
 }
 
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+/*
+ * tfm allocations are tracked on crypto_tfm_list so that they can be wiped
+ * before kdump.
+ */
+static HLIST_HEAD(crypto_tfm_list);
+static DEFINE_SPINLOCK(crypto_tfm_list_lock);
+
+static int crypto_crash_wipe(struct notifier_block *nb, unsigned long action,
+		void *data)
+{
+	struct crypto_tfm *tfm;
+
+	rcu_read_lock();
+	hlist_for_each_entry_rcu(tfm, &crypto_tfm_list, wipe_list)
+		crash_wipe_memzero(tfm->__crt_ctx, tfm->wipe_size);
+	rcu_read_unlock();
+
+	/* off to kexec()! */
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block crypto_wipe_nb = {
+	.notifier_call = crypto_crash_wipe
+};
+
+static int __init crypto_tfm_track_init(void)
+{
+	crash_wipe_secrets_register(&crypto_wipe_nb);
+	return 0;
+}
+core_initcall(crypto_tfm_track_init);
+
+/* @size is the size of __crt_ctx */
+static void crypto_track_tfm(struct crypto_tfm *tfm, size_t size)
+{
+	tfm->wipe_size = size;
+
+	spin_lock(&crypto_tfm_list_lock);
+	hlist_add_head_rcu(&tfm->wipe_list, &crypto_tfm_list);
+	spin_unlock(&crypto_tfm_list_lock);
+}
+
+static void crypto_untrack_tfm(struct crypto_tfm *tfm)
+{
+	spin_lock(&crypto_tfm_list_lock);
+	hlist_del_rcu(&tfm->wipe_list);
+	spin_unlock(&crypto_tfm_list_lock);
+}
+#else
+static void crypto_track_tfm(struct crypto_tfm *tfm, size_t size) { }
+static void crypto_untrack_tfm(struct crypto_tfm *tfm) { }
+#endif /* CONFIG_CRASH_WIPE_SECRETS */
+
 void crypto_shoot_alg(struct crypto_alg *alg)
 {
 	down_write(&crypto_alg_sem);
@@ -409,15 +466,16 @@ struct crypto_tfm *__crypto_alloc_tfm(struct crypto_alg *alg, u32 type,
 				      u32 mask)
 {
 	struct crypto_tfm *tfm;
-	unsigned int tfm_size;
+	unsigned int ctx_size;
 	int err = -ENOMEM;
 
-	tfm_size = sizeof(*tfm) + crypto_ctxsize(alg, type, mask);
-	tfm = kzalloc(tfm_size, GFP_KERNEL);
+	ctx_size = crypto_ctxsize(alg, type, mask);
+	tfm = kzalloc(sizeof(*tfm) + ctx_size, GFP_KERNEL);
 	if (tfm == NULL)
 		goto out_err;
 
 	tfm->__crt_alg = alg;
+	crypto_track_tfm(tfm, ctx_size);
 
 	if (!tfm->exit && alg->cra_init && (err = alg->cra_init(tfm)))
 		goto cra_init_failed;
@@ -428,7 +486,8 @@ struct crypto_tfm *__crypto_alloc_tfm(struct crypto_alg *alg, u32 type,
 	crypto_exit_ops(tfm);
 	if (err == -EAGAIN)
 		crypto_shoot_alg(alg);
-	kfree(tfm);
+	crypto_untrack_tfm(tfm);
+	kfree_sensitive(tfm);
 out_err:
 	tfm = ERR_PTR(err);
 out:
@@ -497,12 +556,12 @@ void *crypto_create_tfm_node(struct crypto_alg *alg,
 			     int node)
 {
 	struct crypto_tfm *tfm;
-	size_t size;
 	char *mem;
 	int err;
+	size_t ctx_size = frontend->extsize(alg);
 
-	size = frontend->tfmsize + sizeof(*tfm) + frontend->extsize(alg);
-	mem = kzalloc_node(size, GFP_KERNEL, node);
+	mem = kzalloc_node(frontend->tfmsize + sizeof(*tfm) + ctx_size,
+			GFP_KERNEL, node);
 	if (!mem)
 		return ERR_PTR(-ENOMEM);
 
@@ -510,6 +569,7 @@ void *crypto_create_tfm_node(struct crypto_alg *alg,
 	tfm->__crt_alg = alg;
 	tfm->node = node;
 	tfm->fb = tfm;
+	crypto_track_tfm(tfm, ctx_size);
 
 	err = frontend->init_tfm(tfm);
 	if (err)
@@ -525,7 +585,8 @@ void *crypto_create_tfm_node(struct crypto_alg *alg,
 out_free_tfm:
 	if (err == -EAGAIN)
 		crypto_shoot_alg(alg);
-	kfree(mem);
+	crypto_untrack_tfm(tfm);
+	kfree_sensitive(mem);
 	mem = ERR_PTR(err);
 out:
 	return mem;
@@ -627,6 +688,7 @@ void crypto_destroy_tfm(void *mem, struct crypto_tfm *tfm)
 		alg->cra_exit(tfm);
 	crypto_exit_ops(tfm);
 	crypto_mod_put(alg);
+	crypto_untrack_tfm(tfm);
 	kfree_sensitive(mem);
 }
 EXPORT_SYMBOL_GPL(crypto_destroy_tfm);
diff --git a/crypto/asymmetric_keys/asymmetric_type.c b/crypto/asymmetric_keys/asymmetric_type.c
index 16a7ae16593c9..a0a68ebd07463 100644
--- a/crypto/asymmetric_keys/asymmetric_type.c
+++ b/crypto/asymmetric_keys/asymmetric_type.c
@@ -481,6 +481,16 @@ static void asymmetric_key_destroy(struct key *key)
 	asymmetric_key_free_kids(kids);
 }
 
+/* wipe the key without freeing. used by CONFIG_CRASH_WIPE_SECRETS. */
+static void asymmetric_key_wipe(struct key *key)
+{
+	struct asymmetric_key_subtype *subtype = asymmetric_key_subtype(key);
+
+	if (subtype && subtype->wipe)
+		subtype->wipe(key->payload.data[asym_crypto],
+				key->payload.data[asym_auth]);
+}
+
 static struct key_restriction *asymmetric_restriction_alloc(
 	key_restrict_link_func_t check,
 	struct key *key)
@@ -612,6 +622,7 @@ struct key_type key_type_asymmetric = {
 	.match_preparse		= asymmetric_key_match_preparse,
 	.match_free		= asymmetric_key_match_free,
 	.destroy		= asymmetric_key_destroy,
+	.wipe			= asymmetric_key_wipe,
 	.describe		= asymmetric_key_describe,
 	.lookup_restriction	= asymmetric_lookup_restriction,
 	.asym_query		= query_asymmetric_key,
diff --git a/crypto/asymmetric_keys/pkcs7_key_type.c b/crypto/asymmetric_keys/pkcs7_key_type.c
index b930d3bbf1af5..663a246742f71 100644
--- a/crypto/asymmetric_keys/pkcs7_key_type.c
+++ b/crypto/asymmetric_keys/pkcs7_key_type.c
@@ -74,6 +74,7 @@ static struct key_type key_type_pkcs7 = {
 	.instantiate		= generic_key_instantiate,
 	.revoke			= user_revoke,
 	.destroy		= user_destroy,
+	.wipe			= user_wipe,
 	.describe		= user_describe,
 	.read			= user_read,
 };
diff --git a/crypto/asymmetric_keys/public_key.c b/crypto/asymmetric_keys/public_key.c
index 09a0b83d5d77f..9931d56337e55 100644
--- a/crypto/asymmetric_keys/public_key.c
+++ b/crypto/asymmetric_keys/public_key.c
@@ -13,6 +13,7 @@
 #include <crypto/sig.h>
 #include <keys/asymmetric-subtype.h>
 #include <linux/asn1.h>
+#include <linux/crash_core.h>
 #include <linux/err.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
@@ -58,6 +59,19 @@ static void public_key_destroy(void *payload0, void *payload3)
 	public_key_signature_free(payload3);
 }
 
+/* wipe the key without freeing. used by CONFIG_CRASH_WIPE_SECRETS. */
+static void public_key_wipe(void *payload0, void *payload3)
+{
+	struct public_key *key = payload0;
+
+	if (key) {
+		if (key->key)
+			crash_wipe_memzero(key->key, key->keylen);
+		if (key->params)
+			crash_wipe_memzero(key->params, key->paramlen);
+	}
+}
+
 /*
  * Given a public_key, and an encoding and hash_algo to be used for signing
  * and/or verification with that key, determine the name of the corresponding
@@ -464,6 +478,7 @@ struct asymmetric_key_subtype public_key_subtype = {
 	.name_len		= sizeof("public_key") - 1,
 	.describe		= public_key_describe,
 	.destroy		= public_key_destroy,
+	.wipe			= public_key_wipe,
 	.query			= software_key_query,
 	.eds_op			= software_key_eds_op,
 	.verify_signature	= public_key_verify_signature_2,
diff --git a/drivers/md/dm-crypt.c b/drivers/md/dm-crypt.c
index 608b617fb817f..86adb2f9b94c0 100644
--- a/drivers/md/dm-crypt.c
+++ b/drivers/md/dm-crypt.c
@@ -9,12 +9,14 @@
  */
 
 #include <linux/completion.h>
+#include <linux/crash_core.h>
 #include <linux/err.h>
 #include <linux/module.h>
 #include <linux/hex.h>
 #include <linux/init.h>
 #include <linux/kernel.h>
 #include <linux/key.h>
+#include <linux/list.h>
 #include <linux/bio.h>
 #include <linux/blkdev.h>
 #include <linux/blk-integrity.h>
@@ -234,6 +236,11 @@ struct crypt_config {
 	struct mutex bio_alloc_lock;
 
 	u8 *authenc_key; /* space for keys in authenc() format (if used) */
+
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+	struct list_head wipe_list;
+#endif
+
 	u8 key[] __counted_by(key_size);
 };
 
@@ -243,6 +250,12 @@ struct crypt_config {
 
 static DEFINE_SPINLOCK(dm_crypt_clients_lock);
 static unsigned int dm_crypt_clients_n;
+
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+static LIST_HEAD(dm_crypt_wipe_list);
+static DEFINE_SPINLOCK(dm_crypt_wipe_list_lock);
+#endif
+
 static volatile unsigned long dm_crypt_pages_per_client;
 #define DM_CRYPT_MEMORY_PERCENT			2
 #define DM_CRYPT_MIN_PAGES_PER_CLIENT		(BIO_MAX_VECS * 16)
@@ -460,8 +473,7 @@ static void crypt_iv_lmk_dtr(struct crypt_config *cc)
 {
 	struct iv_lmk_private *lmk = &cc->iv_gen_private.lmk;
 
-	kfree_sensitive(lmk->seed);
-	lmk->seed = NULL;
+	kfree_sensitive(xchg(&lmk->seed, NULL));
 }
 
 static int crypt_iv_lmk_ctr(struct crypt_config *cc, struct dm_target *ti,
@@ -582,10 +594,8 @@ static void crypt_iv_tcw_dtr(struct crypt_config *cc)
 {
 	struct iv_tcw_private *tcw = &cc->iv_gen_private.tcw;
 
-	kfree_sensitive(tcw->iv_seed);
-	tcw->iv_seed = NULL;
-	kfree_sensitive(tcw->whitening);
-	tcw->whitening = NULL;
+	kfree_sensitive(xchg(&tcw->iv_seed, NULL));
+	kfree_sensitive(xchg(&tcw->whitening, NULL));
 }
 
 static int crypt_iv_tcw_ctr(struct crypt_config *cc, struct dm_target *ti,
@@ -761,8 +771,7 @@ static void crypt_iv_elephant_dtr(struct crypt_config *cc)
 {
 	struct iv_elephant_private *elephant = &cc->iv_gen_private.elephant;
 
-	kfree_sensitive(elephant->key);
-	elephant->key = NULL;
+	kfree_sensitive(xchg(&elephant->key, NULL));
 }
 
 static int crypt_iv_elephant_ctr(struct crypt_config *cc, struct dm_target *ti,
@@ -2547,8 +2556,7 @@ static int crypt_set_keyring_key(struct crypt_config *cc, const char *key_string
 		goto free_new_key_string;
 
 	set_bit(DM_CRYPT_KEY_VALID, &cc->flags);
-	kfree_sensitive(cc->key_string);
-	cc->key_string = new_key_string;
+	kfree_sensitive(xchg(&cc->key_string, new_key_string));
 	return 0;
 
 free_new_key_string:
@@ -2612,8 +2620,7 @@ static int crypt_set_key(struct crypt_config *cc, char *key)
 	clear_bit(DM_CRYPT_KEY_VALID, &cc->flags);
 
 	/* wipe references to any kernel keyring key */
-	kfree_sensitive(cc->key_string);
-	cc->key_string = NULL;
+	kfree_sensitive(xchg(&cc->key_string, NULL));
 
 	/* Decode key from its hex representation. */
 	if (cc->key_size && hex2bin(cc->key, key, cc->key_size) < 0)
@@ -2641,14 +2648,97 @@ static int crypt_wipe_key(struct crypt_config *cc)
 	if (cc->iv_gen_ops && cc->iv_gen_ops->wipe)
 		cc->iv_gen_ops->wipe(cc);
 
-	kfree_sensitive(cc->key_string);
-	cc->key_string = NULL;
+	kfree_sensitive(xchg(&cc->key_string, NULL));
 	r = crypt_setkey(cc);
 	memset(&cc->key, 0, cc->key_size * sizeof(u8));
 
 	return r;
 }
 
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+static void crypt_crash_wipe_iv(struct crypt_config *cc)
+{
+	if (cc->iv_gen_ops == &crypt_iv_lmk_ops) {
+		struct iv_lmk_private *lmk = &cc->iv_gen_private.lmk;
+
+		if (lmk->seed)
+			crash_wipe_memzero(lmk->seed, LMK_SEED_SIZE);
+	} else if (cc->iv_gen_ops == &crypt_iv_tcw_ops) {
+		struct iv_tcw_private *tcw = &cc->iv_gen_private.tcw;
+
+		if (tcw->iv_seed)
+			crash_wipe_memzero(tcw->iv_seed, cc->iv_size);
+		if (tcw->whitening)
+			crash_wipe_memzero(tcw->whitening, TCW_WHITENING_SIZE);
+	} else if (cc->iv_gen_ops == &crypt_iv_elephant_ops) {
+		struct iv_elephant_private *elephant =
+			&cc->iv_gen_private.elephant;
+
+		if (elephant->key)
+			crash_wipe_memzero(elephant->key,
+					   sizeof(*elephant->key));
+	}
+}
+
+static int crypt_crash_wipe(struct notifier_block *nb, unsigned long action,
+			    void *data)
+{
+	struct crypt_config *cc;
+
+	/* No locking: all other CPUs are stopped, and a cc is unlinked before
+	 * it is freed, so the forward walk can't reach freed memory.
+	 */
+	list_for_each_entry(cc, &dm_crypt_wipe_list, wipe_list) {
+		crash_wipe_memzero(cc->key, cc->key_size);
+
+		if (cc->authenc_key)
+			crash_wipe_memzero(cc->authenc_key,
+					   crypt_authenckey_size(cc));
+
+		if (cc->key_string)
+			crash_wipe_memzero(cc->key_string,
+					   strlen(cc->key_string));
+
+		crypt_crash_wipe_iv(cc);
+	}
+
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block crypt_crash_wipe_nb = {
+	.notifier_call = crypt_crash_wipe
+};
+
+static void crypt_track_cc(struct crypt_config *cc)
+{
+	spin_lock(&dm_crypt_wipe_list_lock);
+	list_add(&cc->wipe_list, &dm_crypt_wipe_list);
+	spin_unlock(&dm_crypt_wipe_list_lock);
+}
+
+static void crypt_untrack_cc(struct crypt_config *cc)
+{
+	spin_lock(&dm_crypt_wipe_list_lock);
+	list_del(&cc->wipe_list);
+	spin_unlock(&dm_crypt_wipe_list_lock);
+}
+
+static void crypt_crash_wipe_init(void)
+{
+	crash_wipe_secrets_register(&crypt_crash_wipe_nb);
+}
+
+static void crypt_crash_wipe_exit(void)
+{
+	crash_wipe_secrets_unregister(&crypt_crash_wipe_nb);
+}
+#else
+static void crypt_track_cc(struct crypt_config *cc) { }
+static void crypt_untrack_cc(struct crypt_config *cc) { }
+static void crypt_crash_wipe_init(void) { }
+static void crypt_crash_wipe_exit(void) { }
+#endif /* CONFIG_CRASH_WIPE_SECRETS */
+
 static void crypt_calculate_pages_per_client(void)
 {
 	unsigned long pages = (totalram_pages() - totalhigh_pages()) * DM_CRYPT_MEMORY_PERCENT / 100;
@@ -2729,12 +2819,15 @@ static void crypt_dtr(struct dm_target *ti)
 		dm_put_device(ti, cc->dev);
 
 	kfree_sensitive(cc->cipher_string);
-	kfree_sensitive(cc->key_string);
+	kfree_sensitive(xchg(&cc->key_string, NULL));
 	kfree_sensitive(cc->cipher_auth);
-	kfree_sensitive(cc->authenc_key);
+	kfree_sensitive(xchg(&cc->authenc_key, NULL));
 
 	mutex_destroy(&cc->bio_alloc_lock);
 
+	memzero_explicit(cc->key, cc->key_size);
+	crypt_untrack_cc(cc);
+
 	/* Must zero key material before freeing */
 	kfree_sensitive(cc);
 
@@ -3210,6 +3303,8 @@ static int crypt_ctr(struct dm_target *ti, unsigned int argc, char **argv)
 
 	ti->private = cc;
 
+	crypt_track_cc(cc);
+
 	spin_lock(&dm_crypt_clients_lock);
 	dm_crypt_clients_n++;
 	crypt_calculate_pages_per_client();
@@ -3716,7 +3811,29 @@ static struct target_type crypt_target = {
 	.iterate_devices = crypt_iterate_devices,
 	.io_hints = crypt_io_hints,
 };
-module_dm(crypt);
+
+static int __init dm_crypt_init(void)
+{
+	int r;
+
+	crypt_crash_wipe_init();
+
+	r = dm_register_target(&crypt_target);
+	if (r) {
+		crypt_crash_wipe_exit();
+		return r;
+	}
+
+	return 0;
+}
+module_init(dm_crypt_init);
+
+static void __exit dm_crypt_exit(void)
+{
+	crypt_crash_wipe_exit();
+	dm_unregister_target(&crypt_target);
+}
+module_exit(dm_crypt_exit);
 
 MODULE_AUTHOR("Jana Saout <jana@saout.de>");
 MODULE_DESCRIPTION(DM_NAME " target for transparent encryption / decryption");
diff --git a/drivers/nvme/common/keyring.c b/drivers/nvme/common/keyring.c
index 32d16c53133be..33303f035fb1b 100644
--- a/drivers/nvme/common/keyring.c
+++ b/drivers/nvme/common/keyring.c
@@ -84,6 +84,7 @@ static struct key_type nvme_tls_psk_key_type = {
 	.instantiate    = generic_key_instantiate,
 	.revoke         = user_revoke,
 	.destroy        = user_destroy,
+	.wipe		= user_wipe,
 	.describe       = nvme_tls_psk_describe,
 	.read           = user_read,
 };
diff --git a/fs/crypto/block.c b/fs/crypto/block.c
index 5193f8ba3ee09..a2fa1469b3c40 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 74329e0953d14..95cb50e5cbcd4 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 76e28d1e00640..5ac302146eb2c 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 87fe13ccb2535..08dd0a682a8f8 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/nfs/nfs4idmap.c b/fs/nfs/nfs4idmap.c
index bc397110d977f..26f579ae2c732 100644
--- a/fs/nfs/nfs4idmap.c
+++ b/fs/nfs/nfs4idmap.c
@@ -190,6 +190,7 @@ static struct key_type key_type_id_resolver = {
 	.instantiate	= generic_key_instantiate,
 	.revoke		= user_revoke,
 	.destroy	= user_destroy,
+	.wipe		= user_wipe,
 	.describe	= user_describe,
 	.read		= user_read,
 };
@@ -412,6 +413,7 @@ static struct key_type key_type_id_resolver_legacy = {
 	.instantiate	= generic_key_instantiate,
 	.revoke		= user_revoke,
 	.destroy	= user_destroy,
+	.wipe		= user_wipe,
 	.describe	= user_describe,
 	.read		= user_read,
 	.request_key	= nfs_idmap_legacy_upcall,
diff --git a/fs/super.c b/fs/super.c
index 5feecf5d9038d..cd51e9cfe93ba 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/keys/asymmetric-subtype.h b/include/keys/asymmetric-subtype.h
index d55171f640a04..17c74070c1014 100644
--- a/include/keys/asymmetric-subtype.h
+++ b/include/keys/asymmetric-subtype.h
@@ -32,6 +32,11 @@ struct asymmetric_key_subtype {
 	/* Destroy a key of this subtype */
 	void (*destroy)(void *payload_crypto, void *payload_auth);
 
+	/* Wipe a key of this subtype without freeing it (optional).  Used by
+	 * CONFIG_CRASH_WIPE_SECRETS from the panic path.
+	 */
+	void (*wipe)(void *payload_crypto, void *payload_auth);
+
 	int (*query)(const struct kernel_pkey_params *params,
 		     struct kernel_pkey_query *info);
 
diff --git a/include/keys/user-type.h b/include/keys/user-type.h
index 386c314327899..685fa054b0ac5 100644
--- a/include/keys/user-type.h
+++ b/include/keys/user-type.h
@@ -40,6 +40,7 @@ extern void user_free_preparse(struct key_preparsed_payload *prep);
 extern int user_update(struct key *key, struct key_preparsed_payload *prep);
 extern void user_revoke(struct key *key);
 extern void user_destroy(struct key *key);
+extern void user_wipe(struct key *key);
 extern void user_describe(const struct key *user, struct seq_file *m);
 extern long user_read(const struct key *key, char *buffer, size_t buflen);
 
diff --git a/include/linux/crash_core.h b/include/linux/crash_core.h
index bc087124cd78e..d3e4192b7e6d6 100644
--- a/include/linux/crash_core.h
+++ b/include/linux/crash_core.h
@@ -5,6 +5,8 @@
 #include <linux/linkage.h>
 #include <linux/elfcore.h>
 #include <linux/elf.h>
+#include <linux/notifier.h>
+#include <linux/string.h>
 
 struct kimage;
 
@@ -14,6 +16,26 @@ struct crash_mem {
 	struct range ranges[] __counted_by(max_nr_ranges);
 };
 
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+/*
+ * Record a range that has already been wiped, or wipe and record it in one
+ * operation.  Architectures may use the ranges to push the wipes out to
+ * memory before kexec disables the caches.
+ */
+void crash_wipe_cache_range(void *addr, size_t size);
+void crash_wipe_memzero(void *addr, size_t size);
+
+void arch_crash_wipe_range(void *addr, size_t size);
+void arch_crash_wipe_flush(void);
+#else
+static inline void crash_wipe_cache_range(void *addr, size_t size) { }
+
+static inline void crash_wipe_memzero(void *addr, size_t size)
+{
+	memzero_explicit(addr, size);
+}
+#endif
+
 #ifdef CONFIG_CRASH_DUMP
 
 int crash_shrink_memory(unsigned long new_size);
@@ -34,6 +56,21 @@ static inline void arch_kexec_protect_crashkres(void) { }
 static inline void arch_kexec_unprotect_crashkres(void) { }
 #endif
 
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+int crash_wipe_secrets_register(struct notifier_block *nb);
+int crash_wipe_secrets_unregister(struct notifier_block *nb);
+#else
+static inline int crash_wipe_secrets_register(struct notifier_block *nb)
+{
+	return 0;
+}
+
+static inline int crash_wipe_secrets_unregister(struct notifier_block *nb)
+{
+	return 0;
+}
+#endif
+
 #ifndef arch_crash_handle_hotplug_event
 static inline void arch_crash_handle_hotplug_event(struct kimage *image, void *arg) { }
 #endif
diff --git a/include/linux/crypto.h b/include/linux/crypto.h
index b7c97f1c47c92..59d6d62180fdd 100644
--- a/include/linux/crypto.h
+++ b/include/linux/crypto.h
@@ -14,6 +14,7 @@
 
 #include <linux/completion.h>
 #include <linux/errno.h>
+#include <linux/list.h>
 #include <linux/refcount_types.h>
 #include <linux/slab.h>
 #include <linux/types.h>
@@ -420,6 +421,16 @@ struct crypto_tfm {
 
 	struct crypto_alg *__crt_alg;
 
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+	/*
+	 * Link on crypto_tfm_list, used to wipe the expanded key schedule in
+	 * __crt_ctx before kdump. wipe_size covers __crt_ctx only, excluding
+	 * this struct and any frontend preceding it.
+	 */
+	struct hlist_node wipe_list;
+	size_t wipe_size;
+#endif
+
 	void *__crt_ctx[] CRYPTO_MINALIGN_ATTR;
 };
 
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 072d8cd09a0b2..7832a77908d2b 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)))
diff --git a/include/linux/key-type.h b/include/linux/key-type.h
index bb97bd3e5af4f..21e07db0c5f2d 100644
--- a/include/linux/key-type.h
+++ b/include/linux/key-type.h
@@ -122,6 +122,15 @@ struct key_type {
 	/* clear the data from a key (optional) */
 	void (*destroy)(struct key *key);
 
+	/* wipe the key material without free'ing (optional)
+	 * - used from CONFIG_CRASH_WIPE_SECRETS during panic to keep keys out
+	 *   of crash dumps
+	 * - called from the panic path with other CPUs stopped and preemption
+	 *   disabled
+	 * - must not sleep, allocate, free or take locks
+	 */
+	void (*wipe)(struct key *key);
+
 	/* describe a key */
 	void (*describe)(const struct key *key, struct seq_file *p);
 
diff --git a/include/linux/set_memory.h b/include/linux/set_memory.h
index 3030d9245f5ac..65d0fa010368d 100644
--- a/include/linux/set_memory.h
+++ b/include/linux/set_memory.h
@@ -44,7 +44,22 @@ static inline bool kernel_page_present(struct page *page)
 {
 	return true;
 }
-#else /* CONFIG_ARCH_HAS_SET_DIRECT_MAP */
+#endif /* !CONFIG_ARCH_HAS_SET_DIRECT_MAP */
+
+/*
+ * Variant of set_direct_map_default_noflush() that is safe to call from atomic
+ * context, for pages the caller knows are already mapped at page granularity.
+ * Architectures that need to split the linear map, and can therefore sleep,
+ * override this.
+ */
+#ifndef set_direct_map_default_nosplit
+static inline int set_direct_map_default_nosplit(struct page *page)
+{
+	return set_direct_map_default_noflush(page);
+}
+#endif
+
+#ifdef CONFIG_ARCH_HAS_SET_DIRECT_MAP
 /*
  * Some architectures, e.g. ARM64 can disable direct map modifications at
  * boot time. Let them overrive this query.
diff --git a/kernel/Kconfig.kexec b/kernel/Kconfig.kexec
index 15632358bcf71..1d2d273145df0 100644
--- a/kernel/Kconfig.kexec
+++ b/kernel/Kconfig.kexec
@@ -179,4 +179,20 @@ config CRASH_MAX_MEMORY_RANGES
 	  the computation behind the value provided through the
 	  /sys/kernel/crash_elfcorehdr_size attribute.
 
+config CRASH_WIPE_SECRETS
+	bool "Wipe secrets before kdump"
+	depends on CRASH_DUMP
+	help
+	  Wipe secrets (e.g. kernel keyring and memfd_secret pages) on crash or
+	  panic. This is a best effort, defense-in-depth feature: If the panic
+	  happens at a really bad time, or if copies of the secrets are present
+	  in places like on the stack, in I/O buffers, or in userspace memory
+	  not allocated through memfd_secret, they may still be leaked.
+
+	  Note that enabling this feature carries some risk of crashing the
+	  system during the wipe process if the kernel was already unstable
+	  when the panic happened.
+
+	  If unsure, say N.
+
 endmenu
diff --git a/kernel/crash_core.c b/kernel/crash_core.c
index 2b36aa9fade06..42faf8d0a4c48 100644
--- a/kernel/crash_core.c
+++ b/kernel/crash_core.c
@@ -23,6 +23,7 @@
 #include <linux/objtool.h>
 #include <linux/delay.h>
 #include <linux/panic.h>
+#include <linux/timekeeping.h>
 
 #include <asm/page.h>
 #include <asm/sections.h>
@@ -33,6 +34,56 @@
 /* Per cpu memory for storing cpu states in case of system crash. */
 note_buf_t __percpu *crash_notes;
 
+#ifdef CONFIG_CRASH_WIPE_SECRETS
+ATOMIC_NOTIFIER_HEAD(crash_wipe_secrets_notifier_list);
+
+int crash_wipe_secrets_register(struct notifier_block *nb)
+{
+	return atomic_notifier_chain_register(
+			&crash_wipe_secrets_notifier_list, nb);
+}
+EXPORT_SYMBOL_GPL(crash_wipe_secrets_register);
+
+int crash_wipe_secrets_unregister(struct notifier_block *nb)
+{
+	return atomic_notifier_chain_unregister(
+			&crash_wipe_secrets_notifier_list, nb);
+}
+EXPORT_SYMBOL_GPL(crash_wipe_secrets_unregister);
+
+/*
+ * Some kexec paths disable the data cache without first cleaning it.  Give
+ * architectures valid virtual ranges for the wiped data, then let them defer
+ * any completion barrier until all crash-wipe callbacks have run.
+ */
+void __weak arch_crash_wipe_range(void *addr, size_t size) { }
+void __weak arch_crash_wipe_flush(void) { }
+
+void crash_wipe_cache_range(void *addr, size_t size)
+{
+	if (size)
+		arch_crash_wipe_range(addr, size);
+}
+EXPORT_SYMBOL_GPL(crash_wipe_cache_range);
+
+void crash_wipe_memzero(void *addr, size_t size)
+{
+	memzero_explicit(addr, size);
+	crash_wipe_cache_range(addr, size);
+}
+EXPORT_SYMBOL_GPL(crash_wipe_memzero);
+
+static void crash_wipe_secrets(void)
+{
+	pr_info("Wiping sensitive secrets...\n");
+	atomic_notifier_call_chain(&crash_wipe_secrets_notifier_list, 0, NULL);
+	arch_crash_wipe_flush();
+	pr_info("Done wiping secrets.\n");
+}
+#else
+static inline void crash_wipe_secrets(void) { }
+#endif /* CONFIG_CRASH_WIPE_SECRETS */
+
 /* time to wait for possible DMA to finish before starting the kdump kernel
  * when a CMA reservation is used
  */
@@ -142,6 +193,7 @@ void __noclone __crash_kexec(struct pt_regs *regs)
 			crash_save_vmcoreinfo();
 			machine_crash_shutdown(&fixed_regs);
 			crash_cma_clear_pending_dma();
+			crash_wipe_secrets();
 			machine_kexec(kexec_crash_image);
 		}
 		kexec_unlock();
diff --git a/mm/secretmem.c b/mm/secretmem.c
index d29865075b6ea..db00b7d060807 100644
--- a/mm/secretmem.c
+++ b/mm/secretmem.c
@@ -13,9 +13,11 @@
 #include <linux/bitops.h>
 #include <linux/printk.h>
 #include <linux/pagemap.h>
+#include <linux/notifier.h>
 #include <linux/syscalls.h>
 #include <linux/pseudo_fs.h>
 #include <linux/secretmem.h>
+#include <linux/crash_core.h>
 #include <linux/set_memory.h>
 #include <linux/sched/signal.h>
 
@@ -187,6 +189,54 @@ static const struct inode_operations secretmem_iops = {
 
 static struct vfsmount *secretmem_mnt;
 
+/* Called far into vpanic from crash_core.c with other CPUs stopped and
+ * preemption disabled
+ */
+static int secretmem_crash_wipe(struct notifier_block *nb, unsigned long action,
+		void *data)
+{
+	struct super_block *sb;
+	struct inode *inode;
+
+	if (!secretmem_mnt)
+		return NOTIFY_DONE;
+	sb = secretmem_mnt->mnt_sb;
+
+	/* If the list was modified in the exact moment we panic'ed, it might be
+	 * in an inconsistent state that would be unsafe to iterate. If we can't
+	 * get the lock, too bad, that's all we can do here.
+	 */
+	if (!spin_trylock(&sb->s_inode_list_lock)) {
+		pr_crit("crash_wipe_secrets: can't acquire secretmem superblock lock.\n"
+			 "crash_wipe_secrets: skipping zeroizing secretmem.\n");
+		return NOTIFY_DONE;
+	}
+
+	list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
+		XA_STATE(xas, &inode->i_mapping->i_pages, 0);
+		struct folio *folio;
+
+		/* no need for locks if we're burning down the house :) */
+		xas_for_each(&xas, folio, ULONG_MAX) {
+			if (xas_retry(&xas, folio) || xa_is_value(folio))
+				continue;
+
+			/* secretmem_fault() already split the linear map */
+			set_direct_map_default_nosplit(folio_page(folio, 0));
+			folio_zero_segment(folio, 0, folio_size(folio));
+			crash_wipe_cache_range(folio_address(folio),
+					       folio_size(folio));
+		}
+	}
+	spin_unlock(&sb->s_inode_list_lock);
+	/* off to kexec()! */
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block secretmem_wipe_nb = {
+	.notifier_call = secretmem_crash_wipe
+};
+
 static struct file *secretmem_file_create(unsigned long flags)
 {
 	struct file *file;
@@ -212,6 +262,8 @@ static struct file *secretmem_file_create(unsigned long flags)
 	inode->i_mode |= S_IFREG;
 	inode->i_size = 0;
 
+	inode_sb_list_add(inode);
+
 	atomic_inc(&secretmem_users);
 
 	return file;
@@ -263,6 +315,7 @@ static int __init secretmem_init(void)
 	if (IS_ERR(secretmem_mnt))
 		return PTR_ERR(secretmem_mnt);
 
+	crash_wipe_secrets_register(&secretmem_wipe_nb);
 	return 0;
 }
 fs_initcall(secretmem_init);
diff --git a/net/rxrpc/ar-internal.h b/net/rxrpc/ar-internal.h
index 865f05fe37ab9..6466eb5a9929e 100644
--- a/net/rxrpc/ar-internal.h
+++ b/net/rxrpc/ar-internal.h
@@ -282,6 +282,11 @@ struct rxrpc_security {
 	/* Destroy the payload of a server key */
 	void (*destroy_server_key)(struct key *);
 
+	/* Wipe the payload of a server key without freeing it.  Used by
+	 * CONFIG_CRASH_WIPE_SECRETS from the panic path.
+	 */
+	void (*wipe_server_key)(struct key *);
+
 	/* Describe a server key */
 	void (*describe_server_key)(const struct key *, struct seq_file *);
 
diff --git a/net/rxrpc/key.c b/net/rxrpc/key.c
index a0aa78d892897..e612b7a8df13f 100644
--- a/net/rxrpc/key.c
+++ b/net/rxrpc/key.c
@@ -11,6 +11,7 @@
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
 #include <linux/module.h>
+#include <linux/crash_core.h>
 #include <linux/net.h>
 #include <linux/overflow.h>
 #include <linux/skbuff.h>
@@ -26,6 +27,7 @@
 static int rxrpc_preparse(struct key_preparsed_payload *);
 static void rxrpc_free_preparse(struct key_preparsed_payload *);
 static void rxrpc_destroy(struct key *);
+static void rxrpc_wipe(struct key *);
 static void rxrpc_describe(const struct key *, struct seq_file *);
 static long rxrpc_read(const struct key *, char *, size_t);
 
@@ -40,6 +42,7 @@ struct key_type key_type_rxrpc = {
 	.free_preparse	= rxrpc_free_preparse,
 	.instantiate	= generic_key_instantiate,
 	.destroy	= rxrpc_destroy,
+	.wipe		= rxrpc_wipe,
 	.describe	= rxrpc_describe,
 	.read		= rxrpc_read,
 };
@@ -570,6 +573,31 @@ static void rxrpc_free_token_list(struct rxrpc_key_token *token)
 	}
 }
 
+static void rxrpc_wipe_token_list(struct rxrpc_key_token *token)
+{
+	struct rxrpc_key_token *next;
+
+	for (; token; token = next) {
+		next = token->next;
+		switch (token->security_index) {
+		case RXRPC_SECURITY_RXKAD:
+			crash_wipe_memzero(token->kad->session_key,
+					   sizeof(token->kad->session_key));
+			crash_wipe_memzero(token->kad->ticket,
+					   token->kad->ticket_len);
+			break;
+		case RXRPC_SECURITY_YFS_RXGK:
+			crash_wipe_memzero(token->rxgk->key.data,
+					   token->rxgk->key.len);
+			crash_wipe_memzero(token->rxgk->ticket.data,
+					   token->rxgk->ticket.len);
+			break;
+		default:
+			break;
+		}
+	}
+}
+
 /*
  * Clean up preparse data.
  */
@@ -586,6 +614,12 @@ static void rxrpc_destroy(struct key *key)
 	rxrpc_free_token_list(key->payload.data[0]);
 }
 
+/* wipe the key without freeing. used by CONFIG_CRASH_WIPE_SECRETS. */
+static void rxrpc_wipe(struct key *key)
+{
+	rxrpc_wipe_token_list(key->payload.data[0]);
+}
+
 /*
  * describe the rxrpc key
  */
diff --git a/net/rxrpc/rxgk.c b/net/rxrpc/rxgk.c
index 77a67ace1d248..51c428fb3a60a 100644
--- a/net/rxrpc/rxgk.c
+++ b/net/rxrpc/rxgk.c
@@ -8,6 +8,7 @@
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
 #include <linux/net.h>
+#include <linux/crash_core.h>
 #include <linux/skbuff.h>
 #include <linux/slab.h>
 #include <linux/key-type.h>
@@ -68,6 +69,15 @@ static void rxgk_destroy_server_key(struct key *key)
 	rxgk_free_server_key(&key->payload);
 }
 
+/* wipe the key without freeing. used by CONFIG_CRASH_WIPE_SECRETS. */
+static void rxgk_wipe_server_key(struct key *key)
+{
+	struct krb5_buffer *server_key = (void *)&key->payload.data[2];
+
+	if (server_key->data)
+		crash_wipe_memzero(server_key->data, server_key->len);
+}
+
 static void rxgk_describe_server_key(const struct key *key, struct seq_file *m)
 {
 	const struct krb5_enctype *krb5 = key->payload.data[0];
@@ -1338,6 +1348,7 @@ const struct rxrpc_security rxgk_yfs = {
 	.preparse_server_key		= rxgk_preparse_server_key,
 	.free_preparse_server_key	= rxgk_free_preparse_server_key,
 	.destroy_server_key		= rxgk_destroy_server_key,
+	.wipe_server_key		= rxgk_wipe_server_key,
 	.describe_server_key		= rxgk_describe_server_key,
 	.init_connection_security	= rxgk_init_connection_security,
 	.alloc_txbuf			= rxgk_alloc_txbuf,
diff --git a/net/rxrpc/rxkad.c b/net/rxrpc/rxkad.c
index ca9f0e82cb9ac..054ab67aca324 100644
--- a/net/rxrpc/rxkad.c
+++ b/net/rxrpc/rxkad.c
@@ -10,6 +10,7 @@
 #include <crypto/des.h>
 #include <kunit/visibility.h>
 #include <linux/export.h>
+#include <linux/crash_core.h>
 #include <linux/fips.h>
 #include <linux/module.h>
 #include <linux/net.h>
@@ -91,6 +92,18 @@ static void rxkad_destroy_server_key(struct key *key)
 	key->payload.data[0] = NULL;
 }
 
+/* wipe the key without freeing. used by CONFIG_CRASH_WIPE_SECRETS. */
+static void rxkad_wipe_server_key(struct key *key)
+{
+	struct des_ctx *des_key = key->payload.data[0];
+
+	if (des_key)
+		crash_wipe_memzero(des_key, sizeof(*des_key));
+
+	/* the raw 8-byte key is kept inline in the payload union */
+	crash_wipe_memzero(&key->payload.data[2], 8);
+}
+
 /*
  * initialise connection security
  */
@@ -1127,6 +1140,7 @@ const struct rxrpc_security rxkad = {
 	.preparse_server_key		= rxkad_preparse_server_key,
 	.free_preparse_server_key	= rxkad_free_preparse_server_key,
 	.destroy_server_key		= rxkad_destroy_server_key,
+	.wipe_server_key		= rxkad_wipe_server_key,
 	.init_connection_security	= rxkad_init_connection_security,
 	.alloc_txbuf			= rxkad_alloc_txbuf,
 	.secure_packet			= rxkad_secure_packet,
diff --git a/net/rxrpc/server_key.c b/net/rxrpc/server_key.c
index 3efe104b1930c..a8e736d587935 100644
--- a/net/rxrpc/server_key.c
+++ b/net/rxrpc/server_key.c
@@ -26,6 +26,7 @@ static int rxrpc_vet_description_s(const char *);
 static int rxrpc_preparse_s(struct key_preparsed_payload *);
 static void rxrpc_free_preparse_s(struct key_preparsed_payload *);
 static void rxrpc_destroy_s(struct key *);
+static void rxrpc_wipe_s(struct key *);
 static void rxrpc_describe_s(const struct key *, struct seq_file *);
 
 /*
@@ -40,6 +41,7 @@ struct key_type key_type_rxrpc_s = {
 	.free_preparse	= rxrpc_free_preparse_s,
 	.instantiate	= generic_key_instantiate,
 	.destroy	= rxrpc_destroy_s,
+	.wipe		= rxrpc_wipe_s,
 	.describe	= rxrpc_describe_s,
 };
 
@@ -105,6 +107,15 @@ static void rxrpc_destroy_s(struct key *key)
 		sec->destroy_server_key(key);
 }
 
+/* wipe the key without freeing. used by CONFIG_CRASH_WIPE_SECRETS. */
+static void rxrpc_wipe_s(struct key *key)
+{
+	const struct rxrpc_security *sec = key->payload.data[1];
+
+	if (sec && sec->wipe_server_key)
+		sec->wipe_server_key(key);
+}
+
 static void rxrpc_describe_s(const struct key *key, struct seq_file *m)
 {
 	const struct rxrpc_security *sec = key->payload.data[1];
diff --git a/security/keys/big_key.c b/security/keys/big_key.c
index 268f702df380e..c01a212bec903 100644
--- a/security/keys/big_key.c
+++ b/security/keys/big_key.c
@@ -8,6 +8,7 @@
 
 #define pr_fmt(fmt) "big_key: "fmt
 #include <linux/init.h>
+#include <linux/crash_core.h>
 #include <linux/seq_file.h>
 #include <linux/file.h>
 #include <linux/shmem_fs.h>
@@ -35,6 +36,8 @@ struct big_key_payload {
  */
 #define BIG_KEY_FILE_THRESHOLD (sizeof(struct inode) + sizeof(struct dentry))
 
+static void big_key_wipe(struct key *key);
+
 /*
  * big_key defined keys take an arbitrary string as the description and an
  * arbitrary blob of data as the payload
@@ -46,6 +49,7 @@ struct key_type key_type_big_key = {
 	.instantiate		= generic_key_instantiate,
 	.revoke			= big_key_revoke,
 	.destroy		= big_key_destroy,
+	.wipe			= big_key_wipe,
 	.describe		= big_key_describe,
 	.read			= big_key_read,
 	.update			= big_key_update,
@@ -279,6 +283,19 @@ long big_key_read(const struct key *key, char *buffer, size_t buflen)
 	return ret;
 }
 
+static void big_key_wipe(struct key *key)
+{
+	struct big_key_payload *payload = to_big_key_payload(key->payload);
+
+	if (payload->data) {
+		if (payload->length > BIG_KEY_FILE_THRESHOLD)
+			crash_wipe_memzero(payload->data,
+					   CHACHA20POLY1305_KEY_SIZE);
+		else
+			crash_wipe_memzero(payload->data, payload->length);
+	}
+}
+
 /*
  * Register key type
  */
diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c
index 59cb77b237b36..9d6fe08ecb7a6 100644
--- a/security/keys/encrypted-keys/encrypted.c
+++ b/security/keys/encrypted-keys/encrypted.c
@@ -12,6 +12,7 @@
  */
 
 #include <linux/uaccess.h>
+#include <linux/crash_core.h>
 #include <linux/module.h>
 #include <linux/hex.h>
 #include <linux/init.h>
@@ -970,11 +971,23 @@ static void encrypted_destroy(struct key *key)
 	kfree_sensitive(key->payload.data[0]);
 }
 
+static void encrypted_wipe(struct key *key)
+{
+	struct encrypted_key_payload *epayload = key->payload.data[0];
+
+	if (!epayload)
+		return;
+
+	crash_wipe_memzero(epayload->payload_data,
+			   epayload->payload_datalen + epayload->datablob_len);
+}
+
 struct key_type key_type_encrypted = {
 	.name = "encrypted",
 	.instantiate = encrypted_instantiate,
 	.update = encrypted_update,
 	.destroy = encrypted_destroy,
+	.wipe = encrypted_wipe,
 	.describe = user_describe,
 	.read = encrypted_read,
 };
diff --git a/security/keys/key.c b/security/keys/key.c
index b34a64d81d47a..213e6f1d5d838 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -12,6 +12,7 @@
 #include <linux/slab.h>
 #include <linux/security.h>
 #include <linux/workqueue.h>
+#include <linux/crash_core.h>
 #include <linux/random.h>
 #include <linux/err.h>
 #include "internal.h"
@@ -1268,6 +1269,47 @@ void unregister_key_type(struct key_type *ktype)
 }
 EXPORT_SYMBOL(unregister_key_type);
 
+/* Called far into vpanic from crash_core.c with other CPUs stopped and
+ * preemption disabled
+ */
+static int key_crash_wipe(struct notifier_block *nb, unsigned long action,
+		void *data)
+{
+	struct rb_node *node;
+
+	/* If we can't acquire the lock, the rbtree might be in an inconsistent
+	 * state. That's all we can do then, as there's no point to waiting
+	 * at this stage.
+	 */
+	if (!spin_trylock(&key_serial_lock)) {
+		pr_crit("crash_wipe_secrets: can't acquire key_serial_lock. skipping keyrings.\n");
+		return NOTIFY_DONE;
+	}
+
+	for (node = rb_first(&key_serial_tree); node; node = rb_next(node)) {
+		struct key *key = rb_entry(node, struct key, serial_node);
+
+		/* Negatively instantiated keys have key->state < 0 and never
+		 * had a payload attached, so only wipe positive ones.
+		 */
+		if (key->type == &key_type_keyring || !key_is_positive(key))
+			continue;
+
+		/* We have a dedicated wipe callback for this since free'ing
+		 * isn't safe at this point
+		 */
+		if (key->type->wipe)
+			key->type->wipe(key);
+	}
+	spin_unlock(&key_serial_lock);
+	/* off to kexec()! */
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block key_crash_wipe_nb = {
+	.notifier_call = key_crash_wipe
+};
+
 /*
  * Initialise the key management state.
  */
@@ -1290,4 +1332,5 @@ void __init key_init(void)
 
 	rb_insert_color(&root_key_user.node,
 			&key_user_tree);
+	crash_wipe_secrets_register(&key_crash_wipe_nb);
 }
diff --git a/security/keys/trusted-keys/trusted_core.c b/security/keys/trusted-keys/trusted_core.c
index 0509d9955f2a6..7b220c6381b5f 100644
--- a/security/keys/trusted-keys/trusted_core.c
+++ b/security/keys/trusted-keys/trusted_core.c
@@ -14,6 +14,7 @@
 #include <keys/trusted_tpm.h>
 #include <keys/trusted_pkwm.h>
 #include <linux/capability.h>
+#include <linux/crash_core.h>
 #include <linux/err.h>
 #include <linux/hex.h>
 #include <linux/init.h>
@@ -325,11 +326,25 @@ static void trusted_destroy(struct key *key)
 	kfree_sensitive(key->payload.data[0]);
 }
 
+static void trusted_wipe(struct key *key)
+{
+	struct trusted_key_payload *p = key->payload.data[0];
+
+	if (!p)
+		return;
+
+	crash_wipe_memzero(p->key, sizeof(p->key));
+	crash_wipe_memzero(p->blob, sizeof(p->blob));
+	p->key_len = 0;
+	p->blob_len = 0;
+}
+
 struct key_type key_type_trusted = {
 	.name = "trusted",
 	.instantiate = trusted_instantiate,
 	.update = trusted_update,
 	.destroy = trusted_destroy,
+	.wipe = trusted_wipe,
 	.describe = user_describe,
 	.read = trusted_read,
 };
diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c
index 6f88b507f9279..f4897c19c2ddf 100644
--- a/security/keys/user_defined.c
+++ b/security/keys/user_defined.c
@@ -6,6 +6,7 @@
  */
 
 #include <linux/export.h>
+#include <linux/crash_core.h>
 #include <linux/init.h>
 #include <linux/slab.h>
 #include <linux/seq_file.h>
@@ -28,6 +29,7 @@ struct key_type key_type_user = {
 	.update			= user_update,
 	.revoke			= user_revoke,
 	.destroy		= user_destroy,
+	.wipe			= user_wipe,
 	.describe		= user_describe,
 	.read			= user_read,
 };
@@ -48,6 +50,7 @@ struct key_type key_type_logon = {
 	.update			= user_update,
 	.revoke			= user_revoke,
 	.destroy		= user_destroy,
+	.wipe			= user_wipe,
 	.describe		= user_describe,
 	.vet_description	= logon_vet_description,
 };
@@ -152,6 +155,18 @@ void user_destroy(struct key *key)
 
 EXPORT_SYMBOL_GPL(user_destroy);
 
+void user_wipe(struct key *key)
+{
+	struct user_key_payload *upayload = key->payload.data[0];
+
+	if (upayload)
+		crash_wipe_memzero(upayload->data, upayload->datalen);
+	key->payload.data[1] = NULL;
+	key->payload.data[2] = NULL;
+	key->payload.data[3] = NULL;
+}
+EXPORT_SYMBOL_GPL(user_wipe);
+
 /*
  * describe the user key
  */

41/3 2026/08/11 18:23 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 22899
							output: 245
							thoughts: 1361
						

Thoughts:
**Analyzing the Kernel Patch for Uninitialized Memory Risks**

Okay, so this patch is introducing `CONFIG_CRASH_WIPE_SECRETS`, which is all about wiping sensitive data (keys, encryption keys, secrets, etc.) before the kdump crash kernel takes over. It's adding a bunch of `.wipe` methods to key types and registering/unregistering callbacks to handle this wiping. It's also making modifications across various subsystems, including crypto, dm-crypt, fscrypt, and secretmem, to actually implement the wiping.

My initial concern is always uninitialized memory. Is this introducing any new areas where uninitialized data could be read or leaked? I'm seeing tracking added for `crypto_tfm` and `crypt_config` structures, which are then wiped on crash. Also, keys get a `wipe` method that zeroes out the payload. The wiping itself uses `crash_wipe_memzero` which calls `memzero_explicit`, then `crash_wipe_cache_range`. Crucially, these wipes are *only* called from the panic path via `crash_wipe_secrets` -> `atomic_notifier_call_chain`. This means it's happening during a crash, after the system is in an unrecoverable state. No new structures are being sent to user space, and there don't appear to be any new allocations that could be left uninitialized. There aren't any modified bounds checks or lengths that would expose uninitialized memory during normal operation. This all looks good *so far*.

Let's dig a bit deeper into those tracking functions: `crypto_track_tfm`, `crypt_track_cc`, `fscrypt_crash_wipe_sb` and `secretmem_crash_wipe`...

`crypto_track_tfm` is simply adding a `crypto_tfm` to a list:

```c
static void crypto_track_tfm(struct crypto_tfm *tfm, size_t size)
{
	tfm->wipe_size = size;

	spin_lock(&crypto_tfm_list_lock);
	hlist_add_head_rcu(&tfm->wipe_list, &crypto_tfm_list);
	spin_unlock(&crypto_tfm_list_lock);
}
```

It doesn't seem like there are uninitialized memory risks here.

Then, `crypt_track_cc`:

```c
static void crypt_track_cc(struct crypt_config *cc)
{
	spin_lock(&dm_crypt_wipe_list_lock);
	list_add(&cc->wipe_list, &dm_crypt_wipe_list);
	spin_unlock(&dm_crypt_wipe_list_lock);
}
```

This also seems safe, just adding to a list.

And `fscrypt_crash_wipe_sb`:

```c
static void fscrypt_crash_wipe_sb(struct super_block *sb)
{
...
}
```

And finally, `secretmem_crash_wipe`:

```c
static int secretmem_crash_wipe(struct notifier_block *nb, unsigned long action,
		void *data)
{
...
}
```

These functions, as the names suggest, will be wiping the memory during crash.

Based on all of this, the patch is *purely* focused on memory wiping during a crash. It doesn't seem to introduce any new structures that could be leaked to user space, nor does it appear to leave memory uninitialized during normal execution. In fact, it's explicitly zeroing memory in the panic path.

Therefore, KMSAN is not needed here.

I will, however, do a quick double check for any alterations that affect *normal* execution paths. I need to be *absolutely* sure there are no subtle changes that could introduce new risks outside of the crash path.

Looking at `crypto/api.c`, I see some changes that track the crypto transform, `crypto_tfm`. I also see `kfree` and `kfree_sensitive` replaced with  `crypto_untrack_tfm(tfm); kfree_sensitive(tfm);` which ensures proper tracking and freeing.

In `drivers/md/dm-crypt.c`, I see safer `xchg` to free and nullify keys. I'm also seeing added `memzero_explicit(cc->key, cc->key_size)` which adds an explicit memory zeroing operation and `crypt_untrack_cc(cc);` before freeing. The changes seem safe.

In `fs/crypto/keyring.c`, there's a change to `sb->s_master_keys` before freeing `


42/3 2026/08/11 18:23 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch series introduces a new feature `CONFIG_CRASH_WIPE_SECRETS` which zeroes out sensitive secrets (such as cryptographic keys, dm-crypt keys, fscrypt keys, and secretmem pages) during a kernel panic before a kdump crash kernel is started. The changes primarily involve adding `.wipe` callbacks to various key types, registering panic notifiers, and tracking allocated cryptographic contexts in lists so they can be iterated and wiped during a crash.\n\nThese changes only affect the panic/crash path and do not introduce any new structures sent to user space, nor do they modify normal execution paths in a way that would leave memory uninitialized. The modifications to normal execution paths are limited to adding/removing objects from tracking lists and using `kfree_sensitive` or `xchg` to safely clear pointers. There is no risk of uninitialized memory being exposed to user space or used in control flow. Standard KASAN and other bug detectors are sufficient to catch any potential memory corruption or use-after-free issues related to the new tracking lists.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch series introduces a new feature `CONFIG_CRASH_WIPE_SECRETS` which zeroes out sensitive secrets (such as cryptographic keys, dm-crypt keys, fscrypt keys, and secretmem pages) during a kernel panic before a kdump crash kernel is started. The changes primarily involve adding `.wipe` callbacks to various key types, registering panic notifiers, and tracking allocated cryptographic contexts in lists so they can be iterated and wiped during a crash.\n\nThese changes only affect the panic/crash path and do not introduce any new structures sent to user space, nor do they modify normal execution paths in a way that would leave memory uninitialized. The modifications to normal execution paths are limited to adding/removing objects from tracking lists and using `kfree_sensitive` or `xchg` to safely clear pointers. There is no risk of uninitialized memory being exposed to user space or used in control flow. Standard KASAN and other bug detectors are sufficient to catch any potential memory corruption or use-after-free issues related to the new tracking lists.",
  "NeedsKMSAN": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)