Skip to content

Commit 2c4611c

Browse files
committed
fix(securesocket): preserve sync TLS behavior with memory BIO and harden io_uring edge handling
* Bridge synchronous CSecureSocket read/write paths when memory BIO is active by ferrying encrypted data between the OS socket and OpenSSL BIOs, preventing spin loops after async handshake. * Add strict BIO_write result checks in async read/write handlers and clamp SSL_read request sizing for int-safe bounds. * Add TLS async regression tests that verify synchronous I/O still works after async connect and async accept handshakes. Signed-off-by: Dan S. Camper <dan.camper@lexisnexisrisk.com>
1 parent 7677218 commit 2c4611c

2 files changed

Lines changed: 523 additions & 28 deletions

File tree

system/security/securesocket/securesocket.cpp

Lines changed: 167 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "jliball.hpp"
2525
#include "string.h"
2626
#include <atomic>
27+
#include <limits>
2728
#include <mutex>
2829

2930
#ifdef _WIN32
@@ -364,7 +365,8 @@ class CAsyncTLSWriteHandler : public CAsyncTLSHandlerBase
364365
{
365366
case TLSWriteState::WantRead:
366367
// Renegotiation data arrived, feed to BIO and retry write
367-
BIO_write(network_bio, encryptedBuffer.bytes(), result);
368+
if (BIO_write(network_bio, encryptedBuffer.bytes(), result) != result)
369+
return handleError(-1);
368370
complete = safeTryOperation();
369371
break;
370372

@@ -598,6 +600,8 @@ class CSecureSocket : implements ISecureSocket, public CInterface
598600
private:
599601
StringBuffer& get_cn(X509* cert, StringBuffer& cn);
600602
void handleError(int ssl_err, bool writing, bool wait, unsigned timeoutMs, const char *opStr);
603+
bool flushPendingEncryptedOutput(CCycleTimer &timer, unsigned timeoutMs, const char *opStr);
604+
size32_t readAndQueueEncryptedSocketInput(CCycleTimer &timer, unsigned timeoutMs, const char *opStr);
601605

602606
// Make verify_cert and network_bio accessible to async handlers
603607
friend class CAsyncTLSAcceptHandler;
@@ -1693,7 +1697,8 @@ bool CAsyncTLSReadHandler::onAsyncComplete(int result)
16931697
if (state == TLSReadState::WantRead && result > 0)
16941698
{
16951699
// Feed received encrypted bytes to SSL's network BIO
1696-
BIO_write(network_bio, encryptedBuffer.bytes(), result);
1700+
if (BIO_write(network_bio, encryptedBuffer.bytes(), result) != result)
1701+
return handleError(-1);
16971702
state = TLSReadState::Reading;
16981703
}
16991704
else if (state == TLSReadState::WantWrite)
@@ -1735,8 +1740,9 @@ bool CAsyncTLSReadHandler::tryRead()
17351740
while (totalRead < minSize && totalRead < maxSize)
17361741
{
17371742
size32_t bytesToRead = maxSize - totalRead;
1738-
1739-
int ret = SSL_read(ssl, (char*)appBuffer + totalRead, bytesToRead);
1743+
size32_t readChunk = std::min(bytesToRead, (size32_t)std::numeric_limits<int>::max());
1744+
1745+
int ret = SSL_read(ssl, (char*)appBuffer + totalRead, (int)readChunk);
17401746

17411747
if (ret > 0)
17421748
{
@@ -1974,6 +1980,39 @@ void CSecureSocket::handleError(int ssl_err, bool writing, bool wait, unsigned t
19741980
}
19751981
}
19761982

1983+
bool CSecureSocket::flushPendingEncryptedOutput(CCycleTimer &timer, unsigned timeoutMs, const char *opStr)
1984+
{
1985+
byte encryptedWriteBuffer[16384];
1986+
1987+
bool flushed = false;
1988+
while (true)
1989+
{
1990+
int pending = BIO_pending(network_bio);
1991+
if (pending <= 0)
1992+
return flushed;
1993+
1994+
size32_t chunk = std::min((size32_t)pending, (size32_t)sizeof(encryptedWriteBuffer));
1995+
int bytesRead = BIO_read(network_bio, encryptedWriteBuffer, chunk);
1996+
if (bytesRead <= 0)
1997+
THROWJSOCKEXCEPTION_MSG(-1, VStringBuffer("%s: failed to drain encrypted TLS output", opStr).str());
1998+
1999+
unsigned remainingMs = timer.remainingMs(timeoutMs);
2000+
m_socket->writetms(encryptedWriteBuffer, bytesRead, bytesRead, remainingMs);
2001+
flushed = true;
2002+
}
2003+
}
2004+
2005+
size32_t CSecureSocket::readAndQueueEncryptedSocketInput(CCycleTimer &timer, unsigned timeoutMs, const char *opStr)
2006+
{
2007+
byte encryptedReadBuffer[16384];
2008+
size32_t encryptedRead = 0;
2009+
unsigned remainingMs = timer.remainingMs(timeoutMs);
2010+
m_socket->readtms(encryptedReadBuffer, 1, sizeof(encryptedReadBuffer), encryptedRead, remainingMs);
2011+
if (encryptedRead > 0 && BIO_write(network_bio, encryptedReadBuffer, encryptedRead) != encryptedRead)
2012+
THROWJSOCKEXCEPTION_MSG(-1, VStringBuffer("%s: failed to queue encrypted socket input", opStr).str());
2013+
return encryptedRead;
2014+
}
2015+
19772016
int CSecureSocket::secure_connect(int logLevel)
19782017
{
19792018
if (m_fqdn.length() > 0)
@@ -2052,10 +2091,85 @@ void CSecureSocket::readtms(void* buf, size32_t min_size, size32_t max_size, siz
20522091
// because we may not get another poll notification because SSL internally has read everything.
20532092
sizeRead = 0;
20542093
CCycleTimer timer;
2094+
2095+
if (network_bio)
2096+
{
2097+
2098+
while (true)
2099+
{
2100+
ERR_clear_error();
2101+
size32_t bytesToRead = max_size - sizeRead;
2102+
size32_t readChunk = std::min(bytesToRead, (size32_t)std::numeric_limits<int>::max());
2103+
int rc = SSL_read(m_ssl, (char *)buf + sizeRead, (int)readChunk);
2104+
unsigned remainingMs = timer.remainingMs(timeoutMs);
2105+
if (rc > 0)
2106+
{
2107+
sizeRead += rc;
2108+
if (sizeRead == max_size)
2109+
break;
2110+
if (0 == remainingMs)
2111+
{
2112+
if (sizeRead >= min_size)
2113+
break;
2114+
THROWJSOCKEXCEPTION_MSG(JSOCKERR_timeout_expired, "timeout expired");
2115+
}
2116+
continue;
2117+
}
2118+
2119+
if (0 == rc)
2120+
{
2121+
m_socket->shutdownNoThrow();
2122+
if (suppresGCIfMinSize && (sizeRead >= min_size))
2123+
break;
2124+
THROWJSOCKEXCEPTION(JSOCKERR_graceful_close);
2125+
}
2126+
2127+
int ssl_err = SSL_get_error(m_ssl, rc);
2128+
if (ssl_err == SSL_ERROR_WANT_READ)
2129+
{
2130+
if (sizeRead >= min_size)
2131+
break;
2132+
2133+
flushPendingEncryptedOutput(timer, timeoutMs, "SSL_read");
2134+
2135+
size32_t encryptedRead = readAndQueueEncryptedSocketInput(timer, timeoutMs, "SSL_read");
2136+
if (encryptedRead == 0)
2137+
{
2138+
m_socket->shutdownNoThrow();
2139+
if (suppresGCIfMinSize && (sizeRead >= min_size))
2140+
break;
2141+
THROWJSOCKEXCEPTION(JSOCKERR_graceful_close);
2142+
}
2143+
continue;
2144+
}
2145+
2146+
if (ssl_err == SSL_ERROR_WANT_WRITE)
2147+
{
2148+
if (sizeRead >= min_size)
2149+
break;
2150+
2151+
if (flushPendingEncryptedOutput(timer, timeoutMs, "SSL_read"))
2152+
continue;
2153+
2154+
// No TLS output to flush; use wait/write-error handling instead of spinning.
2155+
handleError(ssl_err, false, true, remainingMs, "SSL_read");
2156+
continue;
2157+
}
2158+
2159+
bool wait = sizeRead < min_size; // if >= min_size, then handleError will validate errors only
2160+
handleError(ssl_err, false, wait, remainingMs, "SSL_read");
2161+
if (sizeRead >= min_size)
2162+
break;
2163+
}
2164+
}
2165+
else
2166+
{
20552167
while (true)
20562168
{
20572169
ERR_clear_error();
2058-
int rc = SSL_read(m_ssl, (char*)buf + sizeRead, max_size - sizeRead);
2170+
size32_t bytesToRead = max_size - sizeRead;
2171+
size32_t readChunk = std::min(bytesToRead, (size32_t)std::numeric_limits<int>::max());
2172+
int rc = SSL_read(m_ssl, (char*)buf + sizeRead, (int)readChunk);
20592173
unsigned remainingMs = timer.remainingMs(timeoutMs);
20602174
if (rc > 0)
20612175
{
@@ -2087,6 +2201,7 @@ void CSecureSocket::readtms(void* buf, size32_t min_size, size32_t max_size, siz
20872201
break;
20882202
}
20892203
}
2204+
}
20902205

20912206
cycle_t elapsedCycles = timer.elapsedCycles();
20922207
if (!SSTATS)
@@ -2116,6 +2231,52 @@ size32_t CSecureSocket::writetms(void const* buf, size32_t minSize, size32_t siz
21162231
size32_t bytesWritten = 0;
21172232
size32_t bytesRemaining = size;
21182233
char *cbuf = (char *)buf;
2234+
2235+
if (network_bio)
2236+
{
2237+
2238+
ERR_clear_error();
2239+
while (bytesRemaining)
2240+
{
2241+
size32_t bytesChunk = MIN(bytesRemaining, 0x4000);
2242+
int rc = SSL_write(m_ssl, &cbuf[bytesWritten], bytesChunk);
2243+
if (rc > 0)
2244+
{
2245+
dbgassertex(bytesChunk == rc);
2246+
bytesRemaining -= rc;
2247+
bytesWritten += rc;
2248+
flushPendingEncryptedOutput(timer, timeoutMs, "SSL_write");
2249+
continue;
2250+
}
2251+
2252+
int ssl_err = SSL_get_error(m_ssl, rc);
2253+
unsigned remainingMs = timer.remainingMs(timeoutMs);
2254+
if (ssl_err == SSL_ERROR_WANT_READ)
2255+
{
2256+
flushPendingEncryptedOutput(timer, timeoutMs, "SSL_write");
2257+
2258+
size32_t encryptedRead = readAndQueueEncryptedSocketInput(timer, timeoutMs, "SSL_write");
2259+
if (encryptedRead == 0)
2260+
{
2261+
m_socket->shutdownNoThrow();
2262+
THROWJSOCKEXCEPTION(JSOCKERR_graceful_close);
2263+
}
2264+
continue;
2265+
}
2266+
2267+
if (ssl_err == SSL_ERROR_WANT_WRITE)
2268+
{
2269+
if (flushPendingEncryptedOutput(timer, timeoutMs, "SSL_write"))
2270+
continue;
2271+
}
2272+
2273+
handleError(ssl_err, true, true, remainingMs, "SSL_write");
2274+
}
2275+
2276+
flushPendingEncryptedOutput(timer, timeoutMs, "SSL_write");
2277+
}
2278+
else
2279+
{
21192280
ERR_clear_error();
21202281
while (bytesRemaining)
21212282
{
@@ -2135,6 +2296,7 @@ size32_t CSecureSocket::writetms(void const* buf, size32_t minSize, size32_t siz
21352296
handleError(ssl_err, true, true, remainingMs, "SSL_write");
21362297
}
21372298
}
2299+
}
21382300

21392301
cycle_t elapsedCycles = timer.elapsedCycles();
21402302
if (!SSTATS)

0 commit comments

Comments
 (0)