Skip to content

Commit 0e7f707

Browse files
committed
Fix low-risk potential denial of service against RADIUS login.
Corrupt RADIUS responses were treated as errors and not ignored (which the RFC2865 states they should be). This meant that a user with unfiltered access to the network of the PostgreSQL or RADIUS server could send a spoofed RADIUS response to the PostgreSQL server causing it to reject a valid login, provided the attacker could also guess (or brute-force) the correct port number. Fix is to simply retry the receive in a loop until the timeout has expired or a valid (signed by the correct RADIUS server) packet arrives. Reported by Alan DeKok in bug #5687.
1 parent 915116b commit 0e7f707

File tree

1 file changed

+126
-94
lines changed

1 file changed

+126
-94
lines changed

src/backend/libpq/auth.c

Lines changed: 126 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -2619,7 +2619,7 @@ CheckRADIUSAuth(Port *port)
26192619
char portstr[128];
26202620
ACCEPT_TYPE_ARG3 addrsize;
26212621
fd_set fdset;
2622-
struct timeval timeout;
2622+
struct timeval endtime;
26232623
int i,
26242624
r;
26252625

@@ -2777,14 +2777,36 @@ CheckRADIUSAuth(Port *port)
27772777
/* Don't need the server address anymore */
27782778
pg_freeaddrinfo_all(hint.ai_family, serveraddrs);
27792779

2780-
/* Wait for a response */
2781-
timeout.tv_sec = RADIUS_TIMEOUT;
2782-
timeout.tv_usec = 0;
2783-
FD_ZERO(&fdset);
2784-
FD_SET(sock, &fdset);
2780+
/*
2781+
* Figure out at what time we should time out. We can't just use
2782+
* a single call to select() with a timeout, since somebody can
2783+
* be sending invalid packets to our port thus causing us to
2784+
* retry in a loop and never time out.
2785+
*/
2786+
gettimeofday(&endtime, NULL);
2787+
endtime.tv_sec += RADIUS_TIMEOUT;
27852788

27862789
while (true)
27872790
{
2791+
struct timeval timeout;
2792+
struct timeval now;
2793+
int64 timeoutval;
2794+
2795+
gettimeofday(&now, NULL);
2796+
timeoutval = (endtime.tv_sec * 1000000 + endtime.tv_usec) - (now.tv_sec * 1000000 + now.tv_usec);
2797+
if (timeoutval <= 0)
2798+
{
2799+
ereport(LOG,
2800+
(errmsg("timeout waiting for RADIUS response")));
2801+
closesocket(sock);
2802+
return STATUS_ERROR;
2803+
}
2804+
timeout.tv_sec = timeoutval / 1000000;
2805+
timeout.tv_usec = timeoutval % 1000000;
2806+
2807+
FD_ZERO(&fdset);
2808+
FD_SET(sock, &fdset);
2809+
27882810
r = select(sock + 1, &fdset, NULL, NULL, &timeout);
27892811
if (r < 0)
27902812
{
@@ -2805,107 +2827,117 @@ CheckRADIUSAuth(Port *port)
28052827
return STATUS_ERROR;
28062828
}
28072829

2808-
/* else we actually have a packet ready to read */
2809-
break;
2810-
}
2811-
2812-
/* Read the response packet */
2813-
addrsize = sizeof(remoteaddr);
2814-
packetlength = recvfrom(sock, receive_buffer, RADIUS_BUFFER_SIZE, 0,
2815-
(struct sockaddr *) & remoteaddr, &addrsize);
2816-
if (packetlength < 0)
2817-
{
2818-
ereport(LOG,
2819-
(errmsg("could not read RADIUS response: %m")));
2820-
closesocket(sock);
2821-
return STATUS_ERROR;
2822-
}
2830+
/*
2831+
* Attempt to read the response packet, and verify the contents.
2832+
*
2833+
* Any packet that's not actually a RADIUS packet, or otherwise
2834+
* does not validate as an explicit reject, is just ignored and
2835+
* we retry for another packet (until we reach the timeout). This
2836+
* is to avoid the possibility to denial-of-service the login by
2837+
* flooding the server with invalid packets on the port that
2838+
* we're expecting the RADIUS response on.
2839+
*/
28232840

2824-
closesocket(sock);
2841+
addrsize = sizeof(remoteaddr);
2842+
packetlength = recvfrom(sock, receive_buffer, RADIUS_BUFFER_SIZE, 0,
2843+
(struct sockaddr *) & remoteaddr, &addrsize);
2844+
if (packetlength < 0)
2845+
{
2846+
ereport(LOG,
2847+
(errmsg("could not read RADIUS response: %m")));
2848+
return STATUS_ERROR;
2849+
}
28252850

28262851
#ifdef HAVE_IPV6
2827-
if (remoteaddr.sin6_port != htons(port->hba->radiusport))
2852+
if (remoteaddr.sin6_port != htons(port->hba->radiusport))
28282853
#else
2829-
if (remoteaddr.sin_port != htons(port->hba->radiusport))
2854+
if (remoteaddr.sin_port != htons(port->hba->radiusport))
28302855
#endif
2831-
{
2856+
{
28322857
#ifdef HAVE_IPV6
2833-
ereport(LOG,
2834-
(errmsg("RADIUS response was sent from incorrect port: %i",
2835-
ntohs(remoteaddr.sin6_port))));
2858+
ereport(LOG,
2859+
(errmsg("RADIUS response was sent from incorrect port: %i",
2860+
ntohs(remoteaddr.sin6_port))));
28362861
#else
2837-
ereport(LOG,
2838-
(errmsg("RADIUS response was sent from incorrect port: %i",
2839-
ntohs(remoteaddr.sin_port))));
2862+
ereport(LOG,
2863+
(errmsg("RADIUS response was sent from incorrect port: %i",
2864+
ntohs(remoteaddr.sin_port))));
28402865
#endif
2841-
return STATUS_ERROR;
2842-
}
2843-
2844-
if (packetlength < RADIUS_HEADER_LENGTH)
2845-
{
2846-
ereport(LOG,
2847-
(errmsg("RADIUS response too short: %i", packetlength)));
2848-
return STATUS_ERROR;
2849-
}
2850-
2851-
if (packetlength != ntohs(receivepacket->length))
2852-
{
2853-
ereport(LOG,
2854-
(errmsg("RADIUS response has corrupt length: %i (actual length %i)",
2855-
ntohs(receivepacket->length), packetlength)));
2856-
return STATUS_ERROR;
2857-
}
2866+
continue;
2867+
}
28582868

2859-
if (packet->id != receivepacket->id)
2860-
{
2861-
ereport(LOG,
2862-
(errmsg("RADIUS response is to a different request: %i (should be %i)",
2863-
receivepacket->id, packet->id)));
2864-
return STATUS_ERROR;
2865-
}
2869+
if (packetlength < RADIUS_HEADER_LENGTH)
2870+
{
2871+
ereport(LOG,
2872+
(errmsg("RADIUS response too short: %i", packetlength)));
2873+
continue;
2874+
}
28662875

2867-
/*
2868-
* Verify the response authenticator, which is calculated as
2869-
* MD5(Code+ID+Length+RequestAuthenticator+Attributes+Secret)
2870-
*/
2871-
cryptvector = palloc(packetlength + strlen(port->hba->radiussecret));
2876+
if (packetlength != ntohs(receivepacket->length))
2877+
{
2878+
ereport(LOG,
2879+
(errmsg("RADIUS response has corrupt length: %i (actual length %i)",
2880+
ntohs(receivepacket->length), packetlength)));
2881+
continue;
2882+
}
28722883

2873-
memcpy(cryptvector, receivepacket, 4); /* code+id+length */
2874-
memcpy(cryptvector + 4, packet->vector, RADIUS_VECTOR_LENGTH); /* request
2875-
* authenticator, from
2876-
* original packet */
2877-
if (packetlength > RADIUS_HEADER_LENGTH) /* there may be no attributes
2878-
* at all */
2879-
memcpy(cryptvector + RADIUS_HEADER_LENGTH, receive_buffer + RADIUS_HEADER_LENGTH, packetlength - RADIUS_HEADER_LENGTH);
2880-
memcpy(cryptvector + packetlength, port->hba->radiussecret, strlen(port->hba->radiussecret));
2884+
if (packet->id != receivepacket->id)
2885+
{
2886+
ereport(LOG,
2887+
(errmsg("RADIUS response is to a different request: %i (should be %i)",
2888+
receivepacket->id, packet->id)));
2889+
continue;
2890+
}
28812891

2882-
if (!pg_md5_binary(cryptvector,
2883-
packetlength + strlen(port->hba->radiussecret),
2884-
encryptedpassword))
2885-
{
2886-
ereport(LOG,
2887-
(errmsg("could not perform MD5 encryption of received packet")));
2892+
/*
2893+
* Verify the response authenticator, which is calculated as
2894+
* MD5(Code+ID+Length+RequestAuthenticator+Attributes+Secret)
2895+
*/
2896+
cryptvector = palloc(packetlength + strlen(port->hba->radiussecret));
2897+
2898+
memcpy(cryptvector, receivepacket, 4); /* code+id+length */
2899+
memcpy(cryptvector + 4, packet->vector, RADIUS_VECTOR_LENGTH); /* request
2900+
* authenticator, from
2901+
* original packet */
2902+
if (packetlength > RADIUS_HEADER_LENGTH) /* there may be no attributes
2903+
* at all */
2904+
memcpy(cryptvector + RADIUS_HEADER_LENGTH, receive_buffer + RADIUS_HEADER_LENGTH, packetlength - RADIUS_HEADER_LENGTH);
2905+
memcpy(cryptvector + packetlength, port->hba->radiussecret, strlen(port->hba->radiussecret));
2906+
2907+
if (!pg_md5_binary(cryptvector,
2908+
packetlength + strlen(port->hba->radiussecret),
2909+
encryptedpassword))
2910+
{
2911+
ereport(LOG,
2912+
(errmsg("could not perform MD5 encryption of received packet")));
2913+
pfree(cryptvector);
2914+
continue;
2915+
}
28882916
pfree(cryptvector);
2889-
return STATUS_ERROR;
2890-
}
2891-
pfree(cryptvector);
28922917

2893-
if (memcmp(receivepacket->vector, encryptedpassword, RADIUS_VECTOR_LENGTH) != 0)
2894-
{
2895-
ereport(LOG,
2896-
(errmsg("RADIUS response has incorrect MD5 signature")));
2897-
return STATUS_ERROR;
2898-
}
2918+
if (memcmp(receivepacket->vector, encryptedpassword, RADIUS_VECTOR_LENGTH) != 0)
2919+
{
2920+
ereport(LOG,
2921+
(errmsg("RADIUS response has incorrect MD5 signature")));
2922+
continue;
2923+
}
28992924

2900-
if (receivepacket->code == RADIUS_ACCESS_ACCEPT)
2901-
return STATUS_OK;
2902-
else if (receivepacket->code == RADIUS_ACCESS_REJECT)
2903-
return STATUS_ERROR;
2904-
else
2905-
{
2906-
ereport(LOG,
2907-
(errmsg("RADIUS response has invalid code (%i) for user \"%s\"",
2908-
receivepacket->code, port->user_name)));
2909-
return STATUS_ERROR;
2910-
}
2925+
if (receivepacket->code == RADIUS_ACCESS_ACCEPT)
2926+
{
2927+
closesocket(sock);
2928+
return STATUS_OK;
2929+
}
2930+
else if (receivepacket->code == RADIUS_ACCESS_REJECT)
2931+
{
2932+
closesocket(sock);
2933+
return STATUS_ERROR;
2934+
}
2935+
else
2936+
{
2937+
ereport(LOG,
2938+
(errmsg("RADIUS response has invalid code (%i) for user \"%s\"",
2939+
receivepacket->code, port->user_name)));
2940+
continue;
2941+
}
2942+
} /* while (true) */
29112943
}

0 commit comments

Comments
 (0)