From f20409c6f07109722faaf74ef3fa5d8cf482e646 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 22 Jul 2026 15:20:53 +0200 Subject: [PATCH 1/3] ascon: handle empty input in wc_AsconAEAD128_DecryptUpdate Return early when inSz == 0, mirroring the other Ascon Update functions. --- wolfcrypt/src/ascon.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/wolfcrypt/src/ascon.c b/wolfcrypt/src/ascon.c index 8bd1e88d4d6..4544635c08b 100644 --- a/wolfcrypt/src/ascon.c +++ b/wolfcrypt/src/ascon.c @@ -459,6 +459,13 @@ int wc_AsconAEAD128_DecryptUpdate(wc_AsconAEAD128* a, byte* out, else if (a->op != ASCON_AEAD128_DECRYPT) return BAD_STATE_E; + /* Nothing to process. The argument check above deliberately permits + * in == NULL when inSz == 0, so return here before the XMEMCPY() calls + * below would pass a NULL source pointer (undefined behavior, flagged by + * -fsanitize=undefined). Mirrors wc_AsconHash256_Update()/SetAD(). */ + if (inSz == 0) + return 0; + /* Process leftover block */ if (a->lastBlkSz != 0) { word32 toProcess = min(ASCON_AEAD128_RATE - a->lastBlkSz, inSz); From e37da09c81eaef17a620fa4efbeb80d51f32534d Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 22 Jul 2026 15:20:53 +0200 Subject: [PATCH 2/3] rsa: fix cleanup of mp_int temporaries in probable-prime checks Initialize the mp_int temporaries as soon as they are allocated in wc_CompareDiffPQ(), _CheckProbablePrime() and wc_CheckProbablePrime_ex(), so the cleanup path handles them consistently in all cases. --- wolfcrypt/src/rsa.c | 44 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/wolfcrypt/src/rsa.c b/wolfcrypt/src/rsa.c index 096c0e50e21..a19b121acd2 100644 --- a/wolfcrypt/src/rsa.c +++ b/wolfcrypt/src/rsa.c @@ -5062,8 +5062,18 @@ static int wc_CompareDiffPQ(mp_int* p, mp_int* q, int size, int* valid) return BAD_FUNC_ARG; #ifdef WOLFSSL_SMALL_STACK - if (((c = (mp_int *)XMALLOC(sizeof(*c), NULL, DYNAMIC_TYPE_WOLF_BIGINT)) == NULL) || - ((d = (mp_int *)XMALLOC(sizeof(*d), NULL, DYNAMIC_TYPE_WOLF_BIGINT)) == NULL)) + c = (mp_int *)XMALLOC(sizeof(*c), NULL, DYNAMIC_TYPE_WOLF_BIGINT); + d = (mp_int *)XMALLOC(sizeof(*d), NULL, DYNAMIC_TYPE_WOLF_BIGINT); + /* Zero any struct that was allocated so the cleanup path's mp_clear()/ + * mp_forcezero() are safe even when the sibling allocation fails and the + * mp_init_multi() below is skipped on MEMORY_E. Without this, clearing an + * allocated-but-uninitialized mp_int reads a garbage ->used and corrupts + * the heap. */ + if (c != NULL) + XMEMSET(c, 0, sizeof(*c)); + if (d != NULL) + XMEMSET(d, 0, sizeof(*d)); + if (c == NULL || d == NULL) ret = MEMORY_E; else ret = 0; @@ -5205,8 +5215,17 @@ static int _CheckProbablePrime(mp_int* p, mp_int* q, mp_int* e, int nlen, *isPrime = MP_NO; #ifdef WOLFSSL_SMALL_STACK - if (((tmp1 = (mp_int *)XMALLOC(sizeof(*tmp1), NULL, DYNAMIC_TYPE_WOLF_BIGINT)) == NULL) || - ((tmp2 = (mp_int *)XMALLOC(sizeof(*tmp2), NULL, DYNAMIC_TYPE_WOLF_BIGINT)) == NULL)) { + tmp1 = (mp_int *)XMALLOC(sizeof(*tmp1), NULL, DYNAMIC_TYPE_WOLF_BIGINT); + tmp2 = (mp_int *)XMALLOC(sizeof(*tmp2), NULL, DYNAMIC_TYPE_WOLF_BIGINT); + /* Zero any allocated struct so the notOkay cleanup's mp_forcezero()/ + * mp_clear() are safe when the sibling allocation fails and the + * mp_init_multi() below is skipped: clearing an allocated-but- + * uninitialized mp_int reads a garbage ->used and corrupts the heap. */ + if (tmp1 != NULL) + XMEMSET(tmp1, 0, sizeof(*tmp1)); + if (tmp2 != NULL) + XMEMSET(tmp2, 0, sizeof(*tmp2)); + if (tmp1 == NULL || tmp2 == NULL) { ret = MEMORY_E; goto notOkay; } @@ -5309,9 +5328,20 @@ int wc_CheckProbablePrime_ex(const byte* pRaw, word32 pRawSz, #ifdef WOLFSSL_SMALL_STACK - if (((p = (mp_int *)XMALLOC(sizeof(*p), NULL, DYNAMIC_TYPE_RSA_BUFFER)) == NULL) || - ((q = (mp_int *)XMALLOC(sizeof(*q), NULL, DYNAMIC_TYPE_RSA_BUFFER)) == NULL) || - ((e = (mp_int *)XMALLOC(sizeof(*e), NULL, DYNAMIC_TYPE_RSA_BUFFER)) == NULL)) + p = (mp_int *)XMALLOC(sizeof(*p), NULL, DYNAMIC_TYPE_RSA_BUFFER); + q = (mp_int *)XMALLOC(sizeof(*q), NULL, DYNAMIC_TYPE_RSA_BUFFER); + e = (mp_int *)XMALLOC(sizeof(*e), NULL, DYNAMIC_TYPE_RSA_BUFFER); + /* Zero any allocated struct so the cleanup path's mp_forcezero()/ + * mp_clear() are safe when a later allocation in the chain fails and the + * mp_init_multi() below is skipped on MEMORY_E: clearing an allocated-but- + * uninitialized mp_int reads a garbage ->used and corrupts the heap. */ + if (p != NULL) + XMEMSET(p, 0, sizeof(*p)); + if (q != NULL) + XMEMSET(q, 0, sizeof(*q)); + if (e != NULL) + XMEMSET(e, 0, sizeof(*e)); + if (p == NULL || q == NULL || e == NULL) ret = MEMORY_E; else ret = 0; From aaa59d9e9823b3fe53a6ba8261cedc8090c64bbb Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 22 Jul 2026 15:20:53 +0200 Subject: [PATCH 3/3] math: guard fp_cnt_lsb/mp_cnt_lsb digit scan Return 0 when all used digits are zero instead of scanning past them. --- wolfcrypt/src/integer.c | 7 +++++++ wolfcrypt/src/tfm.c | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/wolfcrypt/src/integer.c b/wolfcrypt/src/integer.c index bcd050c117f..894a679a6f0 100644 --- a/wolfcrypt/src/integer.c +++ b/wolfcrypt/src/integer.c @@ -4590,6 +4590,13 @@ int mp_cnt_lsb(mp_int *a) /* scan lower digits until non-zero */ for (x = 0; x < a->used && a->dp[x] == 0; x++) {} + /* All used digits are zero -- a non-normalized zero that mp_iszero() above + * did not catch. There is no set bit; return before reading dp[x] past the + * used digits (which, if also zero, would spin the scan-for-1 loop below + * forever). */ + if (x == a->used) { + return 0; + } if (a->dp) q = a->dp[x]; x *= DIGIT_BIT; diff --git a/wolfcrypt/src/tfm.c b/wolfcrypt/src/tfm.c index e521200ce55..bb5440c99dc 100644 --- a/wolfcrypt/src/tfm.c +++ b/wolfcrypt/src/tfm.c @@ -4835,6 +4835,13 @@ int fp_cnt_lsb(fp_int *a) /* scan lower digits until non-zero */ for (x = 0; x < a->used && a->dp[x] == 0; x++) {} + /* All used digits are zero -- a non-normalized zero that fp_iszero() above + * did not catch. There is no set bit; return before reading dp[x] past the + * used digits (which, if also zero, would spin the scan-for-1 loop below + * forever). */ + if (x == a->used) { + return 0; + } q = a->dp[x]; x *= DIGIT_BIT;