Skip to content

Commit 077711c

Browse files
committed
Remove arbitrary limitation on length of common name in SSL certificates.
Both libpq and the backend would truncate a common name extracted from a certificate at 32 bytes. Replace that fixed-size buffer with dynamically allocated string so that there is no hard limit. While at it, remove the code for extracting peer_dn, which we weren't using for anything; and don't bother to store peer_cn longer than we need it in libpq. This limit was not so terribly unreasonable when the code was written, because we weren't using the result for anything critical, just logging it. But now that there are options for checking the common name against the server host name (in libpq) or using it as the user's name (in the server), this could result in undesirable failures. In the worst case it even seems possible to spoof a server name or user name, if the correct name is exactly 32 bytes and the attacker can persuade a trusted CA to issue a certificate in which that string is a prefix of the certificate's common name. (To exploit this for a server name, he'd also have to send the connection astray via phony DNS data or some such.) The case that this is a realistic security threat is a bit thin, but nonetheless we'll treat it as one. Back-patch to 8.4. Older releases contain the faulty code, but it's not a security problem because the common name wasn't used for anything interesting. Reported and patched by Heikki Linnakangas Security: CVE-2012-0867
1 parent 891e6e7 commit 077711c

File tree

4 files changed

+105
-65
lines changed

4 files changed

+105
-65
lines changed

src/backend/libpq/be-secure.c

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373

7474
#include "libpq/libpq.h"
7575
#include "tcop/tcopprot.h"
76+
#include "utils/memutils.h"
7677

7778

7879
#ifdef USE_SSL
@@ -945,44 +946,54 @@ open_server_SSL(Port *port)
945946

946947
port->count = 0;
947948

948-
/* get client certificate, if available. */
949+
/* Get client certificate, if available. */
949950
port->peer = SSL_get_peer_certificate(port->ssl);
950-
if (port->peer == NULL)
951-
{
952-
strlcpy(port->peer_dn, "(anonymous)", sizeof(port->peer_dn));
953-
strlcpy(port->peer_cn, "(anonymous)", sizeof(port->peer_cn));
954-
}
955-
else
951+
952+
/* and extract the Common Name from it. */
953+
port->peer_cn = NULL;
954+
if (port->peer != NULL)
956955
{
957-
X509_NAME_oneline(X509_get_subject_name(port->peer),
958-
port->peer_dn, sizeof(port->peer_dn));
959-
port->peer_dn[sizeof(port->peer_dn) - 1] = '\0';
960-
r = X509_NAME_get_text_by_NID(X509_get_subject_name(port->peer),
961-
NID_commonName, port->peer_cn, sizeof(port->peer_cn));
962-
port->peer_cn[sizeof(port->peer_cn) - 1] = '\0';
963-
if (r == -1)
964-
{
965-
/* Unable to get the CN, set it to blank so it can't be used */
966-
port->peer_cn[0] = '\0';
967-
}
968-
else
956+
int len;
957+
958+
len = X509_NAME_get_text_by_NID(X509_get_subject_name(port->peer),
959+
NID_commonName, NULL, 0);
960+
if (len != -1)
969961
{
962+
char *peer_cn;
963+
964+
peer_cn = MemoryContextAlloc(TopMemoryContext, len + 1);
965+
r = X509_NAME_get_text_by_NID(X509_get_subject_name(port->peer),
966+
NID_commonName, peer_cn, len + 1);
967+
peer_cn[len] = '\0';
968+
if (r != len)
969+
{
970+
/* shouldn't happen */
971+
pfree(peer_cn);
972+
close_SSL(port);
973+
return -1;
974+
}
975+
970976
/*
971977
* Reject embedded NULLs in certificate common name to prevent
972978
* attacks like CVE-2009-4034.
973979
*/
974-
if (r != strlen(port->peer_cn))
980+
if (len != strlen(peer_cn))
975981
{
976982
ereport(COMMERROR,
977983
(errcode(ERRCODE_PROTOCOL_VIOLATION),
978984
errmsg("SSL certificate's common name contains embedded null")));
985+
pfree(peer_cn);
979986
close_SSL(port);
980987
return -1;
981988
}
989+
990+
port->peer_cn = peer_cn;
982991
}
983992
}
993+
984994
ereport(DEBUG2,
985-
(errmsg("SSL connection from \"%s\"", port->peer_cn)));
995+
(errmsg("SSL connection from \"%s\"",
996+
port->peer_cn ? port->peer_cn : "(anonymous)")));
986997

987998
/* set up debugging/info callback */
988999
SSL_CTX_set_info_callback(SSL_context, info_cb);
@@ -1008,6 +1019,12 @@ close_SSL(Port *port)
10081019
X509_free(port->peer);
10091020
port->peer = NULL;
10101021
}
1022+
1023+
if (port->peer_cn)
1024+
{
1025+
pfree(port->peer_cn);
1026+
port->peer_cn = NULL;
1027+
}
10111028
}
10121029

10131030
/*

src/include/libpq/libpq-be.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,7 @@ typedef struct Port
175175
#ifdef USE_SSL
176176
SSL *ssl;
177177
X509 *peer;
178-
char peer_dn[128 + 1];
179-
char peer_cn[SM_USER + 1];
178+
char *peer_cn;
180179
unsigned long count;
181180
#endif
182181
} Port;

src/interfaces/libpq/fe-secure.c

Lines changed: 66 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -733,40 +733,93 @@ wildcard_certificate_match(const char *pattern, const char *string)
733733
static bool
734734
verify_peer_name_matches_certificate(PGconn *conn)
735735
{
736+
char *peer_cn;
737+
int r;
738+
int len;
739+
bool result;
740+
736741
/*
737742
* If told not to verify the peer name, don't do it. Return true
738743
* indicating that the verification was successful.
739744
*/
740745
if (strcmp(conn->sslmode, "verify-full") != 0)
741746
return true;
742747

748+
/*
749+
* Extract the common name from the certificate.
750+
*
751+
* XXX: Should support alternate names here
752+
*/
753+
/* First find out the name's length and allocate a buffer for it. */
754+
len = X509_NAME_get_text_by_NID(X509_get_subject_name(conn->peer),
755+
NID_commonName, NULL, 0);
756+
if (len == -1)
757+
{
758+
printfPQExpBuffer(&conn->errorMessage,
759+
libpq_gettext("could not get server common name from server certificate\n"));
760+
return false;
761+
}
762+
peer_cn = malloc(len + 1);
763+
if (peer_cn == NULL)
764+
{
765+
printfPQExpBuffer(&conn->errorMessage,
766+
libpq_gettext("out of memory\n"));
767+
return false;
768+
}
769+
770+
r = X509_NAME_get_text_by_NID(X509_get_subject_name(conn->peer),
771+
NID_commonName, peer_cn, len + 1);
772+
if (r != len)
773+
{
774+
/* Got different length than on the first call. Shouldn't happen. */
775+
printfPQExpBuffer(&conn->errorMessage,
776+
libpq_gettext("could not get server common name from server certificate\n"));
777+
free(peer_cn);
778+
return false;
779+
}
780+
peer_cn[len] = '\0';
781+
782+
/*
783+
* Reject embedded NULLs in certificate common name to prevent attacks
784+
* like CVE-2009-4034.
785+
*/
786+
if (len != strlen(peer_cn))
787+
{
788+
printfPQExpBuffer(&conn->errorMessage,
789+
libpq_gettext("SSL certificate's common name contains embedded null\n"));
790+
free(peer_cn);
791+
return false;
792+
}
793+
794+
/*
795+
* We got the peer's common name. Now compare it against the originally
796+
* given hostname.
797+
*/
743798
if (!(conn->pghost && conn->pghost[0] != '\0'))
744799
{
745800
printfPQExpBuffer(&conn->errorMessage,
746801
libpq_gettext("host name must be specified for a verified SSL connection\n"));
747-
return false;
802+
result = false;
748803
}
749804
else
750805
{
751-
/*
752-
* Compare CN to originally given hostname.
753-
*
754-
* XXX: Should support alternate names here
755-
*/
756-
if (pg_strcasecmp(conn->peer_cn, conn->pghost) == 0)
806+
if (pg_strcasecmp(peer_cn, conn->pghost) == 0)
757807
/* Exact name match */
758-
return true;
759-
else if (wildcard_certificate_match(conn->peer_cn, conn->pghost))
808+
result = true;
809+
else if (wildcard_certificate_match(peer_cn, conn->pghost))
760810
/* Matched wildcard certificate */
761-
return true;
811+
result = true;
762812
else
763813
{
764814
printfPQExpBuffer(&conn->errorMessage,
765815
libpq_gettext("server common name \"%s\" does not match host name \"%s\"\n"),
766-
conn->peer_cn, conn->pghost);
767-
return false;
816+
peer_cn, conn->pghost);
817+
result = false;
768818
}
769819
}
820+
821+
free(peer_cn);
822+
return result;
770823
}
771824

772825
#ifdef ENABLE_THREAD_SAFETY
@@ -1372,7 +1425,7 @@ open_client_SSL(PGconn *conn)
13721425
* SSL_CTX_set_verify(), if root.crt exists.
13731426
*/
13741427

1375-
/* pull out server distinguished and common names */
1428+
/* get server certificate */
13761429
conn->peer = SSL_get_peer_certificate(conn->ssl);
13771430
if (conn->peer == NULL)
13781431
{
@@ -1386,33 +1439,6 @@ open_client_SSL(PGconn *conn)
13861439
return PGRES_POLLING_FAILED;
13871440
}
13881441

1389-
X509_NAME_oneline(X509_get_subject_name(conn->peer),
1390-
conn->peer_dn, sizeof(conn->peer_dn));
1391-
conn->peer_dn[sizeof(conn->peer_dn) - 1] = '\0';
1392-
1393-
r = X509_NAME_get_text_by_NID(X509_get_subject_name(conn->peer),
1394-
NID_commonName, conn->peer_cn, SM_USER);
1395-
conn->peer_cn[SM_USER] = '\0'; /* buffer is SM_USER+1 chars! */
1396-
if (r == -1)
1397-
{
1398-
/* Unable to get the CN, set it to blank so it can't be used */
1399-
conn->peer_cn[0] = '\0';
1400-
}
1401-
else
1402-
{
1403-
/*
1404-
* Reject embedded NULLs in certificate common name to prevent attacks
1405-
* like CVE-2009-4034.
1406-
*/
1407-
if (r != strlen(conn->peer_cn))
1408-
{
1409-
printfPQExpBuffer(&conn->errorMessage,
1410-
libpq_gettext("SSL certificate's common name contains embedded null\n"));
1411-
close_SSL(conn);
1412-
return PGRES_POLLING_FAILED;
1413-
}
1414-
}
1415-
14161442
if (!verify_peer_name_matches_certificate(conn))
14171443
{
14181444
close_SSL(conn);

src/interfaces/libpq/libpq-int.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,8 +406,6 @@ struct pg_conn
406406
* attempting normal connection */
407407
SSL *ssl; /* SSL status, if have SSL connection */
408408
X509 *peer; /* X509 cert of server */
409-
char peer_dn[256 + 1]; /* peer distinguished name */
410-
char peer_cn[SM_USER + 1]; /* peer common name */
411409
#ifdef USE_SSL_ENGINE
412410
ENGINE *engine; /* SSL engine, if any */
413411
#else

0 commit comments

Comments
 (0)