Conversation
|
|
Jenkins retest this please - history lost. |
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #11345
Scan targets checked: wolfcrypt-rs-bugs, wolfssl-bugs, wolfssl-src
Findings: 2
2 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Reported findings require changes before merge.
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #11345
Scan targets checked: wolfcrypt-rs-bugs, 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. Reported findings require changes before merge.
|
Jenkins retest this please |
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #11345
Scan targets checked: wolfcrypt-rs-bugs, 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. Reported findings require changes before merge.
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #11345
Scan targets checked: wolfcrypt-rs-bugs, wolfssl-bugs, wolfssl-src
Fenrir result: Approved ✅
No new issues found in the changed files.
Advisory only — this automated result does not count as a GitHub approval.
Fenrir's latest completed scan found no issues; clearing the prior automated change request.
Fenrir's latest completed scan found no issues; clearing the prior automated change request.
|
Jenkins retest this please - flaky test |
64b24b7 to
5421c4a
Compare
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Embedded storage causes a use-after-free with async Intel QAT, and the DH import-floor behavior contradicts the stated contract.
Review effort: Balanced
Findings: 1
Open (2)
What changed in this PR
Resizes and embeds the TLS pre-master secret buffer to fix a DHE-PSK overflow while reducing handshake allocations.
Changes:
- Derives buffer capacity from enabled key exchanges.
- Embeds and consistently wipes the buffer with
Arrays. - Adds boundary, import-policy, and regression tests.
| File | Description |
|---|---|
wolfssl/internal.h |
Defines sizing and embeds the buffer. |
src/internal.c |
Updates allocation, DH/PSK handling, and imports. |
src/keys.c |
Updates derivation buffers and cleanup. |
src/ssl.c |
Resets embedded secret storage. |
src/ssl_api_dtls.c |
Bounds and clears multicast secrets. |
src/ssl_api_pk.c |
Enforces supported DH ceilings. |
src/sniffer.c |
Updates capacities and secret cleanup. |
src/tls.c |
Updates key-share bounds. |
src/tls13.c |
Fully wipes TLS 1.3 secrets. |
tests/api.c |
Updates secret-zeroization assertions. |
tests/api/test_dtls.c |
Tests import and secret boundaries. |
tests/api/test_dtls.h |
Registers the DTLS test. |
tests/api/test_ssl_pk.c |
Tests DH ceiling validation. |
tests/api/test_tls.c |
Adds the DHE-PSK overflow regression. |
tests/api/test_tls.h |
Registers the TLS test. |
tests/api/test_tls13.c |
Updates manual array cleanup. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The pre-master secret buffer was its own allocation, made eagerly for TLS 1.3 and the sniffer and lazily in SendClientKeyExchange, DoClientKeyExchange and wolfSSL_set_secret() otherwise, then freed by CleanPreMaster() or FreeArrays(). That is one allocation and one free per handshake for a buffer whose lifetime is exactly that of the Arrays it belongs to. Reserve ENCRYPT_LEN bytes at the end of the Arrays allocation instead and point preMasterSecret at them. The call sites that used to allocate now only reset the size and clear the buffer, CleanPreMaster() wipes it in place, and FreeArrays() wipes both in one call. A TLS 1.2 only build reserves the buffer when the arrays are created rather than when the key exchange runs, which holds ENCRYPT_LEN bytes for longer but costs no extra peak, as both were live together before. test_tls13_fragmented_session_ticket() releases the arrays by hand, because FreeArrays() is hidden in shared library builds, so it releases the one allocation now.
The pre-master secret buffer was sized with ENCRYPT_LEN. That constant also has to cover an RSA signature, so it follows the largest RSA key the build supports and a build with RSA 4096 certificates reserved 512 bytes for a secret that ECDHE fills 32 to 98 bytes of. MAX_PREMASTER_SZ takes the largest secret any key exchange in the build can produce: the RSA pre-master, the DH parameters the peer may send, an ECDHE secret plus the 32 byte ML-KEM half of a hybrid key share, and the length prefixes and key that the PSK suites append, with the master secret length as a floor. A build with DH is unchanged, since the peer chooses the parameter size there. A build without it, which is the usual TLS 1.3 configuration, holds 98 rather than 512 bytes per handshake. A build with both DH and PSK gains two bytes: a DHE-PSK exchange writes a two byte length, the DH secret, another two byte length and the key, which is two more than ENCRYPT_LEN reserved for the largest DH parameters and the longest PSK. The buffer becomes the first member of Arrays, so async Intel QAT builds allocate Arrays as DYNAMIC_TYPE_SECRET. A QAT key agreement reallocs its output buffer into NUMA memory, and IntelQaRealloc() now finds the Arrays header there: a non-NUMA one makes it free the whole struct, a NUMA one lets it write in place, as it did for the standalone DYNAMIC_TYPE_SECRET buffer. A static assert keeps the buffer at offset 0.
A DHE-PSK exchange stores a two byte length, the DH shared secret, another two byte length and the PSK in the pre-master secret buffer. Sized with ENCRYPT_LEN, as it was before MAX_PREMASTER_SZ, that came up two bytes short for the largest DH parameters the build accepts together with a maximum length PSK, and the last two bytes of the key were written past the allocation on both peers. The test asserts the sizes add up, then runs the handshake that produces the largest secret: 4096 bit parameters loaded on the server and a PSK of MAX_PSK_KEY_LEN bytes. The client asks for an ECC named group so the FFDHE negotiation does not replace the server's parameters with a smaller group, and the negotiated key size is checked to confirm it did not. Against the old sizing the assertion fails, and the handshake reports a heap buffer overflow in MakePSKPreMasterSecret() under a sanitizer build.


One of six independent branches that cut allocations and per-object memory in the TLS layer. This one also carries a memory-safety fix.
The fix
A DHE-PSK exchange stores a two byte length, the DH shared secret, another two byte length and the PSK in the pre-master secret buffer. Sized with
ENCRYPT_LEN, that is two bytes short for the largest DH parameters the build accepts together with a maximum length PSK, and the last two bytes of the key were written past the allocation on both peers. It is reached before authentication completes, from parameters the peer supplies.test_dhe_psk_max_premaster()drives the handshake that produces the largest secret: 4096 bit parameters on the server and aMAX_PSK_KEY_LENPSK. The client asks for an ECC named group so FFDHE negotiation does not substitute a smaller group, and the negotiated key size is checked to confirm it did not. Against the old sizing the assertion fails and a sanitizer build reports a heap buffer overflow inMakePSKPreMasterSecret().What else changes
The buffer is part of the
Arraysstruct. It used to be its own allocation - made eagerly for TLS 1.3 and the sniffer, lazily elsewhere, and freed byCleanPreMaster()orFreeArrays()- for a buffer whose lifetime is exactly that of theArraysit belongs to. It is now a member, so the two are created, wiped and released together, and nothing tracks or frees it separately.It is sized by what the build can actually produce.
ENCRYPT_LENalso has to cover an RSA signature, so it follows the largest RSA key the build supports: a build with RSA 4096 certificates reserved 512 bytes for a secret ECDHE fills 32 to 98 bytes of.MAX_PREMASTER_SZtakes the largest secret any key exchange in the build can produce - the RSA pre-master, the DH parameters a peer may send, an ECDHE secret plus the 32 byte ML-KEM half of a hybrid key share, the length prefixes and key the PSK suites append, the TLS 1.3 handshake secret that reuses the buffer - with the master secret length as a floor.A build with DH is unchanged, since the peer chooses the parameter size. A build without it, the usual TLS 1.3 configuration, holds 98 rather than 512 bytes per handshake. A build with both DH and PSK gains two bytes, which is the overflow above.
WOLFSSL_MAX_GEN_PREMASTER_SZlets an integration that knows what itsGenPreMasterCbwrites opt down; it defaults toENCRYPT_LENso no existing callback can be overrun, and the capacity handed to the callback is unchanged.Behaviour changes worth flagging
wolfSSL_[CTX_]SetMaxDhKey_Sz()now returnsBAD_FUNC_ARGfor a ceiling above what the build's math supports, rather than accepting it and silently reducing it. This is load-bearing: without it an application could raisemaxDhKeySzabove the buffer size and reopen the overflow. An application that defensively callswolfSSL_CTX_SetMaxDhKey_Sz(ctx, 4096)on a build whose math tops out lower now gets an error where it previously got success. The check sits after the crypto-policy block so a request that violates both is still reported as the policy violation.The minimum setters are deliberately unchanged: a floor above what the build supports is a tightening the caller asked for, it simply refuses every key a peer can offer, and it cannot overrun anything.
wolfSSL_dtls_import()treats the serialized DH sizes the same way: the exported ceiling is clamped, a negotiated size above what this build supports fails the import, and the floor carries over as-is, matching the minimum setters.Memory impact
Measured on 64-bit builds.
Arraysis one allocation per handshake; on master the pre-master secret was a second one beside it.TLS 1.3 without DH or PSK (
--enable-tls13 --disable-dh --disable-psk), the usual TLS 1.3 configuration:sizeof(Arrays)That is 420 bytes and one allocation/free pair saved per handshake, because
MAX_PREMASTER_SZcomes out at 98 whereENCRYPT_LENwas 512.Default build (DH enabled, so the peer chooses the parameter size and the buffer cannot shrink):
No meaningful size change, but still one fewer allocation and free per handshake, and the buffer is now wiped together with the arrays rather than needing its own
CleanPreMaster()path.