MAJOR: threads/ssl: Make SSL part thread-safe

First, OpenSSL is now initialized to be thread-safe. This is done by setting 2
callbacks. The first one is ssl_locking_function. It handles the locks and
unlocks. The second one is ssl_id_function. It returns the current thread
id. During the init step, we create as much as R/W locks as needed, ie the
number returned by CRYPTO_num_locks function.

Next, The reusable SSL session in the server context is now thread-local.

Shctx is now also initialized if HAProxy is started with several threads.

And finally, a global lock has been added to protect the LRU cache used to store
generated certificates. The function ssl_sock_get_generated_cert is now
deprecated because the retrieved certificate can be removed by another threads
in same time. Instead, a new function has been added,
ssl_sock_assign_generated_cert. It must be used to search a certificate in the
cache and set it immediatly if found.
This commit is contained in:
Emeric Brun 2017-06-15 16:37:39 +02:00 committed by Willy Tarreau
parent 6b35e9bfbf
commit 821bb9beaa
4 changed files with 118 additions and 31 deletions

View File

@ -158,6 +158,8 @@ enum lock_label {
PEER_LOCK, PEER_LOCK,
BUF_WQ_LOCK, BUF_WQ_LOCK,
STRMS_LOCK, STRMS_LOCK,
SSL_LOCK,
SSL_GEN_CERTS_LOCK,
LOCK_LABELS LOCK_LABELS
}; };
struct lock_stat { struct lock_stat {
@ -244,7 +246,7 @@ static inline void show_lock_stats()
"TASK_RQ", "TASK_WQ", "POOL", "TASK_RQ", "TASK_WQ", "POOL",
"LISTENER", "LISTENER_QUEUE", "PROXY", "SERVER", "LISTENER", "LISTENER_QUEUE", "PROXY", "SERVER",
"UPDATED_SERVERS", "LBPRM", "SIGNALS", "STK_TABLE", "STK_SESS", "UPDATED_SERVERS", "LBPRM", "SIGNALS", "STK_TABLE", "STK_SESS",
"APPLETS", "PEER", "BUF_WQ", "STREAMS" }; "APPLETS", "PEER", "BUF_WQ", "STREAMS", "SSL", "SSL_GEN_CERTS"};
int lbl; int lbl;
for (lbl = 0; lbl < LOCK_LABELS; lbl++) { for (lbl = 0; lbl < LOCK_LABELS; lbl++) {

View File

@ -72,6 +72,7 @@ void ssl_free_dh(void);
void ssl_free_engines(void); void ssl_free_engines(void);
SSL_CTX *ssl_sock_create_cert(struct connection *conn, const char *servername, unsigned int key); SSL_CTX *ssl_sock_create_cert(struct connection *conn, const char *servername, unsigned int key);
SSL_CTX *ssl_sock_assign_generated_cert(unsigned int key, struct bind_conf *bind_conf, SSL *ssl);
SSL_CTX *ssl_sock_get_generated_cert(unsigned int key, struct bind_conf *bind_conf); SSL_CTX *ssl_sock_get_generated_cert(unsigned int key, struct bind_conf *bind_conf);
int ssl_sock_set_generated_cert(SSL_CTX *ctx, unsigned int key, struct bind_conf *bind_conf); int ssl_sock_set_generated_cert(SSL_CTX *ctx, unsigned int key, struct bind_conf *bind_conf);
unsigned int ssl_sock_generated_cert_key(const void *data, size_t len); unsigned int ssl_sock_generated_cert_key(const void *data, size_t len);

View File

@ -273,7 +273,7 @@ struct server {
char *sni_expr; /* Temporary variable to store a sample expression for SNI */ char *sni_expr; /* Temporary variable to store a sample expression for SNI */
struct { struct {
SSL_CTX *ctx; SSL_CTX *ctx;
SSL_SESSION *reused_sess; SSL_SESSION **reused_sess;
char *ciphers; /* cipher suite to use if non-null */ char *ciphers; /* cipher suite to use if non-null */
int options; /* ssl options */ int options; /* ssl options */
struct tls_version_filter methods; /* ssl methods */ struct tls_version_filter methods; /* ssl methods */

View File

@ -206,6 +206,51 @@ static struct {
.capture_cipherlist = 0, .capture_cipherlist = 0,
}; };
#ifdef USE_THREAD
static HA_RWLOCK_T *ssl_rwlocks;
unsigned long ssl_id_function(void)
{
return (unsigned long)tid;
}
void ssl_locking_function(int mode, int n, const char * file, int line)
{
if (mode & CRYPTO_LOCK) {
if (mode & CRYPTO_READ)
RWLOCK_RDLOCK(SSL_LOCK, &ssl_rwlocks[n]);
else
RWLOCK_WRLOCK(SSL_LOCK, &ssl_rwlocks[n]);
}
else {
if (mode & CRYPTO_READ)
RWLOCK_RDUNLOCK(SSL_LOCK, &ssl_rwlocks[n]);
else
RWLOCK_WRUNLOCK(SSL_LOCK, &ssl_rwlocks[n]);
}
}
static int ssl_locking_init(void)
{
int i;
ssl_rwlocks = malloc(sizeof(HA_RWLOCK_T)*CRYPTO_num_locks());
if (!ssl_rwlocks)
return -1;
for (i = 0 ; i < CRYPTO_num_locks() ; i++)
RWLOCK_INIT(&ssl_rwlocks[i]);
CRYPTO_set_id_callback(ssl_id_function);
CRYPTO_set_locking_callback(ssl_locking_function);
return 0;
}
#endif
/* This memory pool is used for capturing clienthello parameters. */ /* This memory pool is used for capturing clienthello parameters. */
struct ssl_capture { struct ssl_capture {
unsigned long long int xxh64; unsigned long long int xxh64;
@ -257,6 +302,12 @@ static char *x509v3_ext_values[X509V3_EXT_SIZE] = {
/* LRU cache to store generated certificate */ /* LRU cache to store generated certificate */
static struct lru64_head *ssl_ctx_lru_tree = NULL; static struct lru64_head *ssl_ctx_lru_tree = NULL;
static unsigned int ssl_ctx_lru_seed = 0; static unsigned int ssl_ctx_lru_seed = 0;
static unsigned int ssl_ctx_serial;
#ifdef USE_THREAD
static HA_RWLOCK_T ssl_ctx_lru_rwlock;
#endif
#endif // SSL_CTRL_SET_TLSEXT_HOSTNAME #endif // SSL_CTRL_SET_TLSEXT_HOSTNAME
static struct ssl_bind_kw ssl_bind_kws[]; static struct ssl_bind_kw ssl_bind_kws[];
@ -1588,8 +1639,6 @@ static int ssl_sock_advertise_alpn_protos(SSL *s, const unsigned char **out,
static SSL_CTX * static SSL_CTX *
ssl_sock_do_create_cert(const char *servername, struct bind_conf *bind_conf, SSL *ssl) ssl_sock_do_create_cert(const char *servername, struct bind_conf *bind_conf, SSL *ssl)
{ {
static unsigned int serial = 0;
X509 *cacert = bind_conf->ca_sign_cert; X509 *cacert = bind_conf->ca_sign_cert;
EVP_PKEY *capkey = bind_conf->ca_sign_pkey; EVP_PKEY *capkey = bind_conf->ca_sign_pkey;
SSL_CTX *ssl_ctx = NULL; SSL_CTX *ssl_ctx = NULL;
@ -1621,9 +1670,7 @@ ssl_sock_do_create_cert(const char *servername, struct bind_conf *bind_conf, SSL
* number */ * number */
if (X509_set_version(newcrt, 2L) != 1) if (X509_set_version(newcrt, 2L) != 1)
goto mkcert_error; goto mkcert_error;
if (!serial) ASN1_INTEGER_set(X509_get_serialNumber(newcrt), HA_ATOMIC_ADD(&ssl_ctx_serial, 1));
serial = now_ms;
ASN1_INTEGER_set(X509_get_serialNumber(newcrt), serial++);
/* Set duration for the certificate */ /* Set duration for the certificate */
if (!X509_gmtime_adj(X509_get_notBefore(newcrt), (long)-60*60*24) || if (!X509_gmtime_adj(X509_get_notBefore(newcrt), (long)-60*60*24) ||
@ -1742,20 +1789,36 @@ ssl_sock_create_cert(struct connection *conn, const char *servername, unsigned i
} }
/* Do a lookup for a certificate in the LRU cache used to store generated /* Do a lookup for a certificate in the LRU cache used to store generated
* certificates. */ * certificates and immediately assign it to the SSL session if not null. */
SSL_CTX * SSL_CTX *
ssl_sock_get_generated_cert(unsigned int key, struct bind_conf *bind_conf) ssl_sock_assign_generated_cert(unsigned int key, struct bind_conf *bind_conf, SSL *ssl)
{ {
struct lru64 *lru = NULL; struct lru64 *lru = NULL;
if (ssl_ctx_lru_tree) { if (ssl_ctx_lru_tree) {
RWLOCK_RDLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock);
lru = lru64_lookup(key, ssl_ctx_lru_tree, bind_conf->ca_sign_cert, 0); lru = lru64_lookup(key, ssl_ctx_lru_tree, bind_conf->ca_sign_cert, 0);
if (lru && lru->domain) if (lru && lru->domain) {
if (ssl)
SSL_set_SSL_CTX(ssl, (SSL_CTX *)lru->data);
RWLOCK_RDUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock);
return (SSL_CTX *)lru->data; return (SSL_CTX *)lru->data;
}
RWLOCK_RDUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock);
} }
return NULL; return NULL;
} }
/* Same as <ssl_sock_assign_generated_cert> but without SSL session. This
* function is not thread-safe, it should only be used to check if a certificate
* exists in the lru cache (with no warranty it will not be removed by another
* thread). It is kept for backward compatibility. */
SSL_CTX *
ssl_sock_get_generated_cert(unsigned int key, struct bind_conf *bind_conf)
{
return ssl_sock_assign_generated_cert(key, bind_conf, NULL);
}
/* Set a certificate int the LRU cache used to store generated /* Set a certificate int the LRU cache used to store generated
* certificate. Return 0 on success, otherwise -1 */ * certificate. Return 0 on success, otherwise -1 */
int int
@ -1764,12 +1827,16 @@ ssl_sock_set_generated_cert(SSL_CTX *ssl_ctx, unsigned int key, struct bind_conf
struct lru64 *lru = NULL; struct lru64 *lru = NULL;
if (ssl_ctx_lru_tree) { if (ssl_ctx_lru_tree) {
RWLOCK_WRLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock);
lru = lru64_get(key, ssl_ctx_lru_tree, bind_conf->ca_sign_cert, 0); lru = lru64_get(key, ssl_ctx_lru_tree, bind_conf->ca_sign_cert, 0);
if (!lru) if (!lru) {
RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock);
return -1; return -1;
}
if (lru->domain && lru->data) if (lru->domain && lru->data)
lru->free((SSL_CTX *)lru->data); lru->free((SSL_CTX *)lru->data);
lru64_commit(lru, ssl_ctx, bind_conf->ca_sign_cert, 0, (void (*)(void *))SSL_CTX_free); lru64_commit(lru, ssl_ctx, bind_conf->ca_sign_cert, 0, (void (*)(void *))SSL_CTX_free);
RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock);
return 0; return 0;
} }
return -1; return -1;
@ -1795,6 +1862,7 @@ ssl_sock_generate_certificate(const char *servername, struct bind_conf *bind_con
key = ssl_sock_generated_cert_key(servername, strlen(servername)); key = ssl_sock_generated_cert_key(servername, strlen(servername));
if (ssl_ctx_lru_tree) { if (ssl_ctx_lru_tree) {
RWLOCK_WRLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock);
lru = lru64_get(key, ssl_ctx_lru_tree, cacert, 0); lru = lru64_get(key, ssl_ctx_lru_tree, cacert, 0);
if (lru && lru->domain) if (lru && lru->domain)
ssl_ctx = (SSL_CTX *)lru->data; ssl_ctx = (SSL_CTX *)lru->data;
@ -1803,6 +1871,7 @@ ssl_sock_generate_certificate(const char *servername, struct bind_conf *bind_con
lru64_commit(lru, ssl_ctx, cacert, 0, (void (*)(void *))SSL_CTX_free); lru64_commit(lru, ssl_ctx, cacert, 0, (void (*)(void *))SSL_CTX_free);
} }
SSL_set_SSL_CTX(ssl, ssl_ctx); SSL_set_SSL_CTX(ssl, ssl_ctx);
RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock);
return 1; return 1;
} }
else { else {
@ -1818,18 +1887,13 @@ static int
ssl_sock_generate_certificate_from_conn(struct bind_conf *bind_conf, SSL *ssl) ssl_sock_generate_certificate_from_conn(struct bind_conf *bind_conf, SSL *ssl)
{ {
unsigned int key; unsigned int key;
SSL_CTX *ssl_ctx = NULL;
struct connection *conn = SSL_get_app_data(ssl); struct connection *conn = SSL_get_app_data(ssl);
conn_get_to_addr(conn); conn_get_to_addr(conn);
if (conn->flags & CO_FL_ADDR_TO_SET) { if (conn->flags & CO_FL_ADDR_TO_SET) {
key = ssl_sock_generated_cert_key(&conn->addr.to, get_addr_len(&conn->addr.to)); key = ssl_sock_generated_cert_key(&conn->addr.to, get_addr_len(&conn->addr.to));
ssl_ctx = ssl_sock_get_generated_cert(key, bind_conf); if (ssl_sock_assign_generated_cert(key, bind_conf, ssl))
if (ssl_ctx) {
/* switch ctx */
SSL_set_SSL_CTX(ssl, ssl_ctx);
return 1; return 1;
}
} }
return 0; return 0;
} }
@ -4351,7 +4415,15 @@ int ssl_sock_prepare_srv_ctx(struct server *srv)
global.ssl_used_backend = 1; global.ssl_used_backend = 1;
/* Initiate SSL context for current server */ /* Initiate SSL context for current server */
srv->ssl_ctx.reused_sess = NULL; if (!srv->ssl_ctx.reused_sess) {
if ((srv->ssl_ctx.reused_sess = calloc(1, global.nbthread*sizeof(SSL_SESSION*))) == NULL) {
Alert("Proxy '%s', server '%s' [%s:%d] out of memory.\n",
curproxy->id, srv->id,
srv->conf.file, srv->conf.line);
cfgerr++;
return cfgerr;
}
}
if (srv->use_ssl) if (srv->use_ssl)
srv->xprt = &ssl_sock; srv->xprt = &ssl_sock;
if (srv->check.use_ssl) if (srv->check.use_ssl)
@ -4599,7 +4671,10 @@ int ssl_sock_prepare_bind_conf(struct bind_conf *bind_conf)
} }
} }
alloc_ctx = shctx_init(&ssl_shctx, global.tune.sslcachesize, sizeof(struct sh_ssl_sess_hdr) + SHSESS_BLOCK_MIN_SIZE, sizeof(*sh_ssl_sess_tree), (!global_ssl.private_cache && (global.nbproc > 1)) ? 1 : 0); alloc_ctx = shctx_init(&ssl_shctx, global.tune.sslcachesize,
sizeof(struct sh_ssl_sess_hdr) + SHSESS_BLOCK_MIN_SIZE,
sizeof(*sh_ssl_sess_tree),
((global.nbthread > 1) || (!global_ssl.private_cache && (global.nbproc > 1))) ? 1 : 0);
if (alloc_ctx < 0) { if (alloc_ctx < 0) {
if (alloc_ctx == SHCTX_E_INIT_LOCK) if (alloc_ctx == SHCTX_E_INIT_LOCK)
Alert("Unable to initialize the lock for the shared SSL session cache. You can retry using the global statement 'tune.ssl.force-private-cache' but it could increase CPU usage due to renegotiations if nbproc > 1.\n"); Alert("Unable to initialize the lock for the shared SSL session cache. You can retry using the global statement 'tune.ssl.force-private-cache' but it could increase CPU usage due to renegotiations if nbproc > 1.\n");
@ -4706,9 +4781,12 @@ ssl_sock_load_ca(struct bind_conf *bind_conf)
return err; return err;
#if (defined SSL_CTRL_SET_TLSEXT_HOSTNAME && !defined SSL_NO_GENERATE_CERTIFICATES) #if (defined SSL_CTRL_SET_TLSEXT_HOSTNAME && !defined SSL_NO_GENERATE_CERTIFICATES)
if (global_ssl.ctx_cache) if (global_ssl.ctx_cache) {
ssl_ctx_lru_tree = lru64_new(global_ssl.ctx_cache); ssl_ctx_lru_tree = lru64_new(global_ssl.ctx_cache);
RWLOCK_INIT(&ssl_ctx_lru_rwlock);
}
ssl_ctx_lru_seed = (unsigned int)time(NULL); ssl_ctx_lru_seed = (unsigned int)time(NULL);
ssl_ctx_serial = now_ms;
#endif #endif
if (!bind_conf->ca_sign_file) { if (!bind_conf->ca_sign_file) {
@ -4826,9 +4904,9 @@ static int ssl_sock_init(struct connection *conn)
SSL_set_connect_state(conn->xprt_ctx); SSL_set_connect_state(conn->xprt_ctx);
if (objt_server(conn->target)->ssl_ctx.reused_sess) { if (objt_server(conn->target)->ssl_ctx.reused_sess) {
if(!SSL_set_session(conn->xprt_ctx, objt_server(conn->target)->ssl_ctx.reused_sess)) { if(!SSL_set_session(conn->xprt_ctx, objt_server(conn->target)->ssl_ctx.reused_sess[tid])) {
SSL_SESSION_free(objt_server(conn->target)->ssl_ctx.reused_sess); SSL_SESSION_free(objt_server(conn->target)->ssl_ctx.reused_sess[tid]);
objt_server(conn->target)->ssl_ctx.reused_sess = NULL; objt_server(conn->target)->ssl_ctx.reused_sess[tid] = NULL;
} }
} }
@ -5131,13 +5209,13 @@ int ssl_sock_handshake(struct connection *conn, unsigned int flag)
global.ssl_be_keys_max = global.ssl_be_keys_per_sec.curr_ctr; global.ssl_be_keys_max = global.ssl_be_keys_per_sec.curr_ctr;
/* check if session was reused, if not store current session on server for reuse */ /* check if session was reused, if not store current session on server for reuse */
if (objt_server(conn->target)->ssl_ctx.reused_sess) { if (objt_server(conn->target)->ssl_ctx.reused_sess[tid]) {
SSL_SESSION_free(objt_server(conn->target)->ssl_ctx.reused_sess); SSL_SESSION_free(objt_server(conn->target)->ssl_ctx.reused_sess[tid]);
objt_server(conn->target)->ssl_ctx.reused_sess = NULL; objt_server(conn->target)->ssl_ctx.reused_sess[tid] = NULL;
} }
if (!(objt_server(conn->target)->ssl_ctx.options & SRV_SSL_O_NO_REUSE)) if (!(objt_server(conn->target)->ssl_ctx.options & SRV_SSL_O_NO_REUSE))
objt_server(conn->target)->ssl_ctx.reused_sess = SSL_get1_session(conn->xprt_ctx); objt_server(conn->target)->ssl_ctx.reused_sess[tid] = SSL_get1_session(conn->xprt_ctx);
} }
else { else {
update_freq_ctr(&global.ssl_fe_keys_per_sec, 1); update_freq_ctr(&global.ssl_fe_keys_per_sec, 1);
@ -5156,9 +5234,9 @@ int ssl_sock_handshake(struct connection *conn, unsigned int flag)
ERR_clear_error(); ERR_clear_error();
/* free resumed session if exists */ /* free resumed session if exists */
if (objt_server(conn->target) && objt_server(conn->target)->ssl_ctx.reused_sess) { if (objt_server(conn->target) && objt_server(conn->target)->ssl_ctx.reused_sess[tid]) {
SSL_SESSION_free(objt_server(conn->target)->ssl_ctx.reused_sess); SSL_SESSION_free(objt_server(conn->target)->ssl_ctx.reused_sess[tid]);
objt_server(conn->target)->ssl_ctx.reused_sess = NULL; objt_server(conn->target)->ssl_ctx.reused_sess[tid] = NULL;
} }
/* Fail on all other handshake errors */ /* Fail on all other handshake errors */
@ -8629,6 +8707,9 @@ static void __ssl_sock_init(void)
SSL_library_init(); SSL_library_init();
cm = SSL_COMP_get_compression_methods(); cm = SSL_COMP_get_compression_methods();
sk_SSL_COMP_zero(cm); sk_SSL_COMP_zero(cm);
#ifdef USE_THREAD
ssl_locking_init();
#endif
#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL && !defined OPENSSL_NO_TLSEXT && !defined OPENSSL_IS_BORINGSSL && !defined LIBRESSL_VERSION_NUMBER) #if (OPENSSL_VERSION_NUMBER >= 0x1000200fL && !defined OPENSSL_NO_TLSEXT && !defined OPENSSL_IS_BORINGSSL && !defined LIBRESSL_VERSION_NUMBER)
sctl_ex_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, ssl_sock_sctl_free_func); sctl_ex_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, ssl_sock_sctl_free_func);
#endif #endif
@ -8740,7 +8821,10 @@ __attribute__((destructor))
static void __ssl_sock_deinit(void) static void __ssl_sock_deinit(void)
{ {
#if (defined SSL_CTRL_SET_TLSEXT_HOSTNAME && !defined SSL_NO_GENERATE_CERTIFICATES) #if (defined SSL_CTRL_SET_TLSEXT_HOSTNAME && !defined SSL_NO_GENERATE_CERTIFICATES)
lru64_destroy(ssl_ctx_lru_tree); if (ssl_ctx_lru_tree) {
lru64_destroy(ssl_ctx_lru_tree);
RWLOCK_DESTROY(&ssl_ctx_lru_rwlock);
}
#endif #endif
ERR_remove_state(0); ERR_remove_state(0);