Be more wary about OpenSSL not setting errno on error.
authorTom Lane <[email protected]>
Mon, 11 Dec 2023 16:51:56 +0000 (11:51 -0500)
committerTom Lane <[email protected]>
Mon, 11 Dec 2023 16:51:56 +0000 (11:51 -0500)
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[email protected]

src/backend/libpq/be-secure-openssl.c
src/backend/libpq/pqcomm.c
src/interfaces/libpq/fe-secure-openssl.c
src/interfaces/libpq/fe-secure.c

index e39952494e65349b70bbc9a01065da3d2bc3aae7..25afb28facde5f29017d312bc41c32473d64b614 100644 (file)
@@ -450,6 +450,7 @@ aloop:
     * per-thread error queue following another call to an OpenSSL I/O
     * routine.
     */
+   errno = 0;
    ERR_clear_error();
    r = SSL_accept(port->ssl);
    if (r <= 0)
@@ -486,7 +487,7 @@ aloop:
                                         WAIT_EVENT_SSL_OPEN_SERVER);
                goto aloop;
            case SSL_ERROR_SYSCALL:
-               if (r < 0)
+               if (r < 0 && errno != 0)
                    ereport(COMMERROR,
                            (errcode_for_socket_access(),
                             errmsg("could not accept SSL connection: %m")));
@@ -711,7 +712,7 @@ be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
            break;
        case SSL_ERROR_SYSCALL:
            /* leave it to caller to ereport the value of errno */
-           if (n != -1)
+           if (n != -1 || errno == 0)
            {
                errno = ECONNRESET;
                n = -1;
@@ -769,8 +770,14 @@ be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
            n = -1;
            break;
        case SSL_ERROR_SYSCALL:
-           /* leave it to caller to ereport the value of errno */
-           if (n != -1)
+
+           /*
+            * Leave it to caller to ereport the value of errno.  However, if
+            * errno is still zero then assume it's a read EOF situation, and
+            * report ECONNRESET.  (This seems possible because SSL_write can
+            * also do reads.)
+            */
+           if (n != -1 || errno == 0)
            {
                errno = ECONNRESET;
                n = -1;
index 44782f2d88d30fc04b40e4f85cf1812e1531fa76..35e194cf0f5dfc295d9f0a64d75cbcb33ebf964f 100644 (file)
@@ -954,6 +954,8 @@ pq_recvbuf(void)
    {
        int         r;
 
+       errno = 0;
+
        r = secure_read(MyProcPort, PqRecvBuffer + PqRecvLength,
                        PQ_RECV_BUFFER_SIZE - PqRecvLength);
 
@@ -966,10 +968,13 @@ pq_recvbuf(void)
             * Careful: an ereport() that tries to write to the client would
             * cause recursion to here, leading to stack overflow and core
             * dump!  This message must go *only* to the postmaster log.
+            *
+            * If errno is zero, assume it's EOF and let the caller complain.
             */
-           ereport(COMMERROR,
-                   (errcode_for_socket_access(),
-                    errmsg("could not receive data from client: %m")));
+           if (errno != 0)
+               ereport(COMMERROR,
+                       (errcode_for_socket_access(),
+                        errmsg("could not receive data from client: %m")));
            return EOF;
        }
        if (r == 0)
@@ -1046,6 +1051,8 @@ pq_getbyte_if_available(unsigned char *c)
    /* Put the socket into non-blocking mode */
    socket_set_nonblocking(true);
 
+   errno = 0;
+
    r = secure_read(MyProcPort, c, 1);
    if (r < 0)
    {
@@ -1062,10 +1069,13 @@ pq_getbyte_if_available(unsigned char *c)
             * Careful: an ereport() that tries to write to the client would
             * cause recursion to here, leading to stack overflow and core
             * dump!  This message must go *only* to the postmaster log.
+            *
+            * If errno is zero, assume it's EOF and let the caller complain.
             */
-           ereport(COMMERROR,
-                   (errcode_for_socket_access(),
-                    errmsg("could not receive data from client: %m")));
+           if (errno != 0)
+               ereport(COMMERROR,
+                       (errcode_for_socket_access(),
+                        errmsg("could not receive data from client: %m")));
            r = EOF;
        }
    }
index b42b4ca5002c73ee4076e5a95d3555aff8dabf12..1e21062a981c795f45ced1b9287771433b7e06d1 100644 (file)
@@ -200,7 +200,7 @@ rloop:
             */
            goto rloop;
        case SSL_ERROR_SYSCALL:
-           if (n < 0)
+           if (n < 0 && SOCK_ERRNO != 0)
            {
                result_errno = SOCK_ERRNO;
                if (result_errno == EPIPE ||
@@ -308,7 +308,13 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len)
            n = 0;
            break;
        case SSL_ERROR_SYSCALL:
-           if (n < 0)
+
+           /*
+            * If errno is still zero then assume it's a read EOF situation,
+            * and report EOF.  (This seems possible because SSL_write can
+            * also do reads.)
+            */
+           if (n < 0 && SOCK_ERRNO != 0)
            {
                result_errno = SOCK_ERRNO;
                if (result_errno == EPIPE || result_errno == ECONNRESET)
@@ -1347,10 +1353,12 @@ open_client_SSL(PGconn *conn)
 {
    int         r;
 
+   SOCK_ERRNO_SET(0);
    ERR_clear_error();
    r = SSL_connect(conn->ssl);
    if (r <= 0)
    {
+       int         save_errno = SOCK_ERRNO;
        int         err = SSL_get_error(conn->ssl, r);
        unsigned long ecode;
 
@@ -1367,10 +1375,10 @@ open_client_SSL(PGconn *conn)
                {
                    char        sebuf[PG_STRERROR_R_BUFLEN];
 
-                   if (r == -1)
+                   if (r == -1 && save_errno != 0)
                        appendPQExpBuffer(&conn->errorMessage,
                                          libpq_gettext("SSL SYSCALL error: %s\n"),
-                                         SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+                                         SOCK_STRERROR(save_errno, sebuf, sizeof(sebuf)));
                    else
                        appendPQExpBufferStr(&conn->errorMessage,
                                             libpq_gettext("SSL SYSCALL error: EOF detected\n"));
index b15d8d137cecdc792d3c213da33a4e870ede9117..63bb13353dab3d2088ef745ff5dafa7e242e3ca4 100644 (file)
@@ -235,6 +235,8 @@ pqsecure_raw_read(PGconn *conn, void *ptr, size_t len)
    int         result_errno = 0;
    char        sebuf[PG_STRERROR_R_BUFLEN];
 
+   SOCK_ERRNO_SET(0);
+
    n = recv(conn->sock, ptr, len, 0);
 
    if (n < 0)
@@ -262,6 +264,11 @@ pqsecure_raw_read(PGconn *conn, void *ptr, size_t len)
                                                   "\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"),