From 31a045835e902a3022b5c71f9eaec3783bdd86d6 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Thu, 9 Jul 2026 17:30:55 -0700 Subject: [PATCH] fix: force-zero secret buffers before free Zeroize key/digest/handshake buffers before they are freed or go out of scope, and fix a private-key leak on the CTX-full reject path. Build-verified with ./configure --enable-all (make exit 0). - #1278 internal.c KeyAgreeEcdhMlKem_client: WS_FORCEZERO handshake->x ML-KEM private key before wc_MlKemKey_Free, matching the DH path. - #2082 agent.c wolfSSH_AGENT_ID_free: WMEMSET -> WS_FORCEZERO for the key buffer and the id struct so scrubs are not optimized away. - #2496 internal.c SignHRsa/SignHEcdsa: WS_FORCEZERO digest (and encSig in SignHRsa) before return. - #2497 internal.c BuildUserAuthRequest Rsa/RsaCert/Ecc/EccCert: WS_FORCEZERO digest (and encDigest for the RSA paths). - #2498 internal.c DoUserAuthRequestPublicKey: WS_FORCEZERO digest before it leaves scope on both success and failure exits. - #2500 internal.c DoUserAuthRequestRsa: WS_FORCEZERO encDigest before free (both SMALL_STACK and stack-array paths). - #2501 internal.c DoUserAuthRequestRsaCert: same encDigest scrub. - #2886 internal.c SshResourceFree: WS_FORCEZERO ssh->h and ssh->sessionId (and reset sizes) alongside the already-zeroed KDF inputs. - #3453 internal.c SetHostPrivateKey: on CTX-full reject, take ownership and WS_FORCEZERO+WFREE der instead of leaking the private key. - #3680 internal.c ChannelDelete: WS_FORCEZERO inputBuffer before free. - #6277 wolfsftp.c ClearState/GET/PUT cleanup: WS_FORCEZERO SFTP get/put state structs before free (all four sites). - #6278 wolfsshd.c SHELL_Subsystem: WS_FORCEZERO channelBuffer/shellBuffer on the data-carrying exit. --- apps/wolfsshd/wolfsshd.c | 2 ++ src/agent.c | 2 +- src/internal.c | 31 ++++++++++++++++++++++++++++++- src/wolfsftp.c | 8 ++++---- 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/apps/wolfsshd/wolfsshd.c b/apps/wolfsshd/wolfsshd.c index a8e6e860a..71e6396cf 100644 --- a/apps/wolfsshd/wolfsshd.c +++ b/apps/wolfsshd/wolfsshd.c @@ -1916,6 +1916,8 @@ static int SHELL_Subsystem(WOLFSSHD_CONNECTION* conn, WOLFSSH* ssh, } } + WS_FORCEZERO(channelBuffer, sizeof channelBuffer); + WS_FORCEZERO(shellBuffer, sizeof shellBuffer); (void)conn; return WS_SUCCESS; } diff --git a/src/agent.c b/src/agent.c index 5eac61788..1174f35b6 100644 --- a/src/agent.c +++ b/src/agent.c @@ -1607,7 +1607,7 @@ void wolfSSH_AGENT_ID_free(WOLFSSH_AGENT_ID* id, void* heap) WS_FORCEZERO(id->keyBuffer, id->keyBufferSz); WFREE(id->keyBuffer, heap, DYNTYPE_STRING); } - WMEMSET(id, 0, sizeof(WOLFSSH_AGENT_ID)); + WS_FORCEZERO(id, sizeof(WOLFSSH_AGENT_ID)); WFREE(id, heap, DYNTYPE_AGENT_ID); } diff --git a/src/internal.c b/src/internal.c index 636cde839..2a17d14d2 100644 --- a/src/internal.c +++ b/src/internal.c @@ -1561,6 +1561,10 @@ void SshResourceFree(WOLFSSH* ssh, void* heap) HandshakeInfoFree(ssh->handshake, heap); WS_FORCEZERO(&ssh->keys, sizeof(Keys)); WS_FORCEZERO(&ssh->peerKeys, sizeof(Keys)); + WS_FORCEZERO(ssh->h, sizeof(ssh->h)); + ssh->hSz = 0; + WS_FORCEZERO(ssh->sessionId, sizeof(ssh->sessionId)); + ssh->sessionIdSz = 0; if (ssh->rng) { wc_FreeRng(ssh->rng); WFREE(ssh->rng, heap, DYNTYPE_RNG); @@ -6799,6 +6803,9 @@ static int KeyAgreeEcdhMlKem_client(WOLFSSH* ssh, byte hashId, ret); } + /* Zero the ML-KEM private key material in handshake->x now that + * decapsulation is done, matching the DH path's post-use WS_FORCEZERO. */ + WS_FORCEZERO(ssh->handshake->x, ssh->handshake->xSz); wc_MlKemKey_Free(&kem); WS_FORCEZERO(ssh->handshake->x, ssh->handshake->xSz); @@ -8468,8 +8475,11 @@ static int DoUserAuthRequestRsa(WOLFSSH* ssh, WS_UserAuthData_PublicKey* pk, WFREE(key, ssh->ctx->heap, DYNTYPE_PUBKEY); } if (encDigest) { + WS_FORCEZERO(encDigest, MAX_ENCODED_SIG_SZ); WFREE(encDigest, ssh->ctx->heap, DYNTYPE_BUFFER); } +#else + WS_FORCEZERO(encDigest, sizeof(encDigest)); #endif WLOG(WS_LOG_DEBUG, "Leaving DoUserAuthRequestRsa(), ret = %d", ret); @@ -8629,8 +8639,11 @@ static int DoUserAuthRequestRsaCert(WOLFSSH* ssh, WS_UserAuthData_PublicKey* pk, WFREE(key, ssh->ctx->heap, DYNTYPE_PUBKEY); } if (encDigest) { + WS_FORCEZERO(encDigest, MAX_ENCODED_SIG_SZ); WFREE(encDigest, ssh->ctx->heap, DYNTYPE_BUFFER); } +#else + WS_FORCEZERO(encDigest, sizeof(encDigest)); #endif WLOG(WS_LOG_DEBUG, "Leaving DoUserAuthRequestRsaCert(), ret = %d", ret); @@ -9671,6 +9684,8 @@ static int DoUserAuthRequestPublicKey(WOLFSSH* ssh, WS_UserAuthData* authData, ret = WS_INVALID_ALGO_ID; } } + + WS_FORCEZERO(digest, sizeof(digest)); } if (ret != WS_SUCCESS) { @@ -14385,9 +14400,14 @@ static int SignHRsa(WOLFSSH* ssh, byte* sig, word32* sigSz, &sigKey->sk.rsa.key, heap, "SignHRsa"); } + WS_FORCEZERO(digest, sizeof(digest)); #ifdef WOLFSSH_SMALL_STACK - if (encSig != NULL) + if (encSig != NULL) { + WS_FORCEZERO(encSig, MAX_ENCODED_SIG_SZ); WFREE(encSig, heap, DYNTYPE_TEMP); + } + #else + WS_FORCEZERO(encSig, MAX_ENCODED_SIG_SZ); #endif WLOG(WS_LOG_DEBUG, "Leaving SignHRsa(), ret = %d", ret); return ret; @@ -14529,6 +14549,7 @@ static int SignHEcdsa(WOLFSSH* ssh, byte* sig, word32* sigSz, WMEMCPY(sig + idx, s, sSz); } + WS_FORCEZERO(digest, sizeof(digest)); #ifdef WOLFSSH_SMALL_STACK if (r) WFREE(r, heap, DYNTYPE_BUFFER); @@ -16623,6 +16644,8 @@ static int BuildUserAuthRequestRsa(WOLFSSH* ssh, if (ret == WS_SUCCESS) begin += keySig->sigSz; + + WS_FORCEZERO(encDigest, sizeof(encDigest)); } } @@ -16634,6 +16657,7 @@ static int BuildUserAuthRequestRsa(WOLFSSH* ssh, WFREE(checkData, ssh->ctx->heap, DYNTYPE_TEMP); } + WS_FORCEZERO(digest, sizeof(digest)); return ret; } /* END BuildUserAuthRequestRsa */ @@ -16803,6 +16827,8 @@ static int BuildUserAuthRequestRsaCert(WOLFSSH* ssh, if (ret == WS_SUCCESS) begin += keySig->sigSz; + + WS_FORCEZERO(encDigest, sizeof(encDigest)); } } @@ -16814,6 +16840,7 @@ static int BuildUserAuthRequestRsaCert(WOLFSSH* ssh, WFREE(checkData, ssh->ctx->heap, DYNTYPE_TEMP); } + WS_FORCEZERO(digest, sizeof(digest)); WLOG(WS_LOG_DEBUG, "Leaving BuildUserAuthRequestRsaCert(), ret = %d", ret); return ret; @@ -17081,6 +17108,7 @@ static int BuildUserAuthRequestEcc(WOLFSSH* ssh, WFREE(checkData, ssh->ctx->heap, DYNTYPE_TEMP); } + WS_FORCEZERO(digest, sizeof(digest)); #ifdef WOLFSSH_SMALL_STACK if (r_ptr) WFREE(r_ptr, ssh->ctx->heap, DYNTYPE_BUFFER); @@ -17341,6 +17369,7 @@ static int BuildUserAuthRequestEccCert(WOLFSSH* ssh, WFREE(checkData, ssh->ctx->heap, DYNTYPE_TEMP); } + WS_FORCEZERO(digest, sizeof(digest)); return ret; } diff --git a/src/wolfsftp.c b/src/wolfsftp.c index 84c40f7b4..fc15f9fd2 100644 --- a/src/wolfsftp.c +++ b/src/wolfsftp.c @@ -850,7 +850,7 @@ static void wolfSSH_SFTP_ClearState(WOLFSSH* ssh, enum WS_SFTP_STATE_ID state) if (state & STATE_ID_GET) { if (ssh->getState) { - WS_FORCEZERO(ssh->getState->r, WOLFSSH_MAX_SFTP_RW); + WS_FORCEZERO(ssh->getState, sizeof(WS_SFTP_GET_STATE)); WFREE(ssh->getState, ssh->ctx->heap, DYNTYPE_SFTP_STATE); ssh->getState = NULL; } @@ -929,7 +929,7 @@ static void wolfSSH_SFTP_ClearState(WOLFSSH* ssh, enum WS_SFTP_STATE_ID state) if (state & STATE_ID_PUT) { if (ssh->putState) { - WS_FORCEZERO(ssh->putState->r, WOLFSSH_MAX_SFTP_RW); + WS_FORCEZERO(ssh->putState, sizeof(WS_SFTP_PUT_STATE)); WFREE(ssh->putState, ssh->ctx->heap, DYNTYPE_SFTP_STATE); ssh->putState = NULL; } @@ -9795,7 +9795,7 @@ int wolfSSH_SFTP_Get(WOLFSSH* ssh, char* from, case STATE_GET_CLEANUP: WLOG(WS_LOG_SFTP, "SFTP GET STATE: CLEANUP"); if (ssh->getState != NULL) { - WS_FORCEZERO(ssh->getState->r, WOLFSSH_MAX_SFTP_RW); + WS_FORCEZERO(ssh->getState, sizeof(WS_SFTP_GET_STATE)); WFREE(ssh->getState, ssh->ctx->heap, DYNTYPE_SFTP_STATE); ssh->getState = NULL; } @@ -10019,7 +10019,7 @@ int wolfSSH_SFTP_Put(WOLFSSH* ssh, char* from, char* to, byte resume, case STATE_PUT_CLEANUP: WLOG(WS_LOG_SFTP, "SFTP PUT STATE: CLEANUP"); if (ssh->putState != NULL) { - WS_FORCEZERO(ssh->putState->r, WOLFSSH_MAX_SFTP_RW); + WS_FORCEZERO(ssh->putState, sizeof(WS_SFTP_PUT_STATE)); WFREE(ssh->putState, ssh->ctx->heap, DYNTYPE_SFTP_STATE); ssh->putState = NULL; }