Re: BUG #18210: libpq: PQputCopyData sometimes fails in non-blocking mode over GSSAPI encrypted connection - Mailing list pgsql-bugs

From Tom Lane
Subject Re: BUG #18210: libpq: PQputCopyData sometimes fails in non-blocking mode over GSSAPI encrypted connection
Date
Msg-id 2524668.1700692174@sss.pgh.pa.us
Whole thread Raw
In response to Re: BUG #18210: libpq: PQputCopyData sometimes fails in non-blocking mode over GSSAPI encrypted connection  (Tom Lane <tgl@sss.pgh.pa.us>)
Responses Re: BUG #18210: libpq: PQputCopyData sometimes fails in non-blocking mode over GSSAPI encrypted connection  (Stephen Frost <sfrost@snowman.net>)
List pgsql-bugs
I wrote:
> I wonder if we should drop the idea of returning a positive bytecount
> after a partial write, and just return the pqsecure_raw_write result,
> and not reset PqGSSSendConsumed until we write everything presented.
> In edge cases maybe that would result in some buffer bloat, but it
> doesn't seem worse than what happens when the very first
> pqsecure_raw_write returns EINTR.

Here's a patch that fixes it along those lines.  I like this better,
I think, not least because it removes the assumption that "interesting"
pqsecure_raw_write failures will recur on the next try.

Still need to look at syncing the backend with this.

            regards, tom lane

diff --git a/src/interfaces/libpq/fe-secure-gssapi.c b/src/interfaces/libpq/fe-secure-gssapi.c
index 7e373236e9..ea8b0020d2 100644
--- a/src/interfaces/libpq/fe-secure-gssapi.c
+++ b/src/interfaces/libpq/fe-secure-gssapi.c
@@ -79,8 +79,8 @@
  * On success, returns the number of data bytes consumed (possibly less than
  * len).  On failure, returns -1 with errno set appropriately.  If the errno
  * indicates a non-retryable error, a message is added to conn->errorMessage.
- * For retryable errors, caller should call again (passing the same data)
- * once the socket is ready.
+ * For retryable errors, caller should call again (passing the same or more
+ * data) once the socket is ready.
  */
 ssize_t
 pg_GSS_write(PGconn *conn, const void *ptr, size_t len)
@@ -90,19 +90,25 @@ pg_GSS_write(PGconn *conn, const void *ptr, size_t len)
     gss_buffer_desc input,
                 output = GSS_C_EMPTY_BUFFER;
     ssize_t        ret = -1;
-    size_t        bytes_sent = 0;
     size_t        bytes_to_encrypt;
     size_t        bytes_encrypted;
     gss_ctx_id_t gctx = conn->gctx;

     /*
-     * When we get a failure, we must not tell the caller we have successfully
-     * transmitted everything, else it won't retry.  Hence a "success"
-     * (positive) return value must only count source bytes corresponding to
-     * fully-transmitted encrypted packets.  The amount of source data
-     * corresponding to the current partly-transmitted packet is remembered in
+     * When we get a retryable failure, we must not tell the caller we have
+     * successfully transmitted everything, else it won't retry.  For
+     * simplicity, we claim we haven't transmitted anything until we have
+     * successfully transmitted all "len" bytes.  Between calls, the amount of
+     * the current input data that's already been encrypted and placed into
+     * PqGSSSendBuffer (and perhaps transmitted) is remembered in
      * PqGSSSendConsumed.  On a retry, the caller *must* be sending that data
      * again, so if it offers a len less than that, something is wrong.
+     *
+     * Note: it may seem attractive to report partial write completion once
+     * we've successfully sent any encrypted packets.  However, that can cause
+     * problems for callers; notably, pqPutMsgEnd's heuristic to send only
+     * full 8K blocks interacts badly with such a hack.  We won't save much,
+     * typically, by letting callers discard data early, so don't risk it.
      */
     if (len < PqGSSSendConsumed)
     {
@@ -140,33 +146,20 @@ pg_GSS_write(PGconn *conn, const void *ptr, size_t len)

             retval = pqsecure_raw_write(conn, PqGSSSendBuffer + PqGSSSendNext, amount);
             if (retval <= 0)
-            {
-                /*
-                 * Report any previously-sent data; if there was none, reflect
-                 * the pqsecure_raw_write result up to our caller.  When there
-                 * was some, we're effectively assuming that any interesting
-                 * failure condition will recur on the next try.
-                 */
-                if (bytes_sent)
-                    return bytes_sent;
                 return retval;
-            }

             /*
              * Check if this was a partial write, and if so, move forward that
              * far in our buffer and try again.
              */
-            if (retval != amount)
+            if (retval < amount)
             {
                 PqGSSSendNext += retval;
                 continue;
             }

-            /* We've successfully sent whatever data was in that packet. */
-            bytes_sent += PqGSSSendConsumed;
-
-            /* All encrypted data was sent, our buffer is empty now. */
-            PqGSSSendLength = PqGSSSendNext = PqGSSSendConsumed = 0;
+            /* We've successfully sent whatever data was in the buffer. */
+            PqGSSSendLength = PqGSSSendNext = 0;
         }

         /*
@@ -192,7 +185,7 @@ pg_GSS_write(PGconn *conn, const void *ptr, size_t len)

         /*
          * Create the next encrypted packet.  Any failure here is considered a
-         * hard failure, so we return -1 even if bytes_sent > 0.
+         * hard failure, so we return -1 even if some data has been sent.
          */
         major = gss_wrap(&minor, gctx, 1, GSS_C_QOP_DEFAULT,
                          &input, &conf_state, &output);
@@ -236,10 +229,13 @@ pg_GSS_write(PGconn *conn, const void *ptr, size_t len)
     }

     /* If we get here, our counters should all match up. */
-    Assert(bytes_sent == len);
-    Assert(bytes_sent == bytes_encrypted);
+    Assert(len == PqGSSSendConsumed);
+    Assert(len == bytes_encrypted);
+
+    /* We're reporting all the data as sent, so reset PqGSSSendConsumed. */
+    PqGSSSendConsumed = 0;

-    ret = bytes_sent;
+    ret = bytes_encrypted;

 cleanup:
     /* Release GSSAPI buffer storage, if we didn't already */
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index c745facfec..22bc682ffc 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -583,8 +583,8 @@ struct pg_conn
     int            gss_SendLength; /* End of data available in gss_SendBuffer */
     int            gss_SendNext;    /* Next index to send a byte from
                                  * gss_SendBuffer */
-    int            gss_SendConsumed;    /* Number of *unencrypted* bytes consumed
-                                     * for current contents of gss_SendBuffer */
+    int            gss_SendConsumed;    /* Number of source bytes encrypted but
+                                     * not yet reported as sent */
     char       *gss_RecvBuffer; /* Received, encrypted data */
     int            gss_RecvLength; /* End of data available in gss_RecvBuffer */
     char       *gss_ResultBuffer;    /* Decryption of data in gss_RecvBuffer */

pgsql-bugs by date:

Previous
From: Lars Kanis
Date:
Subject: Re: BUG #18210: libpq: PQputCopyData sometimes fails in non-blocking mode over GSSAPI encrypted connection
Next
From: Frank Büttner
Date:
Subject: Misconfiguration on SSL for download.postgresql.org ?