From cb495320fe6f41e4af7e58c787f7ccc9d2ec1249 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 17 Apr 2026 16:44:45 +0200 Subject: [PATCH 01/12] Zeroize DER buffer in der_to_enc_pem_alloc before free F-2139 Previously the plaintext private key DER buffer was freed via XFREE without a preceding ForceZero when no password encryption was requested. Track the actual allocation size and zeroize the buffer before release. --- src/pk.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pk.c b/src/pk.c index 1aaf40d065d..9370754c8e0 100644 --- a/src/pk.c +++ b/src/pk.c @@ -480,6 +480,7 @@ static int der_to_enc_pem_alloc(unsigned char* der, int derSz, byte* tmp = NULL; byte* cipherInfo = NULL; int pemSz = 0; + int derAllocSz = derSz; int hashType = WC_HASH_TYPE_NONE; #if !defined(NO_MD5) hashType = WC_MD5; @@ -515,6 +516,7 @@ static int der_to_enc_pem_alloc(unsigned char* der, int derSz, } else { der = tmpBuf; + derAllocSz = derSz + blockSz; /* Encrypt DER inline. */ ret = EncryptDerKey(der, &derSz, cipher, passwd, passwdSz, @@ -562,7 +564,10 @@ static int der_to_enc_pem_alloc(unsigned char* der, int derSz, XFREE(tmp, NULL, DYNAMIC_TYPE_KEY); XFREE(cipherInfo, NULL, DYNAMIC_TYPE_STRING); - XFREE(der, heap, DYNAMIC_TYPE_TMP_BUFFER); + if (der != NULL) { + ForceZero(der, (word32)derAllocSz); + XFREE(der, heap, DYNAMIC_TYPE_TMP_BUFFER); + } return ret; } From a05dd200a9bba03dac8e52ae0312a46200ba29cc Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 17 Apr 2026 16:45:14 +0200 Subject: [PATCH 02/12] Zeroize DSA DER buffer in PEM write before free F-2140 wolfSSL_PEM_write_mem_DSAPrivateKey serializes the DSA private key to a heap DER buffer and freed it on five paths without ForceZero. Zeroize the buffer before each XFREE. --- src/pk.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pk.c b/src/pk.c index 9370754c8e0..be03bc3f97c 100644 --- a/src/pk.c +++ b/src/pk.c @@ -2109,6 +2109,7 @@ int wolfSSL_PEM_write_mem_DSAPrivateKey(WOLFSSL_DSA* dsa, derSz = wc_DsaKeyToDer((DsaKey*)dsa->internal, derBuf, (word32)der_max_len); if (derSz < 0) { WOLFSSL_MSG("wc_DsaKeyToDer failed"); + ForceZero(derBuf, (word32)der_max_len); XFREE(derBuf, NULL, DYNAMIC_TYPE_DER); return 0; } @@ -2121,6 +2122,7 @@ int wolfSSL_PEM_write_mem_DSAPrivateKey(WOLFSSL_DSA* dsa, &cipherInfo, der_max_len, WC_MD5); if (ret != 1) { WOLFSSL_MSG("EncryptDerKey failed"); + ForceZero(derBuf, (word32)der_max_len); XFREE(derBuf, NULL, DYNAMIC_TYPE_DER); return ret; } @@ -2136,6 +2138,7 @@ int wolfSSL_PEM_write_mem_DSAPrivateKey(WOLFSSL_DSA* dsa, tmp = (byte*)XMALLOC((size_t)*pLen, NULL, DYNAMIC_TYPE_PEM); if (tmp == NULL) { WOLFSSL_MSG("malloc failed"); + ForceZero(derBuf, (word32)der_max_len); XFREE(derBuf, NULL, DYNAMIC_TYPE_DER); XFREE(cipherInfo, NULL, DYNAMIC_TYPE_STRING); return 0; @@ -2146,11 +2149,13 @@ int wolfSSL_PEM_write_mem_DSAPrivateKey(WOLFSSL_DSA* dsa, type); if (*pLen <= 0) { WOLFSSL_MSG("wc_DerToPemEx failed"); + ForceZero(derBuf, (word32)der_max_len); XFREE(derBuf, NULL, DYNAMIC_TYPE_DER); XFREE(tmp, NULL, DYNAMIC_TYPE_PEM); XFREE(cipherInfo, NULL, DYNAMIC_TYPE_STRING); return 0; } + ForceZero(derBuf, (word32)der_max_len); XFREE(derBuf, NULL, DYNAMIC_TYPE_DER); XFREE(cipherInfo, NULL, DYNAMIC_TYPE_STRING); From dfd37f42993bb845b6ee52ed80dfcfd54c1af077 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 17 Apr 2026 16:45:29 +0200 Subject: [PATCH 03/12] Zeroize EC DER buffer in PEM write error path F-2141 The error path in wolfSSL_PEM_write_mem_ECPrivateKey freed the EC private key DER staging buffer without ForceZero. Zeroize before free. --- src/pk_ec.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pk_ec.c b/src/pk_ec.c index 66647c9eb77..f7ec038d691 100644 --- a/src/pk_ec.c +++ b/src/pk_ec.c @@ -4095,6 +4095,7 @@ int wolfSSL_PEM_write_mem_ECPrivateKey(WOLFSSL_EC_KEY* ec, derSz = wc_EccKeyToDer((ecc_key*)ec->internal, derBuf, der_max_len); if (derSz < 0) { WOLFSSL_MSG("wc_EccKeyToDer failed"); + ForceZero(derBuf, der_max_len); XFREE(derBuf, NULL, DYNAMIC_TYPE_DER); ret = 0; } From 4c8adc50462222100130f6a44075a01cc9c96c54 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 17 Apr 2026 16:46:24 +0200 Subject: [PATCH 04/12] Zeroize RSA DER buffer in To_Der error path F-2142 wolfSSL_RSA_To_Der could free a buffer holding RSA private key material when the DER encoding step failed. Record the allocation size and ForceZero the buffer before XFREE on the private key path. --- src/pk_rsa.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pk_rsa.c b/src/pk_rsa.c index c102f4ec21f..aafe2bc8392 100644 --- a/src/pk_rsa.c +++ b/src/pk_rsa.c @@ -779,6 +779,7 @@ static int wolfSSL_RSA_To_Der_ex(WOLFSSL_RSA* rsa, byte** outBuf, int publicKey, { int ret = 1; int derSz = 0; + int derAllocSz = 0; byte* derBuf = NULL; WOLFSSL_ENTER("wolfSSL_RSA_To_Der"); @@ -820,6 +821,7 @@ static int wolfSSL_RSA_To_Der_ex(WOLFSSL_RSA* rsa, byte** outBuf, int publicKey, } } + derAllocSz = derSz; if ((ret == 1) && (outBuf != NULL)) { derBuf = *outBuf; if (derBuf == NULL) { @@ -863,6 +865,9 @@ static int wolfSSL_RSA_To_Der_ex(WOLFSSL_RSA* rsa, byte** outBuf, int publicKey, if ((outBuf != NULL) && (*outBuf != derBuf)) { /* Not returning buffer, needs to be disposed of. */ + if ((derBuf != NULL) && (publicKey == 0)) { + ForceZero(derBuf, (word32)derAllocSz); + } XFREE(derBuf, heap, DYNAMIC_TYPE_TMP_BUFFER); } WOLFSSL_LEAVE("wolfSSL_RSA_To_Der", ret); From 9790719955f666ef3febf57454f5a27238ba949d Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 17 Apr 2026 16:46:43 +0200 Subject: [PATCH 05/12] Zeroize sniffer watch key buffer before free F-2143 ssl_SetWatchKey_file loaded a private key file into a heap buffer and freed it without ForceZero on both error and success paths. Zeroize before XFREE. --- src/sniffer.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sniffer.c b/src/sniffer.c index e8664721b15..6c5eba0b14b 100644 --- a/src/sniffer.c +++ b/src/sniffer.c @@ -7242,12 +7242,16 @@ int ssl_SetWatchKey_file(void* vSniffer, const char* keyFile, int keyType, ret = LoadKeyFile(&keyBuf, &keyBufSz, keyFile, 0, keyType, password); if (ret < 0) { SetError(KEY_FILE_STR, error, NULL, 0); + if (keyBuf != NULL) { + ForceZero(keyBuf, keyBufSz); + } XFREE(keyBuf, NULL, DYNAMIC_TYPE_X509); return WOLFSSL_FATAL_ERROR; } ret = ssl_SetWatchKey_buffer(vSniffer, keyBuf, keyBufSz, FILETYPE_DER, error); + ForceZero(keyBuf, keyBufSz); XFREE(keyBuf, NULL, DYNAMIC_TYPE_X509); return ret; From fa3feb744227b227d2fc1ce3cf27c119502a2c05 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 17 Apr 2026 16:46:57 +0200 Subject: [PATCH 06/12] Zeroize static ephemeral key buffer before free F-2144 SetStaticEphemeralKey loaded a private key file into keyBuf and freed it without ForceZero. Static ephemeral keys are long-lived, so zeroize the buffer before XFREE. --- src/ssl.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ssl.c b/src/ssl.c index 0d722f4ae09..05e7a79df38 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -18645,6 +18645,7 @@ static int SetStaticEphemeralKey(WOLFSSL_CTX* ctx, #ifndef NO_FILESYSTEM /* done with keyFile buffer */ if (keyFile && keyBuf) { + ForceZero(keyBuf, keySz); XFREE(keyBuf, heap, DYNAMIC_TYPE_TMP_BUFFER); } #endif From a82828b3ac519777c98406d9332c45089ef2e0bc Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 17 Apr 2026 16:47:19 +0200 Subject: [PATCH 07/12] Zeroize RSA DER buffer in CTX_use_RSAPrivateKey before free F-2145 wolfSSL_CTX_use_RSAPrivateKey staged the RSA private key DER (PKCS#1: n, e, d, p, q, dP, dQ, qInv) in a heap buffer and freed it without ForceZero. Zeroize before XFREE. --- src/ssl_load.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ssl_load.c b/src/ssl_load.c index 5c83e88c5af..04fecf2181b 100644 --- a/src/ssl_load.c +++ b/src/ssl_load.c @@ -5365,6 +5365,9 @@ int wolfSSL_CTX_use_RSAPrivateKey(WOLFSSL_CTX* ctx, WOLFSSL_RSA* rsa) } /* Dispos of dynamically allocated data. */ + if (der != NULL) { + ForceZero(der, (word32)derSize); + } XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); return ret; } From 9925f90720c58e571330c7df68ea1e47c62009dd Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 17 Apr 2026 16:47:33 +0200 Subject: [PATCH 08/12] Zeroize RSA DER buffer in d2i_RSAPrivateKey_bio before free F-2146 wolfSSL_d2i_RSAPrivateKey_bio read PKCS#1-encoded RSA private key DER from a BIO into a heap buffer and freed it without ForceZero. Zeroize before XFREE on both success and error paths. --- src/pk_rsa.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pk_rsa.c b/src/pk_rsa.c index aafe2bc8392..9f5ba4488b0 100644 --- a/src/pk_rsa.c +++ b/src/pk_rsa.c @@ -719,6 +719,9 @@ WOLFSSL_RSA* wolfSSL_d2i_RSAPrivateKey_bio(WOLFSSL_BIO *bio, WOLFSSL_RSA **out) key = NULL; } /* Dispose of allocated data. */ + if (der != NULL) { + ForceZero(der, (word32)derLen); + } XFREE(der, bio ? bio->heap : NULL, DYNAMIC_TYPE_TMP_BUFFER); return key; } From 87e5c62111d37d6e3f3624178b65c5633c66cd53 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 17 Apr 2026 16:47:47 +0200 Subject: [PATCH 09/12] Zeroize EC DER buffer in i2d_ECPrivateKey error path F-2147 The error path in wolfSSL_i2d_ECPrivateKey could free an EC private key DER staging buffer that may contain a partial private scalar. Zeroize before XFREE. --- src/pk_ec.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pk_ec.c b/src/pk_ec.c index f7ec038d691..faed7f20a67 100644 --- a/src/pk_ec.c +++ b/src/pk_ec.c @@ -3524,6 +3524,9 @@ int wolfSSL_i2d_ECPrivateKey(const WOLFSSL_EC_KEY *key, unsigned char **out) /* Dispose of any allocated buffer on error. */ if (err && (*out == buf)) { + if (buf != NULL) { + ForceZero(buf, len); + } XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); *out = NULL; } From 00fff0f3fc82d383560712be2d8c7c011b2af081 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 17 Apr 2026 16:48:06 +0200 Subject: [PATCH 10/12] Zeroize PKCS#8 DER staging area in PEM write helper F-2148 pem_write_mem_pkcs8privatekey stages the PKCS#8 DER encoded private key at the tail of the PEM buffer, then writes the shorter PEM output at the head of the same buffer. The DER tail is not overwritten, leaking the plaintext private key to heap memory after the callers free. Zero the DER staging area before returning. --- src/pk.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pk.c b/src/pk.c index be03bc3f97c..4fe12629f42 100644 --- a/src/pk.c +++ b/src/pk.c @@ -7208,6 +7208,12 @@ static int pem_write_mem_pkcs8privatekey(byte** pem, int* pemSz, } } + /* Zero the DER staging area at the tail of the buffer so the plaintext + * private key material is not left in freed heap memory. */ + if (key != NULL && keySz > 0) { + ForceZero(key, keySz); + } + /* Return appropriate return code. */ return (res == 0) ? 0 : ret; From 1e040923c6b7b6d798b6fb587c24f2480672937f Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 17 Apr 2026 17:02:58 +0200 Subject: [PATCH 11/12] Only zero unused tail of PKCS#8 PEM buffer F-2148 The prior fix zeroed the computed DER staging area, but PEM output from wc_DerToPemEx fills most of the buffer and overlaps that region, corrupting the valid PEM. Preserve the allocation size and zero only the bytes beyond the actual PEM length, or the whole buffer on failure. --- src/pk.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/pk.c b/src/pk.c index 4fe12629f42..f9cc7345ad1 100644 --- a/src/pk.c +++ b/src/pk.c @@ -7117,6 +7117,7 @@ static int pem_write_mem_pkcs8privatekey(byte** pem, int* pemSz, char password[NAME_SZ]; byte* key = NULL; word32 keySz = 0; + word32 allocSz = 0; int type = PKCS8_PRIVATEKEY_TYPE; /* Validate parameters. */ @@ -7155,6 +7156,10 @@ static int pem_write_mem_pkcs8privatekey(byte** pem, int* pemSz, res = 0; } else { + /* Remember the allocation size before *pemSz is updated to the + * actual PEM output length, so we can zero any unused tail that + * held the DER staging area. */ + allocSz = (word32)*pemSz; /* Use end of PEM buffer for key data. */ key = *pem + *pemSz - keySz; } @@ -7208,10 +7213,18 @@ static int pem_write_mem_pkcs8privatekey(byte** pem, int* pemSz, } } - /* Zero the DER staging area at the tail of the buffer so the plaintext - * private key material is not left in freed heap memory. */ - if (key != NULL && keySz > 0) { - ForceZero(key, keySz); + /* Zero any remnants of the DER staging area that persist after PEM + * conversion so plaintext private key material is not left in freed heap + * memory. On success, only the bytes past the actual PEM output need + * clearing; on failure, the whole buffer is zeroed since its state is + * indeterminate. */ + if (*pem != NULL) { + if (res == 1 && (word32)*pemSz < allocSz) { + ForceZero(*pem + *pemSz, allocSz - (word32)*pemSz); + } + else if (res != 1) { + ForceZero(*pem, allocSz); + } } /* Return appropriate return code. */ From a010825bb08103be70da93b67ebab70e2de85b46 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Mon, 20 Apr 2026 12:46:58 +0000 Subject: [PATCH 12/12] Address review comments on Fenrir zeroization fixes Two follow-ups raised by Copilot review on PR #10247: src/pk_rsa.c: Make derAllocSz a word32 instead of int and only assign it after a successful XMALLOC, so the cleanup path can never call ForceZero with a wrapped-around size derived from a negative derSz. src/pk.c: Capture allocSz at the XMALLOC call site (and clear it back to 0 on allocation failure) so the relationship between the buffer allocation and the recorded size is explicit and cannot drift if the surrounding control flow changes. --- src/pk.c | 8 +++----- src/pk_rsa.c | 10 ++++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/pk.c b/src/pk.c index f9cc7345ad1..a0df3af2650 100644 --- a/src/pk.c +++ b/src/pk.c @@ -7150,16 +7150,14 @@ static int pem_write_mem_pkcs8privatekey(byte** pem, int* pemSz, *pemSz += 54; } + allocSz = (word32)*pemSz; /* Allocate enough memory to hold PEM encoded encrypted key. */ - *pem = (byte*)XMALLOC((size_t)*pemSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + *pem = (byte*)XMALLOC((size_t)allocSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); if (*pem == NULL) { + allocSz = 0; res = 0; } else { - /* Remember the allocation size before *pemSz is updated to the - * actual PEM output length, so we can zero any unused tail that - * held the DER staging area. */ - allocSz = (word32)*pemSz; /* Use end of PEM buffer for key data. */ key = *pem + *pemSz - keySz; } diff --git a/src/pk_rsa.c b/src/pk_rsa.c index 9f5ba4488b0..682b1d28086 100644 --- a/src/pk_rsa.c +++ b/src/pk_rsa.c @@ -782,7 +782,7 @@ static int wolfSSL_RSA_To_Der_ex(WOLFSSL_RSA* rsa, byte** outBuf, int publicKey, { int ret = 1; int derSz = 0; - int derAllocSz = 0; + word32 derAllocSz = 0; byte* derBuf = NULL; WOLFSSL_ENTER("wolfSSL_RSA_To_Der"); @@ -824,7 +824,6 @@ static int wolfSSL_RSA_To_Der_ex(WOLFSSL_RSA* rsa, byte** outBuf, int publicKey, } } - derAllocSz = derSz; if ((ret == 1) && (outBuf != NULL)) { derBuf = *outBuf; if (derBuf == NULL) { @@ -835,6 +834,9 @@ static int wolfSSL_RSA_To_Der_ex(WOLFSSL_RSA* rsa, byte** outBuf, int publicKey, WOLFSSL_ERROR_MSG("Memory allocation failed"); ret = MEMORY_ERROR; } + else { + derAllocSz = (word32)derSz; + } } } if ((ret == 1) && (outBuf != NULL)) { @@ -868,8 +870,8 @@ static int wolfSSL_RSA_To_Der_ex(WOLFSSL_RSA* rsa, byte** outBuf, int publicKey, if ((outBuf != NULL) && (*outBuf != derBuf)) { /* Not returning buffer, needs to be disposed of. */ - if ((derBuf != NULL) && (publicKey == 0)) { - ForceZero(derBuf, (word32)derAllocSz); + if ((derBuf != NULL) && (publicKey == 0) && (derAllocSz > 0)) { + ForceZero(derBuf, derAllocSz); } XFREE(derBuf, heap, DYNAMIC_TYPE_TMP_BUFFER); }