Minor bugfixes#10875
Conversation
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.
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.
curve25519.c calls curve25519_base() on the CURVED25519_X64 / aarch64 ARMASM path, but the function is only defined in assembly and had no C prototype in any header, so a strict C compiler (implicit-declaration as error) failed to build that configuration. Declare it alongside the sibling curve25519() prototype under the same guard.
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.
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.
The small (reduced-C) curve25519/ed25519 code and the Intel x64 assembly both define the same fe_/sc_/curve25519 symbols. A user_settings.h that keeps CURVE25519_SMALL/ED25519_SMALL while USE_INTEL_SPEEDUP selects the x64 assembly (as happens with --enable-usersettings, which does not run configure's implementation selection) links with duplicate symbols that are hard to diagnose. Detect the incompatible combination at compile time via a clear #error keyed on CURVED25519_X64 (the single source of truth for 'x64 25519 asm active'), so it fails fast instead. NO_CURVED25519_X64 remains the escape hatch to keep the small implementation.
There was a problem hiding this comment.
Pull request overview
This PR addresses several correctness and build-configuration issues discovered during a wolfcrypt MC/DC coverage effort, improving key validation, error propagation, strict-C build compatibility, and RNG build guards.
Changes:
- Fix big-endian Curve25519 public-key near-prime (“order-1 or higher”) screening to match the little-endian logic.
- Preserve specific DSA import errors by gating the (L,N) size check on prior parse/validation success.
- Add strict-C prototypes / compile-time guards to prevent implicit declarations and incompatible Curve25519 implementation combinations.
- Tighten RNG reseed compilation guards and add a NULL output check to
wc_GenerateSeed()to avoid backend faults.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| wolfssl/wolfcrypt/fe_operations.h | Adds a compile-time incompatibility #error for SMALL vs x64 asm builds and declares curve25519_base() for strict-C configurations. |
| wolfcrypt/src/random.c | Aligns PollAndReSeed build guard with callability and adds NULL-output validation to wc_GenerateSeed(). |
| wolfcrypt/src/dsa.c | Prevents CheckDsaLN from overwriting earlier, more-specific DSA import errors. |
| wolfcrypt/src/curve25519.c | Corrects big-endian wc_curve25519_check_public() boundary scanning to properly detect near-prime values. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Temporary fix before new tests land.
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().
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #10875
Scan targets checked: wolfcrypt-bugs, wolfcrypt-rs-bugs, wolfcrypt-src, wolfssl-bugs, wolfssl-src
Findings: 1
1 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Findings are non-blocking.
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.
6b7a6f8 to
3fed5f9
Compare
|
Jenkins retest this please (one job timed out) |
Description
A bunch of bugs surfaced during wolfcrypt mc-dc coverage campaign have been fixed.
curve25519 big-endian public-key validation.
600880a
wc_curve25519_check_public screens out weak/out-of-range X25519 public keys, and it has mirrored little-endian and big-endian code paths. The "order-1 or higher" boundary loop in the little-endian branch scans for middle bytes equal to 0xff (if (pub[i] != 0xff) break;), which is correct because the field prime p = 2²⁵⁵−19 is 0x7fff…ffed, its interior bytes are 0xff. The big-endian branch instead tested if (pub[i] != 0), so it broke out of the loop on the first non-zero byte and never actually detected the near-prime range. The fix changes that one operand to 0xff so big-endian callers get the same near-field-prime rejection as little-endian ones.
DSA import parameter error-gating.
30ceba0
In _DsaImportParamsRaw, each step (read p, primality-check p, read q, read g) is guarded by if (err == MP_OKAY), but the final (L,N) size check via CheckDsaLN ran unconditionally. So if p failed its primality check (err = DH_CHECK_PUB_E), execution still fell through to the size check, which read qSz from a q that was never actually parsed (its own read is gated on success), and unconditionally overwrote the specific DH_CHECK_PUB_E with a generic BAD_FUNC_ARG. The fix wraps the size check in the same if (err == MP_OKAY) guard, so the first and most specific error is preserved and no stale/unset q is measured.
Note: the associated hardcoded faulty test has been temporarily fixed in 8dcef49 until the final fixed test lands in #10876
curve25519_base prototype for strict-C builds
3aecf6f
On the CURVED25519_X64 (Intel x64 assembly) and aarch64 ARMASM paths, wc_curve25519_make_pub calls curve25519_base(pub, priv), but that function is provided only in assembly (fe_x25519_asm.S, armv8-curve25519) and had no C prototype in any header, unlike its sibling curve25519(), which is declared in fe_operations.h. A strict C compiler treating implicit function declarations as errors (e.g. clang 21 with -Werror=implicit-function-declaration) therefore failed to build that configuration. The fix adds WOLFSSL_LOCAL int curve25519_base(byte* q, const byte* n); next to the curve25519() prototype, guarded by the same config, so the assembly path compiles under strict C. I reproduced the exact config and confirmed the object now references the symbol.
RNG PollAndReSeed build guard.
666de7d
PollAndReSeed was compiled under #ifdef HAVE_HASHDRBG alone, but its only callers live in the !CUSTOM_RAND_GENERATE_BLOCK branch of wc_RNG_GenerateBlock, and it references wc_GenerateSeed, which isn't provided when a custom block generator replaces the seed layer. So defining HAVE_HASHDRBG together with CUSTOM_RAND_GENERATE_BLOCK compiled a function that referenced an undefined symbol, producing a link error. The fix narrows the guard to #if defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) , matching how _InitRng already guards its DRBG code so the function is compiled exactly when it can be called.
RNG wc_GenerateSeed NULL-output check.
0a0e084
The Unix wc_GenerateSeed validated its os argument for NULL but not its output buffer, so a NULL output pointer passed straight into the entropy backend. glibc's vDSO-accelerated getrandom() dereferences the destination without validating it and segfaults rather than returning an error, so a NULL output crashed the process instead of failing cleanly. The fix adds output to the NULL check at the top of the function — mirroring the convention already used in wc_RNG_GenerateBlock so the seed API returns BAD_FUNC_ARG for a NULL buffer regardless of which entropy backend is active.
compile-time guard for small-vs-asm 25519.
9721c52
The small (reduced-C) curve25519/ed25519 code and the Intel x64 assembly both define the same fe_, sc_, and curve25519 symbols, so building both yields duplicate-symbol link errors. This happens with --enable-usersettings (which bypasses configure's implementation selection) when a user_settings.h keeps CURVE25519_SMALL/ED25519_SMALL while USE_INTEL_SPEEDUP enables the x64 asm. Rather than change the build system (broad blast radius, per your steer), the fix adds an #error in fe_operations.h keyed on CURVED25519_X64 && (CURVE25519_SMALL || ED25519_SMALL). CURVED25519_X64 being the single source of truth for "x64 25519 asm active", so the conflict fails fast with a clear message, with NO_CURVED25519_X64 as the documented escape hatch. I verified it stays silent on --enable-all (no false-positive) and fires on the incompatible combination.
deregister mem-zero entries in wc_FreeDhKey
cf5c126
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().
zero wc_MakeRsaKey stack temporaries for mem-zero check
3fed5f9
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.
Testing
mc-dc campaign phase 2 PR #10876