On Tue, Oct 5, 2010 at 11:01, Magnus Hagander <mag...@hagander.net> wrote:
> On Sun, Oct 3, 2010 at 18:30, Alan T DeKok <al...@freeradius.org> wrote:
>> Tom Lane wrote:
>>> Hm ... seems to me that is a network security problem, not our problem.
>>> Who's to say one of the spoofed packets won't pass verification?
>>
>>  The packets are signed with a shared key.  Passing verification means
>> either the attacker knows the key, or the attacker has broken MD5 in
>> ways that are currently unknown.
>>
>>> If you want to change it, I won't stand in the way, but I have real
>>> doubts about both the credibility of this threat and the usefulness
>>> of the proposed fix.
>>
>>  The credibility of the threat is high.  Anyone can trivially send a
>> packet which will cause authentication to fail.  This is a DoS attack.
>
> I don't agree about how high it is - unless I misunderstand the
> wording. You still need to have unfiltered access to the network that
> the database server is on (unlikely) and you need to guess/bruteforce
> the port (using bruteforce not really hard, but likely to be detected
> by an IDS pretty quickly)
>
> It is definitely an opportunity for a DoS attack though, so it should be 
> fixed.
>
> I find your suggested patches kind of hard to read posted inline that
> way - any chance you can repost as attachment or publish it as a git
> repository I can fetch from?

Actually, nevermind that one. Here's a patch I worked up from your
description, and that turns out to be fairly similar to yours in what
it does I think - except I'm not rearranging the code into a separate
function. We already have a while-loop.

See attached context diff, and I've also included a diff without
whitespace changes since the majority of the diff is otherwise coming
from indenting the code one tab...

(so far untested, I seem to have deleted my test-instance of the
radius server, but I figured I should post my attempt anyway)

Also, my patch does not change from log to warning - note that warning
is actually *below* log when it comes to the logfile (see
log_min_messages comments in postgresql.conf). I keep making that
mistake myself...

-- 
 Magnus Hagander
 Me: http://www.hagander.net/
 Work: http://www.redpill-linpro.com/
*** a/src/backend/libpq/auth.c
--- b/src/backend/libpq/auth.c
***************
*** 2619,2625 **** CheckRADIUSAuth(Port *port)
  	char		portstr[128];
  	ACCEPT_TYPE_ARG3 addrsize;
  	fd_set		fdset;
! 	struct timeval timeout;
  	int			i,
  				r;
  
--- 2619,2625 ----
  	char		portstr[128];
  	ACCEPT_TYPE_ARG3 addrsize;
  	fd_set		fdset;
! 	struct timeval endtime;
  	int			i,
  				r;
  
***************
*** 2777,2790 **** 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);
  
  	while (true)
  	{
  		r = select(sock + 1, &fdset, NULL, NULL, &timeout);
  		if (r < 0)
  		{
--- 2777,2812 ----
  	/* Don't need the server address anymore */
  	pg_freeaddrinfo_all(hint.ai_family, serveraddrs);
  
! 	/*
! 	 * Figure out at what time we should time out. We can't just use
! 	 * a single call to select() with a timeout, since somebody can
! 	 * be sending invalid packets to our port thus causing us to
! 	 * retry in a loop and never time out.
! 	 */
! 	gettimeofday(&endtime, NULL);
! 	endtime.tv_sec += RADIUS_TIMEOUT;
  
  	while (true)
  	{
+ 		struct timeval timeout;
+ 		struct timeval now;
+ 		int64 timeoutval;
+ 
+ 		gettimeofday(&now, NULL);
+ 		timeoutval = (endtime.tv_sec * 1000000 + endtime.tv_usec) - (now.tv_sec * 1000000 + now.tv_usec);
+ 		if (timeoutval <= 0)
+ 		{
+ 			ereport(LOG,
+ 					(errmsg("timeout waiting for RADIUS response")));
+ 			closesocket(sock);
+ 			return STATUS_ERROR;
+ 		}
+ 		timeout.tv_sec = timeoutval / 1000000;
+ 		timeout.tv_usec = timeoutval % 1000000;
+ 
+ 		FD_ZERO(&fdset);
+ 		FD_SET(sock, &fdset);
+ 
  		r = select(sock + 1, &fdset, NULL, NULL, &timeout);
  		if (r < 0)
  		{
***************
*** 2805,2911 **** 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,
! 				(errmsg("could not read RADIUS response: %m")));
! 		closesocket(sock);
! 		return STATUS_ERROR;
! 	}
  
! 	closesocket(sock);
  
  #ifdef HAVE_IPV6
! 	if (remoteaddr.sin6_port != htons(port->hba->radiusport))
  #else
! 	if (remoteaddr.sin_port != htons(port->hba->radiusport))
  #endif
! 	{
  #ifdef HAVE_IPV6
! 		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",
! 						ntohs(remoteaddr.sin_port))));
  #endif
! 		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;
! 	}
  
! 	if (receivepacket->code == RADIUS_ACCESS_ACCEPT)
! 		return STATUS_OK;
! 	else if (receivepacket->code == RADIUS_ACCESS_REJECT)
! 		return STATUS_ERROR;
! 	else
! 	{
! 		ereport(LOG,
! 			 (errmsg("RADIUS response has invalid code (%i) for user \"%s\"",
! 					 receivepacket->code, port->user_name)));
! 		return STATUS_ERROR;
! 	}
  }
--- 2827,2943 ----
  			return STATUS_ERROR;
  		}
  
! 		/*
! 		 * Attempt to read the response packet, and verify the contents.
! 		 *
! 		 * Any packet that's not actually a RADIUS packet, or otherwise
! 		 * does not validate as an explicit reject, is just ignored and
! 		 * we retry for another packet (until we reach the timeout). This
! 		 * is to avoid the possibility to denial-of-service the login by
! 		 * flooding the server with invalid packets on the port that
! 		 * we're expecting the RADIUS response on.
! 		 */
  
! 		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")));
! 			return STATUS_ERROR;
! 		}
  
  #ifdef HAVE_IPV6
! 		if (remoteaddr.sin6_port != htons(port->hba->radiusport))
  #else
! 		if (remoteaddr.sin_port != htons(port->hba->radiusport))
  #endif
! 		{
  #ifdef HAVE_IPV6
! 			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",
! 							ntohs(remoteaddr.sin_port))));
  #endif
! 			continue;
! 		}
  
! 		if (packetlength < RADIUS_HEADER_LENGTH)
! 		{
! 			ereport(LOG,
! 					(errmsg("RADIUS response too short: %i", packetlength)));
! 			continue;
! 		}
  
! 		if (packetlength != ntohs(receivepacket->length))
! 		{
! 			ereport(LOG,
! 					(errmsg("RADIUS response has corrupt length: %i (actual length %i)",
! 							ntohs(receivepacket->length), packetlength)));
! 			continue;
! 		}
  
! 		if (packet->id != receivepacket->id)
! 		{
! 			ereport(LOG,
! 					(errmsg("RADIUS response is to a different request: %i (should be %i)",
! 							receivepacket->id, packet->id)));
! 			continue;
! 		}
  
! 		/*
! 		 * 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);
! 			continue;
! 		}
  		pfree(cryptvector);
  
! 		if (memcmp(receivepacket->vector, encryptedpassword, RADIUS_VECTOR_LENGTH) != 0)
! 		{
! 			ereport(LOG,
! 					(errmsg("RADIUS response has incorrect MD5 signature")));
! 			continue;
! 		}
  
! 		if (receivepacket->code == RADIUS_ACCESS_ACCEPT)
! 		{
! 			closesocket(sock);
! 			return STATUS_OK;
! 		}
! 		else if (receivepacket->code == RADIUS_ACCESS_REJECT)
! 		{
! 			closesocket(sock);
! 			return STATUS_ERROR;
! 		}
! 		else
! 		{
! 			ereport(LOG,
! 					(errmsg("RADIUS response has invalid code (%i) for user \"%s\"",
! 							receivepacket->code, port->user_name)));
! 			continue;
! 		}
! 	} /* while (true) */
  }
*** a/src/backend/libpq/auth.c
--- b/src/backend/libpq/auth.c
***************
*** 2619,2625 **** CheckRADIUSAuth(Port *port)
  	char		portstr[128];
  	ACCEPT_TYPE_ARG3 addrsize;
  	fd_set		fdset;
! 	struct timeval timeout;
  	int			i,
  				r;
  
--- 2619,2625 ----
  	char		portstr[128];
  	ACCEPT_TYPE_ARG3 addrsize;
  	fd_set		fdset;
! 	struct timeval endtime;
  	int			i,
  				r;
  
***************
*** 2777,2790 **** 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);
  
  	while (true)
  	{
  		r = select(sock + 1, &fdset, NULL, NULL, &timeout);
  		if (r < 0)
  		{
--- 2777,2812 ----
  	/* Don't need the server address anymore */
  	pg_freeaddrinfo_all(hint.ai_family, serveraddrs);
  
! 	/*
! 	 * Figure out at what time we should time out. We can't just use
! 	 * a single call to select() with a timeout, since somebody can
! 	 * be sending invalid packets to our port thus causing us to
! 	 * retry in a loop and never time out.
! 	 */
! 	gettimeofday(&endtime, NULL);
! 	endtime.tv_sec += RADIUS_TIMEOUT;
  
  	while (true)
  	{
+ 		struct timeval timeout;
+ 		struct timeval now;
+ 		int64 timeoutval;
+ 
+ 		gettimeofday(&now, NULL);
+ 		timeoutval = (endtime.tv_sec * 1000000 + endtime.tv_usec) - (now.tv_sec * 1000000 + now.tv_usec);
+ 		if (timeoutval <= 0)
+ 		{
+ 			ereport(LOG,
+ 					(errmsg("timeout waiting for RADIUS response")));
+ 			closesocket(sock);
+ 			return STATUS_ERROR;
+ 		}
+ 		timeout.tv_sec = timeoutval / 1000000;
+ 		timeout.tv_usec = timeoutval % 1000000;
+ 
+ 		FD_ZERO(&fdset);
+ 		FD_SET(sock, &fdset);
+ 
  		r = select(sock + 1, &fdset, NULL, NULL, &timeout);
  		if (r < 0)
  		{
***************
*** 2805,2815 **** 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);
--- 2827,2843 ----
  			return STATUS_ERROR;
  		}
  
! 		/*
! 		 * Attempt to read the response packet, and verify the contents.
! 		 *
! 		 * Any packet that's not actually a RADIUS packet, or otherwise
! 		 * does not validate as an explicit reject, is just ignored and
! 		 * we retry for another packet (until we reach the timeout). This
! 		 * is to avoid the possibility to denial-of-service the login by
! 		 * flooding the server with invalid packets on the port that
! 		 * we're expecting the RADIUS response on.
! 		 */
  
  	addrsize = sizeof(remoteaddr);
  	packetlength = recvfrom(sock, receive_buffer, RADIUS_BUFFER_SIZE, 0,
  							(struct sockaddr *) & remoteaddr, &addrsize);
***************
*** 2817,2828 **** CheckRADIUSAuth(Port *port)
  	{
  		ereport(LOG,
  				(errmsg("could not read RADIUS response: %m")));
- 		closesocket(sock);
  		return STATUS_ERROR;
  	}
  
- 	closesocket(sock);
- 
  #ifdef HAVE_IPV6
  	if (remoteaddr.sin6_port != htons(port->hba->radiusport))
  #else
--- 2845,2853 ----
***************
*** 2838,2851 **** CheckRADIUSAuth(Port *port)
  				(errmsg("RADIUS response was sent from incorrect port: %i",
  						ntohs(remoteaddr.sin_port))));
  #endif
! 		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))
--- 2863,2876 ----
  				(errmsg("RADIUS response was sent from incorrect port: %i",
  						ntohs(remoteaddr.sin_port))));
  #endif
! 			continue;
  	}
  
  	if (packetlength < RADIUS_HEADER_LENGTH)
  	{
  		ereport(LOG,
  				(errmsg("RADIUS response too short: %i", packetlength)));
! 			continue;
  	}
  
  	if (packetlength != ntohs(receivepacket->length))
***************
*** 2853,2859 **** CheckRADIUSAuth(Port *port)
  		ereport(LOG,
  		 (errmsg("RADIUS response has corrupt length: %i (actual length %i)",
  				 ntohs(receivepacket->length), packetlength)));
! 		return STATUS_ERROR;
  	}
  
  	if (packet->id != receivepacket->id)
--- 2878,2884 ----
  		ereport(LOG,
  		 (errmsg("RADIUS response has corrupt length: %i (actual length %i)",
  				 ntohs(receivepacket->length), packetlength)));
! 			continue;
  	}
  
  	if (packet->id != receivepacket->id)
***************
*** 2861,2867 **** CheckRADIUSAuth(Port *port)
  		ereport(LOG,
  				(errmsg("RADIUS response is to a different request: %i (should be %i)",
  						receivepacket->id, packet->id)));
! 		return STATUS_ERROR;
  	}
  
  	/*
--- 2886,2892 ----
  		ereport(LOG,
  				(errmsg("RADIUS response is to a different request: %i (should be %i)",
  						receivepacket->id, packet->id)));
! 			continue;
  	}
  
  	/*
***************
*** 2886,2892 **** CheckRADIUSAuth(Port *port)
  		ereport(LOG,
  			(errmsg("could not perform MD5 encryption of received packet")));
  		pfree(cryptvector);
! 		return STATUS_ERROR;
  	}
  	pfree(cryptvector);
  
--- 2911,2917 ----
  		ereport(LOG,
  			(errmsg("could not perform MD5 encryption of received packet")));
  		pfree(cryptvector);
! 			continue;
  	}
  	pfree(cryptvector);
  
***************
*** 2894,2911 **** CheckRADIUSAuth(Port *port)
  	{
  		ereport(LOG,
  				(errmsg("RADIUS response has incorrect MD5 signature")));
! 		return STATUS_ERROR;
  	}
  
  	if (receivepacket->code == RADIUS_ACCESS_ACCEPT)
  		return STATUS_OK;
  	else if (receivepacket->code == RADIUS_ACCESS_REJECT)
  		return STATUS_ERROR;
  	else
  	{
  		ereport(LOG,
  			 (errmsg("RADIUS response has invalid code (%i) for user \"%s\"",
  					 receivepacket->code, port->user_name)));
! 		return STATUS_ERROR;
  	}
  }
--- 2919,2943 ----
  	{
  		ereport(LOG,
  				(errmsg("RADIUS response has incorrect MD5 signature")));
! 			continue;
  	}
  
  	if (receivepacket->code == RADIUS_ACCESS_ACCEPT)
+ 		{
+ 			closesocket(sock);
  		return STATUS_OK;
+ 		}
  	else if (receivepacket->code == RADIUS_ACCESS_REJECT)
+ 		{
+ 			closesocket(sock);
  		return STATUS_ERROR;
+ 		}
  	else
  	{
  		ereport(LOG,
  			 (errmsg("RADIUS response has invalid code (%i) for user \"%s\"",
  					 receivepacket->code, port->user_name)));
! 			continue;
  	}
+ 	} /* while (true) */
  }
-- 
Sent via pgsql-bugs mailing list (pgsql-bugs@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-bugs

Reply via email to