From 24321ec7aef06f14c4f84989e372cfdce8560df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 16 Jun 2026 11:35:30 +0200 Subject: [PATCH] Even more missing ForceZero in ML-KEM / ML-DSA Ensure that all sensitive private key (derived) data is zeroed. --- wolfcrypt/src/wc_mldsa.c | 47 +++++++++++++++++++++++++++++++++++ wolfcrypt/src/wc_mlkem_poly.c | 38 ++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/wolfcrypt/src/wc_mldsa.c b/wolfcrypt/src/wc_mldsa.c index 45e247e889b..98962784958 100644 --- a/wolfcrypt/src/wc_mldsa.c +++ b/wolfcrypt/src/wc_mldsa.c @@ -3628,6 +3628,8 @@ static int mldsa_rej_bound_poly(wc_Shake* shake256, byte* seed, sword32* s, while (j < MLDSA_N); } + /* z holds the secret s1/s2 bytes. */ + ForceZero(z, sizeof(z)); return ret; #else int ret; @@ -3656,6 +3658,8 @@ static int mldsa_rej_bound_poly(wc_Shake* shake256, byte* seed, sword32* s, } } + /* z holds the secret s1/s2 bytes. */ + ForceZero(z, MLDSA_GEN_S_BYTES); WC_FREE_VAR_EX(z, NULL, DYNAMIC_TYPE_MLDSA); return ret; #endif @@ -3760,6 +3764,9 @@ static int wc_mldsa_gen_s_4_4_avx2(sword32* s[2], byte* seed) (ctr3 < MLDSA_N)); } + /* rand/state hold secret s-vector material. */ + ForceZero(rand, 4 * MLDSA_GEN_S_BLOCK_BYTES); + ForceZero(state, sizeof(word64) * 25 * 4); WC_FREE_VAR_EX(rand, NULL, DYNAMIC_TYPE_TMP_BUFFER); WC_FREE_VAR_EX(state, NULL, DYNAMIC_TYPE_TMP_BUFFER); @@ -3913,6 +3920,9 @@ static int wc_mldsa_gen_s_5_6_avx2(sword32* s[2], byte* seed) /* Create more blocks if too many rejected. */ while ((ctr0 < MLDSA_N) || (ctr1 < MLDSA_N) || (ctr2 < MLDSA_N)); + /* rand/state hold secret s-vector material. */ + ForceZero(rand, 4 * MLDSA_GEN_S_BLOCK_BYTES); + ForceZero(state, sizeof(word64) * 25 * 4); WC_FREE_VAR_EX(rand, NULL, DYNAMIC_TYPE_TMP_BUFFER); WC_FREE_VAR_EX(state, NULL, DYNAMIC_TYPE_TMP_BUFFER); @@ -4068,6 +4078,9 @@ static int wc_mldsa_gen_s_7_8_avx2(sword32* s[2], byte* seed) /* Create more blocks if too many rejected. */ while ((ctr0 < MLDSA_N) || (ctr1 < MLDSA_N) || (ctr2 < MLDSA_N)); + /* rand/state hold secret s-vector material. */ + ForceZero(rand, 4 * MLDSA_GEN_S_BLOCK_BYTES); + ForceZero(state, sizeof(word64) * 25 * 4); WC_FREE_VAR_EX(rand, NULL, DYNAMIC_TYPE_TMP_BUFFER); WC_FREE_VAR_EX(state, NULL, DYNAMIC_TYPE_TMP_BUFFER); @@ -4257,6 +4270,9 @@ static int wc_mldsa_gen_y_4_avx2(sword32* y, byte* seed, word16 kappa) wc_mldsa_decode_gamma1_17_avx2(rand + 3 * MLDSA_MAX_V, y + 3 * MLDSA_N); + /* rand/state hold the secret mask y. */ + ForceZero(rand, 4 * MLDSA_MAX_V); + ForceZero(state, sizeof(word64) * 25 * 4); WC_FREE_VAR_EX(rand, NULL, DYNAMIC_TYPE_TMP_BUFFER); WC_FREE_VAR_EX(state, NULL, DYNAMIC_TYPE_TMP_BUFFER); @@ -4337,6 +4353,9 @@ static int wc_mldsa_gen_y_5_avx2(sword32* y, byte* seed, word16 kappa, wc_mldsa_decode_gamma1_19_avx2(rand, y + 4 * MLDSA_N); } + /* rand/state hold the secret mask y. */ + ForceZero(rand, 4 * MLDSA_MAX_V); + ForceZero(state, sizeof(word64) * 25 * 4); WC_FREE_VAR_EX(rand, NULL, DYNAMIC_TYPE_TMP_BUFFER); WC_FREE_VAR_EX(state, NULL, DYNAMIC_TYPE_TMP_BUFFER); @@ -4432,6 +4451,9 @@ static int wc_mldsa_gen_y_7_avx2(sword32* y, byte* seed, word16 kappa) wc_mldsa_decode_gamma1_19_avx2(rand + 2 * MLDSA_MAX_V, y + 6 * MLDSA_N); + /* rand/state hold the secret mask y. */ + ForceZero(rand, 4 * MLDSA_MAX_V); + ForceZero(state, sizeof(word64) * 25 * 4); WC_FREE_VAR_EX(rand, NULL, DYNAMIC_TYPE_TMP_BUFFER); WC_FREE_VAR_EX(state, NULL, DYNAMIC_TYPE_TMP_BUFFER); @@ -4490,6 +4512,8 @@ static int mldsa_vec_expand_mask_c(wc_Shake* shake256, byte* seed, } } + /* v holds the secret mask y. */ + ForceZero(v, MLDSA_MAX_V); WC_FREE_VAR_EX(v, NULL, DYNAMIC_TYPE_MLDSA); return ret; } @@ -10934,6 +10958,9 @@ int wc_MlDsaKey_InitLabel(wc_MlDsaKey* key, const char* label, void* heap, int wc_MlDsaKey_SetParams(wc_MlDsaKey* key, byte level) { int ret = 0; +#if !defined(WC_MLDSA_FIXED_ARRAY) && defined(WC_MLDSA_CACHE_PRIV_VECTORS) + const wc_MlDsaParams* oldParams = NULL; +#endif /* Validate parameters. */ if (key == NULL) { @@ -10954,6 +10981,11 @@ int wc_MlDsaKey_SetParams(wc_MlDsaKey* key, byte level) } if (ret == 0) { +#if !defined(WC_MLDSA_FIXED_ARRAY) && defined(WC_MLDSA_CACHE_PRIV_VECTORS) + /* Save old params to size the cached-vector wipe below (key->params + * is about to change to the new level). */ + oldParams = key->params; +#endif /* Get the parameters for level into key. */ ret = mldsa_get_params(level, &key->params); } @@ -10966,6 +10998,11 @@ int wc_MlDsaKey_SetParams(wc_MlDsaKey* key, byte level) key->aSet = 0; #endif #ifdef WC_MLDSA_CACHE_PRIV_VECTORS + /* Cached buffer holds secret s1/s2/t0; zeroize before free. */ + if ((key->s1 != NULL) && (oldParams != NULL)) { + ForceZero(key->s1, (word32)oldParams->s1Sz + + 2U * (word32)oldParams->s2Sz); + } XFREE(key->s1, key->heap, DYNAMIC_TYPE_MLDSA); key->s1 = NULL; key->s2 = NULL; @@ -11050,6 +11087,12 @@ void wc_MlDsaKey_Free(wc_MlDsaKey* key) XFREE(key->t1, key->heap, DYNAMIC_TYPE_MLDSA); #endif #ifdef WC_MLDSA_CACHE_PRIV_VECTORS + /* Cached buffer holds secret s1/s2/t0; zeroize before free (the + * ForceZero(key) below only clears the pointer). */ + if ((key->s1 != NULL) && (key->params != NULL)) { + ForceZero(key->s1, (word32)key->params->s1Sz + + 2U * (word32)key->params->s2Sz); + } XFREE(key->s1, key->heap, DYNAMIC_TYPE_MLDSA); #endif #ifdef WC_MLDSA_CACHE_MATRIX_A @@ -11433,6 +11476,10 @@ int wc_MlDsaKey_CheckKey(wc_MlDsaKey* key) } if (key != NULL) { + /* Zeroize secret s1/s2/t0 at the front (trailing t/t1/A are public). */ + if ((s1 != NULL) && (params != NULL)) { + ForceZero(s1, (word32)params->s1Sz + 2U * (word32)params->s2Sz); + } /* Dispose of allocated memory. */ XFREE(s1, key->heap, DYNAMIC_TYPE_MLDSA); } diff --git a/wolfcrypt/src/wc_mlkem_poly.c b/wolfcrypt/src/wc_mlkem_poly.c index aa3d7835d5d..65fae43aa92 100644 --- a/wolfcrypt/src/wc_mlkem_poly.c +++ b/wolfcrypt/src/wc_mlkem_poly.c @@ -3101,6 +3101,8 @@ static int mlkem_prf(wc_Shake* shake256, byte* out, unsigned int outLen, outLen -= len; } + /* state holds secret PRF output. */ + ForceZero(state, sizeof(state)); return 0; #else int ret; @@ -3152,6 +3154,8 @@ int mlkem_kdf(const byte* seed, int seedLen, byte* out, int outLen) } XMEMCPY(out, state, outLen); + /* state holds secret KDF output. */ + ForceZero(state, sizeof(state)); return 0; } #endif @@ -3178,6 +3182,8 @@ int mlkem_kdf(const byte* seed, int seedLen, byte* out, int outLen) BlockSha3(state); XMEMCPY(out, state, outLen); + /* state holds secret KDF output. */ + ForceZero(state, sizeof(state)); return 0; } #endif @@ -4038,6 +4044,8 @@ static int mlkem_get_noise_eta1_c(MLKEM_PRF_T* prf, sword16* p, /* Sample for values in range -3..3 from 3 bits of random. */ mlkem_cbd_eta3(p, rand); } + /* rand holds secret noise. */ + ForceZero(rand, sizeof(rand)); } else #endif @@ -4050,6 +4058,8 @@ static int mlkem_get_noise_eta1_c(MLKEM_PRF_T* prf, sword16* p, /* Sample for values in range -2..2 from 2 bits of random. */ mlkem_cbd_eta2(p, rand); } + /* rand holds secret noise. */ + ForceZero(rand, sizeof(rand)); } return ret; @@ -4082,6 +4092,8 @@ static int mlkem_get_noise_eta2_c(MLKEM_PRF_T* prf, sword16* p, mlkem_cbd_eta2(p, rand); } + /* rand holds secret noise. */ + ForceZero(rand, sizeof(rand)); return ret; } @@ -4118,6 +4130,9 @@ static void mlkem_get_noise_x4_eta2_avx2(byte* rand, byte* seed, byte o) mlkem_redistribute_16_rand_avx2(state, rand + 0 * ETA2_RAND_SIZE, rand + 1 * ETA2_RAND_SIZE, rand + 2 * ETA2_RAND_SIZE, rand + 3 * ETA2_RAND_SIZE); + + /* state is secret-seeded; caller zeroizes rand. */ + ForceZero(state, sizeof(state)); } #endif @@ -4171,6 +4186,8 @@ static int mlkem_get_noise_eta2_avx2(MLKEM_PRF_T* prf, sword16* p, } mlkem_cbd_eta2_avx2(p, (byte*)state); + /* state holds secret noise. */ + ForceZero(state, sizeof(state)); return 0; } #endif @@ -4211,6 +4228,9 @@ static void mlkem_get_noise_x4_eta3_avx2(byte* rand, byte* seed) mlkem_redistribute_8_rand_avx2(state, rand + i + 0 * PRF_RAND_SZ, rand + i + 1 * PRF_RAND_SZ, rand + i + 2 * PRF_RAND_SZ, rand + i + 3 * PRF_RAND_SZ); + + /* state is secret-seeded; caller zeroizes rand. */ + ForceZero(state, sizeof(state)); } /* Get the noise/error by calculating random bytes and sampling to a binomial @@ -4249,6 +4269,8 @@ static int mlkem_get_noise_k2_avx2(MLKEM_PRF_T* prf, sword16* vec1, ret = mlkem_get_noise_eta2_avx2(prf, poly, seed); } + /* rand holds secret noise. */ + ForceZero(rand, 4 * PRF_RAND_SZ); WC_FREE_VAR_EX(rand, NULL, DYNAMIC_TYPE_TMP_BUFFER); return ret; @@ -4282,6 +4304,8 @@ static int mlkem_get_noise_k3_avx2(sword16* vec1, sword16* vec2, sword16* poly, mlkem_cbd_eta2_avx2(poly, rand + 2 * ETA2_RAND_SIZE); } + /* rand holds secret noise. */ + ForceZero(rand, sizeof(rand)); return 0; } #endif @@ -4320,6 +4344,8 @@ static int mlkem_get_noise_k4_avx2(MLKEM_PRF_T* prf, sword16* vec1, ret = mlkem_get_noise_eta2_avx2(prf, poly, seed); } + /* rand holds secret noise. */ + ForceZero(rand, sizeof(rand)); return ret; } #endif @@ -4391,6 +4417,9 @@ static void mlkem_get_noise_x3_eta3_aarch64(byte* rand, byte* seed, byte o) ETA3_RAND_SIZE - SHA3_256_BYTES); XMEMCPY(rand + 2 * ETA3_RAND_SIZE, state + 2*25, ETA3_RAND_SIZE - SHA3_256_BYTES); + + /* state is secret-seeded; caller zeroizes rand. */ + ForceZero(state, sizeof(state)); } /* Get the noise/error by calculating random bytes. @@ -4419,6 +4448,9 @@ static void mlkem_get_noise_eta3_aarch64(byte* rand, byte* seed, byte o) XMEMCPY(rand , state, SHA3_256_BYTES); BlockSha3(state); XMEMCPY(rand + SHA3_256_BYTES, state, ETA3_RAND_SIZE - SHA3_256_BYTES); + + /* state is secret-seeded; caller zeroizes rand. */ + ForceZero(state, sizeof(state)); } /* Get the noise/error by calculating random bytes and sampling to a binomial @@ -4451,6 +4483,8 @@ static int mlkem_get_noise_k2_aarch64(sword16* vec1, sword16* vec2, mlkem_cbd_eta2(poly , rand + 2 * 25 * 8); } + /* rand holds secret noise. */ + ForceZero(rand, sizeof(rand)); return ret; } #endif @@ -4511,6 +4545,8 @@ static int mlkem_get_noise_k3_aarch64(sword16* vec1, sword16* vec2, mlkem_cbd_eta2(poly , rand + 0 * 25 * 8); } + /* rand holds secret noise. */ + ForceZero(rand, sizeof(rand)); return 0; } #endif @@ -4546,6 +4582,8 @@ static int mlkem_get_noise_k4_aarch64(sword16* vec1, sword16* vec2, mlkem_cbd_eta2(poly, rand + 2 * 25 * 8); } + /* rand holds secret noise. */ + ForceZero(rand, sizeof(rand)); return ret; } #endif