From 7c7d4fddab82dc756d8caa67b1b31fcdde355aab Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Fri, 8 Apr 2016 13:48:14 -0400 Subject: [PATCH] Distrust external OpenSSL clients; clear err queue OpenSSL has an unfortunate tendency to mix per-session state error handling with per-thread error handling. This can cause problems when programs that link to libpq with OpenSSL enabled have some other use of OpenSSL; without care, one caller of OpenSSL may cause problems for the other caller. Backend code might similarly be affected, for example when a third party extension independently uses OpenSSL without taking the appropriate precautions. To fix, don't trust other users of OpenSSL to clear the per-thread error queue. Instead, clear the entire per-thread queue ahead of certain I/O operations when it appears that there might be trouble (these I/O operations mostly need to call SSL_get_error() to check for success, which relies on the queue being empty). This is slightly aggressive, but it's pretty clear that the other callers have a very dubious claim to ownership of the per-thread queue. Do this is both frontend and backend code. Finally, be more careful about clearing our own error queue, so as to not cause these problems ourself. It's possibly that control previously did not always reach SSLerrmessage(), where ERR_get_error() was supposed to be called to clear the queue's earliest code. Make sure ERR_get_error() is always called, so as to spare other users of OpenSSL the possibility of similar problems caused by libpq (as opposed to problems caused by a third party OpenSSL library like PHP's OpenSSL extension). Again, do this is both frontend and backend code. See bug #12799 and https://bugs.php.net/bug.php?id=68276 Based on patches by Dave Vitek and Peter Eisentraut. From: Peter Geoghegan --- src/backend/libpq/be-secure-openssl.c | 70 ++++++++++++++------- src/interfaces/libpq/fe-secure-openssl.c | 77 ++++++++++++++++-------- 2 files changed, 102 insertions(+), 45 deletions(-) diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c index 600966347e..95cceeed7a 100644 --- a/src/backend/libpq/be-secure-openssl.c +++ b/src/backend/libpq/be-secure-openssl.c @@ -78,7 +78,7 @@ static DH *tmp_dh_cb(SSL *s, int is_export, int keylength); static int verify_cb(int, X509_STORE_CTX *); static void info_cb(const SSL *ssl, int type, int args); static void initialize_ecdh(void); -static const char *SSLerrmessage(void); +static const char *SSLerrmessage(unsigned long ecode); static char *X509_NAME_to_cstring(X509_NAME *name); @@ -182,7 +182,7 @@ be_tls_init(void) if (!SSL_context) ereport(FATAL, (errmsg("could not create SSL context: %s", - SSLerrmessage()))); + SSLerrmessage(ERR_get_error())))); /* * Disable OpenSSL's moving-write-buffer sanity check, because it @@ -198,7 +198,7 @@ be_tls_init(void) ereport(FATAL, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("could not load server certificate file \"%s\": %s", - ssl_cert_file, SSLerrmessage()))); + ssl_cert_file, SSLerrmessage(ERR_get_error())))); if (stat(ssl_key_file, &buf) != 0) ereport(FATAL, @@ -251,12 +251,12 @@ be_tls_init(void) SSL_FILETYPE_PEM) != 1) ereport(FATAL, (errmsg("could not load private key file \"%s\": %s", - ssl_key_file, SSLerrmessage()))); + ssl_key_file, SSLerrmessage(ERR_get_error())))); if (SSL_CTX_check_private_key(SSL_context) != 1) ereport(FATAL, (errmsg("check of private key failed: %s", - SSLerrmessage()))); + SSLerrmessage(ERR_get_error())))); } /* set up ephemeral DH keys, and disallow SSL v2/v3 while at it */ @@ -285,7 +285,7 @@ be_tls_init(void) (root_cert_list = SSL_load_client_CA_file(ssl_ca_file)) == NULL) ereport(FATAL, (errmsg("could not load root certificate file \"%s\": %s", - ssl_ca_file, SSLerrmessage()))); + ssl_ca_file, SSLerrmessage(ERR_get_error())))); } /*---------- @@ -316,7 +316,7 @@ be_tls_init(void) else ereport(FATAL, (errmsg("could not load SSL certificate revocation list file \"%s\": %s", - ssl_crl_file, SSLerrmessage()))); + ssl_crl_file, SSLerrmessage(ERR_get_error())))); } } @@ -353,6 +353,7 @@ be_tls_open_server(Port *port) int r; int err; int waitfor; + unsigned long ecode; Assert(!port->ssl); Assert(!port->peer); @@ -362,7 +363,7 @@ be_tls_open_server(Port *port) ereport(COMMERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("could not initialize SSL connection: %s", - SSLerrmessage()))); + SSLerrmessage(ERR_get_error())))); return -1; } if (!my_SSL_set_fd(port, port->sock)) @@ -370,16 +371,36 @@ be_tls_open_server(Port *port) ereport(COMMERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("could not set SSL socket: %s", - SSLerrmessage()))); + SSLerrmessage(ERR_get_error())))); return -1; } port->ssl_in_use = true; aloop: + /* + * Prepare to call SSL_get_error() by clearing thread's OpenSSL error + * queue. In general, the current thread's error queue must be empty + * before the TLS/SSL I/O operation is attempted, or SSL_get_error() + * will not work reliably. An extension may have failed to clear the + * per-thread error queue following another call to an OpenSSL I/O + * routine. + */ + ERR_clear_error(); r = SSL_accept(port->ssl); if (r <= 0) { err = SSL_get_error(port->ssl, r); + + /* + * Other clients of OpenSSL in the backend may fail to call + * ERR_get_error(), but we always do, so as to not cause problems + * for OpenSSL clients that don't call ERR_clear_error() + * defensively. Be sure that this happens by calling now. + * SSL_get_error() relies on the OpenSSL per-thread error queue + * being intact, so this is the earliest possible point + * ERR_get_error() may be called. + */ + ecode = ERR_get_error(); switch (err) { case SSL_ERROR_WANT_READ: @@ -413,7 +434,7 @@ aloop: ereport(COMMERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("could not accept SSL connection: %s", - SSLerrmessage()))); + SSLerrmessage(ecode)))); break; case SSL_ERROR_ZERO_RETURN: ereport(COMMERROR, @@ -522,10 +543,13 @@ be_tls_read(Port *port, void *ptr, size_t len, int *waitfor) { ssize_t n; int err; + unsigned long ecode; errno = 0; + ERR_clear_error(); n = SSL_read(port->ssl, ptr, len); err = SSL_get_error(port->ssl, n); + ecode = (err != SSL_ERROR_NONE || n < 0) ? ERR_get_error() : 0; switch (err) { case SSL_ERROR_NONE: @@ -552,7 +576,7 @@ be_tls_read(Port *port, void *ptr, size_t len, int *waitfor) case SSL_ERROR_SSL: ereport(COMMERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), - errmsg("SSL error: %s", SSLerrmessage()))); + errmsg("SSL error: %s", SSLerrmessage(ecode)))); /* fall through */ case SSL_ERROR_ZERO_RETURN: errno = ECONNRESET; @@ -579,10 +603,13 @@ be_tls_write(Port *port, void *ptr, size_t len, int *waitfor) { ssize_t n; int err; + unsigned long ecode; errno = 0; + ERR_clear_error(); n = SSL_write(port->ssl, ptr, len); err = SSL_get_error(port->ssl, n); + ecode = (err != SSL_ERROR_NONE || n < 0) ? ERR_get_error() : 0; switch (err) { case SSL_ERROR_NONE: @@ -609,7 +636,7 @@ be_tls_write(Port *port, void *ptr, size_t len, int *waitfor) case SSL_ERROR_SSL: ereport(COMMERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), - errmsg("SSL error: %s", SSLerrmessage()))); + errmsg("SSL error: %s", SSLerrmessage(ecode)))); /* fall through */ case SSL_ERROR_ZERO_RETURN: errno = ECONNRESET; @@ -765,7 +792,8 @@ load_dh_file(int keylength) { if (DH_check(dh, &codes) == 0) { - elog(LOG, "DH_check error (%s): %s", fnbuf, SSLerrmessage()); + elog(LOG, "DH_check error (%s): %s", fnbuf, + SSLerrmessage(ERR_get_error())); return NULL; } if (codes & DH_CHECK_P_NOT_PRIME) @@ -805,7 +833,7 @@ load_dh_buffer(const char *buffer, size_t len) if (dh == NULL) ereport(DEBUG2, (errmsg_internal("DH load buffer: %s", - SSLerrmessage()))); + SSLerrmessage(ERR_get_error())))); BIO_free(bio); return dh; @@ -971,26 +999,26 @@ initialize_ecdh(void) } /* - * Obtain reason string for last SSL error + * Obtain reason string for passed SSL errcode + * + * ERR_get_error() is used by caller to get errcode to pass here. * * Some caution is needed here since ERR_reason_error_string will * return NULL if it doesn't recognize the error code. We don't * want to return NULL ever. */ static const char * -SSLerrmessage(void) +SSLerrmessage(unsigned long ecode) { - unsigned long errcode; const char *errreason; static char errbuf[32]; - errcode = ERR_get_error(); - if (errcode == 0) + if (ecode == 0) return _("no SSL error reported"); - errreason = ERR_reason_error_string(errcode); + errreason = ERR_reason_error_string(ecode); if (errreason != NULL) return errreason; - snprintf(errbuf, sizeof(errbuf), _("SSL error code %lu"), errcode); + snprintf(errbuf, sizeof(errbuf), _("SSL error code %lu"), ecode); return errbuf; } diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c index 133546b790..9392506dbd 100644 --- a/src/interfaces/libpq/fe-secure-openssl.c +++ b/src/interfaces/libpq/fe-secure-openssl.c @@ -70,7 +70,7 @@ static int verify_peer_name_matches_certificate_name(PGconn *conn, static void destroy_ssl_system(void); static int initialize_SSL(PGconn *conn); static PostgresPollingStatusType open_client_SSL(PGconn *); -static char *SSLerrmessage(void); +static char *SSLerrmessage(unsigned long ecode); static void SSLerrfree(char *buf); static int my_sock_read(BIO *h, char *buf, int size); @@ -152,7 +152,7 @@ pgtls_open_client(PGconn *conn) !SSL_set_app_data(conn->ssl, conn) || !my_SSL_set_fd(conn, conn->sock)) { - char *err = SSLerrmessage(); + char *err = SSLerrmessage(ERR_get_error()); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not establish SSL connection: %s\n"), @@ -209,11 +209,32 @@ pgtls_read(PGconn *conn, void *ptr, size_t len) int result_errno = 0; char sebuf[256]; int err; + unsigned long ecode; rloop: + /* + * Prepare to call SSL_get_error() by clearing thread's OpenSSL error + * queue. In general, the current thread's error queue must be empty + * before the TLS/SSL I/O operation is attempted, or SSL_get_error() + * will not work reliably. Since the possibility exists that other + * OpenSSL clients running in the same thread but not under our control + * will fail to call ERR_get_error() themselves (after their own I/O + * operations), pro-actively clear the per-thread error queue now. + */ SOCK_ERRNO_SET(0); + ERR_clear_error(); n = SSL_read(conn->ssl, ptr, len); err = SSL_get_error(conn->ssl, n); + + /* + * Other clients of OpenSSL may fail to call ERR_get_error(), but we + * always do, so as to not cause problems for OpenSSL clients that + * don't call ERR_clear_error() defensively. Be sure that this + * happens by calling now. SSL_get_error() relies on the OpenSSL + * per-thread error queue being intact, so this is the earliest + * possible point ERR_get_error() may be called. + */ + ecode = (err != SSL_ERROR_NONE || n < 0) ? ERR_get_error() : 0; switch (err) { case SSL_ERROR_NONE: @@ -266,7 +287,7 @@ rloop: break; case SSL_ERROR_SSL: { - char *errm = SSLerrmessage(); + char *errm = SSLerrmessage(ecode); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("SSL error: %s\n"), errm); @@ -318,10 +339,13 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len) int result_errno = 0; char sebuf[256]; int err; + unsigned long ecode; SOCK_ERRNO_SET(0); + ERR_clear_error(); n = SSL_write(conn->ssl, ptr, len); err = SSL_get_error(conn->ssl, n); + ecode = (err != SSL_ERROR_NONE || n < 0) ? ERR_get_error() : 0; switch (err) { case SSL_ERROR_NONE: @@ -372,7 +396,7 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len) break; case SSL_ERROR_SSL: { - char *errm = SSLerrmessage(); + char *errm = SSLerrmessage(ecode); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("SSL error: %s\n"), errm); @@ -839,7 +863,7 @@ pgtls_init(PGconn *conn) SSL_context = SSL_CTX_new(SSLv23_method()); if (!SSL_context) { - char *err = SSLerrmessage(); + char *err = SSLerrmessage(ERR_get_error()); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not create SSL context: %s\n"), @@ -1009,7 +1033,7 @@ initialize_SSL(PGconn *conn) #endif if (SSL_CTX_use_certificate_chain_file(SSL_context, fnbuf) != 1) { - char *err = SSLerrmessage(); + char *err = SSLerrmessage(ERR_get_error()); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not read certificate file \"%s\": %s\n"), @@ -1024,7 +1048,7 @@ initialize_SSL(PGconn *conn) if (SSL_use_certificate_file(conn->ssl, fnbuf, SSL_FILETYPE_PEM) != 1) { - char *err = SSLerrmessage(); + char *err = SSLerrmessage(ERR_get_error()); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not read certificate file \"%s\": %s\n"), @@ -1079,7 +1103,7 @@ initialize_SSL(PGconn *conn) conn->engine = ENGINE_by_id(engine_str); if (conn->engine == NULL) { - char *err = SSLerrmessage(); + char *err = SSLerrmessage(ERR_get_error()); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not load SSL engine \"%s\": %s\n"), @@ -1091,7 +1115,7 @@ initialize_SSL(PGconn *conn) if (ENGINE_init(conn->engine) == 0) { - char *err = SSLerrmessage(); + char *err = SSLerrmessage(ERR_get_error()); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not initialize SSL engine \"%s\": %s\n"), @@ -1107,7 +1131,7 @@ initialize_SSL(PGconn *conn) NULL, NULL); if (pkey == NULL) { - char *err = SSLerrmessage(); + char *err = SSLerrmessage(ERR_get_error()); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not read private SSL key \"%s\" from engine \"%s\": %s\n"), @@ -1121,7 +1145,7 @@ initialize_SSL(PGconn *conn) } if (SSL_use_PrivateKey(conn->ssl, pkey) != 1) { - char *err = SSLerrmessage(); + char *err = SSLerrmessage(ERR_get_error()); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not load private SSL key \"%s\" from engine \"%s\": %s\n"), @@ -1177,7 +1201,7 @@ initialize_SSL(PGconn *conn) if (SSL_use_PrivateKey_file(conn->ssl, fnbuf, SSL_FILETYPE_PEM) != 1) { - char *err = SSLerrmessage(); + char *err = SSLerrmessage(ERR_get_error()); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not load private key file \"%s\": %s\n"), @@ -1191,7 +1215,7 @@ initialize_SSL(PGconn *conn) if (have_cert && SSL_check_private_key(conn->ssl) != 1) { - char *err = SSLerrmessage(); + char *err = SSLerrmessage(ERR_get_error()); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("certificate does not match private key file \"%s\": %s\n"), @@ -1229,7 +1253,7 @@ initialize_SSL(PGconn *conn) #endif if (SSL_CTX_load_verify_locations(SSL_context, fnbuf, NULL) != 1) { - char *err = SSLerrmessage(); + char *err = SSLerrmessage(ERR_get_error()); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not read root certificate file \"%s\": %s\n"), @@ -1259,7 +1283,7 @@ initialize_SSL(PGconn *conn) X509_STORE_set_flags(cvstore, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); #else - char *err = SSLerrmessage(); + char *err = SSLerrmessage(ERR_get_error()); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("SSL library does not support CRL certificates (file \"%s\")\n"), @@ -1327,11 +1351,14 @@ open_client_SSL(PGconn *conn) { int r; + ERR_clear_error(); r = SSL_connect(conn->ssl); if (r <= 0) { int err = SSL_get_error(conn->ssl, r); + unsigned long ecode; + ecode = ERR_get_error(); switch (err) { case SSL_ERROR_WANT_READ: @@ -1356,7 +1383,7 @@ open_client_SSL(PGconn *conn) } case SSL_ERROR_SSL: { - char *err = SSLerrmessage(); + char *err = SSLerrmessage(ecode); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("SSL error: %s\n"), @@ -1384,7 +1411,9 @@ open_client_SSL(PGconn *conn) conn->peer = SSL_get_peer_certificate(conn->ssl); if (conn->peer == NULL) { - char *err = SSLerrmessage(); + char *err; + + err = SSLerrmessage(ERR_get_error()); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("certificate could not be obtained: %s\n"), @@ -1456,7 +1485,9 @@ pgtls_close(PGconn *conn) /* - * Obtain reason string for last SSL error + * Obtain reason string for passed SSL errcode + * + * ERR_get_error() is used by caller to get errcode to pass here. * * Some caution is needed here since ERR_reason_error_string will * return NULL if it doesn't recognize the error code. We don't @@ -1467,28 +1498,26 @@ static char ssl_nomem[] = "out of memory allocating error description"; #define SSL_ERR_LEN 128 static char * -SSLerrmessage(void) +SSLerrmessage(unsigned long ecode) { - unsigned long errcode; const char *errreason; char *errbuf; errbuf = malloc(SSL_ERR_LEN); if (!errbuf) return ssl_nomem; - errcode = ERR_get_error(); - if (errcode == 0) + if (ecode == 0) { snprintf(errbuf, SSL_ERR_LEN, libpq_gettext("no SSL error reported")); return errbuf; } - errreason = ERR_reason_error_string(errcode); + errreason = ERR_reason_error_string(ecode); if (errreason != NULL) { strlcpy(errbuf, errreason, SSL_ERR_LEN); return errbuf; } - snprintf(errbuf, SSL_ERR_LEN, libpq_gettext("SSL error code %lu"), errcode); + snprintf(errbuf, SSL_ERR_LEN, libpq_gettext("SSL error code %lu"), ecode); return errbuf; } -- 2.40.0