Skip to content

Commit 2c0cefc

Browse files
committed
Set libcrypto callbacks for all connection threads in libpq
Based on an analysis of the OpenSSL code with Jacob, moving to EVP for the cryptohash computations makes necessary the setup of the libcrypto callbacks that were getting set only for SSL connections, but not for connections without SSL. Not setting the callbacks makes the use of threads potentially unsafe for connections calling cryptohashes during authentication, like MD5 or SCRAM, if a failure happens during a cryptohash computation. The logic setting the libssl and libcrypto states is then split into two parts, both using the same locking, with libcrypto being set up for SSL and non-SSL connections, while SSL connections set any libssl state afterwards as needed. Prior to this commit, only SSL connections would have set libcrypto callbacks that are necessary to ensure a proper thread locking when using multiple concurrent threads in libpq (ENABLE_THREAD_SAFETY). Note that this is only required for OpenSSL 1.0.2 and 1.0.1 (oldest version supported on HEAD), as 1.1.0 has its own internal locking and it has dropped support for CRYPTO_set_locking_callback(). Tests with up to 300 threads with OpenSSL 1.0.1 and 1.0.2, mixing SSL and non-SSL connection threads did not show any performance impact after some micro-benchmarking. pgbench can be used here with -C and a mostly-empty script (with one \set meta-command for example) to stress authentication requests, and we have mixed that with some custom programs for testing. Reported-by: Jacob Champion Author: Michael Paquier Reviewed-by: Jacob Champion Discussion: https://postgr.es/m/fd3ba610085f1ff54623478cf2f7adf5af193cbb.camel@vmware.com
1 parent 3f0daeb commit 2c0cefc

File tree

4 files changed

+96
-51
lines changed

4 files changed

+96
-51
lines changed

src/interfaces/libpq/fe-connect.c

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2887,6 +2887,16 @@ PQconnectPoll(PGconn *conn)
28872887

28882888
#ifdef USE_SSL
28892889

2890+
/*
2891+
* Enable the libcrypto callbacks before checking if SSL needs
2892+
* to be done. This is done before sending the startup packet
2893+
* as depending on the type of authentication done, like MD5
2894+
* or SCRAM that use cryptohashes, the callbacks would be
2895+
* required even without a SSL connection
2896+
*/
2897+
if (pqsecure_initialize(conn, false, true) < 0)
2898+
goto error_return;
2899+
28902900
/*
28912901
* If SSL is enabled and we haven't already got encryption of
28922902
* some sort running, request SSL instead of sending the
@@ -2998,8 +3008,14 @@ PQconnectPoll(PGconn *conn)
29983008
{
29993009
/* mark byte consumed */
30003010
conn->inStart = conn->inCursor;
3001-
/* Set up global SSL state if required */
3002-
if (pqsecure_initialize(conn) != 0)
3011+
3012+
/*
3013+
* Set up global SSL state if required. The crypto
3014+
* state has already been set if libpq took care of
3015+
* doing that, so there is no need to make that happen
3016+
* again.
3017+
*/
3018+
if (pqsecure_initialize(conn, true, false) != 0)
30033019
goto error_return;
30043020
}
30053021
else if (SSLok == 'N')

src/interfaces/libpq/fe-secure-openssl.c

Lines changed: 65 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ static bool pq_init_crypto_lib = true;
8585
static bool ssl_lib_initialized = false;
8686

8787
#ifdef ENABLE_THREAD_SAFETY
88-
static long ssl_open_connections = 0;
88+
static long crypto_open_connections = 0;
8989

9090
#ifndef WIN32
9191
static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
@@ -111,7 +111,7 @@ pgtls_init_library(bool do_ssl, int do_crypto)
111111
* Disallow changing the flags while we have open connections, else we'd
112112
* get completely confused.
113113
*/
114-
if (ssl_open_connections != 0)
114+
if (crypto_open_connections != 0)
115115
return;
116116
#endif
117117

@@ -635,7 +635,7 @@ pq_lockingcallback(int mode, int n, const char *file, int line)
635635
* override it.
636636
*/
637637
int
638-
pgtls_init(PGconn *conn)
638+
pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto)
639639
{
640640
#ifdef ENABLE_THREAD_SAFETY
641641
#ifdef WIN32
@@ -684,22 +684,28 @@ pgtls_init(PGconn *conn)
684684
}
685685
}
686686

687-
if (ssl_open_connections++ == 0)
687+
if (do_crypto && !conn->crypto_loaded)
688688
{
689-
/*
690-
* These are only required for threaded libcrypto applications,
691-
* but make sure we don't stomp on them if they're already set.
692-
*/
693-
if (CRYPTO_get_id_callback() == NULL)
694-
CRYPTO_set_id_callback(pq_threadidcallback);
695-
if (CRYPTO_get_locking_callback() == NULL)
696-
CRYPTO_set_locking_callback(pq_lockingcallback);
689+
if (crypto_open_connections++ == 0)
690+
{
691+
/*
692+
* These are only required for threaded libcrypto
693+
* applications, but make sure we don't stomp on them if
694+
* they're already set.
695+
*/
696+
if (CRYPTO_get_id_callback() == NULL)
697+
CRYPTO_set_id_callback(pq_threadidcallback);
698+
if (CRYPTO_get_locking_callback() == NULL)
699+
CRYPTO_set_locking_callback(pq_lockingcallback);
700+
}
701+
702+
conn->crypto_loaded = true;
697703
}
698704
}
699705
#endif /* HAVE_CRYPTO_LOCK */
700706
#endif /* ENABLE_THREAD_SAFETY */
701707

702-
if (!ssl_lib_initialized)
708+
if (!ssl_lib_initialized && do_ssl)
703709
{
704710
if (pq_init_ssl_lib)
705711
{
@@ -740,10 +746,10 @@ destroy_ssl_system(void)
740746
if (pthread_mutex_lock(&ssl_config_mutex))
741747
return;
742748

743-
if (pq_init_crypto_lib && ssl_open_connections > 0)
744-
--ssl_open_connections;
749+
if (pq_init_crypto_lib && crypto_open_connections > 0)
750+
--crypto_open_connections;
745751

746-
if (pq_init_crypto_lib && ssl_open_connections == 0)
752+
if (pq_init_crypto_lib && crypto_open_connections == 0)
747753
{
748754
/*
749755
* No connections left, unregister libcrypto callbacks, if no one
@@ -1402,46 +1408,63 @@ pgtls_close(PGconn *conn)
14021408
{
14031409
bool destroy_needed = false;
14041410

1405-
if (conn->ssl)
1411+
if (conn->ssl_in_use)
14061412
{
1407-
/*
1408-
* We can't destroy everything SSL-related here due to the possible
1409-
* later calls to OpenSSL routines which may need our thread
1410-
* callbacks, so set a flag here and check at the end.
1411-
*/
1412-
destroy_needed = true;
1413+
if (conn->ssl)
1414+
{
1415+
/*
1416+
* We can't destroy everything SSL-related here due to the
1417+
* possible later calls to OpenSSL routines which may need our
1418+
* thread callbacks, so set a flag here and check at the end.
1419+
*/
14131420

1414-
SSL_shutdown(conn->ssl);
1415-
SSL_free(conn->ssl);
1416-
conn->ssl = NULL;
1417-
conn->ssl_in_use = false;
1418-
}
1421+
SSL_shutdown(conn->ssl);
1422+
SSL_free(conn->ssl);
1423+
conn->ssl = NULL;
1424+
conn->ssl_in_use = false;
14191425

1420-
if (conn->peer)
1421-
{
1422-
X509_free(conn->peer);
1423-
conn->peer = NULL;
1424-
}
1426+
destroy_needed = true;
1427+
}
1428+
1429+
if (conn->peer)
1430+
{
1431+
X509_free(conn->peer);
1432+
conn->peer = NULL;
1433+
}
14251434

14261435
#ifdef USE_SSL_ENGINE
1427-
if (conn->engine)
1436+
if (conn->engine)
1437+
{
1438+
ENGINE_finish(conn->engine);
1439+
ENGINE_free(conn->engine);
1440+
conn->engine = NULL;
1441+
}
1442+
#endif
1443+
}
1444+
else
14281445
{
1429-
ENGINE_finish(conn->engine);
1430-
ENGINE_free(conn->engine);
1431-
conn->engine = NULL;
1446+
/*
1447+
* In the non-SSL case, just remove the crypto callbacks if the
1448+
* connection has then loaded. This code path has no dependency
1449+
* on any pending SSL calls.
1450+
*/
1451+
if (conn->crypto_loaded)
1452+
destroy_needed = true;
14321453
}
1433-
#endif
14341454

14351455
/*
1436-
* This will remove our SSL locking hooks, if this is the last SSL
1437-
* connection, which means we must wait to call it until after all SSL
1438-
* calls have been made, otherwise we can end up with a race condition and
1439-
* possible deadlocks.
1456+
* This will remove our crypto locking hooks if this is the last
1457+
* connection using libcrypto which means we must wait to call it until
1458+
* after all the potential SSL calls have been made, otherwise we can end
1459+
* up with a race condition and possible deadlocks.
14401460
*
14411461
* See comments above destroy_ssl_system().
14421462
*/
14431463
if (destroy_needed)
1464+
{
14441465
destroy_ssl_system();
1466+
conn->crypto_loaded = false;
1467+
}
14451468
}
14461469

14471470

src/interfaces/libpq/fe-secure.c

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,12 @@ PQinitOpenSSL(int do_ssl, int do_crypto)
159159
* Initialize global SSL context
160160
*/
161161
int
162-
pqsecure_initialize(PGconn *conn)
162+
pqsecure_initialize(PGconn *conn, bool do_ssl, bool do_crypto)
163163
{
164164
int r = 0;
165165

166166
#ifdef USE_SSL
167-
r = pgtls_init(conn);
167+
r = pgtls_init(conn, do_ssl, do_crypto);
168168
#endif
169169

170170
return r;
@@ -191,8 +191,7 @@ void
191191
pqsecure_close(PGconn *conn)
192192
{
193193
#ifdef USE_SSL
194-
if (conn->ssl_in_use)
195-
pgtls_close(conn);
194+
pgtls_close(conn);
196195
#endif
197196
}
198197

src/interfaces/libpq/libpq-int.h

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,11 @@ struct pg_conn
486486
void *engine; /* dummy field to keep struct the same if
487487
* OpenSSL version changes */
488488
#endif
489+
bool crypto_loaded; /* Track if libcrypto locking callbacks have
490+
* been done for this connection. This can be
491+
* removed once support for OpenSSL 1.0.2 is
492+
* removed as this locking is handled
493+
* internally in OpenSSL >= 1.1.0. */
489494
#endif /* USE_OPENSSL */
490495
#endif /* USE_SSL */
491496

@@ -667,7 +672,7 @@ extern int pqWriteReady(PGconn *conn);
667672

668673
/* === in fe-secure.c === */
669674

670-
extern int pqsecure_initialize(PGconn *);
675+
extern int pqsecure_initialize(PGconn *, bool, bool);
671676
extern PostgresPollingStatusType pqsecure_open_client(PGconn *);
672677
extern void pqsecure_close(PGconn *);
673678
extern ssize_t pqsecure_read(PGconn *, void *ptr, size_t len);
@@ -696,11 +701,13 @@ extern void pgtls_init_library(bool do_ssl, int do_crypto);
696701
* Initialize SSL library.
697702
*
698703
* The conn parameter is only used to be able to pass back an error
699-
* message - no connection-local setup is made here.
704+
* message - no connection-local setup is made here. do_ssl controls
705+
* if SSL is initialized, and do_crypto does the same for the crypto
706+
* part.
700707
*
701708
* Returns 0 if OK, -1 on failure (adding a message to conn->errorMessage).
702709
*/
703-
extern int pgtls_init(PGconn *conn);
710+
extern int pgtls_init(PGconn *conn, bool do_ssl, bool do_crypto);
704711

705712
/*
706713
* Begin or continue negotiating a secure session.

0 commit comments

Comments
 (0)