mirror of
https://github.com/postgres/postgres.git
synced 2025-05-17 06:41:24 +03:00
OpenSSL will sometimes return SSL_ERROR_SYSCALL without having set errno; this is apparently a reflection of recv(2)'s habit of not setting errno when reporting EOF. Ensure that we treat such cases the same as read EOF. Previously, we'd frequently report them like "could not accept SSL connection: Success" which is confusing, or worse report them with an unrelated errno left over from some previous syscall. To fix, ensure that errno is zeroed immediately before the call, and report its value only when it's not zero afterwards; otherwise report EOF. For consistency, I've applied the same coding pattern in libpq's pqsecure_raw_read(). Bare recv(2) shouldn't really return -1 without setting errno, but in case it does we might as well cope. Per report from Andres Freund. Back-patch to all supported versions. Discussion: https://postgr.es/m/20231208181451.deqnflwxqoehhxpe@awork3.anarazel.de
619 lines
14 KiB
C
619 lines
14 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* fe-secure.c
|
|
* functions related to setting up a secure connection to the backend.
|
|
* Secure connections are expected to provide confidentiality,
|
|
* message integrity and endpoint authentication.
|
|
*
|
|
*
|
|
* Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/interfaces/libpq/fe-secure.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres_fe.h"
|
|
|
|
#include <signal.h>
|
|
#include <fcntl.h>
|
|
#include <ctype.h>
|
|
|
|
#ifdef WIN32
|
|
#include "win32.h"
|
|
#else
|
|
#include <sys/socket.h>
|
|
#include <unistd.h>
|
|
#include <netdb.h>
|
|
#include <netinet/in.h>
|
|
#ifdef HAVE_NETINET_TCP_H
|
|
#include <netinet/tcp.h>
|
|
#endif
|
|
#include <arpa/inet.h>
|
|
#endif
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#ifdef ENABLE_THREAD_SAFETY
|
|
#ifdef WIN32
|
|
#include "pthread-win32.h"
|
|
#else
|
|
#include <pthread.h>
|
|
#endif
|
|
#endif
|
|
|
|
#include "fe-auth.h"
|
|
#include "libpq-fe.h"
|
|
#include "libpq-int.h"
|
|
|
|
/*
|
|
* Macros to handle disabling and then restoring the state of SIGPIPE handling.
|
|
* On Windows, these are all no-ops since there's no SIGPIPEs.
|
|
*/
|
|
|
|
#ifndef WIN32
|
|
|
|
#define SIGPIPE_MASKED(conn) ((conn)->sigpipe_so || (conn)->sigpipe_flag)
|
|
|
|
#ifdef ENABLE_THREAD_SAFETY
|
|
|
|
struct sigpipe_info
|
|
{
|
|
sigset_t oldsigmask;
|
|
bool sigpipe_pending;
|
|
bool got_epipe;
|
|
};
|
|
|
|
#define DECLARE_SIGPIPE_INFO(spinfo) struct sigpipe_info spinfo
|
|
|
|
#define DISABLE_SIGPIPE(conn, spinfo, failaction) \
|
|
do { \
|
|
(spinfo).got_epipe = false; \
|
|
if (!SIGPIPE_MASKED(conn)) \
|
|
{ \
|
|
if (pq_block_sigpipe(&(spinfo).oldsigmask, \
|
|
&(spinfo).sigpipe_pending) < 0) \
|
|
failaction; \
|
|
} \
|
|
} while (0)
|
|
|
|
#define REMEMBER_EPIPE(spinfo, cond) \
|
|
do { \
|
|
if (cond) \
|
|
(spinfo).got_epipe = true; \
|
|
} while (0)
|
|
|
|
#define RESTORE_SIGPIPE(conn, spinfo) \
|
|
do { \
|
|
if (!SIGPIPE_MASKED(conn)) \
|
|
pq_reset_sigpipe(&(spinfo).oldsigmask, (spinfo).sigpipe_pending, \
|
|
(spinfo).got_epipe); \
|
|
} while (0)
|
|
#else /* !ENABLE_THREAD_SAFETY */
|
|
|
|
#define DECLARE_SIGPIPE_INFO(spinfo) pqsigfunc spinfo = NULL
|
|
|
|
#define DISABLE_SIGPIPE(conn, spinfo, failaction) \
|
|
do { \
|
|
if (!SIGPIPE_MASKED(conn)) \
|
|
spinfo = pqsignal(SIGPIPE, SIG_IGN); \
|
|
} while (0)
|
|
|
|
#define REMEMBER_EPIPE(spinfo, cond)
|
|
|
|
#define RESTORE_SIGPIPE(conn, spinfo) \
|
|
do { \
|
|
if (!SIGPIPE_MASKED(conn)) \
|
|
pqsignal(SIGPIPE, spinfo); \
|
|
} while (0)
|
|
#endif /* ENABLE_THREAD_SAFETY */
|
|
#else /* WIN32 */
|
|
|
|
#define DECLARE_SIGPIPE_INFO(spinfo)
|
|
#define DISABLE_SIGPIPE(conn, spinfo, failaction)
|
|
#define REMEMBER_EPIPE(spinfo, cond)
|
|
#define RESTORE_SIGPIPE(conn, spinfo)
|
|
#endif /* WIN32 */
|
|
|
|
/* ------------------------------------------------------------ */
|
|
/* Procedures common to all secure sessions */
|
|
/* ------------------------------------------------------------ */
|
|
|
|
|
|
int
|
|
PQsslInUse(PGconn *conn)
|
|
{
|
|
if (!conn)
|
|
return 0;
|
|
return conn->ssl_in_use;
|
|
}
|
|
|
|
/*
|
|
* Exported function to allow application to tell us it's already
|
|
* initialized OpenSSL.
|
|
*/
|
|
void
|
|
PQinitSSL(int do_init)
|
|
{
|
|
#ifdef USE_SSL
|
|
pgtls_init_library(do_init, do_init);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Exported function to allow application to tell us it's already
|
|
* initialized OpenSSL and/or libcrypto.
|
|
*/
|
|
void
|
|
PQinitOpenSSL(int do_ssl, int do_crypto)
|
|
{
|
|
#ifdef USE_SSL
|
|
pgtls_init_library(do_ssl, do_crypto);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Initialize global SSL context
|
|
*/
|
|
int
|
|
pqsecure_initialize(PGconn *conn, bool do_ssl, bool do_crypto)
|
|
{
|
|
int r = 0;
|
|
|
|
#ifdef USE_SSL
|
|
r = pgtls_init(conn, do_ssl, do_crypto);
|
|
#endif
|
|
|
|
return r;
|
|
}
|
|
|
|
/*
|
|
* Begin or continue negotiating a secure session.
|
|
*/
|
|
PostgresPollingStatusType
|
|
pqsecure_open_client(PGconn *conn)
|
|
{
|
|
#ifdef USE_SSL
|
|
return pgtls_open_client(conn);
|
|
#else
|
|
/* shouldn't get here */
|
|
return PGRES_POLLING_FAILED;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Close secure session.
|
|
*/
|
|
void
|
|
pqsecure_close(PGconn *conn)
|
|
{
|
|
#ifdef USE_SSL
|
|
pgtls_close(conn);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Read data from a secure connection.
|
|
*
|
|
* On failure, this function is responsible for appending a suitable message
|
|
* to conn->errorMessage. The caller must still inspect errno, but only
|
|
* to determine whether to continue/retry after error.
|
|
*/
|
|
ssize_t
|
|
pqsecure_read(PGconn *conn, void *ptr, size_t len)
|
|
{
|
|
ssize_t n;
|
|
|
|
#ifdef USE_SSL
|
|
if (conn->ssl_in_use)
|
|
{
|
|
n = pgtls_read(conn, ptr, len);
|
|
}
|
|
else
|
|
#endif
|
|
#ifdef ENABLE_GSS
|
|
if (conn->gssenc)
|
|
{
|
|
n = pg_GSS_read(conn, ptr, len);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
n = pqsecure_raw_read(conn, ptr, len);
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
ssize_t
|
|
pqsecure_raw_read(PGconn *conn, void *ptr, size_t len)
|
|
{
|
|
ssize_t n;
|
|
int result_errno = 0;
|
|
char sebuf[PG_STRERROR_R_BUFLEN];
|
|
|
|
SOCK_ERRNO_SET(0);
|
|
|
|
n = recv(conn->sock, ptr, len, 0);
|
|
|
|
if (n < 0)
|
|
{
|
|
result_errno = SOCK_ERRNO;
|
|
|
|
/* Set error message if appropriate */
|
|
switch (result_errno)
|
|
{
|
|
#ifdef EAGAIN
|
|
case EAGAIN:
|
|
#endif
|
|
#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
|
|
case EWOULDBLOCK:
|
|
#endif
|
|
case EINTR:
|
|
/* no error message, caller is expected to retry */
|
|
break;
|
|
|
|
case EPIPE:
|
|
case ECONNRESET:
|
|
appendPQExpBufferStr(&conn->errorMessage,
|
|
libpq_gettext("server closed the connection unexpectedly\n"
|
|
"\tThis probably means the server terminated abnormally\n"
|
|
"\tbefore or while processing the request.\n"));
|
|
break;
|
|
|
|
case 0:
|
|
/* If errno didn't get set, treat it as regular EOF */
|
|
n = 0;
|
|
break;
|
|
|
|
default:
|
|
appendPQExpBuffer(&conn->errorMessage,
|
|
libpq_gettext("could not receive data from server: %s\n"),
|
|
SOCK_STRERROR(result_errno,
|
|
sebuf, sizeof(sebuf)));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* ensure we return the intended errno to caller */
|
|
SOCK_ERRNO_SET(result_errno);
|
|
|
|
return n;
|
|
}
|
|
|
|
/*
|
|
* Write data to a secure connection.
|
|
*
|
|
* Returns the number of bytes written, or a negative value (with errno
|
|
* set) upon failure. The write count could be less than requested.
|
|
*
|
|
* Note that socket-level hard failures are masked from the caller,
|
|
* instead setting conn->write_failed and storing an error message
|
|
* in conn->write_err_msg; see pqsecure_raw_write. This allows us to
|
|
* postpone reporting of write failures until we're sure no error
|
|
* message is available from the server.
|
|
*
|
|
* However, errors detected in the SSL or GSS management level are reported
|
|
* via a negative result, with message appended to conn->errorMessage.
|
|
* It's frequently unclear whether such errors should be considered read or
|
|
* write errors, so we don't attempt to postpone reporting them.
|
|
*
|
|
* The caller must still inspect errno upon failure, but only to determine
|
|
* whether to continue/retry; a message has been saved someplace in any case.
|
|
*/
|
|
ssize_t
|
|
pqsecure_write(PGconn *conn, const void *ptr, size_t len)
|
|
{
|
|
ssize_t n;
|
|
|
|
#ifdef USE_SSL
|
|
if (conn->ssl_in_use)
|
|
{
|
|
n = pgtls_write(conn, ptr, len);
|
|
}
|
|
else
|
|
#endif
|
|
#ifdef ENABLE_GSS
|
|
if (conn->gssenc)
|
|
{
|
|
n = pg_GSS_write(conn, ptr, len);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
n = pqsecure_raw_write(conn, ptr, len);
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
/*
|
|
* Low-level implementation of pqsecure_write.
|
|
*
|
|
* This is used directly for an unencrypted connection. For encrypted
|
|
* connections, this does the physical I/O on behalf of pgtls_write or
|
|
* pg_GSS_write.
|
|
*
|
|
* This function reports failure (i.e., returns a negative result) only
|
|
* for retryable errors such as EINTR. Looping for such cases is to be
|
|
* handled at some outer level, maybe all the way up to the application.
|
|
* For hard failures, we set conn->write_failed and store an error message
|
|
* in conn->write_err_msg, but then claim to have written the data anyway.
|
|
* This is because we don't want to report write failures so long as there
|
|
* is a possibility of reading from the server and getting an error message
|
|
* that could explain why the connection dropped. Many TCP stacks have
|
|
* race conditions such that a write failure may or may not be reported
|
|
* before all incoming data has been read.
|
|
*
|
|
* Note that this error behavior happens below the SSL management level when
|
|
* we are using SSL. That's because at least some versions of OpenSSL are
|
|
* too quick to report a write failure when there's still a possibility to
|
|
* get a more useful error from the server.
|
|
*/
|
|
ssize_t
|
|
pqsecure_raw_write(PGconn *conn, const void *ptr, size_t len)
|
|
{
|
|
ssize_t n;
|
|
int flags = 0;
|
|
int result_errno = 0;
|
|
char msgbuf[1024];
|
|
char sebuf[PG_STRERROR_R_BUFLEN];
|
|
|
|
DECLARE_SIGPIPE_INFO(spinfo);
|
|
|
|
/*
|
|
* If we already had a write failure, we will never again try to send data
|
|
* on that connection. Even if the kernel would let us, we've probably
|
|
* lost message boundary sync with the server. conn->write_failed
|
|
* therefore persists until the connection is reset, and we just discard
|
|
* all data presented to be written.
|
|
*/
|
|
if (conn->write_failed)
|
|
return len;
|
|
|
|
#ifdef MSG_NOSIGNAL
|
|
if (conn->sigpipe_flag)
|
|
flags |= MSG_NOSIGNAL;
|
|
|
|
retry_masked:
|
|
#endif /* MSG_NOSIGNAL */
|
|
|
|
DISABLE_SIGPIPE(conn, spinfo, return -1);
|
|
|
|
n = send(conn->sock, ptr, len, flags);
|
|
|
|
if (n < 0)
|
|
{
|
|
result_errno = SOCK_ERRNO;
|
|
|
|
/*
|
|
* If we see an EINVAL, it may be because MSG_NOSIGNAL isn't available
|
|
* on this machine. So, clear sigpipe_flag so we don't try the flag
|
|
* again, and retry the send().
|
|
*/
|
|
#ifdef MSG_NOSIGNAL
|
|
if (flags != 0 && result_errno == EINVAL)
|
|
{
|
|
conn->sigpipe_flag = false;
|
|
flags = 0;
|
|
goto retry_masked;
|
|
}
|
|
#endif /* MSG_NOSIGNAL */
|
|
|
|
/* Set error message if appropriate */
|
|
switch (result_errno)
|
|
{
|
|
#ifdef EAGAIN
|
|
case EAGAIN:
|
|
#endif
|
|
#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
|
|
case EWOULDBLOCK:
|
|
#endif
|
|
case EINTR:
|
|
/* no error message, caller is expected to retry */
|
|
break;
|
|
|
|
case EPIPE:
|
|
/* Set flag for EPIPE */
|
|
REMEMBER_EPIPE(spinfo, true);
|
|
|
|
/* FALL THRU */
|
|
|
|
case ECONNRESET:
|
|
conn->write_failed = true;
|
|
/* Store error message in conn->write_err_msg, if possible */
|
|
/* (strdup failure is OK, we'll cope later) */
|
|
snprintf(msgbuf, sizeof(msgbuf),
|
|
libpq_gettext("server closed the connection unexpectedly\n"
|
|
"\tThis probably means the server terminated abnormally\n"
|
|
"\tbefore or while processing the request.\n"));
|
|
conn->write_err_msg = strdup(msgbuf);
|
|
/* Now claim the write succeeded */
|
|
n = len;
|
|
break;
|
|
|
|
default:
|
|
conn->write_failed = true;
|
|
/* Store error message in conn->write_err_msg, if possible */
|
|
/* (strdup failure is OK, we'll cope later) */
|
|
snprintf(msgbuf, sizeof(msgbuf),
|
|
libpq_gettext("could not send data to server: %s\n"),
|
|
SOCK_STRERROR(result_errno,
|
|
sebuf, sizeof(sebuf)));
|
|
conn->write_err_msg = strdup(msgbuf);
|
|
/* Now claim the write succeeded */
|
|
n = len;
|
|
break;
|
|
}
|
|
}
|
|
|
|
RESTORE_SIGPIPE(conn, spinfo);
|
|
|
|
/* ensure we return the intended errno to caller */
|
|
SOCK_ERRNO_SET(result_errno);
|
|
|
|
return n;
|
|
}
|
|
|
|
/* Dummy versions of SSL info functions, when built without SSL support */
|
|
#ifndef USE_SSL
|
|
|
|
void *
|
|
PQgetssl(PGconn *conn)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
void *
|
|
PQsslStruct(PGconn *conn, const char *struct_name)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
const char *
|
|
PQsslAttribute(PGconn *conn, const char *attribute_name)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
const char *const *
|
|
PQsslAttributeNames(PGconn *conn)
|
|
{
|
|
static const char *const result[] = {NULL};
|
|
|
|
return result;
|
|
}
|
|
#endif /* USE_SSL */
|
|
|
|
/*
|
|
* Dummy versions of OpenSSL key password hook functions, when built without
|
|
* OpenSSL.
|
|
*/
|
|
#ifndef USE_OPENSSL
|
|
|
|
PQsslKeyPassHook_OpenSSL_type
|
|
PQgetSSLKeyPassHook_OpenSSL(void)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int
|
|
PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif /* USE_OPENSSL */
|
|
|
|
/* Dummy version of GSSAPI information functions, when built without GSS support */
|
|
#ifndef ENABLE_GSS
|
|
|
|
void *
|
|
PQgetgssctx(PGconn *conn)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
PQgssEncInUse(PGconn *conn)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
#endif /* ENABLE_GSS */
|
|
|
|
|
|
#if defined(ENABLE_THREAD_SAFETY) && !defined(WIN32)
|
|
|
|
/*
|
|
* Block SIGPIPE for this thread. This prevents send()/write() from exiting
|
|
* the application.
|
|
*/
|
|
int
|
|
pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending)
|
|
{
|
|
sigset_t sigpipe_sigset;
|
|
sigset_t sigset;
|
|
|
|
sigemptyset(&sigpipe_sigset);
|
|
sigaddset(&sigpipe_sigset, SIGPIPE);
|
|
|
|
/* Block SIGPIPE and save previous mask for later reset */
|
|
SOCK_ERRNO_SET(pthread_sigmask(SIG_BLOCK, &sigpipe_sigset, osigset));
|
|
if (SOCK_ERRNO)
|
|
return -1;
|
|
|
|
/* We can have a pending SIGPIPE only if it was blocked before */
|
|
if (sigismember(osigset, SIGPIPE))
|
|
{
|
|
/* Is there a pending SIGPIPE? */
|
|
if (sigpending(&sigset) != 0)
|
|
return -1;
|
|
|
|
if (sigismember(&sigset, SIGPIPE))
|
|
*sigpipe_pending = true;
|
|
else
|
|
*sigpipe_pending = false;
|
|
}
|
|
else
|
|
*sigpipe_pending = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Discard any pending SIGPIPE and reset the signal mask.
|
|
*
|
|
* Note: we are effectively assuming here that the C library doesn't queue
|
|
* up multiple SIGPIPE events. If it did, then we'd accidentally leave
|
|
* ours in the queue when an event was already pending and we got another.
|
|
* As long as it doesn't queue multiple events, we're OK because the caller
|
|
* can't tell the difference.
|
|
*
|
|
* The caller should say got_epipe = false if it is certain that it
|
|
* didn't get an EPIPE error; in that case we'll skip the clear operation
|
|
* and things are definitely OK, queuing or no. If it got one or might have
|
|
* gotten one, pass got_epipe = true.
|
|
*
|
|
* We do not want this to change errno, since if it did that could lose
|
|
* the error code from a preceding send(). We essentially assume that if
|
|
* we were able to do pq_block_sigpipe(), this can't fail.
|
|
*/
|
|
void
|
|
pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe)
|
|
{
|
|
int save_errno = SOCK_ERRNO;
|
|
int signo;
|
|
sigset_t sigset;
|
|
|
|
/* Clear SIGPIPE only if none was pending */
|
|
if (got_epipe && !sigpipe_pending)
|
|
{
|
|
if (sigpending(&sigset) == 0 &&
|
|
sigismember(&sigset, SIGPIPE))
|
|
{
|
|
sigset_t sigpipe_sigset;
|
|
|
|
sigemptyset(&sigpipe_sigset);
|
|
sigaddset(&sigpipe_sigset, SIGPIPE);
|
|
|
|
sigwait(&sigpipe_sigset, &signo);
|
|
}
|
|
}
|
|
|
|
/* Restore saved block mask */
|
|
pthread_sigmask(SIG_SETMASK, osigset, NULL);
|
|
|
|
SOCK_ERRNO_SET(save_errno);
|
|
}
|
|
|
|
#endif /* ENABLE_THREAD_SAFETY && !WIN32 */
|