Skip to content

wolfCrypt security hardening, portability fixes, and negative test coverage#10958

Open
Frauschi wants to merge 11 commits into
wolfSSL:masterfrom
Frauschi:fenrir
Open

wolfCrypt security hardening, portability fixes, and negative test coverage#10958
Frauschi wants to merge 11 commits into
wolfSSL:masterfrom
Frauschi:fenrir

Conversation

@Frauschi

Copy link
Copy Markdown
Contributor

Summary

This branch is a batch of eleven independent fixes across wolfCrypt and several hardware ports, from a security review. They fall into four groups: fail-open/validation hardening, undefined-behavior/portability fixes, hardware-port correctness, and negative regression test coverage. Each change is a self-contained commit; there is no shared refactoring.

Validation and fail-open hardening

  • ChaCha20 rejects an unset key. wc_Chacha_Process had no key-state check, so a zero-initialized context that received only a nonce would encrypt with an all-zero, attacker-predictable key and return success. A keySet flag is now set in wc_Chacha_SetKey and checked in wc_Chacha_Process, which returns MISSING_KEY, mirroring wc_Arc4Process.
  • DH validates untrusted moduli with random-witness Miller-Rabin. wc_DhSetKey_ex loads parameters as untrusted but ran a fixed-base primality test (bases 2..19) when no RNG was supplied, which a crafted strong pseudoprime can pass. It now creates a temporary RNG so mp_prime_is_prime_ex runs with random witnesses, and falls back to the deterministic test only when no RNG can be obtained, so there is no new failure mode. Named FFDHE primes still short-circuit.
  • ECDH rejects a point-at-infinity shared secret. The non-SP path of wc_ecc_shared_secret_gen_sync copied the affine x-coordinate to the output without checking for the identity point, so a shared secret that computed to infinity was returned as an all-zero secret with a success code. It now returns ECC_INF_E, as required by SP 800-56Ar3 section 5.7.1.2.

Undefined behavior and portability

  • RSA (SE050): alignment-safe key-id read. wc_InitRsaKey_Id cast the byte id[] array to word32* and dereferenced it, an unaligned 32-bit read that faults or misreads on strict-alignment targets and violates strict aliasing. It now uses readUnalignedWord32, matching the existing wc_ecc_init_id.
  • ML-KEM: alignment-safe reads and writes. The little-endian fast paths of mlkem_cbd_eta3 (input) and mlkem_vec_compress_10_c (output ciphertext) reinterpreted caller byte buffers through word32* pointers. Both now use readUnalignedWord32 / writeUnalignedWord32, matching the neighboring mlkem_cbd_eta2 and mldsa_encode_w1_88_c.

Hardware-port correctness

  • PSoC6: bound ECDSA r/s before serialization. psoc6_ecc_verify_hash_ex wrote attacker-supplied r/s of unbounded size into a fixed 132-byte stack buffer, a pre-authentication stack overflow reachable during TLS signature verification. It now rejects any component larger than the key size, matching the generic path's wc_ecc_check_r_s_range.
  • Intel QAT: surface hardware failures. The synchronous IntelQaSymCipher never translated the cpaCySymPerformOp completion status into its return code, so a hardware failure returned success with the plaintext copy written to the ciphertext output. A non-success status now yields ASYNC_OP_E, matching the asynchronous port.
  • ESP32-C3: fix transposed register and mask. The RSA accelerator deactivation passed the register address and bit mask in the wrong argument order (with a spurious base offset), so the accelerator was never powered down and stray writes hit a bogus address. The arguments now match the activation path and the other targets.

Negative test coverage

  • wc_ecc_check_key. Added a test that imports secp224r1 public keys that are off the curve and out of coordinate range, asserting IS_POINT_E and ECC_OUT_OF_RANGE_E. This closed a mutation-survival gap: the software on-curve and range checks previously had no negative coverage in a software build.
  • Ed448 signature S-range. Added a test that signs a message and then verifies crafted signatures whose scalar S equals or exceeds the group order, including the malleability case S + L, asserting rejection. Without the range check, (R, S + L) verifies as a second valid signature for the same message.

@Frauschi Frauschi self-assigned this Jul 21, 2026
@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

gcc-arm-cortex-m4

  • FLASH: .text +128 B (+0.1%, 200,917 B / 262,144 B, total: 77% used)

gcc-arm-cortex-m4-crypto-only

  • FLASH: .text +128 B (+0.1%, 175,120 B / 262,144 B, total: 67% used)

gcc-arm-cortex-m4-dtls13

  • FLASH: .text +64 B (+0.0%, 181,092 B / 1,048,576 B, total: 17% used)

gcc-arm-cortex-m4-openssl-compat

  • FLASH: .text +128 B (+0.0%, 772,012 B / 1,048,576 B, total: 74% used)

gcc-arm-cortex-m4-pq

  • FLASH: .text +64 B (+0.0%, 280,064 B / 1,048,576 B, total: 27% used)

gcc-arm-cortex-m4-rsa-only

  • FLASH: .text +128 B (+0.0%, 326,040 B / 1,048,576 B, total: 31% used)

gcc-arm-cortex-m4-tls13

  • FLASH: .text +64 B (+0.0%, 236,743 B / 262,144 B, total: 90% used)

gcc-arm-cortex-m7

  • FLASH: .text +64 B (+0.0%, 200,853 B / 262,144 B, total: 77% used)

gcc-arm-cortex-m7-pq

  • FLASH: .text +64 B (+0.0%, 280,640 B / 1,048,576 B, total: 27% used)

gcc-arm-cortex-m7-tls13

@Frauschi
Frauschi force-pushed the fenrir branch 2 times, most recently from 1521775 to 7d339d2 Compare July 22, 2026 08:40
@Frauschi Frauschi assigned wolfSSL-Bot and unassigned Frauschi Jul 22, 2026

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #10958

Scan targets checked: wolfcrypt-bugs, wolfcrypt-port-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.

Comment thread wolfcrypt/src/dh.c
Frauschi added 11 commits July 22, 2026 13:07
psoc6_ecc_verify_hash_ex serialized the signature r and s components
into a fixed 132-byte stack buffer using mp_to_unsigned_bin without
checking their sizes. The values come from attacker-supplied ASN.1 in
DecodeECC_DSA_Sig with no magnitude cap beyond sp_int capacity, so an
oversized r or s wrote past signature_buf, a pre-authentication stack
overflow reachable during TLS signature verification. The generic path
guards this with wc_ecc_check_r_s_range, but that check is compiled out
on WOLFSSL_PSOC6_CRYPTO builds and the port function performed no r/s
validation of its own. Reject any r or s whose serialized size exceeds
the key size before writing into the buffer.

Fixes F-6778.
wc_Chacha_Process validated only its pointer arguments and then produced
keystream directly from the context state. A zero-initialized ChaCha
context, common for static or global storage, that received a nonce via
wc_Chacha_SetIV but never had wc_Chacha_SetKey called would encrypt with
an all-zero, attacker-predictable key and still return success. This is
the same fail-open class already guarded against in wc_Arc4Process.

Add a keySet flag to the ChaCha struct, set it in wc_Chacha_SetKey, and
return MISSING_KEY from wc_Chacha_Process when the key was never set.

Fixes F-6893.
The non-SP-math path of wc_ecc_shared_secret_gen_sync ran the scalar
multiplication and affine map, then copied the x-coordinate to the
output without checking whether the result was the point at infinity.
ecc_map_ex reports the infinity case by setting x, y to zero and z to
one and returning success, so a shared secret that computed to infinity
was returned as an all-zero secret with a success code. SP 800-56Ar3
5.7.1.2 requires an error and stop in that case. Add an explicit check
after the affine map that returns ECC_INF_E when the result is the
identity point.

Fixes F-6770.
wc_DhSetKey_ex loads DH parameters as untrusted and validates that the
modulus is prime, but it passed no RNG, so the check fell back to a
Miller-Rabin test using the fixed small-prime bases 2 through 19. That
test is defeatable: a composite crafted as a strong pseudoprime to those
known bases passes as prime, letting an attacker supply a composite
modulus with a smooth factorization for small-subgroup recovery of the
private exponent and shared secret.

When no RNG is supplied on the untrusted path, create a temporary RNG so
mp_prime_is_prime_ex runs with random witnesses, which such crafted
composites cannot reliably pass. Named FFDHE primes still short-circuit
the check, and builds without an RNG keep the deterministic test.

Fixes F-6776.
wc_ecc_check_key validates a public key's coordinate range, that the
point is on the curve, and its order, but the software path had no
negative coverage: the existing test only exercised a valid key and
NULL, and the off-curve case lived in the crypto-callback test, which
validates the device path rather than the software on-curve check. A
deletion of either the on-curve check or the coordinate-range checks
therefore passed the suite.

Add a test that imports secp256r1 public keys that are off the curve
and out of coordinate range, asserting IS_POINT_E and ECC_OUT_OF_RANGE_E
respectively, exercising the software validation path.

Fixes F-6620.
Ed448 verification rejects a non-canonical signature scalar S (S >= L)
per RFC 8032, and that range check is the only guard against a malleated
signature: because L times the base point is the identity, (R, S + L)
recomputes the same R and would otherwise verify. The check had no
negative coverage, so a deletion or boundary mutation passed the suite
while all canonical KAT signatures kept working.

Add a test that signs a message, then verifies crafted signatures whose
S half equals the order, exceeds it in a high or low byte, and equals
S + L, asserting BAD_FUNC_ARG, plus an in-range wrong S asserting
SIG_VERIFY_E.

Fixes F-6777.
IntelQaSymCipher ran cpaCySymPerformOp synchronously but never
translated its completion status into the return code. Only the AES-GCM
decrypt auth check, driven by verifyResult, could set an error. For
AES-CBC, AES-GCM encrypt, and 3DES-CBC a non-success status left ret at
zero, so the exit path copied the working buffer to the output and
returned success. On encrypt that buffer still holds the plaintext copy,
so a hardware failure returned success with plaintext written to the
ciphertext output.

Translate a non-success perform-op status into ASYNC_OP_E, matching the
asynchronous port, so hardware failures are surfaced instead of
returning zero with unprocessed output.

Fixes F-6623.
The ESP32-C3 deactivation path in esp_mp_hw_unlock passed its arguments
to DPORT_REG_CLR_BIT and DPORT_REG_SET_BIT in the wrong order and added
a stray DR_REG_RSA_BASE offset. The macros take (register address, bit
mask), but the code used the bit mask as part of the register address
and the register as the bit mask. As a result the RSA clock-enable and
memory power-down bits were never updated at deactivation, and reads and
writes landed at a bogus peripheral address. The accelerator was left
powered up after every bignum and RSA operation.

Pass the register address first and the bit mask second and drop the
offset, mirroring the activation path and the other targets.

Fixes F-6779.
wc_InitRsaKey_Id cast the byte array key->id to word32* and dereferenced
it to recover the SE050 key id. key->id is a byte array with no alignment
guarantee inside RsaKey, so the dereference is an unaligned 32-bit read
that faults or mis-reads on strict-alignment targets, and it violates
strict aliasing. Read the value with readUnalignedWord32 instead, which
does an alignment-safe byte copy, matching wc_ecc_init_id.

Fixes F-6624.
The little-endian large-code path of mlkem_cbd_eta3 cast the
caller-supplied byte buffer to word32* and read through it. The buffer
has no alignment guarantee, so on strict-alignment targets such as
Cortex-M3 and M4 with unaligned-access trapping enabled the read faults
or returns wrong values, and the cast violates strict aliasing. Read
each word with readUnalignedWord32, which does an alignment-safe byte
copy, matching the neighboring mlkem_cbd_eta2.

Fixes F-6781.
The little-endian large-code path of mlkem_vec_compress_10_c cast the
output byte buffer to word32* and issued five 32-bit stores through it.
That buffer is the caller-supplied ML-KEM ciphertext, which has no
alignment guarantee, so on strict-alignment targets the store bus-faults
and the cast violates strict aliasing. Write each word with
writeUnalignedWord32, which does an alignment-safe byte copy, matching
mldsa_encode_w1_88_c and the neighboring ML-KEM sampling code.

Fixes F-6782.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants