Skip to content

Fix: WolfCrypt Fenrir - 12 fixes#10786

Open
aidankeefe2022 wants to merge 5 commits into
wolfSSL:masterfrom
aidankeefe2022:fenrir-fixes-jun24/26-ak
Open

Fix: WolfCrypt Fenrir - 12 fixes#10786
aidankeefe2022 wants to merge 5 commits into
wolfSSL:masterfrom
aidankeefe2022:fenrir-fixes-jun24/26-ak

Conversation

@aidankeefe2022

@aidankeefe2022 aidankeefe2022 commented Jun 25, 2026

Copy link
Copy Markdown
Member

Description

https://fenrir.wolfssl.com/finding/6145

wc_DsaVerify/wc_DsaVerify_ex leave *answer uninitialized on all error paths, unlike sibling ECC/ECCSI verify APIs that default to "not verified".

  • Initialized the answer parameter to zero so that on early exit the output parameter is defined as false.

https://fenrir.wolfssl.com/finding/5384

CAAM secure-memory addresses are truncated to 32 bits in ECC keys.

  • Changed the ecc_key fields to CAAM_ADDRESS when it is defined. This allows the address width to expand with the platform so addresses are not truncated.

https://fenrir.wolfssl.com/finding/4432

wc_DrbgState_MutexInit unsafe lazy mutex initialization without WOLFSSL_MUTEX_INITIALIZER.

  • Instead of checking a plain int, we use atomic operations when they are present to initialize the mutex. This ensures that no two threads can initialize the same mutex.

https://fenrir.wolfssl.com/finding/5392

DES key schedule branches on secret key bits.

  • Instead of an if statement, we use a mask to set bits in ks.

https://fenrir.wolfssl.com/finding/5994

Invalid free / use-after-free of embedded X509 NAME in the ESP32 cert-bundle verify callback on a lookup miss.

  • Confirmed and removed the double free in esp_crt_bundle.c.

https://fenrir.wolfssl.com/finding/4445

devcrypto wc_Sha256Copy produces a non-functional hash copy when WOLFSSL_DEVCRYPTO_HASH_KEEP is disabled.

  • Made the function match the rest of the file and return an error when a copy is not available. Also freed the destination before the copy to fix a memory leak.

https://fenrir.wolfssl.com/finding/4446

devcrypto wc_Sha256Final leaks the kernel hash session when GetDigest fails.

  • Added a free on error, which uncovered and fixed memory leaks caught by tests when devcrypto is enabled.

https://fenrir.wolfssl.com/finding/5418

FSPSM AES-GCM TLS key allocation failures return without unlocking hardware.

  • Added a mutex unlock on memory allocation failure.

https://fenrir.wolfssl.com/finding/5420

FSPSM hash Final/GetHash silently succeeds when hardware hash initialization fails.

  • Set ret to an error value so that on return the error is no longer silent.

https://fenrir.wolfssl.com/finding/5411

SipHash assembly paths load the caller key through word64 pointer casts.

  • Swapped multiple byte* to word64* casts to use the GET_U64() helper macro to protect against alignment issues.

https://fenrir.wolfssl.com/finding/5412

Intel RDSEED/RDRAND generators write arbitrary output buffers as word64.

  • Added a temporary word64 value, then used writeUnalignedWord64 to transfer it into the output without alignment issues.

https://fenrir.wolfssl.com/finding/5413

ML-KEM AArch64 noise helpers cast byte buffers and seeds to word64 pointers.

  • Used the alignment-protected helper function writeUnalignedWord64 instead of a byte* -> word64* cast.

@aidankeefe2022 aidankeefe2022 self-assigned this Jun 25, 2026
@aidankeefe2022 aidankeefe2022 changed the title https://fenrir.wolfssl.com/finding/6145 Fix: WolfCrypt Fenrir - 6145, 5384, 4432, 5392 Jun 25, 2026
@aidankeefe2022 aidankeefe2022 force-pushed the fenrir-fixes-jun24/26-ak branch from 9046e08 to 8968849 Compare June 26, 2026 18:34
@aidankeefe2022 aidankeefe2022 marked this pull request as ready for review June 26, 2026 18:47
@github-actions

Copy link
Copy Markdown

retest this please

@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

gcc-arm-cortex-m3

  • FLASH: .text +8 B (+0.0%, 122,449 B / 262,144 B, total: 47% used)

gcc-arm-cortex-m4-openssl-compat

  • FLASH: .text +448 B (+0.1%, 770,764 B / 1,048,576 B, total: 74% used)

gcc-arm-cortex-m4-pkcs7

  • FLASH: .text +256 B (+0.1%, 212,524 B / 262,144 B, total: 81% used)

linuxkm-pie

  • Data: __patchable_function_entries +56 B (+0.2%, 25,512 B)

linuxkm-standard

@aidankeefe2022

Copy link
Copy Markdown
Member Author

Jenkins retest this please

@aidankeefe2022 aidankeefe2022 changed the title Fix: WolfCrypt Fenrir - 6145, 5384, 4432, 5392 Fix: WolfCrypt Fenrir - 6145, 5384, 4432, 5392, 5994, 4445, 4446, 5418, 5420, 5411, 5412, 5413 Jun 26, 2026
@aidankeefe2022 aidankeefe2022 force-pushed the fenrir-fixes-jun24/26-ak branch 3 times, most recently from f0db333 to 60b22a5 Compare June 26, 2026 21:10
@aidankeefe2022

Copy link
Copy Markdown
Member Author

Jenkins retest this please

@aidankeefe2022 aidankeefe2022 changed the title Fix: WolfCrypt Fenrir - 6145, 5384, 4432, 5392, 5994, 4445, 4446, 5418, 5420, 5411, 5412, 5413 Fix: WolfCrypt Fenrir - 12 fixes Jun 26, 2026
@aidankeefe2022

Copy link
Copy Markdown
Member Author

Jenkins retest this please

1 similar comment
@aidankeefe2022

Copy link
Copy Markdown
Member Author

Jenkins retest this please

@aidankeefe2022

Copy link
Copy Markdown
Member Author

rebased branch on to master

Comment thread wolfcrypt/src/port/devcrypto/devcrypto_hash.c Outdated
@aidankeefe2022 aidankeefe2022 force-pushed the fenrir-fixes-jun24/26-ak branch from 7a668b2 to f475fe0 Compare July 1, 2026 21:42
@Frauschi

Frauschi commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Jenkins retest this please.

@Frauschi Frauschi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🐺 Skoll Code Review

Overall recommendation: REQUEST_CHANGES
Findings: 2 total — 2 posted, 0 skipped

Posted findings

  • [High] wc_Sha256Copy frees a zero-initialized dst and closes fd 0 (devcrypto)wolfcrypt/src/port/devcrypto/devcrypto_hash.c:231
  • [Medium] wc_Sha256Copy leaks the just-opened session on XMALLOC failurewolfcrypt/src/port/devcrypto/devcrypto_hash.c:238-241

Review generated by Skoll via Claude/Codex


wc_InitSha256_ex(dst, src->heap, 0);
#ifdef WOLFSSL_DEVCRYPTO_HASH_KEEP
wc_Sha256Free(dst);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 [High] wc_Sha256Copy frees a zero-initialized dst and closes fd 0 (devcrypto)
🚫 BLOCK bug

The PR adds wc_Sha256Free(dst) at the very top of the WOLFSSL_DEVCRYPTO_HASH_KEEP path of wc_Sha256Copy to fix a leak of a pre-existing session in dst. However wc_Sha256Free() -> wc_DevCryptoFree() closes ctx.cfd whenever ctx.cfd >= 0 (see wc_devcrypto.c:191-196). The generic (software) wc_Sha256Copy contract does NOT require dst to be pre-initialized — it simply overwrites dst. Public callers therefore legitimately pass a dst that is zero-initialized, giving cfd == 0. Freeing it runs ioctl(0, CIOCFSESSION, ...) on a garbage session and then close(0) — closing the process's stdin. The internal caller wc_Sha256GetHash was patched to set cpy.ctx.cfd = -1 before calling Copy precisely to avoid this hazard (see the new comment at devcrypto_hash.c:198-201), but external callers were not. Concrete example: ExpectedResumptionSecret() (src/tls13.c:12779-12785) does XMEMSET(&digest, 0, sizeof(Digest)) then wc_Sha256Copy(&ssl->hsHashes->hashSha256, &digest.sha256)digest.sha256.ctx.cfd == 0 -> close(0). The HMAC copy path in src/ssl_crypto.c:1464 has the same shape.

Suggestion:

Suggested change
wc_Sha256Free(dst);
/* Only tear down an existing session; never touch fd 0 for a
* caller-zeroed dst. Match the software backend which does not free dst. */
if (dst->ctx.cfd > 0) {
wc_Sha256Free(dst);
}
if ((ret = wc_InitSha256_ex(dst, src->heap, 0)) != 0) {
dst->ctx.cfd = -1;
return ret;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

fixed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Re-opening — this is only half-fixed.

The internal wc_Sha256GetHash path is safe now (it sets cpy.ctx.cfd = -1 before wc_Sha256Copy), and wc_InitSha256_ex sets the -1 sentinel. But the external, zero-initialized dst path this finding named is still live at head fdb0743:

  • wc_Sha256Copy still calls wc_Sha256Free(dst) unconditionally (devcrypto_hash.c:233) — the comment there says "Sha256Free checks that ctx.cfd is >= 0".
  • wc_DevCryptoFree guards on ctx->cfd >= 0 (wc_devcrypto.c:191) — >= 0 includes 0 — so for a caller-zeroed dst it runs ioctl(0, CIOCFSESSION, …) then close(0).
  • ExpectedResumptionSecret() does XMEMSET(&digest, 0, sizeof(Digest)) then wc_Sha256Copy(&ssl->hsHashes->hashSha256, &digest.sha256) (tls13.c:12860 / :12866) → digest.sha256.ctx.cfd == 0close(0) (stdin) under WOLFSSL_DEVCRYPTO_HASH_KEEP.

The software wc_Sha256Copy contract doesn't require dst to be pre-initialized, and this in-tree TLS 1.3 caller doesn't initialize it — so setting the sentinel only in wc_InitSha256_ex / wc_Sha256GetHash doesn't cover it. Minimal fix is the originally-suggested guard:

/* Only tear down a real session; never touch fd 0 for a caller-zeroed dst */
if (dst->ctx.cfd > 0) {
    wc_Sha256Free(dst);
}

(AFALG's free already uses > 0; devcrypto's >= 0 is the outlier, so tightening the guard in wc_DevCryptoFree would also close this.)

Comment thread wolfcrypt/src/port/devcrypto/devcrypto_hash.c
@aidankeefe2022 aidankeefe2022 force-pushed the fenrir-fixes-jun24/26-ak branch 2 times, most recently from 5fea5ea to 7947f01 Compare July 2, 2026 18:51

@Frauschi Frauschi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Follow-up review — my three earlier wc_Sha256Copy findings are all fixed, thanks. A few adjacent items surfaced while re-checking the touched code:

  • [Med] The wc_fspsm_hw_unlock() fix for finding 5418 was only applied to wc_fspsm_AesGcmEncrypt; the identical return MEMORY_E with the lock held still exists in the wc_fspsm_AesGcmDecrypt sibling.
  • [Med] The alloc-failure block frees the plain/cipher/aTag buffers but not the session-key buffers on a partial allocation.
  • [Low] wc_Sha256Final still has one error path that leaks the session/msg buffer.
  • [Nit] cfd > 0 vs the documented -1 sentinel.

Details inline. #1 (the missing Decrypt unlock) is the one worth fixing before merge — it re-introduces the exact lock leak this PR set out to fix.

Comment thread wolfcrypt/src/port/Renesas/renesas_fspsm_aes.c
Comment thread wolfcrypt/src/port/Renesas/renesas_fspsm_aes.c Outdated
Comment thread wolfcrypt/src/port/devcrypto/devcrypto_hash.c
Comment thread wolfcrypt/src/port/devcrypto/devcrypto_hash.c Outdated
@aidankeefe2022 aidankeefe2022 force-pushed the fenrir-fixes-jun24/26-ak branch 2 times, most recently from 0795ecb to fdb0743 Compare July 6, 2026 17:33
@aidankeefe2022

Copy link
Copy Markdown
Member Author

retest this please

Comment thread wolfcrypt/src/random.c Outdated
Comment thread wolfcrypt/src/port/devcrypto/devcrypto_hash.c Outdated
@aidankeefe2022 aidankeefe2022 force-pushed the fenrir-fixes-jun24/26-ak branch from 37b7051 to ae38cf9 Compare July 7, 2026 18:59
@aidankeefe2022

Copy link
Copy Markdown
Member Author

This PR also fixes a separate bug in the devcrypto AES-GCM path (found by a failing test): the auth tag was being appended past the end of the caller's ciphertext buffer. The encrypt/decrypt now stage the ciphertext || tag in a correctly-sized temporary buffer so nothing is written out of bounds.

Tested with ./configure --enable-devcrypto=all + make check (all suites passing) against a loaded cryptodev module.

@aidankeefe2022

Copy link
Copy Markdown
Member Author

retest this please

@dgarske dgarske left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Skoll Multi-Scan Review

Modes: review + review-securityOverall recommendation: COMMENT
Findings: 8 total — 5 posted, 3 skipped
5 finding(s) posted as inline comments (see file-level comments below)

Posted findings

  • [Medium] [review+review-security] Incomplete alignment fix: RDRAND generator still writes through (word64)output cast* — wolfcrypt/src/random.c:3812
  • [Low] [review] Residual lazy-init race window in wc_DrbgState_MutexInitwolfcrypt/src/random.c:397-408
  • [Low] [review] Stale comment above wc_AesGcmEncrypt after temp-buffer rewritewolfcrypt/src/port/devcrypto/devcrypto_aes.c:373
  • [Low] [review] wc_DrbgState_MutexFree clears atomic flag with a plain store, inconsistent with initwolfcrypt/src/random.c:420
  • [Info] [review-security] AES-GCM devcrypto path now allocates a full-message-size heap buffer that can failwolfcrypt/src/port/devcrypto/devcrypto_aes.c:319-323

Skipped findings

  • [Medium] Rewritten devcrypto AES-GCM path lacks explicit coverage for short-tag and NULL in/out cases
  • [Low] devcrypto AES-GCM temporary buffer holding plaintext freed without zeroization
  • [Info] wc_Sha256Copy now returns NOT_COMPILED_IN for devcrypto builds without WOLFSSL_DEVCRYPTO_HASH_KEEP

Review generated by Skoll

Comment thread wolfcrypt/src/random.c
return -1;
}

for (; (sz / sizeof(word64)) > 0; sz -= sizeof(word64),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🟠 [Medium] Incomplete alignment fix: RDRAND generator still writes through (word64)output cast* · Logic / Unaligned write

The PR (finding 5412) states it fixes both the Intel RDSEED and RDRAND generators to stop writing arbitrary output buffers through word64 pointer casts, using a temporary value plus writeUnalignedWord64. However, only the RDSEED path (wc_GenerateSeed_IntelRD) was changed. The sibling RDRAND path in wc_GenerateRand_IntelRD still does ret = IntelRDrand64_r((word64 *)output);, casting a potentially-unaligned byte* output to word64*. This is the exact unaligned-write / strict-aliasing pattern the PR set out to remove; it is UB and will be flagged by UBSan. On x86 (the only platform where HAVE_INTEL_RDRAND compiles) it does not fault, so this is a pre-existing issue not made worse by the diff, but it is left unfixed despite the PR description implying RDRAND was addressed, and it is inconsistent with the RDSEED loop immediately above (which now zeroizes its temporary on error). Severity view differs across scans: the review mode rates this Medium (bug), while the security mode rates it Info (pre-existing, x86-only, low blast radius); the stricter Medium is retained here.

Fix: Apply the same temp-value + writeUnalignedWord64 treatment used for the RDSEED fix: read into a local word64, store with writeUnalignedWord64(output, tmp), and ForceZero the temporary on the error path so RDRAND matches the RDSEED fix and the PR's stated scope.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed!

Comment thread wolfcrypt/src/random.c Outdated
#ifndef SINGLE_THREADED
#ifndef WOLFSSL_MUTEX_INITIALIZER
if (!drbgStateMutex_inited) {
int expected = 0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🔵 [Low] Residual lazy-init race window in wc_DrbgState_MutexInit · Concurrency

The atomic compare-exchange correctly prevents two threads from both calling wc_InitMutex (the reported double-init bug). But as the new comment acknowledges, the flag is set to 1 before the mutex is actually initialized. A second thread whose CAS fails returns 0 (success) immediately and its caller may proceed to LockDrbgState() -> wc_LockMutex(&drbgStateMutex) while the first thread is still inside wc_InitMutex, i.e. locking a not-yet-initialized mutex. LockDrbgState() does not itself call the init routine, so it relies entirely on init having completed. In practice this is only reachable if two threads enter wolfCrypt_Init concurrently (the sole caller), so severity is low, but the window is real and cannot be closed with a mark-before-init flag alone.

Fix: Consider a two-state scheme (e.g. 0=uninit, 1=in-progress, 2=ready) where callers that lose the CAS wait until the state reaches 'ready', or document that wc_DrbgState_MutexInit must complete before any concurrent DRBG use. At minimum note the residual window so it is not assumed fully thread-safe.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Added state machine and force all threads to wait for success or each take a turn trying to init the mutex if the winner fails. I performed the exact same behavior to the mutexFree function.

XFREE(buf, aes->heap, DYNAMIC_TYPE_AES_BUFFER);
return 0;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🔵 [Low] Stale comment above wc_AesGcmEncrypt after temp-buffer rewrite · Documentation

The comment /* it is assumed that "out" buffer has enough room for cipher text + tag */ predates the rewrite. The new implementation writes only sz ciphertext bytes into out and delivers the tag separately via authTag (using an internal temp buffer), so out no longer needs room for the tag. The comment now misdescribes the contract and could mislead callers/reviewers.

Fix: Update the stale comment to reflect that out holds only ciphertext (sz bytes) and the tag is returned separately in authTag.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

fixed by reverting function behavior and changing the test to make the assumption true.

* from the end of the input. The caller's in/out buffers only hold "sz"
* bytes, so use a temporary buffer with room for the tag to avoid writing
* past their bounds. */
bufSz = sz + WC_AES_BLOCK_SIZE;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

⚪ [Info] AES-GCM devcrypto path now allocates a full-message-size heap buffer that can fail · Resource Leak

BEFORE, wc_DevCrypto_AesGcm used only a fixed 16-byte stack scratch buffer and processed data in place. AFTER, every GCM encrypt/decrypt allocates sz + WC_AES_BLOCK_SIZE bytes on the heap and returns MEMORY_E if the allocation fails. This is functionally correct (and fixes the prior out-of-bounds writes into caller buffers), but it introduces a new failure mode and per-call allocation/copy overhead for large messages where the previous code could not fail on memory. For typical TLS record sizes this is bounded and acceptable; noted for awareness of the behavioral/performance change.

Fix: Acceptable as-is; optionally retain a small-stack fast path for small sz to avoid an allocation per operation. No action required for correctness.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This was found because a test did not allocated a buffer with enough space for the aes authTag to be appended to it. I was looking through the rest of the tests and they do provide a large enough buffer to hold the tag. I decided to fix the test by allocating enough space for the tag to be appended and revert my changes to this function. I could not find a way to not make this assumption without ~40% encryption slow down in the worst case without making major changes. Should we make major changes to the behavior of the function or keep the assumption?

Comment thread wolfcrypt/src/random.c
}
}
#endif
#endif

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🔵 [Low] wc_DrbgState_MutexFree clears atomic flag with a plain store, inconsistent with init · Convention

wc_DrbgState_MutexInit now uses wolfSSL_Atomic_Int_CompareExchange / wolfSSL_Atomic_Int_Exchange on drbgStateMutex_inited, but wc_DrbgState_MutexFree still reads it with a plain if (drbgStateMutex_inited) and clears it with drbgStateMutex_inited = 0;. Since the variable is now wolfSSL_Atomic_Int when WOLFSSL_ATOMIC_OPS is defined, the direct load/store is still atomic and compiles, but using the atomic helper here would be symmetric and clearer. Free is normally a shutdown/single-threaded path, so this is purely stylistic.

Fix: Optionally use wolfSSL_Atomic_Int_Exchange in MutexFree for symmetry with the atomic init; not required for correctness.


Note: Referenced line (wolfcrypt/src/random.c:420) is outside the diff hunk. Comment anchored to nearest changed region.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed matches new MutexInit

@dgarske dgarske left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@aidankeefe2022 aidankeefe2022 force-pushed the fenrir-fixes-jun24/26-ak branch from ae38cf9 to 2541f5e Compare July 8, 2026 17:53
@aidankeefe2022

Copy link
Copy Markdown
Member Author

retest this please

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.

4 participants