Skip to content

Commit fbfeceb

Browse files
committed
Back-patch 9.4-era SSL renegotiation code into 9.3 and 9.2.
This back-patches 9.4 commits 31cf1a1, 86029b3, and 36a3be6 into the prior branches, along with relevant bits of b1aebbb and 7ce2a45. We had foreseen doing this once the code was proven, but that never did happen, probably because we got sufficiently fed up with renegotiation to disable it by default. However, we have to do something now because the prior code doesn't even compile against OpenSSL 1.1. Per discussion, the best solution seems to be to make the older branches look like 9.4. Discussion: https://postgr.es/m/20047.1492305247@sss.pgh.pa.us
1 parent fddc101 commit fbfeceb

File tree

2 files changed

+76
-17
lines changed

2 files changed

+76
-17
lines changed

src/backend/libpq/be-secure.c

Lines changed: 70 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ char *ssl_crl_file;
102102
int ssl_renegotiation_limit;
103103

104104
#ifdef USE_SSL
105+
/* are we in the middle of a renegotiation? */
106+
static bool in_ssl_renegotiation = false;
107+
105108
static SSL_CTX *SSL_context = NULL;
106109
static bool ssl_loaded_verify_locations = false;
107110
#endif
@@ -295,6 +298,7 @@ secure_read(Port *port, void *ptr, size_t len)
295298
(errcode(ERRCODE_PROTOCOL_VIOLATION),
296299
errmsg("unrecognized SSL error code: %d",
297300
err)));
301+
errno = ECONNRESET;
298302
n = -1;
299303
break;
300304
}
@@ -326,29 +330,55 @@ secure_write(Port *port, void *ptr, size_t len)
326330
int err;
327331
unsigned long ecode;
328332

329-
if (ssl_renegotiation_limit && port->count > ssl_renegotiation_limit * 1024L)
333+
/*
334+
* If SSL renegotiations are enabled and we're getting close to the
335+
* limit, start one now; but avoid it if there's one already in
336+
* progress. Request the renegotiation 1kB before the limit has
337+
* actually expired.
338+
*/
339+
if (ssl_renegotiation_limit && !in_ssl_renegotiation &&
340+
port->count > (ssl_renegotiation_limit - 1) * 1024L)
330341
{
342+
in_ssl_renegotiation = true;
343+
344+
/*
345+
* The way we determine that a renegotiation has completed is by
346+
* observing OpenSSL's internal renegotiation counter. Make sure
347+
* we start out at zero, and assume that the renegotiation is
348+
* complete when the counter advances.
349+
*
350+
* OpenSSL provides SSL_renegotiation_pending(), but this doesn't
351+
* seem to work in testing.
352+
*/
353+
SSL_clear_num_renegotiations(port->ssl);
354+
331355
SSL_set_session_id_context(port->ssl, (void *) &SSL_context,
332356
sizeof(SSL_context));
333357
if (SSL_renegotiate(port->ssl) <= 0)
334358
ereport(COMMERROR,
335359
(errcode(ERRCODE_PROTOCOL_VIOLATION),
336-
errmsg("SSL renegotiation failure")));
337-
if (SSL_do_handshake(port->ssl) <= 0)
338-
ereport(COMMERROR,
339-
(errcode(ERRCODE_PROTOCOL_VIOLATION),
340-
errmsg("SSL renegotiation failure")));
341-
if (port->ssl->state != SSL_ST_OK)
342-
ereport(COMMERROR,
343-
(errcode(ERRCODE_PROTOCOL_VIOLATION),
344-
errmsg("SSL failed to send renegotiation request")));
345-
port->ssl->state |= SSL_ST_ACCEPT;
346-
SSL_do_handshake(port->ssl);
347-
if (port->ssl->state != SSL_ST_OK)
348-
ereport(COMMERROR,
349-
(errcode(ERRCODE_PROTOCOL_VIOLATION),
350-
errmsg("SSL renegotiation failure")));
351-
port->count = 0;
360+
errmsg("SSL failure during renegotiation start")));
361+
else
362+
{
363+
int retries;
364+
365+
/*
366+
* A handshake can fail, so be prepared to retry it, but only
367+
* a few times.
368+
*/
369+
for (retries = 0;; retries++)
370+
{
371+
if (SSL_do_handshake(port->ssl) > 0)
372+
break; /* done */
373+
ereport(COMMERROR,
374+
(errcode(ERRCODE_PROTOCOL_VIOLATION),
375+
errmsg("SSL handshake failure on renegotiation, retrying")));
376+
if (retries >= 20)
377+
ereport(FATAL,
378+
(errcode(ERRCODE_PROTOCOL_VIOLATION),
379+
errmsg("could not complete SSL handshake on renegotiation, too many failures")));
380+
}
381+
}
352382
}
353383

354384
wloop:
@@ -393,9 +423,32 @@ secure_write(Port *port, void *ptr, size_t len)
393423
(errcode(ERRCODE_PROTOCOL_VIOLATION),
394424
errmsg("unrecognized SSL error code: %d",
395425
err)));
426+
errno = ECONNRESET;
396427
n = -1;
397428
break;
398429
}
430+
431+
if (n >= 0)
432+
{
433+
/* is renegotiation complete? */
434+
if (in_ssl_renegotiation &&
435+
SSL_num_renegotiations(port->ssl) >= 1)
436+
{
437+
in_ssl_renegotiation = false;
438+
port->count = 0;
439+
}
440+
441+
/*
442+
* if renegotiation is still ongoing, and we've gone beyond the
443+
* limit, kill the connection now -- continuing to use it can be
444+
* considered a security problem.
445+
*/
446+
if (in_ssl_renegotiation &&
447+
port->count > ssl_renegotiation_limit * 1024L)
448+
ereport(FATAL,
449+
(errcode(ERRCODE_PROTOCOL_VIOLATION),
450+
errmsg("SSL failed to renegotiate connection before limit expired")));
451+
}
399452
}
400453
else
401454
#endif

src/backend/tcop/postgres.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,16 +553,22 @@ prepare_for_client_read(void)
553553

554554
/*
555555
* client_read_ended -- get out of the client-input state
556+
*
557+
* This is called just after low-level reads. It must preserve errno!
556558
*/
557559
void
558560
client_read_ended(void)
559561
{
560562
if (DoingCommandRead)
561563
{
564+
int save_errno = errno;
565+
562566
ImmediateInterruptOK = false;
563567

564568
DisableNotifyInterrupt();
565569
DisableCatchupInterrupt();
570+
571+
errno = save_errno;
566572
}
567573
}
568574

0 commit comments

Comments
 (0)