@@ -52545,6 +52545,106 @@ static wc_test_ret_t dilithium_param_test(int param, WC_RNG* rng)
5254552545}
5254652546#endif
5254752547
52548+ #if defined(WC_DILITHIUM_CACHE_MATRIX_A) && \
52549+ !defined(WC_DILITHIUM_FIXED_ARRAY) && \
52550+ !defined(WOLFSSL_DILITHIUM_NO_MAKE_KEY) && \
52551+ !defined(WOLFSSL_DILITHIUM_NO_SIGN) && \
52552+ !defined(WOLFSSL_DILITHIUM_NO_VERIFY)
52553+ /* Regression test for sign path matrix A cache allocation.
52554+ *
52555+ * dilithium_sign_with_seed_mu() previously stored the result of XMALLOC for
52556+ * the matrix A cache into a local variable instead of key->a. The local was
52557+ * then immediately overwritten by `a = key->a` (still NULL), so the just-
52558+ * allocated buffer was leaked and a NULL pointer was passed to
52559+ * dilithium_expand_a().
52560+ *
52561+ * This test exercises that exact code path by clearing the cache state on a
52562+ * key after make_key, then signing. The post-condition asserts that key->a
52563+ * was populated (proving the allocation made it into the key, not the local)
52564+ * and that signing produces a verifiable signature.
52565+ */
52566+ static wc_test_ret_t dilithium_sign_cache_alloc_test(int param, WC_RNG* rng)
52567+ {
52568+ wc_test_ret_t ret;
52569+ dilithium_key* key = NULL;
52570+ byte* sig = NULL;
52571+ word32 sigLen;
52572+ byte msg[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 };
52573+ int res = 0;
52574+
52575+ key = (dilithium_key*)XMALLOC(sizeof(*key), HEAP_HINT,
52576+ DYNAMIC_TYPE_TMP_BUFFER);
52577+ if (key == NULL) {
52578+ ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out);
52579+ }
52580+ /* Init before further allocations so wc_dilithium_free() in the cleanup
52581+ * path operates on a zeroed struct, not garbage cached-pointer fields. */
52582+ ret = wc_dilithium_init_ex(key, NULL, devId);
52583+ if (ret != 0)
52584+ ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out);
52585+
52586+ sig = (byte*)XMALLOC(DILITHIUM_MAX_SIG_SIZE, HEAP_HINT,
52587+ DYNAMIC_TYPE_TMP_BUFFER);
52588+ if (sig == NULL) {
52589+ ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out);
52590+ }
52591+
52592+ ret = wc_dilithium_set_level(key, param);
52593+ if (ret != 0)
52594+ ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out);
52595+
52596+ ret = wc_dilithium_make_key(key, rng);
52597+ if (ret != 0)
52598+ ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out);
52599+
52600+ /* Drop the cached matrix A so the next sign exercises the allocation
52601+ * branch in dilithium_sign_with_seed_mu(). */
52602+ XFREE(key->a, key->heap, DYNAMIC_TYPE_DILITHIUM);
52603+ key->a = NULL;
52604+ key->aSet = 0;
52605+ #ifdef WC_DILITHIUM_CACHE_PRIV_VECTORS
52606+ XFREE(key->s1, key->heap, DYNAMIC_TYPE_DILITHIUM);
52607+ key->s1 = NULL;
52608+ key->s2 = NULL;
52609+ key->t0 = NULL;
52610+ key->privVecsSet = 0;
52611+ #endif
52612+
52613+ sigLen = wc_dilithium_sig_size(key);
52614+ if (sigLen <= 0)
52615+ ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out);
52616+
52617+ ret = wc_dilithium_sign_ctx_msg(NULL, 0, msg, (word32)sizeof(msg), sig,
52618+ &sigLen, key, rng);
52619+ if (ret != 0)
52620+ ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out);
52621+
52622+ /* With the fix, signing must populate key->a (allocated buffer is owned
52623+ * by the key, not leaked to a local). Without the fix, key->a remains
52624+ * NULL because the XMALLOC result was assigned to a local variable. */
52625+ if (key->a == NULL)
52626+ ERROR_OUT(WC_TEST_RET_ENC_NC, out);
52627+ if (key->aSet != 1)
52628+ ERROR_OUT(WC_TEST_RET_ENC_NC, out);
52629+
52630+ ret = wc_dilithium_verify_ctx_msg(sig, sigLen, NULL, 0, msg,
52631+ (word32)sizeof(msg), &res, key);
52632+ if (ret != 0)
52633+ ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out);
52634+ if (res != 1)
52635+ ERROR_OUT(WC_TEST_RET_ENC_EC(res), out);
52636+
52637+ out:
52638+ if (key != NULL)
52639+ wc_dilithium_free(key);
52640+ XFREE(sig, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
52641+ XFREE(key, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
52642+ return ret;
52643+ }
52644+ #endif /* WC_DILITHIUM_CACHE_MATRIX_A && !WC_DILITHIUM_FIXED_ARRAY &&
52645+ * !WOLFSSL_DILITHIUM_NO_MAKE_KEY && !WOLFSSL_DILITHIUM_NO_SIGN &&
52646+ * !WOLFSSL_DILITHIUM_NO_VERIFY */
52647+
5254852648
5254952649#if (defined(WOLFSSL_DILITHIUM_PRIVATE_KEY) && \
5255052650 !defined(WOLFSSL_DILITHIUM_NO_SIGN)) || \
@@ -52835,6 +52935,28 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t dilithium_test(void)
5283552935#endif
5283652936#endif
5283752937
52938+ #if defined(WC_DILITHIUM_CACHE_MATRIX_A) && \
52939+ !defined(WC_DILITHIUM_FIXED_ARRAY) && \
52940+ !defined(WOLFSSL_DILITHIUM_NO_MAKE_KEY) && \
52941+ !defined(WOLFSSL_DILITHIUM_NO_SIGN) && \
52942+ !defined(WOLFSSL_DILITHIUM_NO_VERIFY)
52943+ #ifndef WOLFSSL_NO_ML_DSA_44
52944+ ret = dilithium_sign_cache_alloc_test(WC_ML_DSA_44, &rng);
52945+ if (ret != 0)
52946+ ERROR_OUT(ret, out);
52947+ #endif
52948+ #ifndef WOLFSSL_NO_ML_DSA_65
52949+ ret = dilithium_sign_cache_alloc_test(WC_ML_DSA_65, &rng);
52950+ if (ret != 0)
52951+ ERROR_OUT(ret, out);
52952+ #endif
52953+ #ifndef WOLFSSL_NO_ML_DSA_87
52954+ ret = dilithium_sign_cache_alloc_test(WC_ML_DSA_87, &rng);
52955+ if (ret != 0)
52956+ ERROR_OUT(ret, out);
52957+ #endif
52958+ #endif
52959+
5283852960#if (defined(WOLFSSL_DILITHIUM_PRIVATE_KEY) && \
5283952961 !defined(WOLFSSL_DILITHIUM_NO_SIGN)) || \
5284052962 (defined(WOLFSSL_DILITHIUM_PUBLIC_KEY) && \
0 commit comments