```
==485951== Uninitialised value was created by a stack allocation
==485951== at 0x207D47: des3_key_wrap_test (test.c:12773)
```
and
```
==485951== Uninitialised value was created by a stack allocation
==485951== at 0x3A075E: test_wc_AesGcmArgMcdc (test_aes.c:8968)
```
The RISC-V ASM build provides its own AES-GCM implementation
(wolfcrypt/src/port/riscv/riscv-64-aes.c) rather than AES_GCM_decrypt_C, so
it does not clear the output buffer on authentication failure. Exclude it
from the zero-check, matching the other non-C decrypt paths. Fixes the
riscv64 multi-arch testwolfcrypt failure.
Skoll review of the auth-fail zero-check test in aesgcm_test:
- The guard listed WOLFSSL_ARMASM_NO_HW_CRYPTO and __aarch64__, which are
defined on default x86-64 builds, so the zero-check block was compiled out
and the assertion never actually ran there. They are subsumed by
WOLFSSL_ARMASM (the condition under which AES_GCM_decrypt_C is not the
decrypt path), so use that instead and the check runs on the C path.
- Exclude WC_AES_GCM_DEC_AUTH_EARLY (out is not written on an early-auth
failure) and WOLFSSL_ASYNC_CRYPT (a real async device may offload the
decrypt and not clear the output).
Verified: default make check passes with the zero-check now executing;
testwolfcrypt AES-GCM passes with --enable-aesni and with
-DWC_AES_GCM_DEC_AUTH_EARLY.
Review follow-ups for the constant-time AES-GCM decrypt output clear:
- Guard the output-masking pass with #ifndef WC_AES_GCM_DEC_AUTH_EARLY. In
that configuration the tag is verified before decryption and a mismatch
returns before any output is written, so the masking pass is a guaranteed
no-op; skipping it avoids a wasted O(sz) pass.
- Add a test in aesgcm_test: decrypt with a corrupted tag into a pre-filled
buffer and assert wc_AesGcmDecrypt returns AES_GCM_AUTH_E and, on the
software C path, that the output buffer is cleared to zero. The AES-NI/asm
decrypt paths and the FIPS module do not clear the output on auth failure,
so the zero check forces the C path (use_aesni = 0) and is limited to it
(and skipped under HAVE_FIPS). The AES_GCM_AUTH_E comparison uses
WC_NO_ERR_TRACE().
Verified (gcc 15.2): make check passes on the default (C path) build;
testwolfcrypt AES-GCM passes with --enable-aesni and with
-DWC_AES_GCM_DEC_AUTH_EARLY; ct-valgrind aes_gcm reports 0 errors.
AES_GCM_decrypt_C cleared the output on a tag mismatch with
'if (ret != 0) ForceZero(out, sz)'. That is a conditional branch on the
secret-dependent authentication result, which is not constant time and is
flagged by the ct-valgrind constant-time test (Conditional jump depends on
uninitialised value in AES_GCM_decrypt_C).
Mask the output with 'res' (already computed as all-ones on tag mismatch,
zero on match) instead of branching, matching the constant-time idiom used
for the tag comparison itself. C path only; the AES-NI/ASM paths are
unaffected.
linuxkm/lkcapi_aes_glue.c: zero the ephemeral ivOut in AesGcmCrypt_1().
wolfcrypt/src/port/kcapi/kcapi_aes.c: tighten the test on the return value from kcapi_aead_decrypt().
wc_ShaUpdate, wc_Sha3Update, wc_Shake128_Update and wc_Shake256_Update
guarded inputs as:
if (obj == NULL || (data == NULL && len > 0)) return BAD_FUNC_ARG;
if (data == NULL && len == 0) return 0;
The first guard rejected (data==NULL, len>0) before the second decision,
so that decision's len==0 condition could only ever be observed true --
its MC/DC independence pair was structurally unreachable.
Reorder to the same idiom sha256.c/sha512.c already use:
if (obj == NULL) return BAD_FUNC_ARG;
if (data == NULL && len == 0) return 0; /* (NULL,len>0) now reaches: len==0 false */
if (data == NULL) return BAD_FUNC_ARG;
Behavior is identical for every input; the existing DIGEST_UPDATE_TEST
cases wc_*Update(&dgst, NULL, 1) and (&dgst, NULL, 0) now exercise both
sides of the decision. Closes the four guard-ordering MC/DC residuals in
the sha campaign module (sha.c and sha3.c).
fixes longstanding bug in afalg_aes.c that made no-AAD handles non-interchangeable with AAD handles. also adds missing arg validation and KEYUSAGE_E checks throughout AF_ALG.
wc_MakeRsaKey()'s non-small-stack path declares p/q/tmp1..3 as stack
mp_ints and only mp_init's them after the argument and size checks. An
early 'goto out' from those checks reaches the WOLFSSL_CHECK_MEM_ZERO
cleanup, which calls mp_memzero_check() on the still-uninitialized structs;
the garbage size field makes the check scan an arbitrary stack range and
can false-abort on unrelated registered memory. Zero the temporaries up
front (under WOLFSSL_CHECK_MEM_ZERO) so the early-out cleanup is safe.
ELF V1 headers are need for cross-compiles.
PPC64 is big or little endian so assembly code modified to be able to be built for both.
Fix wiring of vector AES implementations in aes.c.
wc_DhImportKeyPair() registers key->priv for zero-on-free tracking via
mp_memzero_add() under WOLFSSL_CHECK_MEM_ZERO, but wc_FreeDhKey() cleared
priv with mp_forcezero(), which zeroes the data without removing the
registration. Unlike wc_FreeRsaKey(), wc_FreeDhKey() had no
wc_MemZero_Check() to remove the entry, so the registration leaked past
the DhKey's lifetime and a later, unrelated wc_MemZero_Check() over reused
stack could false-abort on it. Add wc_MemZero_Check(key, sizeof(*key)) at
the end of wc_FreeDhKey(), mirroring wc_FreeRsaKey().
wc_GenerateSeed only checked os for NULL, so a NULL output passed straight
into the entropy backend. glibc's vDSO getrandom() dereferences the buffer
without validating it and segfaults instead of returning an error. Add the
output NULL check (matching wc_RNG_GenerateBlock's convention) so the public
seed API fails cleanly with BAD_FUNC_ARG.
PollAndReSeed was compiled under #ifdef HAVE_HASHDRBG alone, but its only
callers (in wc_RNG_GenerateBlock) sit in the #else of CUSTOM_RAND_GENERATE_
BLOCK, and it references wc_GenerateSeed which is not provided when a custom
block generator replaces the seed layer. Defining HAVE_HASHDRBG together
with CUSTOM_RAND_GENERATE_BLOCK therefore produced an undefined-symbol link
error. Match _InitRng's guard so the function is compiled exactly when it
can be called.
The (L,N) size check ran unconditionally, so after an earlier failure it
overwrote the specific error (e.g. DH_CHECK_PUB_E from the p primality
check) with BAD_FUNC_ARG, and computed qSz from a q that was never read
(the q read is itself gated on err==MP_OKAY). Gate the size check the same
way as the surrounding steps so the first, most specific error is returned.
wc_curve25519_check_public's BIG_ENDIAN branch checked pub[i] != 0 in its
top-order boundary loop where the mirrored LITTLE_ENDIAN branch checks
pub[i] != 0xff. The field prime p = 2^255 - 19 has 0xff middle bytes, so
the != 0 test broke out on the first non-0xff byte and the near-prime
rejection was effectively non-functional for big-endian inputs. Match the
little-endian branch so out-of-range big-endian public keys are rejected.