Skip to content

Fenrir fixes#10247

Open
julek-wolfssl wants to merge 11 commits intowolfSSL:masterfrom
julek-wolfssl:fenrir/20260417
Open

Fenrir fixes#10247
julek-wolfssl wants to merge 11 commits intowolfSSL:masterfrom
julek-wolfssl:fenrir/20260417

Conversation

@julek-wolfssl
Copy link
Copy Markdown
Member

2139
2140
2141
2142
2143
2144
2145
2146
2147
2148

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.
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.
F-2141

The error path in wolfSSL_PEM_write_mem_ECPrivateKey freed the EC
private key DER staging buffer without ForceZero. Zeroize before free.
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.
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.
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.
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.
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.
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.
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.
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.
Copilot AI review requested due to automatic review settings April 17, 2026 15:16
@julek-wolfssl julek-wolfssl self-assigned this Apr 17, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR hardens key-handling paths by zeroing sensitive buffers before freeing them, reducing the chance of plaintext key material lingering in heap memory.

Changes:

  • Add ForceZero() calls before XFREE() for RSA/EC/DSA key DER buffers and key-file buffers.
  • Track allocation sizes in a few paths to wipe the full allocation (including any unused tail) before freeing.
  • Add post-conversion cleanup for the PKCS#8 PEM-write path to clear DER staging remnants.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/ssl_load.c Zero RSA private key DER buffer before freeing in wolfSSL_CTX_use_RSAPrivateKey.
src/ssl.c Zero ephemeral key-file buffer before freeing in SetStaticEphemeralKey.
src/sniffer.c Zero watch-key buffers on both error and success paths before freeing.
src/pk_rsa.c Zero temporary RSA DER buffers before freeing; introduce derAllocSz tracking.
src/pk_ec.c Zero EC private key DER buffers on error paths before freeing.
src/pk.c Zero DER buffers before freeing in PEM/PKCS#8 conversion paths and track allocation size for tail wiping.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/pk_rsa.c
{
int ret = 1;
int derSz = 0;
int derAllocSz = 0;
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

derAllocSz is an int that is cast to word32 when passed to ForceZero(). If derSz can ever be negative (or otherwise invalid) on an error path, the cast can wrap to a very large value and cause an out-of-bounds wipe. Make derAllocSz an unsigned size type (e.g., word32/size_t) and only assign/call ForceZero() when the size is known to be > 0 (and ideally set it to the actual allocation size used for derBuf, not just the DER length).

Suggested change
int derAllocSz = 0;
word32 derAllocSz = 0;

Copilot uses AI. Check for mistakes.
Comment thread src/pk_rsa.c
}
}

derAllocSz = derSz;
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

derAllocSz is an int that is cast to word32 when passed to ForceZero(). If derSz can ever be negative (or otherwise invalid) on an error path, the cast can wrap to a very large value and cause an out-of-bounds wipe. Make derAllocSz an unsigned size type (e.g., word32/size_t) and only assign/call ForceZero() when the size is known to be > 0 (and ideally set it to the actual allocation size used for derBuf, not just the DER length).

Copilot uses AI. Check for mistakes.
Comment thread src/pk_rsa.c
Comment on lines +871 to +873
if ((derBuf != NULL) && (publicKey == 0)) {
ForceZero(derBuf, (word32)derAllocSz);
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

derAllocSz is an int that is cast to word32 when passed to ForceZero(). If derSz can ever be negative (or otherwise invalid) on an error path, the cast can wrap to a very large value and cause an out-of-bounds wipe. Make derAllocSz an unsigned size type (e.g., word32/size_t) and only assign/call ForceZero() when the size is known to be > 0 (and ideally set it to the actual allocation size used for derBuf, not just the DER length).

Copilot uses AI. Check for mistakes.
Comment thread src/pk.c
char password[NAME_SZ];
byte* key = NULL;
word32 keySz = 0;
word32 allocSz = 0;
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

The new wiping logic depends on allocSz being captured before *pemSz is updated, but allocSz is only set in one branch. If *pem is allocated and a failure occurs before that branch executes, allocSz may remain 0 and the buffer won’t be cleared even though it may contain sensitive staging data. Consider setting allocSz immediately after the PEM buffer allocation succeeds (at the point where the allocation size is definitively known) so all subsequent failure paths can reliably wipe the allocation.

Copilot uses AI. Check for mistakes.
Comment thread src/pk.c
Comment on lines +7159 to +7162
/* 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;
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

The new wiping logic depends on allocSz being captured before *pemSz is updated, but allocSz is only set in one branch. If *pem is allocated and a failure occurs before that branch executes, allocSz may remain 0 and the buffer won’t be cleared even though it may contain sensitive staging data. Consider setting allocSz immediately after the PEM buffer allocation succeeds (at the point where the allocation size is definitively known) so all subsequent failure paths can reliably wipe the allocation.

Copilot uses AI. Check for mistakes.
Comment thread src/pk.c
Comment on lines +7216 to +7228
/* 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);
}
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

The new wiping logic depends on allocSz being captured before *pemSz is updated, but allocSz is only set in one branch. If *pem is allocated and a failure occurs before that branch executes, allocSz may remain 0 and the buffer won’t be cleared even though it may contain sensitive staging data. Consider setting allocSz immediately after the PEM buffer allocation succeeds (at the point where the allocation size is definitively known) so all subsequent failure paths can reliably wipe the allocation.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown

MemBrowse Memory Report

No memory changes detected for:

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.

2 participants