Fix concurrency issues in BSecureSocket

* Use pthread_once to initialize the SSL context once, in a thread-safe
way.
* Do not delete the BIO immediately when closing a connexion, instead
delay this to the destructor. This makes sure the protocol loop is done
running when we do that.
* Instead of creating a new BIO when we reconnect an already used
connection, create the BIO upfront, and reuse it with the new file
descriptor.
* Fix a memory leak: the SSL struct from OpenSSL was never freed, only
the BIO was.

Fixes #10414.
This commit is contained in:
Adrien Destugues
2014-01-16 11:25:47 +01:00
parent 67af469ef0
commit b70c72a692
+91 -44
View File
@@ -12,6 +12,8 @@
# include <openssl/ssl.h> # include <openssl/ssl.h>
#endif #endif
#include <pthread.h>
#include <Certificate.h> #include <Certificate.h>
#include <FindDirectory.h> #include <FindDirectory.h>
#include <Path.h> #include <Path.h>
@@ -32,8 +34,15 @@
class BSecureSocket::Private { class BSecureSocket::Private {
public: public:
Private();
~Private();
status_t InitCheck();
static SSL_CTX* Context(); static SSL_CTX* Context();
static int VerifyCallback(int ok, X509_STORE_CTX* ctx); static int VerifyCallback(int ok, X509_STORE_CTX* ctx);
private:
static void CreateContext();
public: public:
SSL* fSSL; SSL* fSSL;
BIO* fBIO; BIO* fBIO;
@@ -41,13 +50,60 @@ public:
private: private:
static SSL_CTX* sContext; static SSL_CTX* sContext;
// FIXME When do we SSL_CTX_free it? // FIXME When do we SSL_CTX_free it?
static vint32 sInitOnce; static pthread_once_t sInitOnce;
}; };
/* static */ SSL_CTX* BSecureSocket::Private::sContext = NULL; /* static */ SSL_CTX* BSecureSocket::Private::sContext = NULL;
/* static */ int BSecureSocket::Private::sDataIndex; /* static */ int BSecureSocket::Private::sDataIndex;
/* static */ vint32 BSecureSocket::Private::sInitOnce = false; /* static */ pthread_once_t BSecureSocket::Private::sInitOnce
= PTHREAD_ONCE_INIT;
BSecureSocket::Private::Private()
:
fSSL(NULL),
fBIO(BIO_new(BIO_s_socket()))
{
}
BSecureSocket::Private::~Private()
{
// SSL_free also frees the underlying BIO.
if (fSSL != NULL)
SSL_free(fSSL);
}
status_t
BSecureSocket::Private::InitCheck()
{
if (fBIO == NULL)
return B_NO_MEMORY;
return B_OK;
}
/* static */ void
BSecureSocket::Private::CreateContext()
{
sContext = SSL_CTX_new(SSLv23_method());
// Setup certificate verification
BPath certificateStore;
find_directory(B_SYSTEM_DATA_DIRECTORY, &certificateStore);
certificateStore.Append("ssl/CARootCertificates.pem");
// TODO we may want to add a non-packaged certificate directory?
// (would make it possible to store user-added certificate exceptions
// there)
SSL_CTX_load_verify_locations(sContext, certificateStore.Path(), NULL);
SSL_CTX_set_verify(sContext, SSL_VERIFY_PEER, VerifyCallback);
// Get an unique index number for storing application data in SSL
// structs. We will store a pointer to the BSecureSocket class there.
sDataIndex = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
}
/* static */ SSL_CTX* /* static */ SSL_CTX*
@@ -56,24 +112,7 @@ BSecureSocket::Private::Context()
// We use lazy initialisation here, because reading certificates from disk // We use lazy initialisation here, because reading certificates from disk
// and parsing them is a relatively long operation and uses some memory. // and parsing them is a relatively long operation and uses some memory.
// We don't want programs that don't use SSL to waste resources with that. // We don't want programs that don't use SSL to waste resources with that.
if (!sInitOnce) { pthread_once(&sInitOnce, CreateContext);
sInitOnce = true;
sContext = SSL_CTX_new(SSLv23_method());
// Setup certificate verification
BPath certificateStore;
find_directory(B_SYSTEM_DATA_DIRECTORY, &certificateStore);
certificateStore.Append("ssl/CARootCertificates.pem");
// TODO we may want to add a non-packaged certificate directory?
// (would make it possible to store user-added certificate exceptions
// there)
SSL_CTX_load_verify_locations(sContext, certificateStore.Path(), NULL);
SSL_CTX_set_verify(sContext, SSL_VERIFY_PEER, VerifyCallback);
// Get an unique index number for storing application data in SSL
// structs. We will store a pointer to the BSecureSocket class there.
sDataIndex = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
}
return sContext; return sContext;
} }
@@ -92,7 +131,7 @@ BSecureSocket::Private::VerifyCallback(int ok, X509_STORE_CTX* ctx)
if (ok) if (ok)
return ok; return ok;
// The certificate verification failed. Signal this to the caller, and fail. // The certificate verification failed. Signal this to the BSecureSocket.
// First of all, get the affected BSecureSocket // First of all, get the affected BSecureSocket
SSL* ssl = (SSL*)X509_STORE_CTX_get_ex_data(ctx, SSL* ssl = (SSL*)X509_STORE_CTX_get_ex_data(ctx,
@@ -104,6 +143,7 @@ BSecureSocket::Private::VerifyCallback(int ok, X509_STORE_CTX* ctx)
// chain) // chain)
X509* certificate = X509_STORE_CTX_get_current_cert(ctx); X509* certificate = X509_STORE_CTX_get_current_cert(ctx);
// Let the BSecureSocket (or subclass) decide if we should continue anyway.
return socket->CertificateVerificationFailed(BCertificate( return socket->CertificateVerificationFailed(BCertificate(
new BCertificate::Private(certificate))); new BCertificate::Private(certificate)));
} }
@@ -114,15 +154,17 @@ BSecureSocket::Private::VerifyCallback(int ok, X509_STORE_CTX* ctx)
BSecureSocket::BSecureSocket() BSecureSocket::BSecureSocket()
: :
fPrivate(NULL) fPrivate(new BSecureSocket::Private())
{ {
fInitStatus = fPrivate != NULL ? fPrivate->InitCheck() : B_NO_MEMORY;
} }
BSecureSocket::BSecureSocket(const BNetworkAddress& peer, bigtime_t timeout) BSecureSocket::BSecureSocket(const BNetworkAddress& peer, bigtime_t timeout)
: :
fPrivate(NULL) fPrivate(new BSecureSocket::Private())
{ {
fInitStatus = fPrivate != NULL ? fPrivate->InitCheck() : B_NO_MEMORY;
Connect(peer, timeout); Connect(peer, timeout);
} }
@@ -131,12 +173,13 @@ BSecureSocket::BSecureSocket(const BSecureSocket& other)
: :
BSocket(other) BSocket(other)
{ {
fPrivate = (BSecureSocket::Private*)malloc(sizeof(BSecureSocket::Private)); fPrivate = new BSecureSocket::Private(*other.fPrivate);
if (fPrivate != NULL) { // TODO: this won't work this way! - write working copy constructor for
memcpy(fPrivate, other.fPrivate, sizeof(BSecureSocket::Private)); // Private.
// TODO: this won't work this way!
if (fPrivate != NULL)
SSL_set_ex_data(fPrivate->fSSL, Private::sDataIndex, this); SSL_set_ex_data(fPrivate->fSSL, Private::sDataIndex, this);
} else else
fInitStatus = B_NO_MEMORY; fInitStatus = B_NO_MEMORY;
} }
@@ -144,30 +187,41 @@ BSecureSocket::BSecureSocket(const BSecureSocket& other)
BSecureSocket::~BSecureSocket() BSecureSocket::~BSecureSocket()
{ {
free(fPrivate); delete fPrivate;
} }
status_t status_t
BSecureSocket::Connect(const BNetworkAddress& peer, bigtime_t timeout) BSecureSocket::Connect(const BNetworkAddress& peer, bigtime_t timeout)
{ {
if (fPrivate == NULL) { if (fPrivate == NULL)
fPrivate = (BSecureSocket::Private*)calloc(1, return B_NO_MEMORY;
sizeof(BSecureSocket::Private));
if (fPrivate == NULL) status_t state = fPrivate->InitCheck();
return B_NO_MEMORY; if (state != B_OK)
} return state;
status_t status = BSocket::Connect(peer, timeout); status_t status = BSocket::Connect(peer, timeout);
if (status != B_OK) if (status != B_OK)
return status; return status;
// Do this only after BSocket::Connect has checked wether we're already
// connected. We don't want to kill an existing SSL session, as that would
// likely crash the protocol loop for it.
if (fPrivate->fSSL != NULL) {
SSL_free(fPrivate->fSSL);
}
fPrivate->fSSL = SSL_new(BSecureSocket::Private::Context()); fPrivate->fSSL = SSL_new(BSecureSocket::Private::Context());
fPrivate->fBIO = BIO_new_socket(fSocket, BIO_NOCLOSE); if (fPrivate->fSSL == NULL) {
BSocket::Disconnect();
return B_NO_MEMORY;
}
BIO_set_fd(fPrivate->fBIO, fSocket, BIO_NOCLOSE);
SSL_set_bio(fPrivate->fSSL, fPrivate->fBIO, fPrivate->fBIO); SSL_set_bio(fPrivate->fSSL, fPrivate->fBIO, fPrivate->fBIO);
SSL_set_ex_data(fPrivate->fSSL, Private::sDataIndex, this); SSL_set_ex_data(fPrivate->fSSL, Private::sDataIndex, this);
printf("Connecting %p\n", fPrivate->fSSL);
int sslStatus = SSL_connect(fPrivate->fSSL); int sslStatus = SSL_connect(fPrivate->fSSL);
if (sslStatus <= 0) { if (sslStatus <= 0) {
@@ -199,16 +253,9 @@ BSecureSocket::Disconnect()
if (IsConnected()) { if (IsConnected()) {
if (fPrivate->fSSL != NULL) { if (fPrivate->fSSL != NULL) {
SSL_shutdown(fPrivate->fSSL); SSL_shutdown(fPrivate->fSSL);
fPrivate->fSSL = NULL;
} }
BSocket::Disconnect(); BSocket::Disconnect();
// Must do this before freeing the BIO, to make sure any pending
// read or write gets unlocked properly.
if (fPrivate->fBIO != NULL) {
BIO_free(fPrivate->fBIO);
fPrivate->fBIO = NULL;
}
} }
} }