Commit 7970e288ee for openssl.org

commit 7970e288ee43697956ae0a48ccb045155357a541
Author: Viktor Dukhovni <viktor@openssl.org>
Date:   Mon Aug 3 21:45:36 2026 +1000

    slh-dsa: cleanse temporary copies of sensitive data

    The hash functions worked on stack copies of the prehashed PK.seed context.
    During signing these absorb SK.seed and WOTS+ chain secrets, and every call
    left the final hash state on the stack.  Erasing it per call would burden the
    innermost functions, which dominate signing time.

    Give SLH_DSA_HASH_CTX a heap-allocated scratch context and use it in place of
    the stack copies.  The working state now sits in one reusable buffer, erased
    when the hash context is freed; the per-call copy is unchanged and stack use
    goes down.  For the SHA2 parameter sets the buffer is sized to double as the
    SHA-512 context of security categories 3 and 5.  The prehashed contexts are now
    freed with OPENSSL_clear_free().

    Erase the remaining temporaries holding secrets or data derived from them:
    WOTS+ and FORS secret values, Merkle tree nodes, the message digest and
    candidate FORS keys in sign and verify, the encoded message, and the caller's
    signature buffer when signing fails part way.  Cleanse a wrong-length private
    key that ossl_slh_dsa_key_fromdata() copied in before rejecting it.  The FORS
    roots buffer held one root per tree but was sized for k * a nodes; shrink it to
    k * n bytes and erase only the used length.

    Performance is unchanged within noise; the SHAKE parameter sets measure a few
    percent faster, likely because the working Keccak state now stays at one fixed
    address rather than a fresh stack copy at each recursion depth.

    Reviewed-by: Tim Hudson <tjh@openssl.org>
    Reviewed-by: Bob Beck <beck@openssl.org>
    Reviewed-by: Milan Broz <mbroz@openssl.org>
    MergeDate: Tue Aug 11 06:57:36 2026
    (Merged from https://github.com/openssl/openssl/pull/32148)

diff --git a/crypto/slh_dsa/slh_dsa.c b/crypto/slh_dsa/slh_dsa.c
index 6519e8640f..60c596c800 100644
--- a/crypto/slh_dsa/slh_dsa.c
+++ b/crypto/slh_dsa/slh_dsa.c
@@ -8,6 +8,7 @@
  */
 #include <stddef.h>
 #include <string.h>
+#include <openssl/crypto.h>
 #include <openssl/err.h>
 #include <openssl/proverr.h>
 #include "slh_dsa_local.h"
@@ -122,8 +123,13 @@ static int slh_sign_internal(SLH_DSA_HASH_CTX *hctx,
 err:
     if (!WPACKET_finish(wpkt))
         ret = 0;
+    OPENSSL_cleanse(m_digest, sizeof(m_digest));
+    OPENSSL_cleanse(pk_fors, sizeof(pk_fors));
     if (ret)
         *sig_len = sig_len_expected;
+    else
+        /* Erase any partial signature output */
+        OPENSSL_cleanse(sig, sig_len_expected);
     return ret;
 }

@@ -148,6 +154,7 @@ static int slh_verify_internal(SLH_DSA_HASH_CTX *hctx,
     const uint8_t *msg, size_t msg_len,
     const uint8_t *sig, size_t sig_len)
 {
+    int ret = 0;
     const SLH_DSA_KEY *pub = hctx->key;
     SLH_HASH_FUNC_DECLARE(pub, hashf);
     SLH_ADRS_FUNC_DECLARE(pub, adrsf);
@@ -185,7 +192,7 @@ static int slh_verify_internal(SLH_DSA_HASH_CTX *hctx,

     if (!hashf->H_MSG(hctx, r, pk_seed, pk_root, msg, msg_len,
             m_digest, sizeof(m_digest)))
-        return 0;
+        goto err;

     /*
      * Get md (the first md_len bytes of m_digest to use in
@@ -195,16 +202,20 @@ static int slh_verify_internal(SLH_DSA_HASH_CTX *hctx,
     if (!PACKET_buf_init(m_digest_rpkt, m_digest, sizeof(m_digest))
         || !PACKET_get_bytes(m_digest_rpkt, &md, md_len)
         || !get_tree_ids(m_digest_rpkt, params, &tree_id, &leaf_id))
-        return 0;
+        goto err;

     adrsf->set_tree_address(adrs, tree_id);
     adrsf->set_type_and_clear(adrs, SLH_ADRS_TYPE_FORS_TREE);
     adrsf->set_keypair_address(adrs, leaf_id);
-    return ossl_slh_fors_pk_from_sig(hctx, sig_rpkt, md, pk_seed, adrs,
-               pk_fors, sizeof(pk_fors))
+    ret = ossl_slh_fors_pk_from_sig(hctx, sig_rpkt, md, pk_seed, adrs,
+              pk_fors, sizeof(pk_fors))
         && ossl_slh_ht_verify(hctx, pk_fors, sig_rpkt, pk_seed,
             tree_id, leaf_id, pk_root)
         && PACKET_remaining(sig_rpkt) == 0;
+err:
+    OPENSSL_cleanse(m_digest, sizeof(m_digest));
+    OPENSSL_cleanse(pk_fors, sizeof(pk_fors));
+    return ret;
 }

 /**
@@ -292,8 +303,13 @@ int ossl_slh_dsa_sign(SLH_DSA_HASH_CTX *slh_ctx,
             return 0;
     }
     ret = slh_sign_internal(slh_ctx, m, m_len, sig, siglen, sigsize, add_rand);
-    if (m != msg && m != m_tmp)
-        OPENSSL_free(m);
+    /* The encoded message may contain confidential message content */
+    if (m != msg) {
+        if (m != m_tmp)
+            OPENSSL_clear_free(m, m_len);
+        else
+            OPENSSL_cleanse(m_tmp, sizeof(m_tmp));
+    }
     return ret;
 }

@@ -317,8 +333,13 @@ int ossl_slh_dsa_verify(SLH_DSA_HASH_CTX *slh_ctx,
         return 0;

     ret = slh_verify_internal(slh_ctx, m, m_len, sig, sig_len);
-    if (m != msg && m != m_tmp)
-        OPENSSL_free(m);
+    /* The encoded message may contain confidential message content */
+    if (m != msg) {
+        if (m != m_tmp)
+            OPENSSL_clear_free(m, m_len);
+        else
+            OPENSSL_cleanse(m_tmp, sizeof(m_tmp));
+    }
     return ret;
 }

diff --git a/crypto/slh_dsa/slh_dsa_hash_ctx.c b/crypto/slh_dsa/slh_dsa_hash_ctx.c
index 0c7282af87..f13c40e81c 100644
--- a/crypto/slh_dsa/slh_dsa_hash_ctx.c
+++ b/crypto/slh_dsa/slh_dsa_hash_ctx.c
@@ -98,8 +98,9 @@ void ossl_slh_dsa_hash_ctx_free(SLH_DSA_HASH_CTX *ctx)
 {
     if (ctx == NULL)
         return;
-    OPENSSL_free(ctx->shactx);
-    OPENSSL_free(ctx->shactx_pkseed);
+    OPENSSL_clear_free(ctx->shactx, ctx->shactx_len);
+    OPENSSL_clear_free(ctx->shactx_pkseed, ctx->shactx_len);
+    OPENSSL_clear_free(ctx->scratch, ctx->scratch_len);
     EVP_MAC_CTX_free(ctx->hmac_ctx);
     OPENSSL_free(ctx);
 }
diff --git a/crypto/slh_dsa/slh_dsa_key.c b/crypto/slh_dsa/slh_dsa_key.c
index f99a00efb9..7fec917aa9 100644
--- a/crypto/slh_dsa/slh_dsa_key.c
+++ b/crypto/slh_dsa/slh_dsa_key.c
@@ -307,6 +307,12 @@ int ossl_slh_dsa_key_fromdata(SLH_DSA_KEY *key, const OSSL_PARAM *param_pub,
     key->pub = p;
     return 1;
 err:
+    /*
+     * A private key of unexpected length may have been copied into |priv|
+     * before |has_priv| was set, in which case the reset below would not
+     * erase it, so cleanse unconditionally.
+     */
+    OPENSSL_cleanse(key->priv, sizeof(key->priv));
     ossl_slh_dsa_key_reset(key);
     return 0;
 }
diff --git a/crypto/slh_dsa/slh_dsa_local.h b/crypto/slh_dsa/slh_dsa_local.h
index f11bf097cd..df024dfbc5 100644
--- a/crypto/slh_dsa/slh_dsa_local.h
+++ b/crypto/slh_dsa/slh_dsa_local.h
@@ -52,6 +52,16 @@ struct slh_dsa_hash_ctx_st {
     const SLH_DSA_KEY *key; /* This key is not owned by this object */
     void *shactx; /* A low level SHAKE object */
     void *shactx_pkseed; /* A low level SHAKE or SHA256 object with PK.seed hashed in it */
+    size_t shactx_len; /* The size of the two hash contexts above */
+    /*
+     * A working hash context used by the hash functions in place of local
+     * stack copies, so that intermediate hash states derived from secrets
+     * live in one place and are erased when this object is freed.  It is
+     * also used for the one-shot SHA-512 contexts of the security category
+     * 3 and 5 SHA2 parameter sets.  Not used concurrently.
+     */
+    void *scratch;
+    size_t scratch_len;
     EVP_MAC_CTX *hmac_ctx; /* required by SHA algorithms for PRFmsg() */
     int hmac_digest_used; /* Used for lazy init of hmac_ctx digest */
 };
diff --git a/crypto/slh_dsa/slh_fors.c b/crypto/slh_dsa/slh_fors.c
index 10335cc5df..b52777a88a 100644
--- a/crypto/slh_dsa/slh_fors.c
+++ b/crypto/slh_dsa/slh_fors.c
@@ -17,8 +17,8 @@
 /* a = 6, 8, 9, 12 or 14  - There are (2^a) merkle trees */
 #define SLH_MAX_A 9

-#define SLH_MAX_K_TIMES_A (SLH_MAX_A * SLH_MAX_K)
-#define SLH_MAX_ROOTS (SLH_MAX_K_TIMES_A * SLH_MAX_N)
+/* The FORS public key is computed from the roots of k Merkle trees */
+#define SLH_MAX_ROOTS (SLH_MAX_K * SLH_MAX_N)

 static void slh_base_2b(const uint8_t *in, uint32_t b, uint32_t *out, size_t out_len);

@@ -87,25 +87,25 @@ static int slh_fors_node(SLH_DSA_HASH_CTX *ctx, const uint8_t *sk_seed,

     if (height == 0) {
         /* Gets here for leaf nodes */
-        if (!slh_fors_sk_gen(ctx, sk_seed, pk_seed, adrs, node_id, sk, sizeof(sk)))
-            return 0;
-        adrsf->set_tree_height(adrs, 0);
-        adrsf->set_tree_index(adrs, node_id);
-        ret = key->hash_func->F(ctx, pk_seed, adrs, sk, n, node, node_len);
+        if (slh_fors_sk_gen(ctx, sk_seed, pk_seed, adrs, node_id, sk, sizeof(sk))) {
+            adrsf->set_tree_height(adrs, 0);
+            adrsf->set_tree_index(adrs, node_id);
+            ret = key->hash_func->F(ctx, pk_seed, adrs, sk, n, node, node_len);
+        }
         OPENSSL_cleanse(sk, n);
-        return ret;
     } else {
-        if (!slh_fors_node(ctx, sk_seed, pk_seed, adrs, 2 * node_id, height - 1,
-                lnode, sizeof(rnode))
-            || !slh_fors_node(ctx, sk_seed, pk_seed, adrs, 2 * node_id + 1,
-                height - 1, rnode, sizeof(rnode)))
-            return 0;
-        adrsf->set_tree_height(adrs, height);
-        adrsf->set_tree_index(adrs, node_id);
-        if (!key->hash_func->H(ctx, pk_seed, adrs, lnode, rnode, node, node_len))
-            return 0;
+        if (slh_fors_node(ctx, sk_seed, pk_seed, adrs, 2 * node_id, height - 1,
+                lnode, sizeof(lnode))
+            && slh_fors_node(ctx, sk_seed, pk_seed, adrs, 2 * node_id + 1,
+                height - 1, rnode, sizeof(rnode))) {
+            adrsf->set_tree_height(adrs, height);
+            adrsf->set_tree_index(adrs, node_id);
+            ret = key->hash_func->H(ctx, pk_seed, adrs, lnode, rnode, node, node_len);
+        }
+        OPENSSL_cleanse(lnode, sizeof(lnode));
+        OPENSSL_cleanse(rnode, sizeof(rnode));
     }
-    return 1;
+    return ret;
 }

 /**
@@ -132,6 +132,7 @@ int ossl_slh_fors_sign(SLH_DSA_HASH_CTX *ctx, const uint8_t *md,
     const uint8_t *sk_seed, const uint8_t *pk_seed,
     uint8_t *adrs, WPACKET *sig_wpkt)
 {
+    int ret = 0;
     const SLH_DSA_KEY *key = ctx->key;
     uint32_t tree_id, layer, s, tree_offset;
     uint32_t ids[SLH_MAX_K];
@@ -165,7 +166,7 @@ int ossl_slh_fors_sign(SLH_DSA_HASH_CTX *ctx, const uint8_t *md,
         if (!slh_fors_sk_gen(ctx, sk_seed, pk_seed, adrs,
                 node_id + tree_id_times_two_power_a, out, sizeof(out))
             || !WPACKET_memcpy(sig_wpkt, out, n))
-            return 0;
+            goto err;

         /*
          * Traverse from the bottom of the tree (layer = 0)
@@ -178,15 +179,18 @@ int ossl_slh_fors_sign(SLH_DSA_HASH_CTX *ctx, const uint8_t *md,
             s = node_id ^ 1; /* XOR gets the index of the other child in a binary tree */
             if (!slh_fors_node(ctx, sk_seed, pk_seed, adrs,
                     s + tree_offset, layer, out, sizeof(out)))
-                return 0;
+                goto err;
             node_id >>= 1; /* Get the parent node id */
             tree_offset >>= 1; /* Each layer up has half as many nodes */
             if (!WPACKET_memcpy(sig_wpkt, out, n))
-                return 0;
+                goto err;
         }
         tree_id_times_two_power_a += two_power_a;
     }
-    return 1;
+    ret = 1;
+err:
+    OPENSSL_cleanse(out, sizeof(out));
+    return ret;
 }

 /**
@@ -288,6 +292,8 @@ int ossl_slh_fors_pk_from_sig(SLH_DSA_HASH_CTX *ctx, PACKET *fors_sig_rpkt,
 err:
     if (!WPACKET_finish(wroot_pkt))
         ret = 0;
+    /* At most one |n| byte root per tree was written */
+    OPENSSL_cleanse(roots, k * n);
     return ret;
 }

diff --git a/crypto/slh_dsa/slh_hash.c b/crypto/slh_dsa/slh_hash.c
index 6e25371f29..d8d4a9e969 100644
--- a/crypto/slh_dsa/slh_hash.c
+++ b/crypto/slh_dsa/slh_hash.c
@@ -82,6 +82,7 @@ slh_prf_msg_shake(SLH_DSA_HASH_CTX *hctx, const uint8_t *sk_prf,
     const uint8_t *opt_rand, const uint8_t *msg, size_t msg_len,
     WPACKET *pkt)
 {
+    int ret;
     unsigned char out[SLH_MAX_N];
     const SLH_DSA_PARAMS *params = hctx->key->params;
     size_t n = params->n;
@@ -92,7 +93,9 @@ slh_prf_msg_shake(SLH_DSA_HASH_CTX *hctx, const uint8_t *sk_prf,
     ossl_sha3_absorb(sctx, opt_rand, n);
     ossl_sha3_absorb(sctx, msg, msg_len);
     ossl_sha3_squeeze(sctx, out, n);
-    return WPACKET_memcpy(pkt, out, n);
+    ret = WPACKET_memcpy(pkt, out, n);
+    OPENSSL_cleanse(out, sizeof(out));
+    return ret;
 }

 static int
@@ -101,11 +104,12 @@ slh_f_shake(SLH_DSA_HASH_CTX *hctx, const uint8_t *pk_seed, const uint8_t *adrs,
 {
     const SLH_DSA_PARAMS *params = hctx->key->params;
     size_t n = params->n;
-    KECCAK1600_CTX sctx = *((KECCAK1600_CTX *)(hctx->shactx_pkseed));
+    KECCAK1600_CTX *sctx = (KECCAK1600_CTX *)(hctx->scratch);

-    ossl_sha3_absorb(&sctx, adrs, SLH_ADRS_SIZE);
-    ossl_sha3_absorb(&sctx, m1, m1_len);
-    ossl_sha3_squeeze(&sctx, out, n);
+    *sctx = *((KECCAK1600_CTX *)(hctx->shactx_pkseed));
+    ossl_sha3_absorb(sctx, adrs, SLH_ADRS_SIZE);
+    ossl_sha3_absorb(sctx, m1, m1_len);
+    ossl_sha3_squeeze(sctx, out, n);
     return 1;
 }

@@ -116,11 +120,12 @@ slh_prf_shake(SLH_DSA_HASH_CTX *hctx,
 {
     const SLH_DSA_PARAMS *params = hctx->key->params;
     size_t n = params->n;
-    KECCAK1600_CTX sctx = *((KECCAK1600_CTX *)(hctx->shactx_pkseed));
+    KECCAK1600_CTX *sctx = (KECCAK1600_CTX *)(hctx->scratch);

-    ossl_sha3_absorb(&sctx, adrs, SLH_ADRS_SIZE);
-    ossl_sha3_absorb(&sctx, sk_seed, n);
-    ossl_sha3_squeeze(&sctx, out, n);
+    *sctx = *((KECCAK1600_CTX *)(hctx->shactx_pkseed));
+    ossl_sha3_absorb(sctx, adrs, SLH_ADRS_SIZE);
+    ossl_sha3_absorb(sctx, sk_seed, n);
+    ossl_sha3_squeeze(sctx, out, n);
     return 1;
 }

@@ -128,10 +133,11 @@ static int
 slh_h_shake(SLH_DSA_HASH_CTX *hctx, const uint8_t *pk_seed, const uint8_t *adrs,
     const uint8_t *m1, const uint8_t *m2, uint8_t *out, size_t out_len)
 {
-    KECCAK1600_CTX ctx = *((KECCAK1600_CTX *)(hctx->shactx_pkseed)), *sctx = &ctx;
+    KECCAK1600_CTX *sctx = (KECCAK1600_CTX *)(hctx->scratch);
     const SLH_DSA_PARAMS *params = hctx->key->params;
     size_t n = params->n;

+    *sctx = *((KECCAK1600_CTX *)(hctx->shactx_pkseed));
     ossl_sha3_absorb(sctx, adrs, SLH_ADRS_SIZE);
     ossl_sha3_absorb(sctx, m1, n);
     ossl_sha3_absorb(sctx, m2, n);
@@ -146,7 +152,8 @@ slh_hmsg_sha256(SLH_DSA_HASH_CTX *hctx, const uint8_t *r, const uint8_t *pk_seed
     const uint8_t *pk_root, const uint8_t *msg, size_t msg_len,
     uint8_t *out, size_t out_len)
 {
-    SHA256_CTX ctx, *sctx = &ctx;
+    int ret;
+    SHA256_CTX *sctx = (SHA256_CTX *)(hctx->scratch);
     const SLH_DSA_PARAMS *params = hctx->key->params;
     size_t m = params->m;
     size_t n = params->n;
@@ -161,8 +168,10 @@ slh_hmsg_sha256(SLH_DSA_HASH_CTX *hctx, const uint8_t *r, const uint8_t *pk_seed
     SHA256_Update(sctx, pk_seed, n);
     SHA256_Update(sctx, pk_root, n);
     SHA256_Update(sctx, msg, msg_len);
-    return SHA256_Final(seed + 2 * n, sctx)
+    ret = SHA256_Final(seed + 2 * n, sctx)
         && (PKCS1_MGF1(out, (long)m, seed, seed_len, hctx->key->md) == 0);
+    OPENSSL_cleanse(seed, sizeof(seed));
+    return ret;
 }

 static int
@@ -170,7 +179,8 @@ slh_hmsg_sha512(SLH_DSA_HASH_CTX *hctx, const uint8_t *r, const uint8_t *pk_seed
     const uint8_t *pk_root, const uint8_t *msg, size_t msg_len,
     uint8_t *out, size_t out_len)
 {
-    SHA512_CTX ctx, *sctx = &ctx;
+    int ret;
+    SHA512_CTX *sctx = (SHA512_CTX *)(hctx->scratch);
     const SLH_DSA_PARAMS *params = hctx->key->params;
     size_t m = params->m;
     size_t n = params->n;
@@ -185,8 +195,10 @@ slh_hmsg_sha512(SLH_DSA_HASH_CTX *hctx, const uint8_t *r, const uint8_t *pk_seed
     SHA512_Update(sctx, pk_seed, n);
     SHA512_Update(sctx, pk_root, n);
     SHA512_Update(sctx, msg, msg_len);
-    return SHA512_Final(seed + 2 * n, sctx)
+    ret = SHA512_Final(seed + 2 * n, sctx)
         && (PKCS1_MGF1(out, (long)m, seed, seed_len, hctx->key->md_sha512) == 0);
+    OPENSSL_cleanse(seed, sizeof(seed));
+    return ret;
 }

 static int
@@ -227,6 +239,7 @@ slh_prf_msg_sha2(SLH_DSA_HASH_CTX *hctx,
         && EVP_MAC_update(mctx, msg, msg_len) == 1
         && EVP_MAC_final(mctx, mac, NULL, sizeof(mac)) == 1
         && WPACKET_memcpy(pkt, mac, n); /* Truncate output to n bytes */
+    OPENSSL_cleanse(mac, sizeof(mac));
     return ret;
 }

@@ -235,9 +248,10 @@ slh_prf_sha256(SLH_DSA_HASH_CTX *hctx, const uint8_t *pk_seed,
     const uint8_t *sk_seed, const uint8_t *adrs,
     uint8_t *out, size_t out_len)
 {
-    SHA256_CTX ctx = *((SHA256_CTX *)hctx->shactx_pkseed), *sctx = &ctx;
+    SHA256_CTX *sctx = (SHA256_CTX *)(hctx->scratch);
     size_t n = hctx->key->params->n;

+    *sctx = *((SHA256_CTX *)hctx->shactx_pkseed);
     SHA256_Update(sctx, adrs, SLH_ADRSC_SIZE);
     SHA256_Update(sctx, sk_seed, n);
     sha256_final(sctx, out, n);
@@ -254,7 +268,7 @@ slh_wots_pk_gen_sha2(SLH_DSA_HASH_CTX *hctx,
     size_t i, j = 0, len = SLH_WOTS_LEN(n);
     uint8_t sk[SLH_MAX_N];
     SHA256_CTX *sctx = (SHA256_CTX *)(hctx->shactx_pkseed);
-    SHA256_CTX ctx;
+    SHA256_CTX *ctx = (SHA256_CTX *)(hctx->scratch);
     const SLH_ADRS_FUNC *adrsf = hctx->key->adrs_func;
     SLH_ADRS_DECLARE(sk_adrs);
     SLH_ADRS_FN_DECLARE(adrsf, set_chain_address);
@@ -268,24 +282,25 @@ slh_wots_pk_gen_sha2(SLH_DSA_HASH_CTX *hctx,
         set_chain_address(sk_adrs, (uint32_t)i);

         /* PRF */
-        ctx = *sctx;
-        SHA256_Update(&ctx, sk_adrs, SLH_ADRSC_SIZE);
-        SHA256_Update(&ctx, sk_seed, n);
-        sha256_final(&ctx, sk, n);
+        *ctx = *sctx;
+        SHA256_Update(ctx, sk_adrs, SLH_ADRSC_SIZE);
+        SHA256_Update(ctx, sk_seed, n);
+        sha256_final(ctx, sk, n);

         set_chain_address(adrs, (uint32_t)i);
         for (j = 0; j < NIBBLE_MASK; ++j) {
             set_hash_address(adrs, (uint32_t)j);
             /* F */
-            ctx = *sctx;
-            SHA256_Update(&ctx, adrs, SLH_ADRSC_SIZE);
-            SHA256_Update(&ctx, sk, n);
-            sha256_final(&ctx, sk, n);
+            *ctx = *sctx;
+            SHA256_Update(ctx, adrs, SLH_ADRSC_SIZE);
+            SHA256_Update(ctx, sk, n);
+            sha256_final(ctx, sk, n);
         }
         memcpy(pk_out, sk, n);
         pk_out += n;
     }
     ret = 1;
+    OPENSSL_cleanse(sk, sizeof(sk));
     return ret;
 }

@@ -302,7 +317,7 @@ int slh_wots_pk_gen_shake(SLH_DSA_HASH_CTX *hctx,
     SLH_ADRS_FN_DECLARE(adrsf, set_chain_address);
     SLH_ADRS_FN_DECLARE(adrsf, set_hash_address);
     KECCAK1600_CTX *sctx = (KECCAK1600_CTX *)(hctx->shactx_pkseed);
-    KECCAK1600_CTX ctx;
+    KECCAK1600_CTX *ctx = (KECCAK1600_CTX *)(hctx->scratch);

     adrsf->copy(sk_adrs, adrs);
     adrsf->set_type_and_clear(sk_adrs, SLH_ADRS_TYPE_WOTS_PRF);
@@ -312,24 +327,25 @@ int slh_wots_pk_gen_shake(SLH_DSA_HASH_CTX *hctx,
         set_chain_address(sk_adrs, (uint32_t)i);

         /* PRF */
-        ctx = *sctx;
-        ossl_sha3_absorb(&ctx, sk_adrs, SLH_ADRS_SIZE);
-        ossl_sha3_absorb(&ctx, sk_seed, n);
-        ossl_sha3_squeeze(&ctx, sk, n);
+        *ctx = *sctx;
+        ossl_sha3_absorb(ctx, sk_adrs, SLH_ADRS_SIZE);
+        ossl_sha3_absorb(ctx, sk_seed, n);
+        ossl_sha3_squeeze(ctx, sk, n);

         set_chain_address(adrs, (uint32_t)i);
         for (j = 0; j < NIBBLE_MASK; ++j) {
             set_hash_address(adrs, (uint32_t)j);
             /* F */
-            ctx = *sctx;
-            ossl_sha3_absorb(&ctx, adrs, SLH_ADRS_SIZE);
-            ossl_sha3_absorb(&ctx, sk, n);
-            ossl_sha3_squeeze(&ctx, sk, n);
+            *ctx = *sctx;
+            ossl_sha3_absorb(ctx, adrs, SLH_ADRS_SIZE);
+            ossl_sha3_absorb(ctx, sk, n);
+            ossl_sha3_squeeze(ctx, sk, n);
         }
         memcpy(pk_out, sk, n);
         pk_out += n;
     }
     ret = 1;
+    OPENSSL_cleanse(sk, sizeof(sk));
     return ret;
 }

@@ -337,8 +353,9 @@ static int
 slh_f_sha256(SLH_DSA_HASH_CTX *hctx, const uint8_t *pk_seed, const uint8_t *adrs,
     const uint8_t *m1, size_t m1_len, uint8_t *out, size_t out_len)
 {
-    SHA256_CTX ctx = *((SHA256_CTX *)hctx->shactx_pkseed), *sctx = &ctx;
+    SHA256_CTX *sctx = (SHA256_CTX *)(hctx->scratch);

+    *sctx = *((SHA256_CTX *)hctx->shactx_pkseed);
     SHA256_Update(sctx, adrs, SLH_ADRSC_SIZE);
     SHA256_Update(sctx, m1, m1_len);
     sha256_final(sctx, out, hctx->key->params->n);
@@ -349,10 +366,11 @@ static int
 slh_h_sha256(SLH_DSA_HASH_CTX *hctx, const uint8_t *pk_seed, const uint8_t *adrs,
     const uint8_t *m1, const uint8_t *m2, uint8_t *out, size_t out_len)
 {
-    SHA256_CTX ctx = *((SHA256_CTX *)hctx->shactx_pkseed), *sctx = &ctx;
+    SHA256_CTX *sctx = (SHA256_CTX *)(hctx->scratch);
     const SLH_DSA_PARAMS *prms = hctx->key->params;
     size_t n = prms->n;

+    *sctx = *((SHA256_CTX *)hctx->shactx_pkseed);
     SHA256_Update(sctx, adrs, SLH_ADRSC_SIZE);
     SHA256_Update(sctx, m1, n);
     SHA256_Update(sctx, m2, n);
@@ -364,7 +382,7 @@ static int
 slh_h_sha512(SLH_DSA_HASH_CTX *hctx, const uint8_t *pk_seed, const uint8_t *adrs,
     const uint8_t *m1, const uint8_t *m2, uint8_t *out, size_t out_len)
 {
-    SHA512_CTX ctx, *sctx = &ctx;
+    SHA512_CTX *sctx = (SHA512_CTX *)(hctx->scratch);
     const SLH_DSA_PARAMS *prms = hctx->key->params;
     size_t n = prms->n;

@@ -382,8 +400,9 @@ static int
 slh_t_sha256(SLH_DSA_HASH_CTX *hctx, const uint8_t *pk_seed, const uint8_t *adrs,
     const uint8_t *ml, size_t ml_len, uint8_t *out, size_t out_len)
 {
-    SHA256_CTX ctx = *((SHA256_CTX *)hctx->shactx_pkseed), *sctx = &ctx;
+    SHA256_CTX *sctx = (SHA256_CTX *)(hctx->scratch);

+    *sctx = *((SHA256_CTX *)hctx->shactx_pkseed);
     SHA256_Update(sctx, adrs, SLH_ADRSC_SIZE);
     SHA256_Update(sctx, ml, ml_len);
     sha256_final(sctx, out, hctx->key->params->n);
@@ -394,7 +413,7 @@ static int
 slh_t_sha512(SLH_DSA_HASH_CTX *hctx, const uint8_t *pk_seed, const uint8_t *adrs,
     const uint8_t *ml, size_t ml_len, uint8_t *out, size_t out_len)
 {
-    SHA512_CTX ctx, *sctx = &ctx;
+    SHA512_CTX *sctx = (SHA512_CTX *)(hctx->scratch);
     const SLH_DSA_PARAMS *prms = hctx->key->params;
     size_t n = prms->n;

@@ -409,19 +428,25 @@ slh_t_sha512(SLH_DSA_HASH_CTX *hctx, const uint8_t *pk_seed, const uint8_t *adrs

 static int slh_hash_shake_precache(SLH_DSA_HASH_CTX *hctx, const uint8_t *pkseed, size_t n)
 {
-    KECCAK1600_CTX *ctx = NULL, *seedctx = NULL;
+    KECCAK1600_CTX *ctx = NULL, *seedctx = NULL, *scratch = NULL;

     ctx = ossl_shake256_new();
     if (ctx == NULL)
         return 0;
     seedctx = OPENSSL_memdup(ctx, sizeof(*ctx));
-    if (seedctx == NULL) {
+    scratch = OPENSSL_malloc(sizeof(*scratch));
+    if (seedctx == NULL || scratch == NULL) {
         OPENSSL_free(ctx);
+        OPENSSL_free(seedctx);
+        OPENSSL_free(scratch);
         return 0;
     }
     ossl_sha3_absorb(seedctx, pkseed, n);
     hctx->shactx = (void *)ctx;
     hctx->shactx_pkseed = (void *)seedctx;
+    hctx->shactx_len = sizeof(*ctx);
+    hctx->scratch = (void *)scratch;
+    hctx->scratch_len = sizeof(*scratch);
     return 1;
 }

@@ -440,19 +465,38 @@ static int slh_hash_shake_dup(SLH_DSA_HASH_CTX *dst, const SLH_DSA_HASH_CTX *src
             return 0;
         }
     }
+    dst->shactx_len = src->shactx_len;
+    /* A prehashed context needs a scratch context, its content is transient */
+    if (dst->shactx_pkseed != NULL) {
+        dst->scratch = OPENSSL_malloc(sizeof(KECCAK1600_CTX));
+        if (dst->scratch == NULL)
+            return 0;
+        dst->scratch_len = sizeof(KECCAK1600_CTX);
+    } else {
+        dst->scratch = NULL;
+        dst->scratch_len = 0;
+    }
     return 1;
 }

 static int slh_hash_sha256_precache(SLH_DSA_HASH_CTX *hctx, const uint8_t *pkseed, size_t n)
 {
     SHA256_CTX *ctx = OPENSSL_zalloc(sizeof(*ctx));
+    /* The scratch context is also used as a SHA512_CTX by category 3 and 5 */
+    size_t scratch_len = sizeof(SHA512_CTX);

     if (ctx == NULL)
         return 0;
+    if ((hctx->scratch = OPENSSL_malloc(scratch_len)) == NULL) {
+        OPENSSL_free(ctx);
+        return 0;
+    }
+    hctx->scratch_len = scratch_len;
     SHA256_Init(ctx);
     SHA256_Update(ctx, pkseed, n);
     SHA256_Update(ctx, zeros, 64 - n);
     hctx->shactx_pkseed = (void *)ctx;
+    hctx->shactx_len = sizeof(*ctx);
     return 1;
 }

@@ -463,6 +507,21 @@ static int slh_hash_sha256_dup(SLH_DSA_HASH_CTX *dst, const SLH_DSA_HASH_CTX *sr
         if (dst->shactx_pkseed == NULL)
             return 0;
     }
+    /*
+     * A prehashed context needs a scratch context, its content is transient.
+     * As in slh_hash_sha256_precache() the scratch context is sized to also
+     * serve as a SHA512_CTX for security categories 3 and 5.
+     */
+    dst->shactx_len = src->shactx_len;
+    if (dst->shactx_pkseed != NULL) {
+        dst->scratch = OPENSSL_malloc(sizeof(SHA512_CTX));
+        if (dst->scratch == NULL)
+            return 0;
+        dst->scratch_len = sizeof(SHA512_CTX);
+    } else {
+        dst->scratch = NULL;
+        dst->scratch_len = 0;
+    }
     return 1;
 }

diff --git a/crypto/slh_dsa/slh_hypertree.c b/crypto/slh_dsa/slh_hypertree.c
index bc352bf5bc..781bddbc93 100644
--- a/crypto/slh_dsa/slh_hypertree.c
+++ b/crypto/slh_dsa/slh_hypertree.c
@@ -8,6 +8,7 @@
  */

 #include <string.h>
+#include <openssl/crypto.h>
 #include "slh_dsa_local.h"
 #include "slh_dsa_key.h"

@@ -33,6 +34,7 @@ int ossl_slh_ht_sign(SLH_DSA_HASH_CTX *ctx,
     const uint8_t *pk_seed,
     uint64_t tree_id, uint32_t leaf_id, WPACKET *sig_wpkt)
 {
+    int ret = 0;
     const SLH_DSA_KEY *key = ctx->key;
     SLH_ADRS_FUNC_DECLARE(key, adrsf);
     SLH_ADRS_DECLARE(adrs);
@@ -70,7 +72,7 @@ int ossl_slh_ht_sign(SLH_DSA_HASH_CTX *ctx,
         psig = WPACKET_get_curr(sig_wpkt);
         if (!ossl_slh_xmss_sign(ctx, root, sk_seed, leaf_id, pk_seed, adrs,
                 sig_wpkt))
-            return 0;
+            goto err;
         /*
          * On the last loop it skips getting the public key since it is not needed
          * to calculate another signature. If this was called it should equal
@@ -79,15 +81,18 @@ int ossl_slh_ht_sign(SLH_DSA_HASH_CTX *ctx,
         if (layer < d - 1) {
             if (!PACKET_buf_init(xmss_sig_rpkt, psig,
                     WPACKET_get_curr(sig_wpkt) - psig))
-                return 0;
+                goto err;
             if (!ossl_slh_xmss_pk_from_sig(ctx, leaf_id, xmss_sig_rpkt, root,
                     pk_seed, adrs, root, sizeof(root)))
-                return 0;
+                goto err;
             leaf_id = tree_id & mask;
             tree_id >>= hm;
         }
     }
-    return 1;
+    ret = 1;
+err:
+    OPENSSL_cleanse(root, sizeof(root));
+    return ret;
 }

 /**
@@ -108,6 +113,7 @@ int ossl_slh_ht_verify(SLH_DSA_HASH_CTX *ctx, const uint8_t *msg, PACKET *sig_pk
     const uint8_t *pk_seed, uint64_t tree_id, uint32_t leaf_id,
     const uint8_t *pk_root)
 {
+    int ret = 0;
     const SLH_DSA_KEY *key = ctx->key;
     SLH_ADRS_FUNC_DECLARE(key, adrsf);
     SLH_ADRS_DECLARE(adrs);
@@ -127,9 +133,12 @@ int ossl_slh_ht_verify(SLH_DSA_HASH_CTX *ctx, const uint8_t *msg, PACKET *sig_pk
         adrsf->set_tree_address(adrs, tree_id);
         if (!ossl_slh_xmss_pk_from_sig(ctx, leaf_id, sig_pkt, node,
                 pk_seed, adrs, node, sizeof(node)))
-            return 0;
+            goto err;
         leaf_id = tree_id & mask;
         tree_id >>= tree_height;
     }
-    return (memcmp(node, pk_root, n) == 0);
+    ret = (memcmp(node, pk_root, n) == 0);
+err:
+    OPENSSL_cleanse(node, sizeof(node));
+    return ret;
 }
diff --git a/crypto/slh_dsa/slh_wots.c b/crypto/slh_dsa/slh_wots.c
index 3b84474c41..e4e82f4407 100644
--- a/crypto/slh_dsa/slh_wots.c
+++ b/crypto/slh_dsa/slh_wots.c
@@ -158,6 +158,7 @@ int ossl_slh_wots_pk_gen(SLH_DSA_HASH_CTX *ctx,
     adrsf->copy_keypair_address(wots_pk_adrs, adrs);
     ret = hashf->T(ctx, pk_seed, wots_pk_adrs, tmp, tmp_len, pk_out, pk_out_len);
 end:
+    OPENSSL_cleanse(tmp, tmp_len);
     return ret;
 }

@@ -221,6 +222,8 @@ int ossl_slh_wots_sign(SLH_DSA_HASH_CTX *ctx, const uint8_t *msg,
     }
     ret = 1;
 err:
+    OPENSSL_cleanse(sk, sizeof(sk));
+    OPENSSL_cleanse(msg_and_csum_nibbles, sizeof(msg_and_csum_nibbles));
     return ret;
 }

@@ -288,5 +291,7 @@ int ossl_slh_wots_pk_from_sig(SLH_DSA_HASH_CTX *ctx,
 err:
     if (!WPACKET_finish(tmp_pkt))
         ret = 0;
+    OPENSSL_cleanse(tmp, sizeof(tmp));
+    OPENSSL_cleanse(msg_and_csum_nibbles, sizeof(msg_and_csum_nibbles));
     return ret;
 }
diff --git a/crypto/slh_dsa/slh_xmss.c b/crypto/slh_dsa/slh_xmss.c
index dae036c6a2..9d0b18608e 100644
--- a/crypto/slh_dsa/slh_xmss.c
+++ b/crypto/slh_dsa/slh_xmss.c
@@ -8,6 +8,7 @@
  */

 #include <string.h>
+#include <openssl/crypto.h>
 #include "slh_dsa_local.h"
 #include "slh_dsa_key.h"

@@ -39,29 +40,31 @@ int ossl_slh_xmss_node(SLH_DSA_HASH_CTX *ctx, const uint8_t *sk_seed,
 {
     const SLH_DSA_KEY *key = ctx->key;
     SLH_ADRS_FUNC_DECLARE(key, adrsf);
+    int ret = 0;

     if (h == 0) {
         /* For leaf nodes generate the public key */
         adrsf->set_type_and_clear(adrs, SLH_ADRS_TYPE_WOTS_HASH);
         adrsf->set_keypair_address(adrs, node_id);
-        if (!ossl_slh_wots_pk_gen(ctx, sk_seed, pk_seed, adrs,
+        if (ossl_slh_wots_pk_gen(ctx, sk_seed, pk_seed, adrs,
                 pk_out, pk_out_len))
-            return 0;
+            ret = 1;
     } else {
         uint8_t lnode[SLH_MAX_N], rnode[SLH_MAX_N];

-        if (!ossl_slh_xmss_node(ctx, sk_seed, 2 * node_id, h - 1, pk_seed, adrs,
+        if (ossl_slh_xmss_node(ctx, sk_seed, 2 * node_id, h - 1, pk_seed, adrs,
                 lnode, sizeof(lnode))
-            || !ossl_slh_xmss_node(ctx, sk_seed, 2 * node_id + 1, h - 1,
-                pk_seed, adrs, rnode, sizeof(rnode)))
-            return 0;
-        adrsf->set_type_and_clear(adrs, SLH_ADRS_TYPE_TREE);
-        adrsf->set_tree_height(adrs, h);
-        adrsf->set_tree_index(adrs, node_id);
-        if (!key->hash_func->H(ctx, pk_seed, adrs, lnode, rnode, pk_out, pk_out_len))
-            return 0;
+            && ossl_slh_xmss_node(ctx, sk_seed, 2 * node_id + 1, h - 1,
+                pk_seed, adrs, rnode, sizeof(rnode))) {
+            adrsf->set_type_and_clear(adrs, SLH_ADRS_TYPE_TREE);
+            adrsf->set_tree_height(adrs, h);
+            adrsf->set_tree_index(adrs, node_id);
+            ret = key->hash_func->H(ctx, pk_seed, adrs, lnode, rnode, pk_out, pk_out_len);
+        }
+        OPENSSL_cleanse(lnode, sizeof(lnode));
+        OPENSSL_cleanse(rnode, sizeof(rnode));
     }
-    return 1;
+    return ret;
 }

 /**
diff --git a/providers/implementations/keymgmt/slh_dsa_kmgmt.c b/providers/implementations/keymgmt/slh_dsa_kmgmt.c
index 766953d265..0e90796912 100644
--- a/providers/implementations/keymgmt/slh_dsa_kmgmt.c
+++ b/providers/implementations/keymgmt/slh_dsa_kmgmt.c
@@ -303,7 +303,7 @@ static int slh_dsa_fips140_pairwise_test(const SLH_DSA_KEY *key,
     uint8_t msg[16] = { 0 };
     size_t msg_len = sizeof(msg);
     uint8_t *sig = NULL;
-    size_t sig_len;
+    size_t sig_len = 0;
     OSSL_LIB_CTX *lib_ctx;
     int alloc_ctx = 0;

@@ -347,7 +347,7 @@ static int slh_dsa_fips140_pairwise_test(const SLH_DSA_KEY *key,
 err:
     if (alloc_ctx)
         ossl_slh_dsa_hash_ctx_free(ctx);
-    OPENSSL_free(sig);
+    OPENSSL_clear_free(sig, sig_len);
     OSSL_SELF_TEST_onend(st, ret);
     OSSL_SELF_TEST_free(st);
     return ret;
@@ -425,7 +425,7 @@ static void slh_dsa_gen_cleanup(void *genctx)
     if (gctx == NULL)
         return;

-    OPENSSL_cleanse(gctx->entropy, gctx->entropy_len);
+    OPENSSL_cleanse(gctx->entropy, sizeof(gctx->entropy));
     OPENSSL_free(gctx->propq);
     OPENSSL_free(gctx);
 }
diff --git a/providers/implementations/signature/slh_dsa_sig.c b/providers/implementations/signature/slh_dsa_sig.c
index fa315a7b84..a6deef7048 100644
--- a/providers/implementations/signature/slh_dsa_sig.c
+++ b/providers/implementations/signature/slh_dsa_sig.c
@@ -80,7 +80,7 @@ static void slh_dsa_freectx(void *vctx)

     ossl_slh_dsa_hash_ctx_free(ctx->hash_ctx);
     OPENSSL_free(ctx->propq);
-    OPENSSL_cleanse(ctx->add_random, ctx->add_random_len);
+    OPENSSL_cleanse(ctx->add_random, sizeof(ctx->add_random));
     OPENSSL_free(ctx);
 }