fix: evaluate wolfSSL_pending() under cs_hSocket in the recv-drain loop

The bulk-streaming recv-drain loop called wolfSSL_pending(pnode->ssl) after
releasing cs_hSocket, racing with SocketSendData (wolfSSL_write) and
CloseSocketDisconnect (wolfSSL_free) on the same TLS session -- a data race and
potential use-after-free on any TLS peer. Capture the pending-byte count inside
the cs_hSocket-locked block and use the captured value for the drain decision.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-08 05:48:39 +02:00
parent 19e1ce6f00
commit bf5b066a8d

View File

@@ -593,6 +593,7 @@ int TLSManager::threadSocketHandler(CNode* pnode, fd_set& fdsetRecv, fd_set& fds
int64_t nPassBytes = 0;
bool fKeepReading = true;
while (fKeepReading) {
int nSSLPending = 0;
if (nRecvBase + nPassBytes > (int64_t)ReceiveFloodSize())
break;
{
@@ -609,6 +610,10 @@ int TLSManager::threadSocketHandler(CNode* pnode, fd_set& fdsetRecv, fd_set& fds
wolfSSL_ERR_clear_error(); // clear the error queue, otherwise we may be reading an old error that occurred previously in the current thread
nBytes = wolfSSL_read(pnode->ssl, pchBuf, sizeof(pchBuf));
nRet = wolfSSL_get_error(pnode->ssl, nBytes);
// Capture TLS buffered-byte count while still under cs_hSocket; the drain-continuation
// check below runs unlocked, so touching pnode->ssl there would race with
// SocketSendData / CloseSocketDisconnect (which free ssl) -> data race / use-after-free.
nSSLPending = wolfSSL_pending(pnode->ssl);
} else {
nBytes = recv(pnode->hSocket, pchBuf, sizeof(pchBuf), MSG_DONTWAIT);
nRet = WSAGetLastError();
@@ -628,7 +633,7 @@ int TLSManager::threadSocketHandler(CNode* pnode, fd_set& fdsetRecv, fd_set& fds
// buffer, or TLS has buffered decrypted bytes) and within the per-pass cap.
// The flood ceiling is enforced pre-read at the top of the loop.
if (fKeepReading) {
bool fMore = (nBytes == (int)sizeof(pchBuf)) || (bIsSSL && wolfSSL_pending(pnode->ssl) > 0);
bool fMore = (nBytes == (int)sizeof(pchBuf)) || (bIsSSL && nSSLPending > 0);
if (!fMore || ++nDrainReads >= MAX_DRAIN_READS)
fKeepReading = false;
}