Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1iqNZs-0002Aw-7k for pgsql-hackers@arkaria.postgresql.org; Sat, 11 Jan 2020 20:41:48 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.89) (envelope-from ) id 1iqNZq-0006Yd-Ub for pgsql-hackers@arkaria.postgresql.org; Sat, 11 Jan 2020 20:41:46 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1iqNWV-0004dW-Ke for pgsql-hackers@lists.postgresql.org; Sat, 11 Jan 2020 20:38:20 +0000 Received: from sss.pgh.pa.us ([66.207.139.130]) by makus.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1iqNWO-0003qh-Oe for pgsql-hackers@lists.postgresql.org; Sat, 11 Jan 2020 20:38:16 +0000 Received: from sss1.sss.pgh.pa.us (localhost [127.0.0.1]) by sss.pgh.pa.us (8.14.4/8.14.4) with ESMTP id 00BKbhal001474; Sat, 11 Jan 2020 15:37:44 -0500 From: Tom Lane To: Peter cc: pgsql-hackers@lists.postgresql.org, Stephen Frost Subject: Re: 12.1 not useable: clientlib fails after a dozen queries (GSSAPI ?) In-reply-to: <31477.1578756495@sss.pgh.pa.us> References: <20200109181822.GA74698@gate.oper.dinoex.org> <7042.1578595681@sss.pgh.pa.us> <20200109205319.GA85142@gate.oper.dinoex.org> <15232.1578605504@sss.pgh.pa.us> <20200110022342.GA92515@gate.oper.dinoex.org> <12348.1578679149@sss.pgh.pa.us> <20200110210337.GA9580@gate.oper.dinoex.org> <26433.1578714710@sss.pgh.pa.us> <31477.1578756495@sss.pgh.pa.us> Comments: In-reply-to Tom Lane message dated "Sat, 11 Jan 2020 10:28:15 -0500" MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0" Content-ID: <1442.1578775018.0@sss.pgh.pa.us> Date: Sat, 11 Jan 2020 15:37:43 -0500 Message-ID: <1473.1578775063@sss.pgh.pa.us> List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk ------- =_aaaaaaaaaa0 Content-Type: text/plain; charset="us-ascii" Content-ID: <1442.1578775018.1@sss.pgh.pa.us> I wrote: > So last night I was assuming that this problem just requires more careful > attention to what to return in the error exit paths. In the light of > morning, though, I realize that the algorithms involved in > be-secure-gssapi.c and fe-secure-gssapi.c are just fundamentally wrong: Here's a revised patch that attempts to deal with those issues. (Still doesn't touch the static-buffer issue, though.) The 0002 patch isn't meant for commit, but testing with that gives me a whole lot more confidence that the gssapi code deals with EWOULDBLOCK correctly. regards, tom lane ------- =_aaaaaaaaaa0 Content-Type: text/x-diff; name="0001-fix-assorted-gssapi-bugs-2.patch"; charset="us-ascii" Content-ID: <1442.1578775018.2@sss.pgh.pa.us> Content-Description: 0001-fix-assorted-gssapi-bugs-2.patch Content-Transfer-Encoding: quoted-printable diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-s= ecure-gssapi.c index b02d3dd..87cb6ea 100644 --- a/src/backend/libpq/be-secure-gssapi.c +++ b/src/backend/libpq/be-secure-gssapi.c @@ -5,7 +5,6 @@ * * Portions Copyright (c) 2018-2020, PostgreSQL Global Development Group * - * * IDENTIFICATION * src/backend/libpq/be-secure-gssapi.c * @@ -28,32 +27,36 @@ * Handle the encryption/decryption of data using GSSAPI. * * In the encrypted data stream on the wire, we break up the data - * into packets where each packet starts with a sizeof(uint32)-byte - * length (not allowed to be larger than the buffer sizes defined - * below) and then the encrypted data of that length immediately - * following. + * into packets where each packet starts with a uint32-size length + * word (in network byte order), then encrypted data of that length + * immediately following. Decryption yields the same data stream + * that would appear when not using encryption. * * Encrypted data typically ends up being larger than the same data * unencrypted, so we use fixed-size buffers for handling the * encryption/decryption which are larger than PQComm's buffer will * typically be to minimize the times where we have to make multiple - * packets and therefore sets of recv/send calls for a single - * read/write call to us. + * packets (and therefore multiple recv/send calls for a single + * read/write call to us). * * NOTE: The client and server have to agree on the max packet size, * because we have to pass an entire packet to GSSAPI at a time and we - * don't want the other side to send arbitrairly huge packets as we + * don't want the other side to send arbitrarily huge packets as we * would have to allocate memory for them to then pass them to GSSAPI. + * + * Therefore, these two #define's are effectively part of the protocol + * spec and can't ever be changed. */ #define PQ_GSS_SEND_BUFFER_SIZE 16384 #define PQ_GSS_RECV_BUFFER_SIZE 16384 = /* PqGSSSendBuffer is for *encrypted* data */ static char PqGSSSendBuffer[PQ_GSS_SEND_BUFFER_SIZE]; -static int PqGSSSendPointer; /* Next index to store a byte in - * PqGSSSendBuffer */ -static int PqGSSSendStart; /* Next index to send a byte in +static int PqGSSSendLength; /* End of data available in PqGSSSendBuffer *= / +static int PqGSSSendNext; /* Next index to send a byte from * PqGSSSendBuffer */ +static int PqGSSSendConsumed; /* Number of *unencrypted* bytes consumed f= or + * current contents of PqGSSSendBuffer */ = /* PqGSSRecvBuffer is for *encrypted* data */ static char PqGSSRecvBuffer[PQ_GSS_RECV_BUFFER_SIZE]; @@ -61,73 +64,87 @@ static int PqGSSRecvLength; /* End of data available i= n PqGSSRecvBuffer */ = /* PqGSSResultBuffer is for *unencrypted* data */ static char PqGSSResultBuffer[PQ_GSS_RECV_BUFFER_SIZE]; -static int PqGSSResultPointer; /* Next index to read a byte from - * PqGSSResultBuffer */ static int PqGSSResultLength; /* End of data available in PqGSSResultBuff= er */ +static int PqGSSResultNext; /* Next index to read a byte from + * PqGSSResultBuffer */ = uint32 max_packet_size; /* Maximum size we can encrypt and fit the * results into our output buffer */ = /* - * Attempt to write len bytes of data from ptr along a GSSAPI-encrypted c= onnection. + * Attempt to write len bytes of data from ptr to a GSSAPI-encrypted conn= ection. * - * Connection must be fully established (including authentication step) b= efore - * calling. Returns the bytes actually consumed once complete. Data is - * internally buffered; in the case of an incomplete write, the amount of= data we - * processed (encrypted into our output buffer to be sent) will be return= ed. If - * an error occurs or we would block, a negative value is returned and er= rno is - * set appropriately. + * The connection must be already set up for GSSAPI encryption (i.e., GSS= API + * transport negotiation is complete). * - * To continue writing in the case of EWOULDBLOCK and similar, call this = function - * again with matching ptr and len parameters. + * On success, returns the number of data bytes consumed (possibly less t= han + * len). On failure, returns -1 with errno set appropriately. (For fata= l + * errors, we may just elog and exit, if errno wouldn't be sufficient to + * describe the error.) For retryable errors, caller should call again + * (passing the same data) once the socket is ready. */ ssize_t be_gssapi_write(Port *port, void *ptr, size_t len) { - size_t bytes_to_encrypt =3D len; - size_t bytes_encrypted =3D 0; + OM_uint32 major, + minor; + gss_buffer_desc input, + output; + size_t bytes_sent =3D 0; + size_t bytes_to_encrypt; + size_t bytes_encrypted; + gss_ctx_id_t gctx =3D port->gss->ctx; = /* - * Loop through encrypting data and sending it out until + * When we get a failure, we must not tell the caller we have successful= ly + * 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 + * PqGSSSendConsumed. On a retry, the caller *must* be sending that dat= a + * again, so if it offers a len less than that, something is wrong. + */ + if (len < PqGSSSendConsumed) + elog(FATAL, "GSSAPI caller failed to retransmit all data needing to be = retried"); + + /* Discount whatever source data we already encrypted. */ + bytes_to_encrypt =3D len - PqGSSSendConsumed; + bytes_encrypted =3D PqGSSSendConsumed; + + /* + * Loop through encrypting data and sending it out until it's all done o= r * secure_raw_write() complains (which would likely mean that the socket * is non-blocking and the requested send() would block, or there was so= me - * kind of actual error) and then return. + * kind of actual error). */ - while (bytes_to_encrypt || PqGSSSendPointer) + while (bytes_to_encrypt || PqGSSSendLength) { - OM_uint32 major, - minor; - gss_buffer_desc input, - output; int conf_state =3D 0; uint32 netlen; - pg_gssinfo *gss =3D port->gss; = /* * Check if we have data in the encrypted output buffer that needs to - * be sent, and if so, try to send it. If we aren't able to, return - * that back up to the caller. + * be sent (possibly left over from a previous call), and if so, try + * to send it. If we aren't able to, return that fact back up to the + * caller. */ - if (PqGSSSendPointer) + if (PqGSSSendLength) { ssize_t ret; - ssize_t amount =3D PqGSSSendPointer - PqGSSSendStart; + ssize_t amount =3D PqGSSSendLength - PqGSSSendNext; = - ret =3D secure_raw_write(port, PqGSSSendBuffer + PqGSSSendStart, amoun= t); + ret =3D secure_raw_write(port, PqGSSSendBuffer + PqGSSSendNext, amount= ); if (ret <=3D 0) { /* - * If we encrypted some data and it's in our output buffer, - * but send() is saying that we would block, then tell the - * caller how far we got with encrypting the data so that they - * can call us again with whatever is left, at which point we - * will try to send the remaining encrypted data first and - * then move on to encrypting the rest of the data. + * Report any previously-sent data; if there was none, reflect + * the secure_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_encrypted !=3D 0 && (errno =3D=3D EAGAIN || errno =3D=3D EW= OULDBLOCK || errno =3D=3D EINTR)) - return bytes_encrypted; - else - return ret; + if (bytes_sent) + return bytes_sent; + return ret; } = /* @@ -136,29 +153,27 @@ be_gssapi_write(Port *port, void *ptr, size_t len) */ if (ret !=3D amount) { - PqGSSSendStart +=3D ret; + PqGSSSendNext +=3D ret; continue; } = + /* We've successfully sent whatever data was in that packet. */ + bytes_sent +=3D PqGSSSendConsumed; + /* All encrypted data was sent, our buffer is empty now. */ - PqGSSSendPointer =3D PqGSSSendStart =3D 0; + PqGSSSendLength =3D PqGSSSendNext =3D PqGSSSendConsumed =3D 0; } = /* * Check if there are any bytes left to encrypt. If not, we're done. */ if (!bytes_to_encrypt) - return bytes_encrypted; + break; = /* - * max_packet_size is the maximum amount of unencrypted data that, - * when encrypted, will fit into our encrypted-data output buffer. - * - * If we are being asked to send more than max_packet_size unencrypted - * data, then we will loop and create multiple packets, each with - * max_packet_size unencrypted data encrypted in them (at least, until - * secure_raw_write returns a failure saying we would be blocked, at - * which point we will let the caller know how far we got). + * Check how much we are being asked to send, if it's too much, then + * we will have to loop and possibly be called multiple times to get + * through all the data. */ if (bytes_to_encrypt > max_packet_size) input.length =3D max_packet_size; @@ -171,7 +186,7 @@ be_gssapi_write(Port *port, void *ptr, size_t len) output.length =3D 0; = /* Create the next encrypted packet */ - major =3D gss_wrap(&minor, gss->ctx, 1, GSS_C_QOP_DEFAULT, + major =3D gss_wrap(&minor, gctx, 1, GSS_C_QOP_DEFAULT, &input, &conf_state, &output); if (major !=3D GSS_S_COMPLETE) pg_GSS_error(FATAL, gettext_noop("GSSAPI wrap error"), major, minor); @@ -188,24 +203,34 @@ be_gssapi_write(Port *port, void *ptr, size_t len) = bytes_encrypted +=3D input.length; bytes_to_encrypt -=3D input.length; + PqGSSSendConsumed +=3D input.length; = - /* 4 network-order length bytes, then payload */ + /* 4 network-order bytes of length, then payload */ netlen =3D htonl(output.length); - memcpy(PqGSSSendBuffer + PqGSSSendPointer, &netlen, sizeof(uint32)); - PqGSSSendPointer +=3D sizeof(uint32); + memcpy(PqGSSSendBuffer + PqGSSSendLength, &netlen, sizeof(uint32)); + PqGSSSendLength +=3D sizeof(uint32); = - memcpy(PqGSSSendBuffer + PqGSSSendPointer, output.value, output.length)= ; - PqGSSSendPointer +=3D output.length; + memcpy(PqGSSSendBuffer + PqGSSSendLength, output.value, output.length); + PqGSSSendLength +=3D output.length; } = - return bytes_encrypted; + /* If we get here, our counters should all match up. */ + Assert(bytes_sent =3D=3D len); + Assert(bytes_sent =3D=3D bytes_encrypted); + + return bytes_sent; } = /* - * Read up to len bytes from a GSSAPI-encrypted connection into ptr. Cal= l - * only after the connection has been fully established (i.e., GSSAPI - * authentication is complete). On success, returns the number of bytes - * written into ptr; otherwise, returns -1 and sets errno appropriately. + * Read up to len bytes of data into ptr from a GSSAPI-encrypted connecti= on. + * + * The connection must be already set up for GSSAPI encryption (i.e., GSS= API + * transport negotiation is complete). + * + * Returns the number of data bytes read, or on failure, returns -1 + * with errno set appropriately. (For fatal errors, we may just elog and + * exit, if errno wouldn't be sufficient to describe the error.) For + * retryable errors, caller should call again once the socket is ready. */ ssize_t be_gssapi_read(Port *port, void *ptr, size_t len) @@ -215,89 +240,85 @@ be_gssapi_read(Port *port, void *ptr, size_t len) gss_buffer_desc input, output; ssize_t ret; - size_t bytes_to_return =3D len; size_t bytes_returned =3D 0; - int conf_state =3D 0; - pg_gssinfo *gss =3D port->gss; + gss_ctx_id_t gctx =3D port->gss->ctx; = /* - * The goal here is to read an incoming encrypted packet, one at a time, - * decrypt it into our out buffer, returning to the caller what they ask= ed - * for, and then saving anything else for the next call. - * - * First we look to see if we have unencrypted bytes available and, if s= o, - * copy those to the result. If the caller asked for more than we had - * immediately available, then we try to read a packet off the wire and - * decrypt it. If the read would block, then return the amount of - * unencrypted data we copied into the caller's ptr. + * The plan here is to read one incoming encrypted packet into + * PqGSSRecvBuffer, decrypt it into PqGSSResultBuffer, and then dole out + * data from there to the caller. When we exhaust the current input + * packet, read another. */ - while (bytes_to_return) + while (bytes_returned < len) { + int conf_state =3D 0; + /* Check if we have data in our buffer that we can return immediately *= / - if (PqGSSResultPointer < PqGSSResultLength) + if (PqGSSResultNext < PqGSSResultLength) { - int bytes_in_buffer =3D PqGSSResultLength - PqGSSResultPointer; - int bytes_to_copy =3D bytes_in_buffer < len - bytes_returned ? bytes= _in_buffer : len - bytes_returned; + size_t bytes_in_buffer =3D PqGSSResultLength - PqGSSResultNext; + size_t bytes_to_copy =3D Min(bytes_in_buffer, len - bytes_returned); = /* - * Copy the data from our output buffer into the caller's buffer, - * at the point where we last left off filling their buffer + * Copy the data from our result buffer into the caller's buffer, + * at the point where we last left off filling their buffer. */ - memcpy((char *) ptr + bytes_returned, PqGSSResultBuffer + PqGSSResultP= ointer, bytes_to_copy); - PqGSSResultPointer +=3D bytes_to_copy; - bytes_to_return -=3D bytes_to_copy; + memcpy((char *) ptr + bytes_returned, PqGSSResultBuffer + PqGSSResultN= ext, bytes_to_copy); + PqGSSResultNext +=3D bytes_to_copy; bytes_returned +=3D bytes_to_copy; = - /* Check if our result buffer is now empty and, if so, reset */ - if (PqGSSResultPointer =3D=3D PqGSSResultLength) - PqGSSResultPointer =3D PqGSSResultLength =3D 0; - - continue; + /* + * At this point, we've either filled the caller's buffer or + * emptied our result buffer. Either way, return to caller. In + * the second case, we could try to read another encrypted packet, + * but the odds are good that there isn't one available. (If this + * isn't true, we chose too small a max packet size.) In any + * case, there's no harm letting the caller process the data we've + * already returned. + */ + break; } = + /* Result buffer is empty, so reset buffer pointers */ + PqGSSResultLength =3D PqGSSResultNext =3D 0; + /* - * At this point, our output buffer should be empty with more bytes - * being requested to be read. We are now ready to load the next - * packet and decrypt it (entirely) into our buffer. - * - * If we get a partial read back while trying to read a packet off the - * wire then we return the number of unencrypted bytes we were able to - * copy (if any, if we didn't copy any, then we return whatever - * secure_raw_read returned when we called it; likely -1) into the - * caller's ptr and wait to be called again, until we get a full - * packet to decrypt. + * Because we chose above to return immediately as soon as we emit + * some data, bytes_returned must be zero at this point. Therefore + * the failure exits below can just return -1 without worrying about + * whether we already emitted some data. + */ + Assert(bytes_returned =3D=3D 0); + + /* + * At this point, our result buffer is empty with more bytes being + * requested to be read. We are now ready to load the next packet and + * decrypt it (entirely) into our result buffer. */ = - /* Check if we have the size of the packet already in our buffer. */ + /* Collect the length if we haven't already */ if (PqGSSRecvLength < sizeof(uint32)) { - /* - * We were not able to get the length of the packet last time, so - * we need to do that first. - */ ret =3D secure_raw_read(port, PqGSSRecvBuffer + PqGSSRecvLength, sizeof(uint32) - PqGSSRecvLength); - if (ret < 0) - return bytes_returned ? bytes_returned : ret; + + /* If ret <=3D 0, secure_raw_read already set the correct errno */ + if (ret <=3D 0) + return ret; = PqGSSRecvLength +=3D ret; = - /* - * If we only got part of the packet length, then return however - * many unencrypted bytes we copied to the caller and wait to be - * called again. - */ + /* If we still haven't got the length, return to the caller */ if (PqGSSRecvLength < sizeof(uint32)) - return bytes_returned; + { + errno =3D EWOULDBLOCK; + return -1; + } } = - /* - * We have the length of the next packet at this point, so pull it out - * and then read whatever we have left of the packet to read. - */ + /* Decode the packet length and check for overlength packet */ input.length =3D ntohl(*(uint32 *) PqGSSRecvBuffer); = - /* Check for over-length packet */ if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32)) ereport(FATAL, (errmsg("oversize GSSAPI packet sent by the client (%zu > %zu)", @@ -310,37 +331,29 @@ be_gssapi_read(Port *port, void *ptr, size_t len) */ ret =3D secure_raw_read(port, PqGSSRecvBuffer + PqGSSRecvLength, input.length - (PqGSSRecvLength - sizeof(uint32))); - if (ret < 0) - return bytes_returned ? bytes_returned : ret; + /* If ret <=3D 0, secure_raw_read already set the correct errno */ + if (ret <=3D 0) + return ret; = PqGSSRecvLength +=3D ret; = - /* - * If we got less than the rest of the packet then we need to return - * and be called again. If we didn't have any bytes to return on this - * run then return -1 and set errno to EWOULDBLOCK. - */ + /* If we don't yet have the whole packet, return to the caller */ if (PqGSSRecvLength - sizeof(uint32) < input.length) { - if (!bytes_returned) - { - errno =3D EWOULDBLOCK; - return -1; - } - - return bytes_returned; + errno =3D EWOULDBLOCK; + return -1; } = /* * We now have the full packet and we can perform the decryption and - * refill our output buffer, then loop back up to pass that back to - * the user. + * refill our result buffer, then loop back up to pass data back to + * the caller. */ output.value =3D NULL; output.length =3D 0; input.value =3D PqGSSRecvBuffer + sizeof(uint32); = - major =3D gss_unwrap(&minor, gss->ctx, &input, &output, &conf_state, NU= LL); + major =3D gss_unwrap(&minor, gctx, &input, &output, &conf_state, NULL); if (major !=3D GSS_S_COMPLETE) pg_GSS_error(FATAL, gettext_noop("GSSAPI unwrap error"), major, minor); @@ -350,10 +363,9 @@ be_gssapi_read(Port *port, void *ptr, size_t len) (errmsg("incoming GSSAPI message did not use confidentiality"))); = memcpy(PqGSSResultBuffer, output.value, output.length); - PqGSSResultLength =3D output.length; = - /* Our buffer is now empty, reset it */ + /* Our receive buffer is now empty, reset it */ PqGSSRecvLength =3D 0; = gss_release_buffer(&minor, &output); @@ -379,37 +391,27 @@ read_or_wait(Port *port, ssize_t len) * Keep going until we either read in everything we were asked to, or we * error out. */ - while (PqGSSRecvLength !=3D len) + while (PqGSSRecvLength < len) { ret =3D secure_raw_read(port, PqGSSRecvBuffer + PqGSSRecvLength, len - = PqGSSRecvLength); = /* - * If we got back an error and it wasn't just EWOULDBLOCK/EAGAIN, then - * give up. + * If we got back an error and it wasn't just + * EWOULDBLOCK/EAGAIN/EINTR, then give up. */ - if (ret < 0 && !(errno =3D=3D EWOULDBLOCK || errno =3D=3D EAGAIN)) + if (ret < 0 && + !(errno =3D=3D EWOULDBLOCK || errno =3D=3D EAGAIN || errno =3D=3D EINT= R)) return -1; = /* * Ok, we got back either a positive value, zero, or a negative result - * but EWOULDBLOCK or EAGAIN was set. + * indicating we should retry. * - * If it was zero or negative, then we try to wait on the socket to be + * If it was zero or negative, then we wait on the socket to be * readable again. */ if (ret <=3D 0) { - /* - * If we got back less than zero, indicating an error, and that - * wasn't just a EWOULDBLOCK/EAGAIN, then give up. - */ - if (ret < 0 && !(errno =3D=3D EWOULDBLOCK || errno =3D=3D EAGAIN)) - return -1; - - /* - * We got back either zero, or -1 with EWOULDBLOCK/EAGAIN, so wait - * on socket to be readable again. - */ WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE | WL_EXIT_ON_PM_DEATH, port->sock, 0, WAIT_EVENT_GSS_OPEN_SERVER); @@ -430,7 +432,7 @@ read_or_wait(Port *port, ssize_t len) if (ret =3D=3D 0) return -1; } - else + if (ret < 0) continue; } = @@ -460,7 +462,8 @@ secure_open_gssapi(Port *port) minor; = /* initialize state variables */ - PqGSSSendPointer =3D PqGSSSendStart =3D PqGSSRecvLength =3D PqGSSResultP= ointer =3D PqGSSResultLength =3D 0; + PqGSSSendLength =3D PqGSSSendNext =3D PqGSSSendConsumed =3D 0; + PqGSSRecvLength =3D PqGSSResultLength =3D PqGSSResultNext =3D 0; = /* * Use the configured keytab, if there is one. Unfortunately, Heimdal @@ -542,7 +545,7 @@ secure_open_gssapi(Port *port) * Check if we have data to send and, if we do, make sure to send it * all */ - if (output.length !=3D 0) + if (output.length > 0) { uint32 netlen =3D htonl(output.length); = @@ -553,14 +556,27 @@ secure_open_gssapi(Port *port) PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32)))); = memcpy(PqGSSSendBuffer, (char *) &netlen, sizeof(uint32)); - PqGSSSendPointer +=3D sizeof(uint32); + PqGSSSendLength +=3D sizeof(uint32); = - memcpy(PqGSSSendBuffer + PqGSSSendPointer, output.value, output.length= ); - PqGSSSendPointer +=3D output.length; + memcpy(PqGSSSendBuffer + PqGSSSendLength, output.value, output.length)= ; + PqGSSSendLength +=3D output.length; = - while (PqGSSSendStart !=3D sizeof(uint32) + output.length) + /* we don't bother with PqGSSSendConsumed here */ + + while (PqGSSSendNext < PqGSSSendLength) { - ret =3D secure_raw_write(port, PqGSSSendBuffer + PqGSSSendStart, size= of(uint32) + output.length - PqGSSSendStart); + ret =3D secure_raw_write(port, PqGSSSendBuffer + PqGSSSendNext, + PqGSSSendLength - PqGSSSendNext); + + /* + * If we got back an error and it wasn't just + * EWOULDBLOCK/EAGAIN/EINTR, then give up. + */ + if (ret < 0 && + !(errno =3D=3D EWOULDBLOCK || errno =3D=3D EAGAIN || errno =3D=3D EI= NTR)) + return -1; + + /* Wait and retry if we couldn't write yet */ if (ret <=3D 0) { WaitLatchOrSocket(MyLatch, @@ -569,18 +585,18 @@ secure_open_gssapi(Port *port) continue; } = - PqGSSSendStart +=3D ret; + PqGSSSendNext +=3D ret; } = /* Done sending the packet, reset our buffer */ - PqGSSSendStart =3D PqGSSSendPointer =3D 0; + PqGSSSendLength =3D PqGSSSendNext =3D 0; = gss_release_buffer(&minor, &output); } = /* * If we got back that the connection is finished being set up, now - * that's we've sent the last packet, exit our loop. + * that we've sent the last packet, exit our loop. */ if (complete_next) break; @@ -588,10 +604,11 @@ secure_open_gssapi(Port *port) = /* * Determine the max packet size which will fit in our buffer, after - * accounting for the length + * accounting for the length. be_gssapi_write will need this. */ major =3D gss_wrap_size_limit(&minor, port->gss->ctx, 1, GSS_C_QOP_DEFAU= LT, - PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32), &max_packet_size); + PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32), + &max_packet_size); = if (GSS_ERROR(major)) pg_GSS_error(FATAL, gettext_noop("GSSAPI size check error"), diff --git a/src/interfaces/libpq/fe-secure-gssapi.c b/src/interfaces/libp= q/fe-secure-gssapi.c index e937024..054cf92 100644 --- a/src/interfaces/libpq/fe-secure-gssapi.c +++ b/src/interfaces/libpq/fe-secure-gssapi.c @@ -18,6 +18,7 @@ #include "libpq-int.h" #include "port/pg_bswap.h" = + /* * Require encryption support, as well as mutual authentication and * tamperproofing measures. @@ -26,116 +27,157 @@ GSS_C_SEQUENCE_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG = /* - * We use fixed-size buffers for handling the encryption/decryption - * which are larger than PQComm's buffer will typically be to minimize - * the times where we have to make multiple packets and therefore sets - * of recv/send calls for a single read/write call to us. + * Handle the encryption/decryption of data using GSSAPI. + * + * In the encrypted data stream on the wire, we break up the data + * into packets where each packet starts with a uint32-size length + * word (in network byte order), then encrypted data of that length + * immediately following. Decryption yields the same data stream + * that would appear when not using encryption. + * + * Encrypted data typically ends up being larger than the same data + * unencrypted, so we use fixed-size buffers for handling the + * encryption/decryption which are larger than PQComm's buffer will + * typically be to minimize the times where we have to make multiple + * packets (and therefore multiple recv/send calls for a single + * read/write call to us). * * NOTE: The client and server have to agree on the max packet size, * because we have to pass an entire packet to GSSAPI at a time and we - * don't want the other side to send arbitrairly huge packets as we + * don't want the other side to send arbitrarily huge packets as we * would have to allocate memory for them to then pass them to GSSAPI. + * + * Therefore, these two #define's are effectively part of the protocol + * spec and can't ever be changed. */ #define PQ_GSS_SEND_BUFFER_SIZE 16384 #define PQ_GSS_RECV_BUFFER_SIZE 16384 = /* PqGSSSendBuffer is for *encrypted* data */ static char PqGSSSendBuffer[PQ_GSS_SEND_BUFFER_SIZE]; -static int PqGSSSendPointer; /* Next index to store a byte in - * PqGSSSendBuffer */ -static int PqGSSSendStart; /* Next index to send a byte in +static int PqGSSSendLength; /* End of data available in PqGSSSendBuffer *= / +static int PqGSSSendNext; /* Next index to send a byte from * PqGSSSendBuffer */ +static int PqGSSSendConsumed; /* Number of *unencrypted* bytes consumed f= or + * current contents of PqGSSSendBuffer */ = /* PqGSSRecvBuffer is for *encrypted* data */ static char PqGSSRecvBuffer[PQ_GSS_RECV_BUFFER_SIZE]; -static int PqGSSRecvPointer; /* Next index to read a byte from - * PqGSSRecvBuffer */ static int PqGSSRecvLength; /* End of data available in PqGSSRecvBuffer *= / = /* PqGSSResultBuffer is for *unencrypted* data */ static char PqGSSResultBuffer[PQ_GSS_RECV_BUFFER_SIZE]; -static int PqGSSResultPointer; /* Next index to read a byte from - * PqGSSResultBuffer */ static int PqGSSResultLength; /* End of data available in PqGSSResultBuff= er */ +static int PqGSSResultNext; /* Next index to read a byte from + * PqGSSResultBuffer */ = uint32 max_packet_size; /* Maximum size we can encrypt and fit the * results into our output buffer */ = /* - * Write len bytes of data from ptr along a GSSAPI-encrypted connection. = Note - * that the connection must be already set up for GSSAPI encryption (i.e.= , - * GSSAPI transport negotiation is complete). Returns len when all data = has - * been written; retry when errno is EWOULDBLOCK or similar with the same - * values of ptr and len. On non-socket failures, will log an error mess= age. + * Attempt to write len bytes of data from ptr to a GSSAPI-encrypted conn= ection. + * + * The connection must be already set up for GSSAPI encryption (i.e., GSS= API + * transport negotiation is complete). + * + * On success, returns the number of data bytes consumed (possibly less t= han + * len). On failure, returns -1 with errno set appropriately. If the er= rno + * indicates a non-retryable error, a message is put into conn->errorMess= age. + * For retryable errors, caller should call again (passing the same data) + * once the socket is ready. */ ssize_t pg_GSS_write(PGconn *conn, const void *ptr, size_t len) { - gss_buffer_desc input, - output =3D GSS_C_EMPTY_BUFFER; OM_uint32 major, minor; + gss_buffer_desc input, + output =3D GSS_C_EMPTY_BUFFER; ssize_t ret =3D -1; - size_t bytes_to_encrypt =3D len; - size_t bytes_encrypted =3D 0; + size_t bytes_sent =3D 0; + size_t bytes_to_encrypt; + size_t bytes_encrypted; + gss_ctx_id_t gctx =3D conn->gctx; + + /* + * When we get a failure, we must not tell the caller we have successful= ly + * 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 + * PqGSSSendConsumed. On a retry, the caller *must* be sending that dat= a + * again, so if it offers a len less than that, something is wrong. + */ + if (len < PqGSSSendConsumed) + { + printfPQExpBuffer(&conn->errorMessage, + "GSSAPI caller failed to retransmit all data needing to be retrie= d\n"); + errno =3D EINVAL; + return -1; + } + + /* Discount whatever source data we already encrypted. */ + bytes_to_encrypt =3D len - PqGSSSendConsumed; + bytes_encrypted =3D PqGSSSendConsumed; = /* - * Loop through encrypting data and sending it out until + * Loop through encrypting data and sending it out until it's all done o= r * pqsecure_raw_write() complains (which would likely mean that the sock= et * is non-blocking and the requested send() would block, or there was so= me - * kind of actual error) and then return. + * kind of actual error). */ - while (bytes_to_encrypt || PqGSSSendPointer) + while (bytes_to_encrypt || PqGSSSendLength) { int conf_state =3D 0; uint32 netlen; = /* * Check if we have data in the encrypted output buffer that needs to - * be sent, and if so, try to send it. If we aren't able to, return - * that back up to the caller. + * be sent (possibly left over from a previous call), and if so, try + * to send it. If we aren't able to, return that fact back up to the + * caller. */ - if (PqGSSSendPointer) + if (PqGSSSendLength) { ssize_t ret; - ssize_t amount =3D PqGSSSendPointer - PqGSSSendStart; + ssize_t amount =3D PqGSSSendLength - PqGSSSendNext; = - ret =3D pqsecure_raw_write(conn, PqGSSSendBuffer + PqGSSSendStart, amo= unt); - if (ret < 0) + ret =3D pqsecure_raw_write(conn, PqGSSSendBuffer + PqGSSSendNext, amou= nt); + if (ret <=3D 0) { /* - * If we encrypted some data and it's in our output buffer, - * but send() is saying that we would block, then tell the - * client how far we got with encrypting the data so that they - * can call us again with whatever is left, at which point we - * will try to send the remaining encrypted data first and - * then move on to encrypting the rest of the data. + * 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_encrypted !=3D 0 && (errno =3D=3D EAGAIN || errno =3D=3D EW= OULDBLOCK || errno =3D=3D EINTR)) - return bytes_encrypted; - else - return ret; + if (bytes_sent) + return bytes_sent; + return ret; } = /* - * Partial write, move forward that far in our buffer and try - * again + * Check if this was a partial write, and if so, move forward that + * far in our buffer and try again. */ if (ret !=3D amount) { - PqGSSSendStart +=3D ret; + PqGSSSendNext +=3D ret; continue; } = + /* We've successfully sent whatever data was in that packet. */ + bytes_sent +=3D PqGSSSendConsumed; + /* All encrypted data was sent, our buffer is empty now. */ - PqGSSSendPointer =3D PqGSSSendStart =3D 0; + PqGSSSendLength =3D PqGSSSendNext =3D PqGSSSendConsumed =3D 0; } = /* * Check if there are any bytes left to encrypt. If not, we're done. */ if (!bytes_to_encrypt) - return bytes_encrypted; + break; = /* * Check how much we are being asked to send, if it's too much, then @@ -152,18 +194,24 @@ pg_GSS_write(PGconn *conn, const void *ptr, size_t l= en) output.value =3D NULL; output.length =3D 0; = - /* Create the next encrypted packet */ - major =3D gss_wrap(&minor, conn->gctx, 1, GSS_C_QOP_DEFAULT, + /* + * Create the next encrypted packet. Any failure here is considered a + * hard failure, so we return -1 even if bytes_sent > 0. + */ + major =3D gss_wrap(&minor, gctx, 1, GSS_C_QOP_DEFAULT, &input, &conf_state, &output); if (major !=3D GSS_S_COMPLETE) { pg_GSS_error(libpq_gettext("GSSAPI wrap error"), conn, major, minor); + errno =3D EIO; /* for lack of a better idea */ goto cleanup; } - else if (conf_state =3D=3D 0) + + if (conf_state =3D=3D 0) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("outgoing GSSAPI message would not use confidentia= lity\n")); + errno =3D EIO; /* for lack of a better idea */ goto cleanup; } = @@ -173,22 +221,28 @@ pg_GSS_write(PGconn *conn, const void *ptr, size_t l= en) libpq_gettext("client tried to send oversize GSSAPI packet (%zu = > %zu)\n"), (size_t) output.length, PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32)); + errno =3D EIO; /* for lack of a better idea */ goto cleanup; } = bytes_encrypted +=3D input.length; bytes_to_encrypt -=3D input.length; + PqGSSSendConsumed +=3D input.length; = /* 4 network-order bytes of length, then payload */ netlen =3D htonl(output.length); - memcpy(PqGSSSendBuffer + PqGSSSendPointer, &netlen, sizeof(uint32)); - PqGSSSendPointer +=3D sizeof(uint32); + memcpy(PqGSSSendBuffer + PqGSSSendLength, &netlen, sizeof(uint32)); + PqGSSSendLength +=3D sizeof(uint32); = - memcpy(PqGSSSendBuffer + PqGSSSendPointer, output.value, output.length)= ; - PqGSSSendPointer +=3D output.length; + memcpy(PqGSSSendBuffer + PqGSSSendLength, output.value, output.length); + PqGSSSendLength +=3D output.length; } = - ret =3D bytes_encrypted; + /* If we get here, our counters should all match up. */ + Assert(bytes_sent =3D=3D len); + Assert(bytes_sent =3D=3D bytes_encrypted); + + ret =3D bytes_sent; = cleanup: if (output.value !=3D NULL) @@ -198,9 +252,14 @@ cleanup: = /* * Read up to len bytes of data into ptr from a GSSAPI-encrypted connecti= on. - * Note that GSSAPI transport must already have been negotiated. Returns= the - * number of bytes read into ptr; otherwise, returns -1. Retry with the = same - * ptr and len when errno is EWOULDBLOCK or similar. + * + * The connection must be already set up for GSSAPI encryption (i.e., GSS= API + * transport negotiation is complete). + * + * Returns the number of data bytes read, or on failure, returns -1 + * with errno set appropriately. If the errno indicates a non-retryable + * error, a message is put into conn->errorMessage. For retryable errors= , + * caller should call again once the socket is ready. */ ssize_t pg_GSS_read(PGconn *conn, void *ptr, size_t len) @@ -209,90 +268,94 @@ pg_GSS_read(PGconn *conn, void *ptr, size_t len) minor; gss_buffer_desc input =3D GSS_C_EMPTY_BUFFER, output =3D GSS_C_EMPTY_BUFFER; - ssize_t ret =3D 0; - size_t bytes_to_return =3D len; + ssize_t ret; size_t bytes_returned =3D 0; + gss_ctx_id_t gctx =3D conn->gctx; = /* - * The goal here is to read an incoming encrypted packet, one at a time, - * decrypt it into our out buffer, returning to the caller what they ask= ed - * for, and then saving anything else for the next call. - * - * We get a read request, we look if we have cleartext bytes available - * and, if so, copy those to the result, and then we try to decrypt the - * next packet. - * - * We should not try to decrypt the next packet until the read buffer is - * completely empty. - * - * If the caller asks for more bytes than one decrypted packet, then we - * should try to return all bytes asked for. + * The plan here is to read one incoming encrypted packet into + * PqGSSRecvBuffer, decrypt it into PqGSSResultBuffer, and then dole out + * data from there to the caller. When we exhaust the current input + * packet, read another. */ - while (bytes_to_return) + while (bytes_returned < len) { int conf_state =3D 0; = /* Check if we have data in our buffer that we can return immediately *= / - if (PqGSSResultPointer < PqGSSResultLength) + if (PqGSSResultNext < PqGSSResultLength) { - int bytes_in_buffer =3D PqGSSResultLength - PqGSSResultPointer; - int bytes_to_copy =3D bytes_in_buffer < len - bytes_returned ? bytes= _in_buffer : len - bytes_returned; + size_t bytes_in_buffer =3D PqGSSResultLength - PqGSSResultNext; + size_t bytes_to_copy =3D Min(bytes_in_buffer, len - bytes_returned); = /* - * Copy the data from our output buffer into the caller's buffer, - * at the point where we last left off filling their buffer + * Copy the data from our result buffer into the caller's buffer, + * at the point where we last left off filling their buffer. */ - memcpy((char *) ptr + bytes_returned, PqGSSResultBuffer + PqGSSResultP= ointer, bytes_to_copy); - PqGSSResultPointer +=3D bytes_to_copy; - bytes_to_return -=3D bytes_to_copy; + memcpy((char *) ptr + bytes_returned, PqGSSResultBuffer + PqGSSResultN= ext, bytes_to_copy); + PqGSSResultNext +=3D bytes_to_copy; bytes_returned +=3D bytes_to_copy; = - /* Check if our result buffer is now empty and, if so, reset */ - if (PqGSSResultPointer =3D=3D PqGSSResultLength) - PqGSSResultPointer =3D PqGSSResultLength =3D 0; - - continue; + /* + * At this point, we've either filled the caller's buffer or + * emptied our result buffer. Either way, return to caller. In + * the second case, we could try to read another encrypted packet, + * but the odds are good that there isn't one available. (If this + * isn't true, we chose too small a max packet size.) In any + * case, there's no harm letting the caller process the data we've + * already returned. + */ + break; } = + /* Result buffer is empty, so reset buffer pointers */ + PqGSSResultLength =3D PqGSSResultNext =3D 0; + /* - * At this point, our output buffer should be empty with more bytes - * being requested to be read. We are now ready to load the next - * packet and decrypt it (entirely) into our buffer. - * - * If we get a partial read back while trying to read a packet off the - * wire then we return back what bytes we were able to return and wait - * to be called again, until we get a full packet to decrypt. + * Because we chose above to return immediately as soon as we emit + * some data, bytes_returned must be zero at this point. Therefore + * the failure exits below can just return -1 without worrying about + * whether we already emitted some data. */ + Assert(bytes_returned =3D=3D 0); = - /* Check if we got a partial read just trying to get the length */ + /* + * At this point, our result buffer is empty with more bytes being + * requested to be read. We are now ready to load the next packet and + * decrypt it (entirely) into our result buffer. + */ + + /* Collect the length if we haven't already */ if (PqGSSRecvLength < sizeof(uint32)) { - /* Try to get whatever of the length we still need */ ret =3D pqsecure_raw_read(conn, PqGSSRecvBuffer + PqGSSRecvLength, sizeof(uint32) - PqGSSRecvLength); - if (ret < 0) - return bytes_returned ? bytes_returned : ret; + + /* If ret <=3D 0, pqsecure_raw_read already set the correct errno */ + if (ret <=3D 0) + return ret; = PqGSSRecvLength +=3D ret; + + /* If we still haven't got the length, return to the caller */ if (PqGSSRecvLength < sizeof(uint32)) - return bytes_returned; + { + errno =3D EWOULDBLOCK; + return -1; + } } = - /* - * We should have the whole length at this point, so pull it out and - * then read whatever we have left of the packet - */ + /* Decode the packet length and check for overlength packet */ input.length =3D ntohl(*(uint32 *) PqGSSRecvBuffer); = - /* Check for over-length packet */ if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32)) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("oversize GSSAPI packet sent by the server (%zu > = %zu)\n"), (size_t) input.length, PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32)); - ret =3D -1; - goto cleanup; + errno =3D EIO; /* for lack of a better idea */ + return -1; } = /* @@ -301,47 +364,53 @@ pg_GSS_read(PGconn *conn, void *ptr, size_t len) */ ret =3D pqsecure_raw_read(conn, PqGSSRecvBuffer + PqGSSRecvLength, input.length - (PqGSSRecvLength - sizeof(uint32))); - if (ret < 0) - return bytes_returned ? bytes_returned : ret; + /* If ret <=3D 0, pqsecure_raw_read already set the correct errno */ + if (ret <=3D 0) + return ret; = - /* - * If we got less than the rest of the packet then we need to return - * and be called again. - */ PqGSSRecvLength +=3D ret; + + /* If we don't yet have the whole packet, return to the caller */ if (PqGSSRecvLength - sizeof(uint32) < input.length) - return bytes_returned ? bytes_returned : -1; + { + errno =3D EWOULDBLOCK; + return -1; + } = /* * We now have the full packet and we can perform the decryption and - * refill our output buffer, then loop back up to pass that back to - * the user. + * refill our result buffer, then loop back up to pass data back to + * the caller. Note that error exits below here must take care of + * releasing the gss output buffer. */ output.value =3D NULL; output.length =3D 0; input.value =3D PqGSSRecvBuffer + sizeof(uint32); = - major =3D gss_unwrap(&minor, conn->gctx, &input, &output, &conf_state, = NULL); + major =3D gss_unwrap(&minor, gctx, &input, &output, &conf_state, NULL); if (major !=3D GSS_S_COMPLETE) { pg_GSS_error(libpq_gettext("GSSAPI unwrap error"), conn, major, minor); ret =3D -1; + errno =3D EIO; /* for lack of a better idea */ goto cleanup; } - else if (conf_state =3D=3D 0) + + if (conf_state =3D=3D 0) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("incoming GSSAPI message did not use confidentiali= ty\n")); ret =3D -1; + errno =3D EIO; /* for lack of a better idea */ goto cleanup; } = memcpy(PqGSSResultBuffer, output.value, output.length); PqGSSResultLength =3D output.length; = - /* Our buffer is now empty, reset it */ - PqGSSRecvPointer =3D PqGSSRecvLength =3D 0; + /* Our receive buffer is now empty, reset it */ + PqGSSRecvLength =3D 0; = gss_release_buffer(&minor, &output); } @@ -365,10 +434,13 @@ static PostgresPollingStatusType gss_read(PGconn *conn, void *recv_buffer, size_t length, ssize_t *ret) { *ret =3D pqsecure_raw_read(conn, recv_buffer, length); - if (*ret < 0 && errno =3D=3D EWOULDBLOCK) - return PGRES_POLLING_READING; - else if (*ret < 0) - return PGRES_POLLING_FAILED; + if (*ret < 0) + { + if (errno =3D=3D EAGAIN || errno =3D=3D EWOULDBLOCK || errno =3D=3D EIN= TR) + return PGRES_POLLING_READING; + else + return PGRES_POLLING_FAILED; + } = /* Check for EOF */ if (*ret =3D=3D 0) @@ -382,6 +454,13 @@ gss_read(PGconn *conn, void *recv_buffer, size_t leng= th, ssize_t *ret) return PGRES_POLLING_READING; = *ret =3D pqsecure_raw_read(conn, recv_buffer, length); + if (*ret < 0) + { + if (errno =3D=3D EAGAIN || errno =3D=3D EWOULDBLOCK || errno =3D=3D EI= NTR) + return PGRES_POLLING_READING; + else + return PGRES_POLLING_FAILED; + } if (*ret =3D=3D 0) return PGRES_POLLING_FAILED; } @@ -407,31 +486,37 @@ pqsecure_open_gss(PGconn *conn) gss_buffer_desc input =3D GSS_C_EMPTY_BUFFER, output =3D GSS_C_EMPTY_BUFFER; = - /* Check for data that needs to be written */ + /* Initialize, if first time through */ if (first) { - PqGSSSendPointer =3D PqGSSSendStart =3D PqGSSRecvPointer =3D PqGSSRecvL= ength =3D PqGSSResultPointer =3D PqGSSResultLength =3D 0; + PqGSSSendLength =3D PqGSSSendNext =3D PqGSSSendConsumed =3D 0; + PqGSSRecvLength =3D PqGSSResultLength =3D PqGSSResultNext =3D 0; first =3D 0; } = /* * Check if we have anything to send from a prior call and if so, send i= t. */ - if (PqGSSSendPointer) + if (PqGSSSendLength) { - ssize_t amount =3D PqGSSSendPointer - PqGSSSendStart; + ssize_t amount =3D PqGSSSendLength - PqGSSSendNext; = - ret =3D pqsecure_raw_write(conn, PqGSSSendBuffer + PqGSSSendStart, amou= nt); - if (ret < 0 && errno =3D=3D EWOULDBLOCK) - return PGRES_POLLING_WRITING; + ret =3D pqsecure_raw_write(conn, PqGSSSendBuffer + PqGSSSendNext, amoun= t); + if (ret < 0) + { + if (errno =3D=3D EAGAIN || errno =3D=3D EWOULDBLOCK || errno =3D=3D EI= NTR) + return PGRES_POLLING_WRITING; + else + return PGRES_POLLING_FAILED; + } = - if (ret !=3D amount) + if (ret < amount) { - PqGSSSendStart +=3D amount; + PqGSSSendNext +=3D ret; return PGRES_POLLING_WRITING; } = - PqGSSSendPointer =3D PqGSSSendStart =3D 0; + PqGSSSendLength =3D PqGSSSendNext =3D 0; } = /* @@ -536,7 +621,7 @@ pqsecure_open_gss(PGconn *conn) &output, NULL, NULL); = /* GSS Init Sec Context uses the whole packet, so clear it */ - PqGSSRecvPointer =3D PqGSSRecvLength =3D 0; + PqGSSRecvLength =3D 0; = if (GSS_ERROR(major)) { @@ -544,7 +629,8 @@ pqsecure_open_gss(PGconn *conn) conn, major, minor); return PGRES_POLLING_FAILED; } - else if (output.length =3D=3D 0) + + if (output.length =3D=3D 0) { /* * We're done - hooray! Kind of gross, but we need to disable SSL @@ -553,20 +639,26 @@ pqsecure_open_gss(PGconn *conn) #ifdef USE_SSL conn->allow_ssl_try =3D false; #endif + + /* Clean up */ gss_release_cred(&minor, &conn->gcred); conn->gcred =3D GSS_C_NO_CREDENTIAL; conn->gssenc =3D true; = /* * Determine the max packet size which will fit in our buffer, after - * accounting for the length + * accounting for the length. pg_GSS_write will need this. */ major =3D gss_wrap_size_limit(&minor, conn->gctx, 1, GSS_C_QOP_DEFAULT, - PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32), &max_packet_size); + PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32), + &max_packet_size); = if (GSS_ERROR(major)) + { pg_GSS_error(libpq_gettext("GSSAPI size check error"), conn, major, minor); + return PGRES_POLLING_FAILED; + } = return PGRES_POLLING_OK; } @@ -583,14 +675,16 @@ pqsecure_open_gss(PGconn *conn) netlen =3D htonl(output.length); = memcpy(PqGSSSendBuffer, (char *) &netlen, sizeof(uint32)); - PqGSSSendPointer +=3D sizeof(uint32); + PqGSSSendLength +=3D sizeof(uint32); + + memcpy(PqGSSSendBuffer + PqGSSSendLength, output.value, output.length); + PqGSSSendLength +=3D output.length; = - memcpy(PqGSSSendBuffer + PqGSSSendPointer, output.value, output.length); - PqGSSSendPointer +=3D output.length; + /* We don't bother with PqGSSSendConsumed here */ = gss_release_buffer(&minor, &output); = - /* Asked to be called again to write data */ + /* Ask to be called again to write data */ return PGRES_POLLING_WRITING; } = diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_aut= h.pl index e3eb052..b3aeea9 100644 --- a/src/test/kerberos/t/001_auth.pl +++ b/src/test/kerberos/t/001_auth.pl @@ -19,7 +19,7 @@ use Test::More; = if ($ENV{with_gssapi} eq 'yes') { - plan tests =3D> 12; + plan tests =3D> 18; } else { @@ -166,15 +166,15 @@ $node->safe_psql('postgres', 'CREATE USER test1;'); = note "running tests"; = +# Test connection success or failure, and if success, that query returns = true. sub test_access { - my ($node, $role, $server_check, $expected_res, $gssencmode, $test_name) - =3D @_; + my ($node, $role, $query, $expected_res, $gssencmode, $test_name) =3D @_= ; = # need to connect over TCP/IP for Kerberos my ($res, $stdoutres, $stderrres) =3D $node->psql( 'postgres', - "$server_check", + "$query", extra_params =3D> [ '-XAtd', $node->connstr('postgres') @@ -195,6 +195,29 @@ sub test_access return; } = +# As above, but test for an arbitrary query result. +sub test_query +{ + my ($node, $role, $query, $expected, $gssencmode, $test_name) =3D @_; + + # need to connect over TCP/IP for Kerberos + my ($res, $stdoutres, $stderrres) =3D $node->psql( + 'postgres', + "$query", + extra_params =3D> [ + '-XAtd', + $node->connstr('postgres') + . " host=3D$host hostaddr=3D$hostaddr $gssencmode", + '-U', + $role + ]); + + is($res, 0, $test_name); + like($stdoutres, $expected, $test_name); + is($stderrres, "", $test_name); + return; +} + unlink($node->data_dir . '/pg_hba.conf'); $node->append_conf('pg_hba.conf', qq{host all all $hostaddr/32 gss map=3Dmymap}); @@ -231,6 +254,27 @@ test_access( "gssencmode=3Drequire", "succeeds with GSS-encrypted access required with host hba"); = +# Test that we can transport a reasonable amount of data. +test_query( + $node, + "test1", + 'SELECT * FROM generate_series(1, 100000);', + qr/^1\n.*\n1024\n.*\n9999\n.*\n100000$/s, + "gssencmode=3Drequire", + "receiving 100K lines works"); + +test_query( + $node, + "test1", + "CREATE TABLE mytab (f1 int primary key);\n" + . "COPY mytab FROM STDIN;\n" + . join("\n", (1 .. 100000)) + . "\n\\.\n" + . "SELECT COUNT(*) FROM mytab;", + qr/^100000$/s, + "gssencmode=3Drequire", + "sending 100K lines works"); + unlink($node->data_dir . '/pg_hba.conf'); $node->append_conf('pg_hba.conf', qq{hostgssenc all all $hostaddr/32 gss map=3Dmymap}); ------- =_aaaaaaaaaa0 Content-Type: text/x-diff; name="0002-debug-support.patch"; charset="us-ascii" Content-ID: <1442.1578775018.3@sss.pgh.pa.us> Content-Description: 0002-debug-support.patch Content-Transfer-Encoding: quoted-printable diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index 2ae507a..cf763d7 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -331,6 +331,12 @@ secure_raw_write(Port *port, const void *ptr, size_t = len) { ssize_t n; = + if (random() < INT_MAX / 4) + { + errno =3D EWOULDBLOCK; + return -1; + } + #ifdef WIN32 pgwin32_noblock =3D true; #endif diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-se= cure.c index 52f6e87..c94587d 100644 --- a/src/interfaces/libpq/fe-secure.c +++ b/src/interfaces/libpq/fe-secure.c @@ -27,6 +27,7 @@ #include #include #include +#include = #ifdef WIN32 #include "win32.h" @@ -329,6 +330,12 @@ pqsecure_raw_write(PGconn *conn, const void *ptr, siz= e_t len) = DECLARE_SIGPIPE_INFO(spinfo); = + if (random() < INT_MAX / 2) + { + SOCK_ERRNO_SET(EWOULDBLOCK); + return -1; + } + #ifdef MSG_NOSIGNAL if (conn->sigpipe_flag) flags |=3D MSG_NOSIGNAL; ------- =_aaaaaaaaaa0--