Tom Lane wrote: > [ scratches head ... ] I don't see the problem. AFAICS the "verify > packet" code is just looking at local storage. Where is the spoofing > possibility, and why would delaying the socket close accomplish > anything?
Looking at local storage isn't the issue. There is no buffer overflow or any problem related to local storage. The issue is that the "verify packet code" treats malformed packets as failed authentication. Since an attacker can easily send malformed packets, he can force authentication to fail. The code then stops looking for a *good* response, so the real "authentication succeeded" message is ignored. Similarly, the code checks the signature of the response, as required. But it also treats "bad signature" as failed authentication. This is despite the requirements of RFC 2865 Section 4.2 (among others): ... The Response Authenticator field MUST contain the correct response for the pending Access-Request. Invalid packets are silently discarded. i.e. the invalid packet should be ignored, and the socket left open, so that it can continue to look for the *good* response. I've attached a set of patches which gradually re-arrange the code so that it ignores invalid packets, and keeps trying to read a "good" response until the timer expires. I've also changed the messages saying "bad packet" from 'errors' to 'warnings'. Being attacked isn't an error. :) Alan DeKok.
>From 4d710886f380359b461f6a767fd4b7099f545cce Mon Sep 17 00:00:00 2001 From: Alan T. DeKok <al...@freeradius.org> Date: Thu, 30 Sep 2010 18:18:01 +0200 Subject: [PATCH 1/4] Move RADIUS verify checks to a common function This is in preparation for treating bad packets as errors to ignore, rather than treating them as rejected authentication. If bad packets are treated as reject, then an attacker can force authentication failure by flooding the port with fake replies. --- src/backend/libpq/auth.c | 126 +++++++++++++++++++++++++-------------------- 1 files changed, 70 insertions(+), 56 deletions(-) diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 5d82ad1..9b8ce12 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -2592,6 +2592,71 @@ radius_add_attribute(radius_packet *packet, uint8 type, const unsigned char *dat packet->length += attr->length; } +static int radius_verify(radius_packet *packet, radius_packet *receivepacket, + int packetlength, Port *port) +{ + uint8 *cryptvector; + uint8 calculated_vector[RADIUS_VECTOR_LENGTH]; + + if (packetlength < RADIUS_HEADER_LENGTH) + { + ereport(LOG, + (errmsg("RADIUS response too short: %i", packetlength))); + return STATUS_ERROR; + } + + if (packetlength != ntohs(receivepacket->length)) + { + ereport(LOG, + (errmsg("RADIUS response has corrupt length: %i (actual length %i)", + ntohs(receivepacket->length), packetlength))); + return STATUS_ERROR; + } + + if (packet->id != receivepacket->id) + { + ereport(LOG, + (errmsg("RADIUS response is to a different request: %i (should be %i)", + receivepacket->id, packet->id))); + return STATUS_ERROR; + } + + /* + * Verify the response authenticator, which is calculated as + * MD5(Code+ID+Length+RequestAuthenticator+Attributes+Secret) + */ + cryptvector = palloc(packetlength + strlen(port->hba->radiussecret)); + + memcpy(cryptvector, receivepacket, 4); /* code+id+length */ + memcpy(cryptvector + 4, packet->vector, RADIUS_VECTOR_LENGTH); /* request + * authenticator, from + * original packet */ + if (packetlength > RADIUS_HEADER_LENGTH) /* there may be no attributes + * at all */ + memcpy(cryptvector + RADIUS_HEADER_LENGTH, receivepacket + 1, packetlength - RADIUS_HEADER_LENGTH); + memcpy(cryptvector + packetlength, port->hba->radiussecret, strlen(port->hba->radiussecret)); + + if (!pg_md5_binary(cryptvector, + packetlength + strlen(port->hba->radiussecret), + calculated_vector)) + { + ereport(LOG, + (errmsg("could not perform MD5 encryption of received packet"))); + pfree(cryptvector); + return STATUS_ERROR; + } + pfree(cryptvector); + + if (memcmp(receivepacket->vector, calculated_vector, RADIUS_VECTOR_LENGTH) != 0) + { + ereport(LOG, + (errmsg("RADIUS response has incorrect MD5 signature"))); + return STATUS_ERROR; + } + + return STATUS_OK; +} + static int CheckRADIUSAuth(Port *port) { @@ -2605,6 +2670,7 @@ CheckRADIUSAuth(Port *port) uint8 *cryptvector; uint8 encryptedpassword[RADIUS_VECTOR_LENGTH]; int packetlength; + int verify_result; pgsocket sock; #ifdef HAVE_IPV6 @@ -2841,62 +2907,10 @@ CheckRADIUSAuth(Port *port) return STATUS_ERROR; } - if (packetlength < RADIUS_HEADER_LENGTH) - { - ereport(LOG, - (errmsg("RADIUS response too short: %i", packetlength))); - return STATUS_ERROR; - } - - if (packetlength != ntohs(receivepacket->length)) - { - ereport(LOG, - (errmsg("RADIUS response has corrupt length: %i (actual length %i)", - ntohs(receivepacket->length), packetlength))); - return STATUS_ERROR; - } - - if (packet->id != receivepacket->id) - { - ereport(LOG, - (errmsg("RADIUS response is to a different request: %i (should be %i)", - receivepacket->id, packet->id))); - return STATUS_ERROR; - } - - /* - * Verify the response authenticator, which is calculated as - * MD5(Code+ID+Length+RequestAuthenticator+Attributes+Secret) - */ - cryptvector = palloc(packetlength + strlen(port->hba->radiussecret)); - - memcpy(cryptvector, receivepacket, 4); /* code+id+length */ - memcpy(cryptvector + 4, packet->vector, RADIUS_VECTOR_LENGTH); /* request - * authenticator, from - * original packet */ - if (packetlength > RADIUS_HEADER_LENGTH) /* there may be no attributes - * at all */ - memcpy(cryptvector + RADIUS_HEADER_LENGTH, receive_buffer + RADIUS_HEADER_LENGTH, packetlength - RADIUS_HEADER_LENGTH); - memcpy(cryptvector + packetlength, port->hba->radiussecret, strlen(port->hba->radiussecret)); - - if (!pg_md5_binary(cryptvector, - packetlength + strlen(port->hba->radiussecret), - encryptedpassword)) - { - ereport(LOG, - (errmsg("could not perform MD5 encryption of received packet"))); - pfree(cryptvector); - return STATUS_ERROR; - } - pfree(cryptvector); - - if (memcmp(receivepacket->vector, encryptedpassword, RADIUS_VECTOR_LENGTH) != 0) - { - ereport(LOG, - (errmsg("RADIUS response has incorrect MD5 signature"))); - return STATUS_ERROR; - } - + verify_result = radius_verify(packet, receivepacket, packetlength, port); + if (verify_result != STATUS_OK) + return verify_result; + if (receivepacket->code == RADIUS_ACCESS_ACCEPT) return STATUS_OK; else if (receivepacket->code == RADIUS_ACCESS_REJECT) -- 1.6.4.2
>From f989b717a7c2389c07b488405b8e13b7136a6b2f Mon Sep 17 00:00:00 2001 From: Alan T. DeKok <al...@freeradius.org> Date: Fri, 1 Oct 2010 09:18:29 +0200 Subject: [PATCH 2/4] Move read / verify RADIUS packet code into the loop. This means that for the most part, it will keep reading packets until it receives a valid response. There are still situations where it will return errors, but those will be fixed in later commits. --- src/backend/libpq/auth.c | 53 ++++++++++++++++++++++----------------------- 1 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 9b8ce12..4c2d016 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -2849,7 +2849,7 @@ CheckRADIUSAuth(Port *port) FD_ZERO(&fdset); FD_SET(sock, &fdset); - while (true) + do { r = select(sock + 1, &fdset, NULL, NULL, &timeout); if (r < 0) @@ -2871,43 +2871,42 @@ CheckRADIUSAuth(Port *port) return STATUS_ERROR; } - /* else we actually have a packet ready to read */ - break; - } - - /* Read the response packet */ - addrsize = sizeof(remoteaddr); - packetlength = recvfrom(sock, receive_buffer, RADIUS_BUFFER_SIZE, 0, - (struct sockaddr *) & remoteaddr, &addrsize); - if (packetlength < 0) - { - ereport(LOG, + /* Read the response packet */ + addrsize = sizeof(remoteaddr); + packetlength = recvfrom(sock, receive_buffer, RADIUS_BUFFER_SIZE, 0, + (struct sockaddr *) & remoteaddr, &addrsize); + if (packetlength < 0) + { + ereport(LOG, (errmsg("could not read RADIUS response: %m"))); - closesocket(sock); - return STATUS_ERROR; - } - - closesocket(sock); + closesocket(sock); + return STATUS_ERROR; + } #ifdef HAVE_IPV6 - if (remoteaddr.sin6_port != htons(port->hba->radiusport)) + if (remoteaddr.sin6_port != htons(port->hba->radiusport)) #else - if (remoteaddr.sin_port != htons(port->hba->radiusport)) + if (remoteaddr.sin_port != htons(port->hba->radiusport)) #endif - { + { #ifdef HAVE_IPV6 - ereport(LOG, - (errmsg("RADIUS response was sent from incorrect port: %i", + ereport(LOG, + (errmsg("RADIUS response was sent from incorrect port: %i", ntohs(remoteaddr.sin6_port)))); #else - ereport(LOG, - (errmsg("RADIUS response was sent from incorrect port: %i", + ereport(LOG, + (errmsg("RADIUS response was sent from incorrect port: %i", ntohs(remoteaddr.sin_port)))); #endif - return STATUS_ERROR; - } + return STATUS_ERROR; + } + + } while ((verify_result = radius_verify(packet, + receivepacket, packetlength, + port)) != STATUS_OK); + + closesocket(sock); - verify_result = radius_verify(packet, receivepacket, packetlength, port); if (verify_result != STATUS_OK) return verify_result; -- 1.6.4.2
>From 596e55bc24d9fdc77353837641c48cb53e17e7b2 Mon Sep 17 00:00:00 2001 From: Alan T. DeKok <al...@freeradius.org> Date: Fri, 1 Oct 2010 09:22:32 +0200 Subject: [PATCH 3/4] Turn errors into warnings for invalid RADIUS packets We should log a warning for bad packets, as they may come from an attacker. But they are not errors. --- src/backend/libpq/auth.c | 38 +++++++++++++++++++------------------- 1 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 4c2d016..805db0e 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -2600,24 +2600,24 @@ static int radius_verify(radius_packet *packet, radius_packet *receivepacket, if (packetlength < RADIUS_HEADER_LENGTH) { - ereport(LOG, - (errmsg("RADIUS response too short: %i", packetlength))); + elog(WARNING, + "RADIUS response too short: %i", packetlength); return STATUS_ERROR; } if (packetlength != ntohs(receivepacket->length)) { - ereport(LOG, - (errmsg("RADIUS response has corrupt length: %i (actual length %i)", - ntohs(receivepacket->length), packetlength))); + elog(WARNING, + "RADIUS response has corrupt length: %i (actual length %i)", + ntohs(receivepacket->length), packetlength); return STATUS_ERROR; } if (packet->id != receivepacket->id) { - ereport(LOG, - (errmsg("RADIUS response is to a different request: %i (should be %i)", - receivepacket->id, packet->id))); + elog(WARNING, + "RADIUS response is to a different request: %i (should be %i)", + receivepacket->id, packet->id); return STATUS_ERROR; } @@ -2640,8 +2640,8 @@ static int radius_verify(radius_packet *packet, radius_packet *receivepacket, packetlength + strlen(port->hba->radiussecret), calculated_vector)) { - ereport(LOG, - (errmsg("could not perform MD5 encryption of received packet"))); + elog(WARNING, + "could not perform MD5 encryption of received packet"); pfree(cryptvector); return STATUS_ERROR; } @@ -2649,8 +2649,8 @@ static int radius_verify(radius_packet *packet, radius_packet *receivepacket, if (memcmp(receivepacket->vector, calculated_vector, RADIUS_VECTOR_LENGTH) != 0) { - ereport(LOG, - (errmsg("RADIUS response has incorrect MD5 signature"))); + elog(WARNING, + "RADIUS response has incorrect MD5 signature"); return STATUS_ERROR; } @@ -2890,15 +2890,15 @@ CheckRADIUSAuth(Port *port) #endif { #ifdef HAVE_IPV6 - ereport(LOG, - (errmsg("RADIUS response was sent from incorrect port: %i", - ntohs(remoteaddr.sin6_port)))); + elog(WARNING, + "RADIUS response was sent from incorrect port: %i", + ntohs(remoteaddr.sin6_port)); #else - ereport(LOG, - (errmsg("RADIUS response was sent from incorrect port: %i", - ntohs(remoteaddr.sin_port)))); + elog(WARNING, + "RADIUS response was sent from incorrect port: %i", + ntohs(remoteaddr.sin_port)); #endif - return STATUS_ERROR; + continue; } } while ((verify_result = radius_verify(packet, -- 1.6.4.2
>From 79c0326eb8e17564d07ff61552fcfc016b82f67d Mon Sep 17 00:00:00 2001 From: Alan T. DeKok <al...@freeradius.org> Date: Fri, 1 Oct 2010 09:39:44 +0200 Subject: [PATCH 4/4] Recalculate RADIUS select() timeout if we receive a bad packet. we wait for RADIUS_TIMEOUT after sending one packet, and we do not re-transmit the packet. See RFC 5080, Section 2.2.1 for a recommended retransmission algorithm. --- src/backend/libpq/auth.c | 48 ++++++++++++++++++++++++++++++++++++++++----- 1 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 805db0e..accc26b 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -2657,6 +2657,25 @@ static int radius_verify(radius_packet *packet, radius_packet *receivepacket, return STATUS_OK; } +static void tv_sub(struct timeval *end, struct timeval *start, + struct timeval *elapsed) +{ + elapsed->tv_sec = end->tv_sec - start->tv_sec; + if (elapsed->tv_sec > 0) { + elapsed->tv_sec--; + elapsed->tv_usec = USECS_PER_SEC; + } else { + elapsed->tv_usec = 0; + } + elapsed->tv_usec += end->tv_usec; + elapsed->tv_usec -= start->tv_usec; + + if (elapsed->tv_usec >= USECS_PER_SEC) { + elapsed->tv_usec -= USECS_PER_SEC; + elapsed->tv_sec++; + } +} + static int CheckRADIUSAuth(Port *port) { @@ -2685,7 +2704,7 @@ CheckRADIUSAuth(Port *port) char portstr[128]; ACCEPT_TYPE_ARG3 addrsize; fd_set fdset; - struct timeval timeout; + struct timeval timeout, now, endtime; int i, r; @@ -2843,14 +2862,31 @@ CheckRADIUSAuth(Port *port) /* Don't need the server address anymore */ pg_freeaddrinfo_all(hint.ai_family, serveraddrs); - /* Wait for a response */ - timeout.tv_sec = RADIUS_TIMEOUT; - timeout.tv_usec = 0; - FD_ZERO(&fdset); - FD_SET(sock, &fdset); + /* Find out when we need to stop */ + gettimeofday(&endtime, NULL); + endtime.tv_sec += RADIUS_TIMEOUT; do { + /* Wait for a response */ + gettimeofday(&now, NULL); + + /* the select may have finished just before the timeout */ + if ((now.tv_sec > endtime.tv_sec) || + ((now.tv_sec == endtime.tv_sec) && + (now.tv_usec > endtime.tv_usec))) + { + ereport(LOG, + (errmsg("timeout waiting for RADIUS response"))); + closesocket(sock); + return STATUS_ERROR; + } + + /* calculate how long we need to wait before "end time" */ + tv_sub(&endtime, &now, &timeout); + FD_ZERO(&fdset); + FD_SET(sock, &fdset); + r = select(sock + 1, &fdset, NULL, NULL, &timeout); if (r < 0) { -- 1.6.4.2
-- Sent via pgsql-bugs mailing list (pgsql-bugs@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-bugs