Skip to content

Commit 8c6ea61

Browse files
Fix leak on multiple SSL server connections
Fixes esp8266#4302 The refcnt setup for the WiFiClientSecure's SSLContext and ClientContext had issues in certain conditions, causing a massive memory leak on each SSL server connection. Depending on the state of the machine, after two or three connections it would OOM and crash. This patch replaces most of the refcnt operations with C++11 shared_ptr operations, cleaning up the code substantially and removing the leakage. Also fixes a race condition where ClientContext was free'd before the SSLContext was stopped/shutdown. When the SSLContext tried to do ssl_free, axtls would attempt to send out the real SSL disconnect bits over the wire, however by this time the ClientContext is invalid and it would fault.
1 parent c3bd91a commit 8c6ea61

File tree

2 files changed

+53
-67
lines changed

2 files changed

+53
-67
lines changed

libraries/ESP8266WiFi/src/WiFiClientSecure.cpp

+52-64
Original file line numberDiff line numberDiff line change
@@ -84,27 +84,20 @@ class SSLContext
8484

8585
~SSLContext()
8686
{
87-
if (_ssl) {
88-
ssl_free(_ssl);
89-
_ssl = nullptr;
87+
if (io_ctx) {
88+
io_ctx->unref();
89+
io_ctx = NULL;
9090
}
91-
91+
_ssl = nullptr;
9292
--_ssl_ctx_refcnt;
9393
if (_ssl_ctx_refcnt == 0) {
9494
ssl_ctx_free(_ssl_ctx);
9595
}
9696
}
9797

98-
void ref()
98+
static void _delete_shared_SSL(SSL *_to_del)
9999
{
100-
++_refcnt;
101-
}
102-
103-
void unref()
104-
{
105-
if (--_refcnt == 0) {
106-
delete this;
107-
}
100+
ssl_free(_to_del);
108101
}
109102

110103
void connect(ClientContext* ctx, const char* hostName, uint32_t timeout_ms)
@@ -116,17 +109,23 @@ class SSLContext
116109
ssl_free will want to send a close notify alert, but the old TCP connection
117110
is already gone at this point, so reset io_ctx. */
118111
io_ctx = nullptr;
119-
ssl_free(_ssl);
112+
_ssl = nullptr;
120113
_available = 0;
121114
_read_ptr = nullptr;
122115
}
123116
io_ctx = ctx;
124-
_ssl = ssl_client_new(_ssl_ctx, reinterpret_cast<int>(this), nullptr, 0, ext);
117+
ctx->ref();
118+
119+
// Wrap the new SSL with a smart pointer, custom deleter to call ssl_free
120+
SSL *_new_ssl = ssl_client_new(_ssl_ctx, reinterpret_cast<int>(this), nullptr, 0, ext);
121+
std::shared_ptr<SSL> _new_ssl_shared(_new_ssl, _delete_shared_SSL);
122+
_ssl = _new_ssl_shared;
123+
125124
uint32_t t = millis();
126125

127-
while (millis() - t < timeout_ms && ssl_handshake_status(_ssl) != SSL_OK) {
126+
while (millis() - t < timeout_ms && ssl_handshake_status(_ssl.get()) != SSL_OK) {
128127
uint8_t* data;
129-
int rc = ssl_read(_ssl, &data);
128+
int rc = ssl_read(_ssl.get(), &data);
130129
if (rc < SSL_OK) {
131130
ssl_display_error(rc);
132131
break;
@@ -136,30 +135,40 @@ class SSLContext
136135

137136
void connectServer(ClientContext *ctx) {
138137
io_ctx = ctx;
139-
_ssl = ssl_server_new(_ssl_ctx, reinterpret_cast<int>(this));
138+
ctx->ref();
139+
140+
// Wrap the new SSL with a smart pointer, custom deleter to call ssl_free
141+
SSL *_new_ssl = ssl_server_new(_ssl_ctx, reinterpret_cast<int>(this));
142+
std::shared_ptr<SSL> _new_ssl_shared(_new_ssl, _delete_shared_SSL);
143+
_ssl = _new_ssl_shared;
144+
140145
_isServer = true;
141146

142147
uint32_t timeout_ms = 5000;
143148
uint32_t t = millis();
144149

145-
while (millis() - t < timeout_ms && ssl_handshake_status(_ssl) != SSL_OK) {
150+
while (millis() - t < timeout_ms && ssl_handshake_status(_ssl.get()) != SSL_OK) {
146151
uint8_t* data;
147-
int rc = ssl_read(_ssl, &data);
152+
int rc = ssl_read(_ssl.get(), &data);
148153
if (rc < SSL_OK) {
154+
ssl_display_error(rc);
149155
break;
150156
}
151157
}
152158
}
153159

154160
void stop()
155161
{
162+
if (io_ctx) {
163+
io_ctx->unref();
164+
}
156165
io_ctx = nullptr;
157166
}
158167

159168
bool connected()
160169
{
161170
if (_isServer) return _ssl != nullptr;
162-
else return _ssl != nullptr && ssl_handshake_status(_ssl) == SSL_OK;
171+
else return _ssl != nullptr && ssl_handshake_status(_ssl.get()) == SSL_OK;
163172
}
164173

165174
int read(uint8_t* dst, size_t size)
@@ -302,7 +311,7 @@ class SSLContext
302311

303312
bool verifyCert()
304313
{
305-
int rc = ssl_verify_cert(_ssl);
314+
int rc = ssl_verify_cert(_ssl.get());
306315
if (_allowSelfSignedCerts && rc == SSL_X509_ERROR(X509_VFY_ERROR_SELF_SIGNED)) {
307316
DEBUGV("Allowing self-signed certificate\n");
308317
return true;
@@ -321,12 +330,14 @@ class SSLContext
321330

322331
operator SSL*()
323332
{
324-
return _ssl;
333+
return _ssl.get();
325334
}
326335

327336
static ClientContext* getIOContext(int fd)
328337
{
329-
return reinterpret_cast<SSLContext*>(fd)->io_ctx;
338+
if (!fd) return NULL;
339+
SSLContext *thisSSL = reinterpret_cast<SSLContext*>(fd);
340+
return thisSSL->io_ctx;
330341
}
331342

332343
int loadServerX509Cert(const uint8_t *cert, int len) {
@@ -347,10 +358,9 @@ class SSLContext
347358
optimistic_yield(100);
348359

349360
uint8_t* data;
350-
int rc = ssl_read(_ssl, &data);
361+
int rc = ssl_read(_ssl.get(), &data);
351362
if (rc <= 0) {
352363
if (rc < SSL_OK && rc != SSL_CLOSE_NOTIFY && rc != SSL_ERROR_CONN_LOST) {
353-
ssl_free(_ssl);
354364
_ssl = nullptr;
355365
}
356366
return 0;
@@ -367,7 +377,7 @@ class SSLContext
367377
return 0;
368378
}
369379

370-
int rc = ssl_write(_ssl, src, size);
380+
int rc = ssl_write(_ssl.get(), src, size);
371381
if (rc >= 0) {
372382
return rc;
373383
}
@@ -410,12 +420,11 @@ class SSLContext
410420
{
411421
return !_writeBuffers.empty();
412422
}
413-
423+
public:
414424
bool _isServer = false;
415425
static SSL_CTX* _ssl_ctx;
416426
static int _ssl_ctx_refcnt;
417-
SSL* _ssl = nullptr;
418-
int _refcnt = 0;
427+
std::shared_ptr<SSL> _ssl = nullptr;
419428
const uint8_t* _read_ptr = nullptr;
420429
size_t _available = 0;
421430
BufferList _writeBuffers;
@@ -434,42 +443,28 @@ WiFiClientSecure::WiFiClientSecure()
434443

435444
WiFiClientSecure::~WiFiClientSecure()
436445
{
437-
if (_ssl) {
438-
_ssl->unref();
439-
}
446+
_ssl = nullptr;
440447
}
441448

442-
WiFiClientSecure::WiFiClientSecure(const WiFiClientSecure& other)
443-
: WiFiClient(static_cast<const WiFiClient&>(other))
444-
{
445-
_ssl = other._ssl;
446-
if (_ssl) {
447-
_ssl->ref();
449+
static void _delete_shared_SSLContext(SSLContext *_to_del)
450+
{
451+
delete _to_del;
448452
}
449-
}
450453

451-
WiFiClientSecure& WiFiClientSecure::operator=(const WiFiClientSecure& rhs)
452-
{
453-
(WiFiClient&) *this = rhs;
454-
_ssl = rhs._ssl;
455-
if (_ssl) {
456-
_ssl->ref();
457-
}
458-
return *this;
459-
}
460454

461455
// Only called by the WifiServerSecure, need to get the keys/certs loaded before beginning
462456
WiFiClientSecure::WiFiClientSecure(ClientContext* client, bool usePMEM, const uint8_t *rsakey, int rsakeyLen, const uint8_t *cert, int certLen)
463457
{
458+
// We've been given the client context from the available() call
464459
_client = client;
465-
if (_ssl) {
466-
_ssl->unref();
467-
_ssl = nullptr;
468-
}
460+
_client->ref();
469461

470-
_ssl = new SSLContext;
471-
_ssl->ref();
462+
// Make the "_ssl" SSLContext, in the constructor there should be none yet
463+
SSLContext *_new_ssl = new SSLContext;
464+
std::shared_ptr<SSLContext> _new_ssl_shared(_new_ssl, _delete_shared_SSLContext);
465+
_ssl = _new_ssl_shared;
472466

467+
_ssl = std::make_shared<SSLContext>();
473468
if (usePMEM) {
474469
// When using PMEM based certs, allocate stack and copy from flash to DRAM, call SSL functions to avoid
475470
// heap fragmentation that would happen w/malloc()
@@ -490,7 +485,6 @@ WiFiClientSecure::WiFiClientSecure(ClientContext* client, bool usePMEM, const ui
490485
_ssl->loadServerX509Cert(cert, certLen);
491486
}
492487
}
493-
_client->ref();
494488
_ssl->connectServer(client);
495489
}
496490

@@ -523,14 +517,12 @@ int WiFiClientSecure::connect(const String host, uint16_t port)
523517
int WiFiClientSecure::_connectSSL(const char* hostName)
524518
{
525519
if (!_ssl) {
526-
_ssl = new SSLContext;
527-
_ssl->ref();
520+
_ssl = std::make_shared<SSLContext>();
528521
}
529522
_ssl->connect(_client, hostName, _timeout);
530523

531524
auto status = ssl_handshake_status(*_ssl);
532525
if (status != SSL_OK) {
533-
_ssl->unref();
534526
_ssl = nullptr;
535527
return 0;
536528
}
@@ -550,7 +542,6 @@ size_t WiFiClientSecure::write(const uint8_t *buf, size_t size)
550542
}
551543

552544
if (rc != SSL_CLOSE_NOTIFY) {
553-
_ssl->unref();
554545
_ssl = nullptr;
555546
}
556547

@@ -653,8 +644,6 @@ void WiFiClientSecure::stop()
653644
{
654645
if (_ssl) {
655646
_ssl->stop();
656-
_ssl->unref();
657-
_ssl = nullptr;
658647
}
659648
WiFiClient::stop();
660649
}
@@ -772,8 +761,7 @@ bool WiFiClientSecure::verifyCertChain(const char* domain_name)
772761
void WiFiClientSecure::_initSSLContext()
773762
{
774763
if (!_ssl) {
775-
_ssl = new SSLContext;
776-
_ssl->ref();
764+
_ssl = std::make_shared<SSLContext>();
777765
}
778766
}
779767

libraries/ESP8266WiFi/src/WiFiClientSecure.h

+1-3
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ class WiFiClientSecure : public WiFiClient {
3232
public:
3333
WiFiClientSecure();
3434
~WiFiClientSecure() override;
35-
WiFiClientSecure(const WiFiClientSecure&);
36-
WiFiClientSecure& operator=(const WiFiClientSecure&);
3735

3836
int connect(IPAddress ip, uint16_t port) override;
3937
int connect(const String host, uint16_t port) override;
@@ -91,7 +89,7 @@ friend class WiFiServerSecure; // Needs access to custom constructor below
9189
int _connectSSL(const char* hostName);
9290
bool _verifyDN(const char* name);
9391

94-
SSLContext* _ssl = nullptr;
92+
std::shared_ptr<SSLContext> _ssl = nullptr;
9593
};
9694

9795
#endif //wificlientsecure_h

0 commit comments

Comments
 (0)