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:
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -86,13 +125,13 @@ BSecureSocket::Private::VerifyCallback(int ok, X509_STORE_CTX* ctx)
|
|||||||
{
|
{
|
||||||
// OpenSSL already checked the certificate again the certificate store for
|
// OpenSSL already checked the certificate again the certificate store for
|
||||||
// us, and tells the result of that in the ok parameter.
|
// us, and tells the result of that in the ok parameter.
|
||||||
|
|
||||||
// If the verification succeeded, no need for any further checks. Let's
|
// If the verification succeeded, no need for any further checks. Let's
|
||||||
// proceed with the connection.
|
// proceed with the connection.
|
||||||
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,32 +187,43 @@ 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) {
|
||||||
TRACE("SSLConnection can't connect\n");
|
TRACE("SSLConnection can't connect\n");
|
||||||
BSocket::Disconnect();
|
BSocket::Disconnect();
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user