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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy